[gtk/matthiasc/lottie] Reshuffle code



commit 4994ea43f9b0b0e37837abf4efffeb00b8208f0b
Author: Matthias Clasen <mclasen redhat com>
Date:   Sat Nov 21 14:53:59 2020 -0500

    Reshuffle code

 tests/curve-editor.c | 1603 +++++++++++++++++++++++++-------------------------
 1 file changed, 806 insertions(+), 797 deletions(-)
---
diff --git a/tests/curve-editor.c b/tests/curve-editor.c
index 1c60def6b9..55859ab59e 100644
--- a/tests/curve-editor.c
+++ b/tests/curve-editor.c
@@ -1,63 +1,13 @@
+/* TODO: redo data structure, use gskpath for math */
+
 #include "curve-editor.h"
 
 #include <gtk/gtk.h>
 
-/* Set q to the projection of p onto the line through a and b */
-static void
-closest_point (const graphene_point_t *p,
-               const graphene_point_t *a,
-               const graphene_point_t *b,
-               graphene_point_t       *q)
-{
-  graphene_vec2_t n;
-  graphene_vec2_t ap;
-  float t;
-
-  graphene_vec2_init (&n, b->x - a->x, b->y - a->y);
-  graphene_vec2_init (&ap, p->x - a->x, p->y - a->y);
-
-  t = graphene_vec2_dot (&ap, &n) / graphene_vec2_dot (&n, &n);
-
-  q->x = a->x + t * (b->x - a->x);
-  q->y = a->y + t * (b->y - a->y);
-}
-
-/* Determine if p is on the line through a and b */
-static gboolean
-collinear (const graphene_point_t *p,
-           const graphene_point_t *a,
-           const graphene_point_t *b)
-{
-  graphene_point_t q;
-
-  closest_point (p, a, b, &q);
-
-  return graphene_point_near (p, &q, 0.0001);
-}
-
-/* Set q to the point on the line through p and a that is
- * at a distance of d from p, on the opposite side
- */
-static void
-opposite_point (const graphene_point_t *p,
-                const graphene_point_t *a,
-                float                   d,
-                graphene_point_t       *q)
-{
-  graphene_vec2_t ap;
-  float t;
-
-  graphene_vec2_init (&ap, p->x - a->x, p->y - a->y);
-
-  t = - sqrt (d * d / graphene_vec2_dot (&ap, &ap));
-
-  q->x = p->x + t * (a->x - p->x);
-  q->y = p->y + t * (a->y - p->y);
-}
-
 #define DRAW_RADIUS 5
 #define CLICK_RADIUS 8
 
+/* {{{ Types and structures */
 typedef enum
 {
   MOVE,
@@ -123,264 +73,303 @@ struct _CurveEditorClass
 };
 
 G_DEFINE_TYPE (CurveEditor, curve_editor, GTK_TYPE_WIDGET)
+/* }}} */
+/* {{{ Misc. geometry */
+/* Set q to the projection of p onto the line through a and b */
+static void
+closest_point (const graphene_point_t *p,
+               const graphene_point_t *a,
+               const graphene_point_t *b,
+               graphene_point_t       *q)
+{
+  graphene_vec2_t n;
+  graphene_vec2_t ap;
+  float t;
 
+  graphene_vec2_init (&n, b->x - a->x, b->y - a->y);
+  graphene_vec2_init (&ap, p->x - a->x, p->y - a->y);
+
+  t = graphene_vec2_dot (&ap, &n) / graphene_vec2_dot (&n, &n);
+
+  q->x = a->x + t * (b->x - a->x);
+  q->y = a->y + t * (b->y - a->y);
+}
+
+/* Determine if p is on the line through a and b */
 static gboolean
-point_is_visible (CurveEditor *self,
-                  int          point)
+collinear (const graphene_point_t *p,
+           const graphene_point_t *a,
+           const graphene_point_t *b)
 {
-  g_assert (0 <= point && point < self->n_points);
+  graphene_point_t q;
 
-  if (!self->edit)
-    return FALSE;
+  closest_point (p, a, b, &q);
 
-  switch (point % 3)
-    {
-    case 0: /* point on curve */
-      return TRUE;
-    case 1:
-      if (!self->point_data[point / 3].edit)
-        return FALSE;
-      else
-        return self->point_data[point / 3].op == CURVE;
-    case 2:
-      if (!self->point_data[((point + 1) % self->n_points) / 3].edit)
-        return FALSE;
-      else
-        return self->point_data[point / 3].op == CURVE;
-    default:
-      g_assert_not_reached ();
-    }
+  return graphene_point_near (p, &q, 0.0001);
 }
 
+/* Set q to the point on the line through p and a that is
+ * at a distance of d from p, on the opposite side
+ */
 static void
-drag_begin (GtkGestureDrag *gesture,
-            double          start_x,
-            double          start_y,
-            CurveEditor     *self)
+opposite_point (const graphene_point_t *p,
+                const graphene_point_t *a,
+                float                   d,
+                graphene_point_t       *q)
 {
-  int i;
-  graphene_point_t p = GRAPHENE_POINT_INIT (start_x, start_y);
+  graphene_vec2_t ap;
+  float t;
 
-  if (self->edit)
-    for (i = 0; i < self->n_points; i++)
-      {
-        if (graphene_point_distance (&self->points[i], &p, NULL, NULL) < CLICK_RADIUS)
-          {
-            if (point_is_visible (self, i))
-              {
-                self->dragged = i;
-                gtk_widget_queue_draw (GTK_WIDGET (self));
-              }
-            return;
-          }
-      }
+  graphene_vec2_init (&ap, p->x - a->x, p->y - a->y);
 
-  gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
-}
+  t = - sqrt (d * d / graphene_vec2_dot (&ap, &ap));
 
+  q->x = p->x + t * (a->x - p->x);
+  q->y = p->y + t * (a->y - p->y);
+}
+/* }}} */
+/* {{{ Misc. Bezier math */
 static void
-drag_update (GtkGestureDrag *gesture,
-             double          offset_x,
-             double          offset_y,
-             CurveEditor     *self)
+find_line_point (graphene_point_t *a,
+                 graphene_point_t *b,
+                 graphene_point_t *p,
+                 double           *t,
+                 graphene_point_t *pp,
+                 double           *d)
 {
-  double x, y;
-  double dx, dy;
-  graphene_point_t *c, *p, *d;
-  double l1, l2;
+  graphene_vec2_t n;
+  graphene_vec2_t ap;
+  float tt;
 
-  if (self->dragged == -1)
-    return;
+  graphene_vec2_init (&n, b->x - a->x, b->y - a->y);
+  graphene_vec2_init (&ap, p->x - a->x, p->y - a->y);
 
-  gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
+  tt = graphene_vec2_dot (&ap, &n) / graphene_vec2_dot (&n, &n);
 
-  gtk_gesture_drag_get_start_point (gesture, &x, &y);
+  if (tt < 0)
+    {
+      *pp = *a;
+      *t = 0;
+      *d = graphene_point_distance (a, p, NULL, NULL);
+    }
+  else if (tt > 1)
+    {
+      *pp = *b;
+      *t = 1;
+      *d = graphene_point_distance (b, p, NULL, NULL);
+    }
+  else
+    {
+      pp->x = a->x + tt * (b->x - a->x);
+      pp->y = a->y + tt * (b->y - a->y);
+      *t = tt;
+      *d = graphene_point_distance (pp, p, NULL, NULL);
+    }
+}
 
