summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Clasen <mclasen@redhat.com>2020-11-21 08:47:14 -0500
committerMatthias Clasen <mclasen@redhat.com>2020-11-21 08:47:34 -0500
commit267c1a277c2304ae0a3fddb71f64a0ea9a448539 (patch)
tree0199ab35ec45e3c5e0d2f7d82d7bed34695854df
parentca4d610bf2bca3b000c953f9338bd324f54d562a (diff)
downloadgtk+-267c1a277c2304ae0a3fddb71f64a0ea9a448539.tar.gz
Rename to CurveEditor
And start separating the widget from the demo with an api.
-rw-r--r--tests/curve-editor.c1014
-rw-r--r--tests/curve-editor.h18
-rw-r--r--tests/curve.c891
-rw-r--r--tests/meson.build2
4 files changed, 1062 insertions, 863 deletions
diff --git a/tests/curve-editor.c b/tests/curve-editor.c
new file mode 100644
index 0000000000..5b278bdff8
--- /dev/null
+++ b/tests/curve-editor.c
@@ -0,0 +1,1014 @@
+/* TODO: point insert/remove
+ */
+
+#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 RADIUS 5
+
+typedef enum
+{
+ MOVE,
+ LINE,
+ CURVE
+} Operation;
+
+static const char *
+op_to_string (Operation op)
+{
+ switch (op)
+ {
+ case MOVE:
+ return "move";
+ case LINE:
+ return "line";
+ case CURVE:
+ return "curve";
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static Operation
+op_from_string (const char *s)
+{
+ if (strcmp (s, "move") == 0)
+ return MOVE;
+ else if (strcmp (s, "line") == 0)
+ return LINE;
+ else if (strcmp (s, "curve") == 0)
+ return CURVE;
+ else
+ g_assert_not_reached ();
+}
+
+typedef struct
+{
+ Operation op;
+ gboolean edit;
+ gboolean smooth;
+} PointData;
+
+struct _CurveEditor
+{
+ GtkWidget parent_instance;
+ graphene_point_t *points;
+ int n_points;
+ PointData *point_data; /* length is n_points / 3 */
+ int dragged;
+ int context;
+ gboolean symmetric;
+ gboolean edit;
+
+ GtkWidget *menu;
+ GActionMap *actions;
+};
+
+struct _CurveEditorClass
+{
+ GtkWidgetClass parent_class;
+};
+
+G_DEFINE_TYPE (CurveEditor, curve_editor, GTK_TYPE_WIDGET)
+
+static float
+dist (graphene_point_t *a, graphene_point_t *b)
+{
+ graphene_vec2_t v;
+
+ graphene_vec2_init (&v, a->x - b->x, a->y - b->y);
+ return graphene_vec2_length (&v);
+}
+
+static void
+drag_begin (GtkGestureDrag *gesture,
+ double start_x,
+ double start_y,
+ CurveEditor *self)
+{
+ int i;
+ graphene_point_t p = GRAPHENE_POINT_INIT (start_x, start_y);
+
+ if (self->edit)
+ for (i = 0; i < self->n_points; i++)
+ {
+ if (dist (&self->points[i], &p) < RADIUS)
+ {
+ self->dragged = i;
+ self->symmetric = (gtk_event_controller_get_current_event_state (GTK_EVENT_CONTROLLER (gesture)) & GDK_CONTROL_MASK) == 0;
+
+ gtk_widget_queue_draw (GTK_WIDGET (self));
+ return;
+ }
+ }
+
+ gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
+}
+
+static void
+drag_update (GtkGestureDrag *gesture,
+ double offset_x,
+ double offset_y,
+ 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 = dist (d, c);
+ c = &self->points[(self->dragged + 1) % self->n_points];
+ l2 = dist (d, c);
+
+ dx = x - d->x;
+ dy = y - d->y;
+
+ if (self->dragged % 3 == 0)
+ {
+ /* dragged point is on curve */
+
+ 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)
+ {
+ /* 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;
+ }
+
+ c = &self->points[(self->dragged - 1 + self->n_points) % self->n_points];
+ c->x += dx;
+ c->y += dy;
+
+ 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 = dist (c, p);
+ opposite_point (p, d, l, c);
+ }
+ }
+ if (op == LINE)
+ {
+ /* 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
+ {
+ c = &self->points[(self->dragged -1 + self->n_points) % self->n_points];
+ c->x += dx;
+ c->y += dy;
+ }
+
+ 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 = dist (c, p);
+ 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
+ {
+ /* 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;
+
+ /* then adjust the other control point */
+ if (self->symmetric)
+ l = dist (d, p);
+ else
+ l = dist (c, p);
+
+ opposite_point (p, d, l, c);
+ }
+ else if (op1 == 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;
+ }
+ }
+
+ gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+static void
+drag_end (GtkGestureDrag *gesture,
+ double offset_x,
+ double offset_y,
+ CurveEditor *self)
+{
+ drag_update (gesture, offset_x, offset_y, self);
+ self->dragged = -1;
+ self->symmetric = FALSE;
+}
+
+static void
+maintain_smoothness (CurveEditor *self,
+ int point)
+{
+ gboolean smooth;
+ Operation op, op1;
+
+ smooth = self->point_data[point / 3].smooth;
+
+ op = self->point_data[point / 3].op;
+ op1 = self->point_data[((point - 1 + self->n_points) % self->n_points) / 3].op;
+
+ if (smooth)
+ {
+ graphene_point_t *p;
+
+ p = &self->points[point];
+
+ if (op == CURVE && op1 == CURVE)
+ {
+ graphene_point_t *c, *c2;
+ float d;
+
+ c = &self->points[(point - 1 + self->n_points) % self->n_points];
+ c2 = &self->points[(point + 1) % self->n_points];
+
+ d = dist (c, p);
+ opposite_point (p, c2, d, c);
+ }
+ else if (op == CURVE && op1 == LINE)
+ {
+ graphene_point_t *c, *p2;
+ float d;
+
+ c = &self->points[(point + 1) % self->n_points];
+ p2 = &self->points[(point - 3 + self->n_points) % self->n_points];
+
+ d = dist (c, p);
+ opposite_point (p, p2, d, c);
+ }
+ else if (op == LINE && op1 == CURVE)
+ {
+ graphene_point_t *c, *p2;
+ float d;
+
+ c = &self->points[(point - 1 + self->n_points) % self->n_points];
+ p2 = &self->points[(point + 3) % self->n_points];
+
+ d = dist (c, p);
+ opposite_point (p, p2, d, c);
+ }
+ }
+}
+
+static void
+toggle_smooth (GSimpleAction *action,
+ GVariant *value,
+ gpointer data)
+{
+ CurveEditor *self = CURVE_EDITOR (data);
+
+ self->point_data[self->context / 3].smooth = g_variant_get_boolean (value);
+
+ maintain_smoothness (self, self->context);
+
+ gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+static void
+set_operation (GSimpleAction *action,
+ GVariant *value,
+ gpointer data)
+{
+ CurveEditor *self = CURVE_EDITOR (data);
+
+ self->point_data[self->context / 3].op = op_from_string (g_variant_get_string (value, NULL));
+
+ maintain_smoothness (self, self->context);
+ maintain_smoothness (self, (self->context + 3) % self->n_points);
+
+ gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+static void
+pressed (GtkGestureClick *gesture,
+ int n_press,
+ double x,
+ double y,
+ CurveEditor *self)
+{
+ graphene_point_t m = GRAPHENE_POINT_INIT (x, y);
+ int i;
+ int button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
+
+ if (!self->edit)
+ return;
+
+ if (button != GDK_BUTTON_SECONDARY)
+ return;
+
+ for (i = 0; i < self->n_points; i++)
+ {
+ if (i % 3 != 0)
+ continue;
+
+ if (dist (&self->points[i], &m) < RADIUS)
+ {
+ GAction *action;
+
+ self->context = i;
+
+ action = g_action_map_lookup_action (self->actions, "smooth");
+ g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (self->point_data[i / 3].smooth));
+
+ 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 (self->point_data[i / 3].op)));
+
+ gtk_popover_set_pointing_to (GTK_POPOVER (self->menu),
+ &(const GdkRectangle){ x, y, 1, 1 });
+ gtk_popover_popup (GTK_POPOVER (self->menu));
+ return;
+ }
+ }
+}
+
+static void
+released (GtkGestureClick *gesture,
+ int n_press,
+ double x,
+ double y,
+ CurveEditor *self)
+{
+ graphene_point_t m = GRAPHENE_POINT_INIT (x, y);
+ int i;
+
+ if (!self->edit)
+ return;
+
+ for (i = 0; i < self->n_points; i++)
+ {
+ if (dist (&self->points[i], &m) < RADIUS)
+ {
+ if (i % 3 == 0)
+ {
+ int button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
+ if (button == GDK_BUTTON_PRIMARY)
+ {
+ self->point_data[i / 3].edit = !self->point_data[i / 3].edit;
+ }
+ else if (button == GDK_BUTTON_SECONDARY)
+ {
+ self->context = i;
+ self->point_data[i / 3].smooth = !self->point_data[i / 3].smooth;
+ if (self->point_data[i / 3].smooth)
+ {
+ graphene_point_t *p, *c, *c2;
+ float d;
+
+ p = &self->points[i];
+ c = &self->points[(i - 1 + self->n_points) % self->n_points];
+ c2 = &self->points[(i + 1 + self->n_points) % self->n_points];
+
+ d = dist (c, p);
+ opposite_point (p, c2, d, c);
+ }
+ }
+ }
+ }
+ }
+}
+
+static void
+curve_editor_init (CurveEditor *self)
+{
+ GtkGesture *gesture;
+ GMenu *menu;
+ GMenu *section;
+ GMenuItem *item;
+ GSimpleAction *action;
+
+ self->dragged = -1;
+ self->edit = FALSE;
+
+ gesture = gtk_gesture_drag_new ();
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), GDK_BUTTON_PRIMARY);
+ g_signal_connect (gesture, "drag-begin", G_CALLBACK (drag_begin), self);
+ g_signal_connect (gesture, "drag-update", G_CALLBACK (drag_update), self);
+ g_signal_connect (gesture, "drag-end", G_CALLBACK (drag_end), self);
+ gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture));
+
+ gesture = gtk_gesture_click_new ();
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), 0);
+ g_signal_connect (gesture, "pressed", G_CALLBACK (pressed), self);
+ g_signal_connect (gesture, "released", G_CALLBACK (released), self);
+ gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture));
+
+ 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 (toggle_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 ("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));
+
+ 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);
+
+ 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);
+
+ 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));
+}
+
+static void
+curve_editor_snapshot (GtkWidget *widget,
+ GtkSnapshot *snapshot)
+{
+ CurveEditor *self = (CurveEditor *)widget;
+ GskPathBuilder *builder;
+ GskPath *path;
+ GskStroke *stroke;
+ int i, j;
+ float width;
+ float height;
+
+ if (self->n_points == 0)
+ return;
+
+ width = gtk_widget_get_width (widget);
+ height = gtk_widget_get_width (widget);
+
+ builder = gsk_path_builder_new ();
+
+ if (self->edit)
+ {
+ /* Add the skeleton */
+
+ gsk_path_builder_move_to (builder, self->points[0].x, self->points[0].y);
+ for (i = 1; i < self->n_points; i++)
+ {
+ gboolean edit;
+ gboolean line;
+
+ if (i % 3 == 2)
+ edit = self->point_data[((i + 3) % self->n_points) / 3].edit;
+ else
+ edit = self->point_data[i / 3].edit;
+
+ if (i % 3 == 0)
+ line = self->point_data[((i - 1 + self->n_points) % self->n_points) / 3].op != CURVE;
+ else
+ line = self->point_data[i / 3].op != CURVE;
+
+ if (edit)
+ {
+ if (i % 3 == 2 || line)
+ gsk_path_builder_move_to (builder, self->points[i].x, self->points[i].y);
+ else
+ gsk_path_builder_line_to (builder, self->points[i].x, self->points[i].y);
+ }
+ }
+ if (self->point_data[0].edit)
+ gsk_path_builder_line_to (builder, self->points[0].x, self->points[0].y);
+ }
+
+ /* Add the curve itself */
+
+ 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;
+
+ 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 ();
+ }
+ }
+
+ /* Stroke everything we have so far */
+
+ path = gsk_path_builder_free_to_path (builder);
+ stroke = gsk_stroke_new (1);
+ gtk_snapshot_push_stroke (snapshot, path, stroke);
+ gsk_stroke_free (stroke);
+ gsk_path_unref (path);
+
+ gtk_snapshot_append_color (snapshot,
+ &(GdkRGBA){ 0, 0, 0, 1 },
+ &GRAPHENE_RECT_INIT (0, 0, width, height ));
+
+ gtk_snapshot_pop (snapshot);
+
+ if (self->edit)
+ {
+ /* Draw the circles, in several passes, one for each color */
+
+ const char *colors[] = {
+ "red",
+ "green",
+ "blue"
+ };
+ GdkRGBA color;
+
+ for (j = 0; j < 3; j++)
+ {
+ builder = gsk_path_builder_new ();
+
+ for (i = 0; i < self->n_points; i++)
+ {
+ switch (j)
+ {
+ case 0:
+ if (!(i % 3 == 0 &&
+ self->point_data[i / 3].smooth))
+ continue;
+ break;
+
+ case 1:
+ if (!(i % 3 == 0 &&
+ !self->point_data[i / 3].smooth))
+ continue;
+ break;
+
+ case 2:
+ if (i % 3 == 1)
+ {
+ if (!(self->point_data[i / 3].edit &&
+ self->point_data[i / 3].op == CURVE))
+ continue;
+ }
+ else if (i % 3 == 2)
+ {
+ if (!(self->point_data[((i + 3) % self->n_points) / 3].edit &&
+ self->point_data[i / 3].op == CURVE))
+ continue;
+ }
+ else
+ continue;
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ gsk_path_builder_add_circle (builder, &self->points[i], RADIUS);
+ }
+
+ path = gsk_path_builder_free_to_path (builder);
+
+ gtk_snapshot_push_fill (snapshot, path, GSK_FILL_RULE_WINDING);
+ gdk_rgba_parse (&color, colors[j]);
+ gtk_snapshot_append_color (snapshot, &color, &GRAPHENE_RECT_INIT (0, 0, width, height));
+ gtk_snapshot_pop (snapshot);
+
+ stroke = gsk_stroke_new (1.0);
+ gtk_snapshot_push_stroke (snapshot, path, stroke);
+ gsk_stroke_free (stroke);
+
+ gdk_rgba_parse (&color, "black");
+ gtk_snapshot_append_color (snapshot, &color, &GRAPHENE_RECT_INIT (0, 0, width, height));
+ gtk_snapshot_pop (snapshot);
+
+ gsk_path_unref (path);
+ }
+ }
+}
+
+static void
+curve_editor_measure (GtkWidget *widget,
+ GtkOrientation orientation,
+ int for_size,
+ int *minimum_size,
+ int *natural_size,
+ int *minimum_baseline,
+ int *natural_baseline)
+{
+ *minimum_size = 100;
+ *natural_size = 200;
+}
+
+static void
+curve_editor_size_allocate (GtkWidget *widget,
+ int width,
+ int height,
+ int baseline)
+{
+ CurveEditor *self = CURVE_EDITOR (widget);
+
+ gtk_native_check_resize (GTK_NATIVE (self->menu));
+}
+
+static void
+curve_editor_dispose (GObject *object)
+{
+ CurveEditor *self = CURVE_EDITOR (object);
+
+ g_clear_pointer (&self->points, g_free);
+ g_clear_pointer (&self->point_data, g_free);
+ g_clear_pointer (&self->menu, gtk_widget_unparent);
+
+ G_OBJECT_CLASS (curve_editor_parent_class)->dispose (object);
+}
+
+static void
+curve_editor_class_init (CurveEditorClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
+
+ object_class->dispose = curve_editor_dispose;
+
+ widget_class->snapshot = curve_editor_snapshot;
+ widget_class->measure = curve_editor_measure;
+ widget_class->size_allocate = curve_editor_size_allocate;
+}
+
+GtkWidget *
+curve_editor_new (void)
+{
+ return g_object_new (curve_editor_get_type (), NULL);
+}
+
+void
+curve_editor_set_edit (CurveEditor *self,
+ gboolean edit)
+{
+ int i;
+
+ self->edit = edit;
+ if (!self->edit)
+ {
+ for (i = 0; i < self->n_points / 3; i++)
+ self->point_data[i].edit = FALSE;
+ }
+
+ gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+typedef struct
+{
+ int count;
+ graphene_point_t first;
+ graphene_point_t last;
+ gboolean has_close;
+ gboolean has_initial_move;
+} CountSegmentData;
+
+static gboolean
+count_segments (GskPathOperation op,
+ const graphene_point_t *pts,
+ gsize n_pts,
+ gpointer data)
+{
+ CountSegmentData *d = data;
+
+ if (d->count == 0)
+ {
+ 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;
+}
+
+typedef struct
+{
+ CurveEditor *editor;
+ int pos;
+} CopySegmentData;
+
+static gboolean
+copy_segments (GskPathOperation op,
+ const graphene_point_t *pts,
+ gsize n_pts,
+ gpointer data)
+{
+ CopySegmentData *d = data;
+ int i;
+
+ switch (op)
+ {
+ case GSK_PATH_MOVE:
+ if (d->pos == 0)
+ {
+ d->editor->points[d->pos++] = pts[0];
+ }
+ else
+ {
+ d->editor->point_data[d->pos / 3].op = MOVE;
+ d->editor->point_data[d->pos / 3].smooth = FALSE;
+
+ d->editor->points[d->pos++] = pts[0];
+ d->editor->points[d->pos++] = pts[0];
+ 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;
+
+ 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];
+ 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];
+
+ for (i = 1; i < n_pts; i++)
+ d->editor->points[d->pos++] = pts[i];
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ return TRUE;
+}
+
+/* Check if the points arount point currently satisy
+ * smoothness conditions. Set PointData.smooth accordingly.
+ */
+static void
+update_smoothness (CurveEditor *self,
+ int point)
+{
+ 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;
+
+ 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;
+
+ 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;
+}
+
+void
+curve_editor_set_path (CurveEditor *self,
+ GskPath *path)
+{
+ CountSegmentData data;
+ CopySegmentData data2;
+ int i;
+
+ g_clear_pointer (&self->points, g_free);
+ g_clear_pointer (&self->point_data, g_free);
+ self->n_points = 0;
+
+ data.count = 0;
+ data.has_close = FALSE;
+ gsk_path_foreach (path, count_segments, &data);
+
+ if (data.has_initial_move)
+ data.count--;
+
+ if (!graphene_point_near (&data.first, &data.last, 0.0001) && !data.has_close)
+ data.count++;
+
+ self->n_points = data.count * 3;
+ self->points = g_new0 (graphene_point_t, self->n_points);
+ self->point_data = g_new0 (PointData, data.count);
+
+ data2.editor = self;
+ data2.pos = 0;
+ gsk_path_foreach (path, copy_segments, &data2);
+
+ for (i = 0; i < self->n_points; i += 3)
+ update_smoothness (self, i);
+
+ gtk_widget_queue_draw (GTK_WIDGET (self));
+}
diff --git a/tests/curve-editor.h b/tests/curve-editor.h
new file mode 100644
index 0000000000..e26247ba2b
--- /dev/null
+++ b/tests/curve-editor.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define CURVE_TYPE_EDITOR (curve_editor_get_type ())
+G_DECLARE_FINAL_TYPE (CurveEditor, curve_editor, CURVE, EDITOR, GtkWidget)
+
+GtkWidget * curve_editor_new (void);
+
+void curve_editor_set_edit (CurveEditor *self,
+ gboolean edit);
+
+void curve_editor_set_path (CurveEditor *self,
+ GskPath *path);
+
+G_END_DECLS
diff --git a/tests/curve.c b/tests/curve.c
index bc8c3a18a0..e0b1814d0b 100644
--- a/tests/curve.c
+++ b/tests/curve.c
@@ -1,536 +1,9 @@
-/* TODO
- * - point insert/remove
- * - rename to CurveEditor
- * - add properties
- */
-
#include <gtk/gtk.h>
+#include "curve-editor.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);
-}
-
-/* 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 RADIUS 5
-
-G_DECLARE_FINAL_TYPE (DemoWidget, demo_widget, DEMO, WIDGET, GtkWidget)
-
-typedef enum
-{
- MOVE,
- LINE,
- CURVE
-} Operation;
-
-static const char *
-op_to_string (Operation op)
-{
- switch (op)
- {
- case MOVE:
- return "move";
- case LINE:
- return "line";
- case CURVE:
- return "curve";
- default:
- g_assert_not_reached ();
- }
-}
-
-static Operation
-op_from_string (const char *s)
-{
- if (strcmp (s, "move") == 0)
- return MOVE;
- else if (strcmp (s, "line") == 0)
- return LINE;
- else if (strcmp (s, "curve") == 0)
- return CURVE;
- else
- g_assert_not_reached ();
-}
-
-typedef struct
-{
- Operation op;
- gboolean edit;
- gboolean smooth;
-} PointData;
-
-struct _DemoWidget
-{
- GtkWidget parent_instance;
- graphene_point_t *points;
- int n_points;
- PointData *point_data; /* length is n_points / 3 */
- int dragged;
- int context;
- gboolean symmetric;
- gboolean edit;
-
- GtkWidget *menu;
- GActionMap *actions;
-};
-
-struct _DemoWidgetClass
-{
- GtkWidgetClass parent_class;
-};
-
-G_DEFINE_TYPE (DemoWidget, demo_widget, GTK_TYPE_WIDGET)
-
-static float
-dist (graphene_point_t *a, graphene_point_t *b)
-{
- graphene_vec2_t v;
-
- graphene_vec2_init (&v, a->x - b->x, a->y - b->y);
- return graphene_vec2_length (&v);
-}
-
-static void
-drag_begin (GtkGestureDrag *gesture,
- double start_x,
- double start_y,
- DemoWidget *self)
-{
- int i;
- graphene_point_t p = GRAPHENE_POINT_INIT (start_x, start_y);
-
- if (self->edit)
- for (i = 0; i < self->n_points; i++)
- {
- if (dist (&self->points[i], &p) < RADIUS)
- {
- self->dragged = i;
- self->symmetric = (gtk_event_controller_get_current_event_state (GTK_EVENT_CONTROLLER (gesture)) & GDK_CONTROL_MASK) == 0;
-
- gtk_widget_queue_draw (GTK_WIDGET (self));
- return;
- }
- }
-
- gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
-}
-
-static void
-drag_update (GtkGestureDrag *gesture,
- double offset_x,
- double offset_y,
- DemoWidget *self)
-{
- double x, y;
- double dx, dy;
- graphene_point_t *c, *p, *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 = dist (d, c);
- c = &self->points[(self->dragged + 1) % self->n_points];
- l2 = dist (d, c);
-
- dx = x - d->x;
- dy = y - d->y;
-
- if (self->dragged % 3 == 0)
- {
- /* dragged point is on curve */
-
- 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)
- {
- /* 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;
- }
-
- c = &self->points[(self->dragged - 1 + self->n_points) % self->n_points];
- c->x += dx;
- c->y += dy;
-
- 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 = dist (c, p);
- opposite_point (p, d, l, c);
- }
- }
-
- if (op == LINE)
- {
- /* 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
- {
- c = &self->points[(self->dragged -1 + self->n_points) % self->n_points];
- c->x += dx;
- c->y += dy;
- }
-
- 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 = dist (c, p);
- 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
- {
- /* 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;
-
- /* then adjust the other control point */
- if (self->symmetric)
- l = dist (d, p);
- else
- l = dist (c, p);
-
- opposite_point (p, d, l, c);
- }
- else if (op1 == 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;
- }
- }
-
- gtk_widget_queue_draw (GTK_WIDGET (self));
-}
-
-static void
-drag_end (GtkGestureDrag *gesture,
- double offset_x,
- double offset_y,
- DemoWidget *self)
-{
- drag_update (gesture, offset_x, offset_y, self);
- self->dragged = -1;
- self->symmetric = FALSE;
-}
-
-static void
-maintain_smoothness (DemoWidget *self,
- int point)
-{
- gboolean smooth;
- Operation op, op1;
-
- smooth = self->point_data[point / 3].smooth;
-
- op = self->point_data[point / 3].op;
- op1 = self->point_data[((point - 1 + self->n_points) % self->n_points) / 3].op;
-
- if (smooth)
- {
- graphene_point_t *p;
-
- p = &self->points[point];
-
- if (op == CURVE && op1 == CURVE)
- {
- graphene_point_t *c, *c2;
- float d;
-
- c = &self->points[(point - 1 + self->n_points) % self->n_points];
- c2 = &self->points[(point + 1) % self->n_points];
-
- d = dist (c, p);
- opposite_point (p, c2, d, c);
- }
- else if (op == CURVE && op1 == LINE)
- {
- graphene_point_t *c, *p2;
- float d;
-
- c = &self->points[(point + 1) % self->n_points];
- p2 = &self->points[(point - 3 + self->n_points) % self->n_points];
-
- d = dist (c, p);
- opposite_point (p, p2, d, c);
- }
- else if (op == LINE && op1 == CURVE)
- {
- graphene_point_t *c, *p2;
- float d;
-
- c = &self->points[(point - 1 + self->n_points) % self->n_points];
- p2 = &self->points[(point + 3) % self->n_points];
-
- d = dist (c, p);
- opposite_point (p, p2, d, c);
- }
- }
-}
-
-static void
-toggle_smooth (GSimpleAction *action,
- GVariant *value,
- gpointer data)
-{
- DemoWidget *self = DEMO_WIDGET (data);
-
- self->point_data[self->context / 3].smooth = g_variant_get_boolean (value);
-
- maintain_smoothness (self, self->context);
-
- gtk_widget_queue_draw (GTK_WIDGET (self));
-}
-
-static void
-set_operation (GSimpleAction *action,
- GVariant *value,
- gpointer data)
-{
- DemoWidget *self = DEMO_WIDGET (data);
-
- self->point_data[self->context / 3].op = op_from_string (g_variant_get_string (value, NULL));
-
- maintain_smoothness (self, self->context);
- maintain_smoothness (self, (self->context + 3) % self->n_points);
-
- gtk_widget_queue_draw (GTK_WIDGET (self));
-}
-
-static void
-pressed (GtkGestureClick *gesture,
- int n_press,
- double x,
- double y,
- DemoWidget *self)
-{
- graphene_point_t m = GRAPHENE_POINT_INIT (x, y);
- int i;
- int button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
-
- if (!self->edit)
- return;
-
- if (button != GDK_BUTTON_SECONDARY)
- return;
-
- for (i = 0; i < self->n_points; i++)
- {
- if (i % 3 != 0)
- continue;
-
- if (dist (&self->points[i], &m) < RADIUS)
- {
- GAction *action;
-
- self->context = i;
-
- action = g_action_map_lookup_action (self->actions, "smooth");
- g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (self->point_data[i / 3].smooth));
-
- 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 (self->point_data[i / 3].op)));
-
- gtk_popover_set_pointing_to (GTK_POPOVER (self->menu),
- &(const GdkRectangle){ x, y, 1, 1 });
- gtk_popover_popup (GTK_POPOVER (self->menu));
- return;
- }
- }
-}
-
-static void
-released (GtkGestureClick *gesture,
- int n_press,
- double x,
- double y,
- DemoWidget *self)
-{
- graphene_point_t m = GRAPHENE_POINT_INIT (x, y);
- int i;
-
- if (!self->edit)
- return;
-
- for (i = 0; i < self->n_points; i++)
- {
- if (dist (&self->points[i], &m) < RADIUS)
- {
- if (i % 3 == 0)
- {
- int button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
- if (button == GDK_BUTTON_PRIMARY)
- {
- self->point_data[i / 3].edit = !self->point_data[i / 3].edit;
- }
- else if (button == GDK_BUTTON_SECONDARY)
- {
- self->context = i;
- self->point_data[i / 3].smooth = !self->point_data[i / 3].smooth;
- if (self->point_data[i / 3].smooth)
- {
- graphene_point_t *p, *c, *c2;
- float d;
-
- p = &self->points[i];
- c = &self->points[(i - 1 + self->n_points) % self->n_points];
- c2 = &self->points[(i + 1 + self->n_points) % self->n_points];
-
- d = dist (c, p);
- opposite_point (p, c2, d, c);
- }
- }
- }
- }
- }
-}
-
-static void
-init_points (DemoWidget *self)
+static GskPath *
+make_circle_path (void)
{
float w = 200;
float h = 200;
@@ -540,352 +13,44 @@ init_points (DemoWidget *self)
float r = (w - 2 * pad) / 2;
float k = 0.55228;
float kr = k * r;
- int i;
-
- g_free (self->points);
- g_free (self->point_data);
-
- self->n_points = 12;
- self->points = g_new (graphene_point_t, self->n_points);
- self->point_data = g_new (PointData, self->n_points / 3);
-
-
- self->points[0] = GRAPHENE_POINT_INIT (cx, pad);
- self->points[1] = GRAPHENE_POINT_INIT (cx + kr, pad);
- self->points[2] = GRAPHENE_POINT_INIT (w - pad, cy - kr);
-
- self->points[3] = GRAPHENE_POINT_INIT (w - pad, cy);
- self->points[4] = GRAPHENE_POINT_INIT (w - pad, cy + kr);
- self->points[5] = GRAPHENE_POINT_INIT (cx + kr, h - pad);
-
- self->points[6] = GRAPHENE_POINT_INIT (cx, h - pad);
- self->points[7] = GRAPHENE_POINT_INIT (cx - kr, h - pad);
- self->points[8] = GRAPHENE_POINT_INIT (pad, cy + kr);
-
- self->points[9] = GRAPHENE_POINT_INIT (pad, cy);
- self->points[10] = GRAPHENE_POINT_INIT (pad, cy - kr);
- self->points[11] = GRAPHENE_POINT_INIT (cx - kr, pad);
-
- for (i = 0; i < self->n_points / 3; i++)
- {
- self->point_data[i].edit = FALSE;
- self->point_data[i].smooth = TRUE;
- self->point_data[i].op = CURVE;
- }
-}
-
-static void
-demo_widget_init (DemoWidget *self)
-{
- GtkGesture *gesture;
- GMenu *menu;
- GMenu *section;
- GMenuItem *item;
- GSimpleAction *action;
-
- self->dragged = -1;
- self->edit = FALSE;
-
- gesture = gtk_gesture_drag_new ();
- gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), GDK_BUTTON_PRIMARY);
- g_signal_connect (gesture, "drag-begin", G_CALLBACK (drag_begin), self);
- g_signal_connect (gesture, "drag-update", G_CALLBACK (drag_update), self);
- g_signal_connect (gesture, "drag-end", G_CALLBACK (drag_end), self);
- gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture));
-
- gesture = gtk_gesture_click_new ();
- gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), 0);
- g_signal_connect (gesture, "pressed", G_CALLBACK (pressed), self);
- g_signal_connect (gesture, "released", G_CALLBACK (released), self);
- gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture));
-
- init_points (self);
-
- 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 (toggle_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 ("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));
-
- 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);
-
- 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);
-
- 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));
-}
-
-static void
-demo_widget_snapshot (GtkWidget *widget,
- GtkSnapshot *snapshot)
-{
- DemoWidget *self = (DemoWidget *)widget;
GskPathBuilder *builder;
- GskPath *path;
- GskStroke *stroke;
- int i, j;
- float width;
- float height;
-
- width = gtk_widget_get_width (widget);
- height = gtk_widget_get_width (widget);
builder = gsk_path_builder_new ();
- if (self->edit)
- {
- /* Add the skeleton */
-
- gsk_path_builder_move_to (builder, self->points[0].x, self->points[0].y);
- for (i = 1; i < self->n_points; i++)
- {
- gboolean edit;
- gboolean line;
-
- if (i % 3 == 2)
- edit = self->point_data[((i + 3) % self->n_points) / 3].edit;
- else
- edit = self->point_data[i / 3].edit;
-
- if (i % 3 == 0)
- line = self->point_data[((i - 1 + self->n_points) % self->n_points) / 3].op != CURVE;
- else
- line = self->point_data[i / 3].op != CURVE;
-
- if (edit)
- {
- if (i % 3 == 2 || line)
- gsk_path_builder_move_to (builder, self->points[i].x, self->points[i].y);
- else
- gsk_path_builder_line_to (builder, self->points[i].x, self->points[i].y);
- }
- }
- if (self->point_data[0].edit)
- gsk_path_builder_line_to (builder, self->points[0].x, self->points[0].y);
- }
-
- /* Add the curve itself */
-
- 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;
-
- 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 ();
- }
- }
-
- /* Stroke everything we have so far */
-
- path = gsk_path_builder_free_to_path (builder);
- stroke = gsk_stroke_new (1);
- gtk_snapshot_push_stroke (snapshot, path, stroke);
- gsk_stroke_free (stroke);
- gsk_path_unref (path);
-
- gtk_snapshot_append_color (snapshot,
- &(GdkRGBA){ 0, 0, 0, 1 },
- &GRAPHENE_RECT_INIT (0, 0, width, height ));
-
- gtk_snapshot_pop (snapshot);
+ gsk_path_builder_move_to (builder, cx, pad);
+ gsk_path_builder_curve_to (builder, cx + kr, pad,
+ w - pad, cy - kr,
+ w - pad, cy);
+ gsk_path_builder_curve_to (builder, w - pad, cy + kr,
+ cx + kr, h - pad,
+ cx, h - pad);
+ gsk_path_builder_curve_to (builder, cx - kr, h - pad,
+ pad, cy + kr,
+ pad, cy);
+ gsk_path_builder_curve_to (builder, pad, cy - kr,
+ cx - kr, pad,
+ cx, pad);
- if (self->edit)
- {
- /* Draw the circles, in several passes, one for each color */
-
- const char *colors[] = {
- "red",
- "green",
- "blue"
- };
- GdkRGBA color;
-
- for (j = 0; j < 3; j++)
- {
- builder = gsk_path_builder_new ();
-
- for (i = 0; i < self->n_points; i++)
- {
- switch (j)
- {
- case 0:
- if (!(i % 3 == 0 &&
- self->point_data[i / 3].smooth))
- continue;
- break;
-
- case 1:
- if (!(i % 3 == 0 &&
- !self->point_data[i / 3].smooth))
- continue;
- break;
-
- case 2:
- if (i % 3 == 1)
- {
- if (!(self->point_data[i / 3].edit &&
- self->point_data[i / 3].op == CURVE))
- continue;
- }
- else if (i % 3 == 2)
- {
- if (!(self->point_data[((i + 3) % self->n_points) / 3].edit &&
- self->point_data[i / 3].op == CURVE))
- continue;
- }
- else
- continue;
- break;
-
- default:
- g_assert_not_reached ();
- }
-
- gsk_path_builder_add_circle (builder, &self->points[i], RADIUS);
- }
-
- path = gsk_path_builder_free_to_path (builder);
-
- gtk_snapshot_push_fill (snapshot, path, GSK_FILL_RULE_WINDING);
- gdk_rgba_parse (&color, colors[j]);
- gtk_snapshot_append_color (snapshot, &color, &GRAPHENE_RECT_INIT (0, 0, width, height));
- gtk_snapshot_pop (snapshot);
-
- stroke = gsk_stroke_new (1.0);
- gtk_snapshot_push_stroke (snapshot, path, stroke);
- gsk_stroke_free (stroke);
-
- gdk_rgba_parse (&color, "black");
- gtk_snapshot_append_color (snapshot, &color, &GRAPHENE_RECT_INIT (0, 0, width, height));
- gtk_snapshot_pop (snapshot);
-
- gsk_path_unref (path);
- }
- }
-}
-
-static void
-demo_widget_measure (GtkWidget *widget,
- GtkOrientation orientation,
- int for_size,
- int *minimum_size,
- int *natural_size,
- int *minimum_baseline,
- int *natural_baseline)
-{
- *minimum_size = 100;
- *natural_size = 200;
-}
-
-static void
-demo_widget_size_allocate (GtkWidget *widget,
- int width,
- int height,
- int baseline)
-{
- DemoWidget *self = DEMO_WIDGET (widget);
-
- gtk_native_check_resize (GTK_NATIVE (self->menu));
-}
-
-static void
-demo_widget_dispose (GObject *object)
-{
- DemoWidget *self = DEMO_WIDGET (object);
-
- g_clear_pointer (&self->points, g_free);
- g_clear_pointer (&self->point_data, g_free);
- g_clear_pointer (&self->menu, gtk_widget_unparent);
-
- G_OBJECT_CLASS (demo_widget_parent_class)->dispose (object);
-}
-
-static void
-demo_widget_class_init (DemoWidgetClass *class)
-{
- GObjectClass *object_class = G_OBJECT_CLASS (class);
- GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
-
- object_class->dispose = demo_widget_dispose;
-
- widget_class->snapshot = demo_widget_snapshot;
- widget_class->measure = demo_widget_measure;
- widget_class->size_allocate = demo_widget_size_allocate;
-}
-
-static GtkWidget *
-demo_widget_new (void)
-{
- return g_object_new (demo_widget_get_type (), NULL);
+ return gsk_path_builder_free_to_path (builder);
}
static void
edit_changed (GtkToggleButton *button,
GParamSpec *pspec,
- DemoWidget *self)
+ CurveEditor *editor)
{
- int i;
-
- self->edit = gtk_toggle_button_get_active (button);
- if (!self->edit)
- {
- for (i = 0; i < self->n_points / 3; i++)
- self->point_data[i].edit = FALSE;
- }
- gtk_widget_queue_draw (GTK_WIDGET (self));
+ curve_editor_set_edit (editor, gtk_toggle_button_get_active (button));
}
static void
-reset (GtkButton *button,
- DemoWidget *self)
+reset (GtkButton *button,
+ CurveEditor *editor)
{
- init_points (self);
- gtk_widget_queue_draw (GTK_WIDGET (self));
+ GskPath *path;
+
+ path = make_circle_path ();
+ curve_editor_set_path (editor, path);
+ gsk_path_unref (path);
}
int
@@ -913,11 +78,13 @@ main (int argc, char *argv[])
gtk_window_set_titlebar (GTK_WINDOW (window), titlebar);
- demo = demo_widget_new ();
+ demo = curve_editor_new ();
g_signal_connect (edit_toggle, "notify::active", G_CALLBACK (edit_changed), demo);
g_signal_connect (reset_button, "clicked", G_CALLBACK (reset), demo);
+ reset (NULL, CURVE_EDITOR (demo));
+
gtk_window_set_child (window, demo);
gtk_window_present (window);
diff --git a/tests/meson.build b/tests/meson.build
index e9d456c1fe..92f9502888 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -1,6 +1,6 @@
gtk_tests = [
# testname, optional extra sources
- ['curve'],
+ ['curve', ['curve.c', 'curve-editor.c']],
['testupload'],
['testtransform'],
['testdropdown'],