diff --git a/demos/gtk-demo/cepath.c b/demos/gtk-demo/cepath.c new file mode 100644 index 0000000000..6cfd079919 --- /dev/null +++ b/demos/gtk-demo/cepath.c @@ -0,0 +1,1882 @@ +#include "cepath.h" + +#include + +/* {{{ Misc. geometry */ + +static inline float +point_distance (const graphene_point_t *a, + const graphene_point_t *b) +{ + return graphene_point_distance (a, b, NULL, NULL); +} + +/* 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); +} + +static void +find_point_on_line (const graphene_point_t *p1, + const graphene_point_t *p2, + const graphene_point_t *q, + float *t) +{ + float tx = p2->x - p1->x; + float ty = p2->y - p1->y; + float sx = q->x - p1->x; + float sy = q->y - p1->y; + + *t = (tx*sx + ty*sy) / (tx*tx + ty*ty); +} + +/* Determine if p is on the line through a and b */ +static gboolean +collinear (const graphene_point_t *p, + 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); +} + +/* Set q to the point on the line through p and a that is + * at a distance of d from p, on the same side + */ +static void +scale_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); +} + +/* Set p to the intersection of the lines through a, b + * and c, d + */ +static void +line_intersection (const graphene_point_t *a, + const graphene_point_t *b, + const graphene_point_t *c, + const graphene_point_t *d, + graphene_point_t *p) +{ + double a1 = b->y - a->y; + double b1 = a->x - b->x; + double c1 = a1*a->x + b1*a->y; + + double a2 = d->y - c->y; + double b2 = c->x - d->x; + double c2 = a2*c->x+ b2*c->y; + + double det = a1*b2 - a2*b1; + + if (det == 0) + { + p->x = NAN; + p->y = NAN; + } + else + { + p->x = (b2*c1 - b1*c2) / det; + p->y = (a1*c2 - a2*c1) / det; + } +} + +/* Given 3 points, determine the center of a circle that + * passes through all of them. + */ +static void +circle_through_points (const graphene_point_t *a, + const graphene_point_t *b, + const graphene_point_t *c, + graphene_point_t *center) +{ + graphene_point_t ab; + graphene_point_t ac; + graphene_point_t ab2; + graphene_point_t ac2; + + ab.x = (a->x + b->x) / 2; + ab.y = (a->y + b->y) / 2; + ac.x = (a->x + c->x) / 2; + ac.y = (a->y + c->y) / 2; + + ab2.x = ab.x + a->y - b->y; + ab2.y = ab.y + b->x - a->x; + ac2.x = ac.x + a->y - c->y; + ac2.y = ac.y + c->x - a->x; + + line_intersection (&ab, &ab2, &ac, &ac2, center); +} + +/* Return the cosine of the angle between b1 - a and b2 - a */ +static double +three_point_angle (const graphene_point_t *a, + const graphene_point_t *b1, + const graphene_point_t *b2) +{ + graphene_vec2_t u; + graphene_vec2_t v; + + graphene_vec2_init (&u, b1->x - a->x, b1->y - a->y); + graphene_vec2_init (&v, b2->x - a->x, b2->y - a->y); + graphene_vec2_normalize (&u, &u); + graphene_vec2_normalize (&v, &v); + + return graphene_vec2_dot (&u, &v); +} + +/* }}} */ +/* {{{ Misc. Bezier math */ + +/* Given Bezier control points and a t value between 0 and 1, + * return new Bezier control points for two curves in left + * and right that are obtained by splitting the curve at the + * point for t. + * + * Note that the points in the right array are in returned in + * reverse order. + */ +static void +split_bezier (graphene_point_t *points, + int length, + float t, + graphene_point_t *left, + int *left_pos, + graphene_point_t *right, + int *right_pos) +{ + if (length == 1) + { + left[*left_pos] = points[0]; + (*left_pos)++; + right[*right_pos] = points[0]; + (*right_pos)++; + } + else + { + graphene_point_t *newpoints; + int i; + + newpoints = g_alloca (sizeof (graphene_point_t) * (length - 1)); + for (i = 0; i < length - 1; i++) + { + if (i == 0) + { + left[*left_pos] = points[i]; + (*left_pos)++; + } + if (i + 1 == length - 1) + { + right[*right_pos] = points[i + 1]; + (*right_pos)++; + } + graphene_point_interpolate (&points[i], &points[i + 1], t, &newpoints[i]); + } + split_bezier (newpoints, length - 1, t, left, left_pos, right, right_pos); + } +} + +static double +projection_ratio (double t) +{ + double top, bottom; + + if (t == 0 || t == 1) + return t; + + top = pow (1 - t, 3), + bottom = pow (t, 3) + top; + + return top / bottom; +} + +static double +abc_ratio (double t) +{ + double top, bottom; + + if (t == 0 || t == 1) + return t; + + bottom = pow (t, 3) + pow (1 - t, 3); + top = bottom - 1; + + return fabs (top / bottom); +} + +static void +find_control_points (double t, + const graphene_point_t *A, + const graphene_point_t *B, + const graphene_point_t *C, + const graphene_point_t *S, + const graphene_point_t *E, + graphene_point_t *C1, + graphene_point_t *C2) +{ + double angle; + double dist; + double bc; + double de1; + double de2; + graphene_point_t c; + graphene_point_t t0, t1; + double tlength; + double dx, dy; + graphene_point_t e1, e2; + graphene_point_t v1, v2; + + dist = point_distance (S, E); + angle = atan2 (E->y - S->y, E->x - S->x) - atan2 (B->y - S->y, B->x - S->x); + bc = (angle < 0 || angle > M_PI ? -1 : 1) * dist / 3; + de1 = t * bc; + de2 = (1 - t) * bc; + + circle_through_points (S, B, E, &c); + + t0.x = B->x - (B->y - c.y); + t0.y = B->y + (B->x - c.x); + t1.x = B->x + (B->y - c.y); + t1.y = B->y - (B->x - c.x); + + tlength = point_distance (&t0, &t1); + dx = (t1.x - t0.x) / tlength; + dy = (t1.y - t0.y) / tlength; + + e1.x = B->x + de1 * dx; + e1.y = B->y + de1 * dy; + e2.x = B->x - de2 * dx; + e2.y = B->y - de2 * dy; + + v1.x = A->x + (e1.x - A->x) / (1 - t); + v1.y = A->y + (e1.y - A->y) / (1 - t); + + v2.x = A->x + (e2.x - A->x) / t; + v2.y = A->y + (e2.y - A->y) / t; + + C1->x = S->x + (v1.x - S->x) / t; + C1->y = S->y + (v1.y - S->y) / t; + + C2->x = E->x + (v2.x - E->x) / (1 - t); + C2->y = E->y + (v2.y - E->y) / (1 - t); +} + +/* Given points S, B, E, determine control + * points C1, C2 such that B lies on the + * Bezier curve given bY S, C1, C2, E. + */ +static void +bezier_through (const graphene_point_t *S, + const graphene_point_t *B, + const graphene_point_t *E, + graphene_point_t *C1, + graphene_point_t *C2) +{ + double d1, d2, t; + double u, um, s; + graphene_point_t A, C; + + d1 = point_distance (S, B); + d2 = point_distance (E, B); + t = d1 / (d1 + d2); + + u = projection_ratio (t); + um = 1 - u; + + C.x = u * S->x + um * E->x; + C.y = u * S->y + um * E->y; + + s = abc_ratio (t); + + A.x = B->x + (B->x - C.x) / s; + A.y = B->y + (B->y - C.y) / s; + + find_control_points (t, &A, B, &C, S, E, C1, C2); +} + +/* conics */ + +static void +get_conic_shoulder_point (const graphene_point_t p[3], + float w, + graphene_point_t *q) +{ + graphene_point_t m; + + graphene_point_interpolate (&p[0], &p[2], 0.5, &m); + graphene_point_interpolate (&m, &p[1], w / (1 + w), q); +} + +static void +split_bezier3d_recurse (const graphene_point3d_t *p, + int l, + float t, + graphene_point3d_t *left, + graphene_point3d_t *right, + int *lpos, + int *rpos) +{ + if (l == 1) + { + left[*lpos] = p[0]; + right[*rpos] = p[0]; + } + else + { + graphene_point3d_t *np; + int i; + + np = g_alloca (sizeof (graphene_point3d_t) * (l - 1)); + for (i = 0; i < l - 1; i++) + { + if (i == 0) + { + left[*lpos] = p[i]; + (*lpos)++; + } + if (i + 1 == l - 1) + { + right[*rpos] = p[i + 1]; + (*rpos)--; + } + graphene_point3d_interpolate (&p[i], &p[i + 1], t, &np[i]); + } + split_bezier3d_recurse (np, l - 1, t, left, right, lpos, rpos); + } +} + +static void +split_bezier3d (const graphene_point3d_t *p, + int l, + float t, + graphene_point3d_t *left, + graphene_point3d_t *right) +{ + int lpos = 0; + int rpos = l - 1; + split_bezier3d_recurse (p, l, t, left, right, &lpos, &rpos); +} + +static void +split_conic (const graphene_point_t points[3], float weight, + float t, + graphene_point_t lp[3], float *lw, + graphene_point_t rp[3], float *rw) +{ + /* Given control points and weight for a rational quadratic + * Bezier and t, create two sets of the same that give the + * same curve as the original and split the curve at t. + */ + graphene_point3d_t p[3]; + graphene_point3d_t l[3], r[3]; + int i; + + /* do de Casteljau in homogeneous coordinates... */ + for (i = 0; i < 3; i++) + { + p[i].x = points[i].x; + p[i].y = points[i].y; + p[i].z = 1; + } + + p[1].x *= weight; + p[1].y *= weight; + p[1].z *= weight; + + split_bezier3d (p, 3, t, l, r); + + /* then project the control points down */ + for (i = 0; i < 3; i++) + { + lp[i].x = l[i].x / l[i].z; + lp[i].y = l[i].y / l[i].z; + rp[i].x = r[i].x / r[i].z; + rp[i].y = r[i].y / r[i].z; + } + + /* normalize the outer weights to be 1 by using + * the fact that weights w_i and c*w_i are equivalent + * for any nonzero constant c + */ + for (i = 0; i < 3; i++) + { + l[i].z /= l[0].z; + r[i].z /= r[2].z; + } + + /* normalize the inner weight to be 1 by using + * the fact that w_0*w_2/w_1^2 is a constant for + * all equivalent weights. + */ + *lw = l[1].z / sqrt (l[2].z); + *rw = r[1].z / sqrt (r[0].z); +} + +/* }}} */ +/* {{{ GObject boilerplate */ + +/* A curve describes a Bézier curve and the + * on-curve point where it starts. + */ +struct _CEPathCurve +{ + GskPathOperation op; + graphene_point_t p[4]; + float weight; + CEPathConstraint constraint; + CEPathCurve *prev; + CEPathCurve *next; + int mark; +}; + +struct _CEPath +{ + GObject parent_instance; + + GPtrArray *curves; +}; + +struct _CEPathClass +{ + GObjectClass parent_class; +}; + +G_DEFINE_TYPE (CEPath, ce_path, G_TYPE_OBJECT) + +static void +ce_path_init (CEPath *self) +{ + self->curves = g_ptr_array_new_with_free_func (g_free); +} + +static void +ce_path_finalize (GObject *object) +{ + CEPath *self = CE_PATH (object); + + g_ptr_array_unref (self->curves); + + G_OBJECT_CLASS (ce_path_parent_class)->finalize (object); +} + +static void +ce_path_class_init (CEPathClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + + object_class->finalize = ce_path_finalize; +} + +/* }}} */ +/* {{{ Constraints */ + +static void +maintain_smoothness (CEPath *self, + CEPathCurve *seg1, + CEPathCurve *seg) +{ + const graphene_point_t *p, *p2; + graphene_point_t *c, *c2; + float d; + + g_return_if_fail (seg1 != NULL); + g_return_if_fail (seg != NULL); + + if (seg->constraint == CE_PATH_CUSP) + return; + + if (seg->op == GSK_PATH_MOVE || seg1->op == GSK_PATH_MOVE) + return; + + if (seg->op == GSK_PATH_LINE && seg1->op == GSK_PATH_LINE) + return; + + p = &seg->p[0]; + c = &seg1->p[2]; + c2 = &seg->p[1]; + + if (seg->op == GSK_PATH_CUBIC && seg1->op == GSK_PATH_CUBIC) + { + d = point_distance (c, p); + opposite_point (p, c2, d, c); + } + else if (seg->op == GSK_PATH_CUBIC) + { + if (seg1->op == GSK_PATH_LINE) + p2 = &seg1->p[0]; + else if (seg1->op == GSK_PATH_CONIC || seg1->op == GSK_PATH_QUAD) + p2 = &seg1->p[1]; + else + g_assert_not_reached (); + d = point_distance (c2, p); + opposite_point (p, p2, d, c2); + } + else if (seg1->op == GSK_PATH_CUBIC) + { + if (seg->op == GSK_PATH_LINE) + p2 = &seg->p[3]; + else if (seg->op == GSK_PATH_CONIC || seg->op == GSK_PATH_QUAD) + p2 = &seg->p[1]; + else + g_assert_not_reached (); + d = point_distance (c, p); + opposite_point (p, p2, d, c); + } + else if ((seg->op == GSK_PATH_CONIC && seg1->op == GSK_PATH_CONIC) || + (seg->op == GSK_PATH_QUAD && seg1->op == GSK_PATH_QUAD)) + { + graphene_point_t h, a, b; + + h.x = seg->p[0].x + seg->p[1].x - seg1->p[1].x; + h.y = seg->p[0].y + seg->p[1].y - seg1->p[1].y; + line_intersection (&seg->p[0], &h, &seg1->p[0], &seg1->p[1], &a); + line_intersection (&seg->p[0], &h, &seg->p[1], &seg->p[3], &b); + + seg1->p[1] = a; + seg->p[1] = b; + } +} + +static void +maintain_symmetry (CEPath *self, + CEPathCurve *seg1, + CEPathCurve *seg) +{ + const graphene_point_t *p; + graphene_point_t *c, *c2; + double l1, l2, l; + + g_return_if_fail (seg1 != NULL); + g_return_if_fail (seg != NULL); + + if (seg->op == GSK_PATH_MOVE || seg1->op == GSK_PATH_MOVE) + return; + + if (seg->constraint != CE_PATH_SYMMETRIC) + return; + + if (seg->op != GSK_PATH_CUBIC || seg1->op != GSK_PATH_CUBIC) + return; + + p = &seg->p[0]; + c = &seg1->p[2]; + c2 = &seg->p[1]; + + l1 = point_distance (p, c); + l2 = point_distance (p, c2); + + if (l1 != l2) + { + l = (l1 + l2) / 2; + + scale_point (p, c, l, c); + scale_point (p, c2, l, c2); + } +} + +/* Make the line through the control points perpendicular + * to the line bisecting the angle between neighboring + * points, and make the lengths 1/3 of the distance to + * the corresponding neighboring points. + */ +static void +update_automatic (CEPath *self, + CEPathCurve *seg1, + CEPathCurve *seg) +{ + const graphene_point_t *p, *p1, *p2; + double l1, l2; + graphene_point_t a; + graphene_point_t *c1, *c2; + + if (!seg1 || !seg) + return; + + if (seg1->op != GSK_PATH_CUBIC || seg->op != GSK_PATH_CUBIC) + return; + + if (seg->constraint != CE_PATH_AUTOMATIC) + return; + + p = &seg->p[0]; + c1 = &seg1->p[2]; + c2 = &seg->p[1]; + + p1 = &seg1->p[0]; + p2 = &seg->p[3]; + + l1 = point_distance (p, p1); + l2 = point_distance (p, p2); + + a.x = p2->x + (p->x - p1->x); + a.y = p2->y + (p->y - p1->y); + + scale_point (p, &a, l2/3, c2); + opposite_point (p, &a, l1/3, c1); +} + +static void +maintain_automatic (CEPath *self, + CEPathCurve *seg11, + CEPathCurve *seg1, + CEPathCurve *seg, + CEPathCurve *seg2) +{ + g_return_if_fail (seg1 != NULL); + g_return_if_fail (seg != NULL); + + update_automatic (self, seg1, seg); + update_automatic (self, seg11, seg1); + update_automatic (self, seg, seg2); +} + +static void +update_conic (CEPathCurve *seg) +{ + graphene_point_t p[3]; + + g_return_if_fail (seg != NULL); + + if (seg->op != GSK_PATH_CONIC) + return; + + p[0] = seg->p[0]; + p[1] = seg->p[1]; + p[2] = seg->p[3]; + + get_conic_shoulder_point (p, seg->weight, &seg->p[2]); +} + +/* Check if the points arount point currently satisfy + * smoothness conditions. Set PointData.type accordingly. + */ +static void +check_smoothness (CEPath *self, + CEPathCurve *seg) +{ + GskPathOperation op, op1; + const graphene_point_t *p, *p1, *p2; + CEPathCurve *seg1; + + seg1 = seg->prev; + if (seg1 == NULL) + return; + + p = &seg->p[0]; + + op = seg->op; + op1 = seg1->op; + + if (op == GSK_PATH_CUBIC) + p2 = &seg->p[1]; + else if (op == GSK_PATH_LINE) + p2 = &seg->p[3]; + else + p2 = NULL; + + if (op1 == GSK_PATH_CUBIC) + p1 = &seg1->p[2]; + else if (op1 == GSK_PATH_LINE) + p1 = &seg1->p[0]; + else + p1 = NULL; + + if (!p1 || !p2 || !collinear (p, p1, p2)) + seg->constraint = CE_PATH_CUSP; + else + seg->constraint = CE_PATH_SMOOTH; +} + +/* }}} */ +/* {{{ Basics */ + +CEPath * +ce_path_new (void) +{ + return g_object_new (CE_TYPE_PATH, NULL); +} + +int +ce_path_get_n_curves (CEPath *self) +{ + g_return_val_if_fail (CE_IS_PATH (self), 0); + + return self->curves->len; +} + +CEPathCurve * +ce_path_get_curve (CEPath *self, + int idx) +{ + g_return_val_if_fail (CE_IS_PATH (self), NULL); + g_return_val_if_fail (0 <= idx && idx < self->curves->len, NULL); + + return g_ptr_array_index (self->curves, idx); +} + +CEPathCurve * +ce_path_previous_curve (CEPath *self, + CEPathCurve *seg) +{ + g_return_val_if_fail (CE_IS_PATH (self), NULL); + g_return_val_if_fail (seg != NULL, NULL); + + return seg->prev; +} + +CEPathCurve * +ce_path_next_curve (CEPath *self, + CEPathCurve *seg) +{ + g_return_val_if_fail (CE_IS_PATH (self), NULL); + g_return_val_if_fail (seg != NULL, NULL); + + return seg->next; +} + +/* }}} */ +/* {{{ GskPath interaction */ + +static void +path_builder_add_curve (CEPath *self, + CEPathCurve *seg, + GskPathBuilder *builder, + gboolean need_move_to) +{ + if (need_move_to) + gsk_path_builder_move_to (builder, seg->p[0].x, seg->p[0].y); + + switch (seg->op) + { + case GSK_PATH_LINE: + gsk_path_builder_line_to (builder, seg->p[3].x, seg->p[3].y); + break; + + case GSK_PATH_QUAD: + gsk_path_builder_quad_to (builder, + seg->p[1].x, seg->p[1].y, + seg->p[3].x, seg->p[3].y); + break; + + case GSK_PATH_CUBIC: + gsk_path_builder_cubic_to (builder, + seg->p[1].x, seg->p[1].y, + seg->p[2].x, seg->p[2].y, + seg->p[3].x, seg->p[3].y); + break; + + case GSK_PATH_CONIC: + gsk_path_builder_conic_to (builder, + seg->p[1].x, seg->p[1].y, + seg->p[3].x, seg->p[3].y, + seg->weight); + break; + + case GSK_PATH_MOVE: + case GSK_PATH_CLOSE: + default: + break; + } +} + +static void +path_builder_add_path (CEPath *self, + GskPathBuilder *builder) +{ + CEPathCurve *next, *seg, *seg2; + + for (int i = 0; i < self->curves->len; i++) + { + seg = g_ptr_array_index (self->curves, i); + seg->mark = 1; + } + + do + { + next = NULL; + for (int i = 0; i < self->curves->len; i++) + { + seg = g_ptr_array_index (self->curves, i); + if (seg->mark && seg->prev == NULL) + { + seg2 = seg; + while (seg2->prev != NULL && seg2 != seg) + seg2 = seg2->prev; + next = seg2; + break; + } + } + + if (next) + { + seg = next; + gsk_path_builder_move_to (builder, seg->p[0].x, seg->p[0].y); + do + { + if (seg->mark == 0) + { + gsk_path_builder_close (builder); + break; + } + + path_builder_add_curve (self, seg, builder, FALSE); + + seg->mark = 0; + seg = seg->next; + } + while (seg); + } + } + while (next); +} + +typedef struct +{ + CEPath *self; + CEPathCurve *first; + CEPathCurve *last; +} CollectData; + +static gboolean +copy_curves (GskPathOperation op, + const graphene_point_t *pts, + gsize n_pts, + float weight, + gpointer data) +{ + CollectData *cd = data; + CEPath *self = cd->self; + CEPathCurve *seg; + + if (op == GSK_PATH_MOVE) + { + cd->first = NULL; + cd->last = NULL; + return TRUE; + } + + seg = g_new0 (CEPathCurve, 1); + seg->op = op; + + seg->prev = cd->last; + if (cd->last) + cd->last->next = seg; + seg->next = NULL; + cd->last = seg; + + switch (op) + { + case GSK_PATH_CLOSE: + seg->p[0] = pts[0]; + seg->p[3] = pts[1]; + seg->next = cd->first; + if (cd->first) + cd->first->prev = seg; + cd->first = NULL; + cd->last = NULL; + return TRUE; + + case GSK_PATH_LINE: + seg->p[0] = pts[0]; + seg->p[3] = pts[1]; + break; + + case GSK_PATH_QUAD: + seg->p[0] = pts[0]; + seg->p[1] = pts[1]; + seg->p[3] = pts[2]; + break; + + case GSK_PATH_CUBIC: + seg->p[0] = pts[0]; + seg->p[1] = pts[1]; + seg->p[2] = pts[2]; + seg->p[3] = pts[3]; + break; + + case GSK_PATH_CONIC: + { + seg->p[0] = pts[0]; + seg->p[1] = pts[1]; + seg->p[3] = pts[2]; + seg->weight = weight; + + get_conic_shoulder_point (pts, weight, &seg->p[2]); + } + break; + + case GSK_PATH_MOVE: + default: + g_assert_not_reached (); + } + + g_ptr_array_add (self->curves, seg); + + return TRUE; +} + +void +ce_path_set_gsk_path (CEPath *self, + GskPath *path) +{ + CollectData cd; + + g_ptr_array_set_size (self->curves, 0); + + cd.self = self; + cd.first = NULL; + cd.last = NULL; + + gsk_path_foreach (path, -1, copy_curves, &cd); + + if (self->curves->len == 0) + return; + +#if 0 + /* Currently, the code assumes there is a 1-1 relationship + * between curves and line points. So add an artificial + * move curve at the end, as holder for the endpoint + */ + first = get_curve (self, 0); + last = get_curve (self, self->curves->len - 1); + if (last->op == GSK_PATH_CLOSE) + { + if (graphene_point_near (&last->p[0], &last->p[3], 0.001)) + g_array_remove_index (self->curves, self->curves->len - 1); + else + last->op = GSK_PATH_LINE; + } + else + { + Segment seg; + + memset (&seg, 0, sizeof (Segment)); + + seg.op = GSK_PATH_MOVE; + seg.p[0] = last->p[3]; + seg.p[3] = first->p[0]; + g_array_append_val (self->curves, seg); + } +#endif + + for (int i = 0; i < self->curves->len; i++) + check_smoothness (self, g_ptr_array_index (self->curves, i)); +} + +GskPath * +ce_path_get_gsk_path (CEPath *self) +{ + GskPathBuilder *builder; + + builder = gsk_path_builder_new (); + + path_builder_add_path (self, builder); + + return gsk_path_builder_free_to_path (builder); +} + +/* }}} */ +/* {{{ Segment juggling */ + +void +ce_path_split_curve (CEPath *self, + CEPathCurve *seg, + float pos) +{ + CEPathCurve *seg1, *seg11, *seg2; + CEPathCurve *ns; + + g_return_if_fail (CE_IS_PATH (self)); + g_return_if_fail (seg != NULL); + g_return_if_fail (0 < pos && pos < 1); + + if (seg->op == GSK_PATH_MOVE) + return; + + ns = g_new0 (CEPathCurve, 1); + ns->prev = seg; + ns->next = seg->next; + seg->next->prev = ns; + seg->next = ns; + g_ptr_array_add (self->curves, ns); + + seg1 = seg->next; + seg2 = seg1->next; + seg11 = seg->prev; + + seg1->constraint = CE_PATH_SMOOTH; + + switch (seg->op) + { + case GSK_PATH_LINE: + seg1->op = GSK_PATH_LINE; + + graphene_point_interpolate (&seg->p[0], &seg->p[3], pos, &seg1->p[0]); + seg->p[3] = seg->p[0]; + seg1->p[3] = seg2->p[0]; + break; + + case GSK_PATH_QUAD: + { + graphene_point_t points[3]; + graphene_point_t left[3]; + graphene_point_t right[3]; + int left_pos = 0; + int right_pos = 0; + + seg1->op = GSK_PATH_QUAD; + + points[0] = seg->p[0]; + points[1] = seg->p[1]; + points[2] = seg->p[3]; + split_bezier (points, 3, pos, left, &left_pos, right, &right_pos); + + seg->p[0] = left[0]; + seg->p[1] = left[1]; + seg->p[3] = left[2]; + seg1->p[0] = right[2]; + seg1->p[1] = right[1]; + seg1->p[3] = right[0]; + } + break; + + case GSK_PATH_CUBIC: + { + graphene_point_t left[4]; + graphene_point_t right[4]; + int left_pos = 0; + int right_pos = 0; + + seg1->op = GSK_PATH_CUBIC; + + split_bezier (seg->p, 4, pos, left, &left_pos, right, &right_pos); + + seg->p[0] = left[0]; + seg->p[1] = left[1]; + seg->p[2] = left[2]; + seg->p[3] = left[3]; + seg1->p[0] = right[3]; + seg1->p[1] = right[2]; + seg1->p[2] = right[1]; + seg1->p[3] = right[0]; + } + break; + + case GSK_PATH_CONIC: + { + graphene_point_t points[3]; + graphene_point_t left[3]; + graphene_point_t right[3]; + float lw, rw; + + seg1->op = GSK_PATH_CONIC; + + points[0] = seg->p[0]; + points[1] = seg->p[1]; + points[2] = seg->p[3]; + split_conic (points, seg->weight, pos, left, &lw, right, &rw); + + seg->p[0] = left[0]; + seg->p[1] = left[1]; + seg->p[3] = left[2]; + seg1->p[0] = right[0]; + seg1->p[1] = right[1]; + seg1->p[3] = right[2]; + + seg->weight = lw; + seg1->weight = rw; + + get_conic_shoulder_point (seg->p, seg->weight, &seg->p[2]); + get_conic_shoulder_point (seg1->p, seg1->weight, &seg1->p[2]); + } + break; + + case GSK_PATH_MOVE: + case GSK_PATH_CLOSE: + default: + g_assert_not_reached (); + break; + } + + if (seg1 && seg) + { + maintain_smoothness (self, seg1, seg); + if (seg2) + maintain_smoothness (self, seg1, seg2); + maintain_automatic (self, seg11, seg, seg1, seg2); + } +} + +void +ce_path_remove_curve (CEPath *self, + CEPathCurve *seg) +{ + CEPathCurve *seg1 = NULL, *prev; + graphene_point_t c, p; + + g_return_if_fail (CE_IS_PATH (self)); + g_return_if_fail (seg != NULL); + + if (seg->op == GSK_PATH_MOVE) + { + if (seg->prev) + seg1 = seg->prev->prev; + if (seg1) + { + c = seg1->p[2]; + p = seg1->p[3]; + seg->p[0] = p; + } + seg = seg->prev; + if (seg == NULL) + return; + } + else + { + c = seg->p[2]; + p = seg->p[3]; + } + + prev = seg->prev; + g_ptr_array_remove_fast (self->curves, seg); + seg = prev; + if (seg) + { + seg->p[2] = c; + seg->p[3] = p; + seg1 = seg->prev; + } + + if (seg && seg1) + { + maintain_smoothness (self, seg1, seg); + maintain_automatic (self, seg1->prev, seg1, seg, seg->next); + } +} + +/* }}} */ +/* {{{ Drag implementation */ + +void +ce_path_drag_curve (CEPath *self, + CEPathCurve *seg, + const graphene_point_t *pos) +{ + graphene_point_t *S, *E; + graphene_point_t B, C1, C2; + double l; + CEPathCurve *seg1, *seg2; + + g_return_if_fail (CE_IS_PATH (self)); + g_return_if_fail (seg != NULL); + g_return_if_fail (pos != NULL); + + seg1 = seg->next; + seg2 = seg->prev; + + if (seg->op == GSK_PATH_CONIC) + { + /* FIXME */ + return; + } + + S = &seg->p[0]; + B = *pos; + E = &seg->p[3]; + + bezier_through (S, &B, E, &C1, &C2); + + seg->p[1] = C1; + seg->p[2] = C2; + + /* When the neighboring curves are lines, we can't actually + * use C1 and C2 as-is, since we need control points to lie + * on the line. So we just use their distance. This makes our + * point B not quite match anymore, but we're overconstrained. + */ + if (seg2 && seg2->op == GSK_PATH_LINE) + { + l = point_distance (&seg->p[3], &C1); + if (three_point_angle (&seg2->p[3], &seg2->p[0], &B) > 0) + scale_point (&seg2->p[3], &seg2->p[0], l, &seg->p[1]); + else + opposite_point (&seg2->p[3], &seg2->p[0], l, &seg->p[1]); + } + + if (seg1 && seg1->op == GSK_PATH_LINE) + { + l = point_distance (&seg->p[0], &C2); + if (three_point_angle (&seg1->p[0], &seg1->p[3], &B) > 0) + scale_point (&seg1->p[0], &seg1->p[3], l, &seg->p[2]); + else + opposite_point (&seg1->p[0], &seg1->p[3], l, &seg->p[2]); + } + + /* Maintain smoothness and symmetry */ + if (seg->constraint != CE_PATH_CUSP) + { + if (seg->constraint == CE_PATH_SYMMETRIC) + l = point_distance (&seg->p[0], &seg->p[1]); + else + l = point_distance (&seg->p[0], &seg2->p[2]); + opposite_point (&seg->p[0], &seg->p[1], l, &seg2->p[2]); + } + + if (seg1 && seg1->constraint != CE_PATH_CUSP) + { + if (seg1->constraint == CE_PATH_SYMMETRIC) + l = point_distance (&seg->p[3], &seg->p[2]); + else + l = point_distance (&seg->p[3], &seg1->p[1]); + opposite_point (&seg->p[3], &seg->p[2], l, &seg1->p[1]); + } +} + +/* }}} */ +/* {{{ Hit testing */ + +CEPathCurve * +ce_path_find_closest_curve (CEPath *self, + graphene_point_t *point, + float threshold, + graphene_point_t *p, + float *pos) +{ + graphene_point_t pp; + float t; + CEPathCurve *best; + int i; + + best = NULL; + for (i = 0; i < self->curves->len; i++) + { + CEPathCurve *seg = g_ptr_array_index (self->curves, i); + GskPathBuilder *builder; + GskPath *path; + GskPathMeasure *measure; + GskPathPoint *cp; + float t1; + graphene_point_t pp1; + + builder = gsk_path_builder_new (); + path_builder_add_curve (self, seg, builder, TRUE); + path = gsk_path_builder_free_to_path (builder); + measure = gsk_path_measure_new (path); + + cp = gsk_path_measure_get_closest_point (measure, point, threshold); + if (cp) + { + gsk_path_point_get_position (cp, &pp1); + t1 = gsk_path_point_get_distance (cp); + threshold = point_distance (&pp1, point); + gsk_path_point_unref (cp); + + best = seg; + t = t1 / gsk_path_measure_get_length (measure); + pp = pp1; + } + + gsk_path_measure_unref (measure); + gsk_path_unref (path); + } + + if (best) + { + if (pos) + *pos = t; + if (p) + *p = pp; + } + + return best; +} + +/* }}} */ +/* {{{ Moving points */ + +static void +set_line_point (CEPath *self, + CEPathCurve *seg, + const graphene_point_t *pos) +{ + /* dragged point is on curve */ + CEPathCurve *seg1, *seg2, *seg11; + const graphene_point_t *d, *p; + graphene_point_t *c; + float l1, l2, dx, dy; + + seg1 = seg->prev; + seg2 = seg->next; + seg11 = seg1 ? seg1->prev : NULL; + + d = &seg->p[0]; + + /* before moving the point, record the distances to its neighbors, since + * we may want to preserve those + */ + + if (seg1) + l1 = point_distance (d, &seg1->p[2]); + else + l1 = 0; + l2 = point_distance (d, &seg->p[1]); + + dx = pos->x - d->x; + dy = pos->y - d->y; + + /* first move the point itself */ + seg->p[0] = *pos; + if (seg1) + seg1->p[3] = *pos; + + /* adjust control points as needed */ + + if (seg1 && seg1->op == GSK_PATH_LINE) + { + /* the other endpoint of the line */ + p = &seg1->p[0]; + c = &seg->p[1]; + + if (seg->op == GSK_PATH_CUBIC && seg->constraint != CE_PATH_CUSP) + { + opposite_point (d, p, l2, c); + } + else if (seg->op == GSK_PATH_CONIC && seg->constraint != CE_PATH_CUSP) + { + graphene_point_t u; + line_intersection (&seg1->p[0], &seg1->p[3], &seg->p[3], &seg->p[1], &u); + if (u.x != NAN) + seg->p[1] = u; + else + { + seg->p[1].x += dx; + seg->p[1].y += dy; + } + + update_conic (seg); + } + else + { + c->x += dx; + c->y += dy; + } + + /* always move the other control point along */ + c = &seg1->p[2]; + c->x += dx; + c->y += dy; + + /* handle the far end of the line */ + + if (seg11 && seg11->op == GSK_PATH_CUBIC && seg1->constraint != CE_PATH_CUSP) + { + double l; + const graphene_point_t *p2; + graphene_point_t *c2; + + p2 = &seg1->p[0]; + c2 = &seg11->p[2]; + /* adjust the control point before the line curve */ + l = point_distance (c2, p2); + opposite_point (p2, d, l, c2); + } + else if (seg11 && seg11->op == GSK_PATH_CONIC && seg1->constraint != CE_PATH_CUSP) + { + graphene_point_t u; + line_intersection (&seg11->p[0], &seg11->p[1], &seg1->p[3], &seg1->p[0], &u); + if (u.x != NAN) + seg11->p[1] = u; + + update_conic (seg11); + } + } + + if (seg->op == GSK_PATH_LINE) + { + /* the other endpoint of the line */ + p = &seg->p[3]; + if (seg1) + c = &seg1->p[2]; + + if (seg1 && seg1->op == GSK_PATH_CUBIC && seg->constraint != CE_PATH_CUSP) + { + /* adjust the control point before the line curve */ + opposite_point (d, p, l1, c); + } + else if (seg1 && seg1->op == GSK_PATH_CONIC && seg->constraint != CE_PATH_CUSP) + { + graphene_point_t u; + line_intersection (&seg1->p[0], &seg1->p[1], &seg->p[0], &seg->p[3], &u); + if (u.x != NAN) + seg1->p[1] = u; + else + { + seg1->p[1].x += dx; + seg1->p[1].y += dy; + } + + update_conic (seg11); + } + else if (seg1 && seg1->op == GSK_PATH_CUBIC) + { + c->x += dx; + c->y += dy; + } + + /* always move the other control point along */ + seg->p[1].x += dx; + seg->p[1].y += dy; + + /* handle the other end of the line */ + if (seg2 && seg2->op == GSK_PATH_CUBIC && seg2->constraint != CE_PATH_CUSP) + { + double l; + + /* adjust the control point after the line curve */ + c = &seg2->p[1]; + l = point_distance (c, p); + opposite_point (p, d, l, c); + } + else if (seg2 && seg2->op == GSK_PATH_CONIC && seg2->constraint != CE_PATH_CUSP) + { + graphene_point_t u; + line_intersection (&seg->p[0], &seg->p[3], &seg2->p[1], &seg2->p[3], &u); + if (u.x != NAN) + seg2->p[1] = u; + + update_conic (seg2); + } + } + + if (seg1 && seg1->op != GSK_PATH_LINE && seg->op != GSK_PATH_LINE) + { + if (seg1->op == GSK_PATH_CUBIC) + { + c = &seg1->p[2]; + c->x += dx; + c->y += dy; + } + else if (seg1->op == GSK_PATH_CONIC && seg->constraint != CE_PATH_CUSP) + { + graphene_point_t a, b; + + a.x = seg1->p[1].x + dx; + a.y = seg1->p[1].y + dy; + line_intersection (&seg->p[0], &a, &seg1->p[0], &seg1->p[1], &b); + seg1->p[1] = b; + } + + if (seg->op == GSK_PATH_CUBIC) + { + c = &seg->p[1]; + c->x += dx; + c->y += dy; + } + else if (seg->op == GSK_PATH_CONIC && seg->constraint != CE_PATH_CUSP) + { + graphene_point_t a, b; + + a.x = seg->p[1].x + dx; + a.y = seg->p[1].y + dy; + line_intersection (&seg->p[3], &seg->p[1], &a, &seg->p[0], &b); + seg->p[1] = b; + } + } + + if (seg1 && seg) + { + maintain_smoothness (self, seg1, seg); + maintain_automatic (self, seg11, seg1, seg, seg2); + } + update_conic (seg); + if (seg2) + update_conic (seg2); +} + +static void +set_conic_point (CEPath *self, + CEPathCurve *seg, + int point, + const graphene_point_t *pos) +{ + CEPathCurve *seg1, *seg2; + graphene_point_t *d, *c1; + float l; + + g_assert (seg->op == GSK_PATH_CONIC || seg->op == GSK_PATH_QUAD); + d = &seg->p[point]; + + seg1 = seg->next; + seg2 = seg->prev; + + if (point == 1) + { + if (seg->constraint != CE_PATH_CUSP && seg2 && seg2->op == GSK_PATH_LINE) + { + /* control point must be on the line of seg2 */ + + if (seg1 && seg1->constraint != CE_PATH_CUSP && seg1->op == GSK_PATH_LINE) + { + graphene_point_t c; + + line_intersection (&seg1->p[0], &seg1->p[3], &seg2->p[3], &seg2->p[0], &c); + if (c.x != NAN) + *d = c; /* unmoveable */ + else + { + closest_point (pos, &seg1->p[0], &seg1->p[3], &c); + *d = c; + } + } + else + { + graphene_point_t c; + + closest_point (pos, &seg2->p[0], &seg2->p[3], &c); + *d = c; + + if (seg1 && seg1->constraint != CE_PATH_CUSP) + { + l = point_distance (&seg1->p[0], &seg1->p[1]); + opposite_point (&seg1->p[0], d, l, &seg1->p[1]); + } + } + } + else if (seg1 && seg1->constraint != CE_PATH_CUSP && seg1->op == GSK_PATH_LINE) + { + graphene_point_t c; + + closest_point (pos, &seg1->p[0], &seg1->p[3], &c); + *d = c; + + if (seg2 && seg2->constraint != CE_PATH_CUSP) + { + if (seg2->op == GSK_PATH_CUBIC) + c1 = &seg2->p[2]; + else if (seg2->op == GSK_PATH_CONIC) + c1 = &seg2->p[1]; + else + g_assert_not_reached (); + l = point_distance (&seg2->p[3], c1); + opposite_point (&seg2->p[3], d, l, c1); + } + } + else + { + /* unconstrained */ + d->x = pos->x; + d->y = pos->y; + + if (seg1 && seg1->constraint != CE_PATH_CUSP) + { + l = point_distance (&seg1->p[0], &seg1->p[1]); + opposite_point (&seg1->p[0], d, l, &seg1->p[1]); + } + + if (seg2 && seg2->constraint != CE_PATH_CUSP) + { + if (seg2->op == GSK_PATH_CUBIC) + c1 = &seg2->p[2]; + else if (seg2->op == GSK_PATH_CONIC) + c1 = &seg2->p[1]; + else + g_assert_not_reached (); + l = point_distance (&seg2->p[3], c1); + opposite_point (&seg2->p[3], d, l, c1); + } + } + } + else if (point == 2) + { + /* dragging the shoulder point */ + graphene_point_t m; + float t; + + graphene_point_interpolate (&seg->p[0], &seg->p[3], 0.5, &m); + find_point_on_line (&m, &seg->p[1], pos, &t); + t = CLAMP (t, 0, 0.9); + seg->weight = - t / (t - 1); + } + + update_conic (seg); +} + +static void +set_control_point (CEPath *self, + CEPathCurve *seg, + int point, + const graphene_point_t *pos) +{ + /* dragged point is a control point */ + CEPathCurve *seg1; + const graphene_point_t *p, *p1; + graphene_point_t *c, *d; + CEPathConstraint constraint; + + g_assert (seg->op == GSK_PATH_CUBIC); + d = &seg->p[point]; + + if (point == 2) + { + seg1 = seg->next; + if (seg1) + { + p = &seg1->p[0]; + c = &seg1->p[1]; + constraint = seg1->constraint; + p1 = &seg1->p[3]; + } + else + constraint = CE_PATH_CUSP; + } + else if (point == 1) + { + seg1 = seg->prev; + if (seg1) + { + if (seg1->op == GSK_PATH_CONIC) + c = &seg1->p[1]; + else + c = &seg1->p[2]; + p = &seg->p[0]; + constraint = seg->constraint; + p1 = &seg1->p[0]; + } + else + constraint = CE_PATH_CUSP; + } + else + g_assert_not_reached (); + + if (constraint != CE_PATH_CUSP) + { + if (seg1->op == GSK_PATH_CUBIC) + { + double l; + + /* first move the point itself */ + d->x = pos->x; + d->y = pos->y; + + /* then adjust the other control point */ + if (constraint == CE_PATH_SYMMETRIC) + l = point_distance (d, p); + else + l = point_distance (c, p); + + opposite_point (p, d, l, c); + } + else if (seg1->op == GSK_PATH_CONIC) + { + graphene_point_t u; + + d->x = pos->x; + d->y = pos->y; + line_intersection (p1, c, p, d, &u); + *c = u; + + update_conic (seg1); + } + else if (seg1->op == GSK_PATH_LINE) + { + closest_point (pos, p, p1, d); + } + else + { + d->x = pos->x; + d->y = pos->y; + } + } + else + { + d->x = pos->x; + d->y = pos->y; + } +} + +void +ce_path_set_point (CEPath *self, + CEPathCurve *seg, + int point, + const graphene_point_t *pos) +{ + g_return_if_fail (CE_IS_PATH (self)); + g_return_if_fail (seg != NULL); + g_return_if_fail (0 <= point && point < 4); + g_return_if_fail (pos != NULL); + + if (point == 0) + set_line_point (self, seg, pos); + else if (seg->op == GSK_PATH_CONIC || seg->op == GSK_PATH_QUAD) + set_conic_point (self, seg, point, pos); + else + set_control_point (self, seg, point, pos); +} + +/* }}} */ +/* {{{ Simple getters and setters */ + +void +ce_path_get_point (CEPath *self, + CEPathCurve *seg, + int c, + graphene_point_t *point) +{ + g_return_if_fail (CE_IS_PATH (self)); + g_return_if_fail (seg != NULL); + g_return_if_fail (0 <= c && c < 4); + + *point = seg->p[c]; +} + +GskPathOperation +ce_path_get_operation (CEPath *self, + CEPathCurve *seg) +{ + g_return_val_if_fail (CE_IS_PATH (self), GSK_PATH_LINE); + g_return_val_if_fail (seg != NULL, GSK_PATH_LINE); + + return seg->op; +} + +CEPathConstraint +ce_path_get_constraint (CEPath *self, + CEPathCurve *seg) +{ + g_return_val_if_fail (CE_IS_PATH (self), CE_PATH_CUSP); + g_return_val_if_fail (seg != NULL, CE_PATH_CUSP); + + return seg->constraint; +} + +void +ce_path_set_constraint (CEPath *self, + CEPathCurve *seg, + CEPathConstraint constraint) +{ + CEPathCurve *seg1; + + g_return_if_fail (CE_IS_PATH (self)); + g_return_if_fail (seg != NULL); + + if (seg->constraint == constraint) + return; + + seg->constraint = constraint; + + seg1 = seg->prev; + + if (seg && seg1) + { + maintain_smoothness (self, seg1, seg); + maintain_symmetry (self, seg1, seg); + maintain_automatic (self, seg1->prev, seg1, seg, seg->next); + } +} + +void +ce_path_set_operation (CEPath *self, + CEPathCurve *seg, + GskPathOperation op) +{ + CEPathCurve *seg1, *seg2; + + g_return_if_fail (CE_IS_PATH (self)); + g_return_if_fail (seg != NULL); + + if (seg->op == op) + return; + + seg->op = op; + + if (seg->op == GSK_PATH_CONIC && seg->weight == 0) + seg->weight = 1; + + update_conic (seg); + + seg1 = seg->prev; + seg2 = seg->next; + + if (seg1 && seg) + maintain_smoothness (self, seg1, seg); + if (seg && seg2) + maintain_smoothness (self, seg, seg2); + if (seg1 && seg) + maintain_symmetry (self, seg1, seg); + if (seg && seg2) + maintain_symmetry (self, seg, seg2); +} + +void +ce_path_set_weight (CEPath *self, + CEPathCurve *seg, + float weight) +{ + g_return_if_fail (CE_IS_PATH (self)); + g_return_if_fail (seg != NULL); + g_return_if_fail (0 < weight); + + seg->weight = 1; + update_conic (seg); +} + +float +ce_path_get_weight (CEPath *self, + CEPathCurve *seg) +{ + g_return_val_if_fail (CE_IS_PATH (self), 1.f); + g_return_val_if_fail (seg != NULL, 1.f); + + return seg->weight; +} + +/* }}} */ + +/* vim:set foldmethod=marker expandtab: */ diff --git a/demos/gtk-demo/cepath.h b/demos/gtk-demo/cepath.h new file mode 100644 index 0000000000..96ec1ce983 --- /dev/null +++ b/demos/gtk-demo/cepath.h @@ -0,0 +1,90 @@ +#pragma once + +#include + +G_BEGIN_DECLS + +#define CE_TYPE_PATH (ce_path_get_type ()) +G_DECLARE_FINAL_TYPE (CEPath, ce_path, CE, PATH, GObject) + +typedef struct _CEPathCurve CEPathCurve; + +typedef enum +{ + CE_PATH_CUSP, + CE_PATH_SMOOTH, + CE_PATH_SYMMETRIC, + CE_PATH_AUTOMATIC +} CEPathConstraint; + +CEPath * ce_path_new (void); + +int ce_path_get_n_curves (CEPath *self); + +CEPathCurve * ce_path_get_curve (CEPath *self, + int idx); + +CEPathCurve * ce_path_previous_curve (CEPath *self, + CEPathCurve *seg); + +CEPathCurve * ce_path_next_curve (CEPath *self, + CEPathCurve *seg); + +void ce_path_set_gsk_path (CEPath *self, + GskPath *path); + +GskPath * ce_path_get_gsk_path (CEPath *self); + +void ce_path_split_curve (CEPath *self, + CEPathCurve *seg, + float pos); + +void ce_path_remove_curve (CEPath *self, + CEPathCurve *seg); + +void ce_path_drag_curve (CEPath *self, + CEPathCurve *seg, + const graphene_point_t *pos); + +CEPathCurve * ce_path_find_closest_curve (CEPath *self, + graphene_point_t *point, + float threshold, + graphene_point_t *p, + float *pos); + +void ce_path_get_point (CEPath *self, + CEPathCurve *seg, + int c, + graphene_point_t *point); + +void ce_path_set_point (CEPath *self, + CEPathCurve *seg, + int point, + const graphene_point_t *pos); + +GskPathOperation + ce_path_get_operation (CEPath *self, + CEPathCurve *seg); + +void ce_path_set_operation (CEPath *self, + CEPathCurve *seg, + GskPathOperation op); + +float ce_path_get_weight (CEPath *self, + CEPathCurve *seg); + +void ce_path_set_weight (CEPath *self, + CEPathCurve *seg, + float weight); + +CEPathConstraint + ce_path_get_constraint (CEPath *self, + CEPathCurve *seg); + +void ce_path_set_constraint (CEPath *self, + CEPathCurve *seg, + CEPathConstraint constraint); + + + +G_END_DECLS diff --git a/demos/gtk-demo/curve-editor.c b/demos/gtk-demo/curve-editor.c index a17ba23abc..e214527db5 100644 --- a/demos/gtk-demo/curve-editor.c +++ b/demos/gtk-demo/curve-editor.c @@ -1,12 +1,46 @@ #include "curve-editor.h" +#include "cepath.h" #include +/* {{{ Widget stuff */ + +struct _CurveEditor +{ + GtkWidget parent_instance; + CEPath *path; + CEPathCurve *context; + float context_pos; + gboolean edit; + CEPathCurve *edited_curve; + CEPathCurve *edited_point; + CEPathCurve *dragged_curve; + CEPathCurve *molded_curve; + CEPathCurve *hovered_curve; + int dragged_point; + int hovered_point; + + GtkWidget *menu; + GActionMap *actions; + GskStroke *stroke; + GdkRGBA color; + + gboolean show_outline; +}; + +struct _CurveEditorClass +{ + GtkWidgetClass parent_class; +}; + +G_DEFINE_TYPE (CurveEditor, curve_editor, GTK_TYPE_WIDGET) + +/* }}} */ +/* {{{ Utilities */ + #define DRAW_RADIUS 5 #define CLICK_RADIUS 8 -/* {{{ Types and structures */ - static const char * op_to_string (GskPathOperation op) { @@ -48,622 +82,77 @@ op_from_string (const char *s) g_assert_not_reached (); } -typedef enum -{ - CUSP, - SMOOTH, - SYMMETRIC, - AUTO -} PointType; - static const char * -point_type_to_string (PointType type) +constraint_to_string (CEPathConstraint constraint) { - switch (type) + switch (constraint) { - case CUSP: + case CE_PATH_CUSP: return "cusp"; - case SMOOTH: + case CE_PATH_SMOOTH: return "smooth"; - case SYMMETRIC: + case CE_PATH_SYMMETRIC: return "symmetric"; - case AUTO: - return "auto"; + case CE_PATH_AUTOMATIC: + return "automatic"; default: g_assert_not_reached (); } } -static PointType -point_type_from_string (const char *s) +static CEPathConstraint +constraint_from_string (const char *s) { if (strcmp (s, "cusp") == 0) - return CUSP; + return CE_PATH_CUSP; else if (strcmp (s, "smooth") == 0) - return SMOOTH; + return CE_PATH_SMOOTH; else if (strcmp (s, "symmetric") == 0) - return SYMMETRIC; - else if (strcmp (s, "auto") == 0) - return AUTO; + return CE_PATH_SYMMETRIC; + else if (strcmp (s, "automatic") == 0) + return CE_PATH_AUTOMATIC; else g_assert_not_reached (); } -typedef struct -{ - GskPathOperation op; - graphene_point_t p[4]; - float weight; - PointType type; - int dragged; - int hovered; -} Segment; - -struct _CurveEditor -{ - GtkWidget parent_instance; - GArray *segments; - int context; - float context_pos; - gboolean edit; - int edited_point; - int edited_segment; - int molded; - int dragged; - - GtkWidget *menu; - GActionMap *actions; - GskStroke *stroke; - GdkRGBA color; - - gboolean show_outline; -}; - -struct _CurveEditorClass -{ - GtkWidgetClass parent_class; -}; - -G_DEFINE_TYPE (CurveEditor, curve_editor, GTK_TYPE_WIDGET) - -/* }}} */ -/* {{{ Misc. geometry */ - -/* Set q to the projection of p onto the line through a and b */ -static void -closest_point (const graphene_point_t *p, - const graphene_point_t *a, - const graphene_point_t *b, - graphene_point_t *q) -{ - graphene_vec2_t n; - graphene_vec2_t ap; - float t; - - graphene_vec2_init (&n, b->x - a->x, b->y - a->y); - graphene_vec2_init (&ap, p->x - a->x, p->y - a->y); - - t = graphene_vec2_dot (&ap, &n) / graphene_vec2_dot (&n, &n); - - q->x = a->x + t * (b->x - a->x); - q->y = a->y + t * (b->y - a->y); -} - -static void -find_point_on_line (const graphene_point_t *p1, - const graphene_point_t *p2, - const graphene_point_t *q, - float *t) -{ - float tx = p2->x - p1->x; - float ty = p2->y - p1->y; - float sx = q->x - p1->x; - float sy = q->y - p1->y; - - *t = (tx*sx + ty*sy) / (tx*tx + ty*ty); -} - -/* Determine if p is on the line through a and b */ -static gboolean -collinear (const graphene_point_t *p, - 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); -} - -/* Set q to the point on the line through p and a that is - * at a distance of d from p, on the same side - */ -static void -scale_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); -} - -/* Set p to the intersection of the lines through a, b - * and c, d - */ -static void -line_intersection (const graphene_point_t *a, - const graphene_point_t *b, - const graphene_point_t *c, - const graphene_point_t *d, - graphene_point_t *p) -{ - double a1 = b->y - a->y; - double b1 = a->x - b->x; - double c1 = a1*a->x + b1*a->y; - - double a2 = d->y - c->y; - double b2 = c->x - d->x; - double c2 = a2*c->x+ b2*c->y; - - double det = a1*b2 - a2*b1; - - if (det == 0) - { - p->x = NAN; - p->y = NAN; - } - else - { - p->x = (b2*c1 - b1*c2) / det; - p->y = (a1*c2 - a2*c1) / det; - } -} - -/* Given 3 points, determine the center of a circle that - * passes through all of them. - */ -static void -circle_through_points (const graphene_point_t *a, - const graphene_point_t *b, - const graphene_point_t *c, - graphene_point_t *center) -{ - graphene_point_t ab; - graphene_point_t ac; - graphene_point_t ab2; - graphene_point_t ac2; - - ab.x = (a->x + b->x) / 2; - ab.y = (a->y + b->y) / 2; - ac.x = (a->x + c->x) / 2; - ac.y = (a->y + c->y) / 2; - - ab2.x = ab.x + a->y - b->y; - ab2.y = ab.y + b->x - a->x; - ac2.x = ac.x + a->y - c->y; - ac2.y = ac.y + c->x - a->x; - - line_intersection (&ab, &ab2, &ac, &ac2, center); -} - -/* Return the cosine of the angle between b1 - a and b2 - a */ -static double -three_point_angle (const graphene_point_t *a, - const graphene_point_t *b1, - const graphene_point_t *b2) -{ - graphene_vec2_t u; - graphene_vec2_t v; - - graphene_vec2_init (&u, b1->x - a->x, b1->y - a->y); - graphene_vec2_init (&v, b2->x - a->x, b2->y - a->y); - graphene_vec2_normalize (&u, &u); - graphene_vec2_normalize (&v, &v); - - return graphene_vec2_dot (&u, &v); -} - -/* }}} */ -/* {{{ Misc. Bezier math */ - -/* Given Bezier control points and a t value between 0 and 1, - * return new Bezier control points for two segments in left - * and right that are obtained by splitting the curve at the - * point for t. - * - * Note that the points in the right array are in returned in - * reverse order. - */ -static void -split_bezier (graphene_point_t *points, - int length, - float t, - graphene_point_t *left, - int *left_pos, - graphene_point_t *right, - int *right_pos) -{ - if (length == 1) - { - left[*left_pos] = points[0]; - (*left_pos)++; - right[*right_pos] = points[0]; - (*right_pos)++; - } - else - { - graphene_point_t *newpoints; - int i; - - newpoints = g_alloca (sizeof (graphene_point_t) * (length - 1)); - for (i = 0; i < length - 1; i++) - { - if (i == 0) - { - left[*left_pos] = points[i]; - (*left_pos)++; - } - if (i + 1 == length - 1) - { - right[*right_pos] = points[i + 1]; - (*right_pos)++; - } - graphene_point_interpolate (&points[i], &points[i + 1], t, &newpoints[i]); - } - split_bezier (newpoints, length - 1, t, left, left_pos, right, right_pos); - } -} - -static double -projection_ratio (double t) -{ - double top, bottom; - - if (t == 0 || t == 1) - return t; - - top = pow (1 - t, 3), - bottom = pow (t, 3) + top; - - return top / bottom; -} - -static double -abc_ratio (double t) -{ - double top, bottom; - - if (t == 0 || t == 1) - return t; - - bottom = pow (t, 3) + pow (1 - t, 3); - top = bottom - 1; - - return fabs (top / bottom); -} - -static void -find_control_points (double t, - const graphene_point_t *A, - const graphene_point_t *B, - const graphene_point_t *C, - const graphene_point_t *S, - const graphene_point_t *E, - graphene_point_t *C1, - graphene_point_t *C2) -{ - double angle; - double dist; - double bc; - double de1; - double de2; - graphene_point_t c; - graphene_point_t t0, t1; - double tlength; - double dx, dy; - graphene_point_t e1, e2; - graphene_point_t v1, v2; - - dist = graphene_point_distance (S, E, NULL, NULL); - angle = atan2 (E->y - S->y, E->x - S->x) - atan2 (B->y - S->y, B->x - S->x); - bc = (angle < 0 || angle > M_PI ? -1 : 1) * dist / 3; - de1 = t * bc; - de2 = (1 - t) * bc; - - circle_through_points (S, B, E, &c); - - t0.x = B->x - (B->y - c.y); - t0.y = B->y + (B->x - c.x); - t1.x = B->x + (B->y - c.y); - t1.y = B->y - (B->x - c.x); - - tlength = graphene_point_distance (&t0, &t1, NULL, NULL); - dx = (t1.x - t0.x) / tlength; - dy = (t1.y - t0.y) / tlength; - - e1.x = B->x + de1 * dx; - e1.y = B->y + de1 * dy; - e2.x = B->x - de2 * dx; - e2.y = B->y - de2 * dy; - - v1.x = A->x + (e1.x - A->x) / (1 - t); - v1.y = A->y + (e1.y - A->y) / (1 - t); - - v2.x = A->x + (e2.x - A->x) / t; - v2.y = A->y + (e2.y - A->y) / t; - - C1->x = S->x + (v1.x - S->x) / t; - C1->y = S->y + (v1.y - S->y) / t; - - C2->x = E->x + (v2.x - E->x) / (1 - t); - C2->y = E->y + (v2.y - E->y) / (1 - t); -} - -/* Given points S, B, E, determine control - * points C1, C2 such that B lies on the - * Bezier segment given bY S, C1, C2, E. - */ -static void -bezier_through (const graphene_point_t *S, - const graphene_point_t *B, - const graphene_point_t *E, - graphene_point_t *C1, - graphene_point_t *C2) -{ - double d1, d2, t; - double u, um, s; - graphene_point_t A, C; - - d1 = graphene_point_distance (S, B, NULL, NULL); - d2 = graphene_point_distance (E, B, NULL, NULL); - t = d1 / (d1 + d2); - - u = projection_ratio (t); - um = 1 - u; - - C.x = u * S->x + um * E->x; - C.y = u * S->y + um * E->y; - - s = abc_ratio (t); - - A.x = B->x + (B->x - C.x) / s; - A.y = B->y + (B->y - C.y) / s; - - find_control_points (t, &A, B, &C, S, E, C1, C2); -} - -/* conics */ - -static void -get_conic_shoulder_point (const graphene_point_t p[3], - float w, - graphene_point_t *q) -{ - graphene_point_t m; - - graphene_point_interpolate (&p[0], &p[2], 0.5, &m); - graphene_point_interpolate (&m, &p[1], w / (1 + w), q); -} - -static void -split_bezier3d_recurse (const graphene_point3d_t *p, - int l, - float t, - graphene_point3d_t *left, - graphene_point3d_t *right, - int *lpos, - int *rpos) -{ - if (l == 1) - { - left[*lpos] = p[0]; - right[*rpos] = p[0]; - } - else - { - graphene_point3d_t *np; - int i; - - np = g_alloca (sizeof (graphene_point3d_t) * (l - 1)); - for (i = 0; i < l - 1; i++) - { - if (i == 0) - { - left[*lpos] = p[i]; - (*lpos)++; - } - if (i + 1 == l - 1) - { - right[*rpos] = p[i + 1]; - (*rpos)--; - } - graphene_point3d_interpolate (&p[i], &p[i + 1], t, &np[i]); - } - split_bezier3d_recurse (np, l - 1, t, left, right, lpos, rpos); - } -} - -static void -split_bezier3d (const graphene_point3d_t *p, - int l, - float t, - graphene_point3d_t *left, - graphene_point3d_t *right) -{ - int lpos = 0; - int rpos = l - 1; - split_bezier3d_recurse (p, l, t, left, right, &lpos, &rpos); -} - -static void -split_conic (const graphene_point_t points[3], float weight, - float t, - graphene_point_t lp[3], float *lw, - graphene_point_t rp[3], float *rw) -{ - /* Given control points and weight for a rational quadratic - * Bezier and t, create two sets of the same that give the - * same curve as the original and split the curve at t. - */ - graphene_point3d_t p[3]; - graphene_point3d_t l[3], r[3]; - int i; - - /* do de Casteljau in homogeneous coordinates... */ - for (i = 0; i < 3; i++) - { - p[i].x = points[i].x; - p[i].y = points[i].y; - p[i].z = 1; - } - - p[1].x *= weight; - p[1].y *= weight; - p[1].z *= weight; - - split_bezier3d (p, 3, t, l, r); - - /* then project the control points down */ - for (i = 0; i < 3; i++) - { - lp[i].x = l[i].x / l[i].z; - lp[i].y = l[i].y / l[i].z; - rp[i].x = r[i].x / r[i].z; - rp[i].y = r[i].y / r[i].z; - } - - /* normalize the outer weights to be 1 by using - * the fact that weights w_i and c*w_i are equivalent - * for any nonzero constant c - */ - for (i = 0; i < 3; i++) - { - l[i].z /= l[0].z; - r[i].z /= r[2].z; - } - - /* normalize the inner weight to be 1 by using - * the fact that w_0*w_2/w_1^2 is a constant for - * all equivalent weights. - */ - *lw = l[1].z / sqrt (l[2].z); - *rw = r[1].z / sqrt (r[0].z); -} - -/* }}} */ -/* {{{ Utilities */ - -static Segment * -get_segment (CurveEditor *self, - int idx) -{ - idx = idx % (int)self->segments->len; - if (idx < 0) - idx += (int)self->segments->len; - return &g_array_index (self->segments, Segment, idx); -} - -static void -set_segment_start (CurveEditor *self, - int idx, - graphene_point_t *p) -{ - Segment *seg = get_segment (self, idx); - Segment *seg1 = get_segment (self, idx - 1); - - seg->p[0] = *p; - seg1->p[3] = *p; -} - -static const graphene_point_t * -get_line_point (CurveEditor *self, - int idx) -{ - Segment *seg = get_segment (self, idx); - return &seg->p[0]; -} - -static graphene_point_t * -get_left_control_point (CurveEditor *self, - int idx) -{ - Segment *seg = get_segment (self, idx - 1); - return &seg->p[2]; -} - -static graphene_point_t * -get_right_control_point (CurveEditor *self, - int idx) -{ - Segment *seg = get_segment (self, idx); - return &seg->p[1]; -} - static gboolean point_is_visible (CurveEditor *self, - int point, - int point1) + CEPathCurve *seg, + int point) { - Segment *seg; + CEPathCurve *next; + GskPathOperation op; if (!self->edit) return FALSE; - seg = get_segment (self, point); - switch (point1) + next = ce_path_next_curve (self->path, seg); + op = ce_path_get_operation (self->path, seg); + switch (point) { case 0: /* point on curve */ return TRUE; case 1: - if (self->edited_segment == point && - seg->op != GSK_PATH_LINE) + if (self->edited_curve == seg && + op != GSK_PATH_LINE) return TRUE; - if ((seg->op == GSK_PATH_CONIC || seg->op == GSK_PATH_QUAD)&& - (self->edited_point == point + 1 || - (self->edited_point == 0 && point + 1 == self->segments->len))) + if ((op == GSK_PATH_CONIC || op == GSK_PATH_QUAD) && + self->edited_point == next) return TRUE; - if (self->edited_point == point && - (seg->op == GSK_PATH_CUBIC || - seg->op == GSK_PATH_CONIC || - seg->op == GSK_PATH_QUAD)) + if (self->edited_curve == seg && + (op == GSK_PATH_CUBIC || + op == GSK_PATH_CONIC || + op == GSK_PATH_QUAD)) return TRUE; break; case 2: - if (self->edited_segment == point && - (seg->op != GSK_PATH_LINE && seg->op != GSK_PATH_QUAD)) + if (self->edited_curve == seg && + (op != GSK_PATH_LINE && op != GSK_PATH_QUAD)) return TRUE; - if (seg->op == GSK_PATH_CUBIC && - (self->edited_point == point + 1 || - (self->edited_point == 0 && point + 1 == self->segments->len))) + if (op == GSK_PATH_CUBIC && + self->edited_point == next) return TRUE; break; @@ -674,1080 +163,19 @@ point_is_visible (CurveEditor *self, return FALSE; } -static void -maintain_smoothness (CurveEditor *self, - int point) -{ - Segment *seg, *seg1; - const graphene_point_t *p, *p2; - graphene_point_t *c, *c2; - float d; - - seg = get_segment (self, point); - seg1 = get_segment (self, point - 1); - - if (seg->type == CUSP) - return; - - if (seg->op == GSK_PATH_LINE && seg1->op == GSK_PATH_LINE) - return; - - p = &seg->p[0]; - c = &seg1->p[2]; - c2 = &seg->p[1]; - - if (seg->op == GSK_PATH_CUBIC && seg1->op == GSK_PATH_CUBIC) - { - d = graphene_point_distance (c, p, NULL, NULL); - opposite_point (p, c2, d, c); - } - else if (seg->op == GSK_PATH_CUBIC) - { - if (seg1->op == GSK_PATH_LINE) - p2 = &seg1->p[0]; - else if (seg1->op == GSK_PATH_CONIC || seg1->op == GSK_PATH_QUAD) - p2 = &seg1->p[1]; - else - g_assert_not_reached (); - d = graphene_point_distance (c2, p, NULL, NULL); - opposite_point (p, p2, d, c2); - } - else if (seg1->op == GSK_PATH_CUBIC) - { - if (seg->op == GSK_PATH_LINE) - p2 = &seg->p[3]; - else if (seg->op == GSK_PATH_CONIC || seg->op == GSK_PATH_QUAD) - p2 = &seg->p[1]; - else - g_assert_not_reached (); - d = graphene_point_distance (c, p, NULL, NULL); - opposite_point (p, p2, d, c); - } - else if ((seg->op == GSK_PATH_CONIC && seg1->op == GSK_PATH_CONIC) || - (seg->op == GSK_PATH_QUAD && seg1->op == GSK_PATH_QUAD)) - { - graphene_point_t h, a, b; - - h.x = seg->p[0].x + seg->p[1].x - seg1->p[1].x; - h.y = seg->p[0].y + seg->p[1].y - seg1->p[1].y; - line_intersection (&seg->p[0], &h, &seg1->p[0], &seg1->p[1], &a); - line_intersection (&seg->p[0], &h, &seg->p[1], &seg->p[3], &b); - - seg1->p[1] = a; - seg->p[1] = b; - } -} - -static void -maintain_symmetry (CurveEditor *self, - int point) -{ - Segment *seg, *seg1; - const graphene_point_t *p; - graphene_point_t *c, *c2; - double l1, l2, l; - - seg = get_segment (self, point); - seg1 = get_segment (self, point - 1); - - if (seg->type != SYMMETRIC) - return; - - if (seg->op != GSK_PATH_CUBIC || seg1->op != GSK_PATH_CUBIC) - return; - - p = &seg->p[0]; - c = &seg1->p[2]; - c2 = &seg->p[1]; - - l1 = graphene_point_distance (p, c, NULL, NULL); - l2 = graphene_point_distance (p, c2, NULL, NULL); - - if (l1 != l2) - { - l = (l1 + l2) / 2; - - scale_point (p, c, l, c); - scale_point (p, c2, l, c2); - } -} - -/* Make the line through the control points perpendicular - * to the line bisecting the angle between neighboring - * points, and make the lengths 1/3 of the distance to - * the corresponding neighboring points. - */ -static void -update_automatic (CurveEditor *self, - int point) -{ - Segment *seg; - const graphene_point_t *p, *p1, *p2; - double l1, l2; - graphene_point_t a; - graphene_point_t *c1, *c2; - - seg = get_segment (self, point); - - if (seg->type != AUTO) - return; - - if (seg->op != GSK_PATH_CUBIC || get_segment (self, point - 1)->op != GSK_PATH_CUBIC) - return; - - p = get_line_point (self, point); - c1 = get_left_control_point (self, point); - c2 = get_right_control_point (self, point); - - p1 = get_line_point (self, point - 1); - p2 = get_line_point (self, point + 1); - - l1 = graphene_point_distance (p, p1, NULL, NULL); - l2 = graphene_point_distance (p, p2, NULL, NULL); - - a.x = p2->x + (p->x - p1->x); - a.y = p2->y + (p->y - p1->y); - - scale_point (p, &a, l2/3, c2); - opposite_point (p, &a, l1/3, c1); -} - -static void -maintain_automatic (CurveEditor *self, - int point) -{ - if (get_segment (self, point)->op != GSK_PATH_CUBIC || - get_segment (self, point - 1)->op != GSK_PATH_CUBIC) - return; - - update_automatic (self, point); - update_automatic (self, point - 1); - update_automatic (self, point + 1); -} - -static void -maintain_conic (CurveEditor *self, - int idx) -{ - Segment *seg = get_segment (self, idx); - graphene_point_t p[3]; - - if (seg->op != GSK_PATH_CONIC) - return; - - p[0] = seg->p[0]; - p[1] = seg->p[1]; - p[2] = seg->p[3]; - - get_conic_shoulder_point (p, seg->weight, &seg->p[2]); -} - -/* Check if the points arount point currently satisfy - * smoothness conditions. Set PointData.type accordingly. - */ -static void -check_smoothness (CurveEditor *self, - int point) -{ - GskPathOperation op, op1; - const graphene_point_t *p, *p1, *p2; - Segment *seg, *seg1; - - seg = get_segment (self, point); - seg1 = get_segment (self, point - 1); - p = get_line_point (self, point); - - op = seg->op; - op1 = seg1->op; - - if (op == GSK_PATH_CUBIC) - p2 = get_right_control_point (self, point); - else if (op == GSK_PATH_LINE) - p2 = get_line_point (self, point + 1); - else - p2 = NULL; - - if (op1 == GSK_PATH_CUBIC) - p1 = get_left_control_point (self, point); - else if (op1 == GSK_PATH_LINE) - p1 = get_line_point (self, point - 1); - else - p1 = NULL; - - if (!p1 || !p2 || !collinear (p, p1, p2)) - seg->type = CUSP; - else - seg->type = SMOOTH; -} - -static void -insert_point (CurveEditor *self, - int point, - double pos) -{ - Segment *seg, *seg1, *seg2; - Segment ns = { 0, };; - - seg = get_segment (self, point); - if (seg->op == GSK_PATH_MOVE) - return; - - g_array_insert_val (self->segments, point + 1, ns); - - seg = get_segment (self, point); - seg1 = get_segment (self, point + 1); - seg2 = get_segment (self, point + 2); - - seg1->type = SMOOTH; - seg1->hovered = -1; - seg1->dragged = -1; - - switch (seg->op) - { - case GSK_PATH_LINE: - seg1->op = GSK_PATH_LINE; - - graphene_point_interpolate (&seg->p[0], &seg->p[3], pos, &seg1->p[0]); - seg->p[3] = seg->p[0]; - seg1->p[3] = seg2->p[0]; - break; - - case GSK_PATH_QUAD: - { - graphene_point_t points[3]; - graphene_point_t left[3]; - graphene_point_t right[3]; - int left_pos = 0; - int right_pos = 0; - - seg1->op = GSK_PATH_QUAD; - - points[0] = seg->p[0]; - points[1] = seg->p[1]; - points[2] = seg->p[3]; - split_bezier (points, 3, pos, left, &left_pos, right, &right_pos); - - seg->p[0] = left[0]; - seg->p[1] = left[1]; - seg->p[3] = left[2]; - seg1->p[0] = right[2]; - seg1->p[1] = right[1]; - seg1->p[3] = right[0]; - } - break; - - case GSK_PATH_CUBIC: - { - graphene_point_t left[4]; - graphene_point_t right[4]; - int left_pos = 0; - int right_pos = 0; - - seg1->op = GSK_PATH_CUBIC; - - split_bezier (seg->p, 4, pos, left, &left_pos, right, &right_pos); - - seg->p[0] = left[0]; - seg->p[1] = left[1]; - seg->p[2] = left[2]; - seg->p[3] = left[3]; - seg1->p[0] = right[3]; - seg1->p[1] = right[2]; - seg1->p[2] = right[1]; - seg1->p[3] = right[0]; - } - break; - - case GSK_PATH_CONIC: - { - graphene_point_t points[3]; - graphene_point_t left[3]; - graphene_point_t right[3]; - float lw, rw; - - seg1->op = GSK_PATH_CONIC; - - points[0] = seg->p[0]; - points[1] = seg->p[1]; - points[2] = seg->p[3]; - split_conic (points, seg->weight, pos, left, &lw, right, &rw); - - seg->p[0] = left[0]; - seg->p[1] = left[1]; - seg->p[3] = left[2]; - seg1->p[0] = right[0]; - seg1->p[1] = right[1]; - seg1->p[3] = right[2]; - - seg->weight = lw; - seg1->weight = rw; - - get_conic_shoulder_point (seg->p, seg->weight, &seg->p[2]); - get_conic_shoulder_point (seg1->p, seg1->weight, &seg1->p[2]); - } - break; - - case GSK_PATH_MOVE: - case GSK_PATH_CLOSE: - default: - g_assert_not_reached (); - break; - } - - maintain_smoothness (self, point + 1); - maintain_automatic (self, point + 1); - - gtk_widget_queue_draw (GTK_WIDGET (self)); -} - -static void -remove_point (CurveEditor *self, - int point) -{ - Segment *seg; - graphene_point_t c, p; - - seg = get_segment (self, point); - c = seg->p[2]; - p = seg->p[3]; - - g_array_remove_index (self->segments, point); - - seg = get_segment (self, point - 1); - seg->p[2] = c; - seg->p[3] = p; - - maintain_smoothness (self, point); - maintain_automatic (self, point); -} - -/* }}} */ -/* {{{ GskPath helpers */ - -static void -curve_editor_add_segment (CurveEditor *self, - GskPathBuilder *builder, - int point) -{ - Segment *seg; - - seg = get_segment (self, point); - - gsk_path_builder_move_to (builder, seg->p[0].x, seg->p[0].y); - - switch (seg->op) - { - case GSK_PATH_LINE: - gsk_path_builder_line_to (builder, seg->p[3].x, seg->p[3].y); - break; - - case GSK_PATH_QUAD: - gsk_path_builder_quad_to (builder, - seg->p[1].x, seg->p[1].y, - seg->p[3].x, seg->p[3].y); - break; - - case GSK_PATH_CUBIC: - gsk_path_builder_cubic_to (builder, - seg->p[1].x, seg->p[1].y, - seg->p[2].x, seg->p[2].y, - seg->p[3].x, seg->p[3].y); - break; - - case GSK_PATH_CONIC: - gsk_path_builder_conic_to (builder, - seg->p[1].x, seg->p[1].y, - seg->p[3].x, seg->p[3].y, - seg->weight); - break; - - case GSK_PATH_MOVE: - case GSK_PATH_CLOSE: - default: - break; - } -} - -static void -curve_editor_add_path (CurveEditor *self, - GskPathBuilder *builder) -{ - int i; - - for (i = 0; i < self->segments->len; i++) - { - Segment *seg = get_segment (self, i); - - if (i == 0) - gsk_path_builder_move_to (builder, seg->p[0].x, seg->p[0].y); - - switch (seg->op) - { - case GSK_PATH_MOVE: - gsk_path_builder_move_to (builder, seg->p[3].x, seg->p[3].y); - break; - - case GSK_PATH_LINE: - gsk_path_builder_line_to (builder, seg->p[3].x, seg->p[3].y); - break; - - case GSK_PATH_QUAD: - gsk_path_builder_quad_to (builder, - seg->p[1].x, seg->p[1].y, - seg->p[3].x, seg->p[3].y); - break; - - case GSK_PATH_CUBIC: - gsk_path_builder_cubic_to (builder, - seg->p[1].x, seg->p[1].y, - seg->p[2].x, seg->p[2].y, - seg->p[3].x, seg->p[3].y); - break; - - case GSK_PATH_CONIC: - gsk_path_builder_conic_to (builder, - seg->p[1].x, seg->p[1].y, - seg->p[3].x, seg->p[3].y, - seg->weight); - break; - - case GSK_PATH_CLOSE: - default: - g_assert_not_reached (); - } - } - - gsk_path_builder_close (builder); -} - -static gboolean -find_closest_segment (CurveEditor *self, - graphene_point_t *point, - float threshold, - graphene_point_t *p, - int *segment, - float *pos) -{ - graphene_point_t pp; - float t; - int seg; - gboolean found = FALSE; - int i; - - for (i = 0; i < self->segments->len; i++) - { - GskPathBuilder *builder; - GskPath *path; - GskPathMeasure *measure; - GskPathPoint *cp; - float t1; - graphene_point_t pp1; - - builder = gsk_path_builder_new (); - curve_editor_add_segment (self, builder, i); - path = gsk_path_builder_free_to_path (builder); - measure = gsk_path_measure_new (path); - - cp = gsk_path_measure_get_closest_point (measure, point, threshold); - if (cp) - { - gsk_path_point_get_position (cp, &pp1); - t1 = gsk_path_point_get_distance (cp); - threshold = graphene_point_distance (&pp1, point, NULL, NULL); - gsk_path_point_unref (cp); - - seg = i; - t = t1 / gsk_path_measure_get_length (measure); - pp = pp1; - found = TRUE; - } - - gsk_path_measure_unref (measure); - gsk_path_unref (path); - } - - if (found) - { - if (segment) - *segment = seg; - if (pos) - *pos = t; - if (p) - *p = pp; - } - - return found; -} - -/* }}} */ -/* {{{ Drag implementation */ - -static void -drag_begin (GtkGestureDrag *gesture, - double start_x, - double start_y, - CurveEditor *self) -{ - int i, j; - graphene_point_t p = GRAPHENE_POINT_INIT (start_x, start_y); - float t; - int idx; - - if (!self->edit) - return; - - for (i = 0; i < self->segments->len; i++) - { - Segment *seg = get_segment (self, i); - - for (j = 0; j < 3; j++) - { - if (graphene_point_distance (&seg->p[j], &p, NULL, NULL) < CLICK_RADIUS) - { - if (point_is_visible (self, i, j)) - { - self->dragged = i; - seg->dragged = j; - gtk_widget_queue_draw (GTK_WIDGET (self)); - } - return; - } - } - } - - if (find_closest_segment (self, &p, CLICK_RADIUS, NULL, &idx, &t)) - { - /* Can't bend a straight line */ - get_segment (self, idx)->op = GSK_PATH_CUBIC; - self->molded = idx; - return; - } - - gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED); -} - -static void -drag_line_point (CurveEditor *self, - double x, - double y) -{ - /* dragged point is on curve */ - Segment *seg, *seg1, *seg2, *seg11; - const graphene_point_t *d, *p; - graphene_point_t *c; - float l1, l2, dx, dy; - - seg = get_segment (self, self->dragged); - d = get_line_point (self, self->dragged); - - /* before moving the point, record the distances to its neighbors, since - * we may want to preserve those - */ - l1 = graphene_point_distance (d, get_left_control_point (self, self->dragged), NULL, NULL); - l2 = graphene_point_distance (d, get_right_control_point (self, self->dragged), NULL, NULL); - - dx = x - d->x; - dy = y - d->y; - - /* first move the point itself */ - set_segment_start (self, self->dragged, &GRAPHENE_POINT_INIT (x, y)); - - /* adjust control points as needed */ - seg1 = get_segment (self, self->dragged - 1); - seg2 = get_segment (self, self->dragged + 1); - - if (seg1->op == GSK_PATH_LINE) - { - /* the other endpoint of the line */ - p = get_line_point (self, self->dragged - 1); - c = get_right_control_point (self, self->dragged); - - if (seg->op == GSK_PATH_CUBIC && seg->type != CUSP) - { - opposite_point (d, p, l2, c); - } - else if (seg->op == GSK_PATH_CONIC && seg->type != CUSP) - { - graphene_point_t u; - line_intersection (&seg1->p[0], &seg1->p[3], &seg->p[3], &seg->p[1], &u); - if (u.x != NAN) - seg->p[1] = u; - else - { - seg->p[1].x += dx; - seg->p[1].y += dy; - } - - maintain_conic (self, self->dragged); - } - else - { - c->x += dx; - c->y += dy; - } - - /* always move the other control point along */ - c = get_left_control_point (self, self->dragged); - c->x += dx; - c->y += dy; - - /* handle the far end of the line */ - seg11 = get_segment (self, self->dragged - 2); - - if (seg11->op == GSK_PATH_CUBIC && seg1->type != CUSP) - { - double l; - const graphene_point_t *p2; - graphene_point_t *c2; - - p2 = get_line_point (self, self->dragged - 1); - c2 = get_left_control_point (self, self->dragged - 1); - /* adjust the control point before the line segment */ - l = graphene_point_distance (c2, p2, NULL, NULL); - opposite_point (p2, d, l, c2); - } - else if (seg11->op == GSK_PATH_CONIC && seg1->type != CUSP) - { - graphene_point_t u; - line_intersection (&seg11->p[0], &seg11->p[1], &seg1->p[3], &seg1->p[0], &u); - if (u.x != NAN) - seg11->p[1] = u; - - maintain_conic (self, self->dragged - 2); - } - } - - if (seg->op == GSK_PATH_LINE) - { - /* the other endpoint of the line */ - p = get_line_point (self, self->dragged + 1); - c = get_left_control_point (self, self->dragged); - - if (seg1->op == GSK_PATH_CUBIC && seg->type != CUSP) - { - /* adjust the control point before the line segment */ - opposite_point (d, p, l1, c); - } - else if (seg1->op == GSK_PATH_CONIC && seg->type != CUSP) - { - graphene_point_t u; - line_intersection (&seg1->p[0], &seg1->p[1], &seg->p[0], &seg->p[3], &u); - if (u.x != NAN) - seg1->p[1] = u; - else - { - seg1->p[1].x += dx; - seg1->p[1].y += dy; - } - - maintain_conic (self, self->dragged); - } - else if (seg1->op == GSK_PATH_CUBIC) - { - c->x += dx; - c->y += dy; - } - - /* always move the other control point along */ - c = get_right_control_point (self, self->dragged); - c->x += dx; - c->x += dy; - - /* handle the other end of the line */ - if (seg2->op == GSK_PATH_CUBIC && seg2->type != CUSP) - { - double l; - - /* adjust the control point after the line segment */ - c = get_right_control_point (self, self->dragged + 1); - l = graphene_point_distance (c, p, NULL, NULL); - opposite_point (p, d, l, c); - } - else if (seg2->op == GSK_PATH_CONIC && seg2->type != CUSP) - { - graphene_point_t u; - line_intersection (&seg->p[0], &seg->p[3], &seg2->p[1], &seg2->p[3], &u); - if (u.x != NAN) - seg2->p[1] = u; - - maintain_conic (self, self->dragged + 1); - } - } - - if (seg1->op != GSK_PATH_LINE && seg->op != GSK_PATH_LINE) - { - if (seg1->op == GSK_PATH_CUBIC) - { - c = &seg1->p[2]; - c->x += dx; - c->y += dy; - } - else if (seg1->op == GSK_PATH_CONIC && seg->type != CUSP) - { - graphene_point_t a, b; - - a.x = seg1->p[1].x + dx; - a.y = seg1->p[1].y + dy; - line_intersection (&seg->p[0], &a, &seg1->p[0], &seg1->p[1], &b); - seg1->p[1] = b; - } - - if (seg->op == GSK_PATH_CUBIC) - { - c = &seg->p[1]; - c->x += dx; - c->y += dy; - } - else if (seg->op == GSK_PATH_CONIC && seg->type != CUSP) - { - graphene_point_t a, b; - - a.x = seg->p[1].x + dx; - a.y = seg->p[1].y + dy; - line_intersection (&seg->p[3], &seg->p[1], &a, &seg->p[0], &b); - seg->p[1] = b; - } - } - - maintain_smoothness (self, self->dragged); - maintain_automatic (self, self->dragged); - maintain_conic (self, self->dragged); - maintain_conic (self, self->dragged - 1); -} - -static void -drag_conic_point (CurveEditor *self, - float x, - float y) -{ - Segment *seg, *seg1, *seg2; - graphene_point_t *d, *c1; - float l; - - seg = get_segment (self, self->dragged); - g_assert (seg->op == GSK_PATH_CONIC || seg->op == GSK_PATH_QUAD); - d = &seg->p[seg->dragged]; - - seg1 = get_segment (self, self->dragged + 1); - seg2 = get_segment (self, self->dragged - 1); - - if (seg->dragged == 1) - { - if (seg->type != CUSP && seg2->op == GSK_PATH_LINE) - { - /* control point must be on the line of seg2 */ - - if (seg1->type != CUSP && seg1->op == GSK_PATH_LINE) - { - graphene_point_t c; - - line_intersection (&seg1->p[0], &seg1->p[3], &seg2->p[3], &seg2->p[0], &c); - if (c.x != NAN) - *d = c; /* unmoveable */ - else - { - closest_point (&GRAPHENE_POINT_INIT (x, y), &seg1->p[0], &seg1->p[3], &c); - *d = c; - } - } - else - { - graphene_point_t c; - - closest_point (&GRAPHENE_POINT_INIT (x, y), &seg2->p[0], &seg2->p[3], &c); - *d = c; - - if (seg1->type != CUSP) - { - l = graphene_point_distance (&seg1->p[0], &seg1->p[1], NULL, NULL); - opposite_point (&seg1->p[0], d, l, &seg1->p[1]); - } - } - } - else if (seg1->type != CUSP && seg1->op == GSK_PATH_LINE) - { - graphene_point_t c; - - closest_point (&GRAPHENE_POINT_INIT (x, y), &seg1->p[0], &seg1->p[3], &c); - *d = c; - - if (seg2->type != CUSP) - { - if (seg2->op == GSK_PATH_CUBIC) - c1 = &seg2->p[2]; - else if (seg2->op == GSK_PATH_CONIC) - c1 = &seg2->p[1]; - else - g_assert_not_reached (); - l = graphene_point_distance (&seg2->p[3], c1, NULL, NULL); - opposite_point (&seg2->p[3], d, l, c1); - } - } - else - { - /* unconstrained */ - d->x = x; - d->y = y; - - if (seg1->type != CUSP) - { - l = graphene_point_distance (&seg1->p[0], &seg1->p[1], NULL, NULL); - opposite_point (&seg1->p[0], d, l, &seg1->p[1]); - } - - if (seg2->type != CUSP) - { - if (seg2->op == GSK_PATH_CUBIC) - c1 = &seg2->p[2]; - else if (seg2->op == GSK_PATH_CONIC) - c1 = &seg2->p[1]; - else - g_assert_not_reached (); - l = graphene_point_distance (&seg2->p[3], c1, NULL, NULL); - opposite_point (&seg2->p[3], d, l, c1); - } - } - } - else if (seg->dragged == 2) - { - /* dragging the shoulder point */ - graphene_point_t m; - float t; - - graphene_point_interpolate (&seg->p[0], &seg->p[3], 0.5, &m); - find_point_on_line (&m, &seg->p[1], &GRAPHENE_POINT_INIT (x, y), &t); - t = CLAMP (t, 0, 0.9); - seg->weight = - t / (t - 1); - } - - maintain_conic (self, self->dragged); -} - -static void -drag_control_point (CurveEditor *self, - float x, - float y) -{ - /* dragged point is a control point */ - Segment *seg, *seg1; - const graphene_point_t *p, *p1; - graphene_point_t *c, *d; - PointType type; - - seg = get_segment (self, self->dragged); - g_assert (seg->op == GSK_PATH_CUBIC); - d = &seg->p[seg->dragged]; - - if (seg->dragged == 2) - { - seg1 = get_segment (self, self->dragged + 1); - p = &seg1->p[0]; - c = &seg1->p[1]; - type = seg1->type; - p1 = get_line_point (self, self->dragged + 2); - } - else if (seg->dragged == 1) - { - seg1 = get_segment (self, self->dragged - 1); - if (seg1->op == GSK_PATH_CONIC) - c = &seg1->p[1]; - else - c = &seg1->p[2]; - p = &seg->p[0]; - type = seg->type; - p1 = &seg1->p[0]; - } - else - g_assert_not_reached (); - - if (type != CUSP) - { - if (seg1->op == GSK_PATH_CUBIC) - { - double l; - - /* first move the point itself */ - d->x = x; - d->y = y; - - /* then adjust the other control point */ - if (type == SYMMETRIC) - l = graphene_point_distance (d, p, NULL, NULL); - else - l = graphene_point_distance (c, p, NULL, NULL); - - opposite_point (p, d, l, c); - } - else if (seg1->op == GSK_PATH_CONIC) - { - graphene_point_t u; - - d->x = x; - d->y = y; - line_intersection (p1, c, p, d, &u); - *c = u; - - maintain_conic (self, self->dragged - 1); - maintain_conic (self, self->dragged + 1); - } - else if (seg1->op == GSK_PATH_LINE) - { - graphene_point_t m = GRAPHENE_POINT_INIT (x, y); - closest_point (&m, p, p1, d); - } - else - { - d->x = x; - d->y = y; - } - } - else - { - d->x = x; - d->y = y; - } -} - -static void -drag_point (CurveEditor *self, - double x, - double y) -{ - Segment *seg = get_segment (self, self->dragged); - - if (seg->dragged == 0) - drag_line_point (self, x, y); - else if (seg->op == GSK_PATH_CONIC || seg->op == GSK_PATH_QUAD) - drag_conic_point (self, x, y); - else - drag_control_point (self, x, y); -} - -static void -drag_curve (CurveEditor *self, - double x, - double y) -{ - graphene_point_t *S, *E; - graphene_point_t B, C1, C2; - double l; - Segment *seg, *seg1, *seg2; - - seg = get_segment (self, self->molded); - seg1 = get_segment (self, self->molded + 1); - seg2 = get_segment (self, self->molded - 1); - - if (seg->op == GSK_PATH_CONIC) - { - /* FIXME */ - return; - } - - S = &seg->p[0]; - B = GRAPHENE_POINT_INIT (x, y); - E = &seg->p[3]; - - bezier_through (S, &B, E, &C1, &C2); - - seg->p[1] = C1; - seg->p[2] = C2; - - /* When the neighboring segments are lines, we can't actually - * use C1 and C2 as-is, since we need control points to lie - * on the line. So we just use their distance. This makes our - * point B not quite match anymore, but we're overconstrained. - */ - if (seg2->op == GSK_PATH_LINE) - { - l = graphene_point_distance (&seg->p[3], &C1, NULL, NULL); - if (three_point_angle (&seg2->p[3], &seg2->p[0], &B) > 0) - scale_point (&seg2->p[3], &seg2->p[0], l, &seg->p[1]); - else - opposite_point (&seg2->p[3], &seg2->p[0], l, &seg->p[1]); - } - - if (seg1->op == GSK_PATH_LINE) - { - l = graphene_point_distance (&seg->p[0], &C2, NULL, NULL); - if (three_point_angle (&seg1->p[0], &seg1->p[3], &B) > 0) - scale_point (&seg1->p[0], &seg1->p[3], l, &seg->p[2]); - else - opposite_point (&seg1->p[0], &seg1->p[3], l, &seg->p[2]); - } - - /* Maintain smoothness and symmetry */ - if (seg->type != CUSP) - { - if (seg->type == SYMMETRIC) - l = graphene_point_distance (&seg->p[0], &seg->p[1], NULL, NULL); - else - l = graphene_point_distance (&seg->p[0], &seg2->p[2], NULL, NULL); - opposite_point (&seg->p[0], &seg->p[1], l, &seg2->p[2]); - } - - if (seg1->type != CUSP) - { - if (seg1->type == SYMMETRIC) - l = graphene_point_distance (&seg->p[3], &seg->p[2], NULL, NULL); - else - l = graphene_point_distance (&seg->p[3], &seg1->p[1], NULL, NULL); - opposite_point (&seg->p[3], &seg->p[2], l, &seg1->p[1]); - } -} - -static void -drag_update (GtkGestureDrag *gesture, - double offset_x, - double offset_y, - CurveEditor *self) -{ - double x, y; - - gtk_gesture_drag_get_start_point (gesture, &x, &y); - - x += offset_x; - y += offset_y; - - if (self->dragged != -1) - { - gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED); - drag_point (self, x, y); - gtk_widget_queue_draw (GTK_WIDGET (self)); - } - else if (self->molded != -1) - { - gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED); - drag_curve (self, x, 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->molded = -1; -} - /* }}} */ /* {{{ Action callbacks */ static void -set_point_type (GSimpleAction *action, +set_constraint (GSimpleAction *action, GVariant *value, gpointer data) { CurveEditor *self = CURVE_EDITOR (data); + CEPathConstraint constraint; - get_segment (self, self->context)->type = point_type_from_string (g_variant_get_string (value, NULL)); - - maintain_smoothness (self, self->context); - maintain_symmetry (self, self->context); - maintain_automatic (self, self->context); + constraint = constraint_from_string (g_variant_get_string (value, NULL)); + ce_path_set_constraint (self->path, self->context, constraint); gtk_widget_queue_draw (GTK_WIDGET (self)); } @@ -1758,19 +186,10 @@ set_operation (GSimpleAction *action, gpointer data) { CurveEditor *self = CURVE_EDITOR (data); - Segment *seg = get_segment (self, self->context); + GskPathOperation op; - seg->op = op_from_string (g_variant_get_string (value, NULL)); - - if (seg->op == GSK_PATH_CONIC && seg->weight == 0) - seg->weight = 1; - - maintain_conic (self, self->context); - - maintain_smoothness (self, self->context); - maintain_smoothness (self, self->context + 1); - maintain_symmetry (self, self->context); - maintain_symmetry (self, self->context + 1); + op = op_from_string (g_variant_get_string (value, NULL)); + ce_path_set_operation (self->path, self->context, op); gtk_widget_queue_draw (GTK_WIDGET (self)); } @@ -1782,7 +201,7 @@ insert_new_point (GSimpleAction *action, { CurveEditor *self = CURVE_EDITOR (data); - insert_point (self, self->context, self->context_pos); + ce_path_split_curve (self->path, self->context, self->context_pos); gtk_widget_queue_draw (GTK_WIDGET (self)); } @@ -1794,7 +213,7 @@ remove_current_point (GSimpleAction *action, { CurveEditor *self = CURVE_EDITOR (data); - remove_point (self, self->context); + ce_path_remove_curve (self->path, self->context); gtk_widget_queue_draw (GTK_WIDGET (self)); } @@ -1807,29 +226,29 @@ toggle_edit_point (GSimpleAction *action, CurveEditor *self = CURVE_EDITOR (data); if (self->edited_point == self->context) - self->edited_point = -1; + self->edited_point = NULL; else { self->edited_point = self->context; - self->edited_segment = -1; + self->edited_curve = NULL; } gtk_widget_queue_draw (GTK_WIDGET (self)); } static void -toggle_edit_segment (GSimpleAction *action, - GVariant *value, - gpointer data) +toggle_edit_curve (GSimpleAction *action, + GVariant *value, + gpointer data) { CurveEditor *self = CURVE_EDITOR (data); - if (self->edited_segment == self->context) - self->edited_segment = -1; + if (self->edited_curve == self->context) + self->edited_curve = NULL; else { - self->edited_segment = self->context; - self->edited_point = -1; + self->edited_curve = self->context; + self->edited_point = NULL; } gtk_widget_queue_draw (GTK_WIDGET (self)); @@ -1841,16 +260,104 @@ reset_weight (GSimpleAction *action, gpointer data) { CurveEditor *self = CURVE_EDITOR (data); - Segment *seg = get_segment (self, self->context); - seg->weight = 1; - maintain_conic (self, self->context); + ce_path_set_weight (self->path, self->context, 1.f); + gtk_widget_queue_draw (GTK_WIDGET (self)); } /* }}} */ /* {{{ Event handlers */ +static void +drag_begin (GtkGestureDrag *gesture, + double start_x, + double start_y, + CurveEditor *self) +{ + int i, j; + graphene_point_t point = GRAPHENE_POINT_INIT (start_x, start_y); + float t; + CEPathCurve *seg; + + if (!self->edit) + return; + + for (i = 0; i < ce_path_get_n_curves (self->path); i++) + { + seg = ce_path_get_curve (self->path, i); + for (j = 0; j < 3; j++) + { + graphene_point_t q; + + ce_path_get_point (self->path, seg, j, &q); + if (graphene_point_distance (&q, &point, NULL, NULL) < CLICK_RADIUS) + { + if (point_is_visible (self, seg, j)) + { + self->dragged_curve = seg; + self->dragged_point = j; + gtk_widget_queue_draw (GTK_WIDGET (self)); + return; + } + } + } + } + + seg = ce_path_find_closest_curve (self->path, &point, CLICK_RADIUS, NULL, &t); + if (seg) + { + /* Can't bend a straight line */ + if (ce_path_get_operation (self->path, seg) == GSK_PATH_CUBIC) + { + self->molded_curve = seg; + 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) +{ + graphene_point_t pos; + double x, y; + + gtk_gesture_drag_get_start_point (gesture, &x, &y); + + pos.x = x + offset_x; + pos.y = y + offset_y; + + if (self->dragged_curve != NULL) + { + gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED); + ce_path_set_point (self->path, self->dragged_curve, self->dragged_point, &pos); + gtk_widget_queue_draw (GTK_WIDGET (self)); + } + else if (self->molded_curve != NULL) + { + gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED); + ce_path_drag_curve (self->path, self->molded_curve, &pos); + 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_curve = NULL; + self->dragged_point = -1; + self->molded_curve = NULL; +} + static void pressed (GtkGestureClick *gesture, int n_press, @@ -1862,24 +369,29 @@ pressed (GtkGestureClick *gesture, int i; int button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture)); float t; + CEPathCurve *seg; if (!self->edit) return; if (button == GDK_BUTTON_SECONDARY) { - for (i = 0; i < self->segments->len; i++) + for (i = 0; i < ce_path_get_n_curves (self->path); i++) { - Segment *seg = get_segment (self, i); - const graphene_point_t *p = get_line_point (self, i); + graphene_point_t p; + CEPathConstraint constraint; - if (graphene_point_distance (p, &m, NULL, NULL) < CLICK_RADIUS) + seg = ce_path_get_curve (self->path, i); + ce_path_get_point (self->path, seg, 0, &p); + constraint = ce_path_get_constraint (self->path, seg); + + if (graphene_point_distance (&p, &m, NULL, NULL) < CLICK_RADIUS) { GAction *action; - self->context = i; + self->context = seg; - action = g_action_map_lookup_action (self->actions, "set-segment-type"); + action = g_action_map_lookup_action (self->actions, "set-curve-type"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE); action = g_action_map_lookup_action (self->actions, "add-point"); @@ -1891,15 +403,15 @@ pressed (GtkGestureClick *gesture, action = g_action_map_lookup_action (self->actions, "reset-weight"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE); - action = g_action_map_lookup_action (self->actions, "set-point-type"); + action = g_action_map_lookup_action (self->actions, "set-constraint"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE); - g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_string (point_type_to_string (seg->type))); + g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_string (constraint_to_string (constraint))); action = g_action_map_lookup_action (self->actions, "edit-point"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE); - g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (self->edited_point == i)); + g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (self->edited_point == seg)); - action = g_action_map_lookup_action (self->actions, "edit-segment"); + action = g_action_map_lookup_action (self->actions, "edit-curve"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE); gtk_popover_set_pointing_to (GTK_POPOVER (self->menu), @@ -1909,14 +421,18 @@ pressed (GtkGestureClick *gesture, } } - if (find_closest_segment (self, &m, CLICK_RADIUS, NULL, &i, &t)) + seg = ce_path_find_closest_curve (self->path, &m, CLICK_RADIUS, NULL, &t); + if (seg) { GAction *action; + GskPathOperation op; - self->context = i; + op = ce_path_get_operation (self->path, seg); + + self->context = seg; self->context_pos = t; - action = g_action_map_lookup_action (self->actions, "set-point-type"); + action = g_action_map_lookup_action (self->actions, "set-constraint"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE); action = g_action_map_lookup_action (self->actions, "edit-point"); @@ -1928,17 +444,16 @@ pressed (GtkGestureClick *gesture, action = g_action_map_lookup_action (self->actions, "add-point"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE); - action = g_action_map_lookup_action (self->actions, "edit-segment"); + action = g_action_map_lookup_action (self->actions, "edit-curve"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE); - g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (self->edited_segment == i)); + g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (self->edited_curve == seg)); action = g_action_map_lookup_action (self->actions, "reset-weight"); - g_simple_action_set_enabled (G_SIMPLE_ACTION (action), - get_segment (self, i)->op == GSK_PATH_CONIC); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), op == GSK_PATH_CONIC); - action = g_action_map_lookup_action (self->actions, "set-segment-type"); + action = g_action_map_lookup_action (self->actions, "set-curve-type"); g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE); - g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_string (op_to_string (get_segment (self, i)->op))); + g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_string (op_to_string (op))); gtk_popover_set_pointing_to (GTK_POPOVER (self->menu), &(const GdkRectangle){ x, y, 1, 1 }); @@ -1962,20 +477,22 @@ released (GtkGestureClick *gesture, if (!self->edit) return; - for (i = 0; i < self->segments->len; i++) + for (i = 0; i < ce_path_get_n_curves (self->path); i++) { - const graphene_point_t *p = get_line_point (self, i); + CEPathCurve *seg = ce_path_get_curve (self->path, i); + graphene_point_t p; - if (graphene_point_distance (p, &m, NULL, NULL) < CLICK_RADIUS) + ce_path_get_point (self->path, seg, 0, &p); + if (graphene_point_distance (&p, &m, NULL, NULL) < CLICK_RADIUS) { if (button == GDK_BUTTON_PRIMARY) { - if (self->edited_point == i) - self->edited_point = -1; + if (self->edited_point == seg) + self->edited_point = NULL; else { - self->edited_point = i; - self->edited_segment = -1; + self->edited_point = seg; + self->edited_curve = NULL; } gtk_widget_queue_draw (GTK_WIDGET (self)); return; @@ -1985,14 +502,16 @@ released (GtkGestureClick *gesture, if (button == GDK_BUTTON_PRIMARY) { + CEPathCurve *seg; float t; - int point; - if (find_closest_segment (self, &m, CLICK_RADIUS, NULL, &point, &t)) + seg = ce_path_find_closest_curve (self->path, &m, CLICK_RADIUS, NULL, &t); + if (seg) { - self->dragged = -1; - self->molded = -1; - insert_point (self, point, t); + self->dragged_curve = NULL; + self->dragged_point = -1; + self->molded_curve = NULL; + ce_path_split_curve (self->path, seg, t); } } } @@ -2005,63 +524,60 @@ motion (GtkEventControllerMotion *controller, { graphene_point_t m = GRAPHENE_POINT_INIT (x, y); int i, j; - gboolean changed = FALSE; + CEPathCurve *hovered_curve = NULL; + int hovered_point = -1; if (self->edit) { - for (i = 0; i < self->segments->len; i++) + for (i = 0; i < ce_path_get_n_curves (self->path); i++) { - Segment *seg = get_segment (self, i); + CEPathCurve *seg = ce_path_get_curve (self->path, i); int hovered = -1; for (j = 0; j < 3; j++) { - const graphene_point_t *q = &seg->p[j]; + graphene_point_t q; - if (!point_is_visible (self, i, j)) + if (!point_is_visible (self, seg, j)) continue; - if (graphene_point_distance (q, &m, NULL, NULL) < CLICK_RADIUS) + ce_path_get_point (self->path, seg, j, &q); + + if (graphene_point_distance (&q, &m, NULL, NULL) < CLICK_RADIUS) { hovered = j; break; } } - if (seg->hovered != hovered) + if (hovered != -1) { - seg->hovered = hovered; - changed = TRUE; + hovered_curve = seg; + hovered_point = hovered; + break; } } - } - if (changed) - gtk_widget_queue_draw (GTK_WIDGET (self)); + if (self->hovered_curve != hovered_curve || + self->hovered_point != hovered_point) + gtk_widget_queue_draw (GTK_WIDGET (self)); + + self->hovered_curve = hovered_curve; + self->hovered_point = hovered_point; + } } static void leave (GtkEventController *controller, CurveEditor *self) { - int i; - gboolean changed = FALSE; - for (i = 0; i < self->segments->len; i++) - { - Segment *seg = get_segment (self, i); - if (seg->hovered != -1) - { - seg->hovered = -1; - changed = TRUE; - } - } - - if (changed) - gtk_widget_queue_draw (GTK_WIDGET (self)); + self->hovered_curve = NULL; + self->hovered_point = -1; + gtk_widget_queue_draw (GTK_WIDGET (self)); } /* }}} */ -/* {{{ Snapshot */ +/* {{{ Snaps hot */ static void add_diamond (GskPathBuilder *builder, @@ -2103,7 +619,7 @@ curve_editor_snapshot (GtkWidget *widget, float width; float height; - if (self->segments->len == 0) + if (ce_path_get_n_curves (self->path) == 0) return; width = gtk_widget_get_width (widget); @@ -2111,11 +627,7 @@ curve_editor_snapshot (GtkWidget *widget, /* Add the curve itself */ - builder = gsk_path_builder_new (); - - curve_editor_add_path (self, builder); - - path = gsk_path_builder_free_to_path (builder); + path = ce_path_get_gsk_path (self->path); stroke = gsk_stroke_copy (self->stroke); if (self->show_outline) @@ -2156,80 +668,79 @@ curve_editor_snapshot (GtkWidget *widget, { builder = gsk_path_builder_new (); - if (self->edited_point != -1) + if (self->edited_point != NULL) { + GskPathOperation op; + graphene_point_t p, c; + CEPathCurve *prev; + prev = ce_path_previous_curve (self->path, self->edited_point); + /* Add the skeleton */ - Segment *seg = get_segment (self, self->edited_point); - Segment *seg1 = get_segment (self, self->edited_point - 1); - const graphene_point_t *p = get_line_point (self, self->edited_point); - if (seg1->op == GSK_PATH_CUBIC) + ce_path_get_point (self->path, self->edited_point, 0, &p); + if (prev != NULL) { - graphene_point_t *c = &seg1->p[2]; - gsk_path_builder_move_to (builder, c->x, c->y); - gsk_path_builder_line_to (builder, p->x, p->y); - } - else if (seg1->op == GSK_PATH_CONIC) - { - graphene_point_t *c = &seg1->p[1]; - gsk_path_builder_move_to (builder, c->x, c->y); - gsk_path_builder_line_to (builder, p->x, p->y); - } - else if (seg1->op == GSK_PATH_QUAD) - { - graphene_point_t *c = &seg1->p[1]; - gsk_path_builder_move_to (builder, c->x, c->y); - gsk_path_builder_line_to (builder, p->x, p->y); + op = ce_path_get_operation (self->path, prev); + + if (op == GSK_PATH_CUBIC) + { + ce_path_get_point (self->path, prev, 2, &c); + gsk_path_builder_move_to (builder, c.x, c.y); + gsk_path_builder_line_to (builder, p.x, p.y); + } + else if (op == GSK_PATH_CONIC || op == GSK_PATH_QUAD) + { + ce_path_get_point (self->path, prev, 1, &c); + gsk_path_builder_move_to (builder, c.x, c.y); + gsk_path_builder_line_to (builder, p.x, p.y); + } } - if (seg->op == GSK_PATH_CUBIC) + op = ce_path_get_operation (self->path, self->edited_point); + + if (op == GSK_PATH_CUBIC || op == GSK_PATH_CONIC || op == GSK_PATH_QUAD) { - graphene_point_t *c = &seg->p[1]; - gsk_path_builder_move_to (builder, c->x, c->y); - gsk_path_builder_line_to (builder, p->x, p->y); - } - else if (seg->op == GSK_PATH_CONIC) - { - graphene_point_t *c = &seg->p[1]; - gsk_path_builder_move_to (builder, p->x, p->y); - gsk_path_builder_line_to (builder, c->x, c->y); - } - else if (seg->op == GSK_PATH_QUAD) - { - graphene_point_t *c = &seg->p[1]; - gsk_path_builder_move_to (builder, p->x, p->y); - gsk_path_builder_line_to (builder, c->x, c->y); + ce_path_get_point (self->path, self->edited_point, 1, &c); + gsk_path_builder_move_to (builder, p.x, p.y); + gsk_path_builder_line_to (builder, c.x, c.y); } } - if (self->edited_segment != -1) + if (self->edited_curve != NULL) { - Segment *seg = get_segment (self, self->edited_segment); + graphene_point_t p[4]; + GskPathOperation op; - if (seg->op == GSK_PATH_CUBIC) + op = ce_path_get_operation (self->path, self->edited_curve); + ce_path_get_point (self->path, self->edited_curve, 0, &p[0]); + ce_path_get_point (self->path, self->edited_curve, 1, &p[1]); + ce_path_get_point (self->path, self->edited_curve, 2, &p[2]); + ce_path_get_point (self->path, self->edited_curve, 3, &p[3]); + + if (op == GSK_PATH_CUBIC) { - gsk_path_builder_move_to (builder, seg->p[0].x, seg->p[0].y); - gsk_path_builder_line_to (builder, seg->p[1].x, seg->p[1].y); - gsk_path_builder_line_to (builder, seg->p[2].x, seg->p[2].y); - gsk_path_builder_line_to (builder, seg->p[3].x, seg->p[3].y); + gsk_path_builder_move_to (builder, p[0].x, p[0].y); + gsk_path_builder_line_to (builder, p[1].x, p[1].y); + gsk_path_builder_line_to (builder, p[2].x, p[2].y); + gsk_path_builder_line_to (builder, p[3].x, p[3].y); } - else if (seg->op == GSK_PATH_CONIC) + else if (op == GSK_PATH_CONIC) { - gsk_path_builder_move_to (builder, seg->p[0].x, seg->p[0].y); - gsk_path_builder_line_to (builder, seg->p[1].x, seg->p[1].y); - gsk_path_builder_line_to (builder, seg->p[3].x, seg->p[3].y); + gsk_path_builder_move_to (builder, p[0].x, p[0].y); + gsk_path_builder_line_to (builder, p[1].x, p[1].y); + gsk_path_builder_line_to (builder, p[3].x, p[3].y); } - else if (seg->op == GSK_PATH_QUAD) + else if (op == GSK_PATH_QUAD) { - gsk_path_builder_move_to (builder, seg->p[0].x, seg->p[0].y); - gsk_path_builder_line_to (builder, seg->p[1].x, seg->p[1].y); - gsk_path_builder_line_to (builder, seg->p[3].x, seg->p[3].y); + gsk_path_builder_move_to (builder, p[0].x, p[0].y); + gsk_path_builder_line_to (builder, p[1].x, p[1].y); + gsk_path_builder_line_to (builder, p[3].x, p[3].y); } } path = gsk_path_builder_free_to_path (builder); - if (self->edited_point != -1 || self->edited_segment != -1) + if (self->edited_point != NULL || self->edited_curve != NULL) { stroke = gsk_stroke_new (1); gtk_snapshot_push_stroke (snapshot, path, stroke); @@ -2256,39 +767,40 @@ curve_editor_snapshot (GtkWidget *widget, { builder = gsk_path_builder_new (); - for (i = 0; i < self->segments->len; i++) + for (i = 0; i < ce_path_get_n_curves (self->path); i++) { - Segment *seg = get_segment (self, i); + CEPathCurve *seg = ce_path_get_curve (self->path, i); for (j = 0; j < 3; j++) { - graphene_point_t *p = &seg->p[j]; + graphene_point_t p; - if (!point_is_visible (self, i, j)) + if (!point_is_visible (self, seg, j)) continue; - if ((k == 0 && j != seg->hovered) || - (k == 1 && j == seg->hovered)) + if ((k == 0) != (self->hovered_curve == seg && self->hovered_point == j)) continue; + ce_path_get_point (self->path, seg, j, &p); + if (j != 0) { - gsk_path_builder_add_circle (builder, p, DRAW_RADIUS); + gsk_path_builder_add_circle (builder, &p, DRAW_RADIUS); } else { - switch (seg->type) + switch (ce_path_get_constraint (self->path, seg)) { - case CUSP: - add_diamond (builder, p, DRAW_RADIUS); + case CE_PATH_CUSP: + add_diamond (builder, &p, DRAW_RADIUS); break; - case SMOOTH: - add_square (builder, p, DRAW_RADIUS); + case CE_PATH_SMOOTH: + add_square (builder, &p, DRAW_RADIUS); break; - case SYMMETRIC: - case AUTO: - gsk_path_builder_add_circle (builder, p, DRAW_RADIUS); + case CE_PATH_SYMMETRIC: + case CE_PATH_AUTOMATIC: + gsk_path_builder_add_circle (builder, &p, DRAW_RADIUS); break; default: g_assert_not_reached (); @@ -2355,7 +867,7 @@ curve_editor_dispose (GObject *object) { CurveEditor *self = CURVE_EDITOR (object); - g_clear_pointer (&self->segments, g_array_unref); + g_clear_object (&self->path); g_clear_pointer (&self->menu, gtk_widget_unparent); g_clear_object (&self->actions); @@ -2375,7 +887,7 @@ curve_editor_class_init (CurveEditorClass *class) widget_class->size_allocate = curve_editor_size_allocate; } -/* }}} */ + /* }}} */ /* {{{ Setup */ static void @@ -2387,12 +899,16 @@ curve_editor_init (CurveEditor *self) GMenuItem *item; GSimpleAction *action; - self->segments = g_array_new (FALSE, FALSE, sizeof (Segment)); - self->dragged = -1; - self->molded = -1; - self->edited_point = -1; - self->edited_segment = -1; + self->path = ce_path_new (); + self->context = NULL; + self->dragged_curve = NULL; + self->dragged_point = -1; + self->molded_curve = NULL; + self->edited_curve = NULL; + self->edited_point = NULL; self->edit = FALSE; + self->hovered_curve = NULL; + self->hovered_point = -1; self->stroke = gsk_stroke_new (1.0); self->color = (GdkRGBA){ 0, 0, 0, 1 }; @@ -2416,12 +932,12 @@ curve_editor_init (CurveEditor *self) self->actions = G_ACTION_MAP (g_simple_action_group_new ()); - action = g_simple_action_new_stateful ("set-point-type", G_VARIANT_TYPE_STRING, g_variant_new_string ("smooth")); - g_signal_connect (action, "change-state", G_CALLBACK (set_point_type), self); + action = g_simple_action_new_stateful ("set-constraint", G_VARIANT_TYPE_STRING, g_variant_new_string ("smooth")); + g_signal_connect (action, "change-state", G_CALLBACK (set_constraint), 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 ("set-segment-type", G_VARIANT_TYPE_STRING, g_variant_new_string ("cubic")); + action = g_simple_action_new_stateful ("set-curve-type", G_VARIANT_TYPE_STRING, g_variant_new_string ("cubic")); g_signal_connect (action, "change-state", G_CALLBACK (set_operation), self); g_action_map_add_action (G_ACTION_MAP (self->actions), G_ACTION (action)); @@ -2429,8 +945,8 @@ curve_editor_init (CurveEditor *self) g_signal_connect (action, "change-state", G_CALLBACK (toggle_edit_point), self); g_action_map_add_action (G_ACTION_MAP (self->actions), G_ACTION (action)); - action = g_simple_action_new_stateful ("edit-segment", NULL, g_variant_new_boolean (FALSE)); - g_signal_connect (action, "change-state", G_CALLBACK (toggle_edit_segment), self); + action = g_simple_action_new_stateful ("edit-curve", NULL, g_variant_new_boolean (FALSE)); + g_signal_connect (action, "change-state", G_CALLBACK (toggle_edit_curve), self); g_action_map_add_action (G_ACTION_MAP (self->actions), G_ACTION (action)); action = g_simple_action_new ("add-point", NULL); @@ -2451,22 +967,22 @@ curve_editor_init (CurveEditor *self) section = g_menu_new (); - item = g_menu_item_new ("Cusp", "path.set-point-type::cusp"); + item = g_menu_item_new ("Cusp", "path.set-constraint::cusp"); g_menu_item_set_attribute_value (item, "hidden-when", g_variant_new_string ("action-disabled")); g_menu_append_item (section, item); g_object_unref (item); - item = g_menu_item_new ("Smooth", "path.set-point-type::smooth"); + item = g_menu_item_new ("Smooth", "path.set-constraint::smooth"); g_menu_item_set_attribute_value (item, "hidden-when", g_variant_new_string ("action-disabled")); g_menu_append_item (section, item); g_object_unref (item); - item = g_menu_item_new ("Symmetric", "path.set-point-type::symmetric"); + item = g_menu_item_new ("Symmetric", "path.set-constraint::symmetric"); g_menu_item_set_attribute_value (item, "hidden-when", g_variant_new_string ("action-disabled")); g_menu_append_item (section, item); g_object_unref (item); - item = g_menu_item_new ("Automatic", "path.set-point-type::auto"); + item = g_menu_item_new ("Automatic", "path.set-constraint::automatic"); g_menu_item_set_attribute_value (item, "hidden-when", g_variant_new_string ("action-disabled")); g_menu_append_item (section, item); g_object_unref (item); @@ -2476,22 +992,22 @@ curve_editor_init (CurveEditor *self) section = g_menu_new (); - item = g_menu_item_new ("Line", "path.set-segment-type::line"); + item = g_menu_item_new ("Line", "path.set-curve-type::line"); g_menu_item_set_attribute_value (item, "hidden-when", g_variant_new_string ("action-disabled")); g_menu_append_item (section, item); g_object_unref (item); - item = g_menu_item_new ("Quadratic", "path.set-segment-type::quad"); + item = g_menu_item_new ("Quadratic", "path.set-curve-type::quad"); g_menu_item_set_attribute_value (item, "hidden-when", g_variant_new_string ("action-disabled")); g_menu_append_item (section, item); g_object_unref (item); - item = g_menu_item_new ("Cubic", "path.set-segment-type::cubic"); + item = g_menu_item_new ("Cubic", "path.set-curve-type::cubic"); g_menu_item_set_attribute_value (item, "hidden-when", g_variant_new_string ("action-disabled")); g_menu_append_item (section, item); g_object_unref (item); - item = g_menu_item_new ("Conic", "path.set-segment-type::conic"); + item = g_menu_item_new ("Conic", "path.set-curve-type::conic"); g_menu_item_set_attribute_value (item, "hidden-when", g_variant_new_string ("action-disabled")); g_menu_append_item (section, item); g_object_unref (item); @@ -2506,7 +1022,7 @@ curve_editor_init (CurveEditor *self) g_menu_append_item (section, item); g_object_unref (item); - item = g_menu_item_new ("Edit", "path.edit-segment"); + item = g_menu_item_new ("Edit", "path.edit-curve"); g_menu_item_set_attribute_value (item, "hidden-when", g_variant_new_string ("action-disabled")); g_menu_append_item (section, item); g_object_unref (item); @@ -2540,7 +1056,7 @@ curve_editor_init (CurveEditor *self) gtk_widget_set_parent (self->menu, GTK_WIDGET (self)); } -/* }}} */ + /* }}} */ /* {{{ API */ GtkWidget * @@ -2554,126 +1070,24 @@ curve_editor_set_edit (CurveEditor *self, gboolean edit) { self->edit = edit; - self->edited_point = -1; - self->edited_segment = -1; + self->edited_point = NULL; + self->edited_curve = NULL; gtk_widget_queue_draw (GTK_WIDGET (self)); } -static gboolean -copy_segments (GskPathOperation op, - const graphene_point_t *pts, - gsize n_pts, - float weight, - gpointer data) -{ - CurveEditor *self = data; - Segment seg; - - seg.op = op; - seg.hovered = -1; - seg.dragged = -1; - - switch (op) - { - case GSK_PATH_MOVE: - break; - - case GSK_PATH_CLOSE: - seg.p[0] = pts[0]; - seg.p[3] = pts[1]; - g_array_append_val (self->segments, seg); - break; - - case GSK_PATH_LINE: - seg.p[0] = pts[0]; - seg.p[3] = pts[1]; - g_array_append_val (self->segments, seg); - break; - - case GSK_PATH_QUAD: - { - seg.p[0] = pts[0]; - seg.p[1] = pts[1]; - seg.p[3] = pts[2]; - - g_array_append_val (self->segments, seg); - } - break; - - case GSK_PATH_CUBIC: - seg.p[0] = pts[0]; - seg.p[1] = pts[1]; - seg.p[2] = pts[2]; - seg.p[3] = pts[3]; - g_array_append_val (self->segments, seg); - break; - - case GSK_PATH_CONIC: - { - seg.p[0] = pts[0]; - seg.p[1] = pts[1]; - seg.p[3] = pts[2]; - seg.weight = weight; - - get_conic_shoulder_point (pts, weight, &seg.p[2]); - - g_array_append_val (self->segments, seg); - } - break; - - default: - g_assert_not_reached (); - } - - return TRUE; -} void curve_editor_set_path (CurveEditor *self, GskPath *path) { - int i; - Segment *first, *last; - - g_array_set_size (self->segments, 0); - - gsk_path_foreach (path, -1, copy_segments, self); - - first = get_segment (self, 0); - last = get_segment (self, self->segments->len - 1); - if (last->op == GSK_PATH_CLOSE) - { - if (graphene_point_near (&last->p[0], &last->p[3], 0.001)) - g_array_remove_index (self->segments, self->segments->len - 1); - else - last->op = GSK_PATH_LINE; - } - else - { - Segment seg; - - seg.op = GSK_PATH_MOVE; - seg.p[0] = last->p[3]; - seg.p[3] = first->p[0]; - g_array_append_val (self->segments, seg); - } - - for (i = 0; i < self->segments->len; i++) - check_smoothness (self, i); - + ce_path_set_gsk_path (self->path, path); gtk_widget_queue_draw (GTK_WIDGET (self)); } GskPath * curve_editor_get_path (CurveEditor *self) { - GskPathBuilder *builder; - - builder = gsk_path_builder_new (); - - curve_editor_add_path (self, builder); - - return gsk_path_builder_free_to_path (builder); + return ce_path_get_gsk_path (self->path); } void diff --git a/demos/gtk-demo/curve.c b/demos/gtk-demo/curve.c index 632e6458bf..3d1ddd5791 100644 --- a/demos/gtk-demo/curve.c +++ b/demos/gtk-demo/curve.c @@ -33,10 +33,12 @@ make_circle_path (void) gsk_path_builder_cubic_to (builder, cx - kr, h - pad, pad, cy + kr, pad, cy); +#if 0 gsk_path_builder_cubic_to (builder, pad, cy - kr, cx - kr, pad, cx, pad); gsk_path_builder_close (builder); +#endif return gsk_path_builder_free_to_path (builder); } diff --git a/demos/gtk-demo/meson.build b/demos/gtk-demo/meson.build index cfee5c3e21..5ff96ecef6 100644 --- a/demos/gtk-demo/meson.build +++ b/demos/gtk-demo/meson.build @@ -144,6 +144,7 @@ extra_demo_sources = files([ 'nodewidget.c', 'graphwidget.c', 'curve-editor.c', + 'cepath.c', ]) if os_unix diff --git a/gtk/gtkpath.c b/gtk/gtkpath.c new file mode 100644 index 0000000000..fa58493280 --- /dev/null +++ b/gtk/gtkpath.c @@ -0,0 +1,714 @@ +#include "config.h" +#include "gtkpath.h" +#include "gskcurveprivate.h" + +typedef struct +{ + graphene_point_t point; + float weight; +} PointWeight; + +typedef struct +{ + GskPathOperation op; + gsize idx; +} PathOp; + +struct _GtkPath +{ + GObject parent_instance; + + GArray *ops; + GArray *points; +}; + +struct _GtkPathClass +{ + GObjectClass parent_class; +}; + +G_DEFINE_TYPE (GtkPath, gtk_path, G_TYPE_OBJECT) + +static void +gtk_path_init (GtkPath *self) +{ + self->ops = g_array_new (FALSE, FALSE, sizeof (PathOp)); + self->points = g_array_new (FALSE, FALSE, sizeof (PointWeight)); +} + +static void +gtk_path_finalize (GObject *object) +{ + GtkPath *self = GTK_PATH (object); + + g_array_unref (self->ops); + g_array_unref (self->points); + + G_OBJECT_GET_CLASS (object)->finalize (object); +} + +static void +gtk_path_class_init (GtkPathClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + + object_class->finalize = gtk_path_finalize; +} + +GtkPath * +gtk_path_new (void) +{ + return g_object_new (GTK_TYPE_PATH, NULL); +} + +static gboolean +add_curve (GskPathOperation op, + const graphene_point_t *pts, + gsize n_pts, + float weight, + gpointer user_data) +{ + GtkPath *self = GTK_PATH (user_data); + + switch (op) + { + case GSK_PATH_MOVE: + g_array_append_val (self->ops, ((PathOp) { op, self->points->len })); + g_array_append_val (self->points, ((PointWeight) { pts[0], 1.f } )); + break; + + case GSK_PATH_CLOSE: + g_array_append_val (self->ops, ((PathOp) { op, self->points->len - 1 })); + break; + + case GSK_PATH_LINE: + g_array_append_val (self->ops, ((PathOp) { op, self->points->len - 1 })); + g_array_append_val (self->points, ((PointWeight) { pts[1], 1.f } )); + break; + + case GSK_PATH_QUAD: + g_array_append_val (self->ops, ((PathOp) { op, self->points->len - 1 })); + g_array_append_val (self->points, ((PointWeight) { pts[1], 1.f } )); + g_array_append_val (self->points, ((PointWeight) { pts[2], 1.f } )); + break; + + case GSK_PATH_CUBIC: + g_array_append_val (self->ops, ((PathOp) { op, self->points->len - 1 })); + g_array_append_val (self->points, ((PointWeight) { pts[1], 1.f } )); + g_array_append_val (self->points, ((PointWeight) { pts[2], 1.f } )); + g_array_append_val (self->points, ((PointWeight) { pts[3], 1.f } )); + break; + + case GSK_PATH_CONIC: + g_array_append_val (self->ops, ((PathOp) { op, self->points->len - 1 })); + g_array_append_val (self->points, ((PointWeight) { pts[1], weight } )); + g_array_append_val (self->points, ((PointWeight) { pts[2], 1.f } )); + break; + + default: + g_assert_not_reached (); + } + + return TRUE; +} + +static void +gtk_path_clear (GtkPath *self) +{ + g_array_set_size (self->ops, 0); + g_array_set_size (self->points, 0); +} + +void +gtk_path_set_gsk_path (GtkPath *self, + GskPath *path) +{ + g_return_if_fail (GTK_IS_PATH (self)); + g_return_if_fail (path != NULL); + + gtk_path_clear (self); + gsk_path_foreach (path, -1, add_curve, self); +} + +GskPath * +gtk_path_get_gsk_path (GtkPath *self) +{ + GskPathBuilder *builder; + + g_return_val_if_fail (GTK_IS_PATH (self), NULL); + + builder = gsk_path_builder_new (); + + for (gsize i = 0; i < self->ops->len; i++) + { + PathOp op = g_array_index (self->ops, PathOp, i); + PointWeight *p0, *p1, *p2; + switch (op.op) + { + case GSK_PATH_MOVE: + p0 = &g_array_index (self->points, PointWeight, op.idx); + gsk_path_builder_move_to (builder, p0->point.x, p0->point.y); + break; + + case GSK_PATH_CLOSE: + gsk_path_builder_close (builder); + break; + + case GSK_PATH_LINE: + p0 = &g_array_index (self->points, PointWeight, op.idx); + gsk_path_builder_line_to (builder, p0->point.x, p0->point.y); + break; + + case GSK_PATH_QUAD: + p0 = &g_array_index (self->points, PointWeight, op.idx); + p1 = &g_array_index (self->points, PointWeight, op.idx + 1); + gsk_path_builder_quad_to (builder, + p0->point.x, p0->point.y, + p1->point.x, p1->point.y); + break; + + case GSK_PATH_CUBIC: + p0 = &g_array_index (self->points, PointWeight, op.idx); + p1 = &g_array_index (self->points, PointWeight, op.idx + 1); + p2 = &g_array_index (self->points, PointWeight, op.idx + 2); + gsk_path_builder_cubic_to (builder, + p0->point.x, p0->point.y, + p1->point.x, p1->point.y, + p2->point.x, p2->point.y); + break; + + case GSK_PATH_CONIC: + p0 = &g_array_index (self->points, PointWeight, op.idx); + p1 = &g_array_index (self->points, PointWeight, op.idx + 1); + gsk_path_builder_conic_to (builder, + p0->point.x, p0->point.y, + p1->point.x, p1->point.y, + p0->weight); + break; + + default: + g_assert_not_reached (); + } + } + + return gsk_path_builder_free_to_path (builder); +} + +gsize +gtk_path_get_n_operations (GtkPath *self) +{ + g_return_val_if_fail (GTK_IS_PATH (self), 0); + + return self->ops->len; +} + +GskPathOperation +gtk_path_get_operation (GtkPath *self, + gsize idx) +{ + g_return_val_if_fail (GTK_IS_PATH (self), GSK_PATH_MOVE); + g_return_val_if_fail (idx < self->ops->len, GSK_PATH_MOVE); + + return g_array_index (self->ops, GskPathOperation, idx); +} + +#define OP_CHANGE(op1,op2) ((op1) | ((op2) << 16)) + +static void +shift_indices (GtkPath *self, + gsize from, + int shift) +{ + for (gsize i = from; i < self->ops->len; i++) + { + PathOp *op = &g_array_index (self->ops, PathOp, i); + op->idx += shift; + } +} + +static int +line_intersection (const graphene_point_t *a, + const graphene_point_t *b, + const graphene_point_t *c, + const graphene_point_t *d, + graphene_point_t *p) +{ + float a1 = b->y - a->y; + float b1 = a->x - b->x; + float c1 = a1*a->x + b1*a->y; + + float a2 = d->y - c->y; + float b2 = c->x - d->x; + float c2 = a2*c->x+ b2*c->y; + + float det = a1*b2 - a2*b1; + + if (fabs (det) < 0.001) + { + p->x = NAN; + p->y = NAN; + return 0; + } + else + { + p->x = (b2*c1 - b1*c2) / det; + p->y = (a1*c2 - a2*c1) / det; + return 1; + } +} + +static void +get_curve (GtkPath *self, gsize idx, GskCurve *curve) +{ + PathOp op = g_array_index (self->ops, PathOp, idx); + PointWeight *pw = &g_array_index (self->points, PointWeight, op.idx); + + switch (op.op) + { + case GSK_PATH_LINE: + gsk_curve_init_foreach (curve, + op.op, + (graphene_point_t[]) { pw[0].point, pw[1].point }, + 2, + 1.f); + break; + case GSK_PATH_QUAD: + gsk_curve_init_foreach (curve, + op.op, + (graphene_point_t[]) { pw[0].point, pw[1].point, pw[2].point }, + 3, + 1.f); + break; + case GSK_PATH_CUBIC: + gsk_curve_init_foreach (curve, + op.op, + (graphene_point_t[]) { pw[0].point, pw[1].point, pw[2].point, pw[3].point }, + 4, + 1.f); + break; + case GSK_PATH_CONIC: + gsk_curve_init_foreach (curve, + op.op, + (graphene_point_t[]) { pw[0].point, pw[1].point, pw[2].point }, + 3, + pw[1].weight); + break; + case GSK_PATH_MOVE: + case GSK_PATH_CLOSE: + default: + g_assert_not_reached (); + } +} + +void +gtk_path_set_operation (GtkPath *self, + gsize idx, + GskPathOperation op) +{ + PathOp *old; + const graphene_point_t *p0, *p1, *p2, *p3; + + g_return_if_fail (GTK_IS_PATH (self)); + g_return_if_fail (idx < self->ops->len); + + old = &g_array_index (self->ops, PathOp, idx); + + if (old->op == op) + return; + + /* Can't change the move, for now */ + if (old->op == GSK_PATH_MOVE) + return; + + switch (old->op) + { + case GSK_PATH_CLOSE: + p0 = p1 = &(g_array_index (self->points, PointWeight, old->idx).point); + for (gsize idx2 = idx; idx2 > 0; idx2--) + { + PathOp op2 = g_array_index (self->ops, PathOp, idx2 - 1); + if (op2.op == GSK_PATH_MOVE) + { + p1 = &(g_array_index (self->points, PointWeight, op2.idx).point); + break; + } + } + switch (op) + { + case GSK_PATH_LINE: + { + graphene_point_t q; + + /* Not putting the point quite on top */ + graphene_point_interpolate (p0, p1, 0.9, &q); + g_array_insert_val (self->points, old->idx + 1, ((PointWeight) { q, 1.f })); + shift_indices (self, idx + 1, 1); + old->op = op; + } + break; + + case GSK_PATH_QUAD: + case GSK_PATH_CONIC: + { + graphene_point_t q1, q2; + + /* Not putting the point quite on top */ + graphene_point_interpolate (p0, p1, 0.9, &q2); + graphene_point_interpolate (p0, &q1, 0.5, &q1); + g_array_insert_vals (self->points, old->idx + 1, + (PointWeight[]) { { q1, 1.f }, { q2, 1.f } }, + 2); + shift_indices (self, idx + 1, 2); + old->op = op; + } + break; + + case GSK_PATH_CUBIC: + { + graphene_point_t q1, q2, q3; + + /* Not putting the point quite on top */ + graphene_point_interpolate (p0, p1, 0.9, &q3); + graphene_point_interpolate (p0, &q3, 0.333, &q1); + graphene_point_interpolate (p0, &q3, 0.667, &q2); + g_array_insert_vals (self->points, old->idx + 1, + (PointWeight[]) { { q1, 1.f }, { q2, 1.f }, { q3, 1.f } }, + 3); + shift_indices (self, idx + 1, 3); + old->op = op; + } + break; + + case GSK_PATH_MOVE: + case GSK_PATH_CLOSE: + default: + g_assert_not_reached (); + } + break; + + case GSK_PATH_LINE: + p0 = &(g_array_index (self->points, PointWeight, old->idx).point); + p1 = &(g_array_index (self->points, PointWeight, old->idx + 1).point); + + switch (op) + { + case GSK_PATH_CLOSE: + break; + + case GSK_PATH_QUAD: + case GSK_PATH_CONIC: + { + graphene_point_t q; + + graphene_point_interpolate (p0, p1, 0.5, &q); + g_array_insert_val (self->points, old->idx + 1, ((PointWeight) { q, 1.f })); + shift_indices (self, idx + 1, 1); + old->op = op; + } + break; + + case GSK_PATH_CUBIC: + { + graphene_point_t q1, q2; + + graphene_point_interpolate (p0, p1, 0.333, &q1); + graphene_point_interpolate (p0, p1, 0.667, &q2); + + g_array_insert_vals (self->points, old->idx + 1, + (PointWeight[]) { { q1, 1.f }, { q2, 1.f } }, + 2); + shift_indices (self, idx + 1, 2); + old->op = op; + } + break; + + case GSK_PATH_LINE: + case GSK_PATH_MOVE: + default: + g_assert_not_reached (); + } + break; + + case GSK_PATH_QUAD: + case GSK_PATH_CONIC: + p0 = &(g_array_index (self->points, PointWeight, old->idx)).point; + p1 = &(g_array_index (self->points, PointWeight, old->idx + 1)).point; + p2 = &(g_array_index (self->points, PointWeight, old->idx + 2)).point; + + switch (op) + { + case GSK_PATH_QUAD: + case GSK_PATH_CONIC: + old->op = op; + break; + + case GSK_PATH_CUBIC: + { + graphene_point_t q1, q2; + + graphene_point_interpolate (p0, p1, 0.667, &q1); + graphene_point_interpolate (p1, p2, 0.333, &q2); + + g_array_insert_vals (self->points, old->idx + 1, + (PointWeight[]) { { q1, 1.f }, { q2, 1.f } }, + 2); + shift_indices (self, idx + 1, 2); + old->op = op; + } + break; + + case GSK_PATH_LINE: + g_array_remove_index (self->points, old->idx + 1); + shift_indices (self, idx + 1, -1); + old->op = op; + break; + + case GSK_PATH_MOVE: + case GSK_PATH_CLOSE: + default: + g_assert_not_reached (); + } + break; + + case GSK_PATH_CUBIC: + p0 = &(g_array_index (self->points, PointWeight, old->idx)).point; + p1 = &(g_array_index (self->points, PointWeight, old->idx + 1)).point; + p2 = &(g_array_index (self->points, PointWeight, old->idx + 2)).point; + p3 = &(g_array_index (self->points, PointWeight, old->idx + 3)).point; + + switch (op) + { + case GSK_PATH_CLOSE: + break; + + case GSK_PATH_LINE: + g_array_remove_range (self->points, old->idx + 1, 2); + shift_indices (self, idx + 1, -2); + old->op = op; + break; + + case GSK_PATH_QUAD: + case GSK_PATH_CONIC: + { + graphene_point_t q; + PointWeight *pw; + + if (!line_intersection (p0, p1, p2, p3, &q)) + graphene_point_interpolate (p0, p3, 0.5, &q); + g_array_remove_index (self->points, old->idx + 2); + pw = &g_array_index (self->points, PointWeight, old->idx + 1); + pw->point = q; + shift_indices (self, idx + 1, -1); + old->op = op; + } + break; + + case GSK_PATH_MOVE: + case GSK_PATH_CUBIC: + default: + g_assert_not_reached (); + } + break; + + case GSK_PATH_MOVE: + default: + g_assert_not_reached (); + } +} + +void +gtk_path_split_operation (GtkPath *self, + gsize idx, + float t) +{ + PathOp op; + GskCurve curve, c1, c2; + + g_return_if_fail (GTK_IS_PATH (self)); + g_return_if_fail (idx < self->ops->len); + g_return_if_fail (0 <= t && t <= 1); + + op = g_array_index (self->ops, PathOp, idx); + + if (op.op == GSK_PATH_MOVE || op.op == GSK_PATH_CLOSE) + return; /* Can't do these for now */ + + get_curve (self, idx, &curve); + gsk_curve_split (&curve, t, &c1, &c2); + + switch (op.op) + { + case GSK_PATH_LINE: + g_array_insert_val (self->points, op.idx + 1, + ((PointWeight) { c1.line.points[1], 1.f })); + shift_indices (self, idx + 1, 1); + g_array_insert_val (self->ops, idx + 1, ((PathOp) { op.op, op.idx + 1 })); + break; + + case GSK_PATH_QUAD: + g_array_remove_index (self->points, op.idx + 1); + g_array_insert_vals (self->points, op.idx + 1, + (PointWeight []) { { c1.quad.points[1], 1.f }, + { c1.quad.points[2], 1.f }, + { c2.quad.points[1], 1.f } }, + 3); + shift_indices (self, idx + 1, 2); + g_array_insert_val (self->ops, idx + 1, ((PathOp) { op.op, op.idx + 2 })); + break; + + case GSK_PATH_CUBIC: + g_array_remove_range (self->points, op.idx + 1, 2); + g_array_insert_vals (self->points, op.idx + 1, + (PointWeight []) { { c1.cubic.points[1], 1.f }, + { c1.cubic.points[2], 1.f }, + { c1.cubic.points[3], 1.f }, + { c2.cubic.points[1], 1.f }, + { c2.cubic.points[2], 1.f } }, + 5); + shift_indices (self, idx + 1, 3); + g_array_insert_val (self->ops, idx + 1, ((PathOp) { op.op, op.idx + 3 })); + break; + + case GSK_PATH_CONIC: + g_array_remove_index (self->points, op.idx + 1); + g_array_insert_vals (self->points, op.idx + 1, + (PointWeight []) { { c1.conic.points[1], c1.conic.points[2].x }, + { c1.conic.points[3], 1.f }, + { c2.conic.points[1], c2.conic.points[2].x } }, + 3); + shift_indices (self, idx + 1, 2); + g_array_insert_val (self->ops, idx + 1, ((PathOp) { op.op, op.idx + 2 })); + break; + + case GSK_PATH_MOVE: + case GSK_PATH_CLOSE: + default: + g_assert_not_reached (); + } +} + +void +gtk_path_remove_operation (GtkPath *self, + gsize idx) +{ + PathOp op; + + g_return_if_fail (GTK_IS_PATH (self)); + g_return_if_fail (idx < self->ops->len); + + op = g_array_index (self->ops, PathOp, idx); + + switch (op.op) + { + case GSK_PATH_MOVE: + /* Removing a MOVE is a no-op */ + return; + + case GSK_PATH_CLOSE: + /* No point point shuffling needed */ + g_array_remove_index (self->ops, idx); + return; + + case GSK_PATH_LINE: + g_array_remove_index (self->ops, idx); + g_array_remove_range (self->points, op.idx, 1); + shift_indices (self, idx, -1); + break; + + case GSK_PATH_QUAD: + case GSK_PATH_CONIC: + g_array_remove_index (self->ops, idx); + g_array_remove_range (self->points, op.idx, 2); + shift_indices (self, idx, -2); + break; + + case GSK_PATH_CUBIC: + g_array_remove_index (self->ops, idx); + g_array_remove_range (self->points, op.idx, 3); + shift_indices (self, idx, -3); + break; + + default: + g_assert_not_reached (); + } +} + +gsize +gtk_path_get_points_for_operation (GtkPath *self, + gsize idx) +{ + PathOp op; + + g_return_val_if_fail (GTK_IS_PATH (self), 0); + g_return_val_if_fail (idx < self->ops->len, 0); + + op = g_array_index (self->ops, PathOp, idx); + + return op.idx; +} + +gsize +gtk_path_get_n_points (GtkPath *self) +{ + g_return_val_if_fail (GTK_IS_PATH (self), 0); + + return self->points->len; +} + +void +gtk_path_get_point (GtkPath *self, + gsize idx, + graphene_point_t *point) +{ + PointWeight *p; + + g_return_if_fail (GTK_IS_PATH (self)); + g_return_if_fail (idx < self->points->len); + g_return_if_fail (point != NULL); + + p = &g_array_index (self->points, PointWeight, idx); + *point = p->point; +} + +void +gtk_path_set_point (GtkPath *self, + gsize idx, + const graphene_point_t *point) +{ + PointWeight *p; + + g_return_if_fail (GTK_IS_PATH (self)); + g_return_if_fail (idx < self->points->len); + g_return_if_fail (point != NULL); + + p = &g_array_index (self->points, PointWeight, idx); + p->point = *point; +} + +float +gtk_path_get_conic_weight (GtkPath *self, + gsize idx) +{ + PointWeight *p; + + g_return_val_if_fail (GTK_IS_PATH (self), 1.f); + g_return_val_if_fail (idx < self->points->len, 1.f); + + p = &g_array_index (self->points, PointWeight, idx); + return p->weight; +} + +void +gtk_path_set_conic_weight (GtkPath *self, + gsize idx, + float weight) +{ + PointWeight *p; + + g_return_if_fail (GTK_IS_PATH (self)); + g_return_if_fail (idx < self->points->len); + g_return_if_fail (weight > 0); + + p = &g_array_index (self->points, PointWeight, idx); + p->weight = weight; +} diff --git a/gtk/gtkpath.h b/gtk/gtkpath.h new file mode 100644 index 0000000000..cd517b97df --- /dev/null +++ b/gtk/gtkpath.h @@ -0,0 +1,73 @@ +#pragma once + +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_PATH (gtk_path_get_type ()) + +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (GtkPath, gtk_path, GTK, PATH, GObject) + +GDK_AVAILABLE_IN_ALL +GtkPath * gtk_path_new (void); + +GDK_AVAILABLE_IN_ALL +void gtk_path_set_gsk_path (GtkPath *self, + GskPath *gsk_path); + +GDK_AVAILABLE_IN_ALL +GskPath * gtk_path_get_gsk_path (GtkPath *self); + +GDK_AVAILABLE_IN_ALL +gsize gtk_path_get_n_operations (GtkPath *self); + +GDK_AVAILABLE_IN_ALL +GskPathOperation gtk_path_get_operation (GtkPath *self, + gsize idx); + +GDK_AVAILABLE_IN_ALL +void gtk_path_set_operation (GtkPath *self, + gsize idx, + GskPathOperation op); + +GDK_AVAILABLE_IN_ALL +void gtk_path_split_operation (GtkPath *self, + gsize idx, + float t); + +GDK_AVAILABLE_IN_ALL +void gtk_path_remove_operation (GtkPath *self, + gsize idx); + +GDK_AVAILABLE_IN_ALL +gsize gtk_path_get_points_for_operation (GtkPath *self, + gsize idx); +GDK_AVAILABLE_IN_ALL +gsize gtk_path_get_n_points (GtkPath *self); + +GDK_AVAILABLE_IN_ALL +void gtk_path_get_point (GtkPath *self, + gsize idx, + graphene_point_t *point); + +GDK_AVAILABLE_IN_ALL +void gtk_path_set_point (GtkPath *self, + gsize idx, + const graphene_point_t *point); + +GDK_AVAILABLE_IN_ALL +gsize gtk_path_insert_point (GtkPath *self, + gsize idx, + const graphene_point_t *point); + +GDK_AVAILABLE_IN_ALL +float gtk_path_get_conic_weight (GtkPath *self, + gsize idx); + +GDK_AVAILABLE_IN_ALL +void gtk_path_set_conic_weight (GtkPath *self, + gsize idx, + float weight); + +G_END_DECLS diff --git a/gtk/meson.build b/gtk/meson.build index 80ed4cae16..f9cd988894 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -15,6 +15,7 @@ gtk_cargs = [ # List of sources that do not contain public API, and should not be # introspected gtk_private_sources = files([ + 'gtkpath.c', 'fnmatch.c', 'gdktextureutils.c', 'gsettings-mapping.c',