-  x += offset_x;
-  y += offset_y;
+static void
+gsk_split_get_coefficients (graphene_point_t       coeffs[4],
+                            const graphene_point_t pts[4])
+{
+  coeffs[0] = GRAPHENE_POINT_INIT (pts[3].x - 3.0f * pts[2].x + 3.0f * pts[1].x - pts[0].x,
+                                   pts[3].y - 3.0f * pts[2].y + 3.0f * pts[1].y - pts[0].y);
+  coeffs[1] = GRAPHENE_POINT_INIT (3.0f * pts[2].x - 6.0f * pts[1].x + 3.0f * pts[0].x,
+                                   3.0f * pts[2].y - 6.0f * pts[1].y + 3.0f * pts[0].y);
+  coeffs[2] = GRAPHENE_POINT_INIT (3.0f * pts[1].x - 3.0f * pts[0].x,
+                                   3.0f * pts[1].y - 3.0f * pts[0].y);
+  coeffs[3] = pts[0];
+}
 
-  d = &self->points[self->dragged];
+static void
+gsk_spline_get_point_cubic (const graphene_point_t  pts[4],
+                            float                   progress,
+                            graphene_point_t       *pos,
+                            graphene_vec2_t        *tangent)
+{
+  graphene_point_t c[4];
 
-  /* before moving the point, record the distances to its neighbors, since
-   * we may want to preserve those
-   */
-  c = &self->points[(self->dragged - 1 + self->n_points) % self->n_points];
-  l1 = graphene_point_distance (d, c, NULL, NULL);
-  c = &self->points[(self->dragged + 1) % self->n_points];
-  l2 = graphene_point_distance (d, c, NULL, NULL);
+  gsk_split_get_coefficients (c, pts);
+  if (pos)
+    *pos = GRAPHENE_POINT_INIT (((c[0].x * progress + c[1].x) * progress +c[2].x) * progress + c[3].x,
+                                ((c[0].y * progress + c[1].y) * progress +c[2].y) * progress + c[3].y);
+  if (tangent)
+    {
+      graphene_vec2_init (tangent,
+                          (3.0f * c[0].x * progress + 2.0f * c[1].x) * progress + c[2].x,
+                          (3.0f * c[0].y * progress + 2.0f * c[1].y) * progress + c[2].y);
+      graphene_vec2_normalize (tangent, tangent);
+    }
+}
 
-  dx = x - d->x;
-  dy = y - d->y;
+static void
+find_curve_point (graphene_point_t *points,
+                  graphene_point_t *p,
+                  double           *t,
+                  graphene_point_t *pp,
+                  double           *d)
+{
+  graphene_point_t q;
+  graphene_point_t best_p;
+  double best_d;
+  double best_t;
+  double dd;
+  double tt;
+  int i;
 
-  if (self->dragged % 3 == 0)
+  best_d = G_MAXDOUBLE;
+  best_t = 0;
+
+  for (i = 0; i < 20; i++)
     {
-      /* dragged point is on curve */
+      tt = i / 20.0;
+      gsk_spline_get_point_cubic (points, tt, &q, NULL);
+      dd = graphene_point_distance (&q, p, NULL, NULL);
+      if (dd < best_d)
+        {
+          best_d = dd;
+          best_t = tt;
+          best_p = q;
+        }
+    }
 
-      Operation op, op1, op2;
+  /* TODO: bisect from here */
 
-      /* first move the point itself */
-      d->x = x;
-      d->y = y;
-
-      /* adjust control points as needed */
-      op = self->point_data[self->dragged / 3].op;
-      op1 = self->point_data[((self->dragged - 1 + self->n_points) % self->n_points) / 3].op;
-
-      if (op1 == LINE)
-        {
-          /* the other endpoint of the line */
-          p = &self->points[(self->dragged - 3 + self->n_points) % self->n_points];
-
-          if (op == CURVE && self->point_data[self->dragged / 3].smooth)
-            {
-              /* adjust the control point after the line segment */
-              c = &self->points[(self->dragged + 1) % self->n_points];
-              opposite_point (d, p, l2, c);
-            }
-          else
-            {
-              c = &self->points[(self->dragged + 1) % self->n_points];
-              c->x += dx;
-              c->y += dy;
-            }
+  *t = best_t;
+  *pp = best_p;
+  *d = best_d;
+}
 
-          c = &self->points[(self->dragged - 1 + self->n_points) % self->n_points];
-          c->x += dx;
-          c->y += dy;
+static void
+find_closest_point (CurveEditor      *self,
+                    graphene_point_t *p,
+                    int              *point,
+                    double           *t,
+                    double           *d)
+{
+  int i;
+  int best_i;
+  double best_d;
+  double best_t;
+  double tt;
+  double dd;
+  graphene_point_t pp;
 
-          op2 = self->point_data[((self->dragged - 4 + self->n_points) % self->n_points) / 3].op;
-          if (op2 == CURVE && self->point_data[((self->dragged - 3 + self->n_points) % self->n_points) / 
3].smooth)
-            {
-              double l;
+  best_i = -1;
+  best_d = G_MAXDOUBLE;
+  best_t = 0;
 
-              /* adjust the control point before the line segment */
-              c = &self->points[((self->dragged - 4 + self->n_points) % self->n_points)];
+  for (i = 0; i < self->n_points; i++)
+    {
+      if (i % 3 != 0)
+        continue;
 
-              l = graphene_point_distance (c, p, NULL, NULL);
-              opposite_point (p, d, l, c);
-            }
-        }
-      if (op == LINE)
+      switch (self->point_data[i / 3].op)
         {
-          /* the other endpoint of the line */
-          p = &self->points[(self->dragged + 3) % self->n_points];
-
-          if (op1 == CURVE && self->point_data[self->dragged / 3].smooth)
-            {
-              /* adjust the control point before the line segment */
-              c = &self->points[(self->dragged - 1 + self->n_points) % self->n_points];
-              opposite_point (d, p, l1, c);
-            }
-          else
+        case MOVE:
+          continue;
+        case LINE:
+          find_line_point (&self->points[i], &self->points[(i + 3) % self->n_points], p, &tt, &pp, &dd);
+          if (dd < best_d)
             {
-              c = &self->points[(self->dragged -1 + self->n_points) % self->n_points];
-              c->x += dx;
-              c->y += dy;
+              best_i = i;
+              best_d = dd;
+              best_t = tt;
             }
+          break;
+        case CURVE:
+          {
+            graphene_point_t points[4];
+            int k;
 
-          c = &self->points[(self->dragged + 1) % self->n_points];
-          c->x += dx;
-          c->y += dy;
-
-          op2 = self->point_data[((self->dragged + 3) % self->n_points) / 3].op;
-          if (op2 == CURVE && self->point_data[((self->dragged + 3) % self->n_points) / 3].smooth)
-            {
-              double l;
-
-              /* adjust the control point after the line segment */
-              c = &self->points[((self->dragged + 4) % self->n_points)];
+            for (k = 0; k < 4; k++)
+              points[k] = self->points[(i + k) % self->n_points];
 
-              l = graphene_point_distance (c, p, NULL, NULL);
-              opposite_point (p, d, l, c);
-            }
+            find_curve_point (points, p, &tt, &pp, &dd);
+            if (dd < best_d)
+              {
+                best_i = i;
+                best_d = dd;
+                best_t = tt;
+              }
+          }
+          break;
+        default:
+          g_assert_not_reached ();
         }
-      if (op1 != LINE && op != LINE)
-        {
-          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;
-        }
+  *point = best_i;
+  *t = best_t;
+  *d = best_d;
+}
+
+static void
+split_bezier (graphene_point_t *points,
+              int               length,
+              float             t,
+              graphene_point_t *left,
+              int              *left_pos,
+              graphene_point_t *right,
+              int              *right_pos)
+{
+  if (length == 1)
+    {
+      left[*left_pos] = points[0];
+      (*left_pos)++;
+      right[*right_pos] = points[0];
+      (*right_pos)++;
     }
   else
     {
-      /* dragged point is a control point */
-
-      int point;
-      graphene_point_t *p1;
-      Operation op, op1;
-
-      if (self->dragged % 3 == 1)
-        {
-          point = (self->dragged - 1 + self->n_points) % self->n_points;
-          c = &self->points[(self->dragged - 2 + self->n_points) % self->n_points];
-          p = &self->points[point];
-
-          op = self->point_data[point / 3].op;
-          op1 = self->point_data[((self->dragged - 4 + self->n_points) % self->n_points) / 3].op;
-          p1 = &self->points[(self->dragged - 4 + self->n_points) % self->n_points];
-        }
-      else if (self->dragged % 3 == 2)
-        {
-          point = (self->dragged + 1) % self->n_points;
-          c = &self->points[(self->dragged + 2) % self->n_points];
-          p = &self->points[point];
-
-          op = self->point_data[self->dragged / 3].op;
-          op1 = self->point_data[point / 3].op;
-          p1 = &self->points[(self->dragged + 4) % self->n_points];
-        }
-      else
-        g_assert_not_reached ();
+      graphene_point_t *newpoints;
+      int i;
 
-      if (op == CURVE && self->point_data[point / 3].smooth)
+      newpoints = g_alloca (sizeof (graphene_point_t) * (length - 1));
+      for (i = 0; i < length - 1; i++)
         {
-          if (op1 == CURVE)
-            {
-              double l;
-
-              /* first move the point itself */
-              d->x = x;
-              d->y = y;
-
-              /* then adjust the other control point */
-              if (self->point_data[point / 3].symmetric)
-                l = graphene_point_distance (d, p, NULL, NULL);
-              else
-                l = graphene_point_distance (c, p, NULL, NULL);
-
-              opposite_point (p, d, l, c);
-            }
-          else if (op1 == LINE)
+          if (i == 0)
             {
-              graphene_point_t m = GRAPHENE_POINT_INIT (x, y);
-              closest_point (&m, p, p1, d);
+              left[*left_pos] = points[i];
+              (*left_pos)++;
             }
-          else
+          if (i == length - 2)
             {
-              d->x = x;
-              d->y = y;
+              right[*right_pos] = points[i + 1];
+              (*right_pos)++;
             }
+          graphene_point_interpolate (&points[i], &points[i+1], t, &newpoints[i]);
         }
-      else
-        {
-          d->x = x;
-          d->y = y;
-        }
+      split_bezier (newpoints, length - 1, t, left, left_pos, right, right_pos);
     }
-
-  gtk_widget_queue_draw (GTK_WIDGET (self));
 }
-
-static void
-drag_end (GtkGestureDrag *gesture,
-          double          offset_x,
-          double          offset_y,
-          CurveEditor     *self)
+/* }}} */
+/* {{{ Utilities */
+static gboolean
+point_is_visible (CurveEditor *self,
+                  int          point)
 {
-  drag_update (gesture, offset_x, offset_y, self);
-  self->dragged = -1;
+  g_assert (0 <= point && point < self->n_points);
+
+  if (!self->edit)
+    return FALSE;
+
+  switch (point % 3)
+    {
+    case 0: /* point on curve */
+      return TRUE;
+    case 1:
+      if (!self->point_data[point / 3].edit)
+        return FALSE;
+      else
+        return self->point_data[point / 3].op == CURVE;
+    case 2:
+      if (!self->point_data[((point + 1) % self->n_points) / 3].edit)
+        return FALSE;
+      else
+        return self->point_data[point / 3].op == CURVE;
+    default:
+      g_assert_not_reached ();
+    }
 }
 
 static void
@@ -437,370 +426,535 @@ maintain_smoothness (CurveEditor *self,
     }
 }
 
