[gnome-shell/wip/carlosg/appgrid-navigation: 17/24] js/appDisplay: Implement navigation of pages by hovering/clicking edges
- From: Marge Bot <marge-bot src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-shell/wip/carlosg/appgrid-navigation: 17/24] js/appDisplay: Implement navigation of pages by hovering/clicking edges
- Date: Tue, 2 Mar 2021 22:03:00 +0000 (UTC)
commit d75ed55ed8de8c046ea8c491bd6472bd51813934
Author: Carlos Garnacho <carlosg gnome org>
Date: Wed Feb 3 12:46:07 2021 +0100
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: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1630>
data/theme/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({
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]