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