+/* Check if the points arount point currently satisfy
+ * smoothness conditions. Set PointData.smooth accordingly.
+ */
 static void
-set_smooth (GSimpleAction *action,
-            GVariant      *value,
-            gpointer       data)
+update_smoothness (CurveEditor *self,
+                   int          point)
 {
-  CurveEditor *self = CURVE_EDITOR (data);
+  Operation op, op1;
+  graphene_point_t *p, *p2, *p1;
 
-  self->point_data[self->context / 3].smooth = g_variant_get_boolean (value);
+  p = &self->points[point];
+  op = self->point_data[point / 3].op;
+  op1 = self->point_data[((point - 1 + self->n_points) % self->n_points) / 3].op;
 
-  maintain_smoothness (self, self->context);
+  if (op == CURVE)
+    p2 = &self->points[(point + 1) % self->n_points];
+  else if (op == LINE)
+    p2 = &self->points[(point + 3) % self->n_points];
+  else
+    p2 = NULL;
 
-  gtk_widget_queue_draw (GTK_WIDGET (self));
+  if (op1 == CURVE)
+    p1 = &self->points[(point - 1 + self->n_points) % self->n_points];
+  else if (op1 == LINE)
+    p1 = &self->points[(point - 3 + self->n_points) % self->n_points];
+  else
+    p1 = NULL;
+
+  if (p1 && p2)
+    self->point_data[point / 3].smooth = collinear (p, p1, p2);
+  else
+    self->point_data[point / 3].smooth = TRUE;
 }
 
 static void
-set_symmetric (GSimpleAction *action,
-               GVariant      *value,
-               gpointer       data)
+insert_point (CurveEditor *self,
+              int          point,
+              double       pos)
 {
-  CurveEditor *self = CURVE_EDITOR (data);
+  Operation op = self->point_data[point / 3].op;
+  int i;
+  graphene_point_t points[4];
+      int k;
 
-  self->point_data[self->context / 3].symmetric = g_variant_get_boolean (value);
+  if (op == MOVE)
+    return;
 
-  maintain_smoothness (self, self->context);
+  for (k = 0; k < 4; k++)
+    points[k] = self->points[(point + k) % self->n_points];
 
-  gtk_widget_queue_draw (GTK_WIDGET (self));
-}
+  self->point_data = g_realloc (self->point_data, sizeof (PointData) * (self->n_points / 3 + 1));
+  for (i = self->n_points / 3; i > point / 3; i--)
+    self->point_data[i] = self->point_data[i - 1];
 
-static void
-set_operation (GSimpleAction *action,
-               GVariant      *value,
-               gpointer       data)
-{
-  CurveEditor *self = CURVE_EDITOR (data);
+  self->points = g_realloc (self->points, sizeof (graphene_point_t) * (self->n_points + 3));
+  for (i = self->n_points + 2; i > point + 4; i--)
+    self->points[i] = self->points[i - 3];
 
-  self->point_data[self->context / 3].op = op_from_string (g_variant_get_string (value, NULL));
+  self->n_points += 3;
 
-  maintain_smoothness (self, self->context);
-  maintain_smoothness (self, (self->context + 3) % self->n_points);
+  if (op == LINE)
+    {
+      graphene_point_t p;
+      graphene_point_t q;
 
-  gtk_widget_queue_draw (GTK_WIDGET (self));
-}
+      graphene_point_interpolate (&self->points[point], &self->points[(point + 6) % self->n_points], pos, 
&p);
+      self->points[point + 3] = p;
 
-static void
-remove_point (GSimpleAction *action,
-              GVariant      *value,
-              gpointer       data)
-{
-  CurveEditor *self = CURVE_EDITOR (data);
-  int i;
+      graphene_point_interpolate (&p, &self->points[(point + 6) % self->n_points], 0.33, &q);
 
-  for (i = self->context / 3; i + 1 < self->n_points / 3; i++)
-    self->point_data[i] = self->point_data[i + 1];
+      self->points[point + 4] = q;
 
-  self->point_data = g_realloc (self->point_data, sizeof (PointData) * (self->n_points / 3 - 1));
+      graphene_point_interpolate (&p, &self->points[(point + 6) % self->n_points], 0.66, &q);
 
-  self->points[(self->context - 1 + self->n_points) % self->n_points] = self->points[self->context + 2];
+      self->points[point + 5] = q;
+      self->point_data[point / 3 + 1].smooth = TRUE;
+      self->point_data[point / 3 + 1].op = LINE;
+    }
+  else if (op == CURVE)
+    {
+      graphene_point_t left[4];
+      graphene_point_t right[4];
+      int left_pos = 0;
+      int right_pos = 0;
 
-  for (i = self->context; i + 3 < self->n_points; i++)
-    self->points[i] = self->points[i + 3];
+      split_bezier (points, 4, pos, left, &left_pos, right, &right_pos);
 
-  self->points = g_realloc (self->points, sizeof (graphene_point_t) * (self->n_points - 3));
+      for (k = 0; k < 4; k++)
+        {
+          self->points[(point + k) % self->n_points] = left[k];
+          self->points[(point + 3 + k) % self->n_points] = right[3 - k];
+        }
 
-  self->n_points -= 3;
-}
+      self->point_data[point / 3 + 1].smooth = TRUE;
+      self->point_data[point / 3 + 1].op = CURVE;
+    }
 
