1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
3
* Copyright (c) 2013 Red Hat, Inc.
5
* This program is free software; you can redistribute it and/or modify
6
* it under the terms of the GNU Lesser General Public License as published by
7
* the Free Software Foundation; either version 2 of the License, or (at your
8
* option) any later version.
10
* This program is distributed in the hope that it will be useful, but
11
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
13
* License for more details.
15
* You should have received a copy of the GNU Lesser General Public License
16
* along with this program; if not, write to the Free Software Foundation,
17
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19
* Author: Alexander Larsson <alexl@redhat.com>
24
#include "gd-revealer.h"
35
#define FRAME_TIME_MSEC 17 /* 17 msec => 60 fps */
37
struct _GdRevealerPrivate {
38
GtkOrientation orientation;
41
GdkWindow* bin_window;
42
GdkWindow* view_window;
53
#define GTK_PARAM_READWRITE G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB
54
#define GD_REVEALER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GD_TYPE_REVEALER, GdRevealerPrivate))
56
static void gd_revealer_real_realize (GtkWidget *widget);
57
static void gd_revealer_real_unrealize (GtkWidget *widget);
58
static void gd_revealer_real_add (GtkContainer *widget,
60
static void gd_revealer_real_style_updated (GtkWidget *widget);
61
static void gd_revealer_real_size_allocate (GtkWidget *widget,
62
GtkAllocation *allocation);
63
static void gd_revealer_real_map (GtkWidget *widget);
64
static void gd_revealer_real_unmap (GtkWidget *widget);
65
static gboolean gd_revealer_real_draw (GtkWidget *widget,
67
static void gd_revealer_real_get_preferred_height (GtkWidget *widget,
69
gint *natural_height);
70
static void gd_revealer_real_get_preferred_height_for_width (GtkWidget *widget,
73
gint *natural_height);
74
static void gd_revealer_real_get_preferred_width (GtkWidget *widget,
77
static void gd_revealer_real_get_preferred_width_for_height (GtkWidget *widget,
82
G_DEFINE_TYPE(GdRevealer, gd_revealer, GTK_TYPE_BIN);
85
gd_revealer_init (GdRevealer *revealer)
87
GdRevealerPrivate *priv;
89
priv = GD_REVEALER_GET_PRIVATE (revealer);
90
revealer->priv = priv;
92
priv->orientation = GTK_ORIENTATION_HORIZONTAL;
94
priv->current_pos = 0.0;
95
priv->target_pos = 0.0;
97
gtk_widget_set_has_window ((GtkWidget*) revealer, TRUE);
98
gtk_widget_set_redraw_on_allocate ((GtkWidget*) revealer, FALSE);
102
gd_revealer_finalize (GObject* obj)
104
GdRevealer *revealer = GD_REVEALER (obj);
105
GdRevealerPrivate *priv = revealer->priv;
107
if (priv->tick_id != 0)
108
gtk_widget_remove_tick_callback (GTK_WIDGET (revealer), priv->tick_id);
111
G_OBJECT_CLASS (gd_revealer_parent_class)->finalize (obj);
115
gd_revealer_get_property (GObject *object,
120
GdRevealer *revealer = GD_REVEALER (object);
122
switch (property_id) {
123
case PROP_ORIENTATION:
124
g_value_set_enum (value, gd_revealer_get_orientation (revealer));
127
g_value_set_int (value, gd_revealer_get_duration (revealer));
129
case PROP_REVEAL_CHILD:
130
g_value_set_boolean (value, gd_revealer_get_reveal_child (revealer));
132
case PROP_CHILD_REVEALED:
133
g_value_set_boolean (value, gd_revealer_get_child_revealed (revealer));
136
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
142
gd_revealer_set_property (GObject *object,
147
GdRevealer *revealer = GD_REVEALER (object);
149
switch (property_id) {
150
case PROP_ORIENTATION:
151
gd_revealer_set_orientation (revealer, g_value_get_enum (value));
154
gd_revealer_set_duration (revealer, g_value_get_int (value));
156
case PROP_REVEAL_CHILD:
157
gd_revealer_set_reveal_child (revealer, g_value_get_boolean (value));
160
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
166
gd_revealer_class_init (GdRevealerClass * klass)
168
GObjectClass *object_class = G_OBJECT_CLASS (klass);
169
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
170
GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
172
object_class->get_property = gd_revealer_get_property;
173
object_class->set_property = gd_revealer_set_property;
174
object_class->finalize = gd_revealer_finalize;
176
widget_class->realize = gd_revealer_real_realize;
177
widget_class->unrealize = gd_revealer_real_unrealize;
178
widget_class->style_updated = gd_revealer_real_style_updated;
179
widget_class->size_allocate = gd_revealer_real_size_allocate;
180
widget_class->map = gd_revealer_real_map;
181
widget_class->unmap = gd_revealer_real_unmap;
182
widget_class->draw = gd_revealer_real_draw;
183
widget_class->get_preferred_height = gd_revealer_real_get_preferred_height;
184
widget_class->get_preferred_height_for_width = gd_revealer_real_get_preferred_height_for_width;
185
widget_class->get_preferred_width = gd_revealer_real_get_preferred_width;
186
widget_class->get_preferred_width_for_height = gd_revealer_real_get_preferred_width_for_height;
188
container_class->add = gd_revealer_real_add;
190
g_object_class_install_property (object_class,
192
g_param_spec_enum ("orientation", "orientation",
193
"The orientation of the widget",
194
GTK_TYPE_ORIENTATION,
195
GTK_ORIENTATION_HORIZONTAL,
196
GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
197
g_object_class_install_property (object_class,
199
g_param_spec_int ("duration", "duration",
200
"The animation duration, in milliseconds",
203
GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
204
g_object_class_install_property (object_class,
206
g_param_spec_boolean ("reveal-child", "Reveal Child",
207
"Whether the container should reveal the child",
209
GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
211
g_object_class_install_property (object_class,
213
g_param_spec_boolean ("child-revealed", "Child Revealed",
214
"Whether the child is revealed and the animation target reached",
218
g_type_class_add_private (klass, sizeof (GdRevealerPrivate));
223
gd_revealer_new (void)
225
return g_object_new (GD_TYPE_REVEALER, NULL);
229
gd_revealer_get_child_allocation (GdRevealer *revealer,
230
GtkAllocation* allocation,
231
GtkAllocation* child_allocation)
234
GdRevealerPrivate *priv;
236
g_return_if_fail (revealer != NULL);
237
g_return_if_fail (allocation != NULL);
239
priv = revealer->priv;
241
child_allocation->x = 0;
242
child_allocation->y = 0;
243
child_allocation->width = allocation->width;
244
child_allocation->height = allocation->height;
246
child = gtk_bin_get_child (GTK_BIN (revealer));
247
if (child != NULL && gtk_widget_get_visible (child))
249
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
250
gtk_widget_get_preferred_height_for_width (child, child_allocation->width, NULL,
251
&child_allocation->height);
253
gtk_widget_get_preferred_width_for_height (child, child_allocation->height, NULL,
254
&child_allocation->width);
259
gd_revealer_real_realize (GtkWidget *widget)
261
GdRevealer *revealer = GD_REVEALER (widget);
262
GdRevealerPrivate *priv = revealer->priv;
263
GtkAllocation allocation;
264
GdkWindowAttr attributes = { 0 };
265
GdkWindowAttributesType attributes_mask;
266
GtkAllocation child_allocation;
268
GtkStyleContext *context;
270
gtk_widget_set_realized (widget, TRUE);
272
gtk_widget_get_allocation (widget, &allocation);
274
attributes.x = allocation.x;
275
attributes.y = allocation.y;
276
attributes.width = allocation.width;
277
attributes.height = allocation.height;
278
attributes.window_type = GDK_WINDOW_CHILD;
279
attributes.wclass = GDK_INPUT_OUTPUT;
280
attributes.visual = gtk_widget_get_visual (widget);
281
attributes.event_mask =
282
gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK;
283
attributes_mask = (GDK_WA_X | GDK_WA_Y) | GDK_WA_VISUAL;
286
gdk_window_new (gtk_widget_get_parent_window ((GtkWidget*) revealer),
287
&attributes, attributes_mask);
288
gtk_widget_set_window (widget, priv->view_window);
289
gtk_widget_register_window (widget, priv->view_window);
291
gd_revealer_get_child_allocation (revealer, &allocation, &child_allocation);
295
attributes.width = child_allocation.width;
296
attributes.height = child_allocation.height;
298
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
299
attributes.y = allocation.height - child_allocation.height;
301
attributes.x = allocation.width - child_allocation.width;
304
gdk_window_new (priv->view_window, &attributes, attributes_mask);
305
gtk_widget_register_window (widget, priv->bin_window);
307
child = gtk_bin_get_child (GTK_BIN (revealer));
309
gtk_widget_set_parent_window (child, priv->bin_window);
311
context = gtk_widget_get_style_context (widget);
312
gtk_style_context_set_background (context, priv->view_window);
313
gtk_style_context_set_background (context, priv->bin_window);
314
gdk_window_show (priv->bin_window);
319
gd_revealer_real_unrealize (GtkWidget* widget)
321
GdRevealer *revealer = GD_REVEALER (widget);
322
GdRevealerPrivate *priv = revealer->priv;
324
gtk_widget_unregister_window (widget, priv->bin_window);
325
gdk_window_destroy (priv->bin_window);
326
priv->view_window = NULL;
328
GTK_WIDGET_CLASS (gd_revealer_parent_class)->unrealize (widget);
333
gd_revealer_real_add (GtkContainer* container,
336
GdRevealer *revealer = GD_REVEALER (container);
337
GdRevealerPrivate *priv = revealer->priv;
339
g_return_if_fail (child != NULL);
341
gtk_widget_set_parent_window (child, priv->bin_window);
342
gtk_widget_set_child_visible (child, priv->current_pos != 0.0);
344
GTK_CONTAINER_CLASS (gd_revealer_parent_class)->add (container, child);
349
gd_revealer_real_style_updated (GtkWidget* widget)
351
GdRevealer *revealer = GD_REVEALER (widget);
352
GdRevealerPrivate *priv = revealer->priv;
353
GtkStyleContext* context;
355
GTK_WIDGET_CLASS (gd_revealer_parent_class)->style_updated (widget);
357
if (gtk_widget_get_realized (widget))
359
context = gtk_widget_get_style_context (widget);
360
gtk_style_context_set_background (context, priv->bin_window);
361
gtk_style_context_set_background (context, priv->view_window);
367
gd_revealer_real_size_allocate (GtkWidget* widget,
368
GtkAllocation* allocation)
370
GdRevealer *revealer = GD_REVEALER (widget);
371
GdRevealerPrivate *priv = revealer->priv;
372
GtkAllocation child_allocation;
374
gboolean window_visible;
377
g_return_if_fail (allocation != NULL);
379
gtk_widget_set_allocation (widget, allocation);
380
gd_revealer_get_child_allocation (revealer, allocation, &child_allocation);
382
child = gtk_bin_get_child (GTK_BIN (revealer));
384
gtk_widget_get_visible (child))
385
gtk_widget_size_allocate (child, &child_allocation);
387
if (gtk_widget_get_realized (widget))
389
if (gtk_widget_get_mapped (widget))
392
allocation->width > 0 && allocation->height > 0;
394
if (!window_visible &&
395
gdk_window_is_visible (priv->view_window))
396
gdk_window_hide (priv->view_window);
398
if (window_visible &&
399
!gdk_window_is_visible (priv->view_window))
400
gdk_window_show (priv->view_window);
403
gdk_window_move_resize (priv->view_window,
404
allocation->x, allocation->y,
405
allocation->width, allocation->height);
409
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
410
bin_y = allocation->height - child_allocation.height;
412
bin_x = allocation->width - child_allocation.width;
414
gdk_window_move_resize (priv->bin_window,
416
child_allocation.width, child_allocation.height);
422
gd_revealer_set_position (GdRevealer *revealer,
425
GdRevealerPrivate *priv = revealer->priv;
426
gboolean new_visible;
429
priv->current_pos = pos;
431
/* We check target_pos here too, because we want to ensure we set
432
* child_visible immediately when starting a reveal operation
433
* otherwise the child widgets will not be properly realized
434
* after the reveal returns.
436
new_visible = priv->current_pos != 0.0 || priv->target_pos != 0.0;
438
child = gtk_bin_get_child (GTK_BIN (revealer));
440
new_visible != gtk_widget_get_child_visible (child))
441
gtk_widget_set_child_visible (child, new_visible);
443
gtk_widget_queue_resize (GTK_WIDGET (revealer));
445
if (priv->current_pos == priv->target_pos)
446
g_object_notify (G_OBJECT (revealer), "child-revealed");
450
ease_out_quad (gdouble t, gdouble d)
453
return ((-1.0) * p) * (p - 2);
457
gd_revealer_animate_step (GdRevealer *revealer,
460
GdRevealerPrivate *priv = revealer->priv;
464
if (now < priv->end_time)
465
t = (now - priv->start_time) / (double) (priv->end_time - priv->start_time);
466
t = ease_out_quad (t, 1.0);
468
gd_revealer_set_position (revealer,
469
priv->source_pos + (t * (priv->target_pos - priv->source_pos)));
473
gd_revealer_animate_cb (GdRevealer *revealer,
474
GdkFrameClock *frame_clock,
477
GdRevealerPrivate *priv = revealer->priv;
480
now = gdk_frame_clock_get_frame_time (frame_clock);
481
gd_revealer_animate_step (revealer, now);
482
if (priv->current_pos == priv->target_pos)
492
gd_revealer_start_animation (GdRevealer *revealer,
495
GdRevealerPrivate *priv = revealer->priv;
496
GtkWidget *widget = GTK_WIDGET (revealer);
498
if (priv->target_pos == target)
501
priv->target_pos = target;
502
g_object_notify (G_OBJECT (revealer), "reveal-child");
504
if (gtk_widget_get_mapped (widget))
506
priv->source_pos = priv->current_pos;
507
priv->start_time = gdk_frame_clock_get_frame_time (gtk_widget_get_frame_clock (widget));
508
priv->end_time = priv->start_time + (priv->duration * 1000);
509
if (priv->tick_id == 0)
511
gtk_widget_add_tick_callback (widget, (GtkTickCallback)gd_revealer_animate_cb, revealer, NULL);
512
gd_revealer_animate_step (revealer, priv->start_time);
516
gd_revealer_set_position (revealer, target);
522
gd_revealer_stop_animation (GdRevealer *revealer)
524
GdRevealerPrivate *priv = revealer->priv;
526
priv->current_pos = priv->target_pos;
527
if (priv->tick_id != 0)
529
gtk_widget_remove_tick_callback (GTK_WIDGET (revealer), priv->tick_id);
536
gd_revealer_real_map (GtkWidget *widget)
538
GdRevealer *revealer = GD_REVEALER (widget);
539
GdRevealerPrivate *priv = revealer->priv;
540
GtkAllocation allocation;
542
if (!gtk_widget_get_mapped (widget))
544
gtk_widget_get_allocation (widget, &allocation);
546
if (allocation.width > 0 && allocation.height > 0)
547
gdk_window_show (priv->view_window);
549
gd_revealer_start_animation (revealer, priv->target_pos);
552
GTK_WIDGET_CLASS (gd_revealer_parent_class)->map (widget);
556
gd_revealer_real_unmap (GtkWidget *widget)
558
GdRevealer *revealer = GD_REVEALER (widget);
560
GTK_WIDGET_CLASS (gd_revealer_parent_class)->unmap (widget);
562
gd_revealer_stop_animation (revealer);
567
gd_revealer_real_draw (GtkWidget *widget,
570
GdRevealer *revealer = GD_REVEALER (widget);
571
GdRevealerPrivate *priv = revealer->priv;
573
if (gtk_cairo_should_draw_window (cr, priv->bin_window))
574
GTK_WIDGET_CLASS (gd_revealer_parent_class)->draw (widget, cr);
580
gd_revealer_set_reveal_child (GdRevealer *revealer,
583
g_return_if_fail (GD_IS_REVEALER (revealer));
586
gd_revealer_start_animation (revealer, 1.0);
588
gd_revealer_start_animation (revealer, 0.0);
592
gd_revealer_get_reveal_child (GdRevealer *revealer)
594
g_return_val_if_fail (GD_IS_REVEALER (revealer), FALSE);
596
return revealer->priv->target_pos != 0.0;
600
gd_revealer_get_child_revealed (GdRevealer *revealer)
602
gboolean animation_finished = (revealer->priv->target_pos == revealer->priv->current_pos);
603
gboolean reveal_child = gd_revealer_get_reveal_child (revealer);
605
if (animation_finished)
608
return !reveal_child;
611
/* These all report only the natural size, ignoring the minimal size,
612
* because its not really possible to allocate the right size during
613
* animation if the child size can change (without the child
614
* re-arranging itself during the animation).
618
gd_revealer_real_get_preferred_height (GtkWidget* widget,
619
gint* minimum_height_out,
620
gint* natural_height_out)
622
GdRevealer *revealer = GD_REVEALER (widget);
623
GdRevealerPrivate *priv = revealer->priv;
627
GTK_WIDGET_CLASS (gd_revealer_parent_class)->get_preferred_height (widget, &minimum_height, &natural_height);
629
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
630
natural_height = round (natural_height * priv->current_pos);
632
minimum_height = natural_height;
634
if (minimum_height_out)
635
*minimum_height_out = minimum_height;
636
if (natural_height_out)
637
*natural_height_out = natural_height;
641
gd_revealer_real_get_preferred_height_for_width (GtkWidget* widget,
643
gint* minimum_height_out,
644
gint* natural_height_out)
646
GdRevealer *revealer = GD_REVEALER (widget);
647
GdRevealerPrivate *priv = revealer->priv;
651
GTK_WIDGET_CLASS (gd_revealer_parent_class)->get_preferred_height_for_width (widget, width, &minimum_height, &natural_height);
652
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
653
natural_height = round (natural_height * priv->current_pos);
655
minimum_height = natural_height;
657
if (minimum_height_out)
658
*minimum_height_out = minimum_height;
659
if (natural_height_out)
660
*natural_height_out = natural_height;
664
gd_revealer_real_get_preferred_width (GtkWidget* widget,
665
gint* minimum_width_out,
666
gint* natural_width_out)
668
GdRevealer *revealer = GD_REVEALER (widget);
669
GdRevealerPrivate *priv = revealer->priv;
673
GTK_WIDGET_CLASS (gd_revealer_parent_class)->get_preferred_width (widget, &minimum_width, &natural_width);
675
if (priv->orientation == GTK_ORIENTATION_VERTICAL)
676
natural_width = round (natural_width * priv->current_pos);
678
minimum_width = natural_width;
680
if (minimum_width_out)
681
*minimum_width_out = minimum_width;
682
if (natural_width_out)
683
*natural_width_out = natural_width;
687
gd_revealer_real_get_preferred_width_for_height (GtkWidget* widget,
689
gint* minimum_width_out,
690
gint* natural_width_out)
692
GdRevealer *revealer = GD_REVEALER (widget);
693
GdRevealerPrivate *priv = revealer->priv;
697
GTK_WIDGET_CLASS (gd_revealer_parent_class)->get_preferred_width_for_height (widget, height, &minimum_width, &natural_width);
699
if (priv->orientation == GTK_ORIENTATION_VERTICAL)
700
natural_width = round (natural_width * priv->current_pos);
702
minimum_width = natural_width;
704
if (minimum_width_out)
705
*minimum_width_out = minimum_width;
706
if (natural_width_out)
707
*natural_width_out = natural_width;
711
gd_revealer_get_orientation (GdRevealer *revealer)
713
g_return_val_if_fail (revealer != NULL, 0);
715
return revealer->priv->orientation;
719
gd_revealer_set_orientation (GdRevealer *revealer,
720
GtkOrientation value)
722
g_return_if_fail (revealer != NULL);
724
revealer->priv->orientation = value;
725
g_object_notify (G_OBJECT (revealer), "orientation");
729
gd_revealer_get_duration (GdRevealer *revealer)
731
g_return_val_if_fail (revealer != NULL, 0);
733
return revealer->priv->duration;
737
gd_revealer_set_duration (GdRevealer *revealer,
740
g_return_if_fail (revealer != NULL);
742
revealer->priv->duration = value;
743
g_object_notify (G_OBJECT (revealer), "duration");