diff options
Diffstat (limited to '')
-rw-r--r-- | subprojects/libhandy/src/hdy-leaflet.c | 1209 |
1 files changed, 1209 insertions, 0 deletions
diff --git a/subprojects/libhandy/src/hdy-leaflet.c b/subprojects/libhandy/src/hdy-leaflet.c new file mode 100644 index 0000000..8c1ba2a --- /dev/null +++ b/subprojects/libhandy/src/hdy-leaflet.c @@ -0,0 +1,1209 @@ +/* + * Copyright (C) 2018 Purism SPC + * Copyright (C) 2019 Alexander Mikhaylenko <exalm7659@gmail.com> + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" +#include <glib/gi18n-lib.h> + +#include "hdy-leaflet.h" +#include "hdy-stackable-box-private.h" +#include "hdy-swipeable.h" + +/** + * SECTION:hdy-leaflet + * @short_description: An adaptive container acting like a box or a stack. + * @Title: HdyLeaflet + * + * The #HdyLeaflet widget can display its children like a #GtkBox does or + * like a #GtkStack does, adapting to size changes by switching between + * the two modes. + * + * When there is enough space the children are displayed side by side, otherwise + * only one is displayed and the leaflet is said to be “folded”. + * The threshold is dictated by the preferred minimum sizes of the children. + * When a leaflet is folded, the children can be navigated using swipe gestures. + * + * The “over” and “under” stack the children one on top of the other, while the + * “slide” transition puts the children side by side. While navigating to a + * child on the side or below can be performed by swiping the current child + * away, navigating to an upper child requires dragging it from the edge where + * it resides. This doesn't affect non-dragging swipes. + * + * The “over” and “under” transitions can draw their shadow on top of the + * window's transparent areas, like the rounded corners. This is a side-effect + * of allowing shadows to be drawn on top of OpenGL areas. It can be mitigated + * by using #HdyWindow or #HdyApplicationWindow as they will crop anything drawn + * beyond the rounded corners. + * + * # CSS nodes + * + * #HdyLeaflet has a single CSS node with name leaflet. The node will get the + * style classes .folded when it is folded, .unfolded when it's not, or none if + * it didn't compute its fold yet. + */ + +/** + * HdyLeafletTransitionType: + * @HDY_LEAFLET_TRANSITION_TYPE_OVER: Cover the old page or uncover the new page, sliding from or towards the end according to orientation, text direction and children order + * @HDY_LEAFLET_TRANSITION_TYPE_UNDER: Uncover the new page or cover the old page, sliding from or towards the start according to orientation, text direction and children order + * @HDY_LEAFLET_TRANSITION_TYPE_SLIDE: Slide from left, right, up or down according to the orientation, text direction and the children order + * + * This enumeration value describes the possible transitions between modes and + * children in a #HdyLeaflet widget. + * + * New values may be added to this enumeration over time. + * + * Since: 0.0.12 + */ + +enum { + PROP_0, + PROP_FOLDED, + PROP_HHOMOGENEOUS_FOLDED, + PROP_VHOMOGENEOUS_FOLDED, + PROP_HHOMOGENEOUS_UNFOLDED, + PROP_VHOMOGENEOUS_UNFOLDED, + PROP_VISIBLE_CHILD, + PROP_VISIBLE_CHILD_NAME, + PROP_TRANSITION_TYPE, + PROP_MODE_TRANSITION_DURATION, + PROP_CHILD_TRANSITION_DURATION, + PROP_CHILD_TRANSITION_RUNNING, + PROP_INTERPOLATE_SIZE, + PROP_CAN_SWIPE_BACK, + PROP_CAN_SWIPE_FORWARD, + + /* orientable */ + PROP_ORIENTATION, + LAST_PROP = PROP_ORIENTATION, +}; + +enum { + CHILD_PROP_0, + CHILD_PROP_NAME, + CHILD_PROP_NAVIGATABLE, + LAST_CHILD_PROP, +}; + +typedef struct +{ + HdyStackableBox *box; +} HdyLeafletPrivate; + +static GParamSpec *props[LAST_PROP]; +static GParamSpec *child_props[LAST_CHILD_PROP]; + +static void hdy_leaflet_swipeable_init (HdySwipeableInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (HdyLeaflet, hdy_leaflet, GTK_TYPE_CONTAINER, + G_ADD_PRIVATE (HdyLeaflet) + G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL) + G_IMPLEMENT_INTERFACE (HDY_TYPE_SWIPEABLE, hdy_leaflet_swipeable_init)) + +#define HDY_GET_HELPER(obj) (((HdyLeafletPrivate *) hdy_leaflet_get_instance_private (HDY_LEAFLET (obj)))->box) + +/** + * hdy_leaflet_get_folded: + * @self: a #HdyLeaflet + * + * Gets whether @self is folded. + * + * Returns: whether @self is folded. + */ +gboolean +hdy_leaflet_get_folded (HdyLeaflet *self) +{ + g_return_val_if_fail (HDY_IS_LEAFLET (self), FALSE); + + return hdy_stackable_box_get_folded (HDY_GET_HELPER (self)); +} + +/** + * hdy_leaflet_set_homogeneous: + * @self: a #HdyLeaflet + * @folded: the fold + * @orientation: the orientation + * @homogeneous: %TRUE to make @self homogeneous + * + * Sets the #HdyLeaflet to be homogeneous or not for the given fold and orientation. + * If it is homogeneous, the #HdyLeaflet will request the same + * width or height for all its children depending on the orientation. + * If it isn't and it is folded, the leaflet may change width or height + * when a different child becomes visible. + */ +void +hdy_leaflet_set_homogeneous (HdyLeaflet *self, + gboolean folded, + GtkOrientation orientation, + gboolean homogeneous) +{ + g_return_if_fail (HDY_IS_LEAFLET (self)); + + hdy_stackable_box_set_homogeneous (HDY_GET_HELPER (self), folded, orientation, homogeneous); +} + +/** + * hdy_leaflet_get_homogeneous: + * @self: a #HdyLeaflet + * @folded: the fold + * @orientation: the orientation + * + * Gets whether @self is homogeneous for the given fold and orientation. + * See hdy_leaflet_set_homogeneous(). + * + * Returns: whether @self is homogeneous for the given fold and orientation. + */ +gboolean +hdy_leaflet_get_homogeneous (HdyLeaflet *self, + gboolean folded, + GtkOrientation orientation) +{ + g_return_val_if_fail (HDY_IS_LEAFLET (self), FALSE); + + return hdy_stackable_box_get_homogeneous (HDY_GET_HELPER (self), folded, orientation); +} + +/** + * hdy_leaflet_get_transition_type: + * @self: a #HdyLeaflet + * + * Gets the type of animation that will be used + * for transitions between modes and children in @self. + * + * Returns: the current transition type of @self + * + * Since: 0.0.12 + */ +HdyLeafletTransitionType +hdy_leaflet_get_transition_type (HdyLeaflet *self) +{ + HdyStackableBoxTransitionType type; + + g_return_val_if_fail (HDY_IS_LEAFLET (self), HDY_LEAFLET_TRANSITION_TYPE_OVER); + + type = hdy_stackable_box_get_transition_type (HDY_GET_HELPER (self)); + + switch (type) { + case HDY_STACKABLE_BOX_TRANSITION_TYPE_OVER: + return HDY_LEAFLET_TRANSITION_TYPE_OVER; + + case HDY_STACKABLE_BOX_TRANSITION_TYPE_UNDER: + return HDY_LEAFLET_TRANSITION_TYPE_UNDER; + + case HDY_STACKABLE_BOX_TRANSITION_TYPE_SLIDE: + return HDY_LEAFLET_TRANSITION_TYPE_SLIDE; + + default: + g_assert_not_reached (); + } +} + +/** + * hdy_leaflet_set_transition_type: + * @self: a #HdyLeaflet + * @transition: the new transition type + * + * Sets the type of animation that will be used for transitions between modes + * and children in @self. + * + * The transition type can be changed without problems at runtime, so it is + * possible to change the animation based on the mode or child that is about to + * become current. + * + * Since: 0.0.12 + */ +void +hdy_leaflet_set_transition_type (HdyLeaflet *self, + HdyLeafletTransitionType transition) +{ + HdyStackableBoxTransitionType type; + + g_return_if_fail (HDY_IS_LEAFLET (self)); + g_return_if_fail (transition <= HDY_LEAFLET_TRANSITION_TYPE_SLIDE); + + switch (transition) { + case HDY_LEAFLET_TRANSITION_TYPE_OVER: + type = HDY_STACKABLE_BOX_TRANSITION_TYPE_OVER; + break; + + case HDY_LEAFLET_TRANSITION_TYPE_UNDER: + type = HDY_STACKABLE_BOX_TRANSITION_TYPE_UNDER; + break; + + case HDY_LEAFLET_TRANSITION_TYPE_SLIDE: + type = HDY_STACKABLE_BOX_TRANSITION_TYPE_SLIDE; + break; + + default: + g_assert_not_reached (); + } + + hdy_stackable_box_set_transition_type (HDY_GET_HELPER (self), type); +} + +/** + * hdy_leaflet_get_mode_transition_duration: + * @self: a #HdyLeaflet + * + * Returns the amount of time (in milliseconds) that + * transitions between modes in @self will take. + * + * Returns: the mode transition duration + */ +guint +hdy_leaflet_get_mode_transition_duration (HdyLeaflet *self) +{ + g_return_val_if_fail (HDY_IS_LEAFLET (self), 0); + + return hdy_stackable_box_get_mode_transition_duration (HDY_GET_HELPER (self)); +} + +/** + * hdy_leaflet_set_mode_transition_duration: + * @self: a #HdyLeaflet + * @duration: the new duration, in milliseconds + * + * Sets the duration that transitions between modes in @self + * will take. + */ +void +hdy_leaflet_set_mode_transition_duration (HdyLeaflet *self, + guint duration) +{ + g_return_if_fail (HDY_IS_LEAFLET (self)); + + hdy_stackable_box_set_mode_transition_duration (HDY_GET_HELPER (self), duration); +} + +/** + * hdy_leaflet_get_child_transition_duration: + * @self: a #HdyLeaflet + * + * Returns the amount of time (in milliseconds) that + * transitions between children in @self will take. + * + * Returns: the child transition duration + */ +guint +hdy_leaflet_get_child_transition_duration (HdyLeaflet *self) +{ + g_return_val_if_fail (HDY_IS_LEAFLET (self), 0); + + return hdy_stackable_box_get_child_transition_duration (HDY_GET_HELPER (self)); +} + +/** + * hdy_leaflet_set_child_transition_duration: + * @self: a #HdyLeaflet + * @duration: the new duration, in milliseconds + * + * Sets the duration that transitions between children in @self + * will take. + */ +void +hdy_leaflet_set_child_transition_duration (HdyLeaflet *self, + guint duration) +{ + g_return_if_fail (HDY_IS_LEAFLET (self)); + + hdy_stackable_box_set_child_transition_duration (HDY_GET_HELPER (self), duration); +} + +/** + * hdy_leaflet_get_visible_child: + * @self: a #HdyLeaflet + * + * Gets the visible child widget. + * + * Returns: (transfer none): the visible child widget + */ +GtkWidget * +hdy_leaflet_get_visible_child (HdyLeaflet *self) +{ + g_return_val_if_fail (HDY_IS_LEAFLET (self), NULL); + + return hdy_stackable_box_get_visible_child (HDY_GET_HELPER (self)); +} + +/** + * hdy_leaflet_set_visible_child: + * @self: a #HdyLeaflet + * @visible_child: the new child + * + * Makes @visible_child visible using a transition determined by + * HdyLeaflet:transition-type and HdyLeaflet:child-transition-duration. The + * transition can be cancelled by the user, in which case visible child will + * change back to the previously visible child. + */ +void +hdy_leaflet_set_visible_child (HdyLeaflet *self, + GtkWidget *visible_child) +{ + g_return_if_fail (HDY_IS_LEAFLET (self)); + + hdy_stackable_box_set_visible_child (HDY_GET_HELPER (self), visible_child); +} + +/** + * hdy_leaflet_get_visible_child_name: + * @self: a #HdyLeaflet + * + * Gets the name of the currently visible child widget. + * + * Returns: (transfer none): the name of the visible child + */ +const gchar * +hdy_leaflet_get_visible_child_name (HdyLeaflet *self) +{ + g_return_val_if_fail (HDY_IS_LEAFLET (self), NULL); + + return hdy_stackable_box_get_visible_child_name (HDY_GET_HELPER (self)); +} + +/** + * hdy_leaflet_set_visible_child_name: + * @self: a #HdyLeaflet + * @name: the name of a child + * + * Makes the child with the name @name visible. + * + * See hdy_leaflet_set_visible_child() for more details. + */ +void +hdy_leaflet_set_visible_child_name (HdyLeaflet *self, + const gchar *name) +{ + g_return_if_fail (HDY_IS_LEAFLET (self)); + + hdy_stackable_box_set_visible_child_name (HDY_GET_HELPER (self), name); +} + +/** + * hdy_leaflet_get_child_transition_running: + * @self: a #HdyLeaflet + * + * Returns whether @self is currently in a transition from one page to + * another. + * + * Returns: %TRUE if the transition is currently running, %FALSE otherwise. + */ +gboolean +hdy_leaflet_get_child_transition_running (HdyLeaflet *self) +{ + g_return_val_if_fail (HDY_IS_LEAFLET (self), FALSE); + + return hdy_stackable_box_get_child_transition_running (HDY_GET_HELPER (self)); +} + +/** + * hdy_leaflet_set_interpolate_size: + * @self: a #HdyLeaflet + * @interpolate_size: the new value + * + * Sets whether or not @self will interpolate its size when + * changing the visible child. If the #HdyLeaflet:interpolate-size + * property is set to %TRUE, @self will interpolate its size between + * the current one and the one it'll take after changing the + * visible child, according to the set transition duration. + */ +void +hdy_leaflet_set_interpolate_size (HdyLeaflet *self, + gboolean interpolate_size) +{ + g_return_if_fail (HDY_IS_LEAFLET (self)); + + hdy_stackable_box_set_interpolate_size (HDY_GET_HELPER (self), interpolate_size); +} + +/** + * hdy_leaflet_get_interpolate_size: + * @self: a #HdyLeaflet + * + * Returns whether the #HdyLeaflet is set up to interpolate between + * the sizes of children on page switch. + * + * Returns: %TRUE if child sizes are interpolated + */ +gboolean +hdy_leaflet_get_interpolate_size (HdyLeaflet *self) +{ + g_return_val_if_fail (HDY_IS_LEAFLET (self), FALSE); + + return hdy_stackable_box_get_interpolate_size (HDY_GET_HELPER (self)); +} + +/** + * hdy_leaflet_set_can_swipe_back: + * @self: a #HdyLeaflet + * @can_swipe_back: the new value + * + * Sets whether or not @self allows switching to the previous child that has + * 'navigatable' child property set to %TRUE via a swipe gesture + * + * Since: 0.0.12 + */ +void +hdy_leaflet_set_can_swipe_back (HdyLeaflet *self, + gboolean can_swipe_back) +{ + g_return_if_fail (HDY_IS_LEAFLET (self)); + + hdy_stackable_box_set_can_swipe_back (HDY_GET_HELPER (self), can_swipe_back); +} + +/** + * hdy_leaflet_get_can_swipe_back + * @self: a #HdyLeaflet + * + * Returns whether the #HdyLeaflet allows swiping to the previous child. + * + * Returns: %TRUE if back swipe is enabled. + * + * Since: 0.0.12 + */ +gboolean +hdy_leaflet_get_can_swipe_back (HdyLeaflet *self) +{ + g_return_val_if_fail (HDY_IS_LEAFLET (self), FALSE); + + return hdy_stackable_box_get_can_swipe_back (HDY_GET_HELPER (self)); +} + +/** + * hdy_leaflet_set_can_swipe_forward: + * @self: a #HdyLeaflet + * @can_swipe_forward: the new value + * + * Sets whether or not @self allows switching to the next child that has + * 'navigatable' child property set to %TRUE via a swipe gesture. + * + * Since: 0.0.12 + */ +void +hdy_leaflet_set_can_swipe_forward (HdyLeaflet *self, + gboolean can_swipe_forward) +{ + g_return_if_fail (HDY_IS_LEAFLET (self)); + + hdy_stackable_box_set_can_swipe_forward (HDY_GET_HELPER (self), can_swipe_forward); +} + +/** + * hdy_leaflet_get_can_swipe_forward + * @self: a #HdyLeaflet + * + * Returns whether the #HdyLeaflet allows swiping to the next child. + * + * Returns: %TRUE if forward swipe is enabled. + * + * Since: 0.0.12 + */ +gboolean +hdy_leaflet_get_can_swipe_forward (HdyLeaflet *self) +{ + g_return_val_if_fail (HDY_IS_LEAFLET (self), FALSE); + + return hdy_stackable_box_get_can_swipe_forward (HDY_GET_HELPER (self)); +} + +/** + * hdy_leaflet_get_adjacent_child + * @self: a #HdyLeaflet + * @direction: the direction + * + * Gets the previous or next child that doesn't have 'navigatable' child + * property set to %FALSE, or %NULL if it doesn't exist. This will be the same + * widget hdy_leaflet_navigate() will navigate to. + * + * Returns: (nullable) (transfer none): the previous or next child, or + * %NULL if it doesn't exist. + * + * Since: 1.0 + */ +GtkWidget * +hdy_leaflet_get_adjacent_child (HdyLeaflet *self, + HdyNavigationDirection direction) +{ + g_return_val_if_fail (HDY_IS_LEAFLET (self), NULL); + + return hdy_stackable_box_get_adjacent_child (HDY_GET_HELPER (self), direction); +} + +/** + * hdy_leaflet_navigate + * @self: a #HdyLeaflet + * @direction: the direction + * + * Switches to the previous or next child that doesn't have 'navigatable' child + * property set to %FALSE, similar to performing a swipe gesture to go in + * @direction. + * + * Returns: %TRUE if visible child was changed, %FALSE otherwise. + * + * Since: 1.0 + */ +gboolean +hdy_leaflet_navigate (HdyLeaflet *self, + HdyNavigationDirection direction) +{ + g_return_val_if_fail (HDY_IS_LEAFLET (self), FALSE); + + return hdy_stackable_box_navigate (HDY_GET_HELPER (self), direction); +} + +/** + * hdy_leaflet_get_child_by_name: + * @self: a #HdyLeaflet + * @name: the name of the child to find + * + * Finds the child of @self with the name given as the argument. Returns %NULL + * if there is no child with this name. + * + * Returns: (transfer none) (nullable): the requested child of @self + * + * Since: 1.0 + */ +GtkWidget * +hdy_leaflet_get_child_by_name (HdyLeaflet *self, + const gchar *name) +{ + g_return_val_if_fail (HDY_IS_LEAFLET (self), NULL); + + return hdy_stackable_box_get_child_by_name (HDY_GET_HELPER (self), name); +} + +/* This private method is prefixed by the call name because it will be a virtual + * method in GTK 4. + */ +static void +hdy_leaflet_measure (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + hdy_stackable_box_measure (HDY_GET_HELPER (widget), + orientation, for_size, + minimum, natural, + minimum_baseline, natural_baseline); +} + +static void +hdy_leaflet_get_preferred_width (GtkWidget *widget, + gint *minimum_width, + gint *natural_width) +{ + hdy_leaflet_measure (widget, GTK_ORIENTATION_HORIZONTAL, -1, + minimum_width, natural_width, NULL, NULL); +} + +static void +hdy_leaflet_get_preferred_height (GtkWidget *widget, + gint *minimum_height, + gint *natural_height) +{ + hdy_leaflet_measure (widget, GTK_ORIENTATION_VERTICAL, -1, + minimum_height, natural_height, NULL, NULL); +} + +static void +hdy_leaflet_get_preferred_width_for_height (GtkWidget *widget, + gint height, + gint *minimum_width, + gint *natural_width) +{ + hdy_leaflet_measure (widget, GTK_ORIENTATION_HORIZONTAL, height, + minimum_width, natural_width, NULL, NULL); +} + +static void +hdy_leaflet_get_preferred_height_for_width (GtkWidget *widget, + gint width, + gint *minimum_height, + gint *natural_height) +{ + hdy_leaflet_measure (widget, GTK_ORIENTATION_VERTICAL, width, + minimum_height, natural_height, NULL, NULL); +} + +static void +hdy_leaflet_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + hdy_stackable_box_size_allocate (HDY_GET_HELPER (widget), allocation); +} + +static gboolean +hdy_leaflet_draw (GtkWidget *widget, + cairo_t *cr) +{ + return hdy_stackable_box_draw (HDY_GET_HELPER (widget), cr); +} + +static void +hdy_leaflet_direction_changed (GtkWidget *widget, + GtkTextDirection previous_direction) +{ + hdy_stackable_box_direction_changed (HDY_GET_HELPER (widget), previous_direction); +} + +static void +hdy_leaflet_add (GtkContainer *container, + GtkWidget *widget) +{ + hdy_stackable_box_add (HDY_GET_HELPER (container), widget); +} + +static void +hdy_leaflet_remove (GtkContainer *container, + GtkWidget *widget) +{ + hdy_stackable_box_remove (HDY_GET_HELPER (container), widget); +} + +static void +hdy_leaflet_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data) +{ + hdy_stackable_box_forall (HDY_GET_HELPER (container), include_internals, callback, callback_data); +} + +static void +hdy_leaflet_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + HdyLeaflet *self = HDY_LEAFLET (object); + + switch (prop_id) { + case PROP_FOLDED: + g_value_set_boolean (value, hdy_leaflet_get_folded (self)); + break; + case PROP_HHOMOGENEOUS_FOLDED: + g_value_set_boolean (value, hdy_leaflet_get_homogeneous (self, TRUE, GTK_ORIENTATION_HORIZONTAL)); + break; + case PROP_VHOMOGENEOUS_FOLDED: + g_value_set_boolean (value, hdy_leaflet_get_homogeneous (self, TRUE, GTK_ORIENTATION_VERTICAL)); + break; + case PROP_HHOMOGENEOUS_UNFOLDED: + g_value_set_boolean (value, hdy_leaflet_get_homogeneous (self, FALSE, GTK_ORIENTATION_HORIZONTAL)); + break; + case PROP_VHOMOGENEOUS_UNFOLDED: + g_value_set_boolean (value, hdy_leaflet_get_homogeneous (self, FALSE, GTK_ORIENTATION_VERTICAL)); + break; + case PROP_VISIBLE_CHILD: + g_value_set_object (value, hdy_leaflet_get_visible_child (self)); + break; + case PROP_VISIBLE_CHILD_NAME: + g_value_set_string (value, hdy_leaflet_get_visible_child_name (self)); + break; + case PROP_TRANSITION_TYPE: + g_value_set_enum (value, hdy_leaflet_get_transition_type (self)); + break; + case PROP_MODE_TRANSITION_DURATION: + g_value_set_uint (value, hdy_leaflet_get_mode_transition_duration (self)); + break; + case PROP_CHILD_TRANSITION_DURATION: + g_value_set_uint (value, hdy_leaflet_get_child_transition_duration (self)); + break; + case PROP_CHILD_TRANSITION_RUNNING: + g_value_set_boolean (value, hdy_leaflet_get_child_transition_running (self)); + break; + case PROP_INTERPOLATE_SIZE: + g_value_set_boolean (value, hdy_leaflet_get_interpolate_size (self)); + break; + case PROP_CAN_SWIPE_BACK: + g_value_set_boolean (value, hdy_leaflet_get_can_swipe_back (self)); + break; + case PROP_CAN_SWIPE_FORWARD: + g_value_set_boolean (value, hdy_leaflet_get_can_swipe_forward (self)); + break; + case PROP_ORIENTATION: + g_value_set_enum (value, hdy_stackable_box_get_orientation (HDY_GET_HELPER (self))); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +hdy_leaflet_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + HdyLeaflet *self = HDY_LEAFLET (object); + + switch (prop_id) { + case PROP_HHOMOGENEOUS_FOLDED: + hdy_leaflet_set_homogeneous (self, TRUE, GTK_ORIENTATION_HORIZONTAL, g_value_get_boolean (value)); + break; + case PROP_VHOMOGENEOUS_FOLDED: + hdy_leaflet_set_homogeneous (self, TRUE, GTK_ORIENTATION_VERTICAL, g_value_get_boolean (value)); + break; + case PROP_HHOMOGENEOUS_UNFOLDED: + hdy_leaflet_set_homogeneous (self, FALSE, GTK_ORIENTATION_HORIZONTAL, g_value_get_boolean (value)); + break; + case PROP_VHOMOGENEOUS_UNFOLDED: + hdy_leaflet_set_homogeneous (self, FALSE, GTK_ORIENTATION_VERTICAL, g_value_get_boolean (value)); + break; + case PROP_VISIBLE_CHILD: + hdy_leaflet_set_visible_child (self, g_value_get_object (value)); + break; + case PROP_VISIBLE_CHILD_NAME: + hdy_leaflet_set_visible_child_name (self, g_value_get_string (value)); + break; + case PROP_TRANSITION_TYPE: + hdy_leaflet_set_transition_type (self, g_value_get_enum (value)); + break; + case PROP_MODE_TRANSITION_DURATION: + hdy_leaflet_set_mode_transition_duration (self, g_value_get_uint (value)); + break; + case PROP_CHILD_TRANSITION_DURATION: + hdy_leaflet_set_child_transition_duration (self, g_value_get_uint (value)); + break; + case PROP_INTERPOLATE_SIZE: + hdy_leaflet_set_interpolate_size (self, g_value_get_boolean (value)); + break; + case PROP_CAN_SWIPE_BACK: + hdy_leaflet_set_can_swipe_back (self, g_value_get_boolean (value)); + break; + case PROP_CAN_SWIPE_FORWARD: + hdy_leaflet_set_can_swipe_forward (self, g_value_get_boolean (value)); + break; + case PROP_ORIENTATION: + hdy_stackable_box_set_orientation (HDY_GET_HELPER (self), g_value_get_enum (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +hdy_leaflet_finalize (GObject *object) +{ + HdyLeaflet *self = HDY_LEAFLET (object); + HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); + + g_clear_object (&priv->box); + + G_OBJECT_CLASS (hdy_leaflet_parent_class)->finalize (object); +} + +static void +hdy_leaflet_get_child_property (GtkContainer *container, + GtkWidget *widget, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case CHILD_PROP_NAME: + g_value_set_string (value, hdy_stackable_box_get_child_name (HDY_GET_HELPER (container), widget)); + break; + + case CHILD_PROP_NAVIGATABLE: + g_value_set_boolean (value, hdy_stackable_box_get_child_navigatable (HDY_GET_HELPER (container), widget)); + break; + + default: + GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec); + break; + } +} + +static void +hdy_leaflet_set_child_property (GtkContainer *container, + GtkWidget *widget, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case CHILD_PROP_NAME: + hdy_stackable_box_set_child_name (HDY_GET_HELPER (container), widget, g_value_get_string (value)); + gtk_container_child_notify_by_pspec (container, widget, pspec); + break; + + case CHILD_PROP_NAVIGATABLE: + hdy_stackable_box_set_child_navigatable (HDY_GET_HELPER (container), widget, g_value_get_boolean (value)); + gtk_container_child_notify_by_pspec (container, widget, pspec); + break; + + default: + GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec); + break; + } +} + +static void +hdy_leaflet_realize (GtkWidget *widget) +{ + hdy_stackable_box_realize (HDY_GET_HELPER (widget)); +} + +static void +hdy_leaflet_unrealize (GtkWidget *widget) +{ + hdy_stackable_box_unrealize (HDY_GET_HELPER (widget)); +} + +static void +hdy_leaflet_map (GtkWidget *widget) +{ + hdy_stackable_box_map (HDY_GET_HELPER (widget)); +} + +static void +hdy_leaflet_unmap (GtkWidget *widget) +{ + hdy_stackable_box_unmap (HDY_GET_HELPER (widget)); +} + +static void +hdy_leaflet_switch_child (HdySwipeable *swipeable, + guint index, + gint64 duration) +{ + hdy_stackable_box_switch_child (HDY_GET_HELPER (swipeable), index, duration); +} + +static HdySwipeTracker * +hdy_leaflet_get_swipe_tracker (HdySwipeable *swipeable) +{ + return hdy_stackable_box_get_swipe_tracker (HDY_GET_HELPER (swipeable)); +} + +static gdouble +hdy_leaflet_get_distance (HdySwipeable *swipeable) +{ + return hdy_stackable_box_get_distance (HDY_GET_HELPER (swipeable)); +} + +static gdouble * +hdy_leaflet_get_snap_points (HdySwipeable *swipeable, + gint *n_snap_points) +{ + return hdy_stackable_box_get_snap_points (HDY_GET_HELPER (swipeable), n_snap_points); +} + +static gdouble +hdy_leaflet_get_progress (HdySwipeable *swipeable) +{ + return hdy_stackable_box_get_progress (HDY_GET_HELPER (swipeable)); +} + +static gdouble +hdy_leaflet_get_cancel_progress (HdySwipeable *swipeable) +{ + return hdy_stackable_box_get_cancel_progress (HDY_GET_HELPER (swipeable)); +} + +static void +hdy_leaflet_get_swipe_area (HdySwipeable *swipeable, + HdyNavigationDirection navigation_direction, + gboolean is_drag, + GdkRectangle *rect) +{ + hdy_stackable_box_get_swipe_area (HDY_GET_HELPER (swipeable), navigation_direction, is_drag, rect); +} + +static void +hdy_leaflet_class_init (HdyLeafletClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = (GtkWidgetClass*) klass; + GtkContainerClass *container_class = (GtkContainerClass*) klass; + + object_class->get_property = hdy_leaflet_get_property; + object_class->set_property = hdy_leaflet_set_property; + object_class->finalize = hdy_leaflet_finalize; + + widget_class->realize = hdy_leaflet_realize; + widget_class->unrealize = hdy_leaflet_unrealize; + widget_class->map = hdy_leaflet_map; + widget_class->unmap = hdy_leaflet_unmap; + widget_class->get_preferred_width = hdy_leaflet_get_preferred_width; + widget_class->get_preferred_height = hdy_leaflet_get_preferred_height; + widget_class->get_preferred_width_for_height = hdy_leaflet_get_preferred_width_for_height; + widget_class->get_preferred_height_for_width = hdy_leaflet_get_preferred_height_for_width; + widget_class->size_allocate = hdy_leaflet_size_allocate; + widget_class->draw = hdy_leaflet_draw; + widget_class->direction_changed = hdy_leaflet_direction_changed; + + container_class->add = hdy_leaflet_add; + container_class->remove = hdy_leaflet_remove; + container_class->forall = hdy_leaflet_forall; + container_class->set_child_property = hdy_leaflet_set_child_property; + container_class->get_child_property = hdy_leaflet_get_child_property; + gtk_container_class_handle_border_width (container_class); + + g_object_class_override_property (object_class, + PROP_ORIENTATION, + "orientation"); + + /** + * HdyLeaflet:folded: + * + * %TRUE if the leaflet is folded. + * + * The leaflet will be folded if the size allocated to it is smaller than the + * sum of the natural size of its children, it will be unfolded otherwise. + */ + props[PROP_FOLDED] = + g_param_spec_boolean ("folded", + _("Folded"), + _("Whether the widget is folded"), + FALSE, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY); + + /** + * HdyLeaflet:hhomogeneous_folded: + * + * %TRUE if the leaflet allocates the same width for all children when folded. + */ + props[PROP_HHOMOGENEOUS_FOLDED] = + g_param_spec_boolean ("hhomogeneous-folded", + _("Horizontally homogeneous folded"), + _("Horizontally homogeneous sizing when the leaflet is folded"), + TRUE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + /** + * HdyLeaflet:vhomogeneous_folded: + * + * %TRUE if the leaflet allocates the same height for all children when folded. + */ + props[PROP_VHOMOGENEOUS_FOLDED] = + g_param_spec_boolean ("vhomogeneous-folded", + _("Vertically homogeneous folded"), + _("Vertically homogeneous sizing when the leaflet is folded"), + TRUE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + /** + * HdyLeaflet:hhomogeneous_unfolded: + * + * %TRUE if the leaflet allocates the same width for all children when unfolded. + */ + props[PROP_HHOMOGENEOUS_UNFOLDED] = + g_param_spec_boolean ("hhomogeneous-unfolded", + _("Box horizontally homogeneous"), + _("Horizontally homogeneous sizing when the leaflet is unfolded"), + FALSE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + /** + * HdyLeaflet:vhomogeneous_unfolded: + * + * %TRUE if the leaflet allocates the same height for all children when unfolded. + */ + props[PROP_VHOMOGENEOUS_UNFOLDED] = + g_param_spec_boolean ("vhomogeneous-unfolded", + _("Box vertically homogeneous"), + _("Vertically homogeneous sizing when the leaflet is unfolded"), + FALSE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + props[PROP_VISIBLE_CHILD] = + g_param_spec_object ("visible-child", + _("Visible child"), + _("The widget currently visible when the leaflet is folded"), + GTK_TYPE_WIDGET, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + props[PROP_VISIBLE_CHILD_NAME] = + g_param_spec_string ("visible-child-name", + _("Name of visible child"), + _("The name of the widget currently visible when the children are stacked"), + NULL, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + /** + * HdyLeaflet:transition-type: + * + * The type of animation that will be used for transitions between modes and + * children. + * + * The transition type can be changed without problems at runtime, so it is + * possible to change the animation based on the mode or child that is about + * to become current. + * + * Since: 0.0.12 + */ + props[PROP_TRANSITION_TYPE] = + g_param_spec_enum ("transition-type", + _("Transition type"), + _("The type of animation used to transition between modes and children"), + HDY_TYPE_LEAFLET_TRANSITION_TYPE, HDY_LEAFLET_TRANSITION_TYPE_OVER, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + props[PROP_MODE_TRANSITION_DURATION] = + g_param_spec_uint ("mode-transition-duration", + _("Mode transition duration"), + _("The mode transition animation duration, in milliseconds"), + 0, G_MAXUINT, 250, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + props[PROP_CHILD_TRANSITION_DURATION] = + g_param_spec_uint ("child-transition-duration", + _("Child transition duration"), + _("The child transition animation duration, in milliseconds"), + 0, G_MAXUINT, 200, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + props[PROP_CHILD_TRANSITION_RUNNING] = + g_param_spec_boolean ("child-transition-running", + _("Child transition running"), + _("Whether or not the child transition is currently running"), + FALSE, + G_PARAM_READABLE); + + props[PROP_INTERPOLATE_SIZE] = + g_param_spec_boolean ("interpolate-size", + _("Interpolate size"), + _("Whether or not the size should smoothly change when changing between differently sized children"), + FALSE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + /** + * HdyLeaflet:can-swipe-back: + * + * Whether or not the leaflet allows switching to the previous child that has + * 'navigatable' child property set to %TRUE via a swipe gesture. + * + * Since: 0.0.12 + */ + props[PROP_CAN_SWIPE_BACK] = + g_param_spec_boolean ("can-swipe-back", + _("Can swipe back"), + _("Whether or not swipe gesture can be used to switch to the previous child"), + FALSE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + /** + * HdyLeaflet:can-swipe-forward: + * + * Whether or not the leaflet allows switching to the next child that has + * 'navigatable' child property set to %TRUE via a swipe gesture. + * + * Since: 0.0.12 + */ + props[PROP_CAN_SWIPE_FORWARD] = + g_param_spec_boolean ("can-swipe-forward", + _("Can swipe forward"), + _("Whether or not swipe gesture can be used to switch to the next child"), + FALSE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (object_class, LAST_PROP, props); + + child_props[CHILD_PROP_NAME] = + g_param_spec_string ("name", + _("Name"), + _("The name of the child page"), + NULL, + G_PARAM_READWRITE); + + /** + * HdyLeaflet:navigatable: + * + * Whether the child can be navigated to when folded. + * If %FALSE, the child will be ignored by hdy_leaflet_get_adjacent_child(), + * hdy_leaflet_navigate(), and swipe gestures. + * + * This can be used used to prevent switching to widgets like separators. + * + * Since: 1.0 + */ + child_props[CHILD_PROP_NAVIGATABLE] = + g_param_spec_boolean ("navigatable", + _("Navigatable"), + _("Whether the child can be navigated to"), + TRUE, + G_PARAM_READWRITE); + + gtk_container_class_install_child_properties (container_class, LAST_CHILD_PROP, child_props); + + gtk_widget_class_set_accessible_role (widget_class, ATK_ROLE_PANEL); + gtk_widget_class_set_css_name (widget_class, "leaflet"); +} + +GtkWidget * +hdy_leaflet_new (void) +{ + return g_object_new (HDY_TYPE_LEAFLET, NULL); +} + +#define NOTIFY(func, prop) \ +static void \ +func (HdyLeaflet *self) { \ + g_object_notify_by_pspec (G_OBJECT (self), props[prop]); \ +} + +NOTIFY (notify_folded_cb, PROP_FOLDED); +NOTIFY (notify_hhomogeneous_folded_cb, PROP_HHOMOGENEOUS_FOLDED); +NOTIFY (notify_vhomogeneous_folded_cb, PROP_VHOMOGENEOUS_FOLDED); +NOTIFY (notify_hhomogeneous_unfolded_cb, PROP_HHOMOGENEOUS_UNFOLDED); +NOTIFY (notify_vhomogeneous_unfolded_cb, PROP_VHOMOGENEOUS_UNFOLDED); +NOTIFY (notify_visible_child_cb, PROP_VISIBLE_CHILD); +NOTIFY (notify_visible_child_name_cb, PROP_VISIBLE_CHILD_NAME); +NOTIFY (notify_transition_type_cb, PROP_TRANSITION_TYPE); +NOTIFY (notify_mode_transition_duration_cb, PROP_MODE_TRANSITION_DURATION); +NOTIFY (notify_child_transition_duration_cb, PROP_CHILD_TRANSITION_DURATION); +NOTIFY (notify_child_transition_running_cb, PROP_CHILD_TRANSITION_RUNNING); +NOTIFY (notify_interpolate_size_cb, PROP_INTERPOLATE_SIZE); +NOTIFY (notify_can_swipe_back_cb, PROP_CAN_SWIPE_BACK); +NOTIFY (notify_can_swipe_forward_cb, PROP_CAN_SWIPE_FORWARD); + +static void +notify_orientation_cb (HdyLeaflet *self) +{ + g_object_notify (G_OBJECT (self), "orientation"); +} + +static void +hdy_leaflet_init (HdyLeaflet *self) +{ + HdyLeafletPrivate *priv = hdy_leaflet_get_instance_private (self); + + priv->box = hdy_stackable_box_new (GTK_CONTAINER (self), + GTK_CONTAINER_CLASS (hdy_leaflet_parent_class), + TRUE); + + g_signal_connect_object (priv->box, "notify::folded", G_CALLBACK (notify_folded_cb), self, G_CONNECT_SWAPPED); + g_signal_connect_object (priv->box, "notify::hhomogeneous-folded", G_CALLBACK (notify_hhomogeneous_folded_cb), self, G_CONNECT_SWAPPED); + g_signal_connect_object (priv->box, "notify::vhomogeneous-folded", G_CALLBACK (notify_vhomogeneous_folded_cb), self, G_CONNECT_SWAPPED); + g_signal_connect_object (priv->box, "notify::hhomogeneous-unfolded", G_CALLBACK (notify_hhomogeneous_unfolded_cb), self, G_CONNECT_SWAPPED); + g_signal_connect_object (priv->box, "notify::vhomogeneous-unfolded", G_CALLBACK (notify_vhomogeneous_unfolded_cb), self, G_CONNECT_SWAPPED); + g_signal_connect_object (priv->box, "notify::visible-child", G_CALLBACK (notify_visible_child_cb), self, G_CONNECT_SWAPPED); + g_signal_connect_object (priv->box, "notify::visible-child-name", G_CALLBACK (notify_visible_child_name_cb), self, G_CONNECT_SWAPPED); + g_signal_connect_object (priv->box, "notify::transition-type", G_CALLBACK (notify_transition_type_cb), self, G_CONNECT_SWAPPED); + g_signal_connect_object (priv->box, "notify::mode-transition-duration", G_CALLBACK (notify_mode_transition_duration_cb), self, G_CONNECT_SWAPPED); + g_signal_connect_object (priv->box, "notify::child-transition-duration", G_CALLBACK (notify_child_transition_duration_cb), self, G_CONNECT_SWAPPED); + g_signal_connect_object (priv->box, "notify::child-transition-running", G_CALLBACK (notify_child_transition_running_cb), self, G_CONNECT_SWAPPED); + g_signal_connect_object (priv->box, "notify::interpolate-size", G_CALLBACK (notify_interpolate_size_cb), self, G_CONNECT_SWAPPED); + g_signal_connect_object (priv->box, "notify::can-swipe-back", G_CALLBACK (notify_can_swipe_back_cb), self, G_CONNECT_SWAPPED); + g_signal_connect_object (priv->box, "notify::can-swipe-forward", G_CALLBACK (notify_can_swipe_forward_cb), self, G_CONNECT_SWAPPED); + g_signal_connect_object (priv->box, "notify::orientation", G_CALLBACK (notify_orientation_cb), self, G_CONNECT_SWAPPED); +} + +static void +hdy_leaflet_swipeable_init (HdySwipeableInterface *iface) +{ + iface->switch_child = hdy_leaflet_switch_child; + iface->get_swipe_tracker = hdy_leaflet_get_swipe_tracker; + iface->get_distance = hdy_leaflet_get_distance; + iface->get_snap_points = hdy_leaflet_get_snap_points; + iface->get_progress = hdy_leaflet_get_progress; + iface->get_cancel_progress = hdy_leaflet_get_cancel_progress; + iface->get_swipe_area = hdy_leaflet_get_swipe_area; +} |