1 /*
2 * Copyright (C) 2019 Zander Brown <zbrown@gnome.org>
3 * Copyright (C) 2019 Purism SPC
4 *
5 * SPDX-License-Identifier: LGPL-2.1-or-later
6 */
7
8 #include "config.h"
9
10 #include "adw-enums.h"
11 #include "adw-view-switcher-bar.h"
12
13 /**
14 * AdwViewSwitcherBar:
15 *
16 * A view switcher action bar.
17 *
18 * An action bar letting you switch between multiple views contained in a
19 * [class@Adw.ViewStack], via an [class@Adw.ViewSwitcher]. It is designed to be put
20 * at the bottom of a window and to be revealed only on really narrow windows,
21 * e.g. on mobile phones. It can't be revealed if there are less than two pages.
22 *
23 * You can conveniently bind the [property@Adw.ViewSwitcherBar:reveal] property
24 * to [property@Adw.ViewSwitcherTitle:title-visible] to automatically reveal the
25 * view switcher bar when the title label is displayed in place of the view
26 * switcher.
27 *
28 * An example of the UI definition for a common use case:
29 *
30 * ```xml
31 * <object class="GtkWindow"/>
32 * <child type="titlebar">
33 * <object class="AdwHeaderBar">
34 * <property name="centering-policy">strict</property>
35 * <child type="title">
36 * <object class="AdwViewSwitcherTitle" id="title">
37 * <property name="stack">stack</property>
38 * </object>
39 * </child>
40 * </object>
41 * </child>
42 * <child>
43 * <object class="GtkBox">
44 * <child>
45 * <object class="AdwViewStack" id="stack"/>
46 * </child>
47 * <child>
48 * <object class="AdwViewSwitcherBar">
49 * <property name="stack">stack</property>
50 * <binding name="reveal">
51 * <lookup name="title-visible">title</lookup>
52 * </binding>
53 * </object>
54 * </child>
55 * </object>
56 * </child>
57 * </object>
58 * ```
59 *
60 * ## CSS nodes
61 *
62 * `AdwViewSwitcherBar` has a single CSS node with name` viewswitcherbar`.
63 *
64 * Since: 1.0
65 */
66
67 enum {
68 PROP_0,
69 PROP_POLICY,
70 PROP_STACK,
71 PROP_REVEAL,
72 LAST_PROP,
73 };
74
75 struct _AdwViewSwitcherBar
76 {
77 GtkWidget parent_instance;
78
79 GtkWidget *action_bar;
80 GtkRevealer *revealer;
81 AdwViewSwitcher *view_switcher;
82
83 AdwViewSwitcherPolicy policy;
84 GtkSelectionModel *pages;
85 gboolean reveal;
86 };
87
88 static GParamSpec *props[LAST_PROP];
89
G_DEFINE_TYPE(AdwViewSwitcherBar,adw_view_switcher_bar,GTK_TYPE_WIDGET)90 G_DEFINE_TYPE (AdwViewSwitcherBar, adw_view_switcher_bar, GTK_TYPE_WIDGET)
91
92 static void
93 update_bar_revealed (AdwViewSwitcherBar *self) {
94 int count = 0;
95
96 if (!self->revealer)
97 return;
98
99 if (self->reveal && self->pages) {
100 guint i, n;
101
102 n = g_list_model_get_n_items (G_LIST_MODEL (self->pages));
103 for (i = 0; i < n; i++) {
104 AdwViewStackPage *page = g_list_model_get_item (G_LIST_MODEL (self->pages), i);
105
106 if (adw_view_stack_page_get_visible (page))
107 count++;
108 }
109 }
110
111 gtk_revealer_set_reveal_child (self->revealer, count > 1);
112 }
113
114 static void
adw_view_switcher_bar_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)115 adw_view_switcher_bar_get_property (GObject *object,
116 guint prop_id,
117 GValue *value,
118 GParamSpec *pspec)
119 {
120 AdwViewSwitcherBar *self = ADW_VIEW_SWITCHER_BAR (object);
121
122 switch (prop_id) {
123 case PROP_POLICY:
124 g_value_set_enum (value, adw_view_switcher_bar_get_policy (self));
125 break;
126 case PROP_STACK:
127 g_value_set_object (value, adw_view_switcher_bar_get_stack (self));
128 break;
129 case PROP_REVEAL:
130 g_value_set_boolean (value, adw_view_switcher_bar_get_reveal (self));
131 break;
132 default:
133 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
134 break;
135 }
136 }
137
138 static void
adw_view_switcher_bar_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)139 adw_view_switcher_bar_set_property (GObject *object,
140 guint prop_id,
141 const GValue *value,
142 GParamSpec *pspec)
143 {
144 AdwViewSwitcherBar *self = ADW_VIEW_SWITCHER_BAR (object);
145
146 switch (prop_id) {
147 case PROP_POLICY:
148 adw_view_switcher_bar_set_policy (self, g_value_get_enum (value));
149 break;
150 case PROP_STACK:
151 adw_view_switcher_bar_set_stack (self, g_value_get_object (value));
152 break;
153 case PROP_REVEAL:
154 adw_view_switcher_bar_set_reveal (self, g_value_get_boolean (value));
155 break;
156 default:
157 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
158 break;
159 }
160 }
161
162 static void
adw_view_switcher_bar_dispose(GObject * object)163 adw_view_switcher_bar_dispose (GObject *object)
164 {
165 AdwViewSwitcherBar *self = ADW_VIEW_SWITCHER_BAR (object);
166
167 gtk_widget_unparent (self->action_bar);
168 self->revealer = NULL;
169
170 G_OBJECT_CLASS (adw_view_switcher_bar_parent_class)->dispose (object);
171 }
172
173 static void
adw_view_switcher_bar_class_init(AdwViewSwitcherBarClass * klass)174 adw_view_switcher_bar_class_init (AdwViewSwitcherBarClass *klass)
175 {
176 GObjectClass *object_class = G_OBJECT_CLASS (klass);
177 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
178
179 object_class->get_property = adw_view_switcher_bar_get_property;
180 object_class->set_property = adw_view_switcher_bar_set_property;
181 object_class->dispose = adw_view_switcher_bar_dispose;
182
183 /**
184 * AdwViewSwitcherBar:policy: (attributes org.gtk.Property.get=adw_view_switcher_bar_get_policy org.gtk.Property.set=adw_view_switcher_bar_set_policy)
185 *
186 * The policy to determine which mode to use.
187 *
188 * Since: 1.0
189 */
190 props[PROP_POLICY] =
191 g_param_spec_enum ("policy",
192 "Policy",
193 "The policy to determine the mode to use",
194 ADW_TYPE_VIEW_SWITCHER_POLICY,
195 ADW_VIEW_SWITCHER_POLICY_NARROW,
196 G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
197
198 /**
199 * AdwViewSwitcherBar:stack: (attributes org.gtk.Property.get=adw_view_switcher_bar_get_stack org.gtk.Property.set=adw_view_switcher_bar_set_stack)
200 *
201 * The stack the view switcher controls.
202 *
203 * Since: 1.0
204 */
205 props[PROP_STACK] =
206 g_param_spec_object ("stack",
207 "Stack",
208 "The stack the view switcher controls",
209 ADW_TYPE_VIEW_STACK,
210 G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
211
212 /**
213 * AdwViewSwitcherBar:reveal: (attributes org.gtk.Property.get=adw_view_switcher_bar_get_reveal org.gtk.Property.set=adw_view_switcher_bar_set_reveal)
214 *
215 * Whether the bar should be revealed or hidden.
216 *
217 * Since: 1.0
218 */
219 props[PROP_REVEAL] =
220 g_param_spec_boolean ("reveal",
221 "Reveal",
222 "Whether the bar should be revealed or hidden",
223 FALSE,
224 G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
225
226 g_object_class_install_properties (object_class, LAST_PROP, props);
227
228 gtk_widget_class_set_css_name (widget_class, "viewswitcherbar");
229 gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
230
231 gtk_widget_class_set_template_from_resource (widget_class,
232 "/org/gnome/Adwaita/ui/adw-view-switcher-bar.ui");
233 gtk_widget_class_bind_template_child (widget_class, AdwViewSwitcherBar, action_bar);
234 gtk_widget_class_bind_template_child (widget_class, AdwViewSwitcherBar, view_switcher);
235 }
236
237 static void
adw_view_switcher_bar_init(AdwViewSwitcherBar * self)238 adw_view_switcher_bar_init (AdwViewSwitcherBar *self)
239 {
240 /* This must be initialized before the template so the embedded view switcher
241 * can pick up the correct default value.
242 */
243 self->policy = ADW_VIEW_SWITCHER_POLICY_NARROW;
244
245 gtk_widget_init_template (GTK_WIDGET (self));
246
247 self->revealer = GTK_REVEALER (gtk_widget_get_first_child (GTK_WIDGET (self->action_bar)));
248 update_bar_revealed (self);
249 gtk_revealer_set_transition_type (self->revealer, GTK_REVEALER_TRANSITION_TYPE_SLIDE_UP);
250 }
251
252 /**
253 * adw_view_switcher_bar_new:
254 *
255 * Creates a new `AdwViewSwitcherBar`.
256 *
257 * Returns: the newly created `AdwViewSwitcherBar`
258 *
259 * Since: 1.0
260 */
261 GtkWidget *
adw_view_switcher_bar_new(void)262 adw_view_switcher_bar_new (void)
263 {
264 return g_object_new (ADW_TYPE_VIEW_SWITCHER_BAR, NULL);
265 }
266
267 /**
268 * adw_view_switcher_bar_get_policy: (attributes org.gtk.Method.get_property=policy)
269 * @self: a `AdwViewSwitcherBar`
270 *
271 * Gets the policy of @self.
272 *
273 * Returns: the policy of @self
274 *
275 * Since: 1.0
276 */
277 AdwViewSwitcherPolicy
adw_view_switcher_bar_get_policy(AdwViewSwitcherBar * self)278 adw_view_switcher_bar_get_policy (AdwViewSwitcherBar *self)
279 {
280 g_return_val_if_fail (ADW_IS_VIEW_SWITCHER_BAR (self), ADW_VIEW_SWITCHER_POLICY_NARROW);
281
282 return self->policy;
283 }
284
285 /**
286 * adw_view_switcher_bar_set_policy: (attributes org.gtk.Method.set_property=policy)
287 * @self: a `AdwViewSwitcherBar`
288 * @policy: the new policy
289 *
290 * Sets the policy of @self.
291 *
292 * Since: 1.0
293 */
294 void
adw_view_switcher_bar_set_policy(AdwViewSwitcherBar * self,AdwViewSwitcherPolicy policy)295 adw_view_switcher_bar_set_policy (AdwViewSwitcherBar *self,
296 AdwViewSwitcherPolicy policy)
297 {
298 g_return_if_fail (ADW_IS_VIEW_SWITCHER_BAR (self));
299
300 if (self->policy == policy)
301 return;
302
303 self->policy = policy;
304
305 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_POLICY]);
306
307 gtk_widget_queue_resize (GTK_WIDGET (self));
308 }
309
310 /**
311 * adw_view_switcher_bar_get_stack: (attributes org.gtk.Method.get_property=stack)
312 * @self: a `AdwViewSwitcherBar`
313 *
314 * Gets the stack controlled by @self.
315 *
316 * Returns: (nullable) (transfer none): the stack
317 *
318 * Since: 1.0
319 */
320 AdwViewStack *
adw_view_switcher_bar_get_stack(AdwViewSwitcherBar * self)321 adw_view_switcher_bar_get_stack (AdwViewSwitcherBar *self)
322 {
323 g_return_val_if_fail (ADW_IS_VIEW_SWITCHER_BAR (self), NULL);
324
325 return adw_view_switcher_get_stack (self->view_switcher);
326 }
327
328 /**
329 * adw_view_switcher_bar_set_stack: (attributes org.gtk.Method.set_property=stack)
330 * @self: a `AdwViewSwitcherBar`
331 * @stack: (nullable): a stack
332 *
333 * Sets the stack controlled by @self.
334 *
335 * Since: 1.0
336 */
337 void
adw_view_switcher_bar_set_stack(AdwViewSwitcherBar * self,AdwViewStack * stack)338 adw_view_switcher_bar_set_stack (AdwViewSwitcherBar *self,
339 AdwViewStack *stack)
340 {
341 AdwViewStack *previous_stack;
342
343 g_return_if_fail (ADW_IS_VIEW_SWITCHER_BAR (self));
344 g_return_if_fail (stack == NULL || ADW_IS_VIEW_STACK (stack));
345
346 previous_stack = adw_view_switcher_get_stack (self->view_switcher);
347
348 if (previous_stack == stack)
349 return;
350
351 if (previous_stack) {
352 g_signal_handlers_disconnect_by_func (self->pages, G_CALLBACK (update_bar_revealed), self);
353 g_clear_object (&self->pages);
354 }
355
356 adw_view_switcher_set_stack (self->view_switcher, stack);
357
358 if (stack) {
359 self->pages = adw_view_stack_get_pages (stack);
360
361 g_signal_connect_swapped (self->pages, "items-changed", G_CALLBACK (update_bar_revealed), self);
362 }
363
364 update_bar_revealed (self);
365
366 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_STACK]);
367 }
368
369 /**
370 * adw_view_switcher_bar_get_reveal: (attributes org.gtk.Method.get_property=reveal)
371 * @self: a `AdwViewSwitcherBar`
372 *
373 * Gets whether @self should be revealed or hidden.
374 *
375 * Returns: whether @self is revealed
376 *
377 * Since: 1.0
378 */
379 gboolean
adw_view_switcher_bar_get_reveal(AdwViewSwitcherBar * self)380 adw_view_switcher_bar_get_reveal (AdwViewSwitcherBar *self)
381 {
382 g_return_val_if_fail (ADW_IS_VIEW_SWITCHER_BAR (self), FALSE);
383
384 return self->reveal;
385 }
386
387 /**
388 * adw_view_switcher_bar_set_reveal: (attributes org.gtk.Method.set_property=reveal)
389 * @self: a `AdwViewSwitcherBar`
390 * @reveal: whether to reveal @self
391 *
392 * Sets whether @self should be revealed or hidden.
393 *
394 * Since: 1.0
395 */
396 void
adw_view_switcher_bar_set_reveal(AdwViewSwitcherBar * self,gboolean reveal)397 adw_view_switcher_bar_set_reveal (AdwViewSwitcherBar *self,
398 gboolean reveal)
399 {
400 g_return_if_fail (ADW_IS_VIEW_SWITCHER_BAR (self));
401
402 reveal = !!reveal;
403
404 if (self->reveal == reveal)
405 return;
406
407 self->reveal = reveal;
408 update_bar_revealed (self);
409
410 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_REVEAL]);
411 }
412