+  gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+/* }}} */
+/* {{{ GskPath helpers */
 static void
-find_line_point (graphene_point_t *a,
-                 graphene_point_t *b,
-                 graphene_point_t *p,
-                 double           *t,
-                 graphene_point_t *pp,
-                 double           *d)
+curve_editor_add_path (CurveEditor    *self,
+                       GskPathBuilder *builder)
 {
-  graphene_vec2_t n;
-  graphene_vec2_t ap;
-  float tt;
+  int i;
 
-  graphene_vec2_init (&n, b->x - a->x, b->y - a->y);
-  graphene_vec2_init (&ap, p->x - a->x, p->y - a->y);
+  gsk_path_builder_move_to (builder, self->points[0].x, self->points[0].y);
+  for (i = 1; i < self->n_points; i += 3)
+    {
+      switch (self->point_data[i / 3].op)
+        {
+        case MOVE:
+          gsk_path_builder_move_to (builder,
+                                    self->points[(i + 2) % self->n_points].x, self->points[(i + 2) % 
self->n_points].y);
+          break;
 
-  tt = graphene_vec2_dot (&ap, &n) / graphene_vec2_dot (&n, &n);
+        case LINE:
+          gsk_path_builder_line_to (builder,
+                                    self->points[(i + 2) % self->n_points].x, self->points[(i + 2) % 
self->n_points].y);
+          break;
 
-  if (tt < 0)
-    {
-      *pp = *a;
-      *t = 0;
-      *d = graphene_point_distance (a, p, NULL, NULL);
-    }
-  else if (tt > 1)
-    {
-      *pp = *b;
-      *t = 1;
-      *d = graphene_point_distance (b, p, NULL, NULL);
-    }
-  else
-    {
-      pp->x = a->x + tt * (b->x - a->x);
-      pp->y = a->y + tt * (b->y - a->y);
-      *t = tt;
-      *d = graphene_point_distance (pp, p, NULL, NULL);
+        case CURVE:
+          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);
+          break;
+        default:
+          g_assert_not_reached ();
+        }
     }
 }
 
-static void
-gsk_split_get_coefficients (graphene_point_t       coeffs[4],
-                            const graphene_point_t pts[4])
+typedef struct
 {
-  coeffs[0] = GRAPHENE_POINT_INIT (pts[3].x - 3.0f * pts[2].x + 3.0f * pts[1].x - pts[0].x,
-                                   pts[3].y - 3.0f * pts[2].y + 3.0f * pts[1].y - pts[0].y);
-  coeffs[1] = GRAPHENE_POINT_INIT (3.0f * pts[2].x - 6.0f * pts[1].x + 3.0f * pts[0].x,
-                                   3.0f * pts[2].y - 6.0f * pts[1].y + 3.0f * pts[0].y);
-  coeffs[2] = GRAPHENE_POINT_INIT (3.0f * pts[1].x - 3.0f * pts[0].x,
-                                   3.0f * pts[1].y - 3.0f * pts[0].y);
-  coeffs[3] = pts[0];
-}
+  int count;
+  graphene_point_t first;
+  graphene_point_t last;
+  gboolean has_close;
+  gboolean has_initial_move;
+} CountSegmentData;
 
-static void
-gsk_spline_get_point_cubic (const graphene_point_t  pts[4],
-                            float                   progress,
-                            graphene_point_t       *pos,
-                            graphene_vec2_t        *tangent)
+static gboolean
+count_segments (GskPathOperation        op,
+                const graphene_point_t *pts,
+                gsize                   n_pts,
+                gpointer                data)
 {
-  graphene_point_t c[4];
+  CountSegmentData *d = data;
 
-  gsk_split_get_coefficients (c, pts);
-  if (pos)
-    *pos = GRAPHENE_POINT_INIT (((c[0].x * progress + c[1].x) * progress +c[2].x) * progress + c[3].x,
-                                ((c[0].y * progress + c[1].y) * progress +c[2].y) * progress + c[3].y);
-  if (tangent)
+  if (d->count == 0)
     {
-      graphene_vec2_init (tangent,
-                          (3.0f * c[0].x * progress + 2.0f * c[1].x) * progress + c[2].x,
-                          (3.0f * c[0].y * progress + 2.0f * c[1].y) * progress + c[2].y);
-      graphene_vec2_normalize (tangent, tangent);
+      d->first = pts[0];
+      if (op == GSK_PATH_MOVE)
+        d->has_initial_move = TRUE;
     }
+
+  d->last = pts[n_pts - 1];
+  d->count++;
+
+  if (op == GSK_PATH_CLOSE)
+    d->has_close = TRUE;
+
+  return TRUE;
 }
 
-static void
-find_curve_point (graphene_point_t *points,
-                  graphene_point_t *p,
-                  double           *t,
-                  graphene_point_t *pp,
-                  double           *d)
+typedef struct
 {
-  graphene_point_t q;
-  graphene_point_t best_p;
-  double best_d;
-  double best_t;
-  double dd;
-  double tt;
-  int i;
+  CurveEditor *editor;
+  int pos;
+} CopySegmentData;
 
-  best_d = G_MAXDOUBLE;
-  best_t = 0;
+static gboolean
+copy_segments (GskPathOperation        op,
+               const graphene_point_t *pts,
+               gsize                   n_pts,
+               gpointer                data)
+{
+  CopySegmentData *d = data;
 
-  for (i = 0; i < 20; i++)
+  switch (op)
     {
-      tt = i / 20.0;
-      gsk_spline_get_point_cubic (points, tt, &q, NULL);
-      dd = graphene_point_distance (&q, p, NULL, NULL);
-      if (dd < best_d)
+    case GSK_PATH_MOVE:
+      if (d->pos != 0)
         {
-          best_d = dd;
-          best_t = tt;
-          best_p = q;
-        }
-    }
+          d->editor->point_data[d->pos / 3].op = MOVE;
+          d->editor->point_data[d->pos / 3].smooth = FALSE;
 
-  /* TODO: bisect from here */
+          d->editor->points[d->pos++] = pts[0];
+          d->editor->points[d->pos++] = pts[0];
+          if (d->pos < d->editor->n_points)
+            d->editor->points[d->pos++] = pts[0];
+        }
+      break;
+    case GSK_PATH_CLOSE:
+      break;
+    case GSK_PATH_LINE:
+      d->editor->point_data[d->pos / 3].op = LINE;
+      d->editor->point_data[d->pos / 3].smooth = FALSE;
 
-  *t = best_t;
-  *pp = best_p;
-  *d = best_d;
-}
+      if (d->pos == 0)
+        d->editor->points[d->pos++] = pts[0];
+
+      d->editor->points[d->pos++] = pts[1];
+      d->editor->points[d->pos++] = pts[1];
+      if (d->pos < d->editor->n_points)
+        d->editor->points[d->pos++] = pts[1];
+      break;
+    case GSK_PATH_CURVE:
+      d->editor->point_data[d->pos / 3].op = CURVE;
+      d->editor->point_data[d->pos / 3].smooth = FALSE;
+
+      if (d->pos == 0)
+        d->editor->points[d->pos++] = pts[0];
+
+      d->editor->points[d->pos++] = pts[1];
+      d->editor->points[d->pos++] = pts[2];
+
+      if (d->pos < d->editor->n_points)
+        d->editor->points[d->pos++] = pts[3];
+      break;
+    default:
+      g_assert_not_reached ();
+    }
 
