[gtk+/gtk-3-22] gtk-demo: Add GtkFishbowl



commit dd406c80629c8b23eb5391608df5fea93580ea06
Author: Benjamin Otte <otte redhat com>
Date:   Sat Jan 7 01:59:23 2017 +0100

    gtk-demo: Add GtkFishbowl
    
    Avoids usage of GtkFixed where child properties eat up all the CPU time.
    And that's kinda not what I want to benchmark.

 demos/gtk-demo/Makefile.am        |    2 +
 demos/gtk-demo/demo.gresource.xml |    2 +
 demos/gtk-demo/fishbowl.c         |  188 +-----------
 demos/gtk-demo/fishbowl.ui        |    3 +-
 demos/gtk-demo/gtkfishbowl.c      |  581 +++++++++++++++++++++++++++++++++++++
 demos/gtk-demo/gtkfishbowl.h      |   58 ++++
 6 files changed, 658 insertions(+), 176 deletions(-)
---
diff --git a/demos/gtk-demo/Makefile.am b/demos/gtk-demo/Makefile.am
index acc1f99..e3b7ff2 100644
--- a/demos/gtk-demo/Makefile.am
+++ b/demos/gtk-demo/Makefile.am
@@ -138,6 +138,8 @@ nodist_gtk3_demo_SOURCES = demos.h
 
 gtk3_demo_SOURCES =            \
        $(demos)                \
+       gtkfishbowl.c           \
+       gtkfishbowl.h           \
        demo_resources.c        \
        main.c
 
diff --git a/demos/gtk-demo/demo.gresource.xml b/demos/gtk-demo/demo.gresource.xml
index a6f8200..a02c484 100644
--- a/demos/gtk-demo/demo.gresource.xml
+++ b/demos/gtk-demo/demo.gresource.xml
@@ -96,6 +96,8 @@
   </gresource>
   <gresource prefix="/fishbowl">
     <file>fishbowl.ui</file>
+    <file>gtkfishbowl.c</file>
+    <file>gtkfishbowl.h</file>
   </gresource>
   <gresource prefix="/iconview">
     <file preprocess="to-pixdata">gnome-fs-directory.png</file>
diff --git a/demos/gtk-demo/fishbowl.c b/demos/gtk-demo/fishbowl.c
index 766bcdc..40c5e2a 100644
--- a/demos/gtk-demo/fishbowl.c
+++ b/demos/gtk-demo/fishbowl.c
@@ -7,47 +7,10 @@
 
 #include <gtk/gtk.h>
 
-char **icon_names = NULL;
-gsize n_icon_names = 0;
+#include "gtkfishbowl.h"
 
 GtkWidget *allow_changes;
 
-static void
-init_icon_names (GtkIconTheme *theme)
-{
-  GPtrArray *icons;
-  GList *l, *icon_list;
-
-  if (icon_names)
-    return;
-
-  icon_list = gtk_icon_theme_list_icons (theme, NULL);
-  icons = g_ptr_array_new ();
-
-  for (l = icon_list; l; l = l->next)
-    {
-      if (g_str_has_suffix (l->data, "symbolic"))
-        continue;
-
-      g_ptr_array_add (icons, g_strdup (l->data));
-    }
-
-  n_icon_names = icons->len;
-  g_ptr_array_add (icons, NULL); /* NULL-terminate the array */
-  icon_names = (char **) g_ptr_array_free (icons, FALSE);
-
-  /* don't free strings, we assigned them to the array */
-  g_list_free_full (icon_list, g_free);
-}
-
-static const char *
-get_random_icon_name (GtkIconTheme *theme)
-{
-  init_icon_names (theme);
-
-  return icon_names[g_random_int_range(0, n_icon_names)];
-}
-
 #define N_STATS 5
 
 #define STATS_UPDATE_TIME G_USEC_PER_SEC
@@ -85,17 +48,16 @@ get_stats (GtkWidget *widget)
   return stats;
 }
 
