[gtk/wip/matthiasc/lottie-stroke: 6/9] Add an interactive path test




commit b81e56f50794f45feef52085d5a2e942124f8f82
Author: Matthias Clasen <mclasen redhat com>
Date:   Tue Nov 24 14:24:40 2020 -0500

    Add an interactive path test
    
    This one is for interactive exploring of svg paths.
    
    You can enter an SVG path in the entry and hit Enter
    to see how GSK renders it. If you click the button
    in the headerbar, you can see what GTK thinks the
    closest point, tangent and distance are wrt. to the
    mouse position, and the bounding box of the path.
    
    There's also stroke parameters to play with.

 tests/curve2.c    | 592 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 tests/meson.build |   1 +
 2 files changed, 593 insertions(+)
---
diff --git a/tests/curve2.c b/tests/curve2.c
new file mode 100644
index 0000000000..7ae0b943ec
--- /dev/null
+++ b/tests/curve2.c
@@ -0,0 +1,592 @@
+#include <gtk/gtk.h>
+
+#define DEMO_TYPE_WIDGET (demo_widget_get_type ())
+G_DECLARE_FINAL_TYPE (DemoWidget, demo_widget, DEMO, WIDGET, GtkWidget)
+
+struct _DemoWidget
+{
+  GtkWidget parent_instance;
+  GskPath *orig_path;
+  GskPath *path;
+  GskPathMeasure *measure;
+  double x, y;
+  graphene_point_t point;
+  graphene_point_t point2;
+  graphene_vec2_t tangent;
+  double start, end;
+
+  gboolean track;
+  gboolean show_bounding_box;
+  GtkWidget *label;
+
+  gboolean do_stroke;
+  GskPath *stroke_path;
+  GskStroke *stroke;
+  GskPathMeasure *stroke_measure;
+  GskPath *outline_path;
+  GskStroke *outline_stroke;
+  gboolean inside;
+  GskFillRule fill_rule;
+};
+
+struct _DemoWidgetClass
+{
+  GtkWidgetClass parent_class;
+};
+
+G_DEFINE_TYPE (DemoWidget, demo_widget, GTK_TYPE_WIDGET)
+
+static void
+motion (GtkEventControllerMotion *controller,
+        double                    x,
+        double                    y,
+        DemoWidget               *self)
+{
+  if (self->track)
+    {
+      float distance;
+      char *text;
+      float t;
+
+      self->x = x;
+      self->y = y;
+
+      gsk_path_measure_get_closest_point_full (self->measure,
+                                               &GRAPHENE_POINT_INIT (x, y),
+                                               INFINITY,
+                                               &distance,
+                                               &self->point,
+                                               &t,
+                                               &self->tangent);
+
+      gsk_path_measure_get_point (self->measure, t, &self->point2, NULL);
+
+      text = g_strdup_printf ("%.1f", distance);
+      gtk_label_set_label (GTK_LABEL (self->label), text);
+
+      gtk_widget_queue_draw (GTK_WIDGET (self));
+    }
+
+  if (self->do_stroke)
+    {
+      gboolean inside = TRUE;
+
+      inside = gsk_path_measure_in_fill (self->stroke_measure, &GRAPHENE_POINT_INIT (x, y), self->fill_rule);
+
+      if (self->inside != inside)
+        {
+          self->inside = inside;
+          gtk_widget_queue_draw (GTK_WIDGET (self));
+        }
+    }
+}
+
+static void
+demo_widget_init (DemoWidget *self)
+{
+  GtkEventController *controller;
+
+  self->start = 0;
+  self->end = 1;
+
+  self->label = gtk_label_new ("");
+  gtk_widget_set_parent (self->label, GTK_WIDGET (self));
+  gtk_widget_set_halign (self->label, GTK_ALIGN_END);
+  gtk_widget_set_valign (self->label, GTK_ALIGN_START);
+
+  controller = gtk_event_controller_motion_new ();
+  g_signal_connect (controller, "motion", G_CALLBACK (motion), self);
+  gtk_widget_add_controller (GTK_WIDGET (self), controller);
+}
+
+static void
+demo_widget_snapshot (GtkWidget   *widget,
+                      GtkSnapshot *snapshot)
+{
+  DemoWidget *self = DEMO_WIDGET (widget);
+  int width, height;
+  GskStroke *stroke;
+  GskPathBuilder *builder;
+  GskPath *path;
+
+  if (!self->path)
+    return;
+
+  width = gtk_widget_get_width (widget);
+  height = gtk_widget_get_width (widget);
+
+  if (self->do_stroke)
+    {
+      if (self->inside)
+        {
+           gtk_snapshot_push_fill (snapshot, self->stroke_path, self->fill_rule);
+
+           gtk_snapshot_append_color (snapshot,
+                                      &(GdkRGBA){ 1, 0, 1, 0.3},
+                                      &GRAPHENE_RECT_INIT (0, 0, width, height ));
+
+           gtk_snapshot_pop (snapshot);
+        }
+
+      gtk_snapshot_push_fill (snapshot, self->outline_path, GSK_FILL_RULE_WINDING);
+
+      gtk_snapshot_append_color (snapshot,
+                                 &(GdkRGBA){ 0, 0, 0, 0.2},
+                                 &GRAPHENE_RECT_INIT (0, 0, width, height ));
+
+      gtk_snapshot_pop (snapshot);
+
+      stroke = gsk_stroke_new (1);
+      gtk_snapshot_push_stroke (snapshot, self->path, stroke);
+      gsk_stroke_free (stroke);
+
+      gtk_snapshot_append_color (snapshot,
+                                 &(GdkRGBA){ 0, 0, 0, 0.3},
+                                 &GRAPHENE_RECT_INIT (0, 0, width, height ));
+
+      gtk_snapshot_pop (snapshot);
+    }
+  else
+    {
+      gtk_snapshot_push_stroke (snapshot, self->path, self->outline_stroke);
+
+      gtk_snapshot_append_color (snapshot,
+                                 &(GdkRGBA){ 0, 0, 0, 1},
+                                 &GRAPHENE_RECT_INIT (0, 0, width, height ));
+
+      gtk_snapshot_pop (snapshot);
+    }
+
+  if (self->show_bounding_box)
+    {
+      graphene_rect_t bounds;
+
+      if (gsk_path_get_bounds (self->do_stroke ? self->outline_path : self->path, &bounds))
+        {
+          builder = gsk_path_builder_new ();
+
+          gsk_path_builder_add_rect (builder, &bounds);
+
+          path = gsk_path_builder_free_to_path (builder);
+
+          stroke = gsk_stroke_new (1.0);
+          gtk_snapshot_push_stroke (snapshot, path, stroke);
+          gsk_stroke_free (stroke);
+
+          gtk_snapshot_append_color (snapshot,
+                                     &(GdkRGBA){ 0, 0, 0, 0.5},
+                                     &GRAPHENE_RECT_INIT (0, 0, width, height ));
+
+          gtk_snapshot_pop (snapshot);
+
+          gsk_path_unref (path);
+        }
+    }
+
+  if (self->track)
+    {
+      graphene_point_t p;
+
+      p.x = self->point.x + graphene_vec2_get_x (&self->tangent) * 40;
+      p.y = self->point.y + graphene_vec2_get_y (&self->tangent) * 40;
+
+      builder = gsk_path_builder_new ();
+
+      gsk_path_builder_move_to (builder, self->point.x, self->point.y);
+      gsk_path_builder_line_to (builder, p.x, p.y);
+
+      path = gsk_path_builder_free_to_path (builder);
+
+      stroke = gsk_stroke_new (1.0);
+      gtk_snapshot_push_stroke (snapshot, path, stroke);
+      gsk_stroke_free (stroke);
+
+      gtk_snapshot_append_color (snapshot,
+                                 &(GdkRGBA){ 0, 0, 0, 1},
+                                 &GRAPHENE_RECT_INIT (0, 0, width, height ));
+
+      gtk_snapshot_pop (snapshot);
+
+      gsk_path_unref (path);
+
+      builder = gsk_path_builder_new ();
+
+      gsk_path_builder_add_circle (builder, &self->point, 5);
+      gsk_path_builder_add_circle (builder, &p, 2.5);
+      gsk_path_builder_add_circle (builder, &self->point2, 5);
+
+      path = gsk_path_builder_free_to_path (builder);
+
+      gtk_snapshot_push_fill (snapshot, path, 0);
+      gtk_snapshot_append_color (snapshot,
+                                 &(GdkRGBA){ 1, 0, 0, 1},
+                                 &GRAPHENE_RECT_INIT (0, 0, width, height ));
+      gtk_snapshot_pop (snapshot);
+
+      stroke = gsk_stroke_new (1.0);
+      gtk_snapshot_push_stroke (snapshot, path, stroke);
+      gsk_stroke_free (stroke);
+
+      gtk_snapshot_append_color (snapshot,
+                                 &(GdkRGBA){ 0, 0, 0, 1},
+                                 &GRAPHENE_RECT_INIT (0, 0, width, height ));
+
+      gtk_snapshot_pop (snapshot);
+
+      gsk_path_unref (path);
+
+      gtk_widget_snapshot_child (widget, self->label, snapshot);
+    }
+}
+
+static void
+demo_widget_dispose (GObject *object)
+{
+  DemoWidget *self = DEMO_WIDGET (object);
+
+  g_clear_pointer (&self->path, gsk_path_unref);
+  g_clear_pointer (&self->measure, gsk_path_measure_unref);
+  g_clear_pointer (&self->stroke_path, gsk_path_unref);
+  g_clear_pointer (&self->stroke_measure, gsk_path_measure_unref);
+  g_clear_pointer (&self->stroke, gsk_stroke_free);
+  g_clear_pointer (&self->outline_path, gsk_path_unref);
+  g_clear_pointer (&self->outline_stroke, gsk_stroke_free);
+  g_clear_pointer (&self->label, gtk_widget_unparent);
+
+  G_OBJECT_CLASS (demo_widget_parent_class)->dispose (object);
+}
+
+static void
+demo_widget_class_init (DemoWidgetClass *class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
+
+  object_class->dispose = demo_widget_dispose;
+
+  widget_class->snapshot = demo_widget_snapshot;
+
+  gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
+}
+
+static GtkWidget *
+demo_widget_new (void)
+{
+  return g_object_new (DEMO_TYPE_WIDGET, NULL);
+}
+
+static void
+update_outline_path (DemoWidget *self)
+{
+  if (self->stroke_measure)
+    {
+      g_clear_pointer (&self->outline_path, gsk_path_unref);
+      self->outline_path = gsk_path_measure_stroke (self->stroke_measure, self->outline_stroke);
+      gtk_widget_queue_draw (GTK_WIDGET (self));
+    }
+}
+
+static void
+update_stroke_path (DemoWidget *self)
+{
+  g_clear_pointer (&self->stroke_path, gsk_path_unref);
+  g_clear_pointer (&self->stroke_measure, gsk_path_measure_unref);
+
+  if (self->do_stroke)
+    {
+      self->stroke_path = gsk_path_measure_stroke (self->measure, self->stroke);
+      self->stroke_measure = gsk_path_measure_new (self->stroke_path);
+      update_outline_path (self);
+    }
+
+  gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+static void
+update_path (DemoWidget *self)
+{
+  g_clear_pointer (&self->path, gsk_path_unref);
+  g_clear_pointer (&self->measure, gsk_path_measure_unref);
+
+  if (self->start > 0 || self->end < 1)
+    {
+      GskPathMeasure *measure;
+      GskPathBuilder *builder;
+      float length;
+
+      measure = gsk_path_measure_new (self->orig_path);
+      length = gsk_path_measure_get_length (measure);
+      builder = gsk_path_builder_new ();
+      gsk_path_builder_add_segment (builder, measure, self->start * length, self->end * length);
+      self->path = gsk_path_builder_free_to_path (builder);
+      gsk_path_measure_unref (measure);
+    }
+  else
+    self->path = gsk_path_ref (self->orig_path);
+
+  self->measure = gsk_path_measure_new (self->path);
+
+  update_stroke_path (self);
+
+  gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+static void
+demo_widget_set_path (DemoWidget *self,
+                      GskPath    *path)
+{
+  g_clear_pointer (&self->orig_path, gsk_path_unref);
+  self->orig_path = gsk_path_ref (path);
+
+  update_path (self);
+}
+
+static void
+activate (GtkEntry *entry,
+          DemoWidget *demo)
+{
+  GskPath *path;
+
+  path = gsk_path_parse (gtk_editable_get_text (GTK_EDITABLE (entry)));
+  if (path)
+    {
+      demo_widget_set_path (demo, path);
+      gsk_path_unref (path);
+    }
+}
+
+static void
+init_demo (DemoWidget  *demo,
+           GtkEditable *editable)
+{
+  GskPathBuilder *builder;
+  GskPath *path;
+  char *string;
+
+  builder = gsk_path_builder_new ();
+  gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (150, 150), 100);
+  gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (100, 100, 100, 100));
+  gsk_path_builder_move_to (builder, 300, 150);
+  gsk_path_builder_curve_to (builder, 300, 50, 400, 50, 400, 150);
+  gsk_path_builder_curve_to (builder, 400, 250, 500, 250, 500, 150);
+  gsk_path_builder_line_to (builder, 600, 150);
+  gsk_path_builder_line_to (builder, 530, 190);
+  path = gsk_path_builder_free_to_path (builder);
+
+  demo_widget_set_path (demo, path);
+
+  string = gsk_path_to_string (path);
+  gtk_editable_set_text (editable, string);
+  g_free (string);
+  gsk_path_unref (path);
+
+  demo->stroke = gsk_stroke_new (20);
+  demo->outline_stroke = gsk_stroke_new (1);
+}
+
+static void
+track_toggled (GtkCheckButton *button,
+               DemoWidget      *self)
+{
+  self->track = gtk_check_button_get_active (button);
+  gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+static void
+bb_toggled (GtkCheckButton *button,
+            DemoWidget      *self)
+{
+  self->show_bounding_box = gtk_check_button_get_active (button);
+  gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+static void
+stroke_toggled (GtkCheckButton *button,
+                DemoWidget      *self)
+{
+  self->do_stroke = gtk_check_button_get_active (button);
+  update_stroke_path (self);
+  gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+static GtkWidget *start_scale;
+static GtkWidget *end_scale;
+
+static void
+range_changed (GtkRange   *range,
+               DemoWidget *self)
+{
+  double start, end;
+
+  if (range == GTK_RANGE (start_scale))
+    {
+      start = gtk_range_get_value (range);
+      end = MAX (start, gtk_range_get_value (GTK_RANGE (end_scale)));
+      gtk_range_set_value (GTK_RANGE (end_scale), end);
+    }
+  else
+    {
+      end = gtk_range_get_value (range);
+      start = MIN (end, gtk_range_get_value (GTK_RANGE (start_scale)));
+      gtk_range_set_value (GTK_RANGE (start_scale), start);
+    }
+
+  self->start = start;
+  self->end = end;
+
+  update_path (self);
+}
+
+static void
+fill_rule_changed (GtkDropDown *combo,
+                   GParamSpec  *pspec,
+                   DemoWidget  *self)
+{
+  self->fill_rule = (GskFillRule)gtk_drop_down_get_selected (combo);
+  gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+static void
+cap_changed (GtkDropDown *combo,
+             GParamSpec  *pspec,
+             DemoWidget  *self)
+{
+  gsk_stroke_set_line_cap (self->stroke, (GskLineCap)gtk_drop_down_get_selected (combo));
+  update_stroke_path (self);
+}
+
+static void
+join_changed (GtkDropDown *combo,
+              GParamSpec  *pspec,
+              DemoWidget  *self)
+{
+  gsk_stroke_set_line_join (self->stroke, (GskLineJoin)gtk_drop_down_get_selected (combo));
+  update_stroke_path (self);
+}
+
+static void
+limit_changed (GtkSpinButton *spin,
+               DemoWidget    *self)
+{
+  gsk_stroke_set_miter_limit (self->stroke, gtk_spin_button_get_value (spin));
+  update_stroke_path (self);
+}
+
+static void
+stroke_width_changed (GtkSpinButton *spin,
+                      DemoWidget    *self)
+{
+  gsk_stroke_set_line_width (self->stroke, gtk_spin_button_get_value (spin));
+  update_stroke_path (self);
+}
+
+static void
+line_width_changed (GtkSpinButton *spin,
+                    DemoWidget    *self)
+{
+  gsk_stroke_set_line_width (self->outline_stroke, gtk_spin_button_get_value (spin));
+  update_outline_path (self);
+}
+
+int
+main (int argc, char *argv[])
+{
+  GtkWidget *window, *box, *demo, *entry;
+  GtkWidget *popover, *button, *grid;
+  GtkWidget *header, *toggle, *combo, *spin;
+
+  gtk_init ();
+
+  window = gtk_window_new ();
+  gtk_window_set_default_size (GTK_WINDOW (window), 700, 500);
+  box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+  gtk_window_set_child (GTK_WINDOW (window), box);
+
+  demo = demo_widget_new ();
+  gtk_widget_set_hexpand (demo, TRUE);
+  gtk_widget_set_vexpand (demo, TRUE);
+  gtk_box_append (GTK_BOX (box), demo);
+
+  header = gtk_header_bar_new ();
+  button = gtk_menu_button_new ();
+  gtk_menu_button_set_icon_name (GTK_MENU_BUTTON (button), "emblem-system-symbolic");
+  gtk_header_bar_pack_start (GTK_HEADER_BAR (header), button);
+  gtk_window_set_titlebar (GTK_WINDOW (window), header);
+
+  popover = gtk_popover_new ();
+  gtk_menu_button_set_popover (GTK_MENU_BUTTON (button), popover);
+
+  grid = gtk_grid_new ();
+  gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
+  gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
+  gtk_popover_set_child (GTK_POPOVER (popover), grid);
+
+  toggle = gtk_check_button_new_with_label ("Show closest point");
+  g_signal_connect (toggle, "toggled", G_CALLBACK (track_toggled), demo);
+  gtk_grid_attach (GTK_GRID (grid), toggle, 1, 0, 1, 1);
+
+  toggle = gtk_check_button_new_with_label ("Show bounding box");
+  g_signal_connect (toggle, "toggled", G_CALLBACK (bb_toggled), demo);
+  gtk_grid_attach (GTK_GRID (grid), toggle, 1, 1, 1, 1);
+
+  toggle = gtk_check_button_new_with_label ("Stroke path");
+  g_signal_connect (toggle, "toggled", G_CALLBACK (stroke_toggled), demo);
+  gtk_grid_attach (GTK_GRID (grid), toggle, 1, 2, 1, 1);
+
+  gtk_grid_attach (GTK_GRID (grid), gtk_label_new ("Fill rule"), 0, 3, 1, 1);
+
+  combo = gtk_drop_down_new_from_strings ((const char *[]){"Winding", "Even-Odd", NULL });
+  g_signal_connect (combo, "notify::selected", G_CALLBACK (fill_rule_changed), demo);
+  gtk_grid_attach (GTK_GRID (grid), combo, 1, 3, 1, 1);
+
+  gtk_grid_attach (GTK_GRID (grid), gtk_label_new ("Line cap:"), 0, 4, 1, 1);
+  combo = gtk_drop_down_new_from_strings ((const char *[]){"Butt", "Round", "Square", NULL});
+  g_signal_connect (combo, "notify::selected", G_CALLBACK (cap_changed), demo);
+  gtk_grid_attach (GTK_GRID (grid), combo, 1, 4, 1, 1);
+
+  gtk_grid_attach (GTK_GRID (grid), gtk_label_new ("Line join:"), 0, 5, 1, 1);
+  combo = gtk_drop_down_new_from_strings ((const char *[]){"Miter", "Miter-clip", "Round", "Bevel", NULL});
+  g_signal_connect (combo, "notify::selected", G_CALLBACK (join_changed), demo);
+  gtk_grid_attach (GTK_GRID (grid), combo, 1, 5, 1, 1);
+
+  gtk_grid_attach (GTK_GRID (grid), gtk_label_new ("Miter limit:"), 0, 6, 1, 1);
+  spin = gtk_spin_button_new_with_range (0, 10, 1);
+  gtk_spin_button_set_digits (GTK_SPIN_BUTTON (spin), 1);
+  gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin), 4);
+  g_signal_connect (spin, "value-changed", G_CALLBACK (limit_changed), demo);
+  gtk_grid_attach (GTK_GRID (grid), spin, 1, 6, 1, 1);
+
+  gtk_grid_attach (GTK_GRID (grid), gtk_label_new ("Stroke width:"), 0, 7, 1, 1);
+  spin = gtk_spin_button_new_with_range (1, 40, 1);
+  gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin), 20);
+  g_signal_connect (spin, "value-changed", G_CALLBACK (stroke_width_changed), demo);
+  gtk_grid_attach (GTK_GRID (grid), spin, 1, 7, 1, 1);
+
+  gtk_grid_attach (GTK_GRID (grid), gtk_label_new ("Line width:"), 0, 8, 1, 1);
+  spin = gtk_spin_button_new_with_range (1, 20, 1);
+  gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin), 1);
+  g_signal_connect (spin, "value-changed", G_CALLBACK (line_width_changed), demo);
+  gtk_grid_attach (GTK_GRID (grid), spin, 1, 8, 1, 1);
+
+  entry = gtk_entry_new ();
+  g_signal_connect (entry, "activate", G_CALLBACK (activate), demo);
+  gtk_box_append (GTK_BOX (box), entry);
+
+  start_scale = gtk_scale_new_with_range (GTK_ORIENTATION_HORIZONTAL, 0, 1, 0.1);
+  g_signal_connect (start_scale, "value-changed", G_CALLBACK (range_changed), demo);
+  gtk_box_append (GTK_BOX (box), start_scale);
+
+  end_scale = gtk_scale_new_with_range (GTK_ORIENTATION_HORIZONTAL, 0, 1, 0.1);
+  gtk_range_set_value (GTK_RANGE (end_scale), 1);
+  g_signal_connect (end_scale, "value-changed", G_CALLBACK (range_changed), demo);
+  gtk_box_append (GTK_BOX (box), end_scale);
+
+  init_demo (DEMO_WIDGET (demo), GTK_EDITABLE (entry));
+
+  gtk_window_present (GTK_WINDOW (window));
+
+  while (g_list_model_get_n_items (gtk_window_get_toplevels ()) > 0)
+    g_main_context_iteration (NULL, TRUE);
+
+  return 0;
+}
diff --git a/tests/meson.build b/tests/meson.build
index 51b2311a80..369a22de08 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -1,5 +1,6 @@
 gtk_tests = [
   # testname, optional extra sources
+  ['curve2'],
   ['testupload'],
   ['testtransform'],
   ['testdropdown'],


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]