1/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2/*
3 * Copyright 2013, 2015 Red Hat, Inc.
4 *
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.
9 *
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.
14 *
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
18 *
19 * Author: Alexander Larsson <alexl@redhat.com>
20 * Carlos Soriano <csoriano@gnome.org>
21 */
22
23#include "config.h"
24
25#include "gtkrevealer.h"
26
27#include "gtkintl.h"
28#include "gtkprivate.h"
29#include "gtkprogresstrackerprivate.h"
30#include "gtksettingsprivate.h"
31#include "gtktypebuiltins.h"
32#include "gtkwidgetprivate.h"
33#include "gtkbuildable.h"
34
35/**
36 * GtkRevealer:
37 *
38 * A `GtkRevealer` animates the transition of its child from invisible to visible.
39 *
40 * The style of transition can be controlled with
41 * [method@Gtk.Revealer.set_transition_type].
42 *
43 * These animations respect the [property@Gtk.Settings:gtk-enable-animations]
44 * setting.
45 *
46 * # CSS nodes
47 *
48 * `GtkRevealer` has a single CSS node with name revealer.
49 * When styling `GtkRevealer` using CSS, remember that it only hides its contents,
50 * not itself. That means applied margin, padding and borders will be visible even
51 * when the [property@Gtk.Revealer:reveal-child] property is set to %FALSE.
52 *
53 * # Accessibility
54 *
55 * `GtkRevealer` uses the %GTK_ACCESSIBLE_ROLE_GROUP role.
56 *
57 * The child of `GtkRevealer`, if set, is always available in the accessibility
58 * tree, regardless of the state of the revealer widget.
59 */
60
61/**
62 * GtkRevealerTransitionType:
63 * @GTK_REVEALER_TRANSITION_TYPE_NONE: No transition
64 * @GTK_REVEALER_TRANSITION_TYPE_CROSSFADE: Fade in
65 * @GTK_REVEALER_TRANSITION_TYPE_SLIDE_RIGHT: Slide in from the left
66 * @GTK_REVEALER_TRANSITION_TYPE_SLIDE_LEFT: Slide in from the right
67 * @GTK_REVEALER_TRANSITION_TYPE_SLIDE_UP: Slide in from the bottom
68 * @GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN: Slide in from the top
69 * @GTK_REVEALER_TRANSITION_TYPE_SWING_RIGHT: Floop in from the left
70 * @GTK_REVEALER_TRANSITION_TYPE_SWING_LEFT: Floop in from the right
71 * @GTK_REVEALER_TRANSITION_TYPE_SWING_UP: Floop in from the bottom
72 * @GTK_REVEALER_TRANSITION_TYPE_SWING_DOWN: Floop in from the top
73 *
74 * These enumeration values describe the possible transitions
75 * when the child of a `GtkRevealer` widget is shown or hidden.
76 */
77
78struct _GtkRevealer
79{
80 GtkWidget parent_instance;
81
82 GtkWidget *child;
83
84 GtkRevealerTransitionType transition_type;
85 guint transition_duration;
86
87 double current_pos;
88 double source_pos;
89 double target_pos;
90
91 guint tick_id;
92 GtkProgressTracker tracker;
93};
94
95typedef struct
96{
97 GtkWidgetClass parent_class;
98} GtkRevealerClass;
99
100enum {
101 PROP_0,
102 PROP_TRANSITION_TYPE,
103 PROP_TRANSITION_DURATION,
104 PROP_REVEAL_CHILD,
105 PROP_CHILD_REVEALED,
106 PROP_CHILD,
107 LAST_PROP
108};
109
110static GParamSpec *props[LAST_PROP] = { NULL, };
111
112static void gtk_revealer_size_allocate (GtkWidget *widget,
113 int width,
114 int height,
115 int baseline);
116static void gtk_revealer_measure (GtkWidget *widget,
117 GtkOrientation orientation,
118 int for_size,
119 int *minimum,
120 int *natural,
121 int *minimum_baseline,
122 int *natural_baseline);
123
124static void gtk_revealer_set_position (GtkRevealer *revealer,
125 double pos);
126
127static void gtk_revealer_buildable_iface_init (GtkBuildableIface *iface);
128
129G_DEFINE_TYPE_WITH_CODE (GtkRevealer, gtk_revealer, GTK_TYPE_WIDGET,
130 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
131 gtk_revealer_buildable_iface_init))
132
133static GtkBuildableIface *parent_buildable_iface;
134
135static void
136gtk_revealer_buildable_add_child (GtkBuildable *buildable,
137 GtkBuilder *builder,
138 GObject *child,
139 const char *type)
140{
141 if (GTK_IS_WIDGET (child))
142 gtk_revealer_set_child (GTK_REVEALER (buildable), GTK_WIDGET (child));
143 else
144 parent_buildable_iface->add_child (buildable, builder, child, type);
145}
146
147static void
148gtk_revealer_buildable_iface_init (GtkBuildableIface *iface)
149{
150 parent_buildable_iface = g_type_interface_peek_parent (g_iface: iface);
151
152 iface->add_child = gtk_revealer_buildable_add_child;
153}
154
155static void
156gtk_revealer_init (GtkRevealer *revealer)
157{
158 revealer->transition_type = GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN;
159 revealer->transition_duration = 250;
160 revealer->current_pos = 0.0;
161 revealer->target_pos = 0.0;
162
163 gtk_widget_set_overflow (GTK_WIDGET (revealer), overflow: GTK_OVERFLOW_HIDDEN);
164}
165
166static void
167gtk_revealer_dispose (GObject *obj)
168{
169 GtkRevealer *revealer = GTK_REVEALER (obj);
170
171 g_clear_pointer (&revealer->child, gtk_widget_unparent);
172
173 G_OBJECT_CLASS (gtk_revealer_parent_class)->dispose (obj);
174}
175
176static void
177gtk_revealer_finalize (GObject *obj)
178{
179 GtkRevealer *revealer = GTK_REVEALER (obj);
180
181 if (revealer->tick_id != 0)
182 gtk_widget_remove_tick_callback (GTK_WIDGET (revealer), id: revealer->tick_id);
183 revealer->tick_id = 0;
184
185 G_OBJECT_CLASS (gtk_revealer_parent_class)->finalize (obj);
186}
187
188static void
189gtk_revealer_get_property (GObject *object,
190 guint property_id,
191 GValue *value,
192 GParamSpec *pspec)
193{
194 GtkRevealer *revealer = GTK_REVEALER (object);
195
196 switch (property_id)
197 {
198 case PROP_TRANSITION_TYPE:
199 g_value_set_enum (value, v_enum: gtk_revealer_get_transition_type (revealer));
200 break;
201 case PROP_TRANSITION_DURATION:
202 g_value_set_uint (value, v_uint: gtk_revealer_get_transition_duration (revealer));
203 break;
204 case PROP_REVEAL_CHILD:
205 g_value_set_boolean (value, v_boolean: gtk_revealer_get_reveal_child (revealer));
206 break;
207 case PROP_CHILD_REVEALED:
208 g_value_set_boolean (value, v_boolean: gtk_revealer_get_child_revealed (revealer));
209 break;
210 case PROP_CHILD:
211 g_value_set_object (value, v_object: gtk_revealer_get_child (revealer));
212 break;
213 default:
214 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
215 break;
216 }
217}
218
219static void
220gtk_revealer_set_property (GObject *object,
221 guint property_id,
222 const GValue *value,
223 GParamSpec *pspec)
224{
225 GtkRevealer *revealer = GTK_REVEALER (object);
226
227 switch (property_id)
228 {
229 case PROP_TRANSITION_TYPE:
230 gtk_revealer_set_transition_type (revealer, transition: g_value_get_enum (value));
231 break;
232 case PROP_TRANSITION_DURATION:
233 gtk_revealer_set_transition_duration (revealer, duration: g_value_get_uint (value));
234 break;
235 case PROP_REVEAL_CHILD:
236 gtk_revealer_set_reveal_child (revealer, reveal_child: g_value_get_boolean (value));
237 break;
238 case PROP_CHILD:
239 gtk_revealer_set_child (revealer, child: g_value_get_object (value));
240 break;
241 default:
242 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
243 break;
244 }
245}
246
247static void
248gtk_revealer_unmap (GtkWidget *widget)
249{
250 GtkRevealer *revealer = GTK_REVEALER (widget);
251
252 GTK_WIDGET_CLASS (gtk_revealer_parent_class)->unmap (widget);
253
254 /* Finish & stop the animation */
255 if (revealer->current_pos != revealer->target_pos)
256 gtk_revealer_set_position (revealer, pos: revealer->target_pos);
257
258 if (revealer->tick_id != 0)
259 {
260 gtk_widget_remove_tick_callback (GTK_WIDGET (revealer), id: revealer->tick_id);
261 revealer->tick_id = 0;
262 }
263}
264
265static void
266gtk_revealer_compute_expand (GtkWidget *widget,
267 gboolean *hexpand,
268 gboolean *vexpand)
269{
270 GtkRevealer *revealer = GTK_REVEALER (widget);
271
272 if (revealer->child)
273 {
274 *hexpand = gtk_widget_compute_expand (widget: revealer->child, orientation: GTK_ORIENTATION_HORIZONTAL);
275 *vexpand = gtk_widget_compute_expand (widget: revealer->child, orientation: GTK_ORIENTATION_VERTICAL);
276 }
277 else
278 {
279 *hexpand = FALSE;
280 *vexpand = FALSE;
281 }
282}
283
284static GtkSizeRequestMode
285gtk_revealer_get_request_mode (GtkWidget *widget)
286{
287 GtkRevealer *revealer = GTK_REVEALER (widget);
288
289 if (revealer->child)
290 return gtk_widget_get_request_mode (widget: revealer->child);
291 else
292 return GTK_SIZE_REQUEST_CONSTANT_SIZE;
293}
294
295static void
296gtk_revealer_class_init (GtkRevealerClass *klass)
297{
298 GObjectClass *object_class = G_OBJECT_CLASS (klass);
299 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
300
301 object_class->dispose = gtk_revealer_dispose;
302 object_class->finalize = gtk_revealer_finalize;
303 object_class->get_property = gtk_revealer_get_property;
304 object_class->set_property = gtk_revealer_set_property;
305
306 widget_class->unmap = gtk_revealer_unmap;
307 widget_class->size_allocate = gtk_revealer_size_allocate;
308 widget_class->measure = gtk_revealer_measure;
309 widget_class->compute_expand = gtk_revealer_compute_expand;
310 widget_class->get_request_mode = gtk_revealer_get_request_mode;
311
312 /**
313 * GtkRevealer:transition-type: (attributes org.gtk.Property.get=gtk_revealer_get_transition_type org.gtk.Property.set=gtk_revealer_set_transition_type)
314 *
315 * The type of animation used to transition.
316 */
317 props[PROP_TRANSITION_TYPE] =
318 g_param_spec_enum (name: "transition-type",
319 P_("Transition type"),
320 P_("The type of animation used to transition"),
321 enum_type: GTK_TYPE_REVEALER_TRANSITION_TYPE,
322 default_value: GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN,
323 GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY);
324
325 /**
326 * GtkRevealer:transition-duration: (attributes org.gtk.Property.get=gtk_revealer_get_transition_duration org.gtk.Property.set=gtk_revealer_set_transition_duration)
327 *
328 * The animation duration, in milliseconds.
329 */
330 props[PROP_TRANSITION_DURATION] =
331 g_param_spec_uint (name: "transition-duration",
332 P_("Transition duration"),
333 P_("The animation duration, in milliseconds"),
334 minimum: 0, G_MAXUINT, default_value: 250,
335 GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY);
336
337 /**
338 * GtkRevealer:reveal-child: (attributes org.gtk.Proeprty.get=gtk_revealer_get_reveal_child org.gtk.Property.set=gtk_revealer_set_reveal_child)
339 *
340 * Whether the revealer should reveal the child.
341 */
342 props[PROP_REVEAL_CHILD] =
343 g_param_spec_boolean (name: "reveal-child",
344 P_("Reveal Child"),
345 P_("Whether the container should reveal the child"),
346 FALSE,
347 GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY);
348
349 /**
350 * GtkRevealer:child-revealed: (attributes org.gtk.Property.get=gtk_revealer_get_child_revealed)
351 *
352 * Whether the child is revealed and the animation target reached.
353 */
354 props[PROP_CHILD_REVEALED] =
355 g_param_spec_boolean (name: "child-revealed",
356 P_("Child Revealed"),
357 P_("Whether the child is revealed and the animation target reached"),
358 FALSE,
359 GTK_PARAM_READABLE);
360
361 /**
362 * GtkRevealer:child: (attributes org.gtk.Property.get=gtk_revealer_get_child org.gtk.Property.set=gtk_revealer_set_child)
363 *
364 * The child widget.
365 */
366 props[PROP_CHILD] =
367 g_param_spec_object (name: "child",
368 P_("Child"),
369 P_("The child widget"),
370 GTK_TYPE_WIDGET,
371 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
372
373
374 g_object_class_install_properties (oclass: object_class, n_pspecs: LAST_PROP, pspecs: props);
375
376 gtk_widget_class_set_css_name (widget_class, I_("revealer"));
377 gtk_widget_class_set_accessible_role (widget_class, accessible_role: GTK_ACCESSIBLE_ROLE_GROUP);
378}
379
380/**
381 * gtk_revealer_new:
382 *
383 * Creates a new `GtkRevealer`.
384 *
385 * Returns: a newly created `GtkRevealer`
386 */
387GtkWidget *
388gtk_revealer_new (void)
389{
390 return g_object_new (GTK_TYPE_REVEALER, NULL);
391}
392
393static GtkRevealerTransitionType
394effective_transition (GtkRevealer *revealer)
395{
396 if (gtk_widget_get_direction (GTK_WIDGET (revealer)) == GTK_TEXT_DIR_RTL)
397 {
398 if (revealer->transition_type == GTK_REVEALER_TRANSITION_TYPE_SLIDE_LEFT)
399 return GTK_REVEALER_TRANSITION_TYPE_SLIDE_RIGHT;
400 else if (revealer->transition_type == GTK_REVEALER_TRANSITION_TYPE_SLIDE_RIGHT)
401 return GTK_REVEALER_TRANSITION_TYPE_SLIDE_LEFT;
402 if (revealer->transition_type == GTK_REVEALER_TRANSITION_TYPE_SWING_LEFT)
403 return GTK_REVEALER_TRANSITION_TYPE_SWING_RIGHT;
404 else if (revealer->transition_type == GTK_REVEALER_TRANSITION_TYPE_SWING_RIGHT)
405 return GTK_REVEALER_TRANSITION_TYPE_SWING_LEFT;
406 }
407
408 return revealer->transition_type;
409}
410
411static double
412get_child_size_scale (GtkRevealer *revealer,
413 GtkOrientation orientation)
414{
415 switch (effective_transition (revealer))
416 {
417 case GTK_REVEALER_TRANSITION_TYPE_SLIDE_RIGHT:
418 case GTK_REVEALER_TRANSITION_TYPE_SLIDE_LEFT:
419 if (orientation == GTK_ORIENTATION_HORIZONTAL)
420 return revealer->current_pos;
421 else
422 return 1.0;
423
424 case GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN:
425 case GTK_REVEALER_TRANSITION_TYPE_SLIDE_UP:
426 if (orientation == GTK_ORIENTATION_VERTICAL)
427 return revealer->current_pos;
428 else
429 return 1.0;
430
431 case GTK_REVEALER_TRANSITION_TYPE_SWING_RIGHT:
432 case GTK_REVEALER_TRANSITION_TYPE_SWING_LEFT:
433 if (orientation == GTK_ORIENTATION_HORIZONTAL)
434 return sin (G_PI * revealer->current_pos / 2);
435 else
436 return 1.0;
437
438 case GTK_REVEALER_TRANSITION_TYPE_SWING_DOWN:
439 case GTK_REVEALER_TRANSITION_TYPE_SWING_UP:
440 if (orientation == GTK_ORIENTATION_VERTICAL)
441 return sin (G_PI * revealer->current_pos / 2);
442 else
443 return 1.0;
444
445 case GTK_REVEALER_TRANSITION_TYPE_NONE:
446 case GTK_REVEALER_TRANSITION_TYPE_CROSSFADE:
447 default:
448 return 1.0;
449 }
450}
451
452static void
453gtk_revealer_size_allocate (GtkWidget *widget,
454 int width,
455 int height,
456 int baseline)
457{
458 GtkRevealer *revealer = GTK_REVEALER (widget);
459 GskTransform *transform;
460 double hscale, vscale;
461 int child_width, child_height;
462
463 if (revealer->child == NULL || !gtk_widget_get_visible (widget: revealer->child))
464 return;
465
466 if (revealer->current_pos >= 1.0)
467 {
468 gtk_widget_allocate (widget: revealer->child, width, height, baseline, NULL);
469 return;
470 }
471
472 hscale = get_child_size_scale (revealer, orientation: GTK_ORIENTATION_HORIZONTAL);
473 vscale = get_child_size_scale (revealer, orientation: GTK_ORIENTATION_VERTICAL);
474 if (hscale <= 0 || vscale <= 0)
475 {
476 /* don't allocate anything, the child is invisible and the numbers
477 * don't make sense. */
478 return;
479 }
480
481 /* We request a different size than the child requested scaled by
482 * this scale as it will render smaller from the transition.
483 * However, we still want to allocate the child widget with its
484 * unscaled size so it renders right instead of e.g. ellipsizing or
485 * some other form of clipping. We do this by reverse-applying
486 * the scale when size allocating the child.
487 *
488 * Unfortunately this causes precision issues.
489 *
490 * So we assume that the fully expanded revealer will likely get
491 * an allocation that matches the child's minimum or natural allocation,
492 * so we special-case these two values.
493 * So when - due to the precision loss - multiple sizes would match
494 * the current allocation, we don't pick one at random, we prefer the
495 * min and nat size.
496 *
497 * On top, the scaled size request is always rounded up to an integer.
498 * For instance if natural with is 100, and scale is 0.001, we would
499 * request a natural size of ceil(0.1) == 1, but reversing this would
500 * result in 1 / 0.001 == 1000 (rather than 100).
501 * In the swing case we can get the scale arbitrarily near 0 causing
502 * arbitrary large problems.
503 * These also get avoided by the preference.
504 */
505
506 if (hscale < 1.0)
507 {
508 int min, nat;
509 g_assert (vscale == 1.0);
510 gtk_widget_measure (widget: revealer->child, orientation: GTK_ORIENTATION_HORIZONTAL, for_size: height, minimum: &min, natural: &nat, NULL, NULL);
511 if (ceil (x: nat * hscale) == width)
512 child_width = nat;
513 else if (ceil (x: min * hscale) == width)
514 child_width = min;
515 else
516 child_width = floor (x: width / hscale);
517 child_height = height;
518 }
519 else if (vscale < 1.0)
520 {
521 int min, nat;
522 child_width = width;
523 gtk_widget_measure (widget: revealer->child, orientation: GTK_ORIENTATION_VERTICAL, for_size: width, minimum: &min, natural: &nat, NULL, NULL);
524 if (ceil (x: nat * vscale) == height)
525 child_height = nat;
526 else if (ceil (x: min * vscale) == height)
527 child_height = min;
528 else
529 child_height = floor (x: height / vscale);
530 }
531 else
532 {
533 child_width = width;
534 child_height = height;
535 }
536
537 transform = NULL;
538 switch (effective_transition (revealer))
539 {
540 case GTK_REVEALER_TRANSITION_TYPE_SLIDE_RIGHT:
541 transform = gsk_transform_translate (next: transform, point: &GRAPHENE_POINT_INIT (width - child_width, 0));
542 break;
543
544 case GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN:
545 transform = gsk_transform_translate (next: transform, point: &GRAPHENE_POINT_INIT (0, height - child_height));
546 break;
547
548 case GTK_REVEALER_TRANSITION_TYPE_SWING_LEFT:
549 transform = gsk_transform_translate (next: transform, point: &GRAPHENE_POINT_INIT (width, height / 2));
550 transform = gsk_transform_perspective (next: transform, depth: 2 * MAX (width, height));
551 transform = gsk_transform_rotate_3d (next: transform, angle: -90 * (1.0 - revealer->current_pos), axis: graphene_vec3_y_axis ());
552 transform = gsk_transform_translate (next: transform, point: &GRAPHENE_POINT_INIT (- child_width, - child_height / 2));
553 break;
554
555 case GTK_REVEALER_TRANSITION_TYPE_SWING_RIGHT:
556 transform = gsk_transform_translate (next: transform, point: &GRAPHENE_POINT_INIT (0, height / 2));
557 transform = gsk_transform_perspective (next: transform, depth: 2 * MAX (width, height));
558 transform = gsk_transform_rotate_3d (next: transform, angle: 90 * (1.0 - revealer->current_pos), axis: graphene_vec3_y_axis ());
559 transform = gsk_transform_translate (next: transform, point: &GRAPHENE_POINT_INIT (0, - child_height / 2));
560 break;
561
562 case GTK_REVEALER_TRANSITION_TYPE_SWING_DOWN:
563 transform = gsk_transform_translate (next: transform, point: &GRAPHENE_POINT_INIT (width / 2, 0));
564 transform = gsk_transform_perspective (next: transform, depth: 2 * MAX (width, height));
565 transform = gsk_transform_rotate_3d (next: transform, angle: -90 * (1.0 - revealer->current_pos), axis: graphene_vec3_x_axis ());
566 transform = gsk_transform_translate (next: transform, point: &GRAPHENE_POINT_INIT (- child_width / 2, 0));
567 break;
568
569 case GTK_REVEALER_TRANSITION_TYPE_SWING_UP:
570 transform = gsk_transform_translate (next: transform, point: &GRAPHENE_POINT_INIT (width / 2, height));
571 transform = gsk_transform_perspective (next: transform, depth: 2 * MAX (width, height));
572 transform = gsk_transform_rotate_3d (next: transform, angle: 90 * (1.0 - revealer->current_pos), axis: graphene_vec3_x_axis ());
573 transform = gsk_transform_translate (next: transform, point: &GRAPHENE_POINT_INIT (- child_width / 2, - child_height));
574 break;
575
576 case GTK_REVEALER_TRANSITION_TYPE_NONE:
577 case GTK_REVEALER_TRANSITION_TYPE_CROSSFADE:
578 case GTK_REVEALER_TRANSITION_TYPE_SLIDE_LEFT:
579 case GTK_REVEALER_TRANSITION_TYPE_SLIDE_UP:
580 default:
581 break;
582 }
583
584 gtk_widget_allocate (widget: revealer->child, width: child_width, height: child_height, baseline: -1, transform);
585}
586
587static void
588gtk_revealer_set_position (GtkRevealer *revealer,
589 double pos)
590{
591 gboolean new_visible;
592 GtkRevealerTransitionType transition;
593
594 revealer->current_pos = pos;
595
596 new_visible = revealer->current_pos != 0.0;
597
598 if (revealer->child != NULL &&
599 new_visible != gtk_widget_get_child_visible (widget: revealer->child))
600 {
601 gtk_widget_set_child_visible (widget: revealer->child, child_visible: new_visible);
602 gtk_widget_queue_resize (GTK_WIDGET (revealer));
603 }
604
605 transition = effective_transition (revealer);
606 if (transition == GTK_REVEALER_TRANSITION_TYPE_NONE)
607 {
608 gtk_widget_queue_draw (GTK_WIDGET (revealer));
609 }
610 else if (transition == GTK_REVEALER_TRANSITION_TYPE_CROSSFADE)
611 {
612 gtk_widget_set_opacity (GTK_WIDGET (revealer), opacity: revealer->current_pos);
613 gtk_widget_queue_draw (GTK_WIDGET (revealer));
614 }
615 else
616 {
617 gtk_widget_queue_resize (GTK_WIDGET (revealer));
618 }
619
620 if (revealer->current_pos == revealer->target_pos)
621 g_object_notify_by_pspec (G_OBJECT (revealer), pspec: props[PROP_CHILD_REVEALED]);
622}
623
624static gboolean
625gtk_revealer_animate_cb (GtkWidget *widget,
626 GdkFrameClock *frame_clock,
627 gpointer user_data)
628{
629 GtkRevealer *revealer = GTK_REVEALER (widget);
630 double ease;
631
632 gtk_progress_tracker_advance_frame (tracker: &revealer->tracker,
633 frame_time: gdk_frame_clock_get_frame_time (frame_clock));
634 ease = gtk_progress_tracker_get_ease_out_cubic (tracker: &revealer->tracker, FALSE);
635 gtk_revealer_set_position (revealer,
636 pos: revealer->source_pos + (ease * (revealer->target_pos - revealer->source_pos)));
637
638 if (gtk_progress_tracker_get_state (tracker: &revealer->tracker) == GTK_PROGRESS_STATE_AFTER)
639 {
640 revealer->tick_id = 0;
641 return FALSE;
642 }
643
644 return TRUE;
645}
646
647static void
648gtk_revealer_start_animation (GtkRevealer *revealer,
649 double target)
650{
651 GtkWidget *widget = GTK_WIDGET (revealer);
652 GtkRevealerTransitionType transition;
653
654 if (revealer->target_pos == target)
655 return;
656
657 revealer->target_pos = target;
658 g_object_notify_by_pspec (G_OBJECT (revealer), pspec: props[PROP_REVEAL_CHILD]);
659
660 transition = effective_transition (revealer);
661 if (gtk_widget_get_mapped (widget) &&
662 revealer->transition_duration != 0 &&
663 transition != GTK_REVEALER_TRANSITION_TYPE_NONE &&
664 gtk_settings_get_enable_animations (settings: gtk_widget_get_settings (widget)))
665 {
666 revealer->source_pos = revealer->current_pos;
667 if (revealer->tick_id == 0)
668 revealer->tick_id =
669 gtk_widget_add_tick_callback (widget, callback: gtk_revealer_animate_cb, user_data: revealer, NULL);
670 gtk_progress_tracker_start (tracker: &revealer->tracker,
671 duration: revealer->transition_duration * 1000,
672 delay: 0,
673 iteration_count: 1.0);
674 }
675 else
676 {
677 gtk_revealer_set_position (revealer, pos: target);
678 }
679}
680
681/**
682 * gtk_revealer_set_reveal_child: (attributes org.gtk.Method.set_property=reveal-child)
683 * @revealer: a `GtkRevealer`
684 * @reveal_child: %TRUE to reveal the child
685 *
686 * Tells the `GtkRevealer` to reveal or conceal its child.
687 *
688 * The transition will be animated with the current
689 * transition type of @revealer.
690 */
691void
692gtk_revealer_set_reveal_child (GtkRevealer *revealer,
693 gboolean reveal_child)
694{
695 g_return_if_fail (GTK_IS_REVEALER (revealer));
696
697 if (reveal_child)
698 gtk_revealer_start_animation (revealer, target: 1.0);
699 else
700 gtk_revealer_start_animation (revealer, target: 0.0);
701}
702
703/**
704 * gtk_revealer_get_reveal_child: (attributes org.gtk.Method.get_property=reveal-child)
705 * @revealer: a `GtkRevealer`
706 *
707 * Returns whether the child is currently revealed.
708 *
709 * This function returns %TRUE as soon as the transition
710 * is to the revealed state is started. To learn whether
711 * the child is fully revealed (ie the transition is completed),
712 * use [method@Gtk.Revealer.get_child_revealed].
713 *
714 * Returns: %TRUE if the child is revealed.
715 */
716gboolean
717gtk_revealer_get_reveal_child (GtkRevealer *revealer)
718{
719 g_return_val_if_fail (GTK_IS_REVEALER (revealer), FALSE);
720
721 return revealer->target_pos != 0.0;
722}
723
724/**
725 * gtk_revealer_get_child_revealed: (attributes org.gtk.Method.get_property=child-revealed)
726 * @revealer: a `GtkRevealer`
727 *
728 * Returns whether the child is fully revealed.
729 *
730 * In other words, this returns whether the transition
731 * to the revealed state is completed.
732 *
733 * Returns: %TRUE if the child is fully revealed
734 */
735gboolean
736gtk_revealer_get_child_revealed (GtkRevealer *revealer)
737{
738 gboolean animation_finished = (revealer->target_pos == revealer->current_pos);
739 gboolean reveal_child = gtk_revealer_get_reveal_child (revealer);
740
741 if (animation_finished)
742 return reveal_child;
743 else
744 return !reveal_child;
745}
746
747static void
748gtk_revealer_measure (GtkWidget *widget,
749 GtkOrientation orientation,
750 int for_size,
751 int *minimum,
752 int *natural,
753 int *minimum_baseline,
754 int *natural_baseline)
755{
756 GtkRevealer *revealer = GTK_REVEALER (widget);
757 double scale;
758
759 scale = get_child_size_scale (revealer, OPPOSITE_ORIENTATION (orientation));
760
761 if (for_size >= 0)
762 {
763 if (scale == 0)
764 return;
765 else
766 for_size = MIN (G_MAXINT, ceil (for_size / scale));
767 }
768
769 if (revealer->child != NULL && _gtk_widget_get_visible (widget: revealer->child))
770 {
771 gtk_widget_measure (widget: revealer->child,
772 orientation,
773 for_size,
774 minimum, natural,
775 NULL, NULL);
776 }
777 else
778 {
779 *minimum = 0;
780 *natural = 0;
781 }
782
783 scale = get_child_size_scale (revealer, orientation);
784 *minimum = ceil (x: *minimum * scale);
785 *natural = ceil (x: *natural * scale);
786}
787
788/**
789 * gtk_revealer_get_transition_duration: (attributes org.gtk.Method.get_property=transition-duration)
790 * @revealer: a `GtkRevealer`
791 *
792 * Returns the amount of time (in milliseconds) that
793 * transitions will take.
794 *
795 * Returns: the transition duration
796 */
797guint
798gtk_revealer_get_transition_duration (GtkRevealer *revealer)
799{
800 g_return_val_if_fail (GTK_IS_REVEALER (revealer), 0);
801
802 return revealer->transition_duration;
803}
804
805/**
806 * gtk_revealer_set_transition_duration: (attributes org.gtk.Method.set_property=transition-duration)
807 * @revealer: a `GtkRevealer`
808 * @duration: the new duration, in milliseconds
809 *
810 * Sets the duration that transitions will take.
811 */
812void
813gtk_revealer_set_transition_duration (GtkRevealer *revealer,
814 guint value)
815{
816 g_return_if_fail (GTK_IS_REVEALER (revealer));
817
818 if (revealer->transition_duration == value)
819 return;
820
821 revealer->transition_duration = value;
822 g_object_notify_by_pspec (G_OBJECT (revealer), pspec: props[PROP_TRANSITION_DURATION]);
823}
824
825/**
826 * gtk_revealer_get_transition_type: (attributes org.gtk.Method.get_property=transition-type)
827 * @revealer: a `GtkRevealer`
828 *
829 * Gets the type of animation that will be used
830 * for transitions in @revealer.
831 *
832 * Returns: the current transition type of @revealer
833 */
834GtkRevealerTransitionType
835gtk_revealer_get_transition_type (GtkRevealer *revealer)
836{
837 g_return_val_if_fail (GTK_IS_REVEALER (revealer), GTK_REVEALER_TRANSITION_TYPE_NONE);
838
839 return revealer->transition_type;
840}
841
842/**
843 * gtk_revealer_set_transition_type: (attributes org.gtk.Method.set_property=transition-type)
844 * @revealer: a `GtkRevealer`
845 * @transition: the new transition type
846 *
847 * Sets the type of animation that will be used for
848 * transitions in @revealer.
849 *
850 * Available types include various kinds of fades and slides.
851 */
852void
853gtk_revealer_set_transition_type (GtkRevealer *revealer,
854 GtkRevealerTransitionType transition)
855{
856 g_return_if_fail (GTK_IS_REVEALER (revealer));
857
858 if (revealer->transition_type == transition)
859 return;
860
861 revealer->transition_type = transition;
862 gtk_widget_queue_resize (GTK_WIDGET (revealer));
863 g_object_notify_by_pspec (G_OBJECT (revealer), pspec: props[PROP_TRANSITION_TYPE]);
864}
865
866/**
867 * gtk_revealer_set_child: (attributes org.gtk.Method.set_property=child)
868 * @revealer: a `GtkRevealer`
869 * @child: (nullable): the child widget
870 *
871 * Sets the child widget of @revealer.
872 */
873void
874gtk_revealer_set_child (GtkRevealer *revealer,
875 GtkWidget *child)
876{
877 g_return_if_fail (GTK_IS_REVEALER (revealer));
878 g_return_if_fail (child == NULL || GTK_IS_WIDGET (child));
879
880 g_clear_pointer (&revealer->child, gtk_widget_unparent);
881
882 if (child)
883 {
884 gtk_widget_set_parent (widget: child, GTK_WIDGET (revealer));
885 gtk_widget_set_child_visible (widget: child, child_visible: revealer->current_pos != 0.0);
886 revealer->child = child;
887 }
888
889 g_object_notify_by_pspec (G_OBJECT (revealer), pspec: props[PROP_CHILD]);
890}
891
892/**
893 * gtk_revealer_get_child: (attributes org.gtk.Method.get_property=child)
894 * @revealer: a `GtkRevealer`
895 *
896 * Gets the child widget of @revealer.
897 *
898 * Returns: (nullable) (transfer none): the child widget of @revealer
899 */
900GtkWidget *
901gtk_revealer_get_child (GtkRevealer *revealer)
902{
903 g_return_val_if_fail (GTK_IS_REVEALER (revealer), NULL);
904
905 return revealer->child;
906}
907

source code of gtk/gtk/gtkrevealer.c