+  return TRUE;
+}
+/* }}} */
+/* {{{ Drag implementation */
 static void
-find_closest_point (CurveEditor      *self,
-                    graphene_point_t *p,
-                    int              *point,
-                    double           *t,
-                    double           *d)
+drag_begin (GtkGestureDrag *gesture,
+            double          start_x,
+            double          start_y,
+            CurveEditor     *self)
 {
   int i;
-  int best_i;
-  double best_d;
-  double best_t;
-  double tt;
-  double dd;
-  graphene_point_t pp;
+  graphene_point_t p = GRAPHENE_POINT_INIT (start_x, start_y);
 
-  best_i = -1;
-  best_d = G_MAXDOUBLE;
-  best_t = 0;
+  if (self->edit)
+    for (i = 0; i < self->n_points; i++)
+      {
+        if (graphene_point_distance (&self->points[i], &p, NULL, NULL) < CLICK_RADIUS)
+          {
+            if (point_is_visible (self, i))
+              {
+                self->dragged = i;
+                gtk_widget_queue_draw (GTK_WIDGET (self));
+              }
+            return;
+          }
+      }
 
-  for (i = 0; i < self->n_points; i++)
+  gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
+}
+
+static void
+drag_update (GtkGestureDrag *gesture,
+             double          offset_x,
+             double          offset_y,
+             CurveEditor     *self)
+{
+  double x, y;
+  double dx, dy;
+  graphene_point_t *c, *p, *d;
+  double l1, l2;
+
+  if (self->dragged == -1)
+    return;
+
+  gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
+
+  gtk_gesture_drag_get_start_point (gesture, &x, &y);
+
+  x += offset_x;
+  y += offset_y;
+
+  d = &self->points[self->dragged];
+
+  /* before moving the point, record the distances to its neighbors, since
+   * we may want to preserve those
+   */
+  c = &self->points[(self->dragged - 1 + self->n_points) % self->n_points];
+  l1 = graphene_point_distance (d, c, NULL, NULL);
+  c = &self->points[(self->dragged + 1) % self->n_points];
+  l2 = graphene_point_distance (d, c, NULL, NULL);
+
+  dx = x - d->x;
+  dy = y - d->y;
+
+  if (self->dragged % 3 == 0)
     {
-      if (i % 3 != 0)
-        continue;
+      /* dragged point is on curve */
 
-      switch (self->point_data[i / 3].op)
+      Operation op, op1, op2;
+
+      /* first move the point itself */
+      d->x = x;
+      d->y = y;
+
+      /* adjust control points as needed */
+      op = self->point_data[self->dragged / 3].op;
+      op1 = self->point_data[((self->dragged - 1 + self->n_points) % self->n_points) / 3].op;
+
+      if (op1 == LINE)
         {
-        case MOVE:
-          continue;
-        case LINE:
-          find_line_point (&self->points[i], &self->points[(i + 3) % self->n_points], p, &tt, &pp, &dd);
-          if (dd < best_d)
+          /* the other endpoint of the line */
+          p = &self->points[(self->dragged - 3 + self->n_points) % self->n_points];
+
+          if (op == CURVE && self->point_data[self->dragged / 3].smooth)
             {
-              best_i = i;
-              best_d = dd;
-              best_t = tt;
+              /* adjust the control point after the line segment */
+              c = &self->points[(self->dragged + 1) % self->n_points];
+              opposite_point (d, p, l2, c);
+            }
+          else
+            {
+              c = &self->points[(self->dragged + 1) % self->n_points];
+              c->x += dx;
+              c->y += dy;
             }
-          break;
-        case CURVE:
-          {
-            graphene_point_t points[4];
-            int k;
 
-            for (k = 0; k < 4; k++)
-              points[k] = self->points[(i + k) % self->n_points];
+          c = &self->points[(self->dragged - 1 + self->n_points) % self->n_points];
+          c->x += dx;
+          c->y += dy;
 
-            find_curve_point (points, p, &tt, &pp, &dd);
-            if (dd < best_d)
-              {
-                best_i = i;
-                best_d = dd;
-                best_t = tt;
-              }
-          }
-          break;
-        default:
-          g_assert_not_reached ();
+          op2 = self->point_data[((self->dragged - 4 + self->n_points) % self->n_points) / 3].op;
+          if (op2 == CURVE && self->point_data[((self->dragged - 3 + self->n_points) % self->n_points) / 
3].smooth)
+            {
+              double l;
+
+              /* adjust the control point before the line segment */
+              c = &self->points[((self->dragged - 4 + self->n_points) % self->n_points)];
+
+              l = graphene_point_distance (c, p, NULL, NULL);
+              opposite_point (p, d, l, c);
+            }
         }
-    }
+      if (op == LINE)
+        {
+          /* the other endpoint of the line */
+          p = &self->points[(self->dragged + 3) % self->n_points];
 
-  *point = best_i;
-  *t = best_t;
-  *d = best_d;
-}
+          if (op1 == CURVE && self->point_data[self->dragged / 3].smooth)
+            {
+              /* adjust the control point before the line segment */
+              c = &self->points[(self->dragged - 1 + self->n_points) % self->n_points];
+              opposite_point (d, p, l1, c);
+            }
+          else
+            {
+              c = &self->points[(self->dragged -1 + self->n_points) % self->n_points];
+              c->x += dx;
+              c->y += dy;
+            }
 
-static void
-split_bezier (graphene_point_t *points,
-              int               length,
-              float             t,
-              graphene_point_t *left,
-              int              *left_pos,
-              graphene_point_t *right,
-              int              *right_pos)
-{
-  if (length == 1)
-    {
-      left[*left_pos] = points[0];
-      (*left_pos)++;
-      right[*right_pos] = points[0];
-      (*right_pos)++;
+          c = &self->points[(self->dragged + 1) % self->n_points];
+          c->x += dx;
+          c->y += dy;
+
+          op2 = self->point_data[((self->dragged + 3) % self->n_points) / 3].op;
+          if (op2 == CURVE && self->point_data[((self->dragged + 3) % self->n_points) / 3].smooth)
+            {
+              double l;
+
+              /* adjust the control point after the line segment */
+              c = &self->points[((self->dragged + 4) % self->n_points)];
+
+              l = graphene_point_distance (c, p, NULL, NULL);
+              opposite_point (p, d, l, c);
+            }
+        }
+      if (op1 != LINE && op != LINE)
+        {
+          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
     {
-      graphene_point_t *newpoints;
-      int i;
+      /* dragged point is a control point */
+
+      int point;
+      graphene_point_t *p1;
+      Operation op, op1;
+
+      if (self->dragged % 3 == 1)
+        {
+          point = (self->dragged - 1 + self->n_points) % self->n_points;
+          c = &self->points[(self->dragged - 2 + self->n_points) % self->n_points];
+          p = &self->points[point];
+
+          op = self->point_data[point / 3].op;
+          op1 = self->point_data[((self->dragged - 4 + self->n_points) % self->n_points) / 3].op;
+          p1 = &self->points[(self->dragged - 4 + self->n_points) % self->n_points];
+        }
+      else if (self->dragged % 3 == 2)
+        {
+          point = (self->dragged + 1) % self->n_points;
+          c = &self->points[(self->dragged + 2) % self->n_points];
+          p = &self->points[point];
+
+          op = self->point_data[self->dragged / 3].op;
+          op1 = self->point_data[point / 3].op;
+          p1 = &self->points[(self->dragged + 4) % self->n_points];
+        }
+      else
+        g_assert_not_reached ();
+
+      if (op == CURVE && self->point_data[point / 3].smooth)
+        {
+          if (op1 == CURVE)
+            {
+              double l;
+
+              /* first move the point itself */
+              d->x = x;
+              d->y = y;
 
-      newpoints = g_alloca (sizeof (graphene_point_t) * (length - 1));
-      for (i = 0; i < length - 1; i++)
-        {
-          if (i == 0)
+              /* then adjust the other control point */
+              if (self->point_data[point / 3].symmetric)
+                l = graphene_point_distance (d, p, NULL, NULL);
+              else
+                l = graphene_point_distance (c, p, NULL, NULL);
+
+              opposite_point (p, d, l, c);
+            }
+          else if (op1 == LINE)
             {
-              left[*left_pos] = points[i];
-              (*left_pos)++;
+              graphene_point_t m = GRAPHENE_POINT_INIT (x, y);
+              closest_point (&m, p, p1, d);
             }
-          if (i == length - 2)
+          else
             {
-              right[*right_pos] = points[i + 1];
-              (*right_pos)++;
+              d->x = x;
+              d->y = y;
             }
-          graphene_point_interpolate (&points[i], &points[i+1], t, &newpoints[i]);
         }
-      split_bezier (newpoints, length - 1, t, left, left_pos, right, right_pos);
+      else
+        {
+          d->x = x;
+          d->y = y;
+        }
     }
+
+  gtk_widget_queue_draw (GTK_WIDGET (self));
 }
 
 static void
-insert_point (CurveEditor *self,
-              int          point,
-              double       pos)
+drag_end (GtkGestureDrag *gesture,
+          double          offset_x,
+          double          offset_y,
+          CurveEditor     *self)
 {
-  Operation op = self->point_data[point / 3].op;
-  int i;
-  graphene_point_t points[4];
-      int k;
-
-  if (op == MOVE)
-    return;
-
-  for (k = 0; k < 4; k++)
-    points[k] = self->points[(point + k) % self->n_points];
-
-  self->point_data = g_realloc (self->point_data, sizeof (PointData) * (self->n_points / 3 + 1));
-  for (i = self->n_points / 3; i > point / 3; i--)
-    self->point_data[i] = self->point_data[i - 1];
-
-  self->points = g_realloc (self->points, sizeof (graphene_point_t) * (self->n_points + 3));
-  for (i = self->n_points + 2; i > point + 4; i--)
-    self->points[i] = self->points[i - 3];
+  drag_update (gesture, offset_x, offset_y, self);
+  self->dragged = -1;
+}
+/* }}} */
+/* {{{ Action callbacks */
+static void
+set_smooth (GSimpleAction *action,
+            GVariant      *value,
+            gpointer       data)
+{
+  CurveEditor *self = CURVE_EDITOR (data);
 
-  self->n_points += 3;
+  self->point_data[self->context / 3].smooth = g_variant_get_boolean (value);
 
-  if (op == LINE)
-    {
-      graphene_point_t p;
-      graphene_point_t q;
+  maintain_smoothness (self, self->context);
 
-      graphene_point_interpolate (&self->points[point], &self->points[(point + 6) % self->n_points], pos, 
&p);
-      self->points[point + 3] = p;
+  gtk_widget_queue_draw (GTK_WIDGET (self));
+}
 
-      graphene_point_interpolate (&p, &self->points[(point + 6) % self->n_points], 0.33, &q);
+static void
+set_symmetric (GSimpleAction *action,
+               GVariant      *value,
+               gpointer       data)
+{
+  CurveEditor *self = CURVE_EDITOR (data);
 
-      self->points[point + 4] = q;
+  self->point_data[self->context / 3].symmetric = g_variant_get_boolean (value);
 
-      graphene_point_interpolate (&p, &self->points[(point + 6) % self->n_points], 0.66, &q);
+  maintain_smoothness (self, self->context);
 
-      self->points[point + 5] = q;
-      self->point_data[point / 3 + 1].smooth = TRUE;
-      self->point_data[point / 3 + 1].op = LINE;
-    }
-  else if (op == CURVE)
-    {
-      graphene_point_t left[4];
-      graphene_point_t right[4];
-      int left_pos = 0;
-      int right_pos = 0;
+  gtk_widget_queue_draw (GTK_WIDGET (self));
+}
 
-      split_bezier (points, 4, pos, left, &left_pos, right, &right_pos);
+static void
+set_operation (GSimpleAction *action,
+               GVariant      *value,
+               gpointer       data)
+{
+  CurveEditor *self = CURVE_EDITOR (data);
 
-      for (k = 0; k < 4; k++)
-        {
-          self->points[(point + k) % self->n_points] = left[k];
-          self->points[(point + 3 + k) % self->n_points] = right[3 - k];
-        }
+  self->point_data[self->context / 3].op = op_from_string (g_variant_get_string (value, NULL));
 
-      self->point_data[point / 3 + 1].smooth = TRUE;
-      self->point_data[point / 3 + 1].op = CURVE;
-    }
+  maintain_smoothness (self, self->context);
+  maintain_smoothness (self, (self->context + 3) % self->n_points);
 
   gtk_widget_queue_draw (GTK_WIDGET (self));
 }
 
 static void
-maybe_insert_point (CurveEditor *self,
-                    double       x,
-                    double       y)
+remove_point (GSimpleAction *action,
+              GVariant      *value,
+              gpointer       data)
 {
-  graphene_point_t m = GRAPHENE_POINT_INIT (x, y);
-  int point;
-  double t;
-  double d;
+  CurveEditor *self = CURVE_EDITOR (data);
+  int i;
+
+  for (i = self->context / 3; i + 1 < self->n_points / 3; i++)
+    self->point_data[i] = self->point_data[i + 1];
 
-  find_closest_point (self, &m, &point, &t, &d);
+  self->point_data = g_realloc (self->point_data, sizeof (PointData) * (self->n_points / 3 - 1));
 
-  if (d > CLICK_RADIUS)
-    return;
+  self->points[(self->context - 1 + self->n_points) % self->n_points] = self->points[self->context + 2];
 
-  insert_point (self, point, t);
-}
+  for (i = self->context; i + 3 < self->n_points; i++)
+    self->points[i] = self->points[i + 3];
 
+  self->points = g_realloc (self->points, sizeof (graphene_point_t) * (self->n_points - 3));
+
+  self->n_points -= 3;
+}
+/* }}} */
+/* {{{ Event handlers */
 static void
 pressed (GtkGestureClick *gesture,
          int              n_press,
@@ -906,7 +1060,16 @@ released (GtkGestureClick *gesture,
     }
 
   if (button == GDK_BUTTON_PRIMARY)
-    maybe_insert_point (self, x, y);
+    {
+      int point;
+      double t;
+      double d;
+
+      find_closest_point (self, &m, &point, &t, &d);
+
+      if (d <= CLICK_RADIUS)
+        insert_point (self, point, t);
+    }
 }
 
 static void
@@ -928,154 +1091,29 @@ motion (GtkEventControllerMotion *controller,
           continue;
 
         if (graphene_point_distance (&self->points[i], &m, NULL, NULL) < CLICK_RADIUS)
-          {
-            if (self->hovered != i)
-              self->hovered = i;
-            break;
-          }
-    }
-
-  if (self->hovered != was_hovered)
-    gtk_widget_queue_draw (GTK_WIDGET (self));
-}
-
-static void
-leave (GtkEventController *controller,
-       CurveEditor        *self)
-{
-  if (self->hovered != -1)
-    {
-      self->hovered = -1;
-      gtk_widget_queue_draw (GTK_WIDGET (self));
-    }
-}
-
-static void
-curve_editor_init (CurveEditor *self)
-{
-  GtkEventController *controller;
-  GMenu *menu;
-  GMenu *section;
-  GMenuItem *item;
-  GSimpleAction *action;
-
-  self->dragged = -1;
-  self->edit = FALSE;
-
-  controller = GTK_EVENT_CONTROLLER (gtk_gesture_drag_new ());
-  gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (controller), GDK_BUTTON_PRIMARY);
-  g_signal_connect (controller, "drag-begin", G_CALLBACK (drag_begin), self);
-  g_signal_connect (controller, "drag-update", G_CALLBACK (drag_update), self);
-  g_signal_connect (controller, "drag-end", G_CALLBACK (drag_end), self);
-  gtk_widget_add_controller (GTK_WIDGET (self), controller);
-
-  controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ());
-  gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (controller), 0);
-  g_signal_connect (controller, "pressed", G_CALLBACK (pressed), self);
-  g_signal_connect (controller, "released", G_CALLBACK (released), self);
-  gtk_widget_add_controller (GTK_WIDGET (self), controller);
-
-  controller = gtk_event_controller_motion_new ();
-  g_signal_connect (controller, "motion", G_CALLBACK (motion), self);
-  g_signal_connect (controller, "leave", G_CALLBACK (leave), self);
-  gtk_widget_add_controller (GTK_WIDGET (self), controller);
-
-  self->points = NULL;
-  self->point_data = NULL;
-  self->n_points = 0;
-
-  self->actions = G_ACTION_MAP (g_simple_action_group_new ());
-
-  action = g_simple_action_new_stateful ("smooth", NULL, g_variant_new_boolean (FALSE));
-  g_signal_connect (action, "change-state", G_CALLBACK (set_smooth), self);
-  g_action_map_add_action (G_ACTION_MAP (self->actions), G_ACTION (action));
-  gtk_widget_insert_action_group (GTK_WIDGET (self), "point", G_ACTION_GROUP (self->actions));
-
-  action = g_simple_action_new_stateful ("symmetric", NULL, g_variant_new_boolean (FALSE));
-  g_signal_connect (action, "change-state", G_CALLBACK (set_symmetric), self);
-  g_action_map_add_action (G_ACTION_MAP (self->actions), G_ACTION (action));
-  gtk_widget_insert_action_group (GTK_WIDGET (self), "point", G_ACTION_GROUP (self->actions));
-
-  action = g_simple_action_new_stateful ("operation", G_VARIANT_TYPE_STRING, g_variant_new_string ("curve"));
-  g_signal_connect (action, "change-state", G_CALLBACK (set_operation), self);
-  g_action_map_add_action (G_ACTION_MAP (self->actions), G_ACTION (action));
-
-  action = g_simple_action_new ("remove", NULL);
-  g_signal_connect (action, "activate", G_CALLBACK (remove_point), self);
-  g_action_map_add_action (G_ACTION_MAP (self->actions), G_ACTION (action));
-
-  gtk_widget_insert_action_group (GTK_WIDGET (self), "point", G_ACTION_GROUP (self->actions));
-
-  menu = g_menu_new ();
-
-  item = g_menu_item_new ("Smooth", "point.smooth");
-  g_menu_append_item (menu, item);
-  g_object_unref (item);
-
-  item = g_menu_item_new ("Symmetric", "point.symmetric");
-  g_menu_append_item (menu, item);
-  g_object_unref (item);
-
-  section = g_menu_new ();
-
-  item = g_menu_item_new ("Move", "point.operation::move");
-  g_menu_append_item (section, item);
-  g_object_unref (item);
-
-  item = g_menu_item_new ("Line", "point.operation::line");
-  g_menu_append_item (section, item);
-  g_object_unref (item);
-
-  item = g_menu_item_new ("Curve", "point.operation::curve");
-  g_menu_append_item (section, item);
-  g_object_unref (item);
-
-  g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
-  g_object_unref (section);
-
-  item = g_menu_item_new ("Remove", "point.remove");
-  g_menu_append_item (section, item);
-  g_object_unref (item);
-
-  self->menu = gtk_popover_menu_new_from_model (G_MENU_MODEL (menu));
-  g_object_unref (menu);
+          {
+            if (self->hovered != i)
+              self->hovered = i;
+            break;
+          }
+    }
 
