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