[gtk/wip/matthiasc/lottie-stroke: 14/17] curve-editor: Rewrite with conics support




commit 9c68cd9ec1fa53542e2a8ebca461f4dd7d6e82a0
Author: Matthias Clasen <mclasen redhat com>
Date:   Tue Dec 1 20:54:58 2020 -0500

    curve-editor: Rewrite with conics support
    
    Add support for editing conics. This ends up
    being far too much code, but it works.

 tests/curve-editor.c | 1720 ++++++++++++++++++++++++++++++++++++--------------
 1 file changed, 1240 insertions(+), 480 deletions(-)
---
diff --git a/tests/curve-editor.c b/tests/curve-editor.c
index adf42d7c7d..6ab229807b 100644
--- a/tests/curve-editor.c
+++ b/tests/curve-editor.c
@@ -6,38 +6,39 @@
 #define CLICK_RADIUS 8
 
 /* {{{ Types and structures */
-typedef enum
-{
-  MOVE,
-  LINE,
-  CURVE
-} Operation;
-
 static const char *
-op_to_string (Operation op)
+op_to_string (GskPathOperation op)
 {
   switch (op)
     {
-    case MOVE:
+    case GSK_PATH_MOVE:
       return "move";
-    case LINE:
+    case GSK_PATH_LINE:
       return "line";
-    case CURVE:
+    case GSK_PATH_CURVE:
       return "curve";
+    case GSK_PATH_CONIC:
+      return "conic";
+    case GSK_PATH_CLOSE:
+      return "close";
     default:
       g_assert_not_reached ();
     }
 }
 
-static Operation
+static GskPathOperation
 op_from_string (const char *s)
 {
   if (strcmp (s, "move") == 0)
-    return MOVE;
+    return GSK_PATH_MOVE;
   else if (strcmp (s, "line") == 0)
-    return LINE;
+    return GSK_PATH_LINE;
   else if (strcmp (s, "curve") == 0)
-    return CURVE;
+    return GSK_PATH_CURVE;
+  else if (strcmp (s, "conic") == 0)
+    return GSK_PATH_CONIC;
+  else if (strcmp (s, "close") == 0)
+    return GSK_PATH_CLOSE;
   else
     g_assert_not_reached ();
 }
@@ -83,33 +84,27 @@ point_type_from_string (const char *s)
     g_assert_not_reached ();
 }
 