-  gtk_widget_set_parent (self->menu, GTK_WIDGET (self));
+  if (self->hovered != was_hovered)
+    gtk_widget_queue_draw (GTK_WIDGET (self));
 }
 
 static void
-curve_editor_add_path (CurveEditor    *self,
-                       GskPathBuilder *builder)
+leave (GtkEventController *controller,
+       CurveEditor        *self)
 {
-  int i;
-
-  gsk_path_builder_move_to (builder, self->points[0].x, self->points[0].y);
-  for (i = 1; i < self->n_points; i += 3)
+  if (self->hovered != -1)
     {
-      switch (self->point_data[i / 3].op)
-        {
-        case MOVE:
-          gsk_path_builder_move_to (builder,
-                                    self->points[(i + 2) % self->n_points].x, self->points[(i + 2) % 
self->n_points].y);
-          break;
-
-        case LINE:
-          gsk_path_builder_line_to (builder,
-                                    self->points[(i + 2) % self->n_points].x, self->points[(i + 2) % 
self->n_points].y);
-          break;
-
-        case CURVE:
-          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);
-          break;
-        default:
-          g_assert_not_reached ();
-        }
+      self->hovered = -1;
+      gtk_widget_queue_draw (GTK_WIDGET (self));
     }
 }
-
+/* }}} */
+/* {{{ Snapshot */
 static void
 curve_editor_snapshot (GtkWidget   *widget,
                        GtkSnapshot *snapshot)
