From 8cb3825d4817de35a839639a434b8ed0126206bf Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Wed, 3 Feb 2021 12:03:11 +0100 Subject: [PATCH 01/12] st/viewport: Add clip-to-view property This property controls whether the viewport clips the content to its own allocation or not. This will be necessary in special modes that we want to render past the viewport inside a scrollview. Part-of: --- src/st/st-viewport.c | 46 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/src/st/st-viewport.c b/src/st/st-viewport.c index c663ae4cd3..e2593196e8 100644 --- a/src/st/st-viewport.c +++ b/src/st/st-viewport.c @@ -55,6 +55,7 @@ static void st_viewport_scrollable_interface_init (StScrollableInterface *iface) enum { PROP_0, + PROP_CLIP_TO_VIEW, PROP_HADJUST, PROP_VADJUST }; @@ -63,6 +64,7 @@ typedef struct { StAdjustment *hadjustment; StAdjustment *vadjustment; + gboolean clip_to_view; } StViewportPrivate; G_DEFINE_TYPE_WITH_CODE (StViewport, st_viewport, ST_TYPE_WIDGET, @@ -163,12 +165,28 @@ st_viewport_scrollable_interface_init (StScrollableInterface *iface) iface->get_adjustments = scrollable_get_adjustments; } +static void +st_viewport_set_clip_to_view (StViewport *viewport, + gboolean clip_to_view) +{ + StViewportPrivate *priv = + st_viewport_get_instance_private (viewport); + + if (!!priv->clip_to_view != !!clip_to_view) + { + priv->clip_to_view = clip_to_view; + clutter_actor_queue_redraw (CLUTTER_ACTOR (viewport)); + } +} + static void st_viewport_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { + StViewportPrivate *priv = + st_viewport_get_instance_private (ST_VIEWPORT (object)); StAdjustment *adjustment; switch (property_id) @@ -183,6 +201,10 @@ st_viewport_get_property (GObject *object, g_value_set_object (value, adjustment); break; + case PROP_CLIP_TO_VIEW: + g_value_set_boolean (value, priv->clip_to_view); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } @@ -212,6 +234,10 @@ st_viewport_set_property (GObject *object, g_value_get_object (value)); break; + case PROP_CLIP_TO_VIEW: + st_viewport_set_clip_to_view (viewport, g_value_get_boolean (value)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } @@ -405,7 +431,7 @@ st_viewport_paint (ClutterActor *actor, /* The content area forms the viewport into the scrolled contents, while * the borders and background stay in place; after drawing the borders and * background, we clip to the content area */ - if (priv->hadjustment || priv->vadjustment) + if (priv->clip_to_view && (priv->hadjustment || priv->vadjustment)) { cogl_framebuffer_push_rectangle_clip (fb, (int)content_box.x1, @@ -419,7 +445,7 @@ st_viewport_paint (ClutterActor *actor, child = clutter_actor_get_next_sibling (child)) clutter_actor_paint (child, paint_context); - if (priv->hadjustment || priv->vadjustment) + if (priv->clip_to_view && (priv->hadjustment || priv->vadjustment)) cogl_framebuffer_pop_clip (fb); } @@ -478,6 +504,9 @@ st_viewport_get_paint_volume (ClutterActor *actor, if (!clutter_actor_has_allocation (actor)) return FALSE; + if (!priv->clip_to_view) + return CLUTTER_ACTOR_CLASS (st_viewport_parent_class)->get_paint_volume (actor, volume); + /* When have an adjustment we are clipped to the content box, so base * our paint volume on that. */ if (priv->hadjustment || priv->vadjustment) @@ -558,6 +587,14 @@ st_viewport_class_init (StViewportClass *klass) actor_class->get_paint_volume = st_viewport_get_paint_volume; actor_class->pick = st_viewport_pick; + g_object_class_install_property (object_class, + PROP_CLIP_TO_VIEW, + g_param_spec_boolean ("clip-to-view", + "Clip to view", + "Clip to view", + TRUE, + ST_PARAM_READWRITE)); + /* StScrollable properties */ g_object_class_override_property (object_class, PROP_HADJUST, @@ -566,10 +603,13 @@ st_viewport_class_init (StViewportClass *klass) g_object_class_override_property (object_class, PROP_VADJUST, "vadjustment"); - } static void st_viewport_init (StViewport *self) { + StViewportPrivate *priv = + st_viewport_get_instance_private (self); + + priv->clip_to_view = TRUE; } -- GitLab From f60a469a34e2e3973caa83a4bd41996aa3cf2d1d Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Wed, 3 Feb 2021 12:07:34 +0100 Subject: [PATCH 02/12] st/scroll-view: Make fade effect take ClutterMargin Instead of taking just vertical/horizontal offsets, take a ClutterMargin to allow us set the fade offsets on each direction specifically. Also, handle negative values in margins, the fade effect will run in the negative space left by the scrollview padding instead. Another difference now is that areas outside the extents of the effect will be transparent, instead of the effect ending abruptly past the given extents. This will be used by the app grid, in order to selectively let see either of next/prev pages while navigating. While at it, fix code style issues in st_scroll_view_update_fade_effect(), and clean up unused variables from the GLSL code. Part-of: --- js/ui/appDisplay.js | 8 ++- src/st/st-scroll-view-fade.c | 105 ++++++++++++-------------------- src/st/st-scroll-view-fade.glsl | 27 ++++---- src/st/st-scroll-view.c | 49 ++++++++------- src/st/st-scroll-view.h | 5 +- 5 files changed, 89 insertions(+), 105 deletions(-) diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index c3958ae9ce..ca0f9d89d0 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -264,7 +264,13 @@ var BaseAppView = GObject.registerClass({ return; } - this._scrollView.update_fade_effect(vOffset, hOffset); + this._scrollView.update_fade_effect( + new Clutter.Margin({ + left: hOffset, + right: hOffset, + top: vOffset, + bottom: vOffset, + })); } _createGrid() { diff --git a/src/st/st-scroll-view-fade.c b/src/st/st-scroll-view-fade.c index a0bb85bda8..38d40ed651 100644 --- a/src/st/st-scroll-view-fade.c +++ b/src/st/st-scroll-view-fade.c @@ -46,8 +46,7 @@ struct _StScrollViewFade guint fade_edges : 1; - float vfade_offset; - float hfade_offset; + ClutterMargin fade_margins; }; G_DEFINE_TYPE (StScrollViewFade, @@ -57,8 +56,7 @@ G_DEFINE_TYPE (StScrollViewFade, enum { PROP_0, - PROP_VFADE_OFFSET, - PROP_HFADE_OFFSET, + PROP_FADE_MARGINS, PROP_FADE_EDGES, N_PROPS @@ -136,6 +134,15 @@ st_scroll_view_fade_paint_target (ClutterOffscreenEffect *effect, if (h_scroll_visible) fade_area_bottomright[1] -= clutter_actor_get_height (hscroll); + if (self->fade_margins.left < 0) + fade_area_topleft[0] -= ABS (self->fade_margins.left); + if (self->fade_margins.right < 0) + fade_area_bottomright[0] += ABS (self->fade_margins.right); + if (self->fade_margins.top < 0) + fade_area_topleft[1] -= ABS (self->fade_margins.top); + if (self->fade_margins.bottom < 0) + fade_area_bottomright[1] += ABS (self->fade_margins.bottom); + st_adjustment_get_values (self->vadjustment, &value, &lower, &upper, NULL, NULL, &page_size); value = (value - lower) / (upper - page_size - lower); clutter_shader_effect_set_uniform (shader, "fade_edges_top", G_TYPE_INT, 1, self->fade_edges ? value >= 0.0 : value > 0.0); @@ -146,8 +153,10 @@ st_scroll_view_fade_paint_target (ClutterOffscreenEffect *effect, clutter_shader_effect_set_uniform (shader, "fade_edges_left", G_TYPE_INT, 1, self->fade_edges ? value >= 0.0 : value > 0.0); clutter_shader_effect_set_uniform (shader, "fade_edges_right", G_TYPE_INT, 1, self->fade_edges ? value <= 1.0 : value < 1.0); - clutter_shader_effect_set_uniform (shader, "vfade_offset", G_TYPE_FLOAT, 1, self->vfade_offset); - clutter_shader_effect_set_uniform (shader, "hfade_offset", G_TYPE_FLOAT, 1, self->hfade_offset); + clutter_shader_effect_set_uniform (shader, "fade_offset_top", G_TYPE_FLOAT, 1, ABS (self->fade_margins.top)); + clutter_shader_effect_set_uniform (shader, "fade_offset_bottom", G_TYPE_FLOAT, 1, ABS (self->fade_margins.bottom)); + clutter_shader_effect_set_uniform (shader, "fade_offset_left", G_TYPE_FLOAT, 1, ABS (self->fade_margins.left)); + clutter_shader_effect_set_uniform (shader, "fade_offset_right", G_TYPE_FLOAT, 1, ABS (self->fade_margins.right)); clutter_shader_effect_set_uniform (shader, "tex", G_TYPE_INT, 1, 0); clutter_shader_effect_set_uniform (shader, "height", G_TYPE_FLOAT, 1, clutter_actor_get_height (self->actor)); clutter_shader_effect_set_uniform (shader, "width", G_TYPE_FLOAT, 1, clutter_actor_get_width (self->actor)); @@ -266,39 +275,21 @@ st_scroll_view_fade_dispose (GObject *gobject) } static void -st_scroll_view_vfade_set_offset (StScrollViewFade *self, - float fade_offset) -{ - if (self->vfade_offset == fade_offset) - return; - - g_object_freeze_notify (G_OBJECT (self)); - - self->vfade_offset = fade_offset; - - if (self->actor != NULL) - clutter_actor_queue_redraw (self->actor); - - g_object_notify_by_pspec (G_OBJECT (self), props[PROP_VFADE_OFFSET]); - g_object_thaw_notify (G_OBJECT (self)); -} - -static void -st_scroll_view_hfade_set_offset (StScrollViewFade *self, - float fade_offset) +st_scroll_view_set_fade_margins (StScrollViewFade *self, + ClutterMargin *fade_margins) { - if (self->hfade_offset == fade_offset) + if (self->fade_margins.left == fade_margins->left && + self->fade_margins.right == fade_margins->right && + self->fade_margins.top == fade_margins->top && + self->fade_margins.bottom == fade_margins->bottom) return; - g_object_freeze_notify (G_OBJECT (self)); - - self->hfade_offset = fade_offset; + self->fade_margins = *fade_margins; if (self->actor != NULL) clutter_actor_queue_redraw (self->actor); - g_object_notify_by_pspec (G_OBJECT (self), props[PROP_HFADE_OFFSET]); - g_object_thaw_notify (G_OBJECT (self)); + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_FADE_MARGINS]); } static void @@ -329,11 +320,8 @@ st_scroll_view_fade_set_property (GObject *object, switch (prop_id) { - case PROP_VFADE_OFFSET: - st_scroll_view_vfade_set_offset (self, g_value_get_float (value)); - break; - case PROP_HFADE_OFFSET: - st_scroll_view_hfade_set_offset (self, g_value_get_float (value)); + case PROP_FADE_MARGINS: + st_scroll_view_set_fade_margins (self, g_value_get_boxed (value)); break; case PROP_FADE_EDGES: st_scroll_view_fade_set_fade_edges (self, g_value_get_boolean (value)); @@ -354,11 +342,8 @@ st_scroll_view_fade_get_property (GObject *object, switch (prop_id) { - case PROP_HFADE_OFFSET: - g_value_set_float (value, self->hfade_offset); - break; - case PROP_VFADE_OFFSET: - g_value_set_float (value, self->vfade_offset); + case PROP_FADE_MARGINS: + g_value_set_boxed (value, &self->fade_margins); break; case PROP_FADE_EDGES: g_value_set_boolean (value, self->fade_edges); @@ -391,29 +376,15 @@ st_scroll_view_fade_class_init (StScrollViewFadeClass *klass) offscreen_class->paint_target = st_scroll_view_fade_paint_target; /** - * StScrollViewFade:vfade-offset: - * - * The height of area which is faded at the top and bottom edges of the - * #StScrollViewFade. - */ - props[PROP_VFADE_OFFSET] = - g_param_spec_float ("vfade-offset", - "Vertical Fade Offset", - "The height of the area which is faded at the edge", - 0.f, G_MAXFLOAT, DEFAULT_FADE_OFFSET, - ST_PARAM_READWRITE); - - /** - * StScrollViewFade:hfade-offset: + * StScrollViewFade:fade-margins: * - * The height of area which is faded at the left and right edges of the - * #StScrollViewFade. + * The margins widths that are faded. */ - props[PROP_HFADE_OFFSET] = - g_param_spec_float ("hfade-offset", - "Horizontal Fade Offset", - "The width of the area which is faded at the edge", - 0.f, G_MAXFLOAT, DEFAULT_FADE_OFFSET, + props[PROP_FADE_MARGINS] = + g_param_spec_boxed ("fade-margins", + "Fade margins", + "The margin widths that are faded", + CLUTTER_TYPE_MARGIN, ST_PARAM_READWRITE); /** @@ -434,8 +405,12 @@ st_scroll_view_fade_class_init (StScrollViewFadeClass *klass) static void st_scroll_view_fade_init (StScrollViewFade *self) { - self->vfade_offset = DEFAULT_FADE_OFFSET; - self->hfade_offset = DEFAULT_FADE_OFFSET; + self->fade_margins = (ClutterMargin) { + DEFAULT_FADE_OFFSET, + DEFAULT_FADE_OFFSET, + DEFAULT_FADE_OFFSET, + DEFAULT_FADE_OFFSET, + }; } /** diff --git a/src/st/st-scroll-view-fade.glsl b/src/st/st-scroll-view-fade.glsl index e3f1a259f3..264f3bcd72 100644 --- a/src/st/st-scroll-view-fade.glsl +++ b/src/st/st-scroll-view-fade.glsl @@ -20,8 +20,10 @@ uniform sampler2D tex; uniform float height; uniform float width; -uniform float vfade_offset; -uniform float hfade_offset; +uniform float fade_offset_top; +uniform float fade_offset_bottom; +uniform float fade_offset_left; +uniform float fade_offset_right; uniform bool fade_edges_top; uniform bool fade_edges_right; uniform bool fade_edges_bottom; @@ -37,23 +39,19 @@ void main () float y = height * cogl_tex_coord_in[0].y; float x = width * cogl_tex_coord_in[0].x; - /* - * We cannot just return here due to a bug in llvmpipe see: - * https://bugzilla.freedesktop.org/show_bug.cgi?id=62357 - */ if (x > fade_area_topleft[0] && x < fade_area_bottomright[0] && - y > fade_area_topleft[1] && y < fade_area_bottomright[1]) { + y > fade_area_topleft[1] && y < fade_area_bottomright[1]) + { float ratio = 1.0; - float fade_top_start = fade_area_topleft[1] + vfade_offset; - float fade_left_start = fade_area_topleft[0] + hfade_offset; - float fade_bottom_start = fade_area_bottomright[1] - vfade_offset; - float fade_right_start = fade_area_bottomright[0] - hfade_offset; - bool fade_top = y < vfade_offset && fade_edges_top; + float fade_top_start = fade_area_topleft[1] + fade_offset_top; + float fade_left_start = fade_area_topleft[0] + fade_offset_left; + float fade_bottom_start = fade_area_bottomright[1] - fade_offset_bottom; + float fade_right_start = fade_area_bottomright[0] - fade_offset_right; + bool fade_top = y < fade_top_start && fade_edges_top; bool fade_bottom = y > fade_bottom_start && fade_edges_bottom; bool fade_left = x < fade_left_start && fade_edges_left; bool fade_right = x > fade_right_start && fade_edges_right; - float vfade_scale = height / vfade_offset; if (fade_top) { ratio *= (fade_area_topleft[1] - y) / (fade_area_topleft[1] - fade_top_start); } @@ -62,7 +60,6 @@ void main () ratio *= (fade_area_bottomright[1] - y) / (fade_area_bottomright[1] - fade_bottom_start); } - float hfade_scale = width / hfade_offset; if (fade_left) { ratio *= (fade_area_topleft[0] - x) / (fade_area_topleft[0] - fade_left_start); } @@ -72,5 +69,7 @@ void main () } cogl_color_out *= ratio; + } else { + cogl_color_out *= 0; } } diff --git a/src/st/st-scroll-view.c b/src/st/st-scroll-view.c index 0cf48adafc..3b9711ea21 100644 --- a/src/st/st-scroll-view.c +++ b/src/st/st-scroll-view.c @@ -164,42 +164,41 @@ st_scroll_view_get_property (GObject *object, /** * st_scroll_view_update_fade_effect: * @scroll: a #StScrollView - * @vfade_offset: The length of the vertical fade effect, in pixels. - * @hfade_offset: The length of the horizontal fade effect, in pixels. + * @fade_margins: a #ClutterMargin defining the vertical fade effects, in pixels. * - * Sets the height of the fade area area in pixels. A value of 0 + * Sets the fade effects in all four edges of the view. A value of 0 * disables the effect. */ void -st_scroll_view_update_fade_effect (StScrollView *scroll, - float vfade_offset, - float hfade_offset) +st_scroll_view_update_fade_effect (StScrollView *scroll, + ClutterMargin *fade_margins) { StScrollViewPrivate *priv = ST_SCROLL_VIEW (scroll)->priv; - /* A fade amount of more than 0 enables the effect. */ - if (vfade_offset > 0. || hfade_offset > 0.) + /* A fade amount of other than 0 enables the effect. */ + if (fade_margins->left != 0. || fade_margins->right != 0. || + fade_margins->top != 0. || fade_margins->bottom != 0.) { - if (priv->fade_effect == NULL) { - priv->fade_effect = g_object_new (ST_TYPE_SCROLL_VIEW_FADE, NULL); + if (priv->fade_effect == NULL) + { + priv->fade_effect = g_object_new (ST_TYPE_SCROLL_VIEW_FADE, NULL); - clutter_actor_add_effect_with_name (CLUTTER_ACTOR (scroll), "fade", - CLUTTER_EFFECT (priv->fade_effect)); - } + clutter_actor_add_effect_with_name (CLUTTER_ACTOR (scroll), "fade", + CLUTTER_EFFECT (priv->fade_effect)); + } g_object_set (priv->fade_effect, - "vfade-offset", vfade_offset, - NULL); - g_object_set (priv->fade_effect, - "hfade-offset", hfade_offset, + "fade-margins", fade_margins, NULL); } else { - if (priv->fade_effect != NULL) { - clutter_actor_remove_effect (CLUTTER_ACTOR (scroll), CLUTTER_EFFECT (priv->fade_effect)); - priv->fade_effect = NULL; - } + if (priv->fade_effect != NULL) + { + clutter_actor_remove_effect (CLUTTER_ACTOR (scroll), + CLUTTER_EFFECT (priv->fade_effect)); + priv->fade_effect = NULL; + } } clutter_actor_queue_redraw (CLUTTER_ACTOR (scroll)); @@ -744,7 +743,13 @@ st_scroll_view_style_changed (StWidget *widget) StThemeNode *theme_node = st_widget_get_theme_node (widget); gdouble vfade_offset = st_theme_node_get_length (theme_node, "-st-vfade-offset"); gdouble hfade_offset = st_theme_node_get_length (theme_node, "-st-hfade-offset"); - st_scroll_view_update_fade_effect (self, vfade_offset, hfade_offset); + st_scroll_view_update_fade_effect (self, + &(ClutterMargin) { + .top = vfade_offset, + .bottom = vfade_offset, + .left = hfade_offset, + .right = hfade_offset, + }); st_widget_style_changed (ST_WIDGET (priv->hscroll)); st_widget_style_changed (ST_WIDGET (priv->vscroll)); diff --git a/src/st/st-scroll-view.h b/src/st/st-scroll-view.h index 2f2c31d6c9..e2acaca2f5 100644 --- a/src/st/st-scroll-view.h +++ b/src/st/st-scroll-view.h @@ -82,9 +82,8 @@ gboolean st_scroll_view_get_overlay_scrollbars (StScrollView *scroll); void st_scroll_view_set_policy (StScrollView *scroll, StPolicyType hscroll, StPolicyType vscroll); -void st_scroll_view_update_fade_effect (StScrollView *scroll, - float vfade_offset, - float hfade_offset); +void st_scroll_view_update_fade_effect (StScrollView *scroll, + ClutterMargin *fade_margins); G_END_DECLS -- GitLab From 0d62dadfbc4532953668a976a2585369d753ae01 Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Fri, 19 Feb 2021 16:42:34 +0100 Subject: [PATCH 03/12] st/scrollview: Add ::content-padding property to StScrollView This will be needed for fine tuning of the visible area for appGrid navigation purposes. We most nominally can let it happen via CSS as the size calculations happen on size allocate, so we want to avoid triggering relayouts while adapting to the given size. Part-of: --- src/st/st-scroll-view-fade.c | 8 ++++++++ src/st/st-scroll-view.c | 21 +++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/st/st-scroll-view-fade.c b/src/st/st-scroll-view-fade.c index 38d40ed651..3ca7313372 100644 --- a/src/st/st-scroll-view-fade.c +++ b/src/st/st-scroll-view-fade.c @@ -97,6 +97,7 @@ st_scroll_view_fade_paint_target (ClutterOffscreenEffect *effect, gboolean h_scroll_visible, v_scroll_visible; ClutterActorBox allocation, content_box, paint_box; + ClutterMargin *content_padding; float fade_area_topleft[2]; float fade_area_bottomright[2]; @@ -108,6 +109,13 @@ st_scroll_view_fade_paint_target (ClutterOffscreenEffect *effect, clutter_actor_get_allocation_box (self->actor, &allocation); st_theme_node_get_content_box (st_widget_get_theme_node (ST_WIDGET (self->actor)), (const ClutterActorBox *)&allocation, &content_box); + g_object_get (self->actor, "content-padding", &content_padding, NULL); + + content_box.x1 += content_padding->left; + content_box.x2 -= content_padding->right; + content_box.y1 += content_padding->top; + content_box.y2 -= content_padding->bottom; + clutter_margin_free (content_padding); /* * The FBO is based on the paint_volume's size which can be larger then the actual diff --git a/src/st/st-scroll-view.c b/src/st/st-scroll-view.c index 3b9711ea21..606701565d 100644 --- a/src/st/st-scroll-view.c +++ b/src/st/st-scroll-view.c @@ -84,6 +84,8 @@ struct _StScrollViewPrivate StAdjustment *vadjustment; ClutterActor *vscroll; + ClutterMargin content_padding; + StPolicyType hscrollbar_policy; StPolicyType vscrollbar_policy; @@ -116,6 +118,7 @@ enum { PROP_VSCROLLBAR_VISIBLE, PROP_MOUSE_SCROLL, PROP_OVERLAY_SCROLLBARS, + PROP_CONTENT_PADDING, N_PROPS }; @@ -156,6 +159,9 @@ st_scroll_view_get_property (GObject *object, case PROP_OVERLAY_SCROLLBARS: g_value_set_boolean (value, priv->overlay_scrollbars); break; + case PROP_CONTENT_PADDING: + g_value_set_boxed (value, &priv->content_padding); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } @@ -233,6 +239,9 @@ st_scroll_view_set_property (GObject *object, priv->hscrollbar_policy, g_value_get_enum (value)); break; + case PROP_CONTENT_PADDING: + priv->content_padding = * (ClutterMargin *) g_value_get_boxed (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } @@ -545,6 +554,11 @@ st_scroll_view_allocate (ClutterActor *actor, st_theme_node_get_content_box (theme_node, box, &content_box); + content_box.x1 += priv->content_padding.left; + content_box.x2 -= priv->content_padding.right; + content_box.y1 += priv->content_padding.top; + content_box.y2 += priv->content_padding.bottom; + avail_width = content_box.x2 - content_box.x1; avail_height = content_box.y2 - content_box.y1; @@ -933,6 +947,13 @@ st_scroll_view_class_init (StScrollViewClass *klass) FALSE, ST_PARAM_READWRITE); + props[PROP_CONTENT_PADDING] = + g_param_spec_boxed ("content-padding", + "Content padding", + "Content padding", + CLUTTER_TYPE_MARGIN, + ST_PARAM_READWRITE); + g_object_class_install_properties (object_class, N_PROPS, props); } -- GitLab From f31c49c40ed3e34897e6e1fd2b5a0fe6f61ef962 Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Wed, 3 Feb 2021 12:39:26 +0100 Subject: [PATCH 04/12] js/appDisplay: Generalize app scrollview CSS We want to show left/right side pages during navigation, also in FolderViews. Let this scrollview use the same style than the "all apps" one, and generalize the name a bit. This will compress the scrollview horizontally, so there's actual overflow space to show these pages. Part-of: --- data/theme/gnome-shell-sass/widgets/_app-grid.scss | 2 +- js/ui/appDisplay.js | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/data/theme/gnome-shell-sass/widgets/_app-grid.scss b/data/theme/gnome-shell-sass/widgets/_app-grid.scss index bbe721600d..0e3c54c92e 100644 --- a/data/theme/gnome-shell-sass/widgets/_app-grid.scss +++ b/data/theme/gnome-shell-sass/widgets/_app-grid.scss @@ -124,7 +124,7 @@ $app_grid_fg_color: #fff; } // Some hacks I don't even know -.all-apps { +.apps-scroll-view { // horizontal padding to make sure scrollbars or dash don't overlap content padding: 0 88px; } diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index ca0f9d89d0..d4a627fcb5 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -137,6 +137,7 @@ var BaseAppView = GObject.registerClass({ // Scroll View this._scrollView = new St.ScrollView({ + style_class: 'apps-scroll-view', clip_to_allocation: true, x_expand: true, y_expand: true, @@ -968,8 +969,6 @@ class AppDisplay extends BaseAppView { this._pageManager = new PageManager(); this._pageManager.connect('layout-changed', () => this._redisplay()); - this._scrollView.add_style_class_name('all-apps'); - this._stack = new St.Widget({ layout_manager: new Clutter.BinLayout(), x_expand: true, -- GitLab From d75ed55ed8de8c046ea8c491bd6472bd51813934 Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Wed, 3 Feb 2021 12:46:07 +0100 Subject: [PATCH 05/12] js/appDisplay: Implement navigation of pages by hovering/clicking edges Add the necessary animations to slide in the icons in the previous/next pages, also needing to 1) drop the viewport clipping, and 2) extend scrollview fade effects to let see the pages in the navigated direction(s). The animation is driven via 2 adjustments, one for each side, so they can animate independently. Part-of: --- .../gnome-shell-sass/widgets/_app-grid.scss | 14 + js/ui/appDisplay.js | 239 +++++++++++++++++- 2 files changed, 252 insertions(+), 1 deletion(-) diff --git a/data/theme/gnome-shell-sass/widgets/_app-grid.scss b/data/theme/gnome-shell-sass/widgets/_app-grid.scss index 0e3c54c92e..5620ac26ff 100644 --- a/data/theme/gnome-shell-sass/widgets/_app-grid.scss +++ b/data/theme/gnome-shell-sass/widgets/_app-grid.scss @@ -136,3 +136,17 @@ $app_grid_fg_color: #fff; border-radius: 99px; icon-size: $app_icon_size * 0.5; } + +.page-navigation-hint { + background: rgba(255, 255, 255, 0.05); + width: 88px; + + &.next { + &:ltr { border-radius: 15px 0px 0px 15px; } + &:rtl { border-radius: 0px 15px 15px 0px; } + } + &.previous { + &:ltr { border-radius: 0px 15px 15px 0px; } + &:rtl { border-radius: 15px 0px 0px 15px; } + } +} diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index d4a627fcb5..ad791b26a8 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -38,6 +38,10 @@ var APP_ICON_TITLE_COLLAPSE_TIME = 100; const FOLDER_DIALOG_ANIMATION_TIME = 200; +const PAGE_PREVIEW_ANIMATION_TIME = 150; +const PAGE_PREVIEW_FADE_EFFECT_OFFSET = 160; +const PAGE_INDICATOR_FADE_TIME = 200; + const OVERSHOOT_THRESHOLD = 20; const OVERSHOOT_TIMEOUT = 1000; @@ -48,6 +52,12 @@ const DIALOG_SHADE_HIGHLIGHT = Clutter.Color.from_pixel(0x00000055); let discreteGpuAvailable = false; +var SidePages = { + NONE: 0, + PREVIOUS: 1 << 0, + NEXT: 1 << 1, +}; + function _getCategories(info) { let categoriesStr = info.get_categories(); if (!categoriesStr) @@ -149,6 +159,10 @@ var BaseAppView = GObject.registerClass({ this._canScroll = true; // limiting scrolling speed this._scrollTimeoutId = 0; this._scrollView.connect('scroll-event', this._onScroll.bind(this)); + this._scrollView.connect('motion-event', this._onMotion.bind(this)); + this._scrollView.connect('enter-event', this._onMotion.bind(this)); + this._scrollView.connect('leave-event', this._onLeave.bind(this)); + this._scrollView.connect('button-press-event', this._onButtonPress.bind(this)); this._scrollView.add_actor(this._grid); @@ -172,12 +186,44 @@ var BaseAppView = GObject.registerClass({ this._scrollView.event(event, false); }); + // Navigation indicators + this._nextPageIndicator = new St.Widget({ + style_class: 'page-navigation-hint next', + opacity: 0, + visible: false, + reactive: false, + x_expand: true, + y_expand: true, + x_align: Clutter.ActorAlign.END, + y_align: Clutter.ActorAlign.FILL, + }); + + this._prevPageIndicator = new St.Widget({ + style_class: 'page-navigation-hint previous', + opacity: 0, + visible: false, + reactive: false, + x_expand: true, + y_expand: true, + x_align: Clutter.ActorAlign.START, + y_align: Clutter.ActorAlign.FILL, + }); + + const scrollContainer = new St.Widget({ + layout_manager: new Clutter.BinLayout(), + clip_to_allocation: true, + y_expand: true, + }); + scrollContainer.add_child(this._prevPageIndicator); + scrollContainer.add_child(this._nextPageIndicator); + scrollContainer.add_child(this._scrollView); + this._box = new St.BoxLayout({ vertical: true, x_expand: true, y_expand: true, }); - this._box.add_child(this._scrollView); + this._box.add_child(scrollContainer); this._box.add_child(this._pageIndicators); // Swipe @@ -221,6 +267,8 @@ var BaseAppView = GObject.registerClass({ this._dragCancelledId = 0; this.connect('destroy', this._onDestroy.bind(this)); + + this._previewedPages = new Map(); } _onDestroy() { @@ -243,9 +291,21 @@ var BaseAppView = GObject.registerClass({ this._disconnectDnD(); } + _updateFadeForNavigation() { + const fadeMargin = new Clutter.Margin(); + fadeMargin.right = (this._pagesShown & SidePages.NEXT) !== 0 + ? -PAGE_PREVIEW_FADE_EFFECT_OFFSET : 0; + fadeMargin.left = (this._pagesShown & SidePages.PREVIOUS) !== 0 + ? -PAGE_PREVIEW_FADE_EFFECT_OFFSET : 0; + this._scrollView.update_fade_effect(fadeMargin); + } + _updateFade() { const { pagePadding } = this._grid.layout_manager; + if (this._pagesShown) + return; + if (pagePadding.top === 0 && pagePadding.right === 0 && pagePadding.bottom === 0 && @@ -327,6 +387,41 @@ var BaseAppView = GObject.registerClass({ return Clutter.EVENT_STOP; } + _pageForCoords(x, y) { + const rtl = this.get_text_direction() === Clutter.TextDirection.RTL; + const { allocation } = this._grid; + + const [success, pointerX] = this._scrollView.transform_stage_point(x, y); + if (!success) + return SidePages.NONE; + + if (pointerX < allocation.x1) + return rtl ? SidePages.NEXT : SidePages.PREVIOUS; + else if (pointerX > allocation.x2) + return rtl ? SidePages.PREVIOUS : SidePages.NEXT; + + return SidePages.NONE; + } + + _onMotion(actor, event) { + const page = this._pageForCoords(...event.get_coords()); + this._slideSidePages(page); + + return Clutter.EVENT_PROPAGATE; + } + + _onButtonPress(actor, event) { + const page = this._pageForCoords(...event.get_coords()); + if (page === SidePages.NEXT) + this.goToPage(this._grid.currentPage + 1); + else if (page === SidePages.PREVIOUS) + this.goToPage(this._grid.currentPage - 1); + } + + _onLeave() { + this._slideSidePages(SidePages.NONE); + } + _swipeBegin(tracker, monitor) { if (monitor !== Main.layoutManager.primaryIndex) return; @@ -351,6 +446,8 @@ var BaseAppView = GObject.registerClass({ const adjustment = this._adjustment; const value = endProgress * adjustment.page_size; + this._syncPageHints(endProgress); + adjustment.ease(value, { mode: Clutter.AnimationMode.EASE_OUT_CUBIC, duration, @@ -868,12 +965,37 @@ var BaseAppView = GObject.registerClass({ this._grid.ease(params); } + _syncPageHints(pageNumber, animate = true) { + const showingNextPage = this._pagesShown & SidePages.NEXT; + const showingPrevPage = this._pagesShown & SidePages.PREVIOUS; + const duration = animate ? PAGE_INDICATOR_FADE_TIME : 0; + + if (showingPrevPage) { + const opacity = pageNumber === 0 ? 0 : 255; + this._prevPageIndicator.visible = true; + this._prevPageIndicator.ease({ + opacity, + duration, + }); + } + + if (showingNextPage) { + const opacity = pageNumber === this._grid.nPages - 1 ? 0 : 255; + this._nextPageIndicator.visible = true; + this._nextPageIndicator.ease({ + opacity, + duration, + }); + } + } + goToPage(pageNumber, animate = true) { pageNumber = Math.clamp(pageNumber, 0, this._grid.nPages - 1); if (this._grid.currentPage === pageNumber) return; + this._syncPageHints(pageNumber, animate); this._grid.goToPage(pageNumber, animate); } @@ -894,6 +1016,121 @@ var BaseAppView = GObject.registerClass({ this._availWidth = availWidth; this._availHeight = availHeight; } + + _getPagePreviewAdjustment(page) { + const previewedPage = this._previewedPages.get(page); + return previewedPage?.adjustment; + } + + _syncClip() { + const nextPageAdjustment = this._getPagePreviewAdjustment(1); + const prevPageAdjustment = this._getPagePreviewAdjustment(-1); + this._grid.clip_to_view = + (!prevPageAdjustment || prevPageAdjustment.value === 0) && + (!nextPageAdjustment || nextPageAdjustment.value === 0); + } + + _setupPagePreview(page, state) { + if (this._previewedPages.has(page)) + return this._previewedPages.get(page).adjustment; + + const adjustment = new St.Adjustment({ + actor: this, + lower: 0, + upper: 1, + }); + + const indicator = page > 0 + ? this._nextPageIndicator : this._prevPageIndicator; + const rtl = this.get_text_direction() === Clutter.TextDirection.RTL; + + const notifyId = adjustment.connect('notify::value', () => { + let translationX = (1 - adjustment.value) * 100 * page; + translationX = rtl ? -translationX : translationX; + const nextPage = this._grid.currentPage + page; + if (nextPage >= 0 && + nextPage < this._grid.nPages - 1) { + const items = this._grid.getItemsAtPage(nextPage); + items.forEach(item => (item.translation_x = translationX)); + indicator.set({ + visible: true, + opacity: adjustment.value * 255, + translationX, + }); + } + this._syncClip(); + }); + + this._previewedPages.set(page, { + adjustment, + notifyId, + }); + + return adjustment; + } + + _teardownPagePreview(page) { + const previewedPage = this._previewedPages.get(page); + if (!previewedPage) + return; + + previewedPage.adjustment.value = 1; + previewedPage.adjustment.disconnect(previewedPage.notifyId); + this._previewedPages.delete(page); + } + + _slideSidePages(state) { + if (this._pagesShown === state) + return; + this._pagesShown = state; + const showingNextPage = state & SidePages.NEXT; + const showingPrevPage = state & SidePages.PREVIOUS; + let adjustment; + + adjustment = this._getPagePreviewAdjustment(1); + if (showingNextPage) { + adjustment = this._setupPagePreview(1, state); + + adjustment.ease(1, { + duration: PAGE_PREVIEW_ANIMATION_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + }); + this._updateFadeForNavigation(); + } else if (adjustment) { + adjustment.ease(0, { + duration: PAGE_PREVIEW_ANIMATION_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + onComplete: () => { + this._teardownPagePreview(1); + this._syncClip(); + this._nextPageIndicator.visible = false; + this._updateFadeForNavigation(); + }, + }); + } + + adjustment = this._getPagePreviewAdjustment(-1); + if (showingPrevPage) { + adjustment = this._setupPagePreview(-1, state); + + adjustment.ease(1, { + duration: PAGE_PREVIEW_ANIMATION_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + }); + this._updateFadeForNavigation(); + } else if (adjustment) { + adjustment.ease(0, { + duration: PAGE_PREVIEW_ANIMATION_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + onComplete: () => { + this._teardownPagePreview(-1); + this._syncClip(); + this._prevPageIndicator.visible = false; + this._updateFadeForNavigation(); + }, + }); + } + } }); var PageManager = GObject.registerClass({ -- GitLab From a00db66ffea152f4bb5b39cdede1f3f7e16b0f58 Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Wed, 3 Feb 2021 12:51:17 +0100 Subject: [PATCH 06/12] js/appDisplay: Implement side page previews while DnDing When DnDing an icon, we show both previous/next page, and optionally a "placeholder" actor to allow creating new pages. These sides on the scrollview are drop targets themselves, allowing to drop an app onto the next/prev page without further navigation. Still, preserve the checks to maybe switch to prev/next page without finishing the DnD operation, for finer grained operations. Part-of: --- .../gnome-shell-sass/widgets/_app-grid.scss | 4 ++ js/ui/appDisplay.js | 46 +++++++++++++++++-- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/data/theme/gnome-shell-sass/widgets/_app-grid.scss b/data/theme/gnome-shell-sass/widgets/_app-grid.scss index 5620ac26ff..d7d36b4e95 100644 --- a/data/theme/gnome-shell-sass/widgets/_app-grid.scss +++ b/data/theme/gnome-shell-sass/widgets/_app-grid.scss @@ -141,6 +141,10 @@ $app_grid_fg_color: #fff; background: rgba(255, 255, 255, 0.05); width: 88px; + &.dnd { + background: rgba(255, 255, 255, 0.1); + } + &.next { &:ltr { border-radius: 15px 0px 0px 15px; } &:rtl { border-radius: 0px 15px 15px 0px; } diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index ad791b26a8..8195427ada 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -56,6 +56,7 @@ var SidePages = { NONE: 0, PREVIOUS: 1 << 0, NEXT: 1 << 1, + DND: 1 << 2, }; function _getCategories(info) { @@ -155,6 +156,7 @@ var BaseAppView = GObject.registerClass({ enable_mouse_scrolling: false, }); this._scrollView.set_policy(St.PolicyType.EXTERNAL, St.PolicyType.NEVER); + this._scrollView._delegate = this; this._canScroll = true; // limiting scrolling speed this._scrollTimeoutId = 0; @@ -607,6 +609,7 @@ var BaseAppView = GObject.registerClass({ dragMotion: this._onDragMotion.bind(this), }; DND.addDragMonitor(this._dragMonitor); + this._slideSidePages(SidePages.PREVIOUS | SidePages.NEXT | SidePages.DND); } _onDragMotion(dragEvent) { @@ -615,6 +618,14 @@ var BaseAppView = GObject.registerClass({ const appIcon = dragEvent.source; + this._dropPage = this._pageForCoords(dragEvent.x, dragEvent.y); + if (this._dropPage && + this._dropPage === SidePages.PREVIOUS && + this._grid.currentPage === 0) { + delete this._dropPage; + return DND.DragMotionResult.NO_DROP; + } + // Handle the drag overshoot. When dragging to above the // icon grid, move to the page above; when dragging below, // move to the page below. @@ -633,12 +644,15 @@ var BaseAppView = GObject.registerClass({ } this._resetOvershoot(); + this._slideSidePages(SidePages.NONE); + delete this._dropPage; } _onDragCancelled() { // At this point, the positions aren't stored yet, thus _redisplay() // will move all items to their original positions this._redisplay(); + this._slideSidePages(SidePages.NONE); } _canAccept(source) { @@ -656,8 +670,16 @@ var BaseAppView = GObject.registerClass({ if (!this._canAccept(source)) return false; - // Dropped before the icon was moved - if (this._delayedMoveData) { + if (this._dropPage) { + const increment = this._dropPage === SidePages.NEXT ? 1 : -1; + const { currentPage, nPages } = this._grid; + const page = Math.min(currentPage + increment, nPages); + const position = page < nPages ? -1 : 0; + + this._moveItem(source, page, position); + this.goToPage(page); + } else if (this._delayedMoveData) { + // Dropped before the icon was moved const { page, position } = this._delayedMoveData; this._moveItem(source, page, position); @@ -1048,10 +1070,17 @@ var BaseAppView = GObject.registerClass({ let translationX = (1 - adjustment.value) * 100 * page; translationX = rtl ? -translationX : translationX; const nextPage = this._grid.currentPage + page; - if (nextPage >= 0 && - nextPage < this._grid.nPages - 1) { + const hasFollowingPage = nextPage >= 0 && + nextPage < this._grid.nPages; + + if (hasFollowingPage) { const items = this._grid.getItemsAtPage(nextPage); items.forEach(item => (item.translation_x = translationX)); + } + if (hasFollowingPage || + (page > 0 && + this._grid.layout_manager.allow_incomplete_pages && + (state & SidePages.DND) !== 0)) { indicator.set({ visible: true, opacity: adjustment.value * 255, @@ -1085,8 +1114,17 @@ var BaseAppView = GObject.registerClass({ this._pagesShown = state; const showingNextPage = state & SidePages.NEXT; const showingPrevPage = state & SidePages.PREVIOUS; + const dnd = state & SidePages.DND; let adjustment; + if (dnd) { + this._nextPageIndicator.add_style_class_name('dnd'); + this._prevPageIndicator.add_style_class_name('dnd'); + } else { + this._nextPageIndicator.remove_style_class_name('dnd'); + this._prevPageIndicator.remove_style_class_name('dnd'); + } + adjustment = this._getPagePreviewAdjustment(1); if (showingNextPage) { adjustment = this._setupPagePreview(1, state); -- GitLab From c15dce242e01dae0e826ce1fa8e62990c1fba585 Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Fri, 12 Feb 2021 20:48:37 +0100 Subject: [PATCH 07/12] theme: Improve look of FolderDialog wrt page previews Remove padding on left and right of the contained widgetry, so there's no seams animating things from the border. Also, remove the padding between IconGrid pages, so the nest/prev pages are visible given the dialog width. Part-of: --- data/theme/gnome-shell-sass/widgets/_app-grid.scss | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data/theme/gnome-shell-sass/widgets/_app-grid.scss b/data/theme/gnome-shell-sass/widgets/_app-grid.scss index d7d36b4e95..f19c126b7e 100644 --- a/data/theme/gnome-shell-sass/widgets/_app-grid.scss +++ b/data/theme/gnome-shell-sass/widgets/_app-grid.scss @@ -42,7 +42,7 @@ $app_grid_fg_color: #fff; .app-folder-dialog { //style like the dash border-radius: $modal_radius * 1.5; background-color: $dash_background_color; - padding: 12px; + padding: 12px 0px 12px 0px; & .folder-name-container { padding: 24px 36px 0; @@ -76,6 +76,8 @@ $app_grid_fg_color: #fff; column-spacing: $base_spacing * 5; page-padding-top: 0; page-padding-bottom: 0; + page-padding-left: 0; + page-padding-right: 0; } & .page-indicators { -- GitLab From ffe11e056095479a277bcd5442b113a09c942c6e Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Sat, 13 Feb 2021 19:54:14 +0100 Subject: [PATCH 08/12] appDisplay: Add carousel arrows to app grid These only show during navigation (not DnD), along with the previewed page. Part-of: --- data/gnome-shell-theme.gresource.xml | 2 + .../theme/carousel-arrow-back-24-symbolic.svg | 36 ++++++++++++ .../theme/carousel-arrow-next-24-symbolic.svg | 36 ++++++++++++ .../gnome-shell-sass/widgets/_app-grid.scss | 6 ++ js/ui/appDisplay.js | 57 +++++++++++++++++++ 5 files changed, 137 insertions(+) create mode 100644 data/theme/carousel-arrow-back-24-symbolic.svg create mode 100644 data/theme/carousel-arrow-next-24-symbolic.svg diff --git a/data/gnome-shell-theme.gresource.xml b/data/gnome-shell-theme.gresource.xml index 038c87cf07..4eb185c537 100644 --- a/data/gnome-shell-theme.gresource.xml +++ b/data/gnome-shell-theme.gresource.xml @@ -2,6 +2,8 @@ calendar-today.svg + carousel-arrow-next-24-symbolic.svg + carousel-arrow-back-24-symbolic.svg checkbox-focused.svg checkbox-off-focused.svg checkbox-off.svg diff --git a/data/theme/carousel-arrow-back-24-symbolic.svg b/data/theme/carousel-arrow-back-24-symbolic.svg new file mode 100644 index 0000000000..9848930922 --- /dev/null +++ b/data/theme/carousel-arrow-back-24-symbolic.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + + diff --git a/data/theme/carousel-arrow-next-24-symbolic.svg b/data/theme/carousel-arrow-next-24-symbolic.svg new file mode 100644 index 0000000000..7d6356f239 --- /dev/null +++ b/data/theme/carousel-arrow-next-24-symbolic.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + + diff --git a/data/theme/gnome-shell-sass/widgets/_app-grid.scss b/data/theme/gnome-shell-sass/widgets/_app-grid.scss index f19c126b7e..f58dad06a7 100644 --- a/data/theme/gnome-shell-sass/widgets/_app-grid.scss +++ b/data/theme/gnome-shell-sass/widgets/_app-grid.scss @@ -156,3 +156,9 @@ $app_grid_fg_color: #fff; &:rtl { border-radius: 15px 0px 0px 15px; } } } + +.page-navigation-arrow { + margin: 6px; + width: 24px; + height: 24px; +} diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index 8195427ada..b0cc5d0d88 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -211,6 +211,31 @@ var BaseAppView = GObject.registerClass({ y_align: Clutter.ActorAlign.FILL, }); + // Next/prev page arrows + const rtl = this.get_text_direction() === Clutter.TextDirection.RTL; + this._nextPageArrow = new St.Icon({ + style_class: 'page-navigation-arrow', + icon_name: rtl + ? 'carousel-arrow-back-24-symbolic' + : 'carousel-arrow-next-24-symbolic', + opacity: 0, + reactive: false, + visible: false, + x_expand: true, + x_align: Clutter.ActorAlign.END, + }); + this._prevPageArrow = new St.Icon({ + style_class: 'page-navigation-arrow', + icon_name: rtl + ? 'carousel-arrow-next-24-symbolic' + : 'carousel-arrow-back-24-symbolic', + opacity: 0, + reactive: false, + visible: false, + x_expand: true, + x_align: Clutter.ActorAlign.START, + }); + const scrollContainer = new St.Widget({ layout_manager: new Clutter.BinLayout(), clip_to_allocation: true, @@ -219,6 +244,8 @@ var BaseAppView = GObject.registerClass({ scrollContainer.add_child(this._prevPageIndicator); scrollContainer.add_child(this._nextPageIndicator); scrollContainer.add_child(this._scrollView); + scrollContainer.add_child(this._nextPageArrow); + scrollContainer.add_child(this._prevPageArrow); this._box = new St.BoxLayout({ vertical: true, @@ -990,6 +1017,7 @@ var BaseAppView = GObject.registerClass({ _syncPageHints(pageNumber, animate = true) { const showingNextPage = this._pagesShown & SidePages.NEXT; const showingPrevPage = this._pagesShown & SidePages.PREVIOUS; + const dnd = this._pagesShown & SidePages.DND; const duration = animate ? PAGE_INDICATOR_FADE_TIME : 0; if (showingPrevPage) { @@ -999,6 +1027,14 @@ var BaseAppView = GObject.registerClass({ opacity, duration, }); + + if (!dnd) { + this._prevPageArrow.visible = true; + this._prevPageArrow.ease({ + opacity, + duration, + }); + } } if (showingNextPage) { @@ -1008,6 +1044,14 @@ var BaseAppView = GObject.registerClass({ opacity, duration, }); + + if (!dnd) { + this._nextPageArrow.visible = true; + this._nextPageArrow.ease({ + opacity, + duration, + }); + } } } @@ -1076,6 +1120,17 @@ var BaseAppView = GObject.registerClass({ if (hasFollowingPage) { const items = this._grid.getItemsAtPage(nextPage); items.forEach(item => (item.translation_x = translationX)); + + if (!(state & SidePages.DND)) { + const pageArrow = page > 0 + ? this._nextPageArrow + : this._prevPageArrow; + pageArrow.set({ + visible: true, + opacity: adjustment.value * 255, + translationX, + }); + } } if (hasFollowingPage || (page > 0 && @@ -1141,6 +1196,7 @@ var BaseAppView = GObject.registerClass({ onComplete: () => { this._teardownPagePreview(1); this._syncClip(); + this._nextPageArrow.visible = false; this._nextPageIndicator.visible = false; this._updateFadeForNavigation(); }, @@ -1163,6 +1219,7 @@ var BaseAppView = GObject.registerClass({ onComplete: () => { this._teardownPagePreview(-1); this._syncClip(); + this._prevPageArrow.visible = false; this._prevPageIndicator.visible = false; this._updateFadeForNavigation(); }, -- GitLab From 9e5b357b0b6b12d84be338be2944f01ad1f7fe51 Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Fri, 19 Feb 2021 16:45:57 +0100 Subject: [PATCH 09/12] appDisplay: Adapt to available extra space showing icon grids Depending on the available horizontal space, we may want to manipulate the icon grid and scroll view spacing to result in an optimal layout that has space left to preview prev/next pages. The main change here is that, when adapting to the available size, the space given to a page does not necessarily match the available space, as we need to be able to show more than one page at a time. With this decoupling of available and page sizes in place, we now know how much space there is available in order to extend the padding between pages, or the fade effect applied to the previewed pages. Underneath, we rely a bit less on hardcoded CSS paddings, and a bit more on the StScrollView::content-padding property. All put together, gives us proper space management from ultra-wide displays, to display ratios that are close to the optimal grid ratio. Part-of: --- .../gnome-shell-sass/widgets/_app-grid.scss | 26 ++--- js/ui/appDisplay.js | 95 ++++++++++++++++--- js/ui/iconGrid.js | 3 +- 3 files changed, 99 insertions(+), 25 deletions(-) diff --git a/data/theme/gnome-shell-sass/widgets/_app-grid.scss b/data/theme/gnome-shell-sass/widgets/_app-grid.scss index f58dad06a7..4b6496abc1 100644 --- a/data/theme/gnome-shell-sass/widgets/_app-grid.scss +++ b/data/theme/gnome-shell-sass/widgets/_app-grid.scss @@ -125,10 +125,8 @@ $app_grid_fg_color: #fff; } } -// Some hacks I don't even know .apps-scroll-view { - // horizontal padding to make sure scrollbars or dash don't overlap content - padding: 0 88px; + padding: 0; } // shutdown and other actions in the grid @@ -140,20 +138,26 @@ $app_grid_fg_color: #fff; } .page-navigation-hint { - background: rgba(255, 255, 255, 0.05); - width: 88px; + width: 300px; &.dnd { background: rgba(255, 255, 255, 0.1); } - &.next { - &:ltr { border-radius: 15px 0px 0px 15px; } - &:rtl { border-radius: 0px 15px 15px 0px; } + &.next:ltr, + &.previous:rtl { + background-gradient-start: rgba(255, 255, 255, 0.05); + background-gradient-end: transparent; + background-gradient-direction: horizontal; + border-radius: 15px 0px 0px 15px; } - &.previous { - &:ltr { border-radius: 0px 15px 15px 0px; } - &:rtl { border-radius: 15px 0px 0px 15px; } + + &.previous:ltr, + &.next:rtl { + background-gradient-start: transparent; + background-gradient-end: rgba(255, 255, 255, 0.05); + background-gradient-direction: horizontal; + border-radius: 0px 15px 15px 0px; } } diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index b0cc5d0d88..c0517a5f24 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -39,8 +39,11 @@ var APP_ICON_TITLE_COLLAPSE_TIME = 100; const FOLDER_DIALOG_ANIMATION_TIME = 200; const PAGE_PREVIEW_ANIMATION_TIME = 150; -const PAGE_PREVIEW_FADE_EFFECT_OFFSET = 160; +const PAGE_PREVIEW_ANIMATION_START_OFFSET = 100; +const PAGE_PREVIEW_FADE_EFFECT_MAX_OFFSET = 300; +const PAGE_PREVIEW_MAX_ARROW_OFFSET = 80; const PAGE_INDICATOR_FADE_TIME = 200; +const MAX_PAGE_PADDING = 200; const OVERSHOOT_THRESHOLD = 20; const OVERSHOOT_TIMEOUT = 1000; @@ -322,10 +325,22 @@ var BaseAppView = GObject.registerClass({ _updateFadeForNavigation() { const fadeMargin = new Clutter.Margin(); - fadeMargin.right = (this._pagesShown & SidePages.NEXT) !== 0 - ? -PAGE_PREVIEW_FADE_EFFECT_OFFSET : 0; - fadeMargin.left = (this._pagesShown & SidePages.PREVIOUS) !== 0 - ? -PAGE_PREVIEW_FADE_EFFECT_OFFSET : 0; + const rtl = this.get_text_direction() === Clutter.TextDirection.RTL; + const showingNextPage = this._pagesShown & SidePages.NEXT; + const showingPrevPage = this._pagesShown & SidePages.PREVIOUS; + + if ((showingNextPage && !rtl) || (showingPrevPage && rtl)) { + fadeMargin.right = Math.max( + -PAGE_PREVIEW_FADE_EFFECT_MAX_OFFSET, + -(this._availWidth - this._grid.layout_manager.pageWidth) / 2); + } + + if ((showingPrevPage && !rtl) || (showingNextPage && rtl)) { + fadeMargin.left = Math.max( + -PAGE_PREVIEW_FADE_EFFECT_MAX_OFFSET, + -(this._availWidth - this._grid.layout_manager.pageWidth) / 2); + } + this._scrollView.update_fade_effect(fadeMargin); } @@ -1077,10 +1092,61 @@ var BaseAppView = GObject.registerClass({ const availWidth = box.get_width(); const availHeight = box.get_height(); - this._grid.adaptToSize(availWidth, availHeight); + const gridRatio = this._grid.layout_manager.columnsPerPage / + this._grid.layout_manager.rowsPerPage; + const spaceRatio = availWidth / availHeight; + let pageWidth, pageHeight; + + if (spaceRatio > gridRatio * 1.1) { + // Enough room for some preview + pageHeight = availHeight; + pageWidth = availHeight * gridRatio; + + if (spaceRatio > gridRatio * 1.5) { + // Ultra-wide layout, give some extra space for + // the page area, but up to an extent. + const extraPageSpace = Math.min( + (availWidth - pageWidth) / 2, MAX_PAGE_PADDING); + pageWidth += extraPageSpace; + this._grid.layout_manager.pagePadding.left = extraPageSpace / 2; + this._grid.layout_manager.pagePadding.right = extraPageSpace / 2; + } + } else { + // Not enough room, needs to shrink horizontally + pageWidth = availWidth * 0.8; + pageHeight = availHeight; + this._grid.layout_manager.pagePadding.left = availWidth * 0.02; + this._grid.layout_manager.pagePadding.right = availWidth * 0.02; + } + + this._grid.adaptToSize(pageWidth, pageHeight); + + const horizontalPadding = (availWidth - this._grid.layout_manager.pageWidth) / 2; + const verticalPadding = (availHeight - this._grid.layout_manager.pageHeight) / 2; + + this._scrollView.content_padding = new Clutter.Margin({ + left: horizontalPadding, + right: horizontalPadding, + top: verticalPadding, + bottom: verticalPadding, + }); this._availWidth = availWidth; this._availHeight = availHeight; + + this._pageIndicatorOffset = horizontalPadding; + this._pageArrowOffset = Math.max( + horizontalPadding - PAGE_PREVIEW_MAX_ARROW_OFFSET, 0); + } + + _getIndicatorOffset(page, progress, baseOffset) { + const rtl = this.get_text_direction() === Clutter.TextDirection.RTL; + const translationX = + (1 - progress) * PAGE_PREVIEW_ANIMATION_START_OFFSET; + + page = rtl ? -page : page; + + return (translationX - baseOffset) * page; } _getPagePreviewAdjustment(page) { @@ -1108,18 +1174,18 @@ var BaseAppView = GObject.registerClass({ const indicator = page > 0 ? this._nextPageIndicator : this._prevPageIndicator; - const rtl = this.get_text_direction() === Clutter.TextDirection.RTL; const notifyId = adjustment.connect('notify::value', () => { - let translationX = (1 - adjustment.value) * 100 * page; - translationX = rtl ? -translationX : translationX; const nextPage = this._grid.currentPage + page; const hasFollowingPage = nextPage >= 0 && nextPage < this._grid.nPages; if (hasFollowingPage) { const items = this._grid.getItemsAtPage(nextPage); - items.forEach(item => (item.translation_x = translationX)); + items.forEach(item => { + item.translation_x = + this._getIndicatorOffset(page, adjustment.value, 0); + }); if (!(state & SidePages.DND)) { const pageArrow = page > 0 @@ -1128,7 +1194,9 @@ var BaseAppView = GObject.registerClass({ pageArrow.set({ visible: true, opacity: adjustment.value * 255, - translationX, + translation_x: this._getIndicatorOffset( + page, adjustment.value, + this._pageArrowOffset), }); } } @@ -1139,7 +1207,9 @@ var BaseAppView = GObject.registerClass({ indicator.set({ visible: true, opacity: adjustment.value * 255, - translationX, + translation_x: this._getIndicatorOffset( + page, adjustment.value, + this._pageIndicatorOffset - indicator.width), }); } this._syncClip(); @@ -1382,6 +1452,7 @@ class AppDisplay extends BaseAppView { const [, indicatorHeight] = this._pageIndicators.get_preferred_height(-1); height -= indicatorHeight; + this._grid.findBestModeForSize(width, height); super.adaptToSize(width, height); } diff --git a/js/ui/iconGrid.js b/js/ui/iconGrid.js index e8ac2b07ed..2c71c29c10 100644 --- a/js/ui/iconGrid.js +++ b/js/ui/iconGrid.js @@ -1223,7 +1223,7 @@ var IconGrid = GObject.registerClass({ } } - _findBestModeForSize(width, height) { + findBestModeForSize(width, height) { const { pagePadding } = this.layout_manager; width -= pagePadding.left + pagePadding.right; height -= pagePadding.top + pagePadding.bottom; @@ -1446,7 +1446,6 @@ var IconGrid = GObject.registerClass({ } adaptToSize(width, height) { - this._findBestModeForSize(width, height); this.layout_manager.adaptToSize(width, height); } -- GitLab From ec223f31d98bd6e6026ab3ef7f3ad5a7e5ef0ca2 Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Tue, 23 Feb 2021 17:44:23 +0100 Subject: [PATCH 10/12] appDisplay: Slide page hints along page switching When clicking on the page hints, the hint rectangles being visible in place and not moving together with the page is a bit too distracting. Since the page hints are not part of the iconGrid hierarchy and we have just 2 general ones for prev/next page (i.e. no page associated), do this sliding via some smoke and mirrors: We don't slide the page hints, but a parent container for both of them, and we also control opacity so that the container is fully transparent mid-page. At the point it is transparent, the container can be snapped to the other side of the page, and faded back in as it slides together with it, so it always looks like it goes away and comes from the right sides. Part-of: --- js/ui/appDisplay.js | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index c0517a5f24..94d1ef24e0 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -175,7 +175,22 @@ var BaseAppView = GObject.registerClass({ this._adjustment = scroll.adjustment; this._adjustment.connect('notify::value', adj => { this._updateFade(); - this._pageIndicators.setCurrentPosition(adj.value / adj.page_size); + const value = adj.value / adj.page_size; + this._pageIndicators.setCurrentPosition(value); + + const distanceToPage = Math.abs(Math.round(value) - value); + if (distanceToPage < 0.001) { + this._hintContainer.opacity = 255; + this._hintContainer.translationX = 0; + } else { + this._hintContainer.remove_transition('opacity'); + let opacity = Math.clamp( + 255 * (1 - (distanceToPage * 2)), + 0, 255); + + this._hintContainer.translationX = (Math.round(value) - value) * adj.page_size; + this._hintContainer.opacity = opacity; + } }); // Page Indicators @@ -239,13 +254,20 @@ var BaseAppView = GObject.registerClass({ x_align: Clutter.ActorAlign.START, }); + this._hintContainer = new St.Widget({ + layout_manager: new Clutter.BinLayout(), + x_expand: true, + y_expand: true, + }); + this._hintContainer.add_child(this._prevPageIndicator); + this._hintContainer.add_child(this._nextPageIndicator); + const scrollContainer = new St.Widget({ layout_manager: new Clutter.BinLayout(), clip_to_allocation: true, y_expand: true, }); - scrollContainer.add_child(this._prevPageIndicator); - scrollContainer.add_child(this._nextPageIndicator); + scrollContainer.add_child(this._hintContainer); scrollContainer.add_child(this._scrollView); scrollContainer.add_child(this._nextPageArrow); scrollContainer.add_child(this._prevPageArrow); -- GitLab From 9b24d9c4c0c08f258636d6d3388ea573336fcb9d Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Tue, 23 Feb 2021 23:39:29 +0100 Subject: [PATCH 11/12] appDisplay: Don't reset adjustment on ::pages-changed Let the goToPage call afterwards to take precedence, instead of resetting the adjustment (thus the view) on the side. This resulted in strange state when the last page contains a single icon, and it is dragged. The last page being emptied triggers a pages-changed signal, which half resets the view to the first page while DnD is ongoing. Letting goToPage do its business means we neatly clamp to the closest page to currentPage, the last page in that case. Part-of: --- js/ui/appDisplay.js | 1 - 1 file changed, 1 deletion(-) diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index 94d1ef24e0..0c2b975987 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -143,7 +143,6 @@ var BaseAppView = GObject.registerClass({ // Standard hack for ClutterBinLayout this._grid.x_expand = true; this._grid.connect('pages-changed', () => { - this._adjustment.value = 0; this.goToPage(this._grid.currentPage); this._pageIndicators.setNPages(this._grid.nPages); this._pageIndicators.setCurrentPosition(this._grid.currentPage); -- GitLab From 40b67a140a49843ddd794b09ad6a2104b61643bd Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Sat, 27 Feb 2021 20:23:19 +0100 Subject: [PATCH 12/12] st/scrollview: Apply correct fade_edges_left/right effect uniforms in RTL When the setting is false, we apply anyway depending on the adjustment value. Look for the right value corresponding to that side as per the locale. Part-of: --- src/st/st-scroll-view-fade.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/st/st-scroll-view-fade.c b/src/st/st-scroll-view-fade.c index 3ca7313372..b387e384a5 100644 --- a/src/st/st-scroll-view-fade.c +++ b/src/st/st-scroll-view-fade.c @@ -94,7 +94,7 @@ st_scroll_view_fade_paint_target (ClutterOffscreenEffect *effect, gdouble value, lower, upper, page_size; ClutterActor *vscroll = st_scroll_view_get_vscroll_bar (ST_SCROLL_VIEW (self->actor)); ClutterActor *hscroll = st_scroll_view_get_hscroll_bar (ST_SCROLL_VIEW (self->actor)); - gboolean h_scroll_visible, v_scroll_visible; + gboolean h_scroll_visible, v_scroll_visible, rtl; ClutterActorBox allocation, content_box, paint_box; ClutterMargin *content_padding; @@ -158,8 +158,15 @@ st_scroll_view_fade_paint_target (ClutterOffscreenEffect *effect, st_adjustment_get_values (self->hadjustment, &value, &lower, &upper, NULL, NULL, &page_size); value = (value - lower) / (upper - page_size - lower); - clutter_shader_effect_set_uniform (shader, "fade_edges_left", G_TYPE_INT, 1, self->fade_edges ? value >= 0.0 : value > 0.0); - clutter_shader_effect_set_uniform (shader, "fade_edges_right", G_TYPE_INT, 1, self->fade_edges ? value <= 1.0 : value < 1.0); + rtl = clutter_actor_get_text_direction (self->actor) == CLUTTER_TEXT_DIRECTION_RTL; + clutter_shader_effect_set_uniform (shader, "fade_edges_left", G_TYPE_INT, 1, + self->fade_edges ? + value >= 0.0 : + (rtl ? value < 1.0 : value > 0.0)); + clutter_shader_effect_set_uniform (shader, "fade_edges_right", G_TYPE_INT, 1, + self->fade_edges ? + value <= 1.0 : + (rtl ? value > 0.0 : value < 1.0)); clutter_shader_effect_set_uniform (shader, "fade_offset_top", G_TYPE_FLOAT, 1, ABS (self->fade_margins.top)); clutter_shader_effect_set_uniform (shader, "fade_offset_bottom", G_TYPE_FLOAT, 1, ABS (self->fade_margins.bottom)); -- GitLab