-static gint64
+static void
 do_stats (GtkWidget *widget,
           GtkWidget *info_label,
           gint      *suggested_change)
 {
   Stats *stats;
-  gint64 frame_time, elapsed;
+  gint64 frame_time;
 
   stats = get_stats (widget);
   frame_time = gdk_frame_clock_get_frame_time (gtk_widget_get_frame_clock (widget));
-  elapsed = frame_time - stats->last_frame;
 
   if (stats->last_stats + STATS_UPDATE_TIME < frame_time)
     {
@@ -150,138 +112,16 @@ do_stats (GtkWidget *widget,
   stats->last_frame = frame_time;
   stats->frame_counter[stats->stats_index]++;
   stats->frame_counter_max = MAX (stats->frame_counter_max, stats->frame_counter[stats->stats_index]);
-
-  return elapsed;
 }
 
 static void
-stats_update (GtkWidget *widget,
-              gint       n_items)
+stats_update (GtkWidget *widget)
 {
   Stats *stats;
 
   stats = get_stats (widget);
 
-  g_assert ((gint) stats->item_counter[stats->stats_index] + n_items > 0);
-  stats->item_counter[stats->stats_index] += n_items;
-}
-
-typedef struct _FishData FishData;
-struct _FishData {
-  double x;
-  double y;
-  double x_speed;
-  double y_speed;
-};
-
-static FishData *
-get_fish_data (GtkWidget *fish)
-{
-  static GQuark fish_quark = 0;
-  FishData *data;
-
-  if (G_UNLIKELY (fish_quark == 0))
-    fish_quark = g_quark_from_static_string ("fish");
-
-  data = g_object_get_qdata (G_OBJECT (fish), fish_quark);
-  if (data == NULL)
-    {
-      data = g_new0 (FishData, 1);
-      g_object_set_qdata_full (G_OBJECT (fish), fish_quark, data, g_free);
-      data->x = 10;
-      data->y = 10;
-      data->x_speed = g_random_double_range (1, 200);
-      data->y_speed = g_random_double_range (1, 200);
-    }
-
-  return data;
-}
-
-static void
-add_fish (GtkWidget *bowl,
-          guint      n_fish)
-{
-  GtkWidget *new_fish;
-  guint i;
-
-  for (i = 0; i < n_fish; i++)
-    {
-      new_fish = gtk_image_new_from_icon_name (get_random_icon_name (gtk_icon_theme_get_default ()),
-                                               GTK_ICON_SIZE_DIALOG);
-      gtk_widget_show (new_fish);
-
-      gtk_fixed_put (GTK_FIXED (bowl),
-                     new_fish,
-                     10, 10);
-    }
-
-  stats_update (bowl, n_fish);
-}
-
-static void
-remove_fish (GtkWidget *bowl,
-             guint      n_fish)
-{
-  GList *list, *children;
-  guint i;
-
-  children = gtk_container_get_children (GTK_CONTAINER (bowl));
-  g_assert (n_fish < g_list_length (children));
-
-  list = children;
-  for (i = 0; i < n_fish; i++)
-    {
-      gtk_container_remove (GTK_CONTAINER (bowl), list->data);
-      list = list->next;
-    }
-
-  g_list_free (children);
-
-  stats_update (bowl, - (gint) n_fish);
-
-  {
-    Stats *stats = get_stats (bowl);
-
-    children = gtk_container_get_children (GTK_CONTAINER (bowl));
-    g_assert (stats->item_counter[stats->stats_index] == g_list_length (children));
-    g_list_free (children);
-  }
-}
-
-static void
-move_one_fish (GtkWidget *fish,
-               gpointer   elapsedp)
-{
-  GtkWidget *fixed = gtk_widget_get_parent (fish);
-  FishData *data = get_fish_data (fish);
-  gint64 elapsed = *(gint64 *) elapsedp;
-
-  data->x += data->x_speed * ((double) elapsed / G_USEC_PER_SEC);
-  data->y += data->y_speed * ((double) elapsed / G_USEC_PER_SEC);
-
-  if (data->x <= 0)
-    {
-      data->x = 0;
-      data->x_speed = - g_random_double_range (1, 200) * (data->x_speed > 0 ? 1 : -1);
-    }
-  else if (data->x > gtk_widget_get_allocated_width (fixed) - gtk_widget_get_allocated_width (fish))
-    {
-      data->x = gtk_widget_get_allocated_width (fixed) - gtk_widget_get_allocated_width (fish);
-      data->x_speed = - g_random_double_range (1, 200) * (data->x_speed > 0 ? 1 : -1);
-    }
-
-  if (data->y <= 0)
-    {
-      data->y = 0;
-      data->y_speed = - g_random_double_range (1, 200) * (data->y_speed > 0 ? 1 : -1);
-    }
-  else if (data->y > gtk_widget_get_allocated_height (fixed) - gtk_widget_get_allocated_height (fish))
-    {
-      data->y = gtk_widget_get_allocated_height (fixed) - gtk_widget_get_allocated_height (fish);
-      data->y_speed = - g_random_double_range (1, 200) * (data->y_speed > 0 ? 1 : -1);
-    }
-
-  gtk_fixed_move (GTK_FIXED (fixed), fish, data->x, data->y);
+  stats->item_counter[stats->stats_index] = gtk_fishbowl_get_count (GTK_FISHBOWL (widget));
 }
 
 static gboolean
@@ -289,19 +129,15 @@ move_fish (GtkWidget     *bowl,
            GdkFrameClock *frame_clock,
            gpointer       info_label)
 {
-  gint64 elapsed;
   gint suggested_change = 0;
   
-  elapsed = do_stats (bowl,
-                      info_label,
-                      !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (allow_changes)) ? &suggested_change 
: NULL);
+  do_stats (bowl,
+            info_label,
+            !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (allow_changes)) ? &suggested_change : NULL);
 
-  gtk_container_foreach (GTK_CONTAINER (bowl), move_one_fish, &elapsed);
-
-  if (suggested_change > 0)
-    add_fish (bowl, suggested_change);
-  else if (suggested_change < 0)
-    remove_fish (bowl, - suggested_change);
+  gtk_fishbowl_set_count (GTK_FISHBOWL (bowl),
+                          gtk_fishbowl_get_count (GTK_FISHBOWL (bowl)) + suggested_change);
+  stats_update (bowl);
 
   return G_SOURCE_CONTINUE;
 }