@@ -1221,7 +1259,8 @@ curve_editor_snapshot (GtkWidget   *widget,
         }
     }
 }
-
+/* }}} */
+/* {{{ GtkWidget boilerplate */
 static void
 curve_editor_measure (GtkWidget      *widget,
                      GtkOrientation  orientation,
@@ -1245,7 +1284,8 @@ curve_editor_size_allocate (GtkWidget *widget,
 
   gtk_native_check_resize (GTK_NATIVE (self->menu));
 }
-
+/* }}} */
+/* {{{ GObject boilerplate */
 static void
 curve_editor_dispose (GObject *object)
 {
@@ -1270,157 +1310,124 @@ curve_editor_class_init (CurveEditorClass *class)
   widget_class->measure = curve_editor_measure;
   widget_class->size_allocate = curve_editor_size_allocate;
 }
-
-GtkWidget *
-curve_editor_new (void)
+/* }}} */
+/* {{{ Setup */
+static void
+curve_editor_init (CurveEditor *self)
 {
-  return g_object_new (curve_editor_get_type (), NULL);
-}
+  GtkEventController *controller;
+  GMenu *menu;
+  GMenu *section;
+  GMenuItem *item;
+  GSimpleAction *action;
 
-void
-curve_editor_set_edit (CurveEditor *self,
-                       gboolean     edit)
-{
-  int i;
+  self->dragged = -1;
+  self->edit = FALSE;
 
-  self->edit = edit;
-  if (!self->edit)
-    {
-      self->hovered = -1;
-      for (i = 0; i < self->n_points / 3; i++)
-        self->point_data[i].edit = FALSE;
-    }
+  controller = GTK_EVENT_CONTROLLER (gtk_gesture_drag_new ());
+  gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (controller), GDK_BUTTON_PRIMARY);
+  g_signal_connect (controller, "drag-begin", G_CALLBACK (drag_begin), self);
+  g_signal_connect (controller, "drag-update", G_CALLBACK (drag_update), self);
+  g_signal_connect (controller, "drag-end", G_CALLBACK (drag_end), self);
+  gtk_widget_add_controller (GTK_WIDGET (self), controller);
 
-  gtk_widget_queue_draw (GTK_WIDGET (self));
-}
+  controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ());
+  gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (controller), 0);
+  g_signal_connect (controller, "pressed", G_CALLBACK (pressed), self);
+  g_signal_connect (controller, "released", G_CALLBACK (released), self);
+  gtk_widget_add_controller (GTK_WIDGET (self), controller);
 
