1 /*
2 * Copyright (C) 2013 Red Hat, Inc.
3 * Copyright (C) 2019 Purism SPC
4 *
5 * Author: Alexander Larsson <alexl@redhat.com>
6 * Author: Adrien Plazas <adrien.plazas@puri.sm>
7 *
8 * SPDX-License-Identifier: LGPL-2.1-or-later
9 */
10
11 /*
12 * Forked from the GTK+ 3.24.2 GtkStack widget initially written by Alexander
13 * Larsson, and heavily modified for libadwaita by Adrien Plazas on behalf of
14 * Purism SPC 2019.
15 */
16
17 #include "config.h"
18
19 #include "adw-squeezer.h"
20
21 #include "gtkprogresstrackerprivate.h"
22 #include "adw-animation-util-private.h"
23 #include "adw-animation-private.h"
24 #include "adw-widget-utils-private.h"
25
26 /**
27 * AdwSqueezer:
28 *
29 * A best fit container.
30 *
31 * The `AdwSqueezer` widget is a container which only shows the first of its
32 * children that fits in the available size. It is convenient to offer different
33 * widgets to represent the same data with different levels of detail, making
34 * the widget seem to squeeze itself to fit in the available space.
35 *
36 * Transitions between children can be animated as fades. This can be controlled
37 * with [property@Adw.Squeezer:transition-type].
38 *
39 * ## CSS nodes
40 *
41 * `AdwSqueezer` has a single CSS node with name `squeezer`.
42 *
43 * Since: 1.0
44 */
45
46 /**
47 * AdwSqueezerPage:
48 *
49 * An auxiliary class used by [class@Adw.Squeezer].
50 */
51
52 /**
53 * AdwSqueezerTransitionType:
54 * @ADW_SQUEEZER_TRANSITION_TYPE_NONE: No transition
55 * @ADW_SQUEEZER_TRANSITION_TYPE_CROSSFADE: A cross-fade
56 *
57 * Describes the possible transitions in a [class@Adw.Squeezer] widget.
58 *
59 * Since: 1.0
60 */
61
62 struct _AdwSqueezerPage {
63 GObject parent_instance;
64
65 GtkWidget *widget;
66 GtkWidget *last_focus;
67 gboolean enabled;
68 };
69
70 G_DEFINE_TYPE (AdwSqueezerPage, adw_squeezer_page, G_TYPE_OBJECT)
71
72 enum {
73 PAGE_PROP_0,
74 PAGE_PROP_CHILD,
75 PAGE_PROP_ENABLED,
76 LAST_PAGE_PROP
77 };
78
79 static GParamSpec *page_props[LAST_PAGE_PROP];
80
81 struct _AdwSqueezer
82 {
83 GtkWidget parent_instance;
84
85 GList *children;
86
87 AdwSqueezerPage *visible_child;
88
89 gboolean homogeneous;
90
91 gboolean allow_none;
92
93 AdwSqueezerTransitionType transition_type;
94 guint transition_duration;
95
96 AdwSqueezerPage *last_visible_child;
97 guint tick_id;
98 GtkProgressTracker tracker;
99 gboolean first_frame_skipped;
100
101 int last_visible_widget_width;
102 int last_visible_widget_height;
103
104 AdwSqueezerTransitionType active_transition_type;
105
106 gboolean interpolate_size;
107
108 float xalign;
109 float yalign;
110
111 GtkOrientation orientation;
112
113 GtkSelectionModel *pages;
114 };
115
116 enum {
117 PROP_0,
118 PROP_HOMOGENEOUS,
119 PROP_VISIBLE_CHILD,
120 PROP_ALLOW_NONE,
121 PROP_TRANSITION_DURATION,
122 PROP_TRANSITION_TYPE,
123 PROP_TRANSITION_RUNNING,
124 PROP_INTERPOLATE_SIZE,
125 PROP_XALIGN,
126 PROP_YALIGN,
127 PROP_PAGES,
128
129 /* Overridden properties */
130 PROP_ORIENTATION,
131
132 LAST_PROP = PROP_PAGES + 1,
133 };
134
135 static GParamSpec *props[LAST_PROP];
136
137 static void adw_squeezer_buildable_init (GtkBuildableIface *iface);
138
139 G_DEFINE_TYPE_WITH_CODE (AdwSqueezer, adw_squeezer, GTK_TYPE_WIDGET,
140 G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL)
141 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, adw_squeezer_buildable_init))
142
143 static GtkBuildableIface *parent_buildable_iface;
144
145 static void
adw_squeezer_page_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)146 adw_squeezer_page_get_property (GObject *object,
147 guint property_id,
148 GValue *value,
149 GParamSpec *pspec)
150 {
151 AdwSqueezerPage *self = ADW_SQUEEZER_PAGE (object);
152
153 switch (property_id) {
154 case PAGE_PROP_CHILD:
155 g_value_set_object (value, adw_squeezer_page_get_child (self));
156 break;
157 case PAGE_PROP_ENABLED:
158 g_value_set_boolean (value, adw_squeezer_page_get_enabled (self));
159 break;
160 default:
161 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
162 break;
163 }
164 }
165
166 static void
adw_squeezer_page_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)167 adw_squeezer_page_set_property (GObject *object,
168 guint property_id,
169 const GValue *value,
170 GParamSpec *pspec)
171 {
172 AdwSqueezerPage *self = ADW_SQUEEZER_PAGE (object);
173
174 switch (property_id) {
175 case PAGE_PROP_CHILD:
176 g_set_object (&self->widget, g_value_get_object (value));
177 break;
178 case PAGE_PROP_ENABLED:
179 adw_squeezer_page_set_enabled (self, g_value_get_boolean (value));
180 break;
181 default:
182 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
183 break;
184 }
185 }
186
187 static void
adw_squeezer_page_finalize(GObject * object)188 adw_squeezer_page_finalize (GObject *object)
189 {
190 AdwSqueezerPage *self = ADW_SQUEEZER_PAGE (object);
191
192 g_clear_object (&self->widget);
193
194 if (self->last_focus)
195 g_object_remove_weak_pointer (G_OBJECT (self->last_focus),
196 (gpointer *) &self->last_focus);
197
198 G_OBJECT_CLASS (adw_squeezer_page_parent_class)->finalize (object);
199 }
200
201 static void
adw_squeezer_page_class_init(AdwSqueezerPageClass * klass)202 adw_squeezer_page_class_init (AdwSqueezerPageClass *klass)
203 {
204 GObjectClass *object_class = G_OBJECT_CLASS (klass);
205
206 object_class->get_property = adw_squeezer_page_get_property;
207 object_class->set_property = adw_squeezer_page_set_property;
208 object_class->finalize = adw_squeezer_page_finalize;
209
210 /**
211 * AdwSqueezerPage:child: (attributes org.gtk.Property.get=adw_squeezer_page_get_child)
212 *
213 * The child of the page.
214 *
215 * Since: 1.0
216 */
217 page_props[PAGE_PROP_CHILD] =
218 g_param_spec_object ("child",
219 "Child",
220 "The child of the page",
221 GTK_TYPE_WIDGET,
222 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
223
224 /**
225 * AdwSqueezerPage:enabled: (attributes org.gtk.Property.get=adw_squeezer_page_get_enabled org.gtk.Property.get=adw_squeezer_page_set_enabled)
226 *
227 * Whether the child is enabled.
228 *
229 * If a child is disabled, it will be ignored when looking for the child
230 * fitting the available size best.
231 *
232 * This allows to programmatically and prematurely hide a child even if it
233 * fits in the available space.
234 *
235 * This can be used e.g. to ensure a certain child is hidden below a certain
236 * window width, or any other constraint you find suitable.
237 *
238 * Since: 1.0
239 */
240 page_props[PAGE_PROP_ENABLED] =
241 g_param_spec_boolean ("enabled",
242 "Enabled",
243 "Whether the child is enabled",
244 TRUE,
245 G_PARAM_READWRITE);
246
247 g_object_class_install_properties (object_class, LAST_PAGE_PROP, page_props);
248 }
249
250 static void
adw_squeezer_page_init(AdwSqueezerPage * self)251 adw_squeezer_page_init (AdwSqueezerPage *self)
252 {
253 self->enabled = TRUE;
254 }
255
256 #define ADW_TYPE_SQUEEZER_PAGES (adw_squeezer_pages_get_type ())
257
258 G_DECLARE_FINAL_TYPE (AdwSqueezerPages, adw_squeezer_pages, ADW, SQUEEZER_PAGES, GObject)
259
260 struct _AdwSqueezerPages
261 {
262 GObject parent_instance;
263 AdwSqueezer *squeezer;
264 };
265
266 static GType
adw_squeezer_pages_get_item_type(GListModel * model)267 adw_squeezer_pages_get_item_type (GListModel *model)
268 {
269 return ADW_TYPE_SQUEEZER_PAGE;
270 }
271
272 static guint
adw_squeezer_pages_get_n_items(GListModel * model)273 adw_squeezer_pages_get_n_items (GListModel *model)
274 {
275 AdwSqueezerPages *self = ADW_SQUEEZER_PAGES (model);
276
277 return g_list_length (self->squeezer->children);
278 }
279
280 static gpointer
adw_squeezer_pages_get_item(GListModel * model,guint position)281 adw_squeezer_pages_get_item (GListModel *model,
282 guint position)
283 {
284 AdwSqueezerPages *self = ADW_SQUEEZER_PAGES (model);
285 AdwSqueezerPage *page;
286
287 page = g_list_nth_data (self->squeezer->children, position);
288
289 if (!page)
290 return NULL;
291
292 return g_object_ref (page);
293 }
294
295 static void
adw_squeezer_pages_list_model_init(GListModelInterface * iface)296 adw_squeezer_pages_list_model_init (GListModelInterface *iface)
297 {
298 iface->get_item_type = adw_squeezer_pages_get_item_type;
299 iface->get_n_items = adw_squeezer_pages_get_n_items;
300 iface->get_item = adw_squeezer_pages_get_item;
301 }
302
303 static gboolean
adw_squeezer_pages_is_selected(GtkSelectionModel * model,guint position)304 adw_squeezer_pages_is_selected (GtkSelectionModel *model,
305 guint position)
306 {
307 AdwSqueezerPages *self = ADW_SQUEEZER_PAGES (model);
308 AdwSqueezerPage *page;
309
310 page = g_list_nth_data (self->squeezer->children, position);
311
312 return page && page == self->squeezer->visible_child;
313 }
314
315 static void
adw_squeezer_pages_selection_model_init(GtkSelectionModelInterface * iface)316 adw_squeezer_pages_selection_model_init (GtkSelectionModelInterface *iface)
317 {
318 iface->is_selected = adw_squeezer_pages_is_selected;
319 }
320
G_DEFINE_TYPE_WITH_CODE(AdwSqueezerPages,adw_squeezer_pages,G_TYPE_OBJECT,G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL,adw_squeezer_pages_list_model_init)G_IMPLEMENT_INTERFACE (GTK_TYPE_SELECTION_MODEL,adw_squeezer_pages_selection_model_init))321 G_DEFINE_TYPE_WITH_CODE (AdwSqueezerPages, adw_squeezer_pages, G_TYPE_OBJECT,
322 G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, adw_squeezer_pages_list_model_init)
323 G_IMPLEMENT_INTERFACE (GTK_TYPE_SELECTION_MODEL, adw_squeezer_pages_selection_model_init))
324
325 static void
326 adw_squeezer_pages_init (AdwSqueezerPages *pages)
327 {
328 }
329
330 static void
adw_squeezer_pages_class_init(AdwSqueezerPagesClass * klass)331 adw_squeezer_pages_class_init (AdwSqueezerPagesClass *klass)
332 {
333 }
334
335 static AdwSqueezerPages *
adw_squeezer_pages_new(AdwSqueezer * squeezer)336 adw_squeezer_pages_new (AdwSqueezer *squeezer)
337 {
338 AdwSqueezerPages *pages;
339
340 pages = g_object_new (ADW_TYPE_SQUEEZER_PAGES, NULL);
341 pages->squeezer = squeezer;
342
343 return pages;
344 }
345
346 static GtkOrientation
get_orientation(AdwSqueezer * self)347 get_orientation (AdwSqueezer *self)
348 {
349 return self->orientation;
350 }
351
352 static void
set_orientation(AdwSqueezer * self,GtkOrientation orientation)353 set_orientation (AdwSqueezer *self,
354 GtkOrientation orientation)
355 {
356 if (self->orientation == orientation)
357 return;
358
359 self->orientation = orientation;
360 gtk_widget_queue_resize (GTK_WIDGET (self));
361 g_object_notify (G_OBJECT (self), "orientation");
362 }
363
364 static AdwSqueezerPage *
find_page_for_widget(AdwSqueezer * self,GtkWidget * child)365 find_page_for_widget (AdwSqueezer *self,
366 GtkWidget *child)
367 {
368 AdwSqueezerPage *page;
369 GList *l;
370
371 for (l = self->children; l != NULL; l = l->next) {
372 page = l->data;
373 if (page->widget == child)
374 return page;
375 }
376
377 return NULL;
378 }
379
380 static void
adw_squeezer_progress_updated(AdwSqueezer * self)381 adw_squeezer_progress_updated (AdwSqueezer *self)
382 {
383 if (!self->homogeneous)
384 gtk_widget_queue_resize (GTK_WIDGET (self));
385 else
386 gtk_widget_queue_draw (GTK_WIDGET (self));
387
388 if (gtk_progress_tracker_get_state (&self->tracker) == GTK_PROGRESS_STATE_AFTER &&
389 self->last_visible_child != NULL) {
390 gtk_widget_set_child_visible (self->last_visible_child->widget, FALSE);
391 self->last_visible_child = NULL;
392 }
393 }
394
395 static gboolean
adw_squeezer_transition_cb(GtkWidget * widget,GdkFrameClock * frame_clock,gpointer user_data)396 adw_squeezer_transition_cb (GtkWidget *widget,
397 GdkFrameClock *frame_clock,
398 gpointer user_data)
399 {
400 AdwSqueezer *self = ADW_SQUEEZER (widget);
401
402 if (self->first_frame_skipped) {
403 gtk_progress_tracker_advance_frame (&self->tracker,
404 gdk_frame_clock_get_frame_time (frame_clock));
405 } else {
406 self->first_frame_skipped = TRUE;
407 }
408
409 /* Finish the animation early if the widget isn't mapped anymore. */
410 if (!gtk_widget_get_mapped (widget))
411 gtk_progress_tracker_finish (&self->tracker);
412
413 adw_squeezer_progress_updated (ADW_SQUEEZER (widget));
414
415 if (gtk_progress_tracker_get_state (&self->tracker) == GTK_PROGRESS_STATE_AFTER) {
416 self->tick_id = 0;
417 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TRANSITION_RUNNING]);
418
419 return FALSE;
420 }
421
422 return TRUE;
423 }
424
425 static void
adw_squeezer_schedule_ticks(AdwSqueezer * self)426 adw_squeezer_schedule_ticks (AdwSqueezer *self)
427 {
428 if (self->tick_id == 0) {
429 self->tick_id =
430 gtk_widget_add_tick_callback (GTK_WIDGET (self), adw_squeezer_transition_cb, self, NULL);
431 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TRANSITION_RUNNING]);
432 }
433 }
434
435 static void
adw_squeezer_unschedule_ticks(AdwSqueezer * self)436 adw_squeezer_unschedule_ticks (AdwSqueezer *self)
437 {
438 if (self->tick_id != 0) {
439 gtk_widget_remove_tick_callback (GTK_WIDGET (self), self->tick_id);
440 self->tick_id = 0;
441 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TRANSITION_RUNNING]);
442 }
443 }
444
445 static void
adw_squeezer_start_transition(AdwSqueezer * self,AdwSqueezerTransitionType transition_type,guint transition_duration)446 adw_squeezer_start_transition (AdwSqueezer *self,
447 AdwSqueezerTransitionType transition_type,
448 guint transition_duration)
449 {
450 GtkWidget *widget = GTK_WIDGET (self);
451
452 if (gtk_widget_get_mapped (widget) &&
453 adw_get_enable_animations (widget) &&
454 transition_type != ADW_SQUEEZER_TRANSITION_TYPE_NONE &&
455 transition_duration != 0 &&
456 (self->last_visible_child != NULL || self->allow_none)) {
457 self->active_transition_type = transition_type;
458 self->first_frame_skipped = FALSE;
459 adw_squeezer_schedule_ticks (self);
460 gtk_progress_tracker_start (&self->tracker,
461 self->transition_duration * 1000,
462 0,
463 1.0);
464 } else {
465 adw_squeezer_unschedule_ticks (self);
466 self->active_transition_type = ADW_SQUEEZER_TRANSITION_TYPE_NONE;
467 gtk_progress_tracker_finish (&self->tracker);
468 }
469
470 adw_squeezer_progress_updated (ADW_SQUEEZER (widget));
471 }
472
473 static void
set_visible_child(AdwSqueezer * self,AdwSqueezerPage * page,AdwSqueezerTransitionType transition_type,guint transition_duration)474 set_visible_child (AdwSqueezer *self,
475 AdwSqueezerPage *page,
476 AdwSqueezerTransitionType transition_type,
477 guint transition_duration)
478 {
479 GtkWidget *widget = GTK_WIDGET (self);
480 GtkRoot *root;
481 GtkWidget *focus;
482 gboolean contains_focus = FALSE;
483 guint old_pos = GTK_INVALID_LIST_POSITION;
484 guint new_pos = GTK_INVALID_LIST_POSITION;
485
486 /* If we are being destroyed, do not bother with transitions and
487 * notifications.
488 */
489 if (gtk_widget_in_destruction (widget))
490 return;
491
492 /* If none, pick the first visible. */
493 if (!page && !self->allow_none) {
494 GList *l;
495
496 for (l = self->children; l; l = l->next) {
497 AdwSqueezerPage *p = l->data;
498 if (gtk_widget_get_visible (p->widget)) {
499 page = p;
500 break;
501 }
502 }
503 }
504
505 if (page == self->visible_child)
506 return;
507
508 if (page != NULL && self->pages) {
509 guint position;
510 GList *l;
511
512 for (l = self->children, position = 0; l; l = l->next, position++) {
513 AdwSqueezerPage *p = l->data;
514 if (p == self->visible_child)
515 old_pos = position;
516 else if (p == page)
517 new_pos = position;
518 }
519 }
520
521 root = gtk_widget_get_root (widget);
522 if (root)
523 focus = gtk_root_get_focus (root);
524 else
525 focus = NULL;
526
527 if (focus &&
528 self->visible_child &&
529 self->visible_child->widget &&
530 gtk_widget_is_ancestor (focus, self->visible_child->widget)) {
531 contains_focus = TRUE;
532
533 if (self->visible_child->last_focus)
534 g_object_remove_weak_pointer (G_OBJECT (self->visible_child->last_focus),
535 (gpointer *)&self->visible_child->last_focus);
536 self->visible_child->last_focus = focus;
537 g_object_add_weak_pointer (G_OBJECT (self->visible_child->last_focus),
538 (gpointer *)&self->visible_child->last_focus);
539 }
540
541 if (self->last_visible_child != NULL)
542 gtk_widget_set_child_visible (self->last_visible_child->widget, FALSE);
543 self->last_visible_child = NULL;
544
545 if (self->visible_child && self->visible_child->widget) {
546 if (gtk_widget_is_visible (widget)) {
547 self->last_visible_child = self->visible_child;
548 self->last_visible_widget_width = gtk_widget_get_width (self->last_visible_child->widget);
549 self->last_visible_widget_height = gtk_widget_get_height (self->last_visible_child->widget);
550 } else {
551 gtk_widget_set_child_visible (self->visible_child->widget, FALSE);
552 }
553 }
554
555 self->visible_child = page;
556
557 if (page) {
558 gtk_widget_set_child_visible (page->widget, TRUE);
559
560 if (contains_focus) {
561 if (page->last_focus)
562 gtk_widget_grab_focus (page->last_focus);
563 else
564 gtk_widget_child_focus (page->widget, GTK_DIR_TAB_FORWARD);
565 }
566 }
567
568 if (self->homogeneous)
569 gtk_widget_queue_allocate (widget);
570 else
571 gtk_widget_queue_resize (widget);
572
573 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_VISIBLE_CHILD]);
574
575 if (self->pages) {
576 if (old_pos == GTK_INVALID_LIST_POSITION && new_pos == GTK_INVALID_LIST_POSITION)
577 ; /* nothing to do */
578 else if (old_pos == GTK_INVALID_LIST_POSITION)
579 gtk_selection_model_selection_changed (self->pages, new_pos, 1);
580 else if (new_pos == GTK_INVALID_LIST_POSITION)
581 gtk_selection_model_selection_changed (self->pages, old_pos, 1);
582 else
583 gtk_selection_model_selection_changed (self->pages,
584 MIN (old_pos, new_pos),
585 MAX (old_pos, new_pos) - MIN (old_pos, new_pos) + 1);
586 }
587
588 adw_squeezer_start_transition (self, transition_type, transition_duration);
589 }
590
591 static void
update_child_visible(AdwSqueezer * self,AdwSqueezerPage * page)592 update_child_visible (AdwSqueezer *self,
593 AdwSqueezerPage *page)
594 {
595 gboolean enabled;
596
597 enabled = page->enabled && gtk_widget_get_visible (page->widget);
598
599 if (self->visible_child == NULL && enabled)
600 set_visible_child (self, page, self->transition_type, self->transition_duration);
601 else if (self->visible_child == page && !enabled)
602 set_visible_child (self, NULL, self->transition_type, self->transition_duration);
603
604 if (page == self->last_visible_child) {
605 gtk_widget_set_child_visible (self->last_visible_child->widget, FALSE);
606 self->last_visible_child = NULL;
607 }
608 }
609
610 static void
squeezer_child_visibility_notify_cb(GObject * obj,GParamSpec * pspec,gpointer user_data)611 squeezer_child_visibility_notify_cb (GObject *obj,
612 GParamSpec *pspec,
613 gpointer user_data)
614 {
615 AdwSqueezer *self = ADW_SQUEEZER (user_data);
616 GtkWidget *child = GTK_WIDGET (obj);
617 AdwSqueezerPage *page;
618
619 page = find_page_for_widget (self, child);
620 g_return_if_fail (page != NULL);
621
622 update_child_visible (self, page);
623 }
624
625 static void
add_page(AdwSqueezer * self,AdwSqueezerPage * page)626 add_page (AdwSqueezer *self,
627 AdwSqueezerPage *page)
628 {
629 g_return_if_fail (page->widget != NULL);
630
631 self->children = g_list_append (self->children, g_object_ref (page));
632
633 gtk_widget_set_child_visible (page->widget, FALSE);
634 gtk_widget_set_parent (page->widget, GTK_WIDGET (self));
635
636 if (self->pages)
637 g_list_model_items_changed (G_LIST_MODEL (self->pages), g_list_length (self->children) - 1, 0, 1);
638
639 g_signal_connect (page->widget, "notify::visible",
640 G_CALLBACK (squeezer_child_visibility_notify_cb), self);
641
642 if (self->visible_child == NULL &&
643 gtk_widget_get_visible (page->widget))
644 set_visible_child (self, page, self->transition_type, self->transition_duration);
645
646 if (self->homogeneous || self->visible_child == page)
647 gtk_widget_queue_resize (GTK_WIDGET (self));
648 }
649
650 static void
squeezer_remove(AdwSqueezer * self,GtkWidget * child,gboolean in_dispose)651 squeezer_remove (AdwSqueezer *self,
652 GtkWidget *child,
653 gboolean in_dispose)
654 {
655 AdwSqueezerPage *page;
656 gboolean was_visible;
657
658 page = find_page_for_widget (self, child);
659 if (!page)
660 return;
661
662 self->children = g_list_remove (self->children, page);
663
664 g_signal_handlers_disconnect_by_func (child,
665 squeezer_child_visibility_notify_cb,
666 self);
667
668 was_visible = gtk_widget_get_visible (child);
669
670 g_clear_object (&page->widget);
671
672 if (self->visible_child == page)
673 {
674 if (in_dispose)
675 self->visible_child = NULL;
676 else
677 set_visible_child (self, NULL, self->transition_type, self->transition_duration);
678 }
679
680 if (self->last_visible_child == page)
681 self->last_visible_child = NULL;
682
683 gtk_widget_unparent (child);
684
685 g_object_unref (page);
686
687 if (self->homogeneous && was_visible)
688 gtk_widget_queue_resize (GTK_WIDGET (self));
689 }
690
691 static void
adw_squeezer_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)692 adw_squeezer_get_property (GObject *object,
693 guint property_id,
694 GValue *value,
695 GParamSpec *pspec)
696 {
697 AdwSqueezer *self = ADW_SQUEEZER (object);
698
699 switch (property_id) {
700 case PROP_HOMOGENEOUS:
701 g_value_set_boolean (value, adw_squeezer_get_homogeneous (self));
702 break;
703 case PROP_VISIBLE_CHILD:
704 g_value_set_object (value, adw_squeezer_get_visible_child (self));
705 break;
706 case PROP_ALLOW_NONE:
707 g_value_set_boolean (value, adw_squeezer_get_allow_none (self));
708 break;
709 case PROP_TRANSITION_DURATION:
710 g_value_set_uint (value, adw_squeezer_get_transition_duration (self));
711 break;
712 case PROP_TRANSITION_TYPE:
713 g_value_set_enum (value, adw_squeezer_get_transition_type (self));
714 break;
715 case PROP_TRANSITION_RUNNING:
716 g_value_set_boolean (value, adw_squeezer_get_transition_running (self));
717 break;
718 case PROP_INTERPOLATE_SIZE:
719 g_value_set_boolean (value, adw_squeezer_get_interpolate_size (self));
720 break;
721 case PROP_XALIGN:
722 g_value_set_float (value, adw_squeezer_get_xalign (self));
723 break;
724 case PROP_YALIGN:
725 g_value_set_float (value, adw_squeezer_get_yalign (self));
726 break;
727 case PROP_ORIENTATION:
728 g_value_set_enum (value, get_orientation (self));
729 break;
730 case PROP_PAGES:
731 g_value_take_object (value, adw_squeezer_get_pages (self));
732 break;
733 default:
734 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
735 break;
736 }
737 }
738
739 static void
adw_squeezer_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)740 adw_squeezer_set_property (GObject *object,
741 guint property_id,
742 const GValue *value,
743 GParamSpec *pspec)
744 {
745 AdwSqueezer *self = ADW_SQUEEZER (object);
746
747 switch (property_id) {
748 case PROP_HOMOGENEOUS:
749 adw_squeezer_set_homogeneous (self, g_value_get_boolean (value));
750 break;
751 case PROP_ALLOW_NONE:
752 adw_squeezer_set_allow_none (self, g_value_get_boolean (value));
753 break;
754 case PROP_TRANSITION_DURATION:
755 adw_squeezer_set_transition_duration (self, g_value_get_uint (value));
756 break;
757 case PROP_TRANSITION_TYPE:
758 adw_squeezer_set_transition_type (self, g_value_get_enum (value));
759 break;
760 case PROP_INTERPOLATE_SIZE:
761 adw_squeezer_set_interpolate_size (self, g_value_get_boolean (value));
762 break;
763 case PROP_XALIGN:
764 adw_squeezer_set_xalign (self, g_value_get_float (value));
765 break;
766 case PROP_YALIGN:
767 adw_squeezer_set_yalign (self, g_value_get_float (value));
768 break;
769 case PROP_ORIENTATION:
770 set_orientation (self, g_value_get_enum (value));
771 break;
772 default:
773 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
774 break;
775 }
776 }
777
778 static void
adw_squeezer_snapshot_crossfade(GtkWidget * widget,GtkSnapshot * snapshot)779 adw_squeezer_snapshot_crossfade (GtkWidget *widget,
780 GtkSnapshot *snapshot)
781 {
782 AdwSqueezer *self = ADW_SQUEEZER (widget);
783 double progress = gtk_progress_tracker_get_progress (&self->tracker, FALSE);
784
785 gtk_snapshot_push_cross_fade (snapshot, progress);
786
787 if (self->last_visible_child) {
788 int width_diff = MIN (gtk_widget_get_width (widget) - self->last_visible_widget_width, 0);
789 int height_diff = MIN (gtk_widget_get_height (widget) - self->last_visible_widget_height, 0);
790 float xalign = self->xalign;
791
792 if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
793 xalign = 1 - xalign;
794
795 gtk_snapshot_translate (snapshot,
796 &GRAPHENE_POINT_INIT (
797 width_diff * xalign,
798 height_diff * self->yalign
799 ));
800
801 gtk_widget_snapshot_child (widget,
802 self->last_visible_child->widget,
803 snapshot);
804 }
805
806 gtk_snapshot_pop (snapshot);
807
808 if (self->visible_child)
809 gtk_widget_snapshot_child (widget,
810 self->visible_child->widget,
811 snapshot);
812 gtk_snapshot_pop (snapshot);
813 }
814
815
816 static void
adw_squeezer_snapshot(GtkWidget * widget,GtkSnapshot * snapshot)817 adw_squeezer_snapshot (GtkWidget *widget,
818 GtkSnapshot *snapshot)
819 {
820 AdwSqueezer *self = ADW_SQUEEZER (widget);
821
822 if (self->visible_child || self->allow_none) {
823 if (gtk_progress_tracker_get_state (&self->tracker) != GTK_PROGRESS_STATE_AFTER) {
824 gtk_snapshot_push_clip (snapshot,
825 &GRAPHENE_RECT_INIT(
826 0, 0,
827 gtk_widget_get_width (widget),
828 gtk_widget_get_height (widget)
829 ));
830
831 switch (self->active_transition_type)
832 {
833 case ADW_SQUEEZER_TRANSITION_TYPE_CROSSFADE:
834 adw_squeezer_snapshot_crossfade (widget, snapshot);
835 break;
836 case ADW_SQUEEZER_TRANSITION_TYPE_NONE:
837 default:
838 g_assert_not_reached ();
839 }
840
841 gtk_snapshot_pop (snapshot);
842 } else if (self->visible_child) {
843 gtk_widget_snapshot_child (widget,
844 self->visible_child->widget,
845 snapshot);
846 }
847 }
848 }
849
850 static void
adw_squeezer_size_allocate(GtkWidget * widget,int width,int height,int baseline)851 adw_squeezer_size_allocate (GtkWidget *widget,
852 int width,
853 int height,
854 int baseline)
855 {
856 AdwSqueezer *self = ADW_SQUEEZER (widget);
857 AdwSqueezerPage *page = NULL;
858 GList *l;
859 GtkAllocation child_allocation;
860
861 for (l = self->children; l; l = l->next) {
862 GtkWidget *child = NULL;
863 int child_min;
864 int for_size = -1;
865
866 page = l->data;
867 child = page->widget;
868
869 if (!gtk_widget_get_visible (child))
870 continue;
871
872 if (!page->enabled)
873 continue;
874
875 if (self->orientation == GTK_ORIENTATION_VERTICAL) {
876 if (gtk_widget_get_request_mode (child) == GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH)
877 for_size = width;
878
879 gtk_widget_measure (child, self->orientation, for_size,
880 &child_min, NULL, NULL, NULL);
881
882 if (child_min <= height)
883 break;
884 } else {
885 if (gtk_widget_get_request_mode (child) == GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT)
886 for_size = height;
887
888 gtk_widget_measure (child, self->orientation, for_size,
889 &child_min, NULL, NULL, NULL);
890
891 if (child_min <= width)
892 break;
893 }
894 }
895
896 if (l == NULL && self->allow_none)
897 page = NULL;
898
899 set_visible_child (self, page,
900 self->transition_type,
901 self->transition_duration);
902
903 child_allocation.x = 0;
904 child_allocation.y = 0;
905
906 if (self->last_visible_child) {
907 int min, nat;
908
909 if (self->orientation == GTK_ORIENTATION_HORIZONTAL) {
910 gtk_widget_measure (self->last_visible_child->widget, GTK_ORIENTATION_HORIZONTAL,
911 -1, &min, &nat, NULL, NULL);
912 child_allocation.width = MAX (min, width);
913 gtk_widget_measure (self->last_visible_child->widget, GTK_ORIENTATION_VERTICAL,
914 child_allocation.width, &min, &nat, NULL, NULL);
915 child_allocation.height = MAX (min, height);
916 } else {
917 gtk_widget_measure (self->last_visible_child->widget, GTK_ORIENTATION_VERTICAL,
918 -1, &min, &nat, NULL, NULL);
919 child_allocation.height = MAX (min, height);
920 gtk_widget_measure (self->last_visible_child->widget, GTK_ORIENTATION_HORIZONTAL,
921 child_allocation.height, &min, &nat, NULL, NULL);
922 child_allocation.width = MAX (min, width);
923 }
924
925 gtk_widget_size_allocate (self->last_visible_child->widget, &child_allocation, -1);
926 }
927
928 child_allocation.width = width;
929 child_allocation.height = height;
930
931 if (self->visible_child) {
932 int min;
933
934 gtk_widget_measure (self->visible_child->widget, GTK_ORIENTATION_HORIZONTAL,
935 height, &min, NULL, NULL, NULL);
936 child_allocation.width = MAX (child_allocation.width, min);
937
938 gtk_widget_measure (self->visible_child->widget, GTK_ORIENTATION_VERTICAL,
939 width, &min, NULL, NULL, NULL);
940 child_allocation.height = MAX (child_allocation.height, min);
941
942 if (child_allocation.width > width) {
943 if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
944 child_allocation.x = (width - child_allocation.width) * (1 - self->xalign);
945 else
946 child_allocation.x = (width - child_allocation.width) * self->xalign;
947 }
948
949 if (child_allocation.height > height)
950 child_allocation.y = (height - child_allocation.height) * self->yalign;
951
952 gtk_widget_size_allocate (self->visible_child->widget, &child_allocation, -1);
953 }
954 }
955
956 static void
adw_squeezer_measure(GtkWidget * widget,GtkOrientation orientation,int for_size,int * minimum,int * natural,int * minimum_baseline,int * natural_baseline)957 adw_squeezer_measure (GtkWidget *widget,
958 GtkOrientation orientation,
959 int for_size,
960 int *minimum,
961 int *natural,
962 int *minimum_baseline,
963 int *natural_baseline)
964 {
965 AdwSqueezer *self = ADW_SQUEEZER (widget);
966 int child_min, child_nat;
967 GList *l;
968 int min = 0, nat = 0;
969
970 for (l = self->children; l != NULL; l = l->next) {
971 AdwSqueezerPage *page = l->data;
972 GtkWidget *child = page->widget;
973
974 if (self->orientation != orientation && !self->homogeneous &&
975 self->visible_child != page)
976 continue;
977
978 if (!gtk_widget_get_visible (child))
979 continue;
980
981 /* Disabled children are taken into account when measuring the widget, to
982 * keep its size request and allocation consistent. This avoids the
983 * appearant size and position of a child to changes suddenly when a larger
984 * child gets enabled/disabled.
985 */
986 gtk_widget_measure (child, orientation, for_size,
987 &child_min, &child_nat, NULL, NULL);
988
989 if (self->orientation == orientation) {
990 if (self->allow_none)
991 min = 0;
992 else
993 min = min == 0 ? child_min : MIN (min, child_min);
994 } else {
995 min = MAX (min, child_min);
996 }
997
998 nat = MAX (nat, child_nat);
999 }
1000
1001 if (self->orientation != orientation && !self->homogeneous &&
1002 self->interpolate_size &&
1003 (self->last_visible_child != NULL || self->allow_none)) {
1004 double t = gtk_progress_tracker_get_ease_out_cubic (&self->tracker, FALSE);
1005
1006 if (orientation == GTK_ORIENTATION_VERTICAL) {
1007 min = adw_lerp (self->last_visible_widget_height, min, t);
1008 nat = adw_lerp (self->last_visible_widget_height, nat, t);
1009 } else {
1010 min = adw_lerp (self->last_visible_widget_width, min, t);
1011 nat = adw_lerp (self->last_visible_widget_width, nat, t);
1012 }
1013 }
1014
1015 if (minimum)
1016 *minimum = min;
1017 if (natural)
1018 *natural = nat;
1019 if (minimum_baseline)
1020 *minimum_baseline = -1;
1021 if (natural_baseline)
1022 *natural_baseline = -1;
1023 }
1024
1025 static void
adw_squeezer_dispose(GObject * object)1026 adw_squeezer_dispose (GObject *object)
1027 {
1028 AdwSqueezer *self = ADW_SQUEEZER (object);
1029 GtkWidget *child;
1030
1031 if (self->pages)
1032 g_list_model_items_changed (G_LIST_MODEL (self->pages), 0,
1033 g_list_length (self->children), 0);
1034
1035 while ((child = gtk_widget_get_first_child (GTK_WIDGET (self))))
1036 squeezer_remove (self, child, TRUE);
1037
1038 G_OBJECT_CLASS (adw_squeezer_parent_class)->dispose (object);
1039 }
1040
1041 static void
adw_squeezer_finalize(GObject * object)1042 adw_squeezer_finalize (GObject *object)
1043 {
1044 AdwSqueezer *self = ADW_SQUEEZER (object);
1045
1046 if (self->pages)
1047 g_object_remove_weak_pointer (G_OBJECT (self->pages),
1048 (gpointer *) &self->pages);
1049
1050 adw_squeezer_unschedule_ticks (self);
1051
1052 G_OBJECT_CLASS (adw_squeezer_parent_class)->finalize (object);
1053 }
1054
1055 static void
adw_squeezer_class_init(AdwSqueezerClass * klass)1056 adw_squeezer_class_init (AdwSqueezerClass *klass)
1057 {
1058 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1059 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1060
1061 object_class->get_property = adw_squeezer_get_property;
1062 object_class->set_property = adw_squeezer_set_property;
1063 object_class->dispose = adw_squeezer_dispose;
1064 object_class->finalize = adw_squeezer_finalize;
1065
1066 widget_class->size_allocate = adw_squeezer_size_allocate;
1067 widget_class->snapshot = adw_squeezer_snapshot;
1068 widget_class->measure = adw_squeezer_measure;
1069 widget_class->get_request_mode = adw_widget_get_request_mode;
1070 widget_class->compute_expand = adw_widget_compute_expand;
1071
1072 g_object_class_override_property (object_class,
1073 PROP_ORIENTATION,
1074 "orientation");
1075
1076 /**
1077 * AdwSqueezer:homogeneous: (attributes org.gtk.Property.get=adw_squeezer_get_homogeneous org.gtk.Property.set=adw_squeezer_set_homogeneous)
1078 *
1079 * Whether all children have the same size for the opposite orientation.
1080 *
1081 * For example, if a squeezer is horizontal and is homogeneous, it will request
1082 * the same height for all its children. If it isn't, the squeezer may change
1083 * size when a different child becomes visible.
1084 *
1085 * Since: 1.0
1086 */
1087 props[PROP_HOMOGENEOUS] =
1088 g_param_spec_boolean ("homogeneous",
1089 "Homogeneous",
1090 "Whether all children have the same size for the opposite orientation",
1091 FALSE,
1092 G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
1093
1094 /**
1095 * AdwSqueezer:visible-child: (attributes org.gtk.Property.get=adw_squeezer_get_visible_child)
1096 *
1097 * The currently visible child.
1098 *
1099 * Since: 1.0
1100 */
1101 props[PROP_VISIBLE_CHILD] =
1102 g_param_spec_object ("visible-child",
1103 "Visible child",
1104 "The currently visible child",
1105 GTK_TYPE_WIDGET,
1106 G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
1107
1108 /**
1109 * AdwSqueezer:allow-none: (attributes org.gtk.Property.get=adw_squeezer_get_allow_none org.gtk.Property.set=adw_squeezer_set_allow_none)
1110 *
1111 * Whether to allow squeezing beyond the last child's minimum size.
1112 *
1113 * If set to `TRUE`, the squeezer can shrink to the point where no child can
1114 * be shown. This is functionally equivalent to appending a widget with 0x0
1115 * minimum size.
1116 *
1117 * Since: 1.0
1118 */
1119 props[PROP_ALLOW_NONE] =
1120 g_param_spec_boolean ("allow-none",
1121 "Allow none",
1122 "Whether to allow squeezing beyond the last child's minimum size",
1123 FALSE,
1124 G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
1125
1126 /**
1127 * AdwSqueezer:transition-duration: (attributes org.gtk.Property.get=adw_squeezer_get_transition_duration org.gtk.Property.set=adw_squeezer_set_transition_duration)
1128 *
1129 * The animation duration, in milliseconds.
1130 *
1131 * Since: 1.0
1132 */
1133 props[PROP_TRANSITION_DURATION] =
1134 g_param_spec_uint ("transition-duration",
1135 "Transition duration",
1136 "The animation duration, in milliseconds",
1137 0, G_MAXUINT, 200,
1138 G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
1139
1140 /**
1141 * AdwSqueezer:transition-type: (attributes org.gtk.Property.get=adw_squeezer_get_transition_type org.gtk.Property.set=adw_squeezer_set_transition_type)
1142 *
1143 * The type of animation used for transitions between children.
1144 *
1145 * Since: 1.0
1146 */
1147 props[PROP_TRANSITION_TYPE] =
1148 g_param_spec_enum ("transition-type",
1149 "Transition type",
1150 "The type of animation used for transitions between children",
1151 ADW_TYPE_SQUEEZER_TRANSITION_TYPE,
1152 ADW_SQUEEZER_TRANSITION_TYPE_NONE,
1153 G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
1154
1155 /**
1156 * AdwSqueezer:transition-running: (attributes org.gtk.Property.get=adw_squeezer_get_transition_running)
1157 *
1158 * Whether a transition is currently running.
1159 *
1160 * Since: 1.0
1161 */
1162 props[PROP_TRANSITION_RUNNING] =
1163 g_param_spec_boolean ("transition-running",
1164 "Transition running",
1165 "Whether a transition is currently running",
1166 FALSE,
1167 G_PARAM_READABLE);
1168
1169 /**
1170 * AdwSqueezer:interpolate-size: (attributes org.gtk.Property.get=adw_squeezer_get_interpolate_size org.gtk.Property.set=adw_squeezer_set_interpolate_size)
1171 *
1172 * Whether the squeezer interpolates its size when changing the visible child.
1173 *
1174 * If `TRUE`, the squeezer will interpolate its size between the one of the
1175 * previous visible child and the one of the new visible child, according to
1176 * the set transition duration and the orientation, e.g. if the squeezer is
1177 * horizontal, it will interpolate the its height.
1178 *
1179 * Since: 1.0
1180 */
1181 props[PROP_INTERPOLATE_SIZE] =
1182 g_param_spec_boolean ("interpolate-size",
1183 "Interpolate size",
1184 "Whether the squeezer interpolates its size when changing the visible child",
1185 FALSE,
1186 G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
1187
1188 /**
1189 * AdwSqueezer:xalign: (attributes org.gtk.Property.get=adw_squeezer_get_xalign org.gtk.Property.set=adw_squeezer_set_xalign)
1190 *
1191 * The horizontal alignment, from 0 (start) to 1 (end).
1192 *
1193 * This affects the children allocation during transitions, when they exceed
1194 * the size of the squeezer.
1195 *
1196 * For example, 0.5 means the child will be centered, 0 means it will keep the
1197 * start side aligned and overflow the end side, and 1 means the opposite.
1198 *
1199 * Since: 1.0
1200 */
1201 props[PROP_XALIGN] =
1202 g_param_spec_float ("xalign",
1203 "X align",
1204 "The horizontal alignment, from 0 (start) to 1 (end)",
1205 0.0, 1.0,
1206 0.5,
1207 G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
1208
1209 /**
1210 * AdwSqueezer:yalign: (attributes org.gtk.Property.get=adw_squeezer_get_yalign org.gtk.Property.set=adw_squeezer_set_yalign)
1211 *
1212 * The vertical alignment, from 0 (top) to 1 (bottom).
1213 *
1214 * This affects the children allocation during transitions, when they exceed
1215 * the size of the squeezer.
1216 *
1217 * For example, 0.5 means the child will be centered, 0 means it will keep the
1218 * top side aligned and overflow the bottom side, and 1 means the opposite.
1219 *
1220 * Since: 1.0
1221 */
1222 props[PROP_YALIGN] =
1223 g_param_spec_float ("yalign",
1224 "Y align",
1225 "The vertical alignment, from 0 (top) to 1 (bottom)",
1226 0.0, 1.0,
1227 0.5,
1228 G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
1229
1230 /**
1231 * AdwSqueezer:pages: (attributes org.gtk.Property.get=adw_squeezer_get_pages)
1232 *
1233 * A selection model with the squeezer's pages.
1234 *
1235 * This can be used to keep an up-to-date view. The model also implements
1236 * [iface@Gtk.SelectionModel] and can be used to track the visible page.
1237 *
1238 * Since: 1.0
1239 */
1240 props[PROP_PAGES] =
1241 g_param_spec_object ("pages",
1242 "Pages",
1243 "A selection model with the squeezer's pages",
1244 GTK_TYPE_SELECTION_MODEL,
1245 G_PARAM_READABLE);
1246
1247 g_object_class_install_properties (object_class, LAST_PROP, props);
1248
1249 gtk_widget_class_set_css_name (widget_class, "squeezer");
1250 }
1251
1252 static void
adw_squeezer_init(AdwSqueezer * self)1253 adw_squeezer_init (AdwSqueezer *self)
1254 {
1255 self->homogeneous = TRUE;
1256 self->transition_duration = 200;
1257 self->transition_type = ADW_SQUEEZER_TRANSITION_TYPE_NONE;
1258 self->xalign = 0.5;
1259 self->yalign = 0.5;
1260 }
1261
1262 static void
adw_squeezer_buildable_add_child(GtkBuildable * buildable,GtkBuilder * builder,GObject * child,const char * type)1263 adw_squeezer_buildable_add_child (GtkBuildable *buildable,
1264 GtkBuilder *builder,
1265 GObject *child,
1266 const char *type)
1267 {
1268 if (ADW_IS_SQUEEZER_PAGE (child))
1269 add_page (ADW_SQUEEZER (buildable), ADW_SQUEEZER_PAGE (child));
1270 else if (GTK_IS_WIDGET (child))
1271 adw_squeezer_add (ADW_SQUEEZER (buildable), GTK_WIDGET (child));
1272 else
1273 parent_buildable_iface->add_child (buildable, builder, child, type);
1274 }
1275
1276 static void
adw_squeezer_buildable_init(GtkBuildableIface * iface)1277 adw_squeezer_buildable_init (GtkBuildableIface *iface)
1278 {
1279 parent_buildable_iface = g_type_interface_peek_parent (iface);
1280
1281 iface->add_child = adw_squeezer_buildable_add_child;
1282 }
1283
1284 /**
1285 * adw_squeezer_page_get_child: (attributes org.gtk.Method.get_property=child)
1286 * @self: a `AdwSqueezerPage`
1287 *
1288 * Returns the squeezer child to which @self belongs.
1289 *
1290 * Returns: (transfer none): the child to which @self belongs
1291 *
1292 * Since: 1.0
1293 */
1294 GtkWidget *
adw_squeezer_page_get_child(AdwSqueezerPage * self)1295 adw_squeezer_page_get_child (AdwSqueezerPage *self)
1296 {
1297 g_return_val_if_fail (ADW_IS_SQUEEZER_PAGE (self), NULL);
1298
1299 return self->widget;
1300 }
1301
1302 /**
1303 * adw_squeezer_page_get_enabled: (attributes org.gtk.Method.get_property=enabled)
1304 * @self: a `AdwSqueezerPage`
1305 *
1306 * Gets whether @self is enabled.
1307 *
1308 * Returns: whether @self is enabled
1309 *
1310 * Since: 1.0
1311 */
1312 gboolean
adw_squeezer_page_get_enabled(AdwSqueezerPage * self)1313 adw_squeezer_page_get_enabled (AdwSqueezerPage *self)
1314 {
1315 g_return_val_if_fail (ADW_IS_SQUEEZER_PAGE (self), FALSE);
1316
1317 return self->enabled;
1318 }
1319
1320 /**
1321 * adw_squeezer_page_set_enabled: (attributes org.gtk.Method.set_property=enabled)
1322 * @self: a `AdwSqueezerPage`
1323 * @enabled: whether @self is enabled
1324 *
1325 * Sets whether @self is enabled.
1326 *
1327 * Since: 1.0
1328 */
1329 void
adw_squeezer_page_set_enabled(AdwSqueezerPage * self,gboolean enabled)1330 adw_squeezer_page_set_enabled (AdwSqueezerPage *self,
1331 gboolean enabled)
1332 {
1333 g_return_if_fail (ADW_IS_SQUEEZER_PAGE (self));
1334
1335 enabled = !!enabled;
1336
1337 if (enabled == self->enabled)
1338 return;
1339
1340 self->enabled = enabled;
1341
1342 if (self->widget && gtk_widget_get_parent (self->widget)) {
1343 AdwSqueezer *squeezer = ADW_SQUEEZER (gtk_widget_get_parent (self->widget));
1344
1345 gtk_widget_queue_resize (GTK_WIDGET (squeezer));
1346 update_child_visible (squeezer, self);
1347 }
1348
1349 g_object_notify_by_pspec (G_OBJECT (self), page_props[PAGE_PROP_ENABLED]);
1350 }
1351
1352 /**
1353 * adw_squeezer_new:
1354 *
1355 * Creates a new `AdwSqueezer`.
1356 *
1357 * Returns: the newly created `AdwSqueezer`
1358 *
1359 * Since: 1.0
1360 */
1361 GtkWidget *
adw_squeezer_new(void)1362 adw_squeezer_new (void)
1363 {
1364 return g_object_new (ADW_TYPE_SQUEEZER, NULL);
1365 }
1366
1367 /**
1368 * adw_squeezer_add:
1369 * @self: a `AdwSqueezer`
1370 * @child: the widget to add
1371 *
1372 * Adds a child to @self.
1373 *
1374 * Returns: (transfer none): the [class@Adw.SqueezerPage] for @child
1375 *
1376 * Since: 1.0
1377 */
1378 AdwSqueezerPage *
adw_squeezer_add(AdwSqueezer * self,GtkWidget * child)1379 adw_squeezer_add (AdwSqueezer *self,
1380 GtkWidget *child)
1381 {
1382 AdwSqueezerPage *page;
1383
1384 g_return_val_if_fail (ADW_IS_SQUEEZER (self), NULL);
1385 g_return_val_if_fail (GTK_IS_WIDGET (child), NULL);
1386
1387 page = g_object_new (ADW_TYPE_SQUEEZER_PAGE, NULL);
1388 page->widget = g_object_ref (child);
1389
1390 add_page (self, page);
1391
1392 g_object_unref (page);
1393
1394 return page;
1395 }
1396
1397 /**
1398 * adw_squeezer_remove:
1399 * @self: a `AdwSqueezer`
1400 * @child: the child to remove
1401 *
1402 * Removes a child widget from @self.
1403 *
1404 * Since: 1.0
1405 */
1406 void
adw_squeezer_remove(AdwSqueezer * self,GtkWidget * child)1407 adw_squeezer_remove (AdwSqueezer *self,
1408 GtkWidget *child)
1409 {
1410 GList *l;
1411 guint position;
1412
1413 g_return_if_fail (ADW_IS_SQUEEZER (self));
1414 g_return_if_fail (GTK_IS_WIDGET (child));
1415 g_return_if_fail (gtk_widget_get_parent (child) == GTK_WIDGET (self));
1416
1417 for (l = self->children, position = 0; l; l = l->next, position++) {
1418 AdwSqueezerPage *page = l->data;
1419
1420 if (page->widget == child)
1421 break;
1422 }
1423
1424 squeezer_remove (self, child, FALSE);
1425
1426 if (self->pages)
1427 g_list_model_items_changed (G_LIST_MODEL (self->pages), position, 1, 0);
1428 }
1429
1430 /**
1431 * adw_squeezer_get_page:
1432 * @self: a `AdwSqueezer`
1433 * @child: a child of @self
1434 *
1435 * Returns the [class@Adw.SqueezerPage] object for @child.
1436 *
1437 * Returns: (transfer none): the page object for @child
1438 *
1439 * Since: 1.0
1440 */
1441 AdwSqueezerPage *
adw_squeezer_get_page(AdwSqueezer * self,GtkWidget * child)1442 adw_squeezer_get_page (AdwSqueezer *self,
1443 GtkWidget *child)
1444 {
1445 g_return_val_if_fail (ADW_IS_SQUEEZER (self), NULL);
1446 g_return_val_if_fail (GTK_IS_WIDGET (child), NULL);
1447
1448 return find_page_for_widget (self, child);
1449 }
1450
1451 /**
1452 * adw_squeezer_get_homogeneous: (attributes org.gtk.Method.get_property=homogeneous)
1453 * @self: a `AdwSqueezer`
1454 *
1455 * Gets whether all children have the same size for the opposite orientation.
1456 *
1457 * Returns: whether @self is homogeneous
1458 *
1459 * Since: 1.0
1460 */
1461 gboolean
adw_squeezer_get_homogeneous(AdwSqueezer * self)1462 adw_squeezer_get_homogeneous (AdwSqueezer *self)
1463 {
1464 g_return_val_if_fail (ADW_IS_SQUEEZER (self), FALSE);
1465
1466 return self->homogeneous;
1467 }
1468
1469 /**
1470 * adw_squeezer_set_homogeneous: (attributes org.gtk.Method.set_property=homogeneous)
1471 * @self: a `AdwSqueezer`
1472 * @homogeneous: whether @self is homogeneous
1473 *
1474 * Sets whether all children have the same size for the opposite orientation.
1475 *
1476 * Since: 1.0
1477 */
1478 void
adw_squeezer_set_homogeneous(AdwSqueezer * self,gboolean homogeneous)1479 adw_squeezer_set_homogeneous (AdwSqueezer *self,
1480 gboolean homogeneous)
1481 {
1482 g_return_if_fail (ADW_IS_SQUEEZER (self));
1483
1484 homogeneous = !!homogeneous;
1485
1486 if (self->homogeneous == homogeneous)
1487 return;
1488
1489 self->homogeneous = homogeneous;
1490
1491 if (gtk_widget_get_visible (GTK_WIDGET(self)))
1492 gtk_widget_queue_resize (GTK_WIDGET (self));
1493
1494 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_HOMOGENEOUS]);
1495 }
1496
1497 /**
1498 * adw_squeezer_get_allow_none: (attributes org.gtk.Method.get_property=allow-none)
1499 * @self: a `AdwSqueezer`
1500 *
1501 * Gets whether to allow squeezing beyond the last child's minimum size.
1502 *
1503 * Returns: whether @self allows squeezing beyond the last child
1504 *
1505 * Since: 1.0
1506 */
1507 gboolean
adw_squeezer_get_allow_none(AdwSqueezer * self)1508 adw_squeezer_get_allow_none (AdwSqueezer *self)
1509 {
1510 g_return_val_if_fail (ADW_IS_SQUEEZER (self), FALSE);
1511
1512 return self->allow_none;
1513 }
1514
1515 /**
1516 * adw_squeezer_set_allow_none: (attributes org.gtk.Method.set_property=allow-none)
1517 * @self: a `AdwSqueezer`
1518 * @allow_none: whether @self allows squeezing beyond the last child
1519 *
1520 * Sets whether to allow squeezing beyond the last child's minimum size.
1521 *
1522 * Since: 1.0
1523 */
1524 void
adw_squeezer_set_allow_none(AdwSqueezer * self,gboolean allow_none)1525 adw_squeezer_set_allow_none (AdwSqueezer *self,
1526 gboolean allow_none)
1527 {
1528 g_return_if_fail (ADW_IS_SQUEEZER (self));
1529
1530 allow_none = !!allow_none;
1531
1532 if (self->allow_none == allow_none)
1533 return;
1534
1535 self->allow_none = allow_none;
1536
1537 gtk_widget_queue_resize (GTK_WIDGET (self));
1538
1539 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ALLOW_NONE]);
1540 }
1541
1542 /**
1543 * adw_squeezer_get_transition_duration: (attributes org.gtk.Method.get_property=transition-duration)
1544 * @self: a `AdwSqueezer`
1545 *
1546 * Gets the transition animation duration for @self.
1547 *
1548 * Returns: the transition duration, in milliseconds
1549 *
1550 * Since: 1.0
1551 */
1552 guint
adw_squeezer_get_transition_duration(AdwSqueezer * self)1553 adw_squeezer_get_transition_duration (AdwSqueezer *self)
1554 {
1555 g_return_val_if_fail (ADW_IS_SQUEEZER (self), 0);
1556
1557 return self->transition_duration;
1558 }
1559
1560 /**
1561 * adw_squeezer_set_transition_duration: (attributes org.gtk.Method.set_property=transition-duration)
1562 * @self: a `AdwSqueezer`
1563 * @duration: the new duration, in milliseconds
1564 *
1565 * Sets the transition animation duration for @self.
1566 *
1567 * Since: 1.0
1568 */
1569 void
adw_squeezer_set_transition_duration(AdwSqueezer * self,guint duration)1570 adw_squeezer_set_transition_duration (AdwSqueezer *self,
1571 guint duration)
1572 {
1573 g_return_if_fail (ADW_IS_SQUEEZER (self));
1574
1575 if (self->transition_duration == duration)
1576 return;
1577
1578 self->transition_duration = duration;
1579 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TRANSITION_DURATION]);
1580 }
1581
1582 /**
1583 * adw_squeezer_get_transition_type: (attributes org.gtk.Method.get_property=transition-type)
1584 * @self: a `AdwSqueezer`
1585 *
1586 * Gets the type of animation used for transitions between children in @self.
1587 *
1588 * Returns: the current transition type of @self
1589 *
1590 * Since: 1.0
1591 */
1592 AdwSqueezerTransitionType
adw_squeezer_get_transition_type(AdwSqueezer * self)1593 adw_squeezer_get_transition_type (AdwSqueezer *self)
1594 {
1595 g_return_val_if_fail (ADW_IS_SQUEEZER (self), ADW_SQUEEZER_TRANSITION_TYPE_NONE);
1596
1597 return self->transition_type;
1598 }
1599
1600 /**
1601 * adw_squeezer_set_transition_type: (attributes org.gtk.Method.set_property=transition-type)
1602 * @self: a `AdwSqueezer`
1603 * @transition: the new transition type
1604 *
1605 * Sets the type of animation used for transitions between children in @self.
1606 *
1607 * Since: 1.0
1608 */
1609 void
adw_squeezer_set_transition_type(AdwSqueezer * self,AdwSqueezerTransitionType transition)1610 adw_squeezer_set_transition_type (AdwSqueezer *self,
1611 AdwSqueezerTransitionType transition)
1612 {
1613 g_return_if_fail (ADW_IS_SQUEEZER (self));
1614
1615 if (self->transition_type == transition)
1616 return;
1617
1618 self->transition_type = transition;
1619 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TRANSITION_TYPE]);
1620 }
1621
1622 /**
1623 * adw_squeezer_get_transition_running: (attributes org.gtk.Method.get_property=transition-running)
1624 * @self: a `AdwSqueezer`
1625 *
1626 * Gets whether a transition is currently running for @self.
1627 *
1628 * Returns: whether a transition is currently running
1629 *
1630 * Since: 1.0
1631 */
1632 gboolean
adw_squeezer_get_transition_running(AdwSqueezer * self)1633 adw_squeezer_get_transition_running (AdwSqueezer *self)
1634 {
1635 g_return_val_if_fail (ADW_IS_SQUEEZER (self), FALSE);
1636
1637 return (self->tick_id != 0);
1638 }
1639
1640 /**
1641 * adw_squeezer_get_interpolate_size: (attributes org.gtk.Method.get_property=interpolate-size)
1642 * @self: A `AdwSqueezer`
1643 *
1644 * Gets whether @self interpolates its size when changing the visible child.
1645 *
1646 * Returns: whether the size is interpolated
1647 *
1648 * Since: 1.0
1649 */
1650 gboolean
adw_squeezer_get_interpolate_size(AdwSqueezer * self)1651 adw_squeezer_get_interpolate_size (AdwSqueezer *self)
1652 {
1653 g_return_val_if_fail (ADW_IS_SQUEEZER (self), FALSE);
1654
1655 return self->interpolate_size;
1656 }
1657
1658 /**
1659 * adw_squeezer_set_interpolate_size: (attributes org.gtk.Method.set_property=interpolate-size)
1660 * @self: A `AdwSqueezer`
1661 * @interpolate_size: whether to interpolate the size
1662 *
1663 * Sets whether @self interpolates its size when changing the visible child.
1664 *
1665 * Since: 1.0
1666 */
1667 void
adw_squeezer_set_interpolate_size(AdwSqueezer * self,gboolean interpolate_size)1668 adw_squeezer_set_interpolate_size (AdwSqueezer *self,
1669 gboolean interpolate_size)
1670 {
1671 g_return_if_fail (ADW_IS_SQUEEZER (self));
1672
1673 interpolate_size = !!interpolate_size;
1674
1675 if (self->interpolate_size == interpolate_size)
1676 return;
1677
1678 self->interpolate_size = interpolate_size;
1679 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_INTERPOLATE_SIZE]);
1680 }
1681
1682 /**
1683 * adw_squeezer_get_visible_child: (attributes org.gtk.Method.get_property=visible-child)
1684 * @self: a `AdwSqueezer`
1685 *
1686 * Gets the currently visible child of @self.
1687 *
1688 * Returns: (transfer none) (nullable): the visible child
1689 *
1690 * Since: 1.0
1691 */
1692 GtkWidget *
adw_squeezer_get_visible_child(AdwSqueezer * self)1693 adw_squeezer_get_visible_child (AdwSqueezer *self)
1694 {
1695 g_return_val_if_fail (ADW_IS_SQUEEZER (self), NULL);
1696
1697 return self->visible_child ? self->visible_child->widget : NULL;
1698 }
1699
1700 /**
1701 * adw_squeezer_get_xalign: (attributes org.gtk.Method.get_property=xalign)
1702 * @self: a `AdwSqueezer`
1703 *
1704 * Gets the horizontal alignment, from 0 (start) to 1 (end).
1705 *
1706 * Returns: the alignment value
1707 *
1708 * Since: 1.0
1709 */
1710 float
adw_squeezer_get_xalign(AdwSqueezer * self)1711 adw_squeezer_get_xalign (AdwSqueezer *self)
1712 {
1713 g_return_val_if_fail (ADW_IS_SQUEEZER (self), 0.5);
1714
1715 return self->xalign;
1716 }
1717
1718 /**
1719 * adw_squeezer_set_xalign: (attributes org.gtk.Method.set_property=xalign)
1720 * @self: a `AdwSqueezer`
1721 * @xalign: the new alignment value
1722 *
1723 * Sets the horizontal alignment, from 0 (start) to 1 (end).
1724 *
1725 * Since: 1.0
1726 */
1727 void
adw_squeezer_set_xalign(AdwSqueezer * self,float xalign)1728 adw_squeezer_set_xalign (AdwSqueezer *self,
1729 float xalign)
1730 {
1731 g_return_if_fail (ADW_IS_SQUEEZER (self));
1732
1733 xalign = CLAMP (xalign, 0.0, 1.0);
1734
1735 if (self->xalign == xalign)
1736 return;
1737
1738 self->xalign = xalign;
1739 gtk_widget_queue_draw (GTK_WIDGET (self));
1740 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_XALIGN]);
1741 }
1742
1743 /**
1744 * adw_squeezer_get_yalign: (attributes org.gtk.Method.get_property=yalign)
1745 * @self: a `AdwSqueezer`
1746 *
1747 * Gets the vertical alignment, from 0 (top) to 1 (bottom).
1748 *
1749 * Returns: the alignment value
1750 *
1751 * Since: 1.0
1752 */
1753 float
adw_squeezer_get_yalign(AdwSqueezer * self)1754 adw_squeezer_get_yalign (AdwSqueezer *self)
1755 {
1756 g_return_val_if_fail (ADW_IS_SQUEEZER (self), 0.5);
1757
1758 return self->yalign;
1759 }
1760
1761 /**
1762 * adw_squeezer_set_yalign: (attributes org.gtk.Method.set_property=yalign)
1763 * @self: a `AdwSqueezer`
1764 * @yalign: the new alignment value
1765 *
1766 * Sets the vertical alignment, from 0 (top) to 1 (bottom).
1767 *
1768 * Since: 1.0
1769 */
1770 void
adw_squeezer_set_yalign(AdwSqueezer * self,float yalign)1771 adw_squeezer_set_yalign (AdwSqueezer *self,
1772 float yalign)
1773 {
1774 g_return_if_fail (ADW_IS_SQUEEZER (self));
1775
1776 yalign = CLAMP (yalign, 0.0, 1.0);
1777
1778 if (self->yalign == yalign)
1779 return;
1780
1781 self->yalign = yalign;
1782 gtk_widget_queue_draw (GTK_WIDGET (self));
1783 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_YALIGN]);
1784 }
1785
1786 /**
1787 * adw_squeezer_get_pages: (attributes org.gtk.Method.get_property=pages)
1788 * @self: a `AdwSqueezer`
1789 *
1790 * Returns a `GListModel` that contains the pages of the squeezer,
1791 *
1792 * This can be used to keep an up-to-date view. The model also implements
1793 * [iface@Gtk.SelectionModel] and can be used to track the visible page.
1794 *
1795 * Returns: (transfer full): a `GtkSelectionModel` for the squeezer's children
1796 *
1797 * Since: 1.0
1798 */
1799 GtkSelectionModel *
adw_squeezer_get_pages(AdwSqueezer * self)1800 adw_squeezer_get_pages (AdwSqueezer *self)
1801 {
1802 g_return_val_if_fail (ADW_IS_SQUEEZER (self), NULL);
1803
1804 if (self->pages)
1805 return g_object_ref (self->pages);
1806
1807 self->pages = GTK_SELECTION_MODEL (adw_squeezer_pages_new (self));
1808 g_object_add_weak_pointer (G_OBJECT (self->pages), (gpointer *) &self->pages);
1809
1810 return self->pages;
1811 }
1812