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 | |
78 | struct _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 | |
95 | typedef struct |
96 | { |
97 | GtkWidgetClass parent_class; |
98 | } GtkRevealerClass; |
99 | |
100 | enum { |
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 | |
110 | static GParamSpec *props[LAST_PROP] = { NULL, }; |
111 | |
112 | static void gtk_revealer_size_allocate (GtkWidget *widget, |
113 | int width, |
114 | int height, |
115 | int baseline); |
116 | static 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 | |
124 | static void gtk_revealer_set_position (GtkRevealer *revealer, |
125 | double pos); |
126 | |
127 | static void gtk_revealer_buildable_iface_init (GtkBuildableIface *iface); |
128 | |
129 | G_DEFINE_TYPE_WITH_CODE (GtkRevealer, gtk_revealer, GTK_TYPE_WIDGET, |
130 | G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, |
131 | gtk_revealer_buildable_iface_init)) |
132 | |
133 | static GtkBuildableIface *parent_buildable_iface; |
134 | |
135 | static void |
136 | gtk_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 | |
147 | static void |
148 | gtk_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 | |
155 | static void |
156 | gtk_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 | |
166 | static void |
167 | gtk_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 | |
176 | static void |
177 | gtk_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 | |
188 | static void |
189 | gtk_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 | |
219 | static void |
220 | gtk_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 | |
247 | static void |
248 | gtk_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 | |
265 | static void |
266 | gtk_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 | |
284 | static GtkSizeRequestMode |
285 | gtk_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 | |
295 | static void |
296 | gtk_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 | */ |
387 | GtkWidget * |
388 | gtk_revealer_new (void) |
389 | { |
390 | return g_object_new (GTK_TYPE_REVEALER, NULL); |
391 | } |
392 | |
393 | static GtkRevealerTransitionType |
394 | effective_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 | |
411 | static double |
412 | get_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 | |
452 | static void |
453 | gtk_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 | |
587 | static void |
588 | gtk_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 | |
624 | static gboolean |
625 | gtk_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 | |
647 | static void |
648 | gtk_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 | */ |
691 | void |
692 | gtk_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 | */ |
716 | gboolean |
717 | gtk_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 | */ |
735 | gboolean |
736 | gtk_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 | |
747 | static void |
748 | gtk_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 | */ |
797 | guint |
798 | gtk_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 | */ |
812 | void |
813 | gtk_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 | */ |
834 | GtkRevealerTransitionType |
835 | gtk_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 | */ |
852 | void |
853 | gtk_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 | */ |
873 | void |
874 | gtk_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 | */ |
900 | GtkWidget * |
901 | gtk_revealer_get_child (GtkRevealer *revealer) |
902 | { |
903 | g_return_val_if_fail (GTK_IS_REVEALER (revealer), NULL); |
904 | |
905 | return revealer->child; |
906 | } |
907 | |