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




commit 8ec49ca2891bbac582275db9efc33703e83416a8
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 | 153 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 153 insertions(+)
---
diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index 628f235ccb..a9433fddb5 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);
 
@@ -242,9 +255,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 +351,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 +956,98 @@ var BaseAppView = GObject.registerClass({
         this._availWidth = availWidth;
         this._availHeight = availHeight;
     }
+
+    _syncClip() {
+        this._grid.clip_to_view =
+            (!this._prevPageAdjustment || this._prevPageAdjustment.value === 0) &&
+            (!this._nextPageAdjustment || this._nextPageAdjustment.value === 0);
+    }
+
+    _slideSidePages(pages) {
+        if (this._pagesShown === pages)
+            return;
+        this._pagesShown = pages;
+        const rtl = this.get_text_direction() === Clutter.TextDirection.RTL;
+        const showingNextPage = pages & SidePages.NEXT;
+        const showingPrevPage = pages & SidePages.PREVIOUS;
+
+        if (showingNextPage) {
+            if (!this._nextPageAdjustment) {
+                this._nextPageAdjustment = new St.Adjustment({
+                    actor: this,
+                    lower: 0,
+                    upper: 1,
+                });
+
+                this._nextPageAdjustmentId = this._nextPageAdjustment.connect('notify::value', () => {
+                    let translationX = (1 - this._nextPageAdjustment.value) * 100;
+                    translationX = rtl ? -translationX : translationX;
+                    if (this._grid.currentPage < this._grid.nPages - 1) {
+                        const items = this._grid.layout_manager.getItemsAtPage(this._grid.currentPage + 1);
+                        items.forEach(item => (item.translation_x = translationX));
+                    }
+                    this._syncClip();
+                });
+            }
+
+            this._nextPageAdjustment.ease(1, {
+                duration: PAGE_PREVIEW_ANIMATION_TIME,
+                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
+            });
+            this._updateFadeForNavigation();
+        } else if (!showingNextPage && this._nextPageAdjustment) {
+            this._nextPageAdjustment.ease(0, {
+                duration: PAGE_PREVIEW_ANIMATION_TIME,
+                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
+                onComplete: () => {
+                    this._nextPageAdjustment.value = 1;
+                    this._nextPageAdjustment.disconnect(this._nextPageAdjustmentId);
+                    delete this._nextPageAdjustment;
+                    this._emptyPageIndicator.visible = false;
+                    this._syncClip();
+                    this._updateFadeForNavigation();
+                },
+            });
+        }
+
+        if (showingPrevPage) {
+            if (!this._prevPageAdjustment) {
+                this._prevPageAdjustment = new St.Adjustment({
+                    actor: this,
+                    lower: 0,
+                    upper: 1,
+                });
+
+                this._prevPageAdjustmentId = this._prevPageAdjustment.connect('notify::value', () => {
+                    let translationX = (1 - this._prevPageAdjustment.value) * -100;
+                    translationX = rtl ? -translationX : translationX;
+                    if (this._grid.currentPage > 0) {
+                        const items = this._grid.layout_manager.getItemsAtPage(this._grid.currentPage - 1);
+                        items.forEach(item => (item.translation_x = translationX));
+                    }
+                    this._syncClip();
+                });
+            }
+
+            this._prevPageAdjustment.ease(1, {
+                duration: PAGE_PREVIEW_ANIMATION_TIME,
+                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
+            });
+            this._updateFadeForNavigation();
+        } else if (!showingPrevPage && this._prevPageAdjustment) {
+            this._prevPageAdjustment.ease(0, {
+                duration: PAGE_PREVIEW_ANIMATION_TIME,
+                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
+                onComplete: () => {
+                    this._prevPageAdjustment.value = 1;
+                    this._prevPageAdjustment.disconnect(this._prevPageAdjustmentId);
+                    delete this._prevPageAdjustment;
+                    this._syncClip();
+                    this._updateFadeForNavigation();
+                },
+            });
+        }
+    }
 });
 
 var PageManager = GObject.registerClass({


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