[gtk/matthiasc/for-master] gtk-demo: Add a layout manager demo



commit 6ed781e18efddaba75c71398ef7e2253d05788cd
Author: Matthias Clasen <mclasen redhat com>
Date:   Tue Aug 11 21:14:05 2020 -0400

    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/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 +-
 8 files changed, 481 insertions(+), 1 deletion(-)
---
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 @@
     <file>gnome-fs-directory.png</file>
     <file>gnome-fs-regular.png</file>
   </gresource>
+  <gresource prefix="/layoutmanager">
+    <file>demolayout.h</file>
+    <file>demolayout.c</file>
+    <file>demowidget.h</file>
+    <file>demowidget.c</file>
+    <file>demochild.h</file>
+    <file>demochild.c</file>
+  </gresource>
   <gresource prefix="/listview_filebrowser">
     <file>listview_filebrowser.ui</file>
     <file>listview_filebrowser.css</file>
@@ -206,6 +214,7 @@
     <file>iconview_edit.c</file>
     <file>images.c</file>
     <file>infobar.c</file>
+    <file>layoutmanager.c</file>
     <file>links.c</file>
     <file>listbox.c</file>
     <file>listbox2.c</file>
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/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 <gtk/gtk.h>
+
+#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 <gtk/gtk.h>
+
+#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 <gtk/gtk.h>
+
+#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')


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]