[gtk/matthiasc/lottie] Play with paths




commit 0d8e0521233e4e20e2a7aa18d0804fbf89a7510c
Author: Matthias Clasen <mclasen redhat com>
Date:   Thu Nov 19 13:41:21 2020 -0500

    Play with paths
    
    Add a simple demo for editing a poly-Bezier curve.

 tests/curve.c     | 377 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 tests/meson.build |   1 +
 tests/simple.c    | 126 ++++++++++++------
 3 files changed, 463 insertions(+), 41 deletions(-)
---
diff --git a/tests/curve.c b/tests/curve.c
new file mode 100644
index 0000000000..2c5c9177a3
--- /dev/null
+++ b/tests/curve.c
@@ -0,0 +1,377 @@
+#include <gtk/gtk.h>
+
+
+#define RADIUS 5
+
+typedef struct
+{
+  GtkWidget parent_instance;
+  graphene_point_t *points;
+  int n_points;
+  int dragged;
+  gboolean edit;
+} DemoWidget;
+
+typedef struct
+{
+  GtkWidgetClass parent_class;
+} DemoWidgetClass;
+
+GType demo_widget_get_type (void) G_GNUC_CONST;
+
+G_DEFINE_TYPE (DemoWidget, demo_widget, GTK_TYPE_WIDGET)
+
+static float
+dist (graphene_point_t *a, graphene_point_t *b)
+{
+  graphene_vec2_t v;
+
+  graphene_vec2_init (&v, a->x - b->x, a->y - b->y);
+  return graphene_vec2_length (&v);
+}
+
+static void
+drag_begin (GtkGestureDrag *gesture,
+            double          start_x,
+            double          start_y,
+            DemoWidget     *self)
+{
+  int i;
+  graphene_point_t p = GRAPHENE_POINT_INIT (start_x, start_y);
+
+  if (self->edit)
+    for (i = 0; i < self->n_points; i++)
+      {
+        if (dist (&self->points[i], &p) < RADIUS)
+          {
+            self->dragged = i;
+            gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
+            gtk_widget_queue_draw (GTK_WIDGET (self));
+            return;
+          }
+      }
+
+  gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
+}
+
+static void
+drag_update (GtkGestureDrag *gesture,
+             double          offset_x,
+             double          offset_y,
+             DemoWidget     *self)
+{
+  double x, y;
+  double dx, dy;
+  graphene_point_t *c, *p;
+  double a;
+  double d;
+
+  if (self->dragged == -1)
+    return;
+
+  gtk_gesture_drag_get_start_point (gesture, &x, &y);
+
+  x += offset_x;
+  y += offset_y;
+
+  dx = x - self->points[self->dragged].x;
+  dy = y - self->points[self->dragged].y;
+
+  self->points[self->dragged].x += dx;
+  self->points[self->dragged].y += dy;
+
+  if (self->dragged % 3 == 0)
+    {
+      /* point is on curve */
+      self->points[(self->dragged - 1 + self->n_points) % self->n_points].x += dx;
+      self->points[(self->dragged - 1 + self->n_points) % self->n_points].y += dy;
+
+      self->points[(self->dragged + 1) % self->n_points].x += dx;
+      self->points[(self->dragged + 1) % self->n_points].y += dy;
+    }
+  else
+    {
+      if (self->dragged % 3 == 1)
+        {
+          c = &self->points[(self->dragged - 2 + self->n_points) % self->n_points];
+          p = &self->points[(self->dragged - 1 + self->n_points) % self->n_points];
+        }
+      else if (self->dragged % 3 == 2)
+        {
+          c = &self->points[(self->dragged + 2) % self->n_points];
+          p = &self->points[(self->dragged + 1) % self->n_points];
+        }
+      else
+        g_assert_not_reached ();
+
+      a = atan2 (self->points[self->dragged].y - p->y, self->points[self->dragged].x - p->x) + M_PI;
+      d = dist (c, p);
+      c->x = p->x + d * cos (a);
+      c->y = p->y + d * sin (a);
+    }
+
+  gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+static void
+drag_end (GtkGestureDrag *gesture,
+          double          offset_x,
+          double          offset_y,
+          DemoWidget     *self)
+{
+  drag_update (gesture, offset_x, offset_y, self);
+  self->dragged = -1;
+}
+
+static void
+init_points (DemoWidget *self)
+{
+  float w = 200;
+  float h = 200;
+  float cx = w / 2;
+  float cy = h / 2;
+  float pad = 20;
+  float r = (w - 2 * pad) / 2;
+  float k = 0.55228;
+  float kr = k  * r;
+
+  /* curve 1 */
+  self->points[0] = GRAPHENE_POINT_INIT (cx, pad);
+  self->points[1] = GRAPHENE_POINT_INIT (cx + kr, pad);
+  self->points[2] = GRAPHENE_POINT_INIT (w - pad, cy - kr);
+  self->points[3] = GRAPHENE_POINT_INIT (w - pad, cy);
+  /* curve 2 */
+  self->points[4] = GRAPHENE_POINT_INIT (w - pad, cy + kr);
+  self->points[5] = GRAPHENE_POINT_INIT (cx + kr, h - pad);
+  self->points[6] = GRAPHENE_POINT_INIT (cx, h - pad);
+  /* curve 3 */
+  self->points[7] = GRAPHENE_POINT_INIT (cx - kr, h - pad);
+  self->points[8] = GRAPHENE_POINT_INIT (pad, cy + kr);
+  self->points[9] = GRAPHENE_POINT_INIT (pad, cy);
+  /* curve 4 */
+  self->points[10] = GRAPHENE_POINT_INIT (pad, cy - kr);
+  self->points[11] = GRAPHENE_POINT_INIT (cx - kr, pad);
+}
+
+static void
+demo_widget_init (DemoWidget *self)
+{
+  GtkGesture *gesture;
+
+  self->dragged = -1;
+  self->edit = FALSE;
+
+  gesture = gtk_gesture_drag_new ();
+  g_signal_connect (gesture, "drag-begin", G_CALLBACK (drag_begin), self);
+  g_signal_connect (gesture, "drag-update", G_CALLBACK (drag_update), self);
+  g_signal_connect (gesture, "drag-end", G_CALLBACK (drag_end), self);
+  gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture));
+
+  self->n_points = 12;
+  self->points = g_new (graphene_point_t, self->n_points);
+
+  init_points (self);
+}
+
+static void
+demo_widget_snapshot (GtkWidget   *widget,
+                      GtkSnapshot *snapshot)
+{
+  DemoWidget *self = (DemoWidget *)widget;
+  GskPathBuilder *builder;
+  GskPath *path;
+  GskStroke *stroke;
+  int i;
+  float width;
+  float height;
+
+  width = gtk_widget_get_width (widget);
+  height = gtk_widget_get_width (widget);
+
+  builder = gsk_path_builder_new ();
+
+  if (self->edit)
+    {
+      gsk_path_builder_move_to (builder, self->points[0].x, self->points[0].y);
+      for (i = 1; i < self->n_points; i++)
+        {
+          if (i % 3 == 2)
+            gsk_path_builder_move_to (builder, self->points[i].x, self->points[i].y);
+          else
+            gsk_path_builder_line_to (builder, self->points[i].x, self->points[i].y);
+        }
+      gsk_path_builder_line_to (builder, self->points[0].x, self->points[0].y);
+    }
+
+  gsk_path_builder_move_to (builder, self->points[0].x, self->points[0].y);
+  for (i = 1; i < self->n_points; i += 3)
+    {
+      gsk_path_builder_curve_to (builder,
+                                 self->points[i].x, self->points[i].y,
+                                 self->points[(i + 1) % self->n_points].x, self->points[(i + 1) % 
self->n_points].y,
+                                 self->points[(i + 2) % self->n_points].x, self->points[(i + 2) % 
self->n_points].y);
+    }
+
+  path = gsk_path_builder_free_to_path (builder);
+  stroke = gsk_stroke_new (1);
+  gtk_snapshot_push_stroke (snapshot, path, stroke);
+  gsk_stroke_free (stroke);
+  gsk_path_unref (path);
+
+  gtk_snapshot_append_color (snapshot,
+                             &(GdkRGBA){ 0, 0, 0, 1 },
+                             &GRAPHENE_RECT_INIT (0, 0, width, height ));
+
+  gtk_snapshot_pop (snapshot);
+
+  if (self->edit)
+    {
+      if (self->dragged != -1)
+        {
+          builder = gsk_path_builder_new ();
+          gsk_path_builder_add_circle (builder, &self->points[self->dragged], RADIUS);
+          path = gsk_path_builder_free_to_path (builder);
+
+          gtk_snapshot_push_fill (snapshot, path, GSK_FILL_RULE_WINDING);
+          gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 1, 1, 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);
+        }
+
+      builder = gsk_path_builder_new ();
+
+      for (i = 0; i < self->n_points; i++)
+        {
+          if (i % 3 == 0 && i != self->dragged)
+            gsk_path_builder_add_circle (builder, &self->points[i], RADIUS);
+        }
+
+      path = gsk_path_builder_free_to_path (builder);
+
+      gtk_snapshot_push_fill (snapshot, path, GSK_FILL_RULE_WINDING);
+      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);
+
+      builder = gsk_path_builder_new ();
+
+      for (i = 0; i < self->n_points; i++)
+        {
+          if (i % 3 != 0 && i != self->dragged)
+            gsk_path_builder_add_circle (builder, &self->points[i], RADIUS);
+        }
+
+      path = gsk_path_builder_free_to_path (builder);
+
+      gtk_snapshot_push_fill (snapshot, path, GSK_FILL_RULE_WINDING);
+      gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 0, 0, 1, 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);
+    }
+}
+
+static void
+demo_widget_measure (GtkWidget      *widget,
+                     GtkOrientation  orientation,
+                     int             for_size,
+                    int            *minimum_size,
+                     int            *natural_size,
+                     int            *minimum_baseline,
+                     int            *natural_baseline)
+{
+  *minimum_size = 100;
+  *natural_size = 200;
+}
+
+static void
+demo_widget_class_init (DemoWidgetClass *class)
+{
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
+
+  widget_class->snapshot = demo_widget_snapshot;
+  widget_class->measure = demo_widget_measure;
+}
+
+static GtkWidget *
+demo_widget_new (void)
+{
+  return g_object_new (demo_widget_get_type (), NULL);
+}
+
+static void
+edit_changed (GtkToggleButton *button,
+              GParamSpec      *pspec,
+              DemoWidget      *self)
+{
+  self->edit = gtk_toggle_button_get_active (button);
+  gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+static void
+reset (GtkButton  *button,
+       DemoWidget *self)
+{
+  init_points (self);
+  gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+int
+main (int argc, char *argv[])
+{
+  GtkWindow *window;
+  GtkWidget *demo;
+  GtkWidget *edit_toggle;
+  GtkWidget *reset_button;
+  GtkWidget *titlebar;
+
+  gtk_init ();
+
+  window = GTK_WINDOW (gtk_window_new ());
+  gtk_window_set_default_size (GTK_WINDOW (window), 250, 250);
+
+  edit_toggle = gtk_toggle_button_new ();
+  gtk_button_set_icon_name (GTK_BUTTON (edit_toggle), "document-edit-symbolic");
+
+  reset_button = gtk_button_new_from_icon_name ("edit-undo-symbolic");
+
+  titlebar = gtk_header_bar_new ();
+  gtk_header_bar_pack_start (GTK_HEADER_BAR (titlebar), edit_toggle);
+  gtk_header_bar_pack_start (GTK_HEADER_BAR (titlebar), reset_button);
+
+  gtk_window_set_titlebar (GTK_WINDOW (window), titlebar);
+
+  demo = demo_widget_new ();
+
+  g_signal_connect (edit_toggle, "notify::active", G_CALLBACK (edit_changed), demo);
+  g_signal_connect (reset_button, "clicked", G_CALLBACK (reset), demo);
+
+  gtk_window_set_child (window, demo);
+
+  gtk_window_present (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 99f95009f0..e9d456c1fe 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -1,5 +1,6 @@
 gtk_tests = [
   # testname, optional extra sources
+  ['curve'],
   ['testupload'],
   ['testtransform'],
   ['testdropdown'],
diff --git a/tests/simple.c b/tests/simple.c
index 062afdc9fa..7b24ac9a00 100644
--- a/tests/simple.c
+++ b/tests/simple.c
@@ -1,67 +1,111 @@
-/* simple.c
- * Copyright (C) 2017  Red Hat, Inc
- * Author: Benjamin Otte
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public
- * License along with this library. If not, see <http://www.gnu.org/licenses/>.
- */
-#include "config.h"
 #include <gtk/gtk.h>
 
 
+typedef struct
+{
+  GtkWidget parent_instance;
+} DemoWidget;
+
+typedef struct
+{
+  GtkWidgetClass parent_class;
+} DemoWidgetClass;
+
+GType demo_widget_get_type (void) G_GNUC_CONST;
+
+G_DEFINE_TYPE (DemoWidget, demo_widget, GTK_TYPE_WIDGET)
+
+static void
+demo_widget_init (DemoWidget *demo)
+{
+}
+
+static void
+demo_widget_snapshot (GtkWidget   *widget,
+                      GtkSnapshot *snapshot)
+{
+  GdkRGBA red, green, yellow, blue;
+  float w, h;
+  GskPathBuilder *builder;
+  GskPath *path;
+
+  gdk_rgba_parse (&red, "red");
+  gdk_rgba_parse (&green, "green");
+  gdk_rgba_parse (&yellow, "yellow");
+  gdk_rgba_parse (&blue, "blue");
+
+  w = gtk_widget_get_width (widget) / 2.0;
+  h = gtk_widget_get_height (widget) / 2.0;
+
+  builder = gsk_path_builder_new ();
+  gsk_path_builder_move_to (builder, 10, 10);
+  gsk_path_builder_curve_to (builder, 100, 10, 110, 20, 110, 30);
+  gsk_path_builder_curve_to (builder, 80, 30, 100, 60, 80, 60);
+  gsk_path_builder_line_to (builder, 120, 100);
+  gsk_path_builder_curve_to (builder, 110, 110, 80, 120, 30, 70);
+  gsk_path_builder_close (builder);
+  path = gsk_path_builder_free_to_path (builder);
+
+  gtk_snapshot_push_fill (snapshot, path, GSK_FILL_RULE_WINDING);
+  gsk_path_unref (path);
+
+  gtk_snapshot_append_color (snapshot, &red,
+                             &GRAPHENE_RECT_INIT(0, 0, w, h));
+  gtk_snapshot_append_color (snapshot, &green,
+                             &GRAPHENE_RECT_INIT(w, 0, w, h));
+  gtk_snapshot_append_color (snapshot, &yellow,
+                             &GRAPHENE_RECT_INIT(0, h, w, h));
+  gtk_snapshot_append_color (snapshot, &blue,
+                             &GRAPHENE_RECT_INIT(w, h, w, h));
+
+  gtk_snapshot_pop (snapshot);
+}
+
 static void
-hello (void)
+demo_widget_measure (GtkWidget      *widget,
+                     GtkOrientation  orientation,
+                     int             for_size,
+                     int            *minimum_size,
+                     int            *natural_size,
+                     int            *minimum_baseline,
+                     int            *natural_baseline)
 {
-  g_print ("hello world\n");
+  *minimum_size = 100;
+  *natural_size = 200;
 }
 
 static void
-quit_cb (GtkWidget *widget,
-         gpointer   data)
+demo_widget_class_init (DemoWidgetClass *class)
 {
-  gboolean *done = data;
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
 
-  *done = TRUE;
+  widget_class->snapshot = demo_widget_snapshot;
+  widget_class->measure = demo_widget_measure;
+}
 
-  g_main_context_wakeup (NULL);
+static GtkWidget *
+demo_widget_new (void)
+{
+  return g_object_new (demo_widget_get_type (), NULL);
 }
 
 int
 main (int argc, char *argv[])
 {
-  GtkWidget *window, *button;
-  gboolean done = FALSE;
+  GtkWindow *window;
+  GtkWidget *demo;
 
   gtk_init ();
 
-  window = gtk_window_new ();
-  gtk_window_set_title (GTK_WINDOW (window), "hello world");
-  gtk_window_set_resizable (GTK_WINDOW (window), FALSE);
-  g_signal_connect (window, "destroy", G_CALLBACK (quit_cb), &done);
+  window = GTK_WINDOW (gtk_window_new ());
 
-  button = gtk_button_new ();
-  gtk_button_set_label (GTK_BUTTON (button), "hello world");
-  gtk_widget_set_margin_top (button, 10);
-  gtk_widget_set_margin_bottom (button, 10);
-  gtk_widget_set_margin_start (button, 10);
-  gtk_widget_set_margin_end (button, 10);
-  g_signal_connect (button, "clicked", G_CALLBACK (hello), NULL);
+  demo = demo_widget_new ();
 
-  gtk_window_set_child (GTK_WINDOW (window), button);
+  gtk_window_set_child (window, demo);
 
-  gtk_widget_show (window);
+  gtk_window_present (window);
 
-  while (!done)
+  while (g_list_model_get_n_items (gtk_window_get_toplevels ()) > 0)
     g_main_context_iteration (NULL, TRUE);
 
   return 0;


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