-typedef struct
-{
-  int count;
-  graphene_point_t first;
-  graphene_point_t last;
-  gboolean has_close;
-  gboolean has_initial_move;
-} CountSegmentData;
+  controller = gtk_event_controller_motion_new ();
+  g_signal_connect (controller, "motion", G_CALLBACK (motion), self);
+  g_signal_connect (controller, "leave", G_CALLBACK (leave), self);
+  gtk_widget_add_controller (GTK_WIDGET (self), controller);
 
-static gboolean
-count_segments (GskPathOperation        op,
-                const graphene_point_t *pts,
-                gsize                   n_pts,
-                gpointer                data)
-{
-  CountSegmentData *d = data;
+  self->points = NULL;
+  self->point_data = NULL;
+  self->n_points = 0;
 
-  if (d->count == 0)
-    {
-      d->first = pts[0];
-      if (op == GSK_PATH_MOVE)
-        d->has_initial_move = TRUE;
-    }
+  self->actions = G_ACTION_MAP (g_simple_action_group_new ());
 
-  d->last = pts[n_pts - 1];
-  d->count++;
+  action = g_simple_action_new_stateful ("smooth", NULL, g_variant_new_boolean (FALSE));
+  g_signal_connect (action, "change-state", G_CALLBACK (set_smooth), self);
+  g_action_map_add_action (G_ACTION_MAP (self->actions), G_ACTION (action));
+  gtk_widget_insert_action_group (GTK_WIDGET (self), "point", G_ACTION_GROUP (self->actions));
 
-  if (op == GSK_PATH_CLOSE)
-    d->has_close = TRUE;
+  action = g_simple_action_new_stateful ("symmetric", NULL, g_variant_new_boolean (FALSE));
+  g_signal_connect (action, "change-state", G_CALLBACK (set_symmetric), self);
+  g_action_map_add_action (G_ACTION_MAP (self->actions), G_ACTION (action));
+  gtk_widget_insert_action_group (GTK_WIDGET (self), "point", G_ACTION_GROUP (self->actions));
 
-  return TRUE;
-}
+  action = g_simple_action_new_stateful ("operation", G_VARIANT_TYPE_STRING, g_variant_new_string ("curve"));
+  g_signal_connect (action, "change-state", G_CALLBACK (set_operation), self);
+  g_action_map_add_action (G_ACTION_MAP (self->actions), G_ACTION (action));
 
-typedef struct
-{
-  CurveEditor *editor;
-  int pos;
-} CopySegmentData;
+  action = g_simple_action_new ("remove", NULL);
+  g_signal_connect (action, "activate", G_CALLBACK (remove_point), self);
+  g_action_map_add_action (G_ACTION_MAP (self->actions), G_ACTION (action));
 
-static gboolean
-copy_segments (GskPathOperation        op,
-               const graphene_point_t *pts,
-               gsize                   n_pts,
-               gpointer                data)
-{
-  CopySegmentData *d = data;
+  gtk_widget_insert_action_group (GTK_WIDGET (self), "point", G_ACTION_GROUP (self->actions));
 
-  switch (op)
-    {
-    case GSK_PATH_MOVE:
-      if (d->pos != 0)
-        {
-          d->editor->point_data[d->pos / 3].op = MOVE;
-          d->editor->point_data[d->pos / 3].smooth = FALSE;
+  menu = g_menu_new ();
 
-          d->editor->points[d->pos++] = pts[0];
-          d->editor->points[d->pos++] = pts[0];
-          if (d->pos < d->editor->n_points)
-            d->editor->points[d->pos++] = pts[0];
-        }
-      break;
-    case GSK_PATH_CLOSE:
-      break;
-    case GSK_PATH_LINE:
-      d->editor->point_data[d->pos / 3].op = LINE;
-      d->editor->point_data[d->pos / 3].smooth = FALSE;
+  item = g_menu_item_new ("Smooth", "point.smooth");
+  g_menu_append_item (menu, item);
+  g_object_unref (item);
 
-      if (d->pos == 0)
-        d->editor->points[d->pos++] = pts[0];
+  item = g_menu_item_new ("Symmetric", "point.symmetric");
+  g_menu_append_item (menu, item);
+  g_object_unref (item);
 
-      d->editor->points[d->pos++] = pts[1];
-      d->editor->points[d->pos++] = pts[1];
-      if (d->pos < d->editor->n_points)
-        d->editor->points[d->pos++] = pts[1];
-      break;
-    case GSK_PATH_CURVE:
-      d->editor->point_data[d->pos / 3].op = CURVE;
-      d->editor->point_data[d->pos / 3].smooth = FALSE;
+  section = g_menu_new ();
 
-      if (d->pos == 0)
-        d->editor->points[d->pos++] = pts[0];
+  item = g_menu_item_new ("Move", "point.operation::move");
+  g_menu_append_item (section, item);
+  g_object_unref (item);
 
-      d->editor->points[d->pos++] = pts[1];
-      d->editor->points[d->pos++] = pts[2];
+  item = g_menu_item_new ("Line", "point.operation::line");
+  g_menu_append_item (section, item);
+  g_object_unref (item);
 
-      if (d->pos < d->editor->n_points)
-        d->editor->points[d->pos++] = pts[3];
-      break;
-    default:
-      g_assert_not_reached ();
-    }
+  item = g_menu_item_new ("Curve", "point.operation::curve");
+  g_menu_append_item (section, item);
+  g_object_unref (item);
 
-  return TRUE;
+  g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
+  g_object_unref (section);
+
+  item = g_menu_item_new ("Remove", "point.remove");
+  g_menu_append_item (section, item);
+  g_object_unref (item);
+
+  self->menu = gtk_popover_menu_new_from_model (G_MENU_MODEL (menu));
+  g_object_unref (menu);
+
+  gtk_widget_set_parent (self->menu, GTK_WIDGET (self));
 }
 
-/* Check if the points arount point currently satisy
- * smoothness conditions. Set PointData.smooth accordingly.
- */
-static void
-update_smoothness (CurveEditor *self,
-                   int          point)
+/* }}} */
+/* {{{ API */
+GtkWidget *
+curve_editor_new (void)
 {
-  Operation op, op1;
-  graphene_point_t *p, *p2, *p1;
-
-  p = &self->points[point];
-  op = self->point_data[point / 3].op;
-  op1 = self->point_data[((point - 1 + self->n_points) % self->n_points) / 3].op;
+  return g_object_new (curve_editor_get_type (), NULL);
+}
 
-  if (op == CURVE)
-    p2 = &self->points[(point + 1) % self->n_points];
-  else if (op == LINE)
-    p2 = &self->points[(point + 3) % self->n_points];
-  else
-    p2 = NULL;
+void
+curve_editor_set_edit (CurveEditor *self,
+                       gboolean     edit)
+{
+  int i;
 
-  if (op1 == CURVE)
-    p1 = &self->points[(point - 1 + self->n_points) % self->n_points];
-  else if (op1 == LINE)
-    p1 = &self->points[(point - 3 + self->n_points) % self->n_points];
-  else
-    p1 = NULL;
+  self->edit = edit;
+  if (!self->edit)
+    {
+      self->hovered = -1;
+      for (i = 0; i < self->n_points / 3; i++)
+        self->point_data[i].edit = FALSE;
+    }
 
-  if (p1 && p2)
-    self->point_data[point / 3].smooth = collinear (p, p1, p2);
-  else
-    self->point_data[point / 3].smooth = TRUE;
+  gtk_widget_queue_draw (GTK_WIDGET (self));
 }
 
 void
@@ -1470,3 +1477,5 @@ curve_editor_get_path (CurveEditor *self)
 
   return gsk_path_builder_free_to_path (builder);
 }
+/* }}} */
+/* vim:set foldmethod=marker expandtab: */


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