1 | /* GTK - The GIMP Toolkit |
2 | * |
3 | * Copyright (C) 2010 Intel Corporation |
4 | * Copyright (C) 2010 RedHat, Inc. |
5 | * |
6 | * This library is free software; you can redistribute it and/or |
7 | * modify it under the terms of the GNU Lesser General Public |
8 | * License as published by the Free Software Foundation; either |
9 | * version 2 of the License, or (at your option) any later version. |
10 | * |
11 | * This library is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 | * Lesser General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU Lesser General Public |
17 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
18 | * |
19 | * Author: |
20 | * Emmanuele Bassi <ebassi@linux.intel.com> |
21 | * Matthias Clasen <mclasen@redhat.com> |
22 | * |
23 | * Based on similar code from Mx. |
24 | */ |
25 | |
26 | /** |
27 | * GtkSwitch: |
28 | * |
29 | * `GtkSwitch` is a "light switch" that has two states: on or off. |
30 | * |
31 | * ![An example GtkSwitch](switch.png) |
32 | * |
33 | * The user can control which state should be active by clicking the |
34 | * empty area, or by dragging the handle. |
35 | * |
36 | * `GtkSwitch` can also handle situations where the underlying state |
37 | * changes with a delay. See [signal@GtkSwitch::state-set] for details. |
38 | * |
39 | * # CSS nodes |
40 | * |
41 | * ``` |
42 | * switch |
43 | * ├── label |
44 | * ├── label |
45 | * ╰── slider |
46 | * ``` |
47 | * |
48 | * `GtkSwitch` has four css nodes, the main node with the name switch and |
49 | * subnodes for the slider and the on and off labels. Neither of them is |
50 | * using any style classes. |
51 | * |
52 | * # Accessibility |
53 | * |
54 | * `GtkSwitch` uses the %GTK_ACCESSIBLE_ROLE_SWITCH role. |
55 | */ |
56 | |
57 | #include "config.h" |
58 | |
59 | #include "gtkswitch.h" |
60 | |
61 | #include "gtkactionable.h" |
62 | #include "gtkactionhelperprivate.h" |
63 | #include "gtkgestureclick.h" |
64 | #include "gtkgesturepan.h" |
65 | #include "gtkgesturesingle.h" |
66 | #include "gtkgizmoprivate.h" |
67 | #include "gtkintl.h" |
68 | #include "gtkimage.h" |
69 | #include "gtkcustomlayout.h" |
70 | #include "gtkmarshalers.h" |
71 | #include "gtkprivate.h" |
72 | #include "gtkprogresstrackerprivate.h" |
73 | #include "gtksettingsprivate.h" |
74 | #include "gtkwidgetprivate.h" |
75 | |
76 | typedef struct _GtkSwitchClass GtkSwitchClass; |
77 | |
78 | struct _GtkSwitch |
79 | { |
80 | GtkWidget parent_instance; |
81 | |
82 | GtkActionHelper *action_helper; |
83 | |
84 | GtkGesture *pan_gesture; |
85 | GtkGesture *click_gesture; |
86 | |
87 | double handle_pos; |
88 | guint tick_id; |
89 | |
90 | guint state : 1; |
91 | guint is_active : 1; |
92 | |
93 | GtkProgressTracker tracker; |
94 | |
95 | GtkWidget *on_image; |
96 | GtkWidget *off_image; |
97 | GtkWidget *slider; |
98 | }; |
99 | |
100 | struct _GtkSwitchClass |
101 | { |
102 | GtkWidgetClass parent_class; |
103 | |
104 | void (* activate) (GtkSwitch *self); |
105 | |
106 | gboolean (* state_set) (GtkSwitch *self, |
107 | gboolean state); |
108 | }; |
109 | |
110 | enum |
111 | { |
112 | PROP_0, |
113 | PROP_ACTIVE, |
114 | PROP_STATE, |
115 | LAST_PROP, |
116 | PROP_ACTION_NAME, |
117 | PROP_ACTION_TARGET |
118 | }; |
119 | |
120 | enum |
121 | { |
122 | ACTIVATE, |
123 | STATE_SET, |
124 | LAST_SIGNAL |
125 | }; |
126 | |
127 | static guint signals[LAST_SIGNAL] = { 0 }; |
128 | |
129 | static GParamSpec *switch_props[LAST_PROP] = { NULL, }; |
130 | |
131 | static void gtk_switch_actionable_iface_init (GtkActionableInterface *iface); |
132 | |
133 | G_DEFINE_TYPE_WITH_CODE (GtkSwitch, gtk_switch, GTK_TYPE_WIDGET, |
134 | G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTIONABLE, |
135 | gtk_switch_actionable_iface_init)) |
136 | |
137 | static void |
138 | gtk_switch_end_toggle_animation (GtkSwitch *self) |
139 | { |
140 | if (self->tick_id != 0) |
141 | { |
142 | gtk_widget_remove_tick_callback (GTK_WIDGET (self), id: self->tick_id); |
143 | self->tick_id = 0; |
144 | } |
145 | } |
146 | |
147 | static gboolean |
148 | gtk_switch_on_frame_clock_update (GtkWidget *widget, |
149 | GdkFrameClock *clock, |
150 | gpointer user_data) |
151 | { |
152 | GtkSwitch *self = GTK_SWITCH (widget); |
153 | |
154 | gtk_progress_tracker_advance_frame (tracker: &self->tracker, |
155 | frame_time: gdk_frame_clock_get_frame_time (frame_clock: clock)); |
156 | |
157 | if (gtk_progress_tracker_get_state (tracker: &self->tracker) != GTK_PROGRESS_STATE_AFTER) |
158 | { |
159 | if (self->is_active) |
160 | self->handle_pos = 1.0 - gtk_progress_tracker_get_ease_out_cubic (tracker: &self->tracker, FALSE); |
161 | else |
162 | self->handle_pos = gtk_progress_tracker_get_ease_out_cubic (tracker: &self->tracker, FALSE); |
163 | } |
164 | else |
165 | { |
166 | gtk_switch_set_active (self, is_active: !self->is_active); |
167 | } |
168 | |
169 | gtk_widget_queue_allocate (GTK_WIDGET (self)); |
170 | |
171 | return G_SOURCE_CONTINUE; |
172 | } |
173 | |
174 | #define ANIMATION_DURATION 100 |
175 | |
176 | static void |
177 | gtk_switch_begin_toggle_animation (GtkSwitch *self) |
178 | { |
179 | if (gtk_settings_get_enable_animations (settings: gtk_widget_get_settings (GTK_WIDGET (self)))) |
180 | { |
181 | gtk_progress_tracker_start (tracker: &self->tracker, duration: 1000 * ANIMATION_DURATION, delay: 0, iteration_count: 1.0); |
182 | if (self->tick_id == 0) |
183 | self->tick_id = gtk_widget_add_tick_callback (GTK_WIDGET (self), |
184 | callback: gtk_switch_on_frame_clock_update, |
185 | NULL, NULL); |
186 | } |
187 | else |
188 | { |
189 | gtk_switch_set_active (self, is_active: !self->is_active); |
190 | } |
191 | } |
192 | |
193 | static void |
194 | gtk_switch_click_gesture_pressed (GtkGestureClick *gesture, |
195 | int n_press, |
196 | double x, |
197 | double y, |
198 | GtkSwitch *self) |
199 | { |
200 | graphene_rect_t switch_bounds; |
201 | |
202 | if (!gtk_widget_compute_bounds (GTK_WIDGET (self), GTK_WIDGET (self), out_bounds: &switch_bounds)) |
203 | return; |
204 | |
205 | /* If the press didn't happen in the draggable handle, |
206 | * cancel the pan gesture right away |
207 | */ |
208 | if ((self->is_active && x <= switch_bounds.size.width / 2.0) || |
209 | (!self->is_active && x > switch_bounds.size.width / 2.0)) |
210 | gtk_gesture_set_state (gesture: self->pan_gesture, state: GTK_EVENT_SEQUENCE_DENIED); |
211 | } |
212 | |
213 | static void |
214 | gtk_switch_click_gesture_released (GtkGestureClick *gesture, |
215 | int n_press, |
216 | double x, |
217 | double y, |
218 | GtkSwitch *self) |
219 | { |
220 | GdkEventSequence *sequence; |
221 | |
222 | sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture)); |
223 | |
224 | if (gtk_widget_contains (GTK_WIDGET (self), x, y) && |
225 | gtk_gesture_handles_sequence (GTK_GESTURE (gesture), sequence)) |
226 | { |
227 | gtk_gesture_set_state (GTK_GESTURE (gesture), state: GTK_EVENT_SEQUENCE_CLAIMED); |
228 | gtk_switch_begin_toggle_animation (self); |
229 | } |
230 | } |
231 | |
232 | static void |
233 | gtk_switch_pan_gesture_pan (GtkGesturePan *gesture, |
234 | GtkPanDirection direction, |
235 | double offset, |
236 | GtkSwitch *self) |
237 | { |
238 | GtkWidget *widget = GTK_WIDGET (self); |
239 | int width; |
240 | |
241 | width = gtk_widget_get_width (widget); |
242 | |
243 | if (direction == GTK_PAN_DIRECTION_LEFT) |
244 | offset = -offset; |
245 | |
246 | gtk_gesture_set_state (GTK_GESTURE (gesture), state: GTK_EVENT_SEQUENCE_CLAIMED); |
247 | |
248 | if (self->is_active) |
249 | offset += width / 2; |
250 | |
251 | offset /= width / 2; |
252 | /* constrain the handle within the trough width */ |
253 | self->handle_pos = CLAMP (offset, 0, 1.0); |
254 | |
255 | /* we need to redraw the handle */ |
256 | gtk_widget_queue_allocate (widget); |
257 | } |
258 | |
259 | static void |
260 | gtk_switch_pan_gesture_drag_end (GtkGestureDrag *gesture, |
261 | double x, |
262 | double y, |
263 | GtkSwitch *self) |
264 | { |
265 | GdkEventSequence *sequence; |
266 | gboolean active; |
267 | |
268 | sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture)); |
269 | |
270 | if (gtk_gesture_get_sequence_state (GTK_GESTURE (gesture), sequence) == GTK_EVENT_SEQUENCE_CLAIMED) |
271 | { |
272 | /* if half the handle passed the middle of the switch, then we |
273 | * consider it to be on |
274 | */ |
275 | active = self->handle_pos >= 0.5; |
276 | } |
277 | else if (!gtk_gesture_handles_sequence (gesture: self->click_gesture, sequence)) |
278 | active = self->is_active; |
279 | else |
280 | return; |
281 | |
282 | self->handle_pos = active ? 1.0 : 0.0; |
283 | gtk_switch_set_active (self, is_active: active); |
284 | gtk_widget_queue_allocate (GTK_WIDGET (self)); |
285 | } |
286 | |
287 | static void |
288 | gtk_switch_activate (GtkSwitch *self) |
289 | { |
290 | gtk_switch_begin_toggle_animation (self); |
291 | } |
292 | |
293 | static void |
294 | gtk_switch_measure (GtkWidget *widget, |
295 | GtkOrientation orientation, |
296 | int for_size, |
297 | int *minimum, |
298 | int *natural, |
299 | int *minimum_baseline, |
300 | int *natural_baseline) |
301 | { |
302 | GtkSwitch *self = GTK_SWITCH (widget); |
303 | int slider_minimum, slider_natural; |
304 | int on_nat, off_nat; |
305 | |
306 | gtk_widget_measure (widget: self->slider, orientation, for_size: -1, |
307 | minimum: &slider_minimum, natural: &slider_natural, |
308 | NULL, NULL); |
309 | |
310 | gtk_widget_measure (widget: self->on_image, orientation, for_size, NULL, natural: &on_nat, NULL, NULL); |
311 | gtk_widget_measure (widget: self->off_image, orientation, for_size, NULL, natural: &off_nat, NULL, NULL); |
312 | |
313 | if (orientation == GTK_ORIENTATION_HORIZONTAL) |
314 | { |
315 | int text_width = MAX (on_nat, off_nat); |
316 | *minimum = 2 * MAX (slider_minimum, text_width); |
317 | *natural = 2 * MAX (slider_natural, text_width); |
318 | } |
319 | else |
320 | { |
321 | int text_height = MAX (on_nat, off_nat); |
322 | *minimum = MAX (slider_minimum, text_height); |
323 | *natural = MAX (slider_natural, text_height); |
324 | } |
325 | } |
326 | |
327 | static void |
328 | gtk_switch_allocate (GtkWidget *widget, |
329 | int width, |
330 | int height, |
331 | int baseline) |
332 | { |
333 | GtkSwitch *self = GTK_SWITCH (widget); |
334 | GtkAllocation child_alloc; |
335 | int min; |
336 | |
337 | gtk_widget_size_allocate (widget: self->slider, |
338 | allocation: &(GtkAllocation) { |
339 | round (x: self->handle_pos * (width / 2)), 0, |
340 | width / 2, height |
341 | }, baseline: -1); |
342 | |
343 | /* Center ON icon in left half */ |
344 | gtk_widget_measure (widget: self->on_image, orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1, minimum: &min, NULL, NULL, NULL); |
345 | child_alloc.x = ((width / 2) - min) / 2; |
346 | child_alloc.width = min; |
347 | gtk_widget_measure (widget: self->on_image, orientation: GTK_ORIENTATION_VERTICAL, for_size: min, minimum: &min, NULL, NULL, NULL); |
348 | child_alloc.y = (height - min) / 2; |
349 | child_alloc.height = min; |
350 | gtk_widget_size_allocate (widget: self->on_image, allocation: &child_alloc, baseline: -1); |
351 | |
352 | /* Center OFF icon in right half */ |
353 | gtk_widget_measure (widget: self->off_image, orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1, minimum: &min, NULL, NULL, NULL); |
354 | child_alloc.x = (width / 2) + ((width / 2) - min) / 2; |
355 | child_alloc.width = min; |
356 | gtk_widget_measure (widget: self->off_image, orientation: GTK_ORIENTATION_VERTICAL, for_size: min, minimum: &min, NULL, NULL, NULL); |
357 | child_alloc.y = (height - min) / 2; |
358 | child_alloc.height = min; |
359 | gtk_widget_size_allocate (widget: self->off_image, allocation: &child_alloc, baseline: -1); |
360 | } |
361 | |
362 | static void |
363 | gtk_switch_set_action_name (GtkActionable *actionable, |
364 | const char *action_name) |
365 | { |
366 | GtkSwitch *self = GTK_SWITCH (actionable); |
367 | |
368 | if (!self->action_helper) |
369 | self->action_helper = gtk_action_helper_new (widget: actionable); |
370 | |
371 | gtk_action_helper_set_action_name (helper: self->action_helper, action_name); |
372 | } |
373 | |
374 | static void |
375 | gtk_switch_set_action_target_value (GtkActionable *actionable, |
376 | GVariant *action_target) |
377 | { |
378 | GtkSwitch *self = GTK_SWITCH (actionable); |
379 | |
380 | if (!self->action_helper) |
381 | self->action_helper = gtk_action_helper_new (widget: actionable); |
382 | |
383 | gtk_action_helper_set_action_target_value (helper: self->action_helper, action_target); |
384 | } |
385 | |
386 | static const char * |
387 | gtk_switch_get_action_name (GtkActionable *actionable) |
388 | { |
389 | GtkSwitch *self = GTK_SWITCH (actionable); |
390 | |
391 | return gtk_action_helper_get_action_name (helper: self->action_helper); |
392 | } |
393 | |
394 | static GVariant * |
395 | gtk_switch_get_action_target_value (GtkActionable *actionable) |
396 | { |
397 | GtkSwitch *self = GTK_SWITCH (actionable); |
398 | |
399 | return gtk_action_helper_get_action_target_value (helper: self->action_helper); |
400 | } |
401 | |
402 | static void |
403 | gtk_switch_actionable_iface_init (GtkActionableInterface *iface) |
404 | { |
405 | iface->get_action_name = gtk_switch_get_action_name; |
406 | iface->set_action_name = gtk_switch_set_action_name; |
407 | iface->get_action_target_value = gtk_switch_get_action_target_value; |
408 | iface->set_action_target_value = gtk_switch_set_action_target_value; |
409 | } |
410 | |
411 | static void |
412 | gtk_switch_set_property (GObject *gobject, |
413 | guint prop_id, |
414 | const GValue *value, |
415 | GParamSpec *pspec) |
416 | { |
417 | GtkSwitch *self = GTK_SWITCH (gobject); |
418 | |
419 | switch (prop_id) |
420 | { |
421 | case PROP_ACTIVE: |
422 | gtk_switch_set_active (self, is_active: g_value_get_boolean (value)); |
423 | break; |
424 | |
425 | case PROP_STATE: |
426 | gtk_switch_set_state (self, state: g_value_get_boolean (value)); |
427 | break; |
428 | |
429 | case PROP_ACTION_NAME: |
430 | gtk_switch_set_action_name (GTK_ACTIONABLE (self), action_name: g_value_get_string (value)); |
431 | break; |
432 | |
433 | case PROP_ACTION_TARGET: |
434 | gtk_switch_set_action_target_value (GTK_ACTIONABLE (self), action_target: g_value_get_variant (value)); |
435 | break; |
436 | |
437 | default: |
438 | G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); |
439 | } |
440 | } |
441 | |
442 | static void |
443 | gtk_switch_get_property (GObject *gobject, |
444 | guint prop_id, |
445 | GValue *value, |
446 | GParamSpec *pspec) |
447 | { |
448 | GtkSwitch *self = GTK_SWITCH (gobject); |
449 | |
450 | switch (prop_id) |
451 | { |
452 | case PROP_ACTIVE: |
453 | g_value_set_boolean (value, v_boolean: self->is_active); |
454 | break; |
455 | |
456 | case PROP_STATE: |
457 | g_value_set_boolean (value, v_boolean: self->state); |
458 | break; |
459 | |
460 | case PROP_ACTION_NAME: |
461 | g_value_set_string (value, v_string: gtk_action_helper_get_action_name (helper: self->action_helper)); |
462 | break; |
463 | |
464 | case PROP_ACTION_TARGET: |
465 | g_value_set_variant (value, variant: gtk_action_helper_get_action_target_value (helper: self->action_helper)); |
466 | break; |
467 | |
468 | default: |
469 | G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); |
470 | } |
471 | } |
472 | |
473 | static void |
474 | gtk_switch_dispose (GObject *object) |
475 | { |
476 | GtkSwitch *self = GTK_SWITCH (object); |
477 | |
478 | g_clear_object (&self->action_helper); |
479 | |
480 | G_OBJECT_CLASS (gtk_switch_parent_class)->dispose (object); |
481 | } |
482 | |
483 | static void |
484 | gtk_switch_finalize (GObject *object) |
485 | { |
486 | GtkSwitch *self = GTK_SWITCH (object); |
487 | |
488 | gtk_switch_end_toggle_animation (self); |
489 | |
490 | gtk_widget_unparent (widget: self->on_image); |
491 | gtk_widget_unparent (widget: self->off_image); |
492 | gtk_widget_unparent (widget: self->slider); |
493 | |
494 | G_OBJECT_CLASS (gtk_switch_parent_class)->finalize (object); |
495 | } |
496 | |
497 | static gboolean |
498 | state_set (GtkSwitch *self, |
499 | gboolean state) |
500 | { |
501 | if (self->action_helper) |
502 | gtk_action_helper_activate (helper: self->action_helper); |
503 | |
504 | gtk_switch_set_state (self, state); |
505 | |
506 | return TRUE; |
507 | } |
508 | |
509 | static void |
510 | gtk_switch_class_init (GtkSwitchClass *klass) |
511 | { |
512 | GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); |
513 | GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
514 | |
515 | /** |
516 | * GtkSwitch:active: (attributes org.gtk.Property.get=gtk_switch_get_active org.gtk.Property.set=gtk_switch_set_active) |
517 | * |
518 | * Whether the `GtkSwitch` widget is in its on or off state. |
519 | */ |
520 | switch_props[PROP_ACTIVE] = |
521 | g_param_spec_boolean (name: "active" , |
522 | P_("Active" ), |
523 | P_("Whether the switch is on or off" ), |
524 | FALSE, |
525 | GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); |
526 | |
527 | /** |
528 | * GtkSwitch:state: (attributes org.gtk.Property.get=gtk_switch_get_state org.gtk.Property.set=gtk_switch_set_state) |
529 | * |
530 | * The backend state that is controlled by the switch. |
531 | * |
532 | * See [signal@GtkSwitch::state-set] for details. |
533 | */ |
534 | switch_props[PROP_STATE] = |
535 | g_param_spec_boolean (name: "state" , |
536 | P_("State" ), |
537 | P_("The backend state" ), |
538 | FALSE, |
539 | GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); |
540 | |
541 | gobject_class->set_property = gtk_switch_set_property; |
542 | gobject_class->get_property = gtk_switch_get_property; |
543 | gobject_class->dispose = gtk_switch_dispose; |
544 | gobject_class->finalize = gtk_switch_finalize; |
545 | |
546 | g_object_class_install_properties (oclass: gobject_class, n_pspecs: LAST_PROP, pspecs: switch_props); |
547 | |
548 | klass->activate = gtk_switch_activate; |
549 | klass->state_set = state_set; |
550 | |
551 | /** |
552 | * GtkSwitch::activate: |
553 | * @widget: the object which received the signal |
554 | * |
555 | * Emitted to animate the switch. |
556 | * |
557 | * Applications should never connect to this signal, |
558 | * but use the [property@Gtk.Switch:active] property. |
559 | */ |
560 | signals[ACTIVATE] = |
561 | g_signal_new (I_("activate" ), |
562 | G_OBJECT_CLASS_TYPE (gobject_class), |
563 | signal_flags: G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, |
564 | G_STRUCT_OFFSET (GtkSwitchClass, activate), |
565 | NULL, NULL, |
566 | NULL, |
567 | G_TYPE_NONE, n_params: 0); |
568 | |
569 | gtk_widget_class_set_activate_signal (widget_class, signal_id: signals[ACTIVATE]); |
570 | |
571 | /** |
572 | * GtkSwitch::state-set: |
573 | * @widget: the object on which the signal was emitted |
574 | * @state: the new state of the switch |
575 | * |
576 | * Emitted to change the underlying state. |
577 | * |
578 | * The ::state-set signal is emitted when the user changes the switch |
579 | * position. The default handler keeps the state in sync with the |
580 | * [property@Gtk.Switch:active] property. |
581 | * |
582 | * To implement delayed state change, applications can connect to this |
583 | * signal, initiate the change of the underlying state, and call |
584 | * [method@Gtk.Switch.set_state] when the underlying state change is |
585 | * complete. The signal handler should return %TRUE to prevent the |
586 | * default handler from running. |
587 | * |
588 | * Visually, the underlying state is represented by the trough color of |
589 | * the switch, while the [property@Gtk.Switch:active] property is |
590 | * represented by the position of the switch. |
591 | * |
592 | * Returns: %TRUE to stop the signal emission |
593 | */ |
594 | signals[STATE_SET] = |
595 | g_signal_new (I_("state-set" ), |
596 | G_OBJECT_CLASS_TYPE (gobject_class), |
597 | signal_flags: G_SIGNAL_RUN_LAST, |
598 | G_STRUCT_OFFSET (GtkSwitchClass, state_set), |
599 | accumulator: _gtk_boolean_handled_accumulator, NULL, |
600 | c_marshaller: _gtk_marshal_BOOLEAN__BOOLEAN, |
601 | G_TYPE_BOOLEAN, n_params: 1, |
602 | G_TYPE_BOOLEAN); |
603 | g_signal_set_va_marshaller (signal_id: signals[STATE_SET], |
604 | G_TYPE_FROM_CLASS (gobject_class), |
605 | va_marshaller: _gtk_marshal_BOOLEAN__BOOLEANv); |
606 | |
607 | g_object_class_override_property (oclass: gobject_class, property_id: PROP_ACTION_NAME, name: "action-name" ); |
608 | g_object_class_override_property (oclass: gobject_class, property_id: PROP_ACTION_TARGET, name: "action-target" ); |
609 | |
610 | gtk_widget_class_set_css_name (widget_class, I_("switch" )); |
611 | |
612 | gtk_widget_class_set_accessible_role (widget_class, accessible_role: GTK_ACCESSIBLE_ROLE_SWITCH); |
613 | } |
614 | |
615 | static void |
616 | gtk_switch_init (GtkSwitch *self) |
617 | { |
618 | GtkLayoutManager *layout; |
619 | GtkGesture *gesture; |
620 | |
621 | gtk_widget_set_focusable (GTK_WIDGET (self), TRUE); |
622 | |
623 | gesture = gtk_gesture_click_new (); |
624 | gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture), FALSE); |
625 | gtk_gesture_single_set_exclusive (GTK_GESTURE_SINGLE (gesture), TRUE); |
626 | g_signal_connect (gesture, "pressed" , |
627 | G_CALLBACK (gtk_switch_click_gesture_pressed), self); |
628 | g_signal_connect (gesture, "released" , |
629 | G_CALLBACK (gtk_switch_click_gesture_released), self); |
630 | gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture), |
631 | phase: GTK_PHASE_BUBBLE); |
632 | gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture)); |
633 | self->click_gesture = gesture; |
634 | |
635 | gesture = gtk_gesture_pan_new (orientation: GTK_ORIENTATION_HORIZONTAL); |
636 | gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture), FALSE); |
637 | gtk_gesture_single_set_exclusive (GTK_GESTURE_SINGLE (gesture), TRUE); |
638 | g_signal_connect (gesture, "pan" , |
639 | G_CALLBACK (gtk_switch_pan_gesture_pan), self); |
640 | g_signal_connect (gesture, "drag-end" , |
641 | G_CALLBACK (gtk_switch_pan_gesture_drag_end), self); |
642 | gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture), |
643 | phase: GTK_PHASE_CAPTURE); |
644 | gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture)); |
645 | self->pan_gesture = gesture; |
646 | |
647 | layout = gtk_custom_layout_new (NULL, |
648 | measure: gtk_switch_measure, |
649 | allocate: gtk_switch_allocate); |
650 | gtk_widget_set_layout_manager (GTK_WIDGET (self), layout_manager: layout); |
651 | |
652 | self->on_image = g_object_new (GTK_TYPE_IMAGE, |
653 | first_property_name: "accessible-role" , GTK_ACCESSIBLE_ROLE_NONE, |
654 | "icon-name" , "switch-on-symbolic" , |
655 | NULL); |
656 | gtk_widget_set_parent (widget: self->on_image, GTK_WIDGET (self)); |
657 | |
658 | self->off_image = g_object_new (GTK_TYPE_IMAGE, |
659 | first_property_name: "accessible-role" , GTK_ACCESSIBLE_ROLE_NONE, |
660 | "icon-name" , "switch-off-symbolic" , |
661 | NULL); |
662 | gtk_widget_set_parent (widget: self->off_image, GTK_WIDGET (self)); |
663 | |
664 | self->slider = gtk_gizmo_new_with_role (css_name: "slider" , |
665 | role: GTK_ACCESSIBLE_ROLE_NONE, |
666 | NULL, NULL, NULL, NULL, NULL, NULL); |
667 | gtk_widget_set_parent (widget: self->slider, GTK_WIDGET (self)); |
668 | |
669 | gtk_accessible_update_state (self: GTK_ACCESSIBLE (ptr: self), |
670 | first_state: GTK_ACCESSIBLE_STATE_CHECKED, FALSE, |
671 | -1); |
672 | } |
673 | |
674 | /** |
675 | * gtk_switch_new: |
676 | * |
677 | * Creates a new `GtkSwitch` widget. |
678 | * |
679 | * Returns: the newly created `GtkSwitch` instance |
680 | */ |
681 | GtkWidget * |
682 | gtk_switch_new (void) |
683 | { |
684 | return g_object_new (GTK_TYPE_SWITCH, NULL); |
685 | } |
686 | |
687 | /** |
688 | * gtk_switch_set_active: (attributes org.gtk.Method.set_property=active) |
689 | * @self: a `GtkSwitch` |
690 | * @is_active: %TRUE if @self should be active, and %FALSE otherwise |
691 | * |
692 | * Changes the state of @self to the desired one. |
693 | */ |
694 | void |
695 | gtk_switch_set_active (GtkSwitch *self, |
696 | gboolean is_active) |
697 | { |
698 | g_return_if_fail (GTK_IS_SWITCH (self)); |
699 | |
700 | gtk_switch_end_toggle_animation (self); |
701 | |
702 | is_active = !!is_active; |
703 | |
704 | if (self->is_active != is_active) |
705 | { |
706 | gboolean handled; |
707 | |
708 | self->is_active = is_active; |
709 | |
710 | if (self->is_active) |
711 | self->handle_pos = 1.0; |
712 | else |
713 | self->handle_pos = 0.0; |
714 | |
715 | g_signal_emit (instance: self, signal_id: signals[STATE_SET], detail: 0, is_active, &handled); |
716 | |
717 | g_object_notify_by_pspec (G_OBJECT (self), pspec: switch_props[PROP_ACTIVE]); |
718 | |
719 | gtk_accessible_update_state (self: GTK_ACCESSIBLE (ptr: self), |
720 | first_state: GTK_ACCESSIBLE_STATE_CHECKED, is_active, |
721 | -1); |
722 | |
723 | gtk_widget_queue_allocate (GTK_WIDGET (self)); |
724 | } |
725 | } |
726 | |
727 | /** |
728 | * gtk_switch_get_active: (attributes org.gtk.Method.get_property=active) |
729 | * @self: a `GtkSwitch` |
730 | * |
731 | * Gets whether the `GtkSwitch` is in its “on” or “off” state. |
732 | * |
733 | * Returns: %TRUE if the `GtkSwitch` is active, and %FALSE otherwise |
734 | */ |
735 | gboolean |
736 | gtk_switch_get_active (GtkSwitch *self) |
737 | { |
738 | g_return_val_if_fail (GTK_IS_SWITCH (self), FALSE); |
739 | |
740 | return self->is_active; |
741 | } |
742 | |
743 | /** |
744 | * gtk_switch_set_state: (attributes org.gtk.Method.set_property=state) |
745 | * @self: a `GtkSwitch` |
746 | * @state: the new state |
747 | * |
748 | * Sets the underlying state of the `GtkSwitch`. |
749 | * |
750 | * Normally, this is the same as [property@Gtk.Switch:active], unless |
751 | * the switch is set up for delayed state changes. This function is |
752 | * typically called from a [signal@Gtk.Switch::state-set] signal handler. |
753 | * |
754 | * See [signal@Gtk.Switch::state-set] for details. |
755 | */ |
756 | void |
757 | gtk_switch_set_state (GtkSwitch *self, |
758 | gboolean state) |
759 | { |
760 | g_return_if_fail (GTK_IS_SWITCH (self)); |
761 | |
762 | state = state != FALSE; |
763 | |
764 | if (self->state == state) |
765 | return; |
766 | |
767 | self->state = state; |
768 | |
769 | /* This will be a no-op if we're switching the state in response |
770 | * to a UI change. We're setting active anyway, to catch 'spontaneous' |
771 | * state changes. |
772 | */ |
773 | gtk_switch_set_active (self, is_active: state); |
774 | |
775 | if (state) |
776 | gtk_widget_set_state_flags (GTK_WIDGET (self), flags: GTK_STATE_FLAG_CHECKED, FALSE); |
777 | else |
778 | gtk_widget_unset_state_flags (GTK_WIDGET (self), flags: GTK_STATE_FLAG_CHECKED); |
779 | |
780 | g_object_notify_by_pspec (G_OBJECT (self), pspec: switch_props[PROP_STATE]); |
781 | } |
782 | |
783 | /** |
784 | * gtk_switch_get_state: (attributes org.gtk.Method.get_property=state) |
785 | * @self: a `GtkSwitch` |
786 | * |
787 | * Gets the underlying state of the `GtkSwitch`. |
788 | * |
789 | * Returns: the underlying state |
790 | */ |
791 | gboolean |
792 | gtk_switch_get_state (GtkSwitch *self) |
793 | { |
794 | g_return_val_if_fail (GTK_IS_SWITCH (self), FALSE); |
795 | |
796 | return self->state; |
797 | } |
798 | |