[gnome-shell/gbsneto/custom-icon-positions: 1/28] iconGrid: Add drop target API



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

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

 js/ui/appDisplay.js |  52 +++++++++++++++++++++++++
 js/ui/iconGrid.js   | 109 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 161 insertions(+)
---
diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index 807c43a539..eadbe9bb4c 100644
--- a/js/ui/appDisplay.js
+++ b/js/ui/appDisplay.js
@@ -404,6 +404,58 @@ var BaseAppView = GObject.registerClass({
         }
     }
 
+    _getDropTarget(x, y, source) {
+        const { currentPage } = this._grid;
+
+        let [item, dragLocation] = this._grid.getDropTarget(x, y);
+
+        const [sourcePage, sourcePosition] = this._grid.getItemPosition(source);
+        const targetPage = currentPage;
+        let targetPosition = item
+            ? this._grid.getItemPosition(item)[1] : -1;
+
+        // In case we're hovering over the edge of an item but the
+        // reflow will happen in the opposite direction (the drag
+        // can't "naturally push the item away"), we instead set the
+        // drop target to the adjacent item that can be pushed away
+        // in the reflow-direction.
+        //
+        // We must avoid doing that if we're hovering over the first
+        // or last column though, in that case there is no adjacent
+        // icon we could push away.
+        if (dragLocation === IconGrid.DragLocation.START_EDGE &&
+            targetPosition > sourcePosition &&
+            targetPage === sourcePage) {
+            const nColumns = this._grid.layout_manager.columns_per_page;
+            const targetColumn = targetPosition % nColumns;
+
+            if (targetColumn > 0) {
+                targetPosition -= 1;
+                dragLocation = IconGrid.DragLocation.END_EDGE;
+            }
+        } else if (dragLocation === IconGrid.DragLocation.END_EDGE &&
+            (targetPosition < sourcePosition ||
+             targetPage !== sourcePage)) {
+            const nColumns = this._grid.layout_manager.columns_per_page;
+            const targetColumn = targetPosition % nColumns;
+
+            if (targetColumn < nColumns - 1) {
+                targetPosition += 1;
+                dragLocation = IconGrid.DragLocation.START_EDGE;
+            }
+        }
+
+        // Append to the page if dragging over empty area
+        if (dragLocation === IconGrid.DragLocation.EMPTY_SPACE) {
+            const pageItems =
+                this._grid.getItemsAtPage(currentPage).filter(c => c.visible);
+
+            targetPosition = pageItems.length;
+        }
+
+        return [targetPage, targetPosition, dragLocation];
+    }
+
     vfunc_allocate(box) {
         const width = box.get_width();
         const height = box.get_height();
diff --git a/js/ui/iconGrid.js b/js/ui/iconGrid.js
index ecbbcb3ccd..2d3a17a626 100644
--- a/js/ui/iconGrid.js
+++ b/js/ui/iconGrid.js
@@ -52,6 +52,17 @@ const defaultGridModes = [
     },
 ];
 
+var LEFT_DIVIDER_LEEWAY = 20;
+var RIGHT_DIVIDER_LEEWAY = 20;
+
+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) {
@@ -946,6 +957,99 @@ var IconGridLayout = GObject.registerClass({
         }
     }
 
+    /**
+     * getDropTarget:
+     * @param {int} x: position of the horizontal axis
+     * @param {int} y: position of the vertical axis
+     *
+     * Retrieves the item located at (@x, @y), as well as the drag location.
+     * Both @x and @y are relative to the grid.
+     *
+     * @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);
+
+        // Out of bounds
+        if (page >= this._pages.length)
+            return [null, DragLocation.INVALID];
+
+        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 (const item of visibleItems) {
+            const childBox = item.allocation.copy();
+
+            // Page offset
+            switch (this._orientation) {
+            case Clutter.Orientation.HORIZONTAL:
+                childBox.set_origin(childBox.x1 % this._pageWidth, childBox.y1);
+                break;
+            case Clutter.Orientation.VERTICAL:
+                childBox.set_origin(childBox.x1, childBox.y1 % 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;
@@ -1559,6 +1663,11 @@ var IconGrid = GObject.registerClass({
         this.queue_relayout();
     }
 
+    getDropTarget(x, y) {
+        const layoutManager = this.layout_manager;
+        return layoutManager.getDropTarget(x, y, this._currentPage);
+    }
+
     get itemsPerPage() {
         const layoutManager = this.layout_manager;
         return layoutManager.rows_per_page * layoutManager.columns_per_page;


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