Compare commits

...

5 Commits

Author SHA1 Message Date
Matthias Clasen
e16c836f60 curve2: Show osculating circles 2020-12-27 00:26:44 -05:00
Matthias Clasen
55bfc167e3 Add gsk_path_measure_get_curvature 2020-12-27 00:26:44 -05:00
Matthias Clasen
5516ec2bf0 Add an interactive path test
This one is for interactive exploring of svg paths.

You can enter an SVG path in the entry and hit Enter
to see how GSK renders it. If you click the button
in the headerbar, you can see what GTK thinks the
closest point, tangent and distance are wrt. to the
mouse position, and the bounding box of the path.

There's also stroke parameters to play with.
2020-12-27 00:26:44 -05:00
Matthias Clasen
7e7d1987a3 xxx: Make gsk_stroke_to_cairo public
It comes in handy, and does no harm.
2020-12-27 00:26:44 -05:00
Matthias Clasen
304c3055ad Add a path editor demo
Add a simple demo for editing a poly-Bezier curve.
2020-12-27 00:26:44 -05:00
12 changed files with 3836 additions and 3 deletions

View File

@@ -62,6 +62,10 @@ struct _GskContourClass
float distance,
graphene_point_t *pos,
graphene_vec2_t *tangent);
float (* get_curvature) (const GskContour *contour,
gpointer measure_data,
float distance,
graphene_point_t *center);
gboolean (* get_closest_point) (const GskContour *contour,
gpointer measure_data,
float tolerance,
@@ -301,6 +305,15 @@ gsk_rect_contour_get_point (const GskContour *contour,
graphene_vec2_init (tangent, 0.0f, - copysignf (self->height, 1.0f));
}
static float
gsk_rect_contour_get_curvature (const GskContour *contour,
gpointer measure_data,
float distance,
graphene_point_t *center)
{
return 0;
}
static gboolean
gsk_rect_contour_get_closest_point (const GskContour *contour,
gpointer measure_data,
@@ -564,6 +577,7 @@ static const GskContourClass GSK_RECT_CONTOUR_CLASS =
gsk_rect_contour_init_measure,
gsk_rect_contour_free_measure,
gsk_rect_contour_get_point,
gsk_rect_contour_get_curvature,
gsk_rect_contour_get_closest_point,
gsk_rect_contour_copy,
gsk_rect_contour_add_segment,
@@ -765,6 +779,20 @@ gsk_circle_contour_get_point (const GskContour *contour,
}
}
static float
gsk_circle_contour_get_curvature (const GskContour *contour,
gpointer measure_data,
float distance,
graphene_point_t *center)
{
const GskCircleContour *self = (const GskCircleContour *) contour;
if (center)
*center = self->center;
return 1 / self->radius;
}
static gboolean
gsk_circle_contour_get_closest_point (const GskContour *contour,
gpointer measure_data,
@@ -950,6 +978,7 @@ static const GskContourClass GSK_CIRCLE_CONTOUR_CLASS =
gsk_circle_contour_init_measure,
gsk_circle_contour_free_measure,
gsk_circle_contour_get_point,
gsk_circle_contour_get_curvature,
gsk_circle_contour_get_closest_point,
gsk_circle_contour_copy,
gsk_circle_contour_add_segment,
@@ -1286,6 +1315,38 @@ gsk_standard_contour_get_point (const GskContour *contour,
gsk_curve_get_tangent (&curve, progress, tangent);
}
static float
gsk_standard_contour_get_curvature (const GskContour *contour,
gpointer measure_data,
float distance,
graphene_point_t *center)
{
GskStandardContour *self = (GskStandardContour *) contour;
GArray *array = measure_data;
guint index;
float progress;
GskStandardContourMeasure *measure;
GskCurve curve;
if (array->len == 0)
{
g_assert (distance == 0);
g_assert (gsk_pathop_op (self->ops[0]) == GSK_PATH_MOVE);
return 0;
}
if (!g_array_binary_search (array, &distance, gsk_standard_contour_find_measure, &index))
index = array->len - 1;
measure = &g_array_index (array, GskStandardContourMeasure, index);
progress = (distance - measure->start) / (measure->end - measure->start);
progress = measure->start_progress + (measure->end_progress - measure->start_progress) * progress;
g_assert (progress >= 0 && progress <= 1);
gsk_curve_init (&curve, self->ops[measure->op]);
return gsk_curve_get_curvature (&curve, progress, center);
}
static gboolean
gsk_standard_contour_get_closest_point (const GskContour *contour,
gpointer measure_data,
@@ -1675,6 +1736,7 @@ static const GskContourClass GSK_STANDARD_CONTOUR_CLASS =
gsk_standard_contour_init_measure,
gsk_standard_contour_free_measure,
gsk_standard_contour_get_point,
gsk_standard_contour_get_curvature,
gsk_standard_contour_get_closest_point,
gsk_standard_contour_copy,
gsk_standard_contour_add_segment,
@@ -1803,6 +1865,15 @@ gsk_contour_get_point (const GskContour *self,
self->klass->get_point (self, measure_data, distance, pos, tangent);
}
float
gsk_contour_get_curvature (const GskContour *self,
gpointer measure_data,
float distance,
graphene_point_t *center)
{
return self->klass->get_curvature (self, measure_data, distance, center);
}
gboolean
gsk_contour_get_closest_point (const GskContour *self,
gpointer measure_data,

View File

@@ -74,6 +74,10 @@ void gsk_contour_get_point (const GskContou
float distance,
graphene_point_t *pos,
graphene_vec2_t *tangent);
float gsk_contour_get_curvature (const GskContour *self,
gpointer measure_data,
float distance,
graphene_point_t *center);
gboolean gsk_contour_get_closest_point (const GskContour *self,
gpointer measure_data,
float tolerance,

View File

@@ -377,6 +377,59 @@ gsk_path_measure_get_point (GskPathMeasure *self,
tangent);
}
/**
* @self: a #GskPathMeasure
* @distance: distance into the path
* @center: (out) (optional) (caller-allocates): The center
* of the osculating circle at the point
*
* Calculates the curvature at the point @distance units into
* the path. Optionally, returns the center of the osculating
* circle as well.
*
* If the curvature is infinite (at line segments), or does
* not exist (at sharp turns), zero is returned, and @center
* is not modified.
*
* Returns: The curvature of the path at the given point
*/
float
gsk_path_measure_get_curvature (GskPathMeasure *self,
float distance,
graphene_point_t *center)
{
gsize i;
g_return_val_if_fail (self != NULL, 0);
distance = gsk_path_measure_clamp_distance (self, distance);
for (i = self->first; i < self->last; i++)
{
if (distance < self->measures[i].length)
break;
distance -= self->measures[i].length;
}
/* weird corner cases */
if (i == self->last)
{
/* the empty path goes here */
if (self->first == self->last)
return 0;
/* rounding errors can make this happen */
i = self->last - 1;
distance = self->measures[i].length;
}
return gsk_contour_get_curvature (gsk_path_get_contour (self->path, i),
self->measures[i].contour_data,
distance,
center);
}
/**
* gsk_path_measure_get_closest_point:
* @self: a #GskPathMeasure

View File

@@ -65,6 +65,11 @@ void gsk_path_measure_get_point (GskPathMeasure
graphene_point_t *pos,
graphene_vec2_t *tangent);
GDK_AVAILABLE_IN_ALL
float gsk_path_measure_get_curvature (GskPathMeasure *self,
float distance,
graphene_point_t *center);
GDK_AVAILABLE_IN_ALL
float gsk_path_measure_get_closest_point (GskPathMeasure *self,
const graphene_point_t *point,
graphene_point_t *out_pos);

View File

@@ -111,6 +111,14 @@ gsk_stroke_free (GskStroke *self)
g_free (self);
}
/**
* gsk_stroke_to_cairo:
* @self: a #GskStroke
* @cr: the cairo context to configure
*
* A helper function that sets the stroke parameters
* of @cr from the values found in @self.
*/
void
gsk_stroke_to_cairo (const GskStroke *self,
cairo_t *cr)

View File

@@ -80,6 +80,10 @@ void gsk_stroke_set_dash_offset (GskStroke
GDK_AVAILABLE_IN_ALL
float gsk_stroke_get_dash_offset (const GskStroke *self);
GDK_AVAILABLE_IN_ALL
void gsk_stroke_to_cairo (const GskStroke *self,
cairo_t *cr);
G_END_DECLS

View File

@@ -54,9 +54,6 @@ gsk_stroke_clear (GskStroke *stroke)
stroke->n_dash = 0; /* better safe than sorry */
}
void gsk_stroke_to_cairo (const GskStroke *self,
cairo_t *cr);
G_END_DECLS
#endif /* __GSK_STROKE_PRIVATE_H__ */

2619
tests/curve-editor.c Normal file

File diff suppressed because it is too large Load Diff

38
tests/curve-editor.h Normal file
View File

@@ -0,0 +1,38 @@
#pragma once
#include <gtk/gtk.h>
G_BEGIN_DECLS
#define CURVE_TYPE_EDITOR (curve_editor_get_type ())
G_DECLARE_FINAL_TYPE (CurveEditor, curve_editor, CURVE, EDITOR, GtkWidget)
GtkWidget * curve_editor_new (void);
void curve_editor_set_edit (CurveEditor *self,
gboolean edit);
void curve_editor_set_path (CurveEditor *self,
GskPath *path);
GskPath * curve_editor_get_path (CurveEditor *self);
void curve_editor_set_stroke (CurveEditor *self,
GskStroke *stroke);
const GskStroke *
curve_editor_get_stroke (CurveEditor *self);
void curve_editor_set_color (CurveEditor *self,
GdkRGBA *color);
const GdkRGBA *
curve_editor_get_color (CurveEditor *self);
gboolean curve_editor_get_show_outline (CurveEditor *self);
void curve_editor_set_show_outline (CurveEditor *self,
gboolean show_outline);
G_END_DECLS

217
tests/curve.c Normal file
View File

@@ -0,0 +1,217 @@
#include <gtk/gtk.h>
#include "curve-editor.h"
static GskPath *
make_circle_path (void)
{
float w = 200;
float h = 200;
float cx = w / 2;
float cy = h / 2;
float pad = 20;
float r = (w - 2 * pad) / 2;
float k = 0.55228;
float kr = k * r;
GskPathBuilder *builder;
builder = gsk_path_builder_new ();
gsk_path_builder_move_to (builder, cx, pad);
gsk_path_builder_curve_to (builder, cx + kr, pad,
w - pad, cy - kr,
w - pad, cy);
gsk_path_builder_curve_to (builder, w - pad, cy + kr,
cx + kr, h - pad,
cx, h - pad);
gsk_path_builder_curve_to (builder, cx - kr, h - pad,
pad, cy + kr,
pad, cy);
gsk_path_builder_curve_to (builder, pad, cy - kr,
cx - kr, pad,
cx, pad);
gsk_path_builder_close (builder);
return gsk_path_builder_free_to_path (builder);
}
static void
edit_changed (GtkToggleButton *button,
GParamSpec *pspec,
CurveEditor *editor)
{
curve_editor_set_edit (editor, gtk_toggle_button_get_active (button));
}
static void
reset (GtkButton *button,
CurveEditor *editor)
{
GskPath *path;
path = make_circle_path ();
curve_editor_set_path (editor, path);
gsk_path_unref (path);
}
static void
line_width_changed (GtkSpinButton *spin,
CurveEditor *editor)
{
GskStroke *stroke;
stroke = gsk_stroke_copy (curve_editor_get_stroke (editor));
gsk_stroke_set_line_width (stroke, gtk_spin_button_get_value (spin));
curve_editor_set_stroke (editor, stroke);
gsk_stroke_free (stroke);
}
static void
cap_changed (GtkDropDown *combo,
GParamSpec *pspec,
CurveEditor *editor)
{
GskStroke *stroke;
stroke = gsk_stroke_copy (curve_editor_get_stroke (editor));
gsk_stroke_set_line_cap (stroke, (GskLineCap)gtk_drop_down_get_selected (combo));
curve_editor_set_stroke (editor, stroke);
gsk_stroke_free (stroke);
}
static void
join_changed (GtkDropDown *combo,
GParamSpec *pspec,
CurveEditor *editor)
{
GskStroke *stroke;
stroke = gsk_stroke_copy (curve_editor_get_stroke (editor));
gsk_stroke_set_line_join (stroke, (GskLineJoin)gtk_drop_down_get_selected (combo));
curve_editor_set_stroke (editor, stroke);
gsk_stroke_free (stroke);
}
static void
color_changed (GtkColorChooser *chooser,
CurveEditor *editor)
{
GdkRGBA color;
gtk_color_chooser_get_rgba (chooser, &color);
curve_editor_set_color (editor, &color);
}
static void
stroke_toggled (GtkCheckButton *button,
CurveEditor *editor)
{
curve_editor_set_show_outline (editor, gtk_check_button_get_active (button));
gtk_widget_queue_draw (GTK_WIDGET (editor));
}
static void
limit_changed (GtkSpinButton *spin,
CurveEditor *editor)
{
GskStroke *stroke;
stroke = gsk_stroke_copy (curve_editor_get_stroke (editor));
gsk_stroke_set_miter_limit (stroke, gtk_spin_button_get_value (spin));
curve_editor_set_stroke (editor, stroke);
gsk_stroke_free (stroke);
}
int
main (int argc, char *argv[])
{
GtkWindow *window;
GtkWidget *demo;
GtkWidget *edit_toggle;
GtkWidget *reset_button;
GtkWidget *titlebar;
GtkWidget *stroke_toggle;
GtkWidget *line_width_spin;
GtkWidget *stroke_button;
GtkWidget *popover;
GtkWidget *grid;
GtkWidget *cap_combo;
GtkWidget *join_combo;
GtkWidget *color_button;
GtkWidget *limit_spin;
gtk_init ();
window = GTK_WINDOW (gtk_window_new ());
gtk_window_set_default_size (GTK_WINDOW (window), 250, 250);
edit_toggle = gtk_toggle_button_new ();
gtk_button_set_icon_name (GTK_BUTTON (edit_toggle), "document-edit-symbolic");
reset_button = gtk_button_new_from_icon_name ("edit-undo-symbolic");
stroke_button = gtk_menu_button_new ();
gtk_menu_button_set_icon_name (GTK_MENU_BUTTON (stroke_button), "open-menu-symbolic");
popover = gtk_popover_new ();
gtk_menu_button_set_popover (GTK_MENU_BUTTON (stroke_button), popover);
grid = gtk_grid_new ();
gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
gtk_popover_set_child (GTK_POPOVER (popover), grid);
gtk_grid_attach (GTK_GRID (grid), gtk_label_new ("Color:"), 0, 0, 1, 1);
color_button = gtk_color_button_new_with_rgba (&(GdkRGBA){ 0., 0., 0., 1.});
gtk_grid_attach (GTK_GRID (grid), color_button, 1, 0, 1, 1);
gtk_grid_attach (GTK_GRID (grid), gtk_label_new ("Line width:"), 0, 1, 1, 1);
line_width_spin = gtk_spin_button_new_with_range (1, 20, 1);
gtk_spin_button_set_value (GTK_SPIN_BUTTON (line_width_spin), 1);
gtk_grid_attach (GTK_GRID (grid), line_width_spin, 1, 1, 1, 1);
gtk_grid_attach (GTK_GRID (grid), gtk_label_new ("Line cap:"), 0, 2, 1, 1);
cap_combo = gtk_drop_down_new_from_strings ((const char *[]){"Butt", "Round", "Square", NULL});
gtk_grid_attach (GTK_GRID (grid), cap_combo, 1, 2, 1, 1);
gtk_grid_attach (GTK_GRID (grid), gtk_label_new ("Line join:"), 0, 3, 1, 1);
join_combo = gtk_drop_down_new_from_strings ((const char *[]){"Miter", "Miter-clip", "Round", "Bevel", NULL});
gtk_grid_attach (GTK_GRID (grid), join_combo, 1, 3, 1, 1);
gtk_grid_attach (GTK_GRID (grid), gtk_label_new ("Miter limit:"), 0, 4, 1, 1);
limit_spin = gtk_spin_button_new_with_range (0, 10, 1);
gtk_spin_button_set_digits (GTK_SPIN_BUTTON (limit_spin), 1);
gtk_spin_button_set_value (GTK_SPIN_BUTTON (limit_spin), 4);
gtk_grid_attach (GTK_GRID (grid), limit_spin, 1, 4, 1, 1);
stroke_toggle = gtk_check_button_new_with_label ("Show outline");
gtk_grid_attach (GTK_GRID (grid), stroke_toggle, 1, 5, 1, 1);
titlebar = gtk_header_bar_new ();
gtk_header_bar_pack_start (GTK_HEADER_BAR (titlebar), edit_toggle);
gtk_header_bar_pack_start (GTK_HEADER_BAR (titlebar), reset_button);
gtk_header_bar_pack_start (GTK_HEADER_BAR (titlebar), stroke_button);
gtk_window_set_titlebar (GTK_WINDOW (window), titlebar);
demo = curve_editor_new ();
g_signal_connect (stroke_toggle, "toggled", G_CALLBACK (stroke_toggled), demo);
g_signal_connect (edit_toggle, "notify::active", G_CALLBACK (edit_changed), demo);
g_signal_connect (reset_button, "clicked", G_CALLBACK (reset), demo);
g_signal_connect (cap_combo, "notify::selected", G_CALLBACK (cap_changed), demo);
g_signal_connect (join_combo, "notify::selected", G_CALLBACK (join_changed), demo);
g_signal_connect (color_button, "color-set", G_CALLBACK (color_changed), demo);
g_signal_connect (line_width_spin, "value-changed", G_CALLBACK (line_width_changed), demo);
g_signal_connect (limit_spin, "value-changed", G_CALLBACK (limit_changed), demo);
reset (NULL, CURVE_EDITOR (demo));
gtk_window_set_child (window, demo);
gtk_window_present (window);
while (g_list_model_get_n_items (gtk_window_get_toplevels ()) > 0)
g_main_context_iteration (NULL, TRUE);
return 0;
}

815
tests/curve2.c Normal file
View File

@@ -0,0 +1,815 @@
#include <gtk/gtk.h>
#define DEMO_TYPE_WIDGET (demo_widget_get_type ())
G_DECLARE_FINAL_TYPE (DemoWidget, demo_widget, DEMO, WIDGET, GtkWidget)
struct _DemoWidget
{
GtkWidget parent_instance;
GskPath *orig_path;
GskPath *path;
GskPathMeasure *measure;
graphene_point_t point;
graphene_point_t point2;
graphene_vec2_t tangent;
double start, end;
float curvature;
graphene_point_t center;
gboolean track;
gboolean show_bounding_box;
GtkWidget *label;
gboolean do_stroke;
gboolean do_offset;
GskPath *stroke_path;
GskPathMeasure *stroke_measure;
GskStroke *stroke;
GskPath *outline_path;
GskStroke *outline_stroke;
gboolean inside;
GskFillRule fill_rule;
gboolean do_fill;
graphene_rect_t bounds;
gboolean show_cairo;
double zoom;
};
struct _DemoWidgetClass
{
GtkWidgetClass parent_class;
};
G_DEFINE_TYPE (DemoWidget, demo_widget, GTK_TYPE_WIDGET)
static void
motion (GtkEventControllerMotion *controller,
double x,
double y,
DemoWidget *self)
{
if (self->track)
{
float distance;
char *text;
float t;
gsk_path_measure_get_closest_point_full (self->measure,
&GRAPHENE_POINT_INIT (x, y),
INFINITY,
&distance,
&self->point,
&t,
&self->tangent);
gsk_path_measure_get_point (self->measure, t, &self->point2, NULL);
self->curvature = gsk_path_measure_get_curvature (self->measure, t, &self->center);
text = g_strdup_printf ("%.1f", distance);
gtk_label_set_label (GTK_LABEL (self->label), text);
gtk_widget_queue_draw (GTK_WIDGET (self));
}
if (self->do_stroke && self->do_fill)
{
gboolean inside = TRUE;
inside = gsk_path_measure_in_fill (self->stroke_measure, &GRAPHENE_POINT_INIT (x, y), self->fill_rule);
if (self->inside != inside)
{
self->inside = inside;
gtk_widget_queue_draw (GTK_WIDGET (self));
}
}
}
static void
demo_widget_init (DemoWidget *self)
{
GtkEventController *controller;
self->start = 0;
self->end = 1;
self->label = gtk_label_new ("");
gtk_widget_set_parent (self->label, GTK_WIDGET (self));
gtk_widget_set_halign (self->label, GTK_ALIGN_END);
gtk_widget_set_valign (self->label, GTK_ALIGN_START);
controller = gtk_event_controller_motion_new ();
g_signal_connect (controller, "motion", G_CALLBACK (motion), self);
gtk_widget_add_controller (GTK_WIDGET (self), controller);
}
static void
demo_widget_snapshot (GtkWidget *widget,
GtkSnapshot *snapshot)
{
DemoWidget *self = DEMO_WIDGET (widget);
int width, height;
GskStroke *stroke;
GskPathBuilder *builder;
GskPath *path;
graphene_rect_t bounds;
if (!self->path)
return;
gtk_snapshot_save (snapshot);
gtk_snapshot_scale (snapshot, self->zoom, self->zoom);
width = gtk_widget_get_width (widget);
height = gtk_widget_get_width (widget);
if (self->do_stroke)
{
if (self->show_cairo)
{
cairo_t *cr;
graphene_rect_init (&bounds, 0, 0, width, height);
cr = gtk_snapshot_append_cairo (snapshot, &bounds);
gsk_path_to_cairo (self->path, cr);
gsk_stroke_to_cairo (self->stroke, cr);
cairo_set_source_rgba (cr, 0, 0, 1, 0.2);
cairo_stroke (cr);
cairo_destroy (cr);
}
if (self->do_fill && self->inside)
{
gtk_snapshot_push_fill (snapshot, self->stroke_path, self->fill_rule);
gtk_snapshot_append_color (snapshot,
&(GdkRGBA){ 1, 0, 1, 0.3},
&GRAPHENE_RECT_INIT (0, 0, width, height ));
gtk_snapshot_pop (snapshot);
}
gtk_snapshot_push_stroke (snapshot, self->outline_path, self->outline_stroke);
gtk_snapshot_append_color (snapshot,
&(GdkRGBA){ 0, 0, 0, 0.2},
&GRAPHENE_RECT_INIT (0, 0, width, height ));
gtk_snapshot_pop (snapshot);
stroke = gsk_stroke_new (1);
gtk_snapshot_push_stroke (snapshot, self->path, stroke);
gsk_stroke_free (stroke);
gtk_snapshot_append_color (snapshot,
&(GdkRGBA){ 0, 0, 0, 0.3},
&GRAPHENE_RECT_INIT (0, 0, width, height ));
gtk_snapshot_pop (snapshot);
}
else if (self->do_offset)
{
for (int i = -3; i < 4; i++)
{
path = gsk_path_offset (self->path,
5 * i,
gsk_stroke_get_line_join (self->stroke),
gsk_stroke_get_miter_limit (self->stroke));
gtk_snapshot_push_stroke (snapshot, path, self->outline_stroke);
gtk_snapshot_append_color (snapshot,
&(GdkRGBA){ 0, 0, 0, 1},
&GRAPHENE_RECT_INIT (0, 0, width, height ));
gtk_snapshot_pop (snapshot);
gsk_path_unref (path);
}
}
else
{
gtk_snapshot_push_stroke (snapshot, self->path, self->outline_stroke);
gtk_snapshot_append_color (snapshot,
&(GdkRGBA){ 0, 0, 0, 1},
&GRAPHENE_RECT_INIT (0, 0, width, height ));
gtk_snapshot_pop (snapshot);
}
if (self->show_bounding_box)
{
if (gsk_path_get_bounds (self->do_stroke ? self->outline_path : self->path, &bounds))
{
builder = gsk_path_builder_new ();
gsk_path_builder_add_rect (builder, &bounds);
path = gsk_path_builder_free_to_path (builder);
stroke = gsk_stroke_new (1.0);
gtk_snapshot_push_stroke (snapshot, path, stroke);
gsk_stroke_free (stroke);
gtk_snapshot_append_color (snapshot,
&(GdkRGBA){ 0, 0, 0, 0.5},
&GRAPHENE_RECT_INIT (0, 0, width, height ));
gtk_snapshot_pop (snapshot);
gsk_path_unref (path);
}
}
if (self->track)
{
graphene_point_t p;
p.x = self->point.x + graphene_vec2_get_x (&self->tangent) * 40;
p.y = self->point.y + graphene_vec2_get_y (&self->tangent) * 40;
builder = gsk_path_builder_new ();
gsk_path_builder_move_to (builder, self->point.x, self->point.y);
gsk_path_builder_line_to (builder, p.x, p.y);
path = gsk_path_builder_free_to_path (builder);
stroke = gsk_stroke_new (1.0);
gtk_snapshot_push_stroke (snapshot, path, stroke);
gsk_stroke_free (stroke);
gtk_snapshot_append_color (snapshot,
&(GdkRGBA){ 0, 0, 0, 1},
&GRAPHENE_RECT_INIT (0, 0, width, height ));
gtk_snapshot_pop (snapshot);
gsk_path_unref (path);
builder = gsk_path_builder_new ();
gsk_path_builder_add_circle (builder, &self->point, 5);
gsk_path_builder_add_circle (builder, &p, 2.5);
gsk_path_builder_add_circle (builder, &self->point2, 5);
path = gsk_path_builder_free_to_path (builder);
gtk_snapshot_push_fill (snapshot, path, 0);
gtk_snapshot_append_color (snapshot,
&(GdkRGBA){ 1, 0, 0, 1},
&GRAPHENE_RECT_INIT (0, 0, width, height ));
gtk_snapshot_pop (snapshot);
stroke = gsk_stroke_new (1.0);
gtk_snapshot_push_stroke (snapshot, path, stroke);
gsk_stroke_free (stroke);
gtk_snapshot_append_color (snapshot,
&(GdkRGBA){ 0, 0, 0, 1},
&GRAPHENE_RECT_INIT (0, 0, width, height ));
gtk_snapshot_pop (snapshot);
gsk_path_unref (path);
if (self->curvature != 0)
{
builder = gsk_path_builder_new ();
gsk_path_builder_add_circle (builder, &self->center, fabs (1/self->curvature));
gsk_path_builder_add_circle (builder, &self->center, 3);
path = gsk_path_builder_free_to_path (builder);
stroke = gsk_stroke_new (1.0);
gtk_snapshot_push_stroke (snapshot, path, stroke);
gsk_stroke_free (stroke);
gtk_snapshot_append_color (snapshot,
&(GdkRGBA){ 1, 0, 1, 1},
&GRAPHENE_RECT_INIT (0, 0, width, height ));
gtk_snapshot_pop (snapshot);
}
gtk_widget_snapshot_child (widget, self->label, snapshot);
}
gtk_snapshot_restore (snapshot);
}
static void
demo_widget_measure (GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline)
{
DemoWidget *self = DEMO_WIDGET (widget);
float size;
if (orientation == GTK_ORIENTATION_HORIZONTAL)
size = self->zoom * (self->bounds.origin.x + self->bounds.size.width);
else
size = self->zoom * (self->bounds.origin.y + self->bounds.size.height);
*minimum = *natural = (int)size;
}
static void
demo_widget_size_allocate (GtkWidget *widget,
int width,
int height,
int baseline)
{
DemoWidget *self = DEMO_WIDGET (widget);
GtkRequisition min, nat;
gtk_widget_get_preferred_size (self->label, &min, &nat);
gtk_widget_size_allocate (self->label,
&(GtkAllocation) { width - nat.width, 0, nat.width, nat.height},
-1);
}
static void
demo_widget_dispose (GObject *object)
{
DemoWidget *self = DEMO_WIDGET (object);
g_clear_pointer (&self->path, gsk_path_unref);
g_clear_pointer (&self->measure, gsk_path_measure_unref);
g_clear_pointer (&self->stroke_path, gsk_path_unref);
g_clear_pointer (&self->stroke_measure, gsk_path_measure_unref);
g_clear_pointer (&self->stroke, gsk_stroke_free);
g_clear_pointer (&self->outline_path, gsk_path_unref);
g_clear_pointer (&self->outline_stroke, gsk_stroke_free);
g_clear_pointer (&self->label, gtk_widget_unparent);
G_OBJECT_CLASS (demo_widget_parent_class)->dispose (object);
}
static void
demo_widget_class_init (DemoWidgetClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
object_class->dispose = demo_widget_dispose;
widget_class->snapshot = demo_widget_snapshot;
widget_class->measure = demo_widget_measure;
widget_class->size_allocate = demo_widget_size_allocate;
}
static GtkWidget *
demo_widget_new (void)
{
return g_object_new (DEMO_TYPE_WIDGET, NULL);
}
static void
update_outline_path (DemoWidget *self)
{
if (self->stroke_path)
{
g_clear_pointer (&self->outline_path, gsk_path_unref);
self->outline_path = gsk_path_ref (self->stroke_path);
gtk_widget_queue_draw (GTK_WIDGET (self));
}
}
static void
update_stroke_path (DemoWidget *self)
{
g_clear_pointer (&self->stroke_path, gsk_path_unref);
g_clear_pointer (&self->stroke_measure, gsk_path_measure_unref);
if (self->do_stroke)
{
self->stroke_path = gsk_path_stroke (self->path, self->stroke);
self->stroke_measure = gsk_path_measure_new (self->stroke_path);
update_outline_path (self);
}
gsk_path_get_bounds (self->orig_path, &self->bounds);
if (self->stroke_path)
{
graphene_rect_t b;
gsk_path_get_bounds (self->stroke_path, &b);
graphene_rect_union (&b, &self->bounds, &self->bounds);
}
gtk_widget_queue_resize (GTK_WIDGET (self));
}
static void
update_path (DemoWidget *self)
{
g_clear_pointer (&self->path, gsk_path_unref);
g_clear_pointer (&self->measure, gsk_path_measure_unref);
if (self->start > 0 || self->end < 1)
{
GskPathMeasure *measure;
GskPathBuilder *builder;
float length;
measure = gsk_path_measure_new (self->orig_path);
length = gsk_path_measure_get_length (measure);
builder = gsk_path_builder_new ();
gsk_path_builder_add_segment (builder, measure, self->start * length, self->end * length);
self->path = gsk_path_builder_free_to_path (builder);
gsk_path_measure_unref (measure);
}
else
self->path = gsk_path_ref (self->orig_path);
self->measure = gsk_path_measure_new (self->path);
update_stroke_path (self);
}
static void
demo_widget_set_path (DemoWidget *self,
GskPath *path)
{
g_clear_pointer (&self->orig_path, gsk_path_unref);
self->orig_path = gsk_path_ref (path);
update_path (self);
}
static void
activate (GtkEntry *entry,
DemoWidget *demo)
{
GskPath *path;
path = gsk_path_parse (gtk_editable_get_text (GTK_EDITABLE (entry)));
if (path)
{
demo_widget_set_path (demo, path);
gsk_path_unref (path);
}
}
static void
init_demo (DemoWidget *demo,
GtkEditable *editable)
{
GskPathBuilder *builder;
GskPath *path;
char *string;
builder = gsk_path_builder_new ();
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (150, 150), 100);
gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (100, 100, 100, 100));
gsk_path_builder_move_to (builder, 300, 150);
gsk_path_builder_curve_to (builder, 300, 50, 400, 50, 400, 150);
gsk_path_builder_curve_to (builder, 400, 250, 500, 250, 500, 150);
gsk_path_builder_line_to (builder, 600, 150);
gsk_path_builder_line_to (builder, 530, 190);
path = gsk_path_builder_free_to_path (builder);
demo_widget_set_path (demo, path);
string = gsk_path_to_string (path);
gtk_editable_set_text (editable, string);
g_free (string);
gsk_path_unref (path);
demo->stroke = gsk_stroke_new (20);
demo->outline_stroke = gsk_stroke_new (1);
demo->zoom = 1;
}
static void
zoom_changed (GtkRange *range,
DemoWidget *self)
{
self->zoom = gtk_range_get_value (range);
gtk_widget_queue_resize (GTK_WIDGET (self));
}
static void
track_toggled (GtkCheckButton *button,
DemoWidget *self)
{
self->track = gtk_check_button_get_active (button);
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
bb_toggled (GtkCheckButton *button,
DemoWidget *self)
{
self->show_bounding_box = gtk_check_button_get_active (button);
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
cairo_toggled (GtkCheckButton *button,
DemoWidget *self)
{
self->show_cairo = gtk_check_button_get_active (button);
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
stroke_toggled (GtkCheckButton *button,
DemoWidget *self)
{
self->do_stroke = gtk_check_button_get_active (button);
update_stroke_path (self);
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
offset_toggled (GtkCheckButton *button,
DemoWidget *self)
{
self->do_offset = gtk_check_button_get_active (button);
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
fill_toggled (GtkCheckButton *button,
DemoWidget *self)
{
self->do_fill = gtk_check_button_get_active (button);
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static GtkWidget *start_scale;
static GtkWidget *end_scale;
static void
range_changed (GtkRange *range,
DemoWidget *self)
{
double start, end;
if (range == GTK_RANGE (start_scale))
{
start = gtk_range_get_value (range);
end = MAX (start, gtk_range_get_value (GTK_RANGE (end_scale)));
gtk_range_set_value (GTK_RANGE (end_scale), end);
}
else
{
end = gtk_range_get_value (range);
start = MIN (end, gtk_range_get_value (GTK_RANGE (start_scale)));
gtk_range_set_value (GTK_RANGE (start_scale), start);
}
self->start = start;
self->end = end;
update_path (self);
}
static void
fill_rule_changed (GtkDropDown *combo,
GParamSpec *pspec,
DemoWidget *self)
{
self->fill_rule = (GskFillRule)gtk_drop_down_get_selected (combo);
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
cap_changed (GtkDropDown *combo,
GParamSpec *pspec,
DemoWidget *self)
{
gsk_stroke_set_line_cap (self->stroke, (GskLineCap)gtk_drop_down_get_selected (combo));
update_stroke_path (self);
}
static void
join_changed (GtkDropDown *combo,
GParamSpec *pspec,
DemoWidget *self)
{
gsk_stroke_set_line_join (self->stroke, (GskLineJoin)gtk_drop_down_get_selected (combo));
update_stroke_path (self);
}
static void
limit_changed (GtkSpinButton *spin,
DemoWidget *self)
{
gsk_stroke_set_miter_limit (self->stroke, gtk_spin_button_get_value (spin));
update_stroke_path (self);
}
static void
stroke_width_changed (GtkSpinButton *spin,
DemoWidget *self)
{
gsk_stroke_set_line_width (self->stroke, gtk_spin_button_get_value (spin));
update_stroke_path (self);
}
static void
line_width_changed (GtkSpinButton *spin,
DemoWidget *self)
{
gsk_stroke_set_line_width (self->outline_stroke, gtk_spin_button_get_value (spin));
update_outline_path (self);
}
static void
dash_changed (GtkEntry *entry,
DemoWidget *self)
{
const char *text;
char **s;
float dash[20];
int i;
text = gtk_editable_get_text (GTK_EDITABLE (entry));
s = g_strsplit (text, " ", -1);
for (i = 0; s[i]; i++)
{
char *end;
float f;
f = g_strtod (s[i], &end);
if (*end != '\0')
return;
dash[i] = f;
}
g_strfreev (s);
gsk_stroke_set_dash (self->stroke, dash, i);
update_stroke_path (self);
}
static void
dash_offset_changed (GtkSpinButton *spin,
DemoWidget *self)
{
gsk_stroke_set_dash_offset (self->stroke, gtk_spin_button_get_value (spin));
update_stroke_path (self);
}
int
main (int argc, char *argv[])
{
GtkWidget *window, *box, *demo, *entry;
GtkWidget *popover, *button, *grid;
GtkWidget *header, *toggle, *toggle2, *toggle3;
GtkWidget *combo, *spin, *sw, *hbox;
GtkWidget *zoom_scale;
int row;
gtk_init ();
window = gtk_window_new ();
gtk_window_set_default_size (GTK_WINDOW (window), 700, 500);
box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
gtk_window_set_child (GTK_WINDOW (window), box);
demo = demo_widget_new ();
gtk_widget_set_hexpand (demo, TRUE);
gtk_widget_set_vexpand (demo, TRUE);
sw = gtk_scrolled_window_new ();
gtk_box_append (GTK_BOX (box), sw);
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), demo);
header = gtk_header_bar_new ();
button = gtk_menu_button_new ();
gtk_menu_button_set_icon_name (GTK_MENU_BUTTON (button), "emblem-system-symbolic");
gtk_header_bar_pack_start (GTK_HEADER_BAR (header), button);
zoom_scale = gtk_scale_new_with_range (GTK_ORIENTATION_HORIZONTAL, 1, 20, 0.25);
gtk_range_set_value (GTK_RANGE (zoom_scale), 1);
g_signal_connect (zoom_scale, "value-changed", G_CALLBACK (zoom_changed), demo);
gtk_widget_set_size_request (zoom_scale, 150, -1);
gtk_header_bar_pack_end (GTK_HEADER_BAR (header), zoom_scale);
gtk_window_set_titlebar (GTK_WINDOW (window), header);
popover = gtk_popover_new ();
gtk_menu_button_set_popover (GTK_MENU_BUTTON (button), popover);
grid = gtk_grid_new ();
gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
gtk_popover_set_child (GTK_POPOVER (popover), grid);
row = 0;
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
toggle = gtk_check_button_new_with_label ("Plain");
gtk_check_button_set_active (GTK_CHECK_BUTTON (toggle), TRUE);
gtk_box_append (GTK_BOX (hbox), toggle);
gtk_widget_set_hexpand (toggle, TRUE);
toggle2 = gtk_check_button_new_with_label ("Stroke");
gtk_widget_set_hexpand (toggle2, TRUE);
gtk_box_append (GTK_BOX (hbox), toggle2);
gtk_check_button_set_group (GTK_CHECK_BUTTON (toggle2), GTK_CHECK_BUTTON (toggle));
g_signal_connect (toggle2, "toggled", G_CALLBACK (stroke_toggled), demo);
toggle3 = gtk_check_button_new_with_label ("Offset");
gtk_widget_set_hexpand (toggle3, TRUE);
gtk_box_append (GTK_BOX (hbox), toggle3);
gtk_check_button_set_group (GTK_CHECK_BUTTON (toggle3), GTK_CHECK_BUTTON (toggle));
g_signal_connect (toggle3, "toggled", G_CALLBACK (offset_toggled), demo);
gtk_grid_attach (GTK_GRID (grid), hbox, 0, row++, 2, 1);
gtk_grid_attach (GTK_GRID (grid), gtk_label_new ("Fill rule"), 0, row, 1, 1);
combo = gtk_drop_down_new_from_strings ((const char *[]){"Winding", "Even-Odd", NULL });
g_signal_connect (combo, "notify::selected", G_CALLBACK (fill_rule_changed), demo);
gtk_grid_attach (GTK_GRID (grid), combo, 1, row++, 1, 1);
gtk_grid_attach (GTK_GRID (grid), gtk_label_new ("Line cap:"), 0, row, 1, 1);
combo = gtk_drop_down_new_from_strings ((const char *[]){"Butt", "Round", "Square", NULL});
g_signal_connect (combo, "notify::selected", G_CALLBACK (cap_changed), demo);
gtk_grid_attach (GTK_GRID (grid), combo, 1, row++, 1, 1);
gtk_grid_attach (GTK_GRID (grid), gtk_label_new ("Line join:"), 0, row, 1, 1);
combo = gtk_drop_down_new_from_strings ((const char *[]){"Miter", "Miter-clip", "Round", "Bevel", "Arcs", NULL});
g_signal_connect (combo, "notify::selected", G_CALLBACK (join_changed), demo);
gtk_grid_attach (GTK_GRID (grid), combo, 1, row++, 1, 1);
gtk_grid_attach (GTK_GRID (grid), gtk_label_new ("Miter limit:"), 0, row, 1, 1);
spin = gtk_spin_button_new_with_range (0, 10, 1);
gtk_spin_button_set_digits (GTK_SPIN_BUTTON (spin), 1);
gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin), 4);
g_signal_connect (spin, "value-changed", G_CALLBACK (limit_changed), demo);
gtk_grid_attach (GTK_GRID (grid), spin, 1, row++, 1, 1);
gtk_grid_attach (GTK_GRID (grid), gtk_label_new ("Stroke width:"), 0, row, 1, 1);
spin = gtk_spin_button_new_with_range (1, 40, 1);
gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin), 20);
g_signal_connect (spin, "value-changed", G_CALLBACK (stroke_width_changed), demo);
gtk_grid_attach (GTK_GRID (grid), spin, 1, row++, 1, 1);
gtk_grid_attach (GTK_GRID (grid), gtk_label_new ("Line width:"), 0, row, 1, 1);
spin = gtk_spin_button_new_with_range (1, 20, 1);
gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin), 1);
g_signal_connect (spin, "value-changed", G_CALLBACK (line_width_changed), demo);
gtk_grid_attach (GTK_GRID (grid), spin, 1, row++, 1, 1);
gtk_grid_attach (GTK_GRID (grid), gtk_label_new ("Dash pattern:"), 0, row, 1, 1);
entry = gtk_entry_new ();
g_signal_connect (entry, "changed", G_CALLBACK (dash_changed), demo);
gtk_grid_attach (GTK_GRID (grid), entry, 1, row++, 1, 1);
gtk_grid_attach (GTK_GRID (grid), gtk_label_new ("Dash offset:"), 0, row, 1, 1);
spin = gtk_spin_button_new_with_range (-1000, 1000, 10);
gtk_spin_button_set_digits (GTK_SPIN_BUTTON (spin), 1);
gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin), 0);
g_signal_connect (spin, "value-changed", G_CALLBACK (dash_offset_changed), demo);
gtk_grid_attach (GTK_GRID (grid), spin, 1, row++, 1, 1);
toggle = gtk_check_button_new_with_label ("Show closest point");
g_signal_connect (toggle, "toggled", G_CALLBACK (track_toggled), demo);
gtk_grid_attach (GTK_GRID (grid), toggle, 1, row++, 1, 1);
toggle = gtk_check_button_new_with_label ("Show bounding box");
g_signal_connect (toggle, "toggled", G_CALLBACK (bb_toggled), demo);
gtk_grid_attach (GTK_GRID (grid), toggle, 1, row++, 1, 1);
toggle = gtk_check_button_new_with_label ("Highlight on hover");
g_signal_connect (toggle, "toggled", G_CALLBACK (fill_toggled), demo);
gtk_grid_attach (GTK_GRID (grid), toggle, 1, row++, 1, 1);
toggle = gtk_check_button_new_with_label ("Compare cairo stroke");
g_signal_connect (toggle, "toggled", G_CALLBACK (cairo_toggled), demo);
gtk_grid_attach (GTK_GRID (grid), toggle, 1, row++, 1, 1);
entry = gtk_entry_new ();
g_signal_connect (entry, "activate", G_CALLBACK (activate), demo);
gtk_box_append (GTK_BOX (box), entry);
start_scale = gtk_scale_new_with_range (GTK_ORIENTATION_HORIZONTAL, 0, 1, 0.01);
g_signal_connect (start_scale, "value-changed", G_CALLBACK (range_changed), demo);
gtk_box_append (GTK_BOX (box), start_scale);
end_scale = gtk_scale_new_with_range (GTK_ORIENTATION_HORIZONTAL, 0, 1, 0.01);
gtk_range_set_value (GTK_RANGE (end_scale), 1);
g_signal_connect (end_scale, "value-changed", G_CALLBACK (range_changed), demo);
gtk_box_append (GTK_BOX (box), end_scale);
init_demo (DEMO_WIDGET (demo), GTK_EDITABLE (entry));
gtk_window_present (GTK_WINDOW (window));
while (g_list_model_get_n_items (gtk_window_get_toplevels ()) > 0)
g_main_context_iteration (NULL, TRUE);
return 0;
}

View File

@@ -5,6 +5,8 @@ gtk_tests = [
['blur-performance', ['../gsk/gskcairoblur.c']],
['motion-compression'],
['overlayscroll'],
['curve', ['curve.c', 'curve-editor.c']],
['curve2'],
['testupload'],
['testtransform'],
['testdropdown'],