1 /*
2  * GTK - The GIMP Toolkit
3  * Copyright (C) 2017 Benjamin Otte <otte@gnome.org>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #ifndef __GTK_FISHBOWL_H__
20 #define __GTK_FISHBOWL_H__
21 
22 #include <gtk/gtk.h>
23 
24 G_BEGIN_DECLS
25 
26 #define GTK_TYPE_FISHBOWL                  (gtk_fishbowl_get_type ())
27 #define GTK_FISHBOWL(obj)                  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_FISHBOWL, GtkFishbowl))
28 #define GTK_FISHBOWL_CLASS(klass)          (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FISHBOWL, GtkFishbowlClass))
29 #define GTK_IS_FISHBOWL(obj)               (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_FISHBOWL))
30 #define GTK_IS_FISHBOWL_CLASS(klass)       (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_FISHBOWL))
31 #define GTK_FISHBOWL_GET_CLASS(obj)        (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_FISHBOWL, GtkFishbowlClass))
32 
33 typedef struct _GtkFishbowl              GtkFishbowl;
34 typedef struct _GtkFishbowlClass         GtkFishbowlClass;
35 
36 typedef GtkWidget * (* GtkFishCreationFunc) (void);
37 
38 struct _GtkFishbowl
39 {
40   GtkContainer parent;
41 };
42 
43 struct _GtkFishbowlClass
44 {
45   GtkContainerClass parent_class;
46 };
47 
48 GType      gtk_fishbowl_get_type          (void) G_GNUC_CONST;
49 
50 GtkWidget* gtk_fishbowl_new               (void);
51 
52 guint      gtk_fishbowl_get_count         (GtkFishbowl       *fishbowl);
53 void       gtk_fishbowl_set_count         (GtkFishbowl       *fishbowl,
54                                            guint              count);
55 gboolean   gtk_fishbowl_get_animating     (GtkFishbowl       *fishbowl);
56 void       gtk_fishbowl_set_animating     (GtkFishbowl       *fishbowl,
57                                            gboolean           animating);
58 gboolean   gtk_fishbowl_get_benchmark     (GtkFishbowl       *fishbowl);
59 void       gtk_fishbowl_set_benchmark     (GtkFishbowl       *fishbowl,
60                                            gboolean           animating);
61 double     gtk_fishbowl_get_framerate     (GtkFishbowl       *fishbowl);
62 gint64     gtk_fishbowl_get_update_delay  (GtkFishbowl       *fishbowl);
63 void       gtk_fishbowl_set_update_delay  (GtkFishbowl       *fishbowl,
64                                            gint64             update_delay);
65 void       gtk_fishbowl_set_creation_func (GtkFishbowl       *fishbowl,
66                                            GtkFishCreationFunc creation_func);
67 
68 G_END_DECLS
69 
70 #include "config.h"
71 
72 #include <math.h>
73 
74 typedef struct _GtkFishbowlPrivate       GtkFishbowlPrivate;
75 typedef struct _GtkFishbowlChild         GtkFishbowlChild;
76 
77 struct _GtkFishbowlPrivate
78 {
79   GtkFishCreationFunc creation_func;
80   GList *children;
81   guint count;
82 
83   gint64 last_frame_time;
84   gint64 update_delay;
85   guint tick_id;
86 
87   double framerate;
88   int last_benchmark_change;
89 
90   guint benchmark : 1;
91 };
92 
93 struct _GtkFishbowlChild
94 {
95   GtkWidget *widget;
96   double x;
97   double y;
98   double dx;
99   double dy;
100 };
101 
102 enum {
103    PROP_0,
104    PROP_ANIMATING,
105    PROP_BENCHMARK,
106    PROP_COUNT,
107    PROP_FRAMERATE,
108    PROP_UPDATE_DELAY,
109    NUM_PROPERTIES
110 };
111 
112 static GParamSpec *props[NUM_PROPERTIES] = { NULL, };
113 
G_DEFINE_TYPE_WITH_PRIVATE(GtkFishbowl,gtk_fishbowl,GTK_TYPE_CONTAINER)114 G_DEFINE_TYPE_WITH_PRIVATE (GtkFishbowl, gtk_fishbowl, GTK_TYPE_CONTAINER)
115 
116 static void
117 gtk_fishbowl_init (GtkFishbowl *fishbowl)
118 {
119   GtkFishbowlPrivate *priv = gtk_fishbowl_get_instance_private (fishbowl);
120 
121   gtk_widget_set_has_window (GTK_WIDGET (fishbowl), FALSE);
122 
123   priv->update_delay = G_USEC_PER_SEC;
124 }
125 
126 /**
127  * gtk_fishbowl_new:
128  *
129  * Creates a new #GtkFishbowl.
130  *
131  * Returns: a new #GtkFishbowl.
132  */
133 GtkWidget*
gtk_fishbowl_new(void)134 gtk_fishbowl_new (void)
135 {
136   return g_object_new (GTK_TYPE_FISHBOWL, NULL);
137 }
138 
139 static void
gtk_fishbowl_get_preferred_width(GtkWidget * widget,int * minimum,int * natural)140 gtk_fishbowl_get_preferred_width (GtkWidget *widget,
141                                   int       *minimum,
142                                   int       *natural)
143 {
144   GtkFishbowl *fishbowl = GTK_FISHBOWL (widget);
145   GtkFishbowlPrivate *priv = gtk_fishbowl_get_instance_private (fishbowl);
146   GtkFishbowlChild *child;
147   GList *children;
148   gint child_min, child_nat;
149 
150   *minimum = 0;
151   *natural = 0;
152 
153   for (children = priv->children; children; children = children->next)
154     {
155       child = children->data;
156 
157       if (!gtk_widget_get_visible (child->widget))
158         continue;
159 
160       gtk_widget_get_preferred_width (child->widget, &child_min, &child_nat);
161 
162       *minimum = MAX (*minimum, child_min);
163       *natural = MAX (*natural, child_nat);
164     }
165 }
166 
167 static void
gtk_fishbowl_get_preferred_height(GtkWidget * widget,int * minimum,int * natural)168 gtk_fishbowl_get_preferred_height (GtkWidget *widget,
169                                    int       *minimum,
170                                    int       *natural)
171 {
172   GtkFishbowl *fishbowl = GTK_FISHBOWL (widget);
173   GtkFishbowlPrivate *priv = gtk_fishbowl_get_instance_private (fishbowl);
174   GtkFishbowlChild *child;
175   GList *children;
176   gint child_min, child_nat;
177 
178   *minimum = 0;
179   *natural = 0;
180 
181   for (children = priv->children; children; children = children->next)
182     {
183       int min_width;
184 
185       child = children->data;
186 
187       if (!gtk_widget_get_visible (child->widget))
188         continue;
189 
190       gtk_widget_get_preferred_width (child->widget, &min_width, NULL);
191       gtk_widget_get_preferred_height_for_width (child->widget, min_width, &child_min, &child_nat);
192 
193       *minimum = MAX (*minimum, child_min);
194       *natural = MAX (*natural, child_nat);
195     }
196 }
197 
198 static void
gtk_fishbowl_size_allocate(GtkWidget * widget,GtkAllocation * allocation)199 gtk_fishbowl_size_allocate (GtkWidget     *widget,
200                             GtkAllocation *allocation)
201 {
202   GtkFishbowl *fishbowl = GTK_FISHBOWL (widget);
203   GtkFishbowlPrivate *priv = gtk_fishbowl_get_instance_private (fishbowl);
204   GtkFishbowlChild *child;
205   GtkAllocation child_allocation;
206   GtkRequisition child_requisition;
207   GList *children;
208 
209   for (children = priv->children; children; children = children->next)
210     {
211       child = children->data;
212 
213       if (!gtk_widget_get_visible (child->widget))
214         continue;
215 
216       gtk_widget_get_preferred_size (child->widget, &child_requisition, NULL);
217       child_allocation.x = allocation->x + round (child->x * (allocation->width - child_requisition.width));
218       child_allocation.y = allocation->y + round (child->y * (allocation->height - child_requisition.height));
219       child_allocation.width = child_requisition.width;
220       child_allocation.height = child_requisition.height;
221 
222       gtk_widget_size_allocate (child->widget, &child_allocation);
223     }
224 }
225 
226 static double
new_speed(void)227 new_speed (void)
228 {
229   /* 5s to 50s to cross screen seems fair */
230   return g_random_double_range (0.02, 0.2);
231 }
232 
233 static void
gtk_fishbowl_add(GtkContainer * container,GtkWidget * widget)234 gtk_fishbowl_add (GtkContainer *container,
235                   GtkWidget    *widget)
236 {
237   GtkFishbowl *fishbowl = GTK_FISHBOWL (container);
238   GtkFishbowlPrivate *priv = gtk_fishbowl_get_instance_private (fishbowl);
239   GtkFishbowlChild *child_info;
240 
241   g_return_if_fail (GTK_IS_FISHBOWL (fishbowl));
242   g_return_if_fail (GTK_IS_WIDGET (widget));
243 
244   child_info = g_new0 (GtkFishbowlChild, 1);
245   child_info->widget = widget;
246   child_info->x = 0;
247   child_info->y = 0;
248   child_info->dx = new_speed ();
249   child_info->dy = new_speed ();
250 
251   gtk_widget_set_parent (widget, GTK_WIDGET (fishbowl));
252 
253   priv->children = g_list_prepend (priv->children, child_info);
254   priv->count++;
255   g_object_notify_by_pspec (G_OBJECT (fishbowl), props[PROP_COUNT]);
256 }
257 
258 static void
gtk_fishbowl_remove(GtkContainer * container,GtkWidget * widget)259 gtk_fishbowl_remove (GtkContainer *container,
260                      GtkWidget    *widget)
261 {
262   GtkFishbowl *fishbowl = GTK_FISHBOWL (container);
263   GtkFishbowlPrivate *priv = gtk_fishbowl_get_instance_private (fishbowl);
264   GtkFishbowlChild *child;
265   GtkWidget *widget_bowl = GTK_WIDGET (fishbowl);
266   GList *children;
267 
268   for (children = priv->children; children; children = children->next)
269     {
270       child = children->data;
271 
272       if (child->widget == widget)
273         {
274           gboolean was_visible = gtk_widget_get_visible (widget);
275 
276           gtk_widget_unparent (widget);
277 
278           priv->children = g_list_remove_link (priv->children, children);
279           g_list_free (children);
280           g_free (child);
281 
282           if (was_visible && gtk_widget_get_visible (widget_bowl))
283             gtk_widget_queue_resize (widget_bowl);
284 
285           priv->count--;
286           g_object_notify_by_pspec (G_OBJECT (fishbowl), props[PROP_COUNT]);
287           break;
288         }
289     }
290 }
291 
292 
293 static void
gtk_fishbowl_forall(GtkContainer * container,gboolean include_internals,GtkCallback callback,gpointer callback_data)294 gtk_fishbowl_forall (GtkContainer *container,
295                      gboolean      include_internals,
296                      GtkCallback   callback,
297                      gpointer      callback_data)
298 {
299   GtkFishbowl *fishbowl = GTK_FISHBOWL (container);
300   GtkFishbowlPrivate *priv = gtk_fishbowl_get_instance_private (fishbowl);
301   GtkFishbowlChild *child;
302   GList *children;
303 
304   if (!include_internals)
305     return;
306 
307   children = priv->children;
308   while (children)
309     {
310       child = children->data;
311       children = children->next;
312 
313       (* callback) (child->widget, callback_data);
314     }
315 }
316 
317 static void
gtk_fishbowl_dispose(GObject * object)318 gtk_fishbowl_dispose (GObject *object)
319 {
320   GtkFishbowl *fishbowl = GTK_FISHBOWL (object);
321 
322   gtk_fishbowl_set_animating (fishbowl, FALSE);
323   gtk_fishbowl_set_count (fishbowl, 0);
324 
325   G_OBJECT_CLASS (gtk_fishbowl_parent_class)->dispose (object);
326 }
327 
328 static void
gtk_fishbowl_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)329 gtk_fishbowl_set_property (GObject         *object,
330                            guint            prop_id,
331                            const GValue    *value,
332                            GParamSpec      *pspec)
333 {
334   GtkFishbowl *fishbowl = GTK_FISHBOWL (object);
335 
336   switch (prop_id)
337     {
338     case PROP_ANIMATING:
339       gtk_fishbowl_set_animating (fishbowl, g_value_get_boolean (value));
340       break;
341 
342     case PROP_BENCHMARK:
343       gtk_fishbowl_set_benchmark (fishbowl, g_value_get_boolean (value));
344       break;
345 
346     case PROP_COUNT:
347       gtk_fishbowl_set_count (fishbowl, g_value_get_uint (value));
348       break;
349 
350     case PROP_UPDATE_DELAY:
351       gtk_fishbowl_set_update_delay (fishbowl, g_value_get_int64 (value));
352       break;
353 
354     default:
355       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
356       break;
357     }
358 }
359 
360 static void
gtk_fishbowl_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)361 gtk_fishbowl_get_property (GObject         *object,
362                            guint            prop_id,
363                            GValue          *value,
364                            GParamSpec      *pspec)
365 {
366   GtkFishbowl *fishbowl = GTK_FISHBOWL (object);
367 
368   switch (prop_id)
369     {
370     case PROP_ANIMATING:
371       g_value_set_boolean (value, gtk_fishbowl_get_animating (fishbowl));
372       break;
373 
374     case PROP_BENCHMARK:
375       g_value_set_boolean (value, gtk_fishbowl_get_benchmark (fishbowl));
376       break;
377 
378     case PROP_COUNT:
379       g_value_set_uint (value, gtk_fishbowl_get_count (fishbowl));
380       break;
381 
382     case PROP_FRAMERATE:
383       g_value_set_double (value, gtk_fishbowl_get_framerate (fishbowl));
384       break;
385 
386     case PROP_UPDATE_DELAY:
387       g_value_set_int64 (value, gtk_fishbowl_get_update_delay (fishbowl));
388       break;
389 
390     default:
391       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
392       break;
393     }
394 }
395 
396 static void
gtk_fishbowl_class_init(GtkFishbowlClass * klass)397 gtk_fishbowl_class_init (GtkFishbowlClass *klass)
398 {
399   GObjectClass *object_class = G_OBJECT_CLASS (klass);
400   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
401   GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
402 
403   object_class->dispose = gtk_fishbowl_dispose;
404   object_class->set_property = gtk_fishbowl_set_property;
405   object_class->get_property = gtk_fishbowl_get_property;
406 
407   widget_class->get_preferred_width = gtk_fishbowl_get_preferred_width;
408   widget_class->get_preferred_height = gtk_fishbowl_get_preferred_height;
409   widget_class->size_allocate = gtk_fishbowl_size_allocate;
410 
411   container_class->add = gtk_fishbowl_add;
412   container_class->remove = gtk_fishbowl_remove;
413   container_class->forall = gtk_fishbowl_forall;
414 
415   props[PROP_ANIMATING] =
416       g_param_spec_boolean ("animating",
417                             "animating",
418                             "Whether children are moving around",
419                             FALSE,
420                             G_PARAM_READWRITE);
421 
422   props[PROP_BENCHMARK] =
423       g_param_spec_boolean ("benchmark",
424                             "Benchmark",
425                             "Adapt the count property to hit the maximum framerate",
426                             FALSE,
427                             G_PARAM_READWRITE);
428 
429   props[PROP_COUNT] =
430       g_param_spec_uint ("count",
431                          "Count",
432                          "Number of widgets",
433                          0, G_MAXUINT,
434                          0,
435                          G_PARAM_READWRITE);
436 
437   props[PROP_FRAMERATE] =
438       g_param_spec_double ("framerate",
439                            "Framerate",
440                            "Framerate of this widget in frames per second",
441                            0, G_MAXDOUBLE,
442                            0,
443                            G_PARAM_READABLE);
444 
445   props[PROP_UPDATE_DELAY] =
446       g_param_spec_int64 ("update-delay",
447                           "Update delay",
448                           "Number of usecs between updates",
449                           0, G_MAXINT64,
450                           G_USEC_PER_SEC,
451                           G_PARAM_READWRITE);
452 
453   g_object_class_install_properties (object_class, NUM_PROPERTIES, props);
454 }
455 
456 guint
gtk_fishbowl_get_count(GtkFishbowl * fishbowl)457 gtk_fishbowl_get_count (GtkFishbowl *fishbowl)
458 {
459   GtkFishbowlPrivate *priv = gtk_fishbowl_get_instance_private (fishbowl);
460 
461   return priv->count;
462 }
463 
464 void
gtk_fishbowl_set_count(GtkFishbowl * fishbowl,guint count)465 gtk_fishbowl_set_count (GtkFishbowl *fishbowl,
466                         guint        count)
467 {
468   GtkFishbowlPrivate *priv = gtk_fishbowl_get_instance_private (fishbowl);
469 
470   if (priv->count == count)
471     return;
472 
473   g_object_freeze_notify (G_OBJECT (fishbowl));
474 
475   while (priv->count > count)
476     {
477       gtk_fishbowl_remove (GTK_CONTAINER (fishbowl), ((GtkFishbowlChild *) priv->children->data)->widget);
478     }
479 
480   while (priv->count < count)
481     {
482       GtkWidget *new_widget;
483 
484       new_widget = priv->creation_func ();
485 
486       gtk_widget_show (new_widget);
487 
488       gtk_fishbowl_add (GTK_CONTAINER (fishbowl), new_widget);
489     }
490 
491   g_object_thaw_notify (G_OBJECT (fishbowl));
492 }
493 
494 gboolean
gtk_fishbowl_get_benchmark(GtkFishbowl * fishbowl)495 gtk_fishbowl_get_benchmark (GtkFishbowl *fishbowl)
496 {
497   GtkFishbowlPrivate *priv = gtk_fishbowl_get_instance_private (fishbowl);
498 
499   return priv->benchmark;
500 }
501 
502 void
gtk_fishbowl_set_benchmark(GtkFishbowl * fishbowl,gboolean benchmark)503 gtk_fishbowl_set_benchmark (GtkFishbowl *fishbowl,
504                             gboolean     benchmark)
505 {
506   GtkFishbowlPrivate *priv = gtk_fishbowl_get_instance_private (fishbowl);
507 
508   if (priv->benchmark == benchmark)
509     return;
510 
511   priv->benchmark = benchmark;
512   if (!benchmark)
513     priv->last_benchmark_change = 0;
514 
515   g_object_notify_by_pspec (G_OBJECT (fishbowl), props[PROP_BENCHMARK]);
516 }
517 
518 gboolean
gtk_fishbowl_get_animating(GtkFishbowl * fishbowl)519 gtk_fishbowl_get_animating (GtkFishbowl *fishbowl)
520 {
521   GtkFishbowlPrivate *priv = gtk_fishbowl_get_instance_private (fishbowl);
522 
523   return priv->tick_id != 0;
524 }
525 
526 static gint64
guess_refresh_interval(GdkFrameClock * frame_clock)527 guess_refresh_interval (GdkFrameClock *frame_clock)
528 {
529   gint64 interval;
530   gint64 i;
531 
532   interval = G_MAXINT64;
533 
534   for (i = gdk_frame_clock_get_history_start (frame_clock);
535        i < gdk_frame_clock_get_frame_counter (frame_clock);
536        i++)
537     {
538       GdkFrameTimings *t, *before;
539       gint64 ts, before_ts;
540 
541       t = gdk_frame_clock_get_timings (frame_clock, i);
542       before = gdk_frame_clock_get_timings (frame_clock, i - 1);
543       if (t == NULL || before == NULL)
544         continue;
545 
546       ts = gdk_frame_timings_get_frame_time (t);
547       before_ts = gdk_frame_timings_get_frame_time (before);
548       if (ts == 0 || before_ts == 0)
549         continue;
550 
551       interval = MIN (interval, ts - before_ts);
552     }
553 
554   if (interval == G_MAXINT64)
555     return 0;
556 
557   return interval;
558 }
559 
560 static void
gtk_fishbowl_do_update(GtkFishbowl * fishbowl)561 gtk_fishbowl_do_update (GtkFishbowl *fishbowl)
562 {
563   GtkFishbowlPrivate *priv = gtk_fishbowl_get_instance_private (fishbowl);
564   GdkFrameClock *frame_clock;
565   GdkFrameTimings *start, *end;
566   gint64 start_counter, end_counter;
567   gint64 n_frames, expected_frames;
568   gint64 start_timestamp, end_timestamp;
569   gint64 interval;
570 
571   frame_clock = gtk_widget_get_frame_clock (GTK_WIDGET (fishbowl));
572   if (frame_clock == NULL)
573     return;
574 
575   start_counter = gdk_frame_clock_get_history_start (frame_clock);
576   end_counter = gdk_frame_clock_get_frame_counter (frame_clock);
577   start = gdk_frame_clock_get_timings (frame_clock, start_counter);
578   for (end = gdk_frame_clock_get_timings (frame_clock, end_counter);
579        end_counter > start_counter && end != NULL && !gdk_frame_timings_get_complete (end);
580        end = gdk_frame_clock_get_timings (frame_clock, end_counter))
581     end_counter--;
582   if (end_counter - start_counter < 4)
583     return;
584 
585   start_timestamp = gdk_frame_timings_get_presentation_time (start);
586   end_timestamp = gdk_frame_timings_get_presentation_time (end);
587   if (start_timestamp == 0 || end_timestamp == 0)
588     {
589       start_timestamp = gdk_frame_timings_get_frame_time (start);
590       end_timestamp = gdk_frame_timings_get_frame_time (end);
591     }
592 
593   n_frames = end_counter - start_counter;
594   priv->framerate = ((double) n_frames) * G_USEC_PER_SEC / (end_timestamp - start_timestamp);
595   g_object_notify_by_pspec (G_OBJECT (fishbowl), props[PROP_FRAMERATE]);
596 
597   if (!priv->benchmark)
598     return;
599 
600   interval = gdk_frame_timings_get_refresh_interval (end);
601   if (interval == 0)
602     {
603       interval = guess_refresh_interval (frame_clock);
604       if (interval == 0)
605         return;
606     }
607   expected_frames = round ((double) (end_timestamp - start_timestamp) / interval);
608 
609   if (n_frames >= expected_frames)
610     {
611       if (priv->last_benchmark_change > 0)
612         priv->last_benchmark_change *= 2;
613       else
614         priv->last_benchmark_change = 1;
615     }
616   else if (n_frames + 1 < expected_frames)
617     {
618       if (priv->last_benchmark_change < 0)
619         priv->last_benchmark_change--;
620       else
621         priv->last_benchmark_change = -1;
622     }
623   else
624     {
625       priv->last_benchmark_change = 0;
626     }
627 
628   gtk_fishbowl_set_count (fishbowl, MAX (1, (int) priv->count + priv->last_benchmark_change));
629 }
630 
631 static gboolean
gtk_fishbowl_tick(GtkWidget * widget,GdkFrameClock * frame_clock,gpointer unused)632 gtk_fishbowl_tick (GtkWidget     *widget,
633                    GdkFrameClock *frame_clock,
634                    gpointer       unused)
635 {
636   GtkFishbowl *fishbowl = GTK_FISHBOWL (widget);
637   GtkFishbowlPrivate *priv = gtk_fishbowl_get_instance_private (fishbowl);
638   GtkFishbowlChild *child;
639   GList *l;
640   gint64 frame_time, elapsed;
641   gboolean do_update;
642 
643   frame_time = gdk_frame_clock_get_frame_time (gtk_widget_get_frame_clock (widget));
644   elapsed = frame_time - priv->last_frame_time;
645   do_update = frame_time / priv->update_delay != priv->last_frame_time / priv->update_delay;
646   priv->last_frame_time = frame_time;
647 
648   /* last frame was 0, so we're just starting to animate */
649   if (elapsed == frame_time)
650     return G_SOURCE_CONTINUE;
651 
652   for (l = priv->children; l; l = l->next)
653     {
654       child = l->data;
655 
656       child->x += child->dx * ((double) elapsed / G_USEC_PER_SEC);
657       child->y += child->dy * ((double) elapsed / G_USEC_PER_SEC);
658 
659       if (child->x <= 0)
660         {
661           child->x = 0;
662           child->dx = new_speed ();
663         }
664       else if (child->x >= 1)
665         {
666           child->x = 1;
667           child->dx =  - new_speed ();
668         }
669 
670       if (child->y <= 0)
671         {
672           child->y = 0;
673           child->dy = new_speed ();
674         }
675       else if (child->y >= 1)
676         {
677           child->y = 1;
678           child->dy =  - new_speed ();
679         }
680     }
681 
682   gtk_widget_queue_allocate (widget);
683 
684   if (do_update)
685     gtk_fishbowl_do_update (fishbowl);
686 
687   return G_SOURCE_CONTINUE;
688 }
689 
690 void
gtk_fishbowl_set_animating(GtkFishbowl * fishbowl,gboolean animating)691 gtk_fishbowl_set_animating (GtkFishbowl *fishbowl,
692                             gboolean     animating)
693 {
694   GtkFishbowlPrivate *priv = gtk_fishbowl_get_instance_private (fishbowl);
695 
696   if (gtk_fishbowl_get_animating (fishbowl) == animating)
697     return;
698 
699   if (animating)
700     {
701       priv->tick_id = gtk_widget_add_tick_callback (GTK_WIDGET (fishbowl),
702                                                     gtk_fishbowl_tick,
703                                                     NULL,
704                                                     NULL);
705     }
706   else
707     {
708       priv->last_frame_time = 0;
709       gtk_widget_remove_tick_callback (GTK_WIDGET (fishbowl), priv->tick_id);
710       priv->tick_id = 0;
711       priv->framerate = 0;
712       g_object_notify_by_pspec (G_OBJECT (fishbowl), props[PROP_FRAMERATE]);
713     }
714 
715   g_object_notify_by_pspec (G_OBJECT (fishbowl), props[PROP_ANIMATING]);
716 }
717 
718 double
gtk_fishbowl_get_framerate(GtkFishbowl * fishbowl)719 gtk_fishbowl_get_framerate (GtkFishbowl *fishbowl)
720 {
721   GtkFishbowlPrivate *priv = gtk_fishbowl_get_instance_private (fishbowl);
722 
723   return priv->framerate;
724 }
725 
726 gint64
gtk_fishbowl_get_update_delay(GtkFishbowl * fishbowl)727 gtk_fishbowl_get_update_delay (GtkFishbowl *fishbowl)
728 {
729   GtkFishbowlPrivate *priv = gtk_fishbowl_get_instance_private (fishbowl);
730 
731   return priv->update_delay;
732 }
733 
734 void
gtk_fishbowl_set_update_delay(GtkFishbowl * fishbowl,gint64 update_delay)735 gtk_fishbowl_set_update_delay (GtkFishbowl *fishbowl,
736                                gint64       update_delay)
737 {
738   GtkFishbowlPrivate *priv = gtk_fishbowl_get_instance_private (fishbowl);
739 
740   if (priv->update_delay == update_delay)
741     return;
742 
743   priv->update_delay = update_delay;
744 
745   g_object_notify_by_pspec (G_OBJECT (fishbowl), props[PROP_UPDATE_DELAY]);
746 }
747 
748 void
gtk_fishbowl_set_creation_func(GtkFishbowl * fishbowl,GtkFishCreationFunc creation_func)749 gtk_fishbowl_set_creation_func (GtkFishbowl         *fishbowl,
750                                 GtkFishCreationFunc  creation_func)
751 {
752   GtkFishbowlPrivate *priv = gtk_fishbowl_get_instance_private (fishbowl);
753 
754   g_object_freeze_notify (G_OBJECT (fishbowl));
755 
756   gtk_fishbowl_set_count (fishbowl, 0);
757   priv->last_benchmark_change = 0;
758 
759   priv->creation_func = creation_func;
760 
761   gtk_fishbowl_set_count (fishbowl, 1);
762 
763   g_object_thaw_notify (G_OBJECT (fishbowl));
764 }
765 
766 #endif /* __GTK_FISHBOWL_H__ */
767