[gtk/wip/matthiasc/lottie-stroke: 14/17] curve-editor: Rewrite with conics support
- From: Matthias Clasen <matthiasc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/wip/matthiasc/lottie-stroke: 14/17] curve-editor: Rewrite with conics support
- Date: Sat, 5 Dec 2020 19:22:54 +0000 (UTC)
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]