From 6c1a128ea3dad4fc352afc1163f55f06017d5b89 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Mon, 14 Aug 2023 08:39:05 -0400 Subject: [PATCH] Make the map demo more interesting Add marching arrows to it. With this, it can also serve as a performance test for rendering medium complexity paths. --- demos/gtk-demo/path_walk.c | 152 +++++++++++++++++++++++++++++++++++- demos/gtk-demo/path_walk.ui | 20 ++++- 2 files changed, 168 insertions(+), 4 deletions(-) diff --git a/demos/gtk-demo/path_walk.c b/demos/gtk-demo/path_walk.c index 84af419413..cd588b0e5d 100644 --- a/demos/gtk-demo/path_walk.c +++ b/demos/gtk-demo/path_walk.c @@ -1,6 +1,6 @@ -/* Path/Map +/* Path/Walk * - * This demo shows how to draw a map using paths. + * This demo draws a world map and shows how to animate objects along a GskPath. * * The world map that is used here is a path with 211 lines and 1569 cubic * BÄ—zier segments in 121 contours. @@ -16,6 +16,7 @@ G_DECLARE_FINAL_TYPE (GtkPathWalk, gtk_path_walk, GTK, PATH_WALK, GtkWidget) enum { PROP_0, + PROP_N_POINTS, PROP_PATH, N_PROPS }; @@ -25,7 +26,10 @@ struct _GtkPathWalk GtkWidget parent_instance; GskPath *path; + GskPathMeasure *measure; graphene_rect_t bounds; + GskPath *arrow_path; + guint n_points; }; struct _GtkPathWalkClass @@ -37,6 +41,74 @@ static GParamSpec *properties[N_PROPS] = { NULL, }; G_DEFINE_TYPE (GtkPathWalk, gtk_path_walk, GTK_TYPE_WIDGET) +static void +rgba_init_from_hsla (GdkRGBA *rgba, + float hue, + float saturation, + float lightness, + float alpha) +{ + float m1, m2; + + if (lightness <= 0.5) + m2 = lightness * (1 + saturation); + else + m2 = lightness + saturation - lightness * saturation; + m1 = 2 * lightness - m2; + + rgba->alpha = alpha; + + if (saturation == 0) + { + rgba->red = lightness; + rgba->green = lightness; + rgba->blue = lightness; + } + else + { + hue = hue + 120; + while (hue > 360) + hue -= 360; + while (hue < 0) + hue += 360; + + if (hue < 60) + rgba->red = m1 + (m2 - m1) * hue / 60; + else if (hue < 180) + rgba->red = m2; + else if (hue < 240) + rgba->red = m1 + (m2 - m1) * (240 - hue) / 60; + else + rgba->red = m1; + + hue -= 120; + if (hue < 0) + hue += 360; + + if (hue < 60) + rgba->green = m1 + (m2 - m1) * hue / 60; + else if (hue < 180) + rgba->green = m2; + else if (hue < 240) + rgba->green = m1 + (m2 - m1) * (240 - hue) / 60; + else + rgba->green = m1; + + hue -= 120; + if (hue < 0) + hue += 360; + + if (hue < 60) + rgba->blue = m1 + (m2 - m1) * hue / 60; + else if (hue < 180) + rgba->blue = m2; + else if (hue < 240) + rgba->blue = m1 + (m2 - m1) * (240 - hue) / 60; + else + rgba->blue = m1; + } +} + static void gtk_path_walk_snapshot (GtkWidget *widget, GtkSnapshot *snapshot) @@ -44,7 +116,9 @@ gtk_path_walk_snapshot (GtkWidget *widget, GtkPathWalk *self = GTK_PATH_WALK (widget); double width = gtk_widget_get_width (widget); double height = gtk_widget_get_height (widget); + float length, progress; GskStroke *stroke; + guint i; if (self->path == NULL) return; @@ -57,6 +131,35 @@ gtk_path_walk_snapshot (GtkWidget *widget, gtk_snapshot_pop (snapshot); gsk_stroke_free (stroke); + length = gsk_path_measure_get_length (self->measure); + progress = 25.f * gdk_frame_clock_get_frame_time (gtk_widget_get_frame_clock (widget)) / G_USEC_PER_SEC; + + stroke = gsk_stroke_new (1.0); + for (i = 0; i < self->n_points; i++) + { + GskPathPoint point; + graphene_point_t position; + float angle; + GdkRGBA color; + float distance; + + distance = i * length / self->n_points; + distance = fmod (distance + progress, length); + + gsk_path_measure_get_point (self->measure, distance, &point); + gsk_path_point_get_position (&point, self->path, &position); + angle = gsk_path_point_get_rotation (&point, self->path, GSK_PATH_FROM_START); + rgba_init_from_hsla (&color, 360.f * i / self->n_points, 1, 0.5, 1); + + gtk_snapshot_save (snapshot); + gtk_snapshot_translate (snapshot, &position); + gtk_snapshot_rotate (snapshot, angle); + gtk_snapshot_append_fill (snapshot, self->arrow_path, GSK_FILL_RULE_EVEN_ODD, &color); + gtk_snapshot_append_stroke (snapshot, self->arrow_path, stroke, &(GdkRGBA) { 0, 0, 0, 1 }); + gtk_snapshot_restore (snapshot); + } + + gsk_stroke_free (stroke); gtk_snapshot_restore (snapshot); } @@ -77,6 +180,20 @@ gtk_path_walk_measure (GtkWidget *widget, *minimum = *natural = (int) ceilf (self->bounds.size.height); } +static void +gtk_path_walk_set_n_points (GtkPathWalk *self, + gsize n_points) +{ + if (self->n_points == n_points) + return; + + self->n_points = n_points; + + gtk_widget_queue_draw (GTK_WIDGET (self)); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_N_POINTS]); +} + static void gtk_path_walk_set_path (GtkPathWalk *self, GskPath *path) @@ -94,6 +211,7 @@ gtk_path_walk_set_path (GtkPathWalk *self, stroke = gsk_stroke_new (2.0); gsk_path_get_stroke_bounds (path, stroke, &self->bounds); gsk_stroke_free (stroke); + self->measure = gsk_path_measure_new (self->path); } gtk_widget_queue_resize (GTK_WIDGET (self)); @@ -112,6 +230,10 @@ gtk_path_walk_set_property (GObject *object, switch (prop_id) { + case PROP_N_POINTS: + gtk_path_walk_set_n_points (self, g_value_get_uint (value)); + break; + case PROP_PATH: gtk_path_walk_set_path (self, g_value_get_boxed (value)); break; @@ -132,6 +254,10 @@ gtk_path_walk_get_property (GObject *object, switch (prop_id) { + case PROP_N_POINTS: + g_value_set_uint (value, self->n_points); + break; + case PROP_PATH: g_value_set_boxed (value, self->path); break; @@ -148,6 +274,8 @@ gtk_path_walk_dispose (GObject *object) GtkPathWalk *self = GTK_PATH_WALK (object); g_clear_pointer (&self->path, gsk_path_unref); + g_clear_pointer (&self->measure, gsk_path_measure_unref); + g_clear_pointer (&self->arrow_path, gsk_path_unref); G_OBJECT_CLASS (gtk_path_walk_parent_class)->dispose (object); } @@ -165,6 +293,13 @@ gtk_path_walk_class_init (GtkPathWalkClass *klass) widget_class->snapshot = gtk_path_walk_snapshot; widget_class->measure = gtk_path_walk_measure; + properties[PROP_N_POINTS] = + g_param_spec_uint ("n-points", + NULL, NULL, + 1, G_MAXUINT, + 500, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + properties[PROP_PATH] = g_param_spec_boxed ("path", NULL, NULL, @@ -174,6 +309,16 @@ gtk_path_walk_class_init (GtkPathWalkClass *klass) g_object_class_install_properties (object_class, N_PROPS, properties); } +static gboolean +tick_tick_tick (GtkWidget *self, + GdkFrameClock *frame_clock, + gpointer unused) +{ + gtk_widget_queue_draw (GTK_WIDGET (self)); + + return G_SOURCE_CONTINUE; +} + static void gtk_path_walk_init (GtkPathWalk *self) { @@ -185,6 +330,9 @@ gtk_path_walk_init (GtkPathWalk *self) g_bytes_unref (data); gtk_path_walk_set_path (self, path); gsk_path_unref (path); + self->arrow_path = gsk_path_parse ("M 5 0 L 0 -5. 0 -2, -5 -2, -5 2, 0 2, 0 5 Z"); + self->n_points = 500; + gtk_widget_add_tick_callback (GTK_WIDGET (self), tick_tick_tick, NULL, NULL); } GtkWidget * diff --git a/demos/gtk-demo/path_walk.ui b/demos/gtk-demo/path_walk.ui index 6a382031c2..352b81baa8 100644 --- a/demos/gtk-demo/path_walk.ui +++ b/demos/gtk-demo/path_walk.ui @@ -2,16 +2,32 @@ World Map - + + + + + + + 0 + 5000 + 500 + + + + + + + vertical + true true - +