[gnome-shell/wip/carlosg/appgrid-navigation: 11/15] js/appDisplay: Implement navigation of pages by hovering/clicking edges




commit 8a08e37e941ef47ad52aa5fc43e0da33f7265a36
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.

 js/ui/appDisplay.js | 174 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 174 insertions(+)
---
diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index 628f235ccb..d741016cce 100644
--- a/js/ui/appDisplay.js
+++ b/js/ui/appDisplay.js
@@ -38,6 +38,9 @@ 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 OVERSHOOT_THRESHOLD = 20;
 const OVERSHOOT_TIMEOUT = 1000;
 
@@ -48,6 +51,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)
@@ -148,6 +157,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);
 
@@ -220,6 +233,8 @@ var BaseAppView = GObject.registerClass({
         this._dragCancelledId = 0;
 
         this.connect('destroy', this._onDestroy.bind(this));
+
+        this._previewedPages = new Map();
     }
 
     _onDestroy() {
@@ -242,9 +257,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 &&
@@ -326,6 +353,42 @@ var BaseAppView = GObject.registerClass({
         return Clutter.EVENT_STOP;
     }
 
+    _pageForCoords(x, y) {
+        const rtl = this.get_text_direction() === Clutter.TextDirection.RTL;
+        const alloc = this._grid.get_allocation_box();
+
+        const [success, pointerX] = this._scrollView.transform_stage_point(x, y);
+        if (!success)
+            return SidePages.NONE;
+
+        if (pointerX < alloc.x1)
+            return rtl ? SidePages.NEXT : SidePages.PREVIOUS;
+        else if (pointerX > alloc.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._grid.currentPage < this._grid.nPages - 1)
+            this._grid.currentPage++;
+        else if (page === SidePages.PREVIOUS && this._grid.currentPage > 0)
+            this._grid.currentPage--;
+    }
+
+    _onLeave() {
+        this._slideSidePages(SidePages.NONE);
+    }
+
     _swipeBegin(tracker, monitor) {
         if (monitor !== Main.layoutManager.primaryIndex)
             return;
@@ -895,6 +958,117 @@ var BaseAppView = GObject.registerClass({
         this._availWidth = availWidth;
         this._availHeight = availHeight;
     }
+
+    _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) {
+        const multiplier = page;
+        if (this._previewedPages.has(page))
+            return;
+
+        const adjustment = new St.Adjustment({
+            actor: this,
+            lower: 0,
+            upper: 1,
+        });
+
+        const rtl = this.get_text_direction() === Clutter.TextDirection.RTL;
+        const notifyId = adjustment.connect('notify::value', () => {
+            let translationX = (1 - adjustment.value) * 100 * multiplier;
+            translationX = rtl ? -translationX : translationX;
+            const nextPage = this._grid.currentPage + page;
+            if (nextPage >= 0 &&
+                nextPage < this._grid.nPages - 1) {
+                const items = this._grid.layout_manager.getItemsAtPage(nextPage);
+                items.forEach(item => (item.translation_x = translationX));
+            }
+            this._syncClip();
+        });
+
+        this._previewedPages.set(page, {
+            adjustment,
+            notifyId,
+        });
+    }
+
+    _teardownPagePreview(page) {
+        const previewedPage = this._previewedPages.get(page);
+        if (!previewedPage)
+            return;
+
+        previewedPage.adjustment.value = 1;
+        previewedPage.adjustment.disconnect(previewedPage.notifyId);
+        this._previewedPages.delete(page);
+    }
+
+    _getPagePreviewAdjustment(page) {
+        const previewedPage = this._previewedPages.get(page);
+        return previewedPage?.adjustment;
+    }
+
+    _slideSidePages(state) {
+        if (this._pagesShown === state)
+            return;
+        this._pagesShown = state;
+        const showingNextPage = state & SidePages.NEXT;
+        const showingPrevPage = state & SidePages.PREVIOUS;
+
+        if (showingNextPage) {
+            this._setupPagePreview(1, state);
+            const adjustment = this._getPagePreviewAdjustment(1);
+
+            adjustment.ease(1, {
+                duration: PAGE_PREVIEW_ANIMATION_TIME,
+                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
+            });
+            this._updateFadeForNavigation();
+        } else {
+            const adjustment = this._getPagePreviewAdjustment(1);
+
+            if (adjustment) {
+                adjustment.ease(0, {
+                    duration: PAGE_PREVIEW_ANIMATION_TIME,
+                    mode: Clutter.AnimationMode.EASE_OUT_QUAD,
+                    onComplete: () => {
+                        this._teardownPagePreview(1);
+                        this._syncClip();
+                        this._updateFadeForNavigation();
+                    },
+                });
+            }
+        }
+
+        if (showingPrevPage) {
+            this._setupPagePreview(-1, state);
+            const adjustment = this._getPagePreviewAdjustment(-1);
+
+            adjustment.ease(1, {
+                duration: PAGE_PREVIEW_ANIMATION_TIME,
+                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
+            });
+            this._updateFadeForNavigation();
+        } else {
+            const adjustment = this._getPagePreviewAdjustment(-1);
+
+            if (adjustment) {
+                adjustment.ease(0, {
+                    duration: PAGE_PREVIEW_ANIMATION_TIME,
+                    mode: Clutter.AnimationMode.EASE_OUT_QUAD,
+                    onComplete: () => {
+                        this._teardownPagePreview(-1);
+                        this._syncClip();
+                        this._updateFadeForNavigation();
+                    },
+                });
+            }
+        }
+    }
 });
 
 var PageManager = GObject.registerClass({


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]