From 5e2aeee9b0eda40890874a5bf0c614da18c8f9f5 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Tue, 11 Aug 2020 21:14:05 -0400 Subject: [PATCH] gtk-demo: Add a layout manager demo This is more or less a copy of the layout manager example from clutter. --- demos/gtk-demo/demo.gresource.xml | 9 ++ demos/gtk-demo/demochild.c | 72 ++++++++++++ demos/gtk-demo/demochild.h | 8 ++ demos/gtk-demo/demolayout.c | 189 ++++++++++++++++++++++++++++++ demos/gtk-demo/demolayout.h | 13 ++ demos/gtk-demo/demowidget.c | 121 +++++++++++++++++++ demos/gtk-demo/demowidget.h | 11 ++ demos/gtk-demo/layoutmanager.c | 61 ++++++++++ demos/gtk-demo/meson.build | 6 +- 9 files changed, 489 insertions(+), 1 deletion(-) create mode 100644 demos/gtk-demo/demochild.c create mode 100644 demos/gtk-demo/demochild.h create mode 100644 demos/gtk-demo/demolayout.c create mode 100644 demos/gtk-demo/demolayout.h create mode 100644 demos/gtk-demo/demowidget.c create mode 100644 demos/gtk-demo/demowidget.h create mode 100644 demos/gtk-demo/layoutmanager.c diff --git a/demos/gtk-demo/demo.gresource.xml b/demos/gtk-demo/demo.gresource.xml index bee2c4042b..9f02e51b16 100644 --- a/demos/gtk-demo/demo.gresource.xml +++ b/demos/gtk-demo/demo.gresource.xml @@ -124,6 +124,14 @@ gnome-fs-directory.png gnome-fs-regular.png + + demolayout.h + demolayout.c + demowidget.h + demowidget.c + demochild.h + demochild.c + listview_filebrowser.ui listview_filebrowser.css @@ -206,6 +214,7 @@ iconview_edit.c images.c infobar.c + layoutmanager.c links.c listbox.c listbox2.c diff --git a/demos/gtk-demo/demochild.c b/demos/gtk-demo/demochild.c new file mode 100644 index 0000000000..147cf606f9 --- /dev/null +++ b/demos/gtk-demo/demochild.c @@ -0,0 +1,72 @@ +#include "demochild.h" + +/* This is a trivial child widget just for demo purposes. + * It draws a 32x32 square in fixed color. + */ + +struct _DemoChild +{ + GtkWidget parent_instance; + GdkRGBA color; +}; + +struct _DemoChildClass +{ + GtkWidgetClass parent_class; +}; + +G_DEFINE_TYPE (DemoChild, demo_child, GTK_TYPE_WIDGET) + +static void +demo_child_init (DemoChild *self) +{ +} + +static void +demo_child_snapshot (GtkWidget *widget, + GtkSnapshot *snapshot) +{ + DemoChild *self = DEMO_CHILD (widget); + int width, height; + + width = gtk_widget_get_width (widget); + height = gtk_widget_get_height (widget); + + gtk_snapshot_append_color (snapshot, &self->color, + &GRAPHENE_RECT_INIT(0, 0, width, height)); +} + +static void +demo_child_measure (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + *minimum = *natural = 32; +} + +static void +demo_child_class_init (DemoChildClass *class) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); + + widget_class->snapshot = demo_child_snapshot; + widget_class->measure = demo_child_measure; +} + +GtkWidget * +demo_child_new (const char *color) +{ + DemoChild *self; + + self = g_object_new (DEMO_TYPE_CHILD, + "tooltip-text", color, + NULL); + + gdk_rgba_parse (&self->color, color); + + return GTK_WIDGET (self); +} diff --git a/demos/gtk-demo/demochild.h b/demos/gtk-demo/demochild.h new file mode 100644 index 0000000000..0a6c6e991c --- /dev/null +++ b/demos/gtk-demo/demochild.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +#define DEMO_TYPE_CHILD (demo_child_get_type ()) +G_DECLARE_FINAL_TYPE (DemoChild, demo_child, DEMO, CHILD, GtkWidget) + +GtkWidget * demo_child_new (const char *color); diff --git a/demos/gtk-demo/demolayout.c b/demos/gtk-demo/demolayout.c new file mode 100644 index 0000000000..fed244a3af --- /dev/null +++ b/demos/gtk-demo/demolayout.c @@ -0,0 +1,189 @@ +#include "demolayout.h" + +struct _DemoLayout +{ + GtkLayoutManager parent_instance; + + float position; + int pos[16]; +}; + +struct _DemoLayoutClass +{ + GtkLayoutManagerClass parent_class; +}; + +G_DEFINE_TYPE (DemoLayout, demo_layout, GTK_TYPE_LAYOUT_MANAGER) + +static void +demo_layout_measure (GtkLayoutManager *layout_manager, + GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + GtkWidget *child; + int minimum_size = 0; + int natural_size = 0; + + for (child = gtk_widget_get_first_child (widget); + child != NULL; + child = gtk_widget_get_next_sibling (child)) + { + int child_min = 0, child_nat = 0; + + if (!gtk_widget_should_layout (child)) + continue; + + gtk_widget_measure (child, orientation, -1, + &child_min, &child_nat, + NULL, NULL); + minimum_size = MAX (minimum_size, child_min); + natural_size = MAX (natural_size, child_nat); + } + + /* A back-of-a-napkin calculation to reserve enough + * space for arranging 16 children in a circle. + */ + *minimum = 16 * minimum_size / G_PI + minimum_size; + *natural = 16 * natural_size / G_PI + natural_size; +} + +static void +demo_layout_allocate (GtkLayoutManager *layout_manager, + GtkWidget *widget, + int width, + int height, + int baseline) +{ + DemoLayout *self = DEMO_LAYOUT (layout_manager); + GtkWidget *child; + int i; + int child_width = 0; + int child_height = 0; + int x0, y0; + float r; + float t; + + t = self->position; + + for (child = gtk_widget_get_first_child (widget); + child != NULL; + child = gtk_widget_get_next_sibling (child)) + { + GtkRequisition child_req; + + if (!gtk_widget_should_layout (child)) + continue; + + gtk_widget_get_preferred_size (child, &child_req, NULL); + + child_width = MAX (child_width, child_req.width); + child_height = MAX (child_height, child_req.height); + } + + /* the center of our layout */ + x0 = (width / 2); + y0 = (height / 2); + + /* the radius for our circle of children */ + r = 8 * child_width / G_PI; + + for (child = gtk_widget_get_first_child (widget), i = 0; + child != NULL; + child = gtk_widget_get_next_sibling (child), i++) + { + GtkRequisition child_req; + float a = self->pos[i] * G_PI / 8; + int gx, gy; + int cx, cy; + int x, y; + + if (!gtk_widget_should_layout (child)) + continue; + + gtk_widget_get_preferred_size (child, &child_req, NULL); + + /* The grid position of child. */ + gx = x0 + (i % 4 - 2) * child_width; + gy = y0 + (i / 4 - 2) * child_height; + + /* The circle position of child. Note that we + * are adjusting the position by half the child size + * to place the center of child on a centered circle. + * This assumes that the children don't use align flags + * or uneven margins that would shift the center. + */ + cx = x0 + sin (a) * r - child_req.width / 2; + cy = y0 + cos (a) * r - child_req.height / 2; + + /* we interpolate between the two layouts according to + * the position value that has been set on the layout. + */ + x = t * cx + (1 - t) * gx; + y = t * cy + (1 - t) * gy; + + gtk_widget_size_allocate (child, + &(const GtkAllocation){ x, y, child_width, child_height}, + -1); + } +} + +static GtkSizeRequestMode +demo_layout_get_request_mode (GtkLayoutManager *layout_manager, + GtkWidget *widget) +{ + return GTK_SIZE_REQUEST_CONSTANT_SIZE; +} + +static void +demo_layout_class_init (DemoLayoutClass *klass) +{ + GtkLayoutManagerClass *layout_class = GTK_LAYOUT_MANAGER_CLASS (klass); + + layout_class->get_request_mode = demo_layout_get_request_mode; + layout_class->measure = demo_layout_measure; + layout_class->allocate = demo_layout_allocate; +} + +static void +demo_layout_init (DemoLayout *self) +{ + int i; + + for (i = 0; i < 16; i++) + self->pos[i] = i; +} + +GtkLayoutManager * +demo_layout_new (void) +{ + return g_object_new (DEMO_TYPE_LAYOUT, NULL); +} + +void +demo_layout_set_position (DemoLayout *layout, + float position) +{ + layout->position = position; +} + +/* Shuffle the circle positions of the children. + * Should be called when we are in the grid layout. + */ +void +demo_layout_shuffle (DemoLayout *layout) +{ + int i, j, tmp; + + for (i = 0; i < 16; i++) + { + j = g_random_int_range (0, i + 1); + tmp = layout->pos[i]; + layout->pos[i] = layout->pos[j]; + layout->pos[j] = tmp; + } +} diff --git a/demos/gtk-demo/demolayout.h b/demos/gtk-demo/demolayout.h new file mode 100644 index 0000000000..8672ba2f3c --- /dev/null +++ b/demos/gtk-demo/demolayout.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#define DEMO_TYPE_LAYOUT (demo_layout_get_type ()) + +G_DECLARE_FINAL_TYPE (DemoLayout, demo_layout, DEMO, LAYOUT, GtkLayoutManager) + +GtkLayoutManager * demo_layout_new (void); + +void demo_layout_set_position (DemoLayout *layout, + float position); +void demo_layout_shuffle (DemoLayout *layout); diff --git a/demos/gtk-demo/demowidget.c b/demos/gtk-demo/demowidget.c new file mode 100644 index 0000000000..16c5c28ad3 --- /dev/null +++ b/demos/gtk-demo/demowidget.c @@ -0,0 +1,121 @@ +#include "demowidget.h" +#include "demolayout.h" + +/* parent widget */ + +struct _DemoWidget +{ + GtkWidget parent_instance; + + gboolean backward; /* whether we go 0 -> 1 or 1 -> 0 */ + gint64 start_time; /* time the transition started */ + guint tick_id; /* our tick cb */ +}; + +struct _DemoWidgetClass +{ + GtkWidgetClass parent_class; +}; + +G_DEFINE_TYPE (DemoWidget, demo_widget, GTK_TYPE_WIDGET) + +/* The widget is controlling the transition by calling + * demo_layout_set_position() in a tick callback. + * + * We take half a second to go from one layout to the other. + */ + +#define DURATION (0.5 * G_TIME_SPAN_SECOND) + +static gboolean +transition (GtkWidget *widget, + GdkFrameClock *frame_clock, + gpointer data) +{ + DemoWidget *self = DEMO_WIDGET (widget); + DemoLayout *demo_layout = DEMO_LAYOUT (gtk_widget_get_layout_manager (widget)); + gint64 now = g_get_monotonic_time (); + + gtk_widget_queue_allocate (widget); + + if (self->backward) + demo_layout_set_position (demo_layout, 1.0 - (now - self->start_time) / DURATION); + else + demo_layout_set_position (demo_layout, (now - self->start_time) / DURATION); + + if (now - self->start_time >= DURATION) + { + self->backward = !self->backward; + demo_layout_set_position (demo_layout, self->backward ? 1.0 : 0.0); + /* keep things interesting by shuffling the positions */ + if (!self->backward) + demo_layout_shuffle (demo_layout); + self->tick_id = 0; + + return G_SOURCE_REMOVE; + } + + return G_SOURCE_CONTINUE; +} + +static void +clicked (GtkGestureClick *gesture, + guint n_press, + double x, + double y, + gpointer data) +{ + DemoWidget *self = data; + + if (self->tick_id != 0) + return; + + self->start_time = g_get_monotonic_time (); + self->tick_id = gtk_widget_add_tick_callback (GTK_WIDGET (self), transition, NULL, NULL); +} + +static void +demo_widget_init (DemoWidget *self) +{ + GtkGesture *gesture; + + gesture = gtk_gesture_click_new (); + g_signal_connect (gesture, "pressed", G_CALLBACK (clicked), self); + gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture)); +} + +static void +demo_widget_dispose (GObject *object) +{ + GtkWidget *child; + + while ((child = gtk_widget_get_first_child (GTK_WIDGET (object)))) + gtk_widget_unparent (child); + + 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; + + /* here is where we use our custom layout manager */ + gtk_widget_class_set_layout_manager_type (widget_class, DEMO_TYPE_LAYOUT); +} + +GtkWidget * +demo_widget_new (void) +{ + return g_object_new (DEMO_TYPE_WIDGET, NULL); +} + +void +demo_widget_add_child (DemoWidget *self, + GtkWidget *child) +{ + gtk_widget_set_parent (child, GTK_WIDGET (self)); +} diff --git a/demos/gtk-demo/demowidget.h b/demos/gtk-demo/demowidget.h new file mode 100644 index 0000000000..cd8a5d4778 --- /dev/null +++ b/demos/gtk-demo/demowidget.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +#define DEMO_TYPE_WIDGET (demo_widget_get_type ()) +G_DECLARE_FINAL_TYPE (DemoWidget, demo_widget, DEMO, WIDGET, GtkWidget) + +GtkWidget * demo_widget_new (void); + +void demo_widget_add_child (DemoWidget *self, + GtkWidget *child); diff --git a/demos/gtk-demo/layoutmanager.c b/demos/gtk-demo/layoutmanager.c new file mode 100644 index 0000000000..3a715d0fc8 --- /dev/null +++ b/demos/gtk-demo/layoutmanager.c @@ -0,0 +1,61 @@ +/* Layout Manager + * + * This examples shows a simple example of a custom layout manager + * and a widget using it. The layout manager places the children + * of the widget in a grid or a circle, or something in between. + * + * The widget is animating the transition between the two layouts. + * Click to start the transition. + */ + +#include + +#include "demowidget.h" +#include "demochild.h" + + +GtkWidget * +do_layoutmanager (GtkWidget *parent) +{ + static GtkWidget *window = NULL; + + if (!window) + { + GtkWidget *widget; + GtkWidget *child; + const char *color[] = { + "red", "orange", "yellow", "green", + "blue", "grey", "magenta", "lime", + "yellow", "firebrick", "aqua", "purple", + "tomato", "pink", "thistle", "maroon" + }; + int i; + + window = gtk_window_new (); + gtk_window_set_title (GTK_WINDOW (window), "Layout Manager"); + gtk_window_set_default_size (GTK_WINDOW (window), 600, 600); + g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window); + + widget = demo_widget_new (); + + for (i = 0; i < 16; i++) + { + child = demo_child_new (color[i]); + gtk_widget_set_margin_start (child, 4); + gtk_widget_set_margin_end (child, 4); + gtk_widget_set_margin_top (child, 4); + gtk_widget_set_margin_bottom (child, 4); + demo_widget_add_child (DEMO_WIDGET (widget), child); + } + + gtk_window_set_child (GTK_WINDOW (window), widget); + } + + if (!gtk_widget_get_visible (window)) + gtk_widget_show (window); + else + gtk_window_destroy (GTK_WINDOW (window)); + + return window; + +} diff --git a/demos/gtk-demo/meson.build b/demos/gtk-demo/meson.build index 86f0b431b1..3cf3762d77 100644 --- a/demos/gtk-demo/meson.build +++ b/demos/gtk-demo/meson.build @@ -38,6 +38,7 @@ demos = files([ 'iconview_edit.c', 'images.c', 'infobar.c', + 'layoutmanager.c', 'links.c', 'listbox.c', 'listbox2.c', @@ -98,7 +99,10 @@ extra_demo_sources = files(['main.c', 'puzzlepiece.c', 'bluroverlay.c', 'demoimage.c', - 'demotaggedentry.c']) + 'demotaggedentry.c', + 'demochild.c', + 'demolayout.c', + 'demowidget.c']) if harfbuzz_dep.found() and pangoft_dep.found() demos += files('font_features.c')