diff options
author | Matthias Clasen <mclasen@redhat.com> | 2020-11-21 08:47:14 -0500 |
---|---|---|
committer | Matthias Clasen <mclasen@redhat.com> | 2020-11-21 08:47:34 -0500 |
commit | 267c1a277c2304ae0a3fddb71f64a0ea9a448539 (patch) | |
tree | 0199ab35ec45e3c5e0d2f7d82d7bed34695854df | |
parent | ca4d610bf2bca3b000c953f9338bd324f54d562a (diff) | |
download | gtk+-267c1a277c2304ae0a3fddb71f64a0ea9a448539.tar.gz |
Rename to CurveEditor
And start separating the widget from the demo
with an api.
-rw-r--r-- | tests/curve-editor.c | 1014 | ||||
-rw-r--r-- | tests/curve-editor.h | 18 | ||||
-rw-r--r-- | tests/curve.c | 891 | ||||
-rw-r--r-- | tests/meson.build | 2 |
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'], |