]> Pileus Git - ~andy/gtk/blob - gdk/gdkframeclockidle.c
GdkFrameClock: add freeze/thaw
[~andy/gtk] / gdk / gdkframeclockidle.c
1 /* GDK - The GIMP Drawing Kit
2  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 /*
21  * Modified by the GTK+ Team and others 1997-2010.  See the AUTHORS
22  * file for a list of people on the GTK+ Team.  See the ChangeLog
23  * files for a list of changes.  These files are distributed with
24  * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
25  */
26
27 #include "config.h"
28
29 #include "gdkframeclockidle.h"
30 #include "gdk.h"
31
32 struct _GdkFrameClockIdlePrivate
33 {
34   GTimer *timer;
35   /* timer_base is used to avoid ever going backward */
36   guint64 timer_base;
37   guint64 frame_time;
38
39   guint idle_id;
40   guint freeze_count;
41
42   GdkFrameClockPhase requested;
43   GdkFrameClockPhase phase;
44 };
45
46 static gboolean gdk_frame_clock_paint_idle (void *data);
47
48 static void gdk_frame_clock_idle_finalize             (GObject                *object);
49 static void gdk_frame_clock_idle_interface_init       (GdkFrameClockInterface *iface);
50
51 G_DEFINE_TYPE_WITH_CODE (GdkFrameClockIdle, gdk_frame_clock_idle, G_TYPE_OBJECT,
52                          G_IMPLEMENT_INTERFACE (GDK_TYPE_FRAME_CLOCK,
53                                                 gdk_frame_clock_idle_interface_init))
54
55 static void
56 gdk_frame_clock_idle_class_init (GdkFrameClockIdleClass *klass)
57 {
58   GObjectClass *gobject_class = (GObjectClass*) klass;
59
60   gobject_class->finalize     = gdk_frame_clock_idle_finalize;
61
62   g_type_class_add_private (klass, sizeof (GdkFrameClockIdlePrivate));
63 }
64
65 static void
66 gdk_frame_clock_idle_init (GdkFrameClockIdle *frame_clock_idle)
67 {
68   GdkFrameClockIdlePrivate *priv;
69
70   frame_clock_idle->priv = G_TYPE_INSTANCE_GET_PRIVATE (frame_clock_idle,
71                                                         GDK_TYPE_FRAME_CLOCK_IDLE,
72                                                         GdkFrameClockIdlePrivate);
73   priv = frame_clock_idle->priv;
74
75   priv->timer = g_timer_new ();
76   priv->freeze_count = 0;
77 }
78
79 static void
80 gdk_frame_clock_idle_finalize (GObject *object)
81 {
82   GdkFrameClockIdlePrivate *priv = GDK_FRAME_CLOCK_IDLE (object)->priv;
83
84   g_timer_destroy (priv->timer);
85
86   G_OBJECT_CLASS (gdk_frame_clock_idle_parent_class)->finalize (object);
87 }
88
89 static guint64
90 compute_frame_time (GdkFrameClockIdle *idle)
91 {
92   GdkFrameClockIdlePrivate *priv = idle->priv;
93   guint64 computed_frame_time;
94   guint64 elapsed;
95
96   elapsed = ((guint64) (g_timer_elapsed (priv->timer, NULL) * 1000)) + priv->timer_base;
97   if (elapsed < priv->frame_time)
98     {
99       /* clock went backward. adapt to that by forevermore increasing
100        * timer_base.  For now, assume we've gone forward in time 1ms.
101        */
102       /* hmm. just fix GTimer? */
103       computed_frame_time = priv->frame_time + 1;
104       priv->timer_base += (priv->frame_time - elapsed) + 1;
105     }
106   else
107     {
108       computed_frame_time = elapsed;
109     }
110
111   return computed_frame_time;
112 }
113
114 static guint64
115 gdk_frame_clock_idle_get_frame_time (GdkFrameClock *clock)
116 {
117   GdkFrameClockIdlePrivate *priv = GDK_FRAME_CLOCK_IDLE (clock)->priv;
118   guint64 computed_frame_time;
119
120   /* can't change frame time during a paint */
121   if (priv->phase != GDK_FRAME_CLOCK_PHASE_NONE)
122     return priv->frame_time;
123
124   /* Outside a paint, pick something close to "now" */
125   computed_frame_time = compute_frame_time (GDK_FRAME_CLOCK_IDLE (clock));
126
127   /* 16ms is 60fps. We only update frame time that often because we'd
128    * like to try to keep animations on the same start times.
129    * get_frame_time() would normally be used outside of a paint to
130    * record an animation start time for example.
131    */
132   if ((computed_frame_time - priv->frame_time) > 16)
133     priv->frame_time = computed_frame_time;
134
135   return priv->frame_time;
136 }
137
138 static void
139 maybe_start_idle (GdkFrameClockIdle *clock_idle)
140 {
141   GdkFrameClockIdlePrivate *priv = clock_idle->priv;
142
143   if (priv->idle_id == 0 && priv->freeze_count == 0 && priv->requested != 0)
144     {
145       priv->idle_id = gdk_threads_add_idle_full (GDK_PRIORITY_REDRAW,
146                                                  gdk_frame_clock_paint_idle,
147                                                  g_object_ref (clock_idle),
148                                                  (GDestroyNotify) g_object_unref);
149
150       gdk_frame_clock_frame_requested (GDK_FRAME_CLOCK (clock_idle));
151     }
152 }
153
154 static gboolean
155 gdk_frame_clock_paint_idle (void *data)
156 {
157   GdkFrameClock *clock = GDK_FRAME_CLOCK (data);
158   GdkFrameClockIdle *clock_idle = GDK_FRAME_CLOCK_IDLE (clock);
159   GdkFrameClockIdlePrivate *priv = clock_idle->priv;
160
161   priv->idle_id = 0;
162
163   priv->frame_time = compute_frame_time (clock_idle);
164
165   switch (priv->phase)
166     {
167     case GDK_FRAME_CLOCK_PHASE_NONE:
168     case GDK_FRAME_CLOCK_PHASE_BEFORE_PAINT:
169       if (priv->freeze_count == 0)
170         {
171           priv->phase = GDK_FRAME_CLOCK_PHASE_BEFORE_PAINT;
172           priv->requested &= ~GDK_FRAME_CLOCK_PHASE_BEFORE_PAINT;
173           /* We always emit ::before-paint and ::after-paint even if
174            * not explicitly requested, and unlike other phases,
175            * they don't get repeated if you freeze/thaw while
176            * in them. */
177           g_signal_emit_by_name (G_OBJECT (clock), "before-paint");
178           priv->phase = GDK_FRAME_CLOCK_PHASE_LAYOUT;
179         }
180     case GDK_FRAME_CLOCK_PHASE_LAYOUT:
181       if (priv->freeze_count == 0)
182         {
183           priv->phase = GDK_FRAME_CLOCK_PHASE_LAYOUT;
184           if (priv->requested & GDK_FRAME_CLOCK_PHASE_LAYOUT)
185             {
186               priv->requested &= ~GDK_FRAME_CLOCK_PHASE_LAYOUT;
187               g_signal_emit_by_name (G_OBJECT (clock), "layout");
188             }
189         }
190     case GDK_FRAME_CLOCK_PHASE_PAINT:
191       if (priv->freeze_count == 0)
192         {
193           priv->phase = GDK_FRAME_CLOCK_PHASE_PAINT;
194           if (priv->requested & GDK_FRAME_CLOCK_PHASE_PAINT)
195             {
196               priv->requested &= ~GDK_FRAME_CLOCK_PHASE_PAINT;
197               g_signal_emit_by_name (G_OBJECT (clock), "paint");
198             }
199         }
200     case GDK_FRAME_CLOCK_PHASE_AFTER_PAINT:
201       if (priv->freeze_count == 0)
202         {
203           priv->phase = GDK_FRAME_CLOCK_PHASE_AFTER_PAINT;
204           priv->requested &= ~GDK_FRAME_CLOCK_PHASE_AFTER_PAINT;
205           g_signal_emit_by_name (G_OBJECT (clock), "after-paint");
206           /* the ::after-paint phase doesn't get repeated on freeze/thaw */
207           priv->phase = GDK_FRAME_CLOCK_PHASE_NONE;
208         }
209     }
210
211   maybe_start_idle (clock_idle);
212
213   return FALSE;
214 }
215
216 static void
217 gdk_frame_clock_idle_request_phase (GdkFrameClock      *clock,
218                                     GdkFrameClockPhase  phase)
219 {
220   GdkFrameClockIdle *clock_idle = GDK_FRAME_CLOCK_IDLE (clock);
221   GdkFrameClockIdlePrivate *priv = clock_idle->priv;
222
223   priv->requested |= phase;
224   maybe_start_idle (clock_idle);
225 }
226
227 static GdkFrameClockPhase
228 gdk_frame_clock_idle_get_requested (GdkFrameClock *clock)
229 {
230   GdkFrameClockIdlePrivate *priv = GDK_FRAME_CLOCK_IDLE (clock)->priv;
231
232   return priv->requested;
233 }
234
235 static void
236 gdk_frame_clock_idle_freeze (GdkFrameClock *clock)
237 {
238   GdkFrameClockIdlePrivate *priv = GDK_FRAME_CLOCK_IDLE (clock)->priv;
239
240   priv->freeze_count++;
241
242   if (priv->freeze_count == 1)
243     {
244       if (priv->idle_id)
245         {
246           g_source_remove (priv->idle_id);
247           priv->idle_id = 0;
248         }
249     }
250 }
251
252 static void
253 gdk_frame_clock_idle_thaw (GdkFrameClock *clock)
254 {
255   GdkFrameClockIdle *clock_idle = GDK_FRAME_CLOCK_IDLE (clock);
256   GdkFrameClockIdlePrivate *priv = clock_idle->priv;
257
258   g_return_if_fail (priv->freeze_count > 0);
259
260   priv->freeze_count--;
261   if (priv->freeze_count == 0)
262     maybe_start_idle (clock_idle);
263 }
264
265 static void
266 gdk_frame_clock_idle_interface_init (GdkFrameClockInterface *iface)
267 {
268   iface->get_frame_time = gdk_frame_clock_idle_get_frame_time;
269   iface->request_phase = gdk_frame_clock_idle_request_phase;
270   iface->get_requested = gdk_frame_clock_idle_get_requested;
271   iface->freeze = gdk_frame_clock_idle_freeze;
272   iface->thaw = gdk_frame_clock_idle_thaw;
273 }
274
275 GdkFrameClock *
276 _gdk_frame_clock_idle_new (void)
277 {
278   GdkFrameClockIdle *clock;
279
280   clock = g_object_new (GDK_TYPE_FRAME_CLOCK_IDLE, NULL);
281
282   return GDK_FRAME_CLOCK (clock);
283 }