[gnome-shell/gbsneto/custom-icon-positions: 20/35] iconGrid: Add item nudge API



commit 6470040bfac6af0d5eea27a6fce1dcd86128a09e
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date:   Mon May 25 15:58:39 2020 -0300

    iconGrid: Add item nudge API
    
    Add a new item nudging API, and the acompanying drop target
    API. The bulk of it is implemented by IconGridLayout, since
    it's this layout manager that knows where each icon is placed
    at.
    
    https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1284

 js/ui/appDisplay.js |  85 ++++++++++++++++++++++++++
 js/ui/iconGrid.js   | 167 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 252 insertions(+)
---
diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index 05f580fdfd..da0af64649 100644
--- a/js/ui/appDisplay.js
+++ b/js/ui/appDisplay.js
@@ -180,6 +180,8 @@ var BaseAppView = GObject.registerClass({
 
         this._availWidth = 0;
         this._availHeight = 0;
+        this._nudgedItem = null;
+        this._nudgedLocation = -1;
 
         this._items = new Map();
         this._orderedItems = [];
@@ -350,6 +352,47 @@ var BaseAppView = GObject.registerClass({
         this._grid.opacity = 255;
     }
 
+    _getRealDropPosition(item, dragLocation) {
+        const itemsPerPage = this._grid.itemsPerPage;
+        let [page, position] = this._grid.getItemPosition(item);
+
+        if (dragLocation === IconGrid.DragLocation.END_EDGE)
+            position += 1;
+
+        if (position >= itemsPerPage) {
+            position %= itemsPerPage;
+            page++;
+        } else if (position < 0) {
+            page--;
+            if (page >= 0) {
+                const pageItems =
+                    this._grid.getItemsAtPage(page).filter(c => c.visible);
+                position = pageItems.length - 1;
+            } else {
+                position = 0;
+                page = 0;
+            }
+        }
+
+        return [page, position];
+    }
+
+    _nudgedItemReallyChanged(item, dragLocation) {
+        if (!this._nudgedItem)
+            return true;
+
+        if (dragLocation === IconGrid.DragLocation.ON_ICON ||
+            this._nudgedLocation === IconGrid.DragLocation.ON_ICON)
+            return true;
+
+        const [realPage, realPosition] =
+            this._getRealDropPosition(item, dragLocation);
+        const [oldPage, oldPosition] =
+            this._getRealDropPosition(this._nudgedItem, this._nudgedLocation);
+
+        return realPage !== oldPage || realPosition !== oldPosition;
+    }
+
     animate(animationDirection, onComplete) {
         if (onComplete) {
             let animationDoneId = this._grid.connect('animation-done', () => {
@@ -435,6 +478,48 @@ var BaseAppView = GObject.registerClass({
         this._grid.goToPage(pageNumber, animate);
     }
 
+    getDropTarget(x, y) {
+        let [item, dragLocation] = this._grid.getDropTarget(x, y);
+
+        // Append to the page if dragging over empty area
+        if (dragLocation === IconGrid.DragLocation.EMPTY_SPACE) {
+            const currentPage = this._grid.currentPage;
+            const pageItems =
+                this._grid.getItemsAtPage(currentPage).filter(c => c.visible);
+
+            item = pageItems[pageItems.length - 1];
+            dragLocation = IconGrid.DragLocation.END_EDGE;
+        }
+
+        return [item, dragLocation];
+    }
+
+    nudgeItem(item, dragLocation) {
+        if (this._nudgedItem === item && this._nudgedLocation === dragLocation)
+            return;
+
+        // Did the nudged item actually change?
+        if (!this._nudgedItemReallyChanged(item, dragLocation))
+            return;
+
+        this.removeNudges();
+
+        this._nudgedItem = item;
+        this._nudgedLocation = dragLocation;
+
+        this._grid.nudgeItem(item, dragLocation);
+    }
+
+    removeNudges() {
+        if (!this._nudgedItem)
+            return;
+
+        this._nudgedItem = null;
+        this._nudgedLocation = -1;
+
+        this._grid.removeNudges();
+    }
+
     adaptToSize(width, height) {
         let box = new Clutter.ActorBox();
         box.x1 = 0;
diff --git a/js/ui/iconGrid.js b/js/ui/iconGrid.js
index 50429fb362..e18f5f0823 100644
--- a/js/ui/iconGrid.js
+++ b/js/ui/iconGrid.js
@@ -52,6 +52,25 @@ const gridModes = [
     },
 ];
 
+var LEFT_DIVIDER_LEEWAY = 20;
+var RIGHT_DIVIDER_LEEWAY = 20;
+
+const NUDGE_ANIMATION_TYPE = Clutter.AnimationMode.EASE_OUT_ELASTIC;
+const NUDGE_DURATION = 800;
+
+const NUDGE_RETURN_ANIMATION_TYPE = Clutter.AnimationMode.EASE_OUT_QUINT;
+const NUDGE_RETURN_DURATION = 300;
+
+const NUDGE_FACTOR = 0.33;
+
+var DragLocation = {
+    INVALID: 0,
+    START_EDGE: 1,
+    ON_ICON: 2,
+    END_EDGE: 3,
+    EMPTY_SPACE: 4,
+};
+
 var BaseIcon = GObject.registerClass(
 class BaseIcon extends St.Bin {
     _init(label, params) {
@@ -899,6 +918,97 @@ var IconGridLayout = GObject.registerClass({
         return itemData.pageIndex;
     }
 
+    /**
+     * getDropTarget:
+     * @param {int} x: position of the horizontal axis
+     * @param {int} y: position of the vertical axis
+     * @param {int} page: page, or -1 for the current page
+     *
+     * Retrieves the item located at (@x, @y), as well as the position.
+     * Both @x and @y are relative to the current page.
+     *
+     * @returns {[Clutter.Actor, DragLocation]} the item and drag location
+     * under (@x, @y)
+     */
+    getDropTarget(x, y) {
+        const childSize = this._getChildrenMaxSize();
+        const [leftEmptySpace, topEmptySpace, hSpacing, vSpacing] =
+            this._calculateSpacing(childSize);
+
+        const isRtl =
+            Clutter.get_default_text_direction() === Clutter.TextDirection.RTL;
+
+        let page = this._orientation === Clutter.Orientation.VERTICAL
+            ? Math.floor(y / this._pageHeight)
+            : Math.floor(x / this._pageWidth);
+
+        if (isRtl && this._orientation === Clutter.Orientation.HORIZONTAL)
+            page = swap(page, this._pages.length);
+
+        // Page-relative coordinates from now on
+        x %= this._pageWidth;
+        y %= this._pageHeight;
+
+        if (x < leftEmptySpace || y < topEmptySpace)
+            return [null, DragLocation.INVALID];
+
+        const gridWidth =
+            childSize * this._columnsPerPage +
+            hSpacing * (this._columnsPerPage - 1);
+        const gridHeight =
+            childSize * this._rowsPerPage +
+            vSpacing * (this._rowsPerPage - 1);
+
+        if (x > leftEmptySpace + gridWidth || y > topEmptySpace + gridHeight)
+            return [null, DragLocation.INVALID];
+
+        const halfHSpacing = hSpacing / 2;
+        const halfVSpacing = vSpacing / 2;
+        const visibleItems = this._getVisibleChildrenForPage(page);
+
+        for (let itemIndex in visibleItems) {
+            const item = visibleItems[itemIndex];
+            const childBox = item.get_allocation_box().copy();
+
+            // Page offset
+            switch (this._orientation) {
+            case Clutter.Orientation.HORIZONTAL:
+                childBox.set_origin(childBox.x1 - page * this._pageWidth, childBox.y1);
+                break;
+            case Clutter.Orientation.VERTICAL:
+                childBox.set_origin(childBox.x1, childBox.y1 - page * this._pageHeight);
+                break;
+            }
+
+            // Outside the icon boundaries
+            if (x < childBox.x1 - halfHSpacing ||
+                x > childBox.x2 + halfHSpacing ||
+                y < childBox.y1 - halfVSpacing ||
+                y > childBox.y2 + halfVSpacing)
+                continue;
+
+            let dragLocation;
+
+            if (x < childBox.x1 + LEFT_DIVIDER_LEEWAY)
+                dragLocation = DragLocation.START_EDGE;
+            else if (x > childBox.x2 - RIGHT_DIVIDER_LEEWAY)
+                dragLocation = DragLocation.END_EDGE;
+            else
+                dragLocation = DragLocation.ON_ICON;
+
+            if (isRtl) {
+                if (dragLocation === DragLocation.START_EDGE)
+                    dragLocation = DragLocation.END_EDGE;
+                else if (dragLocation === DragLocation.END_EDGE)
+                    dragLocation = DragLocation.START_EDGE;
+            }
+
+            return [item, dragLocation];
+        }
+
+        return [null, DragLocation.EMPTY_SPACE];
+    }
+
     // eslint-disable-next-line camelcase
     get allow_incomplete_pages() {
         return this._allowIncompletePages;
@@ -1525,6 +1635,63 @@ var IconGrid = GObject.registerClass({
         });
     }
 
+    nudgeItem(item, dragLocation) {
+        if (dragLocation === DragLocation.INVALID ||
+            dragLocation === DragLocation.EMPTY_AREA ||
+            dragLocation === DragLocation.ON_ICON)
+            return;
+
+        const layoutManager = this.layout_manager;
+        const columnsPerPage = layoutManager.columns_per_page;
+        const itemPage = layoutManager.getItemPage(item);
+        const children = layoutManager.getChildrenAtPage(itemPage).filter(c => c.visible);
+        const nudgeIndex = children.indexOf(item);
+        const rtl = Clutter.get_default_text_direction() === Clutter.TextDirection.RTL;
+
+        if (dragLocation === DragLocation.START_EDGE) {
+            const offset = rtl
+                ? Math.floor(item.width * NUDGE_FACTOR)
+                : Math.floor(-item.width * NUDGE_FACTOR);
+
+            const leftItem = children[nudgeIndex - 1];
+            if (nudgeIndex % columnsPerPage > 0)
+                this._animateNudge(leftItem, NUDGE_ANIMATION_TYPE, NUDGE_DURATION, offset);
+
+            this._animateNudge(item, NUDGE_ANIMATION_TYPE, NUDGE_DURATION, -offset);
+        }
+
+        if (dragLocation === DragLocation.END_EDGE) {
+            const offset = rtl
+                ? Math.floor(-item.width * NUDGE_FACTOR)
+                : Math.floor(item.width * NUDGE_FACTOR);
+
+            this._animateNudge(item, NUDGE_ANIMATION_TYPE, NUDGE_DURATION, -offset);
+
+            const rightItem = children[nudgeIndex + 1];
+            if (nudgeIndex < children.length - 1 &&
+                nudgeIndex % columnsPerPage < columnsPerPage - 1)
+                this._animateNudge(rightItem, NUDGE_ANIMATION_TYPE, NUDGE_DURATION, offset);
+        }
+    }
+
+    removeNudges() {
+        const children = this.get_children().filter(c => c.visible);
+        for (let child of children)
+            this._animateNudge(child, NUDGE_RETURN_ANIMATION_TYPE, NUDGE_RETURN_DURATION, 0);
+    }
+
+    _animateNudge(item, animationType, duration, offset) {
+        item.ease({
+            translation_x: offset,
+            mode: Clutter.AnimationMode.EASE_IN_OUT_QUAD,
+        });
+    }
+
+    getDropTarget(x, y) {
+        const layoutManager = this.layout_manager;
+        return layoutManager.getDropTarget(x, y, this._currentPage);
+    }
+
     get appsPerPage() {
         const layoutManager = this.layout_manager;
 


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