-/* We don't store Bezier segments, but an array of points on
- * the line. Each point comes with its two neighboring control
- * points, so each Bezier segment contains p[1] and p[2] from
- * one point, and p[0] and p[1] from the next.
- *
- * The control points are irrelevant for MOVE and LINE segments.
- */
 typedef struct
 {
-  /* 0 and 2 are control points, 1 is the point on the line */
-  graphene_point_t p[3];
+  GskPathOperation op;
+  graphene_point_t p[4];
+  float weight;
   PointType type;
-  gboolean edit;
   int dragged;
   int hovered;
-  /* refers to the segment following the point */
-  Operation op;
-} PointData;
+} Segment;
 
 struct _CurveEditor
 {
   GtkWidget parent_instance;
-  GArray *points;
-  int dragged;
+  GArray *segments;
   int context;
+  float context_pos;
   gboolean edit;
+  int edited_point;
+  int edited_segment;
   int molded;
+  int dragged;
 
   GtkWidget *menu;
   GActionMap *actions;
@@ -145,6 +140,20 @@ closest_point (const graphene_point_t *p,
   q->y = a->y + t * (b->y - a->y);
 }
 
+static void
+find_point_on_line (const graphene_point_t *p1,
+                    const graphene_point_t *p2,
+                    const graphene_point_t *q,
+                    float                  *t)
+{
+  float tx = p2->x - p1->x;
+  float ty = p2->y - p1->y;
+  float sx = q->x - p1->x;
+  float sy = q->y - p1->y;
+
+  *t = (tx*sx + ty*sy) / (tx*tx + ty*ty);
+}
+
 /* Determine if p is on the line through a and b */
 static gboolean
 collinear (const graphene_point_t *p,
@@ -438,16 +447,171 @@ bezier_through (const graphene_point_t *S,
 
   find_control_points (t, &A, B, &C, S, E, C1, C2);
 }
+
+/* conics */
+
+static void
+get_conic_shoulder_point (const graphene_point_t  p[3],
+                          float                   w,
+                          graphene_point_t       *q)
+{
+  graphene_point_t m;
+
+  graphene_point_interpolate (&p[0], &p[2], 0.5, &m);
+  graphene_point_interpolate (&m, &p[1], w / (1 + w), q);
+}
+
+static void
+split_bezier3d_recurse (const graphene_point3d_t *p,
+                        int                       l,
+                        float                     t,
+                        graphene_point3d_t       *left,
+                        graphene_point3d_t       *right,
+                        int                      *lpos,
+                        int                      *rpos)
+{
+  if (l == 1)
+    {
+      left[*lpos] = p[0];
+      right[*rpos] = p[0];
+    }
+  else
+    {
+      graphene_point3d_t *np;
+      int i;
+
+      np = g_alloca (sizeof (graphene_point3d_t) * (l - 1));
+      for (i = 0; i < l - 1; i++)
+        {
+          if (i == 0)
+            {
+              left[*lpos] = p[i];
+              (*lpos)++;
+            }
+          if (i + 1 == l - 1)
+            {
+              right[*rpos] = p[i + 1];
+              (*rpos)--;
+            }
+          graphene_point3d_interpolate (&p[i], &p[i + 1], t, &np[i]);
+        }
+      split_bezier3d_recurse (np, l - 1, t, left, right, lpos, rpos);
+    }
+}
+
+static void
+split_bezier3d (const graphene_point3d_t *p,
+                int                       l,
+                float                     t,
+                graphene_point3d_t       *left,
+                graphene_point3d_t       *right)
+{
+  int lpos = 0;
+  int rpos = l - 1;
+  split_bezier3d_recurse (p, l, t, left, right, &lpos, &rpos);
+}
+
+static void
+split_conic (const graphene_point_t points[3], float weight,
+             float t,
+             graphene_point_t lp[3], float *lw,
+             graphene_point_t rp[3], float *rw)
+{
+  /* Given control points and weight for a rational quadratic
+   * Bezier and t, create two sets of the same that give the
+   * same curve as the original and split the curve at t.
+   */
+  graphene_point3d_t p[3];
+  graphene_point3d_t l[3], r[3];
+  int i;
+
+  /* do de Casteljau in homogeneous coordinates... */
+  for (i = 0; i < 3; i++)
+    {
+      p[i].x = points[i].x;
+      p[i].y = points[i].y;
+      p[i].z = 1;
+    }
+
+  p[1].x *= weight;
+  p[1].y *= weight;
+  p[1].z *= weight;
+
+  split_bezier3d (p, 3, t, l, r);
+
+  /* then project the control points down */
+  for (i = 0; i < 3; i++)
+    {
+      lp[i].x = l[i].x / l[i].z;
+      lp[i].y = l[i].y / l[i].z;
+      rp[i].x = r[i].x / r[i].z;
+      rp[i].y = r[i].y / r[i].z;
+    }
+
+  /* normalize the outer weights to be 1 by using
+   * the fact that weights w_i and c*w_i are equivalent
+   * for any nonzero constant c
+   */
+  for (i = 0; i < 3; i++)
+    {
+      l[i].z /= l[0].z;
+      r[i].z /= r[2].z;
+    }
+
+  /* normalize the inner weight to be 1 by using
+   * the fact that w_0*w_2/w_1^2 is a constant for
+   * all equivalent weights.
+   */
+  *lw = l[1].z / sqrt (l[2].z);
+  *rw = r[1].z / sqrt (r[0].z);
+}
+
 /* }}} */
 /* {{{ Utilities */
-static PointData *
-get_point (CurveEditor *self,
-           int          point)
+static Segment *
+get_segment (CurveEditor *self,
+             int          idx)
+{
+  idx = idx % (int)self->segments->len;
+  if (idx < 0)
+    idx += (int)self->segments->len;
+  return &g_array_index (self->segments, Segment, idx);
+}
+
+static void
+set_segment_start (CurveEditor      *self,
+                   int               idx,
+                   graphene_point_t *p)
+{
+  Segment *seg = get_segment (self, idx);
+  Segment *seg1 = get_segment (self, idx - 1);
+
+  seg->p[0] = *p;
+  seg1->p[3] = *p;
+}
+
+static const graphene_point_t *
+get_line_point (CurveEditor *self,
+                int          idx)
+{
+  Segment *seg = get_segment (self, idx);
+  return &seg->p[0];
+}
+
+static graphene_point_t *
+get_left_control_point (CurveEditor *self,
+                        int          idx)
+{
+  Segment *seg = get_segment (self, idx - 1);
+  return &seg->p[2];
+}
+
+static graphene_point_t *
+get_right_control_point (CurveEditor *self,
+                         int          idx)
 {
-  point = point % self->points->len;
-  if (point < 0)
-    point += self->points->len;
-  return &g_array_index (self->points, PointData, point);
+  Segment *seg = get_segment (self, idx);
+  return &seg->p[1];
 }
 
 static gboolean
@@ -455,87 +619,131 @@ point_is_visible (CurveEditor *self,
                   int          point,
                   int          point1)
 {
-  PointData *pd;
+  Segment *seg;
 
   if (!self->edit)
     return FALSE;
 
-  pd = get_point (self, point);
+  seg = get_segment (self, point);
   switch (point1)
     {
-    case 0:
-      if (!pd->edit)
-        return FALSE;
-      else
-        return get_point (self, point - 1)->op == CURVE;
-    case 1: /* point on curve */
+    case 0: /* point on curve */
       return TRUE;
+
+    case 1:
+      if (self->edited_segment == point &&
+          seg->op != GSK_PATH_LINE)
+        return TRUE;
+      if (seg->op == GSK_PATH_CONIC &&
+          (self->edited_point == point + 1 ||
+           (self->edited_point == 0 && point + 1 == self->segments->len)))
+        return TRUE;
+      if (self->edited_point == point &&
+          (seg->op == GSK_PATH_CURVE || seg->op == GSK_PATH_CONIC))
+        return TRUE;
+      break;
+
     case 2:
-      if (!pd->edit)
-        return FALSE;
-      else
-        return pd->op == CURVE;
+      if (self->edited_segment == point &&
+          seg->op != GSK_PATH_LINE)
+        return TRUE;
+      if (seg->op == GSK_PATH_CURVE &&
+          (self->edited_point == point + 1 ||
+           (self->edited_point == 0 && point + 1 == self->segments->len)))
+        return TRUE;
+      break;
+
     default:
       g_assert_not_reached ();
     }
+
+  return FALSE;
 }
 
 static void
 maintain_smoothness (CurveEditor *self,
                      int          point)
 {
-  PointData *pd;
-  Operation op, op1;
-  graphene_point_t *p, *c, *c2, *p2;
+  Segment *seg, *seg1;
+  const graphene_point_t *p, *p2;
+  graphene_point_t *c, *c2;
   float d;
 
-  pd = get_point (self, point);
+  seg = get_segment (self, point);
+  seg1 = get_segment (self, point - 1);
 
-  if (pd->type == CUSP)
+  if (seg->type == CUSP)
     return;
 
-  op = pd->op;
-  op1 = get_point (self, point - 1)->op;
+  if (seg->op == GSK_PATH_LINE && seg1->op == GSK_PATH_LINE)
+    return;
 
-  p = &pd->p[1];
-  c = &pd->p[0];
-  c2 = &pd->p[2];
+  p = &seg->p[0];
+  c = &seg1->p[2];
+  c2 = &seg->p[1];
 
-  if (op == CURVE && op1 == CURVE)
+  if (seg->op == GSK_PATH_CURVE && seg1->op == GSK_PATH_CURVE)
     {
       d = graphene_point_distance (c, p, NULL, NULL);
       opposite_point (p, c2, d, c);
     }
-  else if (op == CURVE && op1 == LINE)
+  else if (seg->op == GSK_PATH_CURVE)
     {
-      p2 = &get_point (self, point - 1)->p[1];
+      if (seg1->op == GSK_PATH_LINE)
+        p2 = &seg1->p[0];
+      else if (seg1->op == GSK_PATH_CONIC)
+        p2 = &seg1->p[1];
+      else
+        g_assert_not_reached ();
       d = graphene_point_distance (c2, p, NULL, NULL);
       opposite_point (p, p2, d, c2);
     }
-  else if (op == LINE && op1 == CURVE)
+  else if (seg1->op == GSK_PATH_CURVE)
     {
-      p2 = &get_point (self, point + 1)->p[1];
+      if (seg->op == GSK_PATH_LINE)
+        p2 = &seg->p[3];
+      else if (seg->op == GSK_PATH_CONIC)
+        p2 = &seg->p[1];
+      else
+        g_assert_not_reached ();
       d = graphene_point_distance (c, p, NULL, NULL);
       opposite_point (p, p2, d, c);
     }
+  else if (seg->op == GSK_PATH_CONIC && seg1->op == GSK_PATH_CONIC)
+    {
+      graphene_point_t h, a, b;
+
+      h.x = seg->p[0].x + seg->p[1].x - seg1->p[1].x;
+      h.y = seg->p[0].y + seg->p[1].y - seg1->p[1].y;
+      line_intersection (&seg->p[0], &h, &seg1->p[0], &seg1->p[1], &a);
+      line_intersection (&seg->p[0], &h, &seg->p[1], &seg->p[3], &b);
+
+      seg1->p[1] = a;
+      seg->p[1] = b;
+    }
 }
 
 static void
 maintain_symmetry (CurveEditor *self,
                    int          point)
 {
-  PointData *pd;
-  graphene_point_t *p, *c, *c2;
+  Segment *seg, *seg1;
+  const graphene_point_t *p;
+  graphene_point_t *c, *c2;
   double l1, l2, l;
 
-  pd = get_point (self, point);
+  seg = get_segment (self, point);
+  seg1 = get_segment (self, point - 1);
 
-  if (pd->type != SYMMETRIC)
+  if (seg->type != SYMMETRIC)
     return;
 
-  c = &pd->p[0];
-  p = &pd->p[1];
-  c2 = &pd->p[2];
+  if (seg->op != GSK_PATH_CURVE || seg1->op != GSK_PATH_CURVE)
+    return;
+
+  p = &seg->p[0];
+  c = &seg1->p[3];
+  c2 = &seg->p[1];
 
   l1 = graphene_point_distance (p, c, NULL, NULL);
   l2 = graphene_point_distance (p, c2, NULL, NULL);
@@ -558,34 +766,43 @@ static void
 update_automatic (CurveEditor *self,
                   int          point)
 {
-  PointData *pd, *pd1, *pd2;
+  Segment *seg;
+  const graphene_point_t *p, *p1, *p2;
   double l1, l2;
   graphene_point_t a;
+  graphene_point_t *c1, *c2;
 
-  pd = get_point (self, point);
+  seg = get_segment (self, point);
+
+  if (seg->type != AUTO)
+    return;
 
-  if (pd->type != AUTO)
+  if (seg->op != GSK_PATH_CURVE || get_segment (self, point - 1)->op != GSK_PATH_CURVE)
     return;
 
-  pd1 = get_point (self, point - 1);
-  pd2 = get_point (self, point + 1);
+  p = get_line_point (self, point);
+  c1 = get_left_control_point (self, point);
+  c2 = get_right_control_point (self, point);
 
-  l1 = graphene_point_distance (&pd->p[1], &pd1->p[1], NULL, NULL);
-  l2 = graphene_point_distance (&pd->p[1], &pd2->p[1], NULL, NULL);
+  p1 = get_line_point (self, point - 1);
+  p2 = get_line_point (self, point + 1);
 
-  a.x = pd2->p[1].x + (pd->p[1].x - pd1->p[1].x);
-  a.y = pd2->p[1].y + (pd->p[1].y - pd1->p[1].y);
+  l1 = graphene_point_distance (p, p1, NULL, NULL);
+  l2 = graphene_point_distance (p, p2, NULL, NULL);
 
-  scale_point (&pd->p[1], &a, l2/3, &pd->p[2]);
-  opposite_point (&pd->p[1], &a, l1/3, &pd->p[0]);
+  a.x = p2->x + (p->x - p1->x);
+  a.y = p2->y + (p->y - p1->y);
+
+  scale_point (p, &a, l2/3, c2);
+  opposite_point (p, &a, l1/3, c1);
 }
 
 static void
 maintain_automatic (CurveEditor *self,
                     int          point)
 {
-  if (get_point (self, point)->op != CURVE ||
-      get_point (self, point - 1)->op != CURVE)
+  if (get_segment (self, point)->op != GSK_PATH_CURVE ||
+      get_segment (self, point - 1)->op != GSK_PATH_CURVE)
     return;
 
   update_automatic (self, point);
@@ -593,6 +810,23 @@ maintain_automatic (CurveEditor *self,
   update_automatic (self, point + 1);
 }
 
+static void
+maintain_conic (CurveEditor *self,
+                int          idx)
+{
+  Segment *seg = get_segment (self, idx);
+  graphene_point_t p[3];
+
+  if (seg->op != GSK_PATH_CONIC)
+    return;
+
+  p[0] = seg->p[0];
+  p[1] = seg->p[1];
+  p[2] = seg->p[3];
+
+  get_conic_shoulder_point (p, seg->weight, &seg->p[2]);
+}
+
 /* Check if the points arount point currently satisfy
  * smoothness conditions. Set PointData.type accordingly.
  */
@@ -600,32 +834,35 @@ static void
 check_smoothness (CurveEditor *self,
                   int          point)
 {
-  Operation op, op1;
-  graphene_point_t *p1, *p2;
-  PointData *pd;
+  GskPathOperation op, op1;
+  const graphene_point_t *p, *p1, *p2;
+  Segment *seg, *seg1;
 
-  pd = get_point (self, point);
-  op = pd->op;
-  op1 = get_point (self, point - 1)->op;
+  seg = get_segment (self, point);
+  seg1 = get_segment (self, point - 1);
+  p = get_line_point (self, point);
 
-  if (op == CURVE)
-    p2 = &pd->p[2];
-  else if (op == LINE)
-    p2 = &get_point (self, point + 1)->p[1];
+  op = seg->op;
+  op1 = seg1->op;
+
+  if (op == GSK_PATH_CURVE)
+    p2 = get_right_control_point (self, point);
+  else if (op == GSK_PATH_LINE)
+    p2 = get_line_point (self, point + 1);
   else
     p2 = NULL;
 
-  if (op1 == CURVE)
-    p1 = &pd->p[0];
-  else if (op1 == LINE)
-    p1 = &get_point (self, point - 1)->p[1];
+  if (op1 == GSK_PATH_CURVE)
+    p1 = get_left_control_point (self, point);
+  else if (op1 == GSK_PATH_LINE)
+    p1 = get_line_point (self, point - 1);
   else
     p1 = NULL;
 
-  if (!p1 || !p2 || !collinear (&pd->p[1], p1, p2))
-    pd->type = CUSP;
+  if (!p1 || !p2 || !collinear (p, p1, p2))
+    seg->type = CUSP;
   else
-    pd->type = SMOOTH;
+    seg->type = SMOOTH;
 }
 
 static void
@@ -633,56 +870,90 @@ insert_point (CurveEditor *self,
               int          point,
               double       pos)
 {
-  PointData *pd, *pd1, *pd2;
-  graphene_point_t points[4];
-  PointData np;
+  Segment *seg, *seg1, *seg2;
+  Segment ns;
 
-  pd = get_point (self, point);
-  if (pd->op == MOVE)
+  seg = get_segment (self, point);
+  if (seg->op == GSK_PATH_MOVE)
     return;
 
-  pd1 = get_point (self, point + 1);
-  points[0] = pd->p[1];
-  points[1] = pd->p[2];
-  points[2] = pd1->p[0];
-  points[3] = pd1->p[1];
-
-  g_array_insert_val (self->points, point + 1, np);
+  g_array_insert_val (self->segments, point + 1, ns);
 
-  pd = get_point (self, point);
-  pd1 = get_point (self, point + 1);
-  pd2 = get_point (self, point + 2);
+  seg = get_segment (self, point);
+  seg1 = get_segment (self, point + 1);
+  seg2 = get_segment (self, point + 2);
 
-  pd1->type = SMOOTH;
-  pd1->hovered = -1;
-  pd1->dragged = -1;
+  seg1->type = SMOOTH;
+  seg1->hovered = -1;
+  seg1->dragged = -1;
 
-  if (pd->op == LINE)
-    {
-      pd1->op = LINE;
-      graphene_point_interpolate (&points[0], &points[3], pos, &pd1->p[1]);
-    }
-  else if (pd->op == CURVE)
+  switch (seg->op)
     {
-      graphene_point_t left[4];
-      graphene_point_t right[4];
-      int left_pos = 0;
-      int right_pos = 0;
-
-      pd1->op = CURVE;
-
-      split_bezier (points, 4, pos, left, &left_pos, right, &right_pos);
-
-      pd->p[1] = left[0];
-      pd->p[2] = left[1];
-      pd1->p[0] = left[2];
-      pd1->p[1] = left[3];
-      pd1->p[2] = right[2];
-      pd2->p[0] = right[1];
-      pd2->p[1] = right[0];
+    case GSK_PATH_LINE:
+      seg1->op = GSK_PATH_LINE;
+
+      graphene_point_interpolate (&seg->p[0], &seg->p[3], pos, &seg1->p[0]);
+      seg->p[3] = seg->p[0];
+      seg1->p[3] = seg2->p[0];
+      break;
+
+    case GSK_PATH_CURVE:
+      {
+        graphene_point_t left[4];
+        graphene_point_t right[4];
+        int left_pos = 0;
+        int right_pos = 0;
+
+        seg1->op = GSK_PATH_CURVE;
+
+        split_bezier (seg->p, 4, pos, left, &left_pos, right, &right_pos);
+
+        seg->p[0] = left[0];
+        seg->p[1] = left[1];
+        seg->p[2] = left[2];
+        seg->p[3] = left[3];
+        seg1->p[0] = right[3];
+        seg1->p[1] = right[2];
+        seg1->p[2] = right[1];
+        seg1->p[3] = right[0];
+      }
+      break;
+
+    case GSK_PATH_CONIC:
+      {
+        graphene_point_t points[3];
+        graphene_point_t left[3];
+        graphene_point_t right[3];
+        float lw, rw;
+
+        seg1->op = GSK_PATH_CONIC;
+
+        points[0] = seg->p[0];
+        points[1] = seg->p[1];
+        points[2] = seg->p[3];
+        split_conic (points, seg->weight, pos, left, &lw, right, &rw);
+
+        seg->p[0] = left[0];
+        seg->p[1] = left[1];
+        seg->p[3] = left[2];
+        seg1->p[0] = right[0];
+        seg1->p[1] = right[1];
+        seg1->p[3] = right[2];
+
+        seg->weight = lw;
+        seg1->weight = rw;
+
+        get_conic_shoulder_point (seg->p, seg->weight, &seg->p[2]);
+        get_conic_shoulder_point (seg1->p, seg1->weight, &seg1->p[2]);
+      }
+      break;
+
+    case GSK_PATH_MOVE:
+    case GSK_PATH_CLOSE:
+    default:
+      g_assert_not_reached ();
+      break;
     }
-  else
-    g_assert_not_reached ();
 
   maintain_smoothness (self, point + 1);
   maintain_automatic (self, point + 1);
@@ -694,40 +965,58 @@ static void
 remove_point (CurveEditor *self,
               int          point)
 {
-  g_array_remove_index (self->points, point);
+  Segment *seg;
+  graphene_point_t c, p;
+
+  seg = get_segment (self, point);
+  c = seg->p[2];
+  p = seg->p[3];
+
+  g_array_remove_index (self->segments, point);
+
+  seg = get_segment (self, point - 1);
+  seg->p[2] = c;
+  seg->p[3] = p;
 
   maintain_smoothness (self, point);
   maintain_automatic (self, point);
 }
 
 /* }}} */
-/* {{{ GskPath helpers */ 
+/* {{{ GskPath helpers */
 static void
 curve_editor_add_segment (CurveEditor    *self,
                           GskPathBuilder *builder,
                           int             point)
 {
-  PointData *pd1, *pd;
+  Segment *seg;
 
-  pd1 = get_point (self, point);
-  pd = get_point (self, point + 1);
+  seg = get_segment (self, point);
 
-  gsk_path_builder_move_to (builder, pd1->p[1].x, pd1->p[1].y);
+  gsk_path_builder_move_to (builder, seg->p[0].x, seg->p[0].y);
 
-  switch (pd1->op)
+  switch (seg->op)
     {
-    case LINE:
-      gsk_path_builder_line_to (builder, pd->p[1].x, pd->p[1].y);
+    case GSK_PATH_LINE:
+      gsk_path_builder_line_to (builder, seg->p[3].x, seg->p[3].y);
       break;
 
-    case CURVE:
+    case GSK_PATH_CURVE:
       gsk_path_builder_curve_to (builder,
-                                 pd1->p[2].x, pd1->p[2].y,
-                                 pd->p[0].x, pd->p[0].y,
-                                 pd->p[1].x, pd->p[1].y);
+                                 seg->p[1].x, seg->p[1].y,
+                                 seg->p[2].x, seg->p[2].y,
+                                 seg->p[3].x, seg->p[3].y);
+      break;
+
+    case GSK_PATH_CONIC:
+      gsk_path_builder_conic_to (builder,
+                                 seg->p[1].x, seg->p[1].y,
+                                 seg->p[3].x, seg->p[3].y,
+                                 seg->weight);
       break;
 
-    case MOVE:
+    case GSK_PATH_MOVE:
+    case GSK_PATH_CLOSE:
     default:
       break;
     }
@@ -739,33 +1028,38 @@ curve_editor_add_path (CurveEditor    *self,
 {
   int i;
 
-  for (i = 0; i < self->points->len; i++)
+  for (i = 0; i < self->segments->len; i++)
     {
-      PointData *pd1, *pd;
-
-      pd1 = get_point (self, i);
-      pd = get_point (self, i + 1);
+      Segment *seg = get_segment (self, i);
 
       if (i == 0)
-        gsk_path_builder_move_to (builder, pd1->p[1].x, pd1->p[1].y);
+        gsk_path_builder_move_to (builder, seg->p[0].x, seg->p[0].y);
 
-      switch (pd1->op)
+      switch (seg->op)
         {
-        case MOVE:
-          gsk_path_builder_move_to (builder, pd->p[1].x, pd->p[1].y);
+        case GSK_PATH_MOVE:
+          gsk_path_builder_move_to (builder, seg->p[3].x, seg->p[3].y);
           break;
 
-        case LINE:
-          gsk_path_builder_line_to (builder, pd->p[1].x, pd->p[1].y);
+        case GSK_PATH_LINE:
+          gsk_path_builder_line_to (builder, seg->p[3].x, seg->p[3].y);
           break;
 
-        case CURVE:
+        case GSK_PATH_CURVE:
           gsk_path_builder_curve_to (builder,
-                                     pd1->p[2].x, pd1->p[2].y,
-                                     pd->p[0].x, pd->p[0].y,
-                                     pd->p[1].x, pd->p[1].y);
+                                     seg->p[1].x, seg->p[1].y,
+                                     seg->p[2].x, seg->p[2].y,
+                                     seg->p[3].x, seg->p[3].y);
           break;
 
+        case GSK_PATH_CONIC:
+          gsk_path_builder_conic_to (builder,
+                                     seg->p[1].x, seg->p[1].y,
+                                     seg->p[3].x, seg->p[3].y,
+                                     seg->weight);
+          break;
+
+        case GSK_PATH_CLOSE:
         default:
           g_assert_not_reached ();
         }
@@ -788,7 +1082,7 @@ find_closest_segment (CurveEditor      *self,
   gboolean found = FALSE;
   int i;
 
-  for (i = 0; i < self->points->len; i++)
+  for (i = 0; i < self->segments->len; i++)
     {
       GskPathBuilder *builder;
       GskPath *path;
@@ -836,23 +1130,23 @@ drag_begin (GtkGestureDrag *gesture,
   int i, j;
   graphene_point_t p = GRAPHENE_POINT_INIT (start_x, start_y);
   float t;
-  int point;
+  int idx;
 
   if (!self->edit)
     return;
 
-  for (i = 0; i < self->points->len; i++)
+  for (i = 0; i < self->segments->len; i++)
     {
-      PointData *pd = get_point (self, i);
+      Segment *seg = get_segment (self, i);
 
       for (j = 0; j < 3; j++)
         {
-          if (graphene_point_distance (&pd->p[j], &p, NULL, NULL) < CLICK_RADIUS)
+          if (graphene_point_distance (&seg->p[j], &p, NULL, NULL) < CLICK_RADIUS)
             {
               if (point_is_visible (self, i, j))
                 {
                   self->dragged = i;
-                  pd->dragged = j;
+                  seg->dragged = j;
                   gtk_widget_queue_draw (GTK_WIDGET (self));
                 }
               return;
@@ -860,11 +1154,11 @@ drag_begin (GtkGestureDrag *gesture,
         }
     }
 
-  if (find_closest_segment (self, &p, CLICK_RADIUS, NULL, &point, &t))
+  if (find_closest_segment (self, &p, CLICK_RADIUS, NULL, &idx, &t))
     {
       /* Can't bend a straight line */
-      get_point (self, point)->op = CURVE;
-      self->molded = point;
+      get_segment (self, idx)->op = GSK_PATH_CURVE;
+      self->molded = idx;
       return;
     }
 
@@ -872,179 +1166,400 @@ drag_begin (GtkGestureDrag *gesture,
 }
 
 static void
-drag_control_point (CurveEditor *self,
-                    double       x,
-                    double       y)
+drag_line_point (CurveEditor *self,
+                 double       x,
+                 double       y)
 {
-  double dx, dy;
-  graphene_point_t *c, *p, *d;
-  double l1, l2;
-  PointData *pd;
+  /* dragged point is on curve */
+  Segment *seg, *seg1, *seg2, *seg11;
+  const graphene_point_t *d, *p;
+  graphene_point_t *c;
+  float l1, l2, dx, dy;
 
-  pd = get_point (self, self->dragged);
-  d = &pd->p[pd->dragged];
+  seg = get_segment (self, self->dragged);
+  d = get_line_point (self, self->dragged);
 
   /* before moving the point, record the distances to its neighbors, since
    * we may want to preserve those
    */
-  l1 = graphene_point_distance (&pd->p[1], &pd->p[0], NULL, NULL);
-  l2 = graphene_point_distance (&pd->p[1], &pd->p[2], NULL, NULL);
+  l1 = graphene_point_distance (d, get_left_control_point (self, self->dragged), NULL, NULL);
+  l2 = graphene_point_distance (d, get_right_control_point (self, self->dragged), NULL, NULL);
 
   dx = x - d->x;
   dy = y - d->y;
 
-  if (pd->dragged == 1)
-    {
-      /* dragged point is on curve */
-
-      Operation op, op1, op11, op2;
-      PointData *pd1, *pd2;
-
-      /* first move the point itself */
-      d->x = x;
-      d->y = y;
+  /* first move the point itself */
+  set_segment_start (self, self->dragged, &GRAPHENE_POINT_INIT (x, y));
 
-      /* adjust control points as needed */
-      pd1 = get_point (self, self->dragged - 1);
-      pd2 = get_point (self, self->dragged + 1);
+  /* adjust control points as needed */
+  seg1 = get_segment (self, self->dragged - 1);
+  seg2 = get_segment (self, self->dragged + 1);
 
-      op = pd->op;
-      op1 = pd1->op;
-      op2 = pd2->op;
+  if (seg1->op == GSK_PATH_LINE)
+    {
+      /* the other endpoint of the line */
+      p = get_line_point (self, self->dragged - 1);
+      c = get_right_control_point (self, self->dragged);
 
-      if (op1 == LINE)
+      if (seg->op == GSK_PATH_CURVE && seg->type != CUSP)
         {
-          /* the other endpoint of the line */
-          p = &pd1->p[1];
-
-          if (op == CURVE && pd->type != CUSP)
-            {
-              /* adjust the control point after the line segment */
-              opposite_point (d, p, l2, &pd->p[2]);
-            }
+          opposite_point (d, p, l2, c);
+        }
+      else if (seg->op == GSK_PATH_CONIC && seg->type != CUSP)
+        {
+          graphene_point_t u;
+          line_intersection (&seg1->p[0], &seg1->p[3], &seg->p[3], &seg->p[1], &u);
+          if (u.x != NAN)
+            seg->p[1] = u;
           else
             {
-              pd->p[2].x += dx;
-              pd->p[2].y += dy;
+              seg->p[1].x += dx;
+              seg->p[1].y += dy;
             }
 
-          pd->p[0].x += dx;
-          pd->p[0].y += dy;
+          maintain_conic (self, self->dragged);
+        }
+      else
+        {
+          c->x += dx;
+          c->y += dy;
+        }
 
-          op11 = get_point (self, self->dragged - 2)->op;
+      /* always move the other control point along */
+      c = get_left_control_point (self, self->dragged);
+      c->x += dx;
+      c->y += dy;
 
-          if (op11 == CURVE && pd1->type != CUSP)
-            {
-              double l;
+      /* handle the far end of the line */
+      seg11 = get_segment (self, self->dragged - 2);
 
-              /* adjust the control point before the line segment */
-              l = graphene_point_distance (&pd1->p[0], p, NULL, NULL);
-              opposite_point (p, d, l, &pd1->p[0]);
-            }
+      if (seg11->op == GSK_PATH_CURVE && seg1->type != CUSP)
+        {
+          double l;
+          const graphene_point_t *p2;
+          graphene_point_t *c2;
+
+          p2 = get_line_point (self, self->dragged - 1);
+          c2 = get_left_control_point (self, self->dragged - 1);
+          /* adjust the control point before the line segment */
+          l = graphene_point_distance (c2, p2, NULL, NULL);
+          opposite_point (p2, d, l, c2);
         }
-
-      if (op == LINE)
+      else if (seg11->op == GSK_PATH_CONIC && seg1->type != CUSP)
         {
-          /* the other endpoint of the line */
-          p = &pd2->p[1];
+          graphene_point_t u;
+          line_intersection (&seg11->p[0], &seg11->p[1], &seg1->p[3], &seg1->p[0], &u);
+          if (u.x != NAN)
+            seg11->p[1] = u;
 
-          if (op1 == CURVE && pd->type != CUSP)
-            {
-              /* adjust the control point before the line segment */
-              opposite_point (d, p, l1, &pd->p[0]);
-            }
-          else
-            {
-              pd->p[0].x += dx;
-              pd->p[0].y += dy;
-            }
+          maintain_conic (self, self->dragged - 2);
+        }
+    }
 
-          pd->p[2].x += dx;
-          pd->p[2].y += dy;
+  if (seg->op == GSK_PATH_LINE)
+    {
+      /* the other endpoint of the line */
+      p = get_line_point (self, self->dragged + 1);
+      c = get_left_control_point (self, self->dragged);
 
-          if (op2 == CURVE && pd2->type != CUSP)
+      if (seg1->op == GSK_PATH_CURVE && seg->type != CUSP)
+        {
+          /* adjust the control point before the line segment */
+          opposite_point (d, p, l1, c);
+        }
+      else if (seg1->op == GSK_PATH_CONIC && seg->type != CUSP)
+        {
+          graphene_point_t u;
+          line_intersection (&seg1->p[0], &seg1->p[1], &seg->p[0], &seg->p[3], &u);
+          if (u.x != NAN)
+            seg1->p[1] = u;
+          else
             {
-              double l;
-
-              /* adjust the control point after the line segment */
-              l = graphene_point_distance (&pd2->p[2], p, NULL, NULL);
-              opposite_point (p, d, l, &pd2->p[2]);
+              seg1->p[1].x += dx;
+              seg1->p[1].y += dy;
             }
+
+          maintain_conic (self, self->dragged);
+        }
+      else if (seg1->op == GSK_PATH_CURVE)
+        {
+          c->x += dx;
+          c->y += dy;
         }
 
-      if (op1 != LINE && op != LINE)
+      /* always move the other control point along */
+      c = get_right_control_point (self, self->dragged);
+      c->x += dx;
+      c->x += dy;
+
+      /* handle the other end of the line */
+      if (seg2->op == GSK_PATH_CURVE && seg2->type != CUSP)
         {
-          pd->p[0].x += dx;
-          pd->p[0].y += dy;
-          pd->p[2].x += dx;
-          pd->p[2].y += dy;
+          double l;
+
+          /* adjust the control point after the line segment */
+          c = get_right_control_point (self, self->dragged + 1);
+          l = graphene_point_distance (c, p, NULL, NULL);
+          opposite_point (p, d, l, c);
         }
+      else if (seg2->op == GSK_PATH_CONIC && seg2->type != CUSP)
+        {
+          graphene_point_t u;
+          line_intersection (&seg->p[0], &seg->p[3], &seg2->p[1], &seg2->p[3], &u);
+          if (u.x != NAN)
+            seg2->p[1] = u;
 
-      maintain_automatic (self, self->dragged);
+          maintain_conic (self, self->dragged + 1);
+        }
     }
-  else
+
+  if (seg1->op != GSK_PATH_LINE && seg->op != GSK_PATH_LINE)
     {
-      /* dragged point is a control point */
+      if (seg1->op == GSK_PATH_CURVE)
+        {
+          c = &seg1->p[2];
+          c->x += dx;
+          c->y += dy;
+        }
+      else if (seg1->op == GSK_PATH_CONIC && seg->type != CUSP)
+        {
+          graphene_point_t a, b;
 
-      graphene_point_t *p1;
-      Operation op, op1;
+          a.x = seg1->p[1].x + dx;
+          a.y = seg1->p[1].y + dy;
+          line_intersection (&seg->p[0], &a, &seg1->p[0], &seg1->p[1], &b);
+          seg1->p[1] = b;
+        }
 
-      if (pd->dragged == 0)
+      if (seg->op == GSK_PATH_CURVE)
         {
-          c = &pd->p[2];
-          p = &pd->p[1];
-
-          op = get_point (self, self->dragged - 1)->op;
-          op1 = get_point (self, self->dragged)->op;
-          p1 = &get_point (self, self->dragged + 1)->p[1];
+          c = &seg->p[1];
+          c->x += dx;
+          c->y += dy;
         }
-      else if (pd->dragged == 2)
+      else if (seg->op == GSK_PATH_CONIC && seg->type != CUSP)
         {
-          c = &pd->p[0];
-          p = &pd->p[1];
+          graphene_point_t a, b;
 
-          op = get_point (self, self->dragged)->op;
-          op1 = get_point (self, self->dragged - 1)->op;
-          p1 = &get_point (self, self->dragged - 1)->p[1];
+          a.x = seg->p[1].x + dx;
+          a.y = seg->p[1].y + dy;
+          line_intersection (&seg->p[3], &seg->p[1], &a, &seg->p[0], &b);
+          seg->p[1] = b;
         }
-      else
-        g_assert_not_reached ();
+    }
+
+  maintain_smoothness (self, self->dragged);
+  maintain_automatic (self, self->dragged);
+  maintain_conic (self, self->dragged);
+  maintain_conic (self, self->dragged - 1);
+}
+
+static void
+drag_conic_point (CurveEditor *self,
+                  float        x,
+                  float        y)
+{
+  Segment *seg, *seg1, *seg2;
+  graphene_point_t *d, *c1;
+  float l;
 
-      if (op == CURVE && pd->type != CUSP)
+  seg = get_segment (self, self->dragged);
+  g_assert (seg->op == GSK_PATH_CONIC);
+  d = &seg->p[seg->dragged];
+
+  seg1 = get_segment (self, self->dragged + 1);
+  seg2 = get_segment (self, self->dragged - 1);
+
+  if (seg->dragged == 1)
+    {
+      if (seg->type != CUSP && seg2->op == GSK_PATH_LINE)
         {
-          if (op1 == CURVE)
-            {
-              double l;
+          /* control point must be on the line of seg2 */
 
-              /* first move the point itself */
-              d->x = x;
-              d->y = y;
+          if (seg1->type != CUSP && seg1->op == GSK_PATH_LINE)
+            {
+              graphene_point_t c;
 
-              /* then adjust the other control point */
-              if (pd->type == SYMMETRIC)
-                l = graphene_point_distance (d, p, NULL, NULL);
+              line_intersection (&seg1->p[0], &seg1->p[3], &seg2->p[3], &seg2->p[0], &c);
+              if (c.x != NAN)
+                *d = c; /* unmoveable */
               else
-                l = graphene_point_distance (c, p, NULL, NULL);
+                {
+                  closest_point (&GRAPHENE_POINT_INIT (x, y), &seg1->p[0], &seg1->p[3], &c);
+                  *d = c;
+                }
+            }
+          else
+            {
+              graphene_point_t c;
+
+              closest_point (&GRAPHENE_POINT_INIT (x, y), &seg2->p[0], &seg2->p[3], &c);
+              *d = c;
 
-              opposite_point (p, d, l, c);
+              if (seg1->type != CUSP)
+                {
+                  l = graphene_point_distance (&seg1->p[0], &seg1->p[1], NULL, NULL);
+                  opposite_point (&seg1->p[0], d, l, &seg1->p[1]);
+                }
             }
-          else if (op1 == LINE)
+        }
+      else if (seg1->type != CUSP && seg1->op == GSK_PATH_LINE)
+        {
+          graphene_point_t c;
+
+          closest_point (&GRAPHENE_POINT_INIT (x, y), &seg1->p[0], &seg1->p[3], &c);
+          *d = c;
+
+          if (seg2->type != CUSP)
             {
-              graphene_point_t m = GRAPHENE_POINT_INIT (x, y);
-              closest_point (&m, p, p1, d);
+              if (seg2->op == GSK_PATH_CURVE)
+                c1 = &seg2->p[2];
+              else if (seg2->op == GSK_PATH_CONIC)
+                c1 = &seg2->p[1];
+              else
+                g_assert_not_reached ();
+              l = graphene_point_distance (&seg2->p[3], c1, NULL, NULL);
+              opposite_point (&seg2->p[3], d, l, c1);
             }
-          else
+        }
+      else
+        {
+          /* unconstrained */
+          d->x = x;
+          d->y = y;
+
+          if (seg1->type != CUSP)
             {
-              d->x = x;
-              d->y = y;
+              l = graphene_point_distance (&seg1->p[0], &seg1->p[1], NULL, NULL);
+              opposite_point (&seg1->p[0], d, l, &seg1->p[1]);
+            }
+
+          if (seg2->type != CUSP)
+            {
+              if (seg2->op == GSK_PATH_CURVE)
+                c1 = &seg2->p[2];
+              else if (seg2->op == GSK_PATH_CONIC)
+                c1 = &seg2->p[1];
+              else
+                g_assert_not_reached ();
+              l = graphene_point_distance (&seg2->p[3], c1, NULL, NULL);
+              opposite_point (&seg2->p[3], d, l, c1);
             }
         }
+    }
+  else if (seg->dragged == 2)
+    {
+      /* dragging the shoulder point */
+      graphene_point_t m;
+      float t;
+
+      graphene_point_interpolate (&seg->p[0], &seg->p[3], 0.5, &m);
+      find_point_on_line (&m, &seg->p[1], &GRAPHENE_POINT_INIT (x, y), &t);
+      t = CLAMP (t, 0, 0.9);
+      seg->weight = - t / (t - 1);
+    }
+
+  maintain_conic (self, self->dragged);
+}
+
+static void
+drag_control_point (CurveEditor *self,
+                    float        x,
+                    float        y)
+{
+  /* dragged point is a control point */
+  Segment *seg, *seg1;
+  const graphene_point_t *p, *p1;
+  graphene_point_t *c, *d;
+  PointType type;
+
+  seg = get_segment (self, self->dragged);
+  g_assert (seg->op == GSK_PATH_CURVE);
+  d = &seg->p[seg->dragged];
+
+  if (seg->dragged == 2)
+    {
+      seg1 = get_segment (self, self->dragged + 1);
+      p = &seg1->p[0];
+      c = &seg1->p[1];
+      type = seg1->type;
+      p1 = get_line_point (self, self->dragged + 2);
+    }
+  else if (seg->dragged == 1)
+    {
+      seg1 = get_segment (self, self->dragged - 1);
+      if (seg1->op == GSK_PATH_CONIC)
+        c = &seg1->p[1];
+      else
+        c = &seg1->p[2];
+      p = &seg->p[0];
+      type = seg->type;
+      p1 = &seg1->p[0];
+    }
+  else
+    g_assert_not_reached ();
+
+  if (type != CUSP)
+    {
+      if (seg1->op == GSK_PATH_CURVE)
+        {
+          double l;
+
+          /* first move the point itself */
+          d->x = x;
+          d->y = y;
+
+          /* then adjust the other control point */
+          if (type == 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 (seg1->op == GSK_PATH_CONIC)
+        {
+          graphene_point_t u;
+
+          d->x = x;
+          d->y = y;
+          line_intersection (p1, c, p, d, &u);
+          *c = u;
+
+          maintain_conic (self, self->dragged - 1);
+          maintain_conic (self, self->dragged + 1);
+        }
+      else if (seg1->op == GSK_PATH_LINE)
+        {
+          graphene_point_t m = GRAPHENE_POINT_INIT (x, y);
+          closest_point (&m, p, p1, d);
+        }
       else
         {
           d->x = x;
           d->y = y;
         }
     }
+  else
+    {
+      d->x = x;
+      d->y = y;
+    }
+}
+
+static void
+drag_point (CurveEditor *self,
+            double       x,
+            double       y)
+{
+  Segment *seg = get_segment (self, self->dragged);
+
+  if (seg->dragged == 0)
+    drag_line_point (self, x, y);
+  else if (seg->op == GSK_PATH_CONIC)
+    drag_conic_point (self, x, y);
+  else
+    drag_control_point (self, x, y);
 }
 
 static void
@@ -1052,65 +1567,70 @@ drag_curve (CurveEditor *self,
             double       x,
             double       y)
 {
-  PointData *pd, *pd1, *pd2, *pd3;
   graphene_point_t *S, *E;
   graphene_point_t B, C1, C2;
   double l;
+  Segment *seg, *seg1, *seg2;
+
+  seg = get_segment (self, self->molded);
+  seg1 = get_segment (self, self->molded + 1);
+  seg2 = get_segment (self, self->molded - 1);
 
-  pd = get_point (self, self->molded);
-  pd1 = get_point (self, self->molded + 1);
-  pd2 = get_point (self, self->molded - 1);
-  pd3 = get_point (self, self->molded + 2);
+  if (seg->op == GSK_PATH_CONIC)
+    {
+      /* FIXME */
+      return;
+    }
 
-  S = &pd->p[1];
+  S = &seg->p[0];
   B = GRAPHENE_POINT_INIT (x, y);
-  E = &pd1->p[1];
+  E = &seg->p[3];
 
   bezier_through (S, &B, E, &C1, &C2);
 
-  pd->p[2] = C1;
-  pd1->p[0] = C2;
+  seg->p[1] = C1;
+  seg->p[2] = C2;
 
   /* When the neighboring segments are lines, we can't actually
    * use C1 and C2 as-is, since we need control points to lie
    * on the line. So we just use their distance. This makes our
    * point B not quite match anymore, but we're overconstrained.
    */
-  if (pd2->op == LINE)
+  if (seg2->op == GSK_PATH_LINE)
     {
-      l = graphene_point_distance (&pd->p[1], &pd->p[2], NULL, NULL);
-      if (three_point_angle (&pd->p[1], &pd2->p[1], &B) > 0)
-        scale_point (&pd->p[1], &pd2->p[1], l, &pd->p[2]);
+      l = graphene_point_distance (&seg->p[3], &C1, NULL, NULL);
+      if (three_point_angle (&seg2->p[3], &seg2->p[0], &B) > 0)
+        scale_point (&seg2->p[3], &seg2->p[0], l, &seg->p[1]);
       else
-        opposite_point (&pd->p[1], &pd2->p[1], l, &pd->p[2]);
+        opposite_point (&seg2->p[3], &seg2->p[0], l, &seg->p[1]);
     }
 
-  if (pd1->op == LINE)
+  if (seg1->op == GSK_PATH_LINE)
     {
-      l = graphene_point_distance (&pd1->p[1], &pd1->p[0], NULL, NULL);
-      if (three_point_angle (&pd1->p[1], &pd3->p[1], &B) > 0)
-        scale_point (&pd1->p[1], &pd3->p[1], l, &pd1->p[0]);
+      l = graphene_point_distance (&seg->p[0], &C2, NULL, NULL);
+      if (three_point_angle (&seg1->p[0], &seg1->p[3], &B) > 0)
+        scale_point (&seg1->p[0], &seg1->p[3], l, &seg->p[2]);
       else
-        opposite_point (&pd1->p[1], &pd3->p[1], l, &pd1->p[0]);
+        opposite_point (&seg1->p[0], &seg1->p[3], l, &seg->p[2]);
     }
 
   /* Maintain smoothness and symmetry */
-  if (pd->type != CUSP)
+  if (seg->type != CUSP)
     {
-      if (pd->type == SYMMETRIC)
-        l = graphene_point_distance (&pd->p[1], &pd->p[2], NULL, NULL);
+      if (seg->type == SYMMETRIC)
+        l = graphene_point_distance (&seg->p[0], &seg->p[1], NULL, NULL);
       else
-        l = graphene_point_distance (&pd->p[1], &pd->p[0], NULL, NULL);
-      opposite_point (&pd->p[1], &pd->p[2], l, &pd->p[0]);
+        l = graphene_point_distance (&seg->p[0], &seg2->p[2], NULL, NULL);
+      opposite_point (&seg->p[0], &seg->p[1], l, &seg2->p[2]);
     }
 
-  if (pd1->type != CUSP)
+  if (seg1->type != CUSP)
     {
-      if (pd1->type == SYMMETRIC)
-        l = graphene_point_distance (&pd1->p[1], &pd1->p[0], NULL, NULL);
+      if (seg1->type == SYMMETRIC)
+        l = graphene_point_distance (&seg->p[3], &seg->p[2], NULL, NULL);
       else
-        l = graphene_point_distance (&pd1->p[1], &pd1->p[2], NULL, NULL);
-      opposite_point (&pd1->p[1], &pd1->p[0], l, &pd1->p[2]);
+        l = graphene_point_distance (&seg->p[3], &seg1->p[1], NULL, NULL);
+      opposite_point (&seg->p[3], &seg->p[2], l, &seg1->p[1]);
     }
 }
 
@@ -1130,7 +1650,7 @@ drag_update (GtkGestureDrag *gesture,
   if (self->dragged != -1)
     {
       gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
-      drag_control_point (self, x, y);
+      drag_point (self, x, y);
       gtk_widget_queue_draw (GTK_WIDGET (self));
     }
   else if (self->molded != -1)
@@ -1160,7 +1680,7 @@ set_point_type (GSimpleAction *action,
 {
   CurveEditor *self = CURVE_EDITOR (data);
 
-  get_point (self, self->context)->type = point_type_from_string (g_variant_get_string (value, NULL));
+  get_segment (self, self->context)->type = point_type_from_string (g_variant_get_string (value, NULL));
 
   maintain_smoothness (self, self->context);
   maintain_symmetry (self, self->context);
@@ -1175,8 +1695,14 @@ set_operation (GSimpleAction *action,
                gpointer       data)
 {
   CurveEditor *self = CURVE_EDITOR (data);
+  Segment *seg = get_segment (self, self->context);
+
+  seg->op = op_from_string (g_variant_get_string (value, NULL));
 
-  get_point (self, self->context)->op = op_from_string (g_variant_get_string (value, NULL));
+  if (seg->op == GSK_PATH_CONIC && seg->weight == 0)
+    seg->weight = 1;
+
+  maintain_conic (self, self->context);
 
   maintain_smoothness (self, self->context);
   maintain_smoothness (self, self->context + 1);
@@ -1186,6 +1712,18 @@ set_operation (GSimpleAction *action,
   gtk_widget_queue_draw (GTK_WIDGET (self));
 }
 
+static void
+insert_new_point (GSimpleAction *action,
+                  GVariant      *value,
+                  gpointer       data)
+{
+  CurveEditor *self = CURVE_EDITOR (data);
+
+  insert_point (self, self->context, self->context_pos);
+
+  gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
 static void
 remove_current_point (GSimpleAction *action,
                       GVariant      *value,
@@ -1196,7 +1734,56 @@ remove_current_point (GSimpleAction *action,
   remove_point (self, self->context);
 
   gtk_widget_queue_draw (GTK_WIDGET (self));
- }
+}
+
+static void
+toggle_edit_point (GSimpleAction *action,
+                   GVariant      *value,
+                   gpointer       data)
+{
+  CurveEditor *self = CURVE_EDITOR (data);
+
+  if (self->edited_point == self->context)
+    self->edited_point = -1;
+  else
+    {
+      self->edited_point = self->context;
+      self->edited_segment = -1;
+    }
+
+  gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+static void
+toggle_edit_segment (GSimpleAction *action,
+                     GVariant      *value,
+                     gpointer       data)
+{
+  CurveEditor *self = CURVE_EDITOR (data);
+
+  if (self->edited_segment == self->context)
+    self->edited_segment = -1;
+  else
+    {
+      self->edited_segment = self->context;
+      self->edited_point = -1;
+    }
+
+  gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+static void
+reset_weight (GSimpleAction *action,
+              GVariant      *value,
+              gpointer       data)
+{
+  CurveEditor *self = CURVE_EDITOR (data);
+  Segment *seg = get_segment (self, self->context);
+
+  seg->weight = 1;
+  maintain_conic (self, self->context);
+  gtk_widget_queue_draw (GTK_WIDGET (self));
+}
 /* }}} */
 /* {{{ Event handlers */
 static void
@@ -1209,27 +1796,46 @@ pressed (GtkGestureClick *gesture,
   graphene_point_t m = GRAPHENE_POINT_INIT (x, y);
   int i;
   int button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
+  float t;
 
   if (!self->edit)
     return;
 
   if (button == GDK_BUTTON_SECONDARY)
     {
-      for (i = 0; i < self->points->len; i++)
+      for (i = 0; i < self->segments->len; i++)
         {
-          PointData *pd = get_point (self, i);
+          Segment *seg = get_segment (self, i);
+          const graphene_point_t *p = get_line_point (self, i);
 
-          if (graphene_point_distance (&pd->p[1], &m, NULL, NULL) < CLICK_RADIUS)
+          if (graphene_point_distance (p, &m, NULL, NULL) < CLICK_RADIUS)
             {
               GAction *action;
 
               self->context = i;
 
-              action = g_action_map_lookup_action (self->actions, "type");
-              g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_string 
(point_type_to_string (pd->type)));
+              action = g_action_map_lookup_action (self->actions, "set-segment-type");
+              g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
 
-              action = g_action_map_lookup_action (self->actions, "operation");
-              g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_string (op_to_string 
(pd->op)));
+              action = g_action_map_lookup_action (self->actions, "add-point");
+              g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
+
+              action = g_action_map_lookup_action (self->actions, "remove-point");
+              g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE);
+
+              action = g_action_map_lookup_action (self->actions, "reset-weight");
+              g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
+
+              action = g_action_map_lookup_action (self->actions, "set-point-type");
+              g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE);
+              g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_string 
(point_type_to_string (seg->type)));
+
+              action = g_action_map_lookup_action (self->actions, "edit-point");
+              g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE);
+              g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (self->edited_point 
== i));
+
+              action = g_action_map_lookup_action (self->actions, "edit-segment");
+              g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
 
               gtk_popover_set_pointing_to (GTK_POPOVER (self->menu),
                                            &(const GdkRectangle){ x, y, 1, 1 });
@@ -1237,6 +1843,43 @@ pressed (GtkGestureClick *gesture,
               return;
             }
         }
+
+      if (find_closest_segment (self, &m, CLICK_RADIUS, NULL, &i, &t))
+        {
+          GAction *action;
+
+          self->context = i;
+          self->context_pos = t;
+
+          action = g_action_map_lookup_action (self->actions, "set-point-type");
+          g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
+
+          action = g_action_map_lookup_action (self->actions, "edit-point");
+          g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
+
+          action = g_action_map_lookup_action (self->actions, "remove-point");
+          g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
+
+          action = g_action_map_lookup_action (self->actions, "add-point");
+          g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE);
+
+          action = g_action_map_lookup_action (self->actions, "edit-segment");
+          g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE);
+          g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (self->edited_segment 
== i));
+
+          action = g_action_map_lookup_action (self->actions, "reset-weight");
+          g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
+                                       get_segment (self, i)->op == GSK_PATH_CONIC);
+
+          action = g_action_map_lookup_action (self->actions, "set-segment-type");
+          g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE);
+          g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_string (op_to_string 
(get_segment (self, i)->op)));
+
+          gtk_popover_set_pointing_to (GTK_POPOVER (self->menu),
+                                           &(const GdkRectangle){ x, y, 1, 1 });
+          gtk_popover_popup (GTK_POPOVER (self->menu));
+          return;
+        }
     }
 }
 
@@ -1254,15 +1897,21 @@ released (GtkGestureClick *gesture,
   if (!self->edit)
     return;
 
-  for (i = 0; i < self->points->len; i++)
+  for (i = 0; i < self->segments->len; i++)
     {
-      PointData *pd = get_point (self, i);
+      const graphene_point_t *p = get_line_point (self, i);
 
-      if (graphene_point_distance (&pd->p[1], &m, NULL, NULL) < CLICK_RADIUS)
+      if (graphene_point_distance (p, &m, NULL, NULL) < CLICK_RADIUS)
         {
           if (button == GDK_BUTTON_PRIMARY)
             {
-              pd->edit = !pd->edit;
+              if (self->edited_point == i)
+                self->edited_point = -1;
+              else
+                {
+                  self->edited_point = i;
+                  self->edited_segment = -1;
+                }
               gtk_widget_queue_draw (GTK_WIDGET (self));
               return;
             }
@@ -1295,25 +1944,27 @@ motion (GtkEventControllerMotion *controller,
 
   if (self->edit)
     {
-      for (i = 0; i < self->points->len; i++)
+      for (i = 0; i < self->segments->len; i++)
         {
-          PointData *pd = get_point (self, i);
+          Segment *seg = get_segment (self, i);
           int hovered = -1;
 
           for (j = 0; j < 3; j++)
             {
+              const graphene_point_t *q = &seg->p[j];
+
               if (!point_is_visible (self, i, j))
                 continue;
 
-              if (graphene_point_distance (&pd->p[j], &m, NULL, NULL) < CLICK_RADIUS)
+              if (graphene_point_distance (q, &m, NULL, NULL) < CLICK_RADIUS)
                 {
                   hovered = j;
                   break;
                 }
             }
-          if (pd->hovered != hovered)
+          if (seg->hovered != hovered)
             {
-              pd->hovered = hovered;
+              seg->hovered = hovered;
               changed = TRUE;
             }
         }
@@ -1330,12 +1981,12 @@ leave (GtkEventController *controller,
   int i;
   gboolean changed = FALSE;
 
-  for (i = 0; i < self->points->len; i++)
+  for (i = 0; i < self->segments->len; i++)
     {
-      PointData *pd = get_point (self, i);
-      if (pd->hovered != -1)
+      Segment *seg = get_segment (self, i);
+      if (seg->hovered != -1)
         {
-          pd->hovered = -1;
+          seg->hovered = -1;
           changed = TRUE;
         }
     }
@@ -1345,6 +1996,34 @@ leave (GtkEventController *controller,
 }
 /* }}} */
 /* {{{ Snapshot */
+static void
+add_diamond (GskPathBuilder   *builder,
+             graphene_point_t *center,
+             float             radius)
+{
+  float r = radius * 2 / (1 + M_SQRT2);
+
+  gsk_path_builder_move_to (builder, center->x, center->y - r * M_SQRT2);
+  gsk_path_builder_line_to (builder, center->x + r * M_SQRT2, center->y);
+  gsk_path_builder_line_to (builder, center->x, center->y + r * M_SQRT2);
+  gsk_path_builder_line_to (builder, center->x - r * M_SQRT2, center->y);
+  gsk_path_builder_close (builder);
+}
+
+static void
+add_square (GskPathBuilder   *builder,
+            graphene_point_t *center,
+            float             radius)
+{
+  float r = radius * 2 / (1 + M_SQRT2);
+
+  gsk_path_builder_move_to (builder, center->x - r, center->y - r);
+  gsk_path_builder_line_to (builder, center->x + r, center->y - r);
+  gsk_path_builder_line_to (builder, center->x + r, center->y + r);
+  gsk_path_builder_line_to (builder, center->x - r, center->y + r);
+  gsk_path_builder_close (builder);
+}
+
 static void
 curve_editor_snapshot (GtkWidget   *widget,
                        GtkSnapshot *snapshot)
@@ -1357,7 +2036,7 @@ curve_editor_snapshot (GtkWidget   *widget,
   float width;
   float height;
 
-  if (self->points->len == 0)
+  if (self->segments->len == 0)
     return;
 
   width = gtk_widget_get_width (widget);
@@ -1381,100 +2060,128 @@ curve_editor_snapshot (GtkWidget   *widget,
 
   if (self->edit)
     {
-      /* Add the skeleton */
-
       builder = gsk_path_builder_new ();
 
-      for (i = 0; i < self->points->len; i++)
+      if (self->edited_point != -1)
         {
-          PointData *pd = get_point (self, i);
-          gboolean need_move = TRUE;
+          /* Add the skeleton */
+          Segment *seg = get_segment (self, self->edited_point);
+          Segment *seg1 = get_segment (self, self->edited_point - 1);
+          const graphene_point_t *p = get_line_point (self, self->edited_point);
 
-          if (point_is_visible (self, i, 0))
+          if (seg1->op == GSK_PATH_CURVE)
             {
-              gsk_path_builder_move_to (builder, pd->p[0].x, pd->p[0].y);
-              gsk_path_builder_line_to (builder, pd->p[1].x, pd->p[1].y);
-              need_move = FALSE;
+              graphene_point_t *c = &seg1->p[2];
+              gsk_path_builder_move_to (builder, c->x, c->y);
+              gsk_path_builder_line_to (builder, p->x, p->y);
             }
-          if (point_is_visible (self, i, 2))
+          else if (seg1->op == GSK_PATH_CONIC)
             {
-              if (need_move)
-                gsk_path_builder_move_to (builder, pd->p[1].x, pd->p[1].y);
-              gsk_path_builder_line_to (builder, pd->p[2].x, pd->p[2].y);
+              graphene_point_t *c = &seg1->p[1];
+              gsk_path_builder_move_to (builder, c->x, c->y);
+              gsk_path_builder_line_to (builder, p->x, p->y);
+            }
+
+          if (seg->op == GSK_PATH_CURVE)
+            {
+              graphene_point_t *c = &seg->p[1];
+              gsk_path_builder_move_to (builder, c->x, c->y);
+              gsk_path_builder_line_to (builder, p->x, p->y);
+            }
+          else if (seg->op == GSK_PATH_CONIC)
+            {
+              graphene_point_t *c = &seg->p[1];
+              gsk_path_builder_move_to (builder, p->x, p->y);
+              gsk_path_builder_line_to (builder, c->x, c->y);
+            }
+        }
+
+      if (self->edited_segment != -1)
+        {
+          Segment *seg = get_segment (self, self->edited_segment);
+
+          if (seg->op == GSK_PATH_CURVE)
+            {
+              gsk_path_builder_move_to (builder, seg->p[0].x, seg->p[0].y);
+              gsk_path_builder_line_to (builder, seg->p[1].x, seg->p[1].y);
+              gsk_path_builder_line_to (builder, seg->p[2].x, seg->p[2].y);
+              gsk_path_builder_line_to (builder, seg->p[3].x, seg->p[3].y);
+            }
+          else if (seg->op == GSK_PATH_CONIC)
+            {
+              gsk_path_builder_move_to (builder, seg->p[0].x, seg->p[0].y);
+              gsk_path_builder_line_to (builder, seg->p[1].x, seg->p[1].y);
+              gsk_path_builder_line_to (builder, seg->p[3].x, seg->p[3].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 ));
+      if (self->edited_point != -1 || self->edited_segment != -1)
+        {
+          stroke = gsk_stroke_new (1);
+          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);
+        }
 
-      gtk_snapshot_pop (snapshot);
+      gsk_path_unref (path);
 
       /* Draw the circles, in several passes, one for each color */
 
       const char *colors[] = {
-        "white", /* hovered */
-        "red",   /* smooth curve points */
-        "green", /* sharp curve points */
-        "blue"   /* control points */
+        "red", /* hovered */
+        "white"   /* smooth curve points */
       };
       GdkRGBA color;
 
-       for (k = 0; k < 4; k++)
+       for (k = 0; k < 2; k++)
         {
           builder = gsk_path_builder_new ();
 
-          for (i = 0; i < self->points->len; i++)
+          for (i = 0; i < self->segments->len; i++)
             {
-              PointData *pd = get_point (self, i);
+              Segment *seg = get_segment (self, i);
 
               for (j = 0; j < 3; j++)
                 {
-                  switch (k)
-                    {
-                    case 0:
-                      if (j != pd->hovered)
-                        continue;
-                      break;
-
-                    case 1:
-                      if (j == pd->hovered)
-                        continue;
-
-                      if (!(j == 1 && pd->type != CUSP))
-                        continue;
-                      break;
-
-                    case 2:
-                      if (j == pd->hovered)
-                        continue;
+                  graphene_point_t *p = &seg->p[j];
 
-                      if (!(j == 1 && pd->type == CUSP))
-                        continue;
-                      break;
+                  if (!point_is_visible (self, i, j))
+                    continue;
 
-                    case 3:
-                      if (j == pd->hovered)
-                        continue;
+                  if ((k == 0 && j != seg->hovered) ||
+                      (k == 1 && j == seg->hovered))
+                    continue;
 
-                      if (j == 1)
-                        continue;
-
-                      if (!point_is_visible (self, i, j))
-                        continue;
-                      break;
-
-                    default:
-                      g_assert_not_reached ();
+                  if (j != 0)
+                    {
+                      gsk_path_builder_add_circle (builder, p, DRAW_RADIUS);
+                    }
+                  else
+                    {
+                      switch (seg->type)
+                        {
+                        case CUSP:
+                          add_diamond (builder, p, DRAW_RADIUS);
+                          break;
+
+                        case SMOOTH:
+                          add_square (builder, p, DRAW_RADIUS);
+                          break;
+                        case SYMMETRIC:
+                        case AUTO:
+                          gsk_path_builder_add_circle (builder, p, DRAW_RADIUS);
+                          break;
+                        default:
+                          g_assert_not_reached ();
+                        }
                     }
-
-                  gsk_path_builder_add_circle (builder, &pd->p[j], DRAW_RADIUS);
                 }
             }
 
@@ -1504,12 +2211,12 @@ curve_editor_snapshot (GtkWidget   *widget,
 /* {{{ GtkWidget boilerplate */
 static void
 curve_editor_measure (GtkWidget      *widget,
-                     GtkOrientation  orientation,
-                     int             for_size,
-                     int            *minimum_size,
-                     int            *natural_size,
-                     int            *minimum_baseline,
-                     int            *natural_baseline)
+                      GtkOrientation  orientation,
+                      int             for_size,
+                      int            *minimum_size,
+                      int            *natural_size,
+                      int            *minimum_baseline,
+                      int            *natural_baseline)
 {
   *minimum_size = 100;
   *natural_size = 200;
@@ -1532,7 +2239,7 @@ curve_editor_dispose (GObject *object)
 {
   CurveEditor *self = CURVE_EDITOR (object);
 
-  g_clear_pointer (&self->points, g_array_unref);
+  g_clear_pointer (&self->segments, g_array_unref);
   g_clear_pointer (&self->menu, gtk_widget_unparent);
   g_clear_object (&self->actions);
 
@@ -1562,9 +2269,11 @@ curve_editor_init (CurveEditor *self)
   GMenuItem *item;
   GSimpleAction *action;
 
-  self->points = g_array_new (FALSE, FALSE, sizeof (PointData));
+  self->segments = g_array_new (FALSE, FALSE, sizeof (Segment));
   self->dragged = -1;
   self->molded = -1;
+  self->edited_point = -1;
+  self->edited_segment = -1;
   self->edit = FALSE;
   self->stroke = gsk_stroke_new (1.0);
   self->color = (GdkRGBA){ 0, 0, 0, 1 };
@@ -1589,56 +2298,93 @@ curve_editor_init (CurveEditor *self)
 
   self->actions = G_ACTION_MAP (g_simple_action_group_new ());
 
-  action = g_simple_action_new_stateful ("type", G_VARIANT_TYPE_STRING, g_variant_new_string ("smooth"));
+  action = g_simple_action_new_stateful ("set-point-type", G_VARIANT_TYPE_STRING, g_variant_new_string 
("smooth"));
   g_signal_connect (action, "change-state", G_CALLBACK (set_point_type), 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"));
+  action = g_simple_action_new_stateful ("set-segment-type", 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);
+  action = g_simple_action_new_stateful ("edit-point", NULL, g_variant_new_boolean (FALSE));
+  g_signal_connect (action, "change-state", G_CALLBACK (toggle_edit_point), self);
+  g_action_map_add_action (G_ACTION_MAP (self->actions), G_ACTION (action));
+
+  action = g_simple_action_new_stateful ("edit-segment", NULL, g_variant_new_boolean (FALSE));
+  g_signal_connect (action, "change-state", G_CALLBACK (toggle_edit_segment), self);
+  g_action_map_add_action (G_ACTION_MAP (self->actions), G_ACTION (action));
+
+  action = g_simple_action_new ("add-point", NULL);
+  g_signal_connect (action, "activate", G_CALLBACK (insert_new_point), self);
+  g_action_map_add_action (G_ACTION_MAP (self->actions), G_ACTION (action));
+
+  action = g_simple_action_new ("remove-point", NULL);
   g_signal_connect (action, "activate", G_CALLBACK (remove_current_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));
+  action = g_simple_action_new ("reset-weight", NULL);
+  g_signal_connect (action, "activate", G_CALLBACK (reset_weight), self);
+  g_action_map_add_action (G_ACTION_MAP (self->actions), G_ACTION (action));
+
+  gtk_widget_insert_action_group (GTK_WIDGET (self), "path", G_ACTION_GROUP (self->actions));
 
   menu = g_menu_new ();
 
   section = g_menu_new ();
 
-  item = g_menu_item_new ("Cusp", "point.type::cusp");
+  item = g_menu_item_new ("Cusp", "path.set-point-type::cusp");
+  g_menu_item_set_attribute_value (item, "hidden-when", g_variant_new_string ("action-disabled"));
   g_menu_append_item (section, item);
   g_object_unref (item);
 
-  item = g_menu_item_new ("Smooth", "point.type::smooth");
+  item = g_menu_item_new ("Smooth", "path.set-point-type::smooth");
+  g_menu_item_set_attribute_value (item, "hidden-when", g_variant_new_string ("action-disabled"));
   g_menu_append_item (section, item);
   g_object_unref (item);
 
-  item = g_menu_item_new ("Symmetric", "point.type::symmetric");
+  item = g_menu_item_new ("Symmetric", "path.set-point-type::symmetric");
+  g_menu_item_set_attribute_value (item, "hidden-when", g_variant_new_string ("action-disabled"));
   g_menu_append_item (section, item);
   g_object_unref (item);
 
-  item = g_menu_item_new ("Automatic", "point.type::auto");
+  item = g_menu_item_new ("Automatic", "path.set-point-type::auto");
+  g_menu_item_set_attribute_value (item, "hidden-when", g_variant_new_string ("action-disabled"));
   g_menu_append_item (section, item);
   g_object_unref (item);
 
-
   g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
   g_object_unref (section);
 
   section = g_menu_new ();
 
-  item = g_menu_item_new ("Move", "point.operation::move");
+  item = g_menu_item_new ("Line", "path.set-segment-type::line");
+  g_menu_item_set_attribute_value (item, "hidden-when", g_variant_new_string ("action-disabled"));
+  g_menu_append_item (section, item);
+  g_object_unref (item);
+
+  item = g_menu_item_new ("Curve", "path.set-segment-type::curve");
+  g_menu_item_set_attribute_value (item, "hidden-when", g_variant_new_string ("action-disabled"));
+  g_menu_append_item (section, item);
+  g_object_unref (item);
+
+  item = g_menu_item_new ("Conic", "path.set-segment-type::conic");
+  g_menu_item_set_attribute_value (item, "hidden-when", g_variant_new_string ("action-disabled"));
   g_menu_append_item (section, item);
   g_object_unref (item);
 
-  item = g_menu_item_new ("Line", "point.operation::line");
+  g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
+  g_object_unref (section);
+
+  section = g_menu_new ();
+
+  item = g_menu_item_new ("Edit", "path.edit-point");
+  g_menu_item_set_attribute_value (item, "hidden-when", g_variant_new_string ("action-disabled"));
   g_menu_append_item (section, item);
   g_object_unref (item);
 
-  item = g_menu_item_new ("Curve", "point.operation::curve");
+  item = g_menu_item_new ("Edit", "path.edit-segment");
+  g_menu_item_set_attribute_value (item, "hidden-when", g_variant_new_string ("action-disabled"));
   g_menu_append_item (section, item);
   g_object_unref (item);
 
@@ -1647,7 +2393,18 @@ curve_editor_init (CurveEditor *self)
 
   section = g_menu_new ();
 
-  item = g_menu_item_new ("Remove", "point.remove");
+  item = g_menu_item_new ("Add", "path.add-point");
+  g_menu_item_set_attribute_value (item, "hidden-when", g_variant_new_string ("action-disabled"));
+  g_menu_append_item (section, item);
+  g_object_unref (item);
+
+  item = g_menu_item_new ("Remove", "path.remove-point");
+  g_menu_item_set_attribute_value (item, "hidden-when", g_variant_new_string ("action-disabled"));
+  g_menu_append_item (section, item);
+  g_object_unref (item);
+
+  item = g_menu_item_new ("Reset", "path.reset-weight");
+  g_menu_item_set_attribute_value (item, "hidden-when", g_variant_new_string ("action-disabled"));
   g_menu_append_item (section, item);
   g_object_unref (item);
 
@@ -1672,18 +2429,9 @@ void
 curve_editor_set_edit (CurveEditor *self,
                        gboolean     edit)
 {
-  int i;
-
   self->edit = edit;
-  if (!self->edit)
-    {
-      for (i = 0; i < self->points->len; i++)
-        {
-          PointData *pd = get_point (self, i);
-          pd->edit = FALSE;
-          pd->hovered = -1;
-        }
-    }
+  self->edited_point = -1;
+  self->edited_segment = -1;
 
   gtk_widget_queue_draw (GTK_WIDGET (self));
 }
@@ -1696,51 +2444,49 @@ copy_segments (GskPathOperation        op,
                gpointer                data)
 {
   CurveEditor *self = data;
-  PointData *pd;
-  PointData *pd1;
-  PointData np;
+  Segment seg;
+
+  seg.op = op;
+  seg.hovered = -1;
+  seg.dragged = -1;
 
   switch (op)
     {
     case GSK_PATH_MOVE:
-      if (self->points->len > 0)
-        {
-          pd = &g_array_index (self->points, PointData, self->points->len - 1);
-          pd->op = MOVE;
-        }
-
-      np.p[1] = pts[0];
-      g_array_append_val (self->points, np);
       break;
 
     case GSK_PATH_CLOSE:
-      pd = &g_array_index (self->points, PointData, self->points->len - 1);
-      pd1 = &g_array_index (self->points, PointData, 0);
-      if (graphene_point_near (&pd->p[1], &pd1->p[1], 0.001))
-        {
-          pd1->p[0] = pd->p[0];
-          g_array_remove_index (self->points, self->points->len - 1);
-        }
+      seg.p[0] = pts[0];
+      seg.p[3] = pts[1];
+      g_array_append_val (self->segments, seg);
       break;
 
     case GSK_PATH_LINE:
-      pd = &g_array_index (self->points, PointData, self->points->len - 1);
-      pd->op = LINE;
-      np.p[1] = pts[1];
-      g_array_append_val (self->points, np);
+      seg.p[0] = pts[0];
+      seg.p[3] = pts[1];
+      g_array_append_val (self->segments, seg);
       break;
 
     case GSK_PATH_CURVE:
-      pd = &g_array_index (self->points, PointData, self->points->len - 1);
-      pd->op = CURVE;
-      pd->p[2] = pts[1];
-      np.p[0] = pts[2];
-      np.p[1] = pts[3];
-      g_array_append_val (self->points, np);
+      seg.p[0] = pts[0];
+      seg.p[1] = pts[1];
+      seg.p[2] = pts[2];
+      seg.p[3] = pts[3];
+      g_array_append_val (self->segments, seg);
       break;
 
     case GSK_PATH_CONIC:
-      /* FIXME */
+      {
+        seg.p[0] = pts[0];
+        seg.p[1] = pts[1];
+        seg.p[3] = pts[2];
+        seg.weight = weight;
+
+        get_conic_shoulder_point (pts, weight, &seg.p[2]);
+
+        g_array_append_val (self->segments, seg);
+      }
+      break;
 
     default:
       g_assert_not_reached ();
@@ -1753,19 +2499,33 @@ curve_editor_set_path (CurveEditor *self,
                        GskPath     *path)
 {
   int i;
+  Segment *first, *last;
 
-  g_array_set_size (self->points, 0);
+  g_array_set_size (self->segments, 0);
 
-  gsk_path_foreach (path, copy_segments, self);
+  gsk_path_foreach (path, GSK_PATH_FOREACH_ALLOW_CURVE | GSK_PATH_FOREACH_ALLOW_CONIC, copy_segments, self);
 
-  for (i = 0; i < self->points->len; i++)
+  first = get_segment (self, 0);
+  last = get_segment (self, self->segments->len - 1);
+  if (last->op == GSK_PATH_CLOSE)
     {
-      PointData *pd = get_point (self, i);
-      pd->hovered = -1;
-      pd->dragged = -1;
-      pd->edit= FALSE;
-      check_smoothness (self, i);
+      if (graphene_point_near (&last->p[0], &last->p[3], 0.001))
+        g_array_remove_index (self->segments, self->segments->len - 1);
+      else
+        last->op = GSK_PATH_LINE;
     }
+  else
+    {
+      Segment seg;
+
+      seg.op = GSK_PATH_MOVE;
+      seg.p[0] = last->p[3];
+      seg.p[3] = first->p[0];
+      g_array_append_val (self->segments, seg);
+    }
+
+  for (i = 0; i < self->segments->len; i++)
+    check_smoothness (self, i);
 
   gtk_widget_queue_draw (GTK_WIDGET (self));
 }


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