@@ -316,6 +152,8 @@ do_fishbowl (GtkWidget *do_widget)
       GtkBuilder *builder;
       GtkWidget *bowl, *info_label;
 
+      g_type_ensure (GTK_TYPE_FISHBOWL);
+
       builder = gtk_builder_new_from_resource ("/fishbowl/fishbowl.ui");
       gtk_builder_connect_signals (builder, NULL);
       window = GTK_WIDGET (gtk_builder_get_object (builder, "window"));
diff --git a/demos/gtk-demo/fishbowl.ui b/demos/gtk-demo/fishbowl.ui
index c8b2a7c..2a64091 100644
--- a/demos/gtk-demo/fishbowl.ui
+++ b/demos/gtk-demo/fishbowl.ui
@@ -51,8 +51,9 @@
       </object>
     </child>
     <child>
-      <object class="GtkFixed" id="bowl">
+      <object class="GtkFishbowl" id="bowl">
         <property name="visible">True</property>
+        <property name="animating">True</property>
       </object>
     </child>
   </object>
diff --git a/demos/gtk-demo/gtkfishbowl.c b/demos/gtk-demo/gtkfishbowl.c
new file mode 100644
index 0000000..54ee7b0
--- /dev/null
+++ b/demos/gtk-demo/gtkfishbowl.c
@@ -0,0 +1,581 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2017 Benjamin Otte <otte gnome org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gtkfishbowl.h"
+
+#include <math.h>
+
+typedef struct _GtkFishbowlPrivate       GtkFishbowlPrivate;
+typedef struct _GtkFishbowlChild         GtkFishbowlChild;
+
+struct _GtkFishbowlPrivate
+{
+  GList *children;
+  guint count;
+
+  gint64 last_frame_time;
+  guint tick_id;
+};
+
+struct _GtkFishbowlChild
+{
+  GtkWidget *widget;
+  double x;
+  double y;
+  double dx;
+  double dy;
+};
+
+enum {
+   PROP_0,
+   PROP_ANIMATING,
+   PROP_COUNT,
+   NUM_PROPERTIES
+};
+
+static GParamSpec *props[NUM_PROPERTIES] = { NULL, };
+
+G_DEFINE_TYPE_WITH_PRIVATE (GtkFishbowl, gtk_fishbowl, GTK_TYPE_CONTAINER)
+
+static void
+gtk_fishbowl_init (GtkFishbowl *fishbowl)
+{
+  gtk_widget_set_has_window (GTK_WIDGET (fishbowl), FALSE);
+}
+
+/**
+ * gtk_fishbowl_new:
+ *
+ * Creates a new #GtkFishbowl.
+ *
+ * Returns: a new #GtkFishbowl.
+ */
+GtkWidget*
+gtk_fishbowl_new (void)
+{
+  return g_object_new (GTK_TYPE_FISHBOWL, NULL);
+}
+
+static void
+gtk_widget_measure (GtkWidget      *widget,
+                    GtkOrientation  orientation,
+                    gint            size,
+                    gint           *minimum,
+                    gint           *natural,
+                    gint           *minimum_baseline,
+                    gint           *natural_baseline)
+{
+  g_return_if_fail (GTK_IS_WIDGET (widget));
+  g_return_if_fail (size >= -1);
+
+  if (orientation == GTK_ORIENTATION_HORIZONTAL)
+    {
+      if (size < 0)
+        gtk_widget_get_preferred_width (widget, minimum, natural);
+      else
+        gtk_widget_get_preferred_width_for_height (widget, size, minimum, natural);
+
+      if (minimum_baseline)
+        *minimum_baseline = -1;
+      if (natural_baseline)
+        *natural_baseline = -1;
+    }
+  else
+    {
+      gtk_widget_get_preferred_height_and_baseline_for_width (widget,
+                                                              size,
+                                                              minimum,
+                                                              natural,
+                                                              minimum_baseline,
+                                                              natural_baseline);
+    }
+}
+
+static void
+gtk_fishbowl_measure (GtkWidget      *widget,
+                      GtkOrientation  orientation,
+                      int             for_size,
+                      int            *minimum,
+                      int            *natural,
+                      int            *minimum_baseline,
+                      int            *natural_baseline)
+{
+  GtkFishbowl *fishbowl = GTK_FISHBOWL (widget);
+  GtkFishbowlPrivate *priv = gtk_fishbowl_get_instance_private (fishbowl);
+  GtkFishbowlChild *child;
+  GList *children;
+  gint child_min, child_nat;
+
+  *minimum = 0;
+  *natural = 0;
+
+  for (children = priv->children; children; children = children->next)
+    {
+      child = children->data;
+
+      if (!gtk_widget_get_visible (child->widget))
+        continue;
+
+      gtk_widget_measure (child->widget, orientation, -1, &child_min, &child_nat, NULL, NULL);
+
+      *minimum = MAX (*minimum, child_min);
+      *natural = MAX (*natural, child_nat);
+    }
+}
+
+static void
+gtk_fishbowl_get_preferred_width (GtkWidget *widget,
+                                  int       *minimum,
+                                  int       *natural)
+{
+  gtk_fishbowl_measure (widget, GTK_ORIENTATION_HORIZONTAL, -1, minimum, natural, NULL, NULL);
+}
+
+static void
+gtk_fishbowl_get_preferred_height (GtkWidget *widget,
+                                   int       *minimum,
+                                   int       *natural)
+{
+  gtk_fishbowl_measure (widget, GTK_ORIENTATION_VERTICAL, -1, minimum, natural, NULL, NULL);
+}
+
+static void
+gtk_fishbowl_get_preferred_width_for_height (GtkWidget *widget,
+                                             int        for_size,
+                                             int       *minimum,
+                                             int       *natural)
+{
+  gtk_fishbowl_measure (widget, GTK_ORIENTATION_HORIZONTAL, for_size, minimum, natural, NULL, NULL);
+}
+
+static void
+gtk_fishbowl_get_preferred_height_and_baseline_for_width (GtkWidget *widget,
+                                                          int        for_size,
+                                                          int       *minimum,
+                                                          int       *natural,
+                                                          int       *minimum_baseline,
+                                                          int       *natural_baseline)
+{
+  gtk_fishbowl_measure (widget, GTK_ORIENTATION_VERTICAL, for_size, minimum, natural, minimum_baseline, 
natural_baseline);
+}
+
+static void
+gtk_fishbowl_size_allocate (GtkWidget     *widget,
+                            GtkAllocation *allocation)
+{
+  GtkFishbowl *fishbowl = GTK_FISHBOWL (widget);
+  GtkFishbowlPrivate *priv = gtk_fishbowl_get_instance_private (fishbowl);
+  GtkFishbowlChild *child;
+  GtkAllocation child_allocation;
+  GtkRequisition child_requisition;
+  GList *children;
+
+  gtk_widget_set_allocation (widget, allocation);
+
+  for (children = priv->children; children; children = children->next)
+    {
+      child = children->data;
+
+      if (!gtk_widget_get_visible (child->widget))
+        continue;
+
+      gtk_widget_get_preferred_size (child->widget, &child_requisition, NULL);
+      child_allocation.x = allocation->x + round (child->x * (allocation->width - child_requisition.width));
+      child_allocation.y = allocation->y + round (child->y * (allocation->height - 
child_requisition.height));
+      child_allocation.width = child_requisition.width;
+      child_allocation.height = child_requisition.height;
+
+      gtk_widget_size_allocate (child->widget, &child_allocation);
+    }
+}
+
+static double
+new_speed (void)
+{
+  /* 5s to 50s to cross screen seems fair */
+  return g_random_double_range (0.02, 0.2);
+}
+
+static void
+gtk_fishbowl_add (GtkContainer *container,
+                  GtkWidget    *widget)
+{
+  GtkFishbowl *fishbowl = GTK_FISHBOWL (container);
+  GtkFishbowlPrivate *priv = gtk_fishbowl_get_instance_private (fishbowl);
+  GtkFishbowlChild *child_info;
+
+  g_return_if_fail (GTK_IS_FISHBOWL (fishbowl));
+  g_return_if_fail (GTK_IS_WIDGET (widget));
+
+  child_info = g_new0 (GtkFishbowlChild, 1);
+  child_info->widget = widget;
+  child_info->x = 0;
+  child_info->y = 0;
+  child_info->dx = new_speed ();
+  child_info->dy = new_speed ();
+
+  gtk_widget_set_parent (widget, GTK_WIDGET (fishbowl));
+
+  priv->children = g_list_prepend (priv->children, child_info);
+  priv->count++;
+  g_object_notify_by_pspec (G_OBJECT (fishbowl), props[PROP_COUNT]);
+}
+
+static void
+gtk_fishbowl_remove (GtkContainer *container,
+                     GtkWidget    *widget)
+{
+  GtkFishbowl *fishbowl = GTK_FISHBOWL (container);
+  GtkFishbowlPrivate *priv = gtk_fishbowl_get_instance_private (fishbowl);
+  GtkFishbowlChild *child;
+  GtkWidget *widget_container = GTK_WIDGET (container);
+  GList *children;
+
+  for (children = priv->children; children; children = children->next)
+    {
+      child = children->data;
+
+      if (child->widget == widget)
+        {
+          gboolean was_visible = gtk_widget_get_visible (widget);
+
+          gtk_widget_unparent (widget);
+
+          priv->children = g_list_remove_link (priv->children, children);
+          g_list_free (children);
+          g_free (child);
+
+          if (was_visible && gtk_widget_get_visible (widget_container))
+            gtk_widget_queue_resize (widget_container);
+
+          priv->count--;
+          g_object_notify_by_pspec (G_OBJECT (fishbowl), props[PROP_COUNT]);
+          break;
+        }
+    }
+}
+
+static void
+gtk_fishbowl_forall (GtkContainer *container,
+                     gboolean      include_internals,
+                     GtkCallback   callback,
+                     gpointer      callback_data)
+{
+  GtkFishbowl *fishbowl = GTK_FISHBOWL (container);
+  GtkFishbowlPrivate *priv = gtk_fishbowl_get_instance_private (fishbowl);
+  GtkFishbowlChild *child;
+  GList *children;
+
+  if (!include_internals)
+    return;
+
+  children = priv->children;
+  while (children)
+    {
+      child = children->data;
+      children = children->next;
+
+      (* callback) (child->widget, callback_data);
+    }
+}
+
+static gboolean
+gtk_fishbowl_draw (GtkWidget *widget,
+                   cairo_t   *cr)
+{
+  GtkFishbowl *fishbowl = GTK_FISHBOWL (widget);
+  GtkFishbowlPrivate *priv = gtk_fishbowl_get_instance_private (fishbowl);
+  GtkFishbowlChild *child;
+  GList *list;
+
+  for (list = priv->children;
+       list;
+       list = list->next)
+    {
+      child = list->data;
+
+      gtk_container_propagate_draw (GTK_CONTAINER (fishbowl),
+                                    child->widget,
+                                    cr);
+    }
+
+  return FALSE;
+}
+
+static void
+gtk_fishbowl_dispose (GObject *object)
+{
+  GtkFishbowl *fishbowl = GTK_FISHBOWL (object);
+
+  gtk_fishbowl_set_animating (fishbowl, FALSE);
+  gtk_fishbowl_set_count (fishbowl, 0);
+
+  G_OBJECT_CLASS (gtk_fishbowl_parent_class)->dispose (object);
+}
+
+static void
+gtk_fishbowl_set_property (GObject         *object,
+                           guint            prop_id,
+                           const GValue    *value,
+                           GParamSpec      *pspec)
+{
+  GtkFishbowl *fishbowl = GTK_FISHBOWL (object);
+
+  switch (prop_id)
+    {
+    case PROP_ANIMATING:
+      gtk_fishbowl_set_animating (fishbowl, g_value_get_boolean (value));
+      break;
+
+    case PROP_COUNT:
+      gtk_fishbowl_set_count (fishbowl, g_value_get_uint (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+gtk_fishbowl_get_property (GObject         *object,
+                           guint            prop_id,
+                           GValue          *value,
+                           GParamSpec      *pspec)
+{
+  GtkFishbowl *fishbowl = GTK_FISHBOWL (object);
+
+  switch (prop_id)
+    {
+    case PROP_ANIMATING:
+      g_value_set_boolean (value, gtk_fishbowl_get_animating (fishbowl));
+      break;
+
+    case PROP_COUNT:
+      g_value_set_uint (value, gtk_fishbowl_get_count (fishbowl));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+gtk_fishbowl_class_init (GtkFishbowlClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+
+  object_class->dispose = gtk_fishbowl_dispose;
+  object_class->set_property = gtk_fishbowl_set_property;
+  object_class->get_property = gtk_fishbowl_get_property;
+
+  widget_class->get_preferred_width = gtk_fishbowl_get_preferred_width;
+  widget_class->get_preferred_height = gtk_fishbowl_get_preferred_height;
+  widget_class->get_preferred_width_for_height = gtk_fishbowl_get_preferred_width_for_height;
+  widget_class->get_preferred_height_and_baseline_for_width = 
gtk_fishbowl_get_preferred_height_and_baseline_for_width;
+  widget_class->size_allocate = gtk_fishbowl_size_allocate;
+  widget_class->draw = gtk_fishbowl_draw;
+
+  container_class->add = gtk_fishbowl_add;
+  container_class->remove = gtk_fishbowl_remove;
+  container_class->forall = gtk_fishbowl_forall;
+
+  props[PROP_ANIMATING] =
+      g_param_spec_boolean ("animating",
+                            "animating",
+                            "Whether children are moving around",
+                            FALSE,
+                            G_PARAM_READWRITE);
+
+  props[PROP_COUNT] =
+      g_param_spec_uint ("count",
+                         "Count",
+                         "Number of widgets",
+                         0, G_MAXUINT,
+                         0,
+                         G_PARAM_READABLE);
+
+  g_object_class_install_properties (object_class, NUM_PROPERTIES, props);
+}
+
+guint
+gtk_fishbowl_get_count (GtkFishbowl *fishbowl)
+{
+  GtkFishbowlPrivate *priv = gtk_fishbowl_get_instance_private (fishbowl);
+
+  return priv->count;
+}
+
+char **icon_names = NULL;
+gsize n_icon_names = 0;
+
+static void
+init_icon_names (GtkIconTheme *theme)
+{
+  GPtrArray *icons;
+  GList *l, *icon_list;
+
+  if (icon_names)
+    return;
+
+  icon_list = gtk_icon_theme_list_icons (theme, NULL);
+  icons = g_ptr_array_new ();
+
+  for (l = icon_list; l; l = l->next)
+    {
+      if (g_str_has_suffix (l->data, "symbolic"))
+        continue;
+
+      g_ptr_array_add (icons, g_strdup (l->data));
+    }
+
+  n_icon_names = icons->len;
+  g_ptr_array_add (icons, NULL); /* NULL-terminate the array */
+  icon_names = (char **) g_ptr_array_free (icons, FALSE);
+
+  /* don't free strings, we assigned them to the array */
+  g_list_free_full (icon_list, g_free);
+}
+
+static const char *
+get_random_icon_name (GtkIconTheme *theme)
+{
+  init_icon_names (theme);
+
+  return icon_names[g_random_int_range(0, n_icon_names)];
+}
+
+void
+gtk_fishbowl_set_count (GtkFishbowl *fishbowl,
+                        guint        count)
+{
+  GtkFishbowlPrivate *priv = gtk_fishbowl_get_instance_private (fishbowl);
+
+  g_object_freeze_notify (G_OBJECT (fishbowl));
+
+  while (priv->count > count)
+    {
+      gtk_container_remove (GTK_CONTAINER (fishbowl),
+                            ((GtkFishbowlChild *) priv->children->data)->widget);
+    }
+
+  while (priv->count < count)
+    {
+      GtkWidget *new_widget;
+        
+      new_widget = gtk_image_new_from_icon_name (get_random_icon_name (gtk_icon_theme_get_default ()),
+                                                 GTK_ICON_SIZE_DIALOG);
+      gtk_widget_show (new_widget);
+      gtk_container_add (GTK_CONTAINER (fishbowl), new_widget);
+    }
+
+  g_object_thaw_notify (G_OBJECT (fishbowl));
+}
+
+gboolean
+gtk_fishbowl_get_animating (GtkFishbowl *fishbowl)
+{
+  GtkFishbowlPrivate *priv = gtk_fishbowl_get_instance_private (fishbowl);
+
+  return priv->tick_id != 0;
+}
+
+static gboolean
+gtk_fishbowl_tick (GtkWidget     *widget,
+                   GdkFrameClock *frame_clock,
+                   gpointer       unused)
+{
+  GtkFishbowl *fishbowl = GTK_FISHBOWL (widget);
+  GtkFishbowlPrivate *priv = gtk_fishbowl_get_instance_private (fishbowl);
+  GtkFishbowlChild *child;
+  GList *l;
+  gint64 frame_time, elapsed;
+
+  frame_time = gdk_frame_clock_get_frame_time (gtk_widget_get_frame_clock (widget));
+  elapsed = frame_time - priv->last_frame_time;
+  priv->last_frame_time = frame_time;
+
+  /* last frame was 0, so we're just starting to animate */
+  if (elapsed == frame_time)
+    return G_SOURCE_CONTINUE;
+
+  for (l = priv->children; l; l = l->next)
+    {
+      child = l->data;
+
+      child->x += child->dx * ((double) elapsed / G_USEC_PER_SEC);
+      child->y += child->dy * ((double) elapsed / G_USEC_PER_SEC);
+
+      if (child->x <= 0)
+        {
+          child->x = 0;
+          child->dx = new_speed ();
+        }
+      else if (child->x >= 1)
+        {
+          child->x = 1;
+          child->dx =  - new_speed ();
+        }
+
+      if (child->y <= 0)
+        {
+          child->y = 0;
+          child->dy = new_speed ();
+        }
+      else if (child->y >= 1)
+        {
+          child->y = 1;
+          child->dy =  - new_speed ();
+        }
+    }
+
+  gtk_widget_queue_allocate (widget);
+
+  return G_SOURCE_CONTINUE;
+}
+
+void
+gtk_fishbowl_set_animating (GtkFishbowl *fishbowl,
+                            gboolean     animating)
+{
+  GtkFishbowlPrivate *priv = gtk_fishbowl_get_instance_private (fishbowl);
+
+  if (gtk_fishbowl_get_animating (fishbowl) == animating)
+    return;
+
+  if (animating)
+    {
+      priv->tick_id = gtk_widget_add_tick_callback (GTK_WIDGET (fishbowl),
+                                                    gtk_fishbowl_tick,
+                                                    NULL,
+                                                    NULL);
+    }
+  else
+    {
+      priv->last_frame_time = 0;
+      gtk_widget_remove_tick_callback (GTK_WIDGET (fishbowl), priv->tick_id);
+      priv->tick_id = 0;
+    }
+
+  g_object_notify_by_pspec (G_OBJECT (fishbowl), props[PROP_ANIMATING]);
+}
+
diff --git a/demos/gtk-demo/gtkfishbowl.h b/demos/gtk-demo/gtkfishbowl.h
new file mode 100644
index 0000000..2ac1ad1
--- /dev/null
+++ b/demos/gtk-demo/gtkfishbowl.h
@@ -0,0 +1,58 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2017 Benjamin Otte <otte gnome org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GTK_FISHBOWL_H__
+#define __GTK_FISHBOWL_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_FISHBOWL                  (gtk_fishbowl_get_type ())
+#define GTK_FISHBOWL(obj)                  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_FISHBOWL, 
GtkFishbowl))
+#define GTK_FISHBOWL_CLASS(klass)          (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FISHBOWL, 
GtkFishbowlClass))
+#define GTK_IS_FISHBOWL(obj)               (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_FISHBOWL))
+#define GTK_IS_FISHBOWL_CLASS(klass)       (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_FISHBOWL))
+#define GTK_FISHBOWL_GET_CLASS(obj)        (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_FISHBOWL, 
GtkFishbowlClass))
+
+typedef struct _GtkFishbowl              GtkFishbowl;
+typedef struct _GtkFishbowlClass         GtkFishbowlClass;
+
+struct _GtkFishbowl
+{
+  GtkContainer container;
+};
+
+struct _GtkFishbowlClass
+{
+  GtkContainerClass parent_class;
+};
+
+GType      gtk_fishbowl_get_type          (void) G_GNUC_CONST;
+
+GtkWidget* gtk_fishbowl_new               (void);
+
+guint      gtk_fishbowl_get_count         (GtkFishbowl       *fishbowl);
+void       gtk_fishbowl_set_count         (GtkFishbowl       *fishbowl,
+                                           guint              count);
+gboolean   gtk_fishbowl_get_animating     (GtkFishbowl       *fishbowl);
+void       gtk_fishbowl_set_animating     (GtkFishbowl       *fishbowl,
+                                           gboolean           animating);
+
+G_END_DECLS
+
+#endif /* __GTK_FISHBOWL_H__ */


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