[gnome-shell/gbsneto/40-stuff: 59/68] workspacesView: Allow dragging between workspaces




commit 01c960b3682116b7519e82933cf556702d67f16f
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date:   Thu Oct 1 15:25:24 2020 -0300

    workspacesView: Allow dragging between workspaces
    
    Add a placeholder actor that creates and moves to the new
    workspace, and either launch the app there, or move the
    window to it.

 data/theme/dash-placeholder.svg                    |  29 +++-
 .../gnome-shell-sass/widgets/_window-picker.scss   |   6 +
 js/ui/workspacesView.js                            | 180 ++++++++++++++++++++-
 3 files changed, 206 insertions(+), 9 deletions(-)
---
diff --git a/data/theme/dash-placeholder.svg b/data/theme/dash-placeholder.svg
index cbae148a28..d76c9c5887 100644
--- a/data/theme/dash-placeholder.svg
+++ b/data/theme/dash-placeholder.svg
@@ -1,14 +1,27 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!-- Created with Inkscape (http://www.inkscape.org/) -->
-
 <svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/";
+   xmlns:cc="http://creativecommons.org/ns#";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
    xmlns:svg="http://www.w3.org/2000/svg";
    xmlns="http://www.w3.org/2000/svg";
    xmlns:xlink="http://www.w3.org/1999/xlink";
-   width="76"
-   height="27"
+   width="27"
+   height="76"
    id="svg11252"
    version="1.1">
+  <metadata
+     id="metadata19">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage"; />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
   <defs
      id="defs11254">
     <radialGradient
@@ -55,13 +68,13 @@
   </defs>
   <g
      id="layer1"
-     transform="translate(-337,-518.86218)">
+     transform="rotate(-90,-52.931091,465.93109)">
     <g
        id="g99967"
        style="display:inline"
        transform="translate(326,44.862171)">
       <rect
-         
style="opacity:0.49375;color:#000000;fill:url(#radialGradient68155-2-3);fill-opacity:1;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+         
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.49375;fill:url(#radialGradient68155-2-3);fill-opacity:1;stroke:none;stroke-width:1;marker:none;enable-background:accumulate"
          id="rect99969"
          width="76"
          height="2"
@@ -70,7 +83,7 @@
          rx="0"
          ry="0" />
       <path
-         
style="opacity:0.43125;color:#000000;fill:url(#radialGradient68157-0-8);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+         
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.43125;fill:url(#radialGradient68157-0-8);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;enable-background:accumulate"
          id="path99971"
          d="M 61,487.5 C 61,493.29899 56.29899,498 50.5,498 44.70101,498 40,493.29899 40,487.5 40,481.70101 
44.70101,477 50.5,477 c 5.79899,0 10.5,4.70101 10.5,10.5 z"
          transform="matrix(1.2857143,0,0,1.2857143,-14.428572,-139.28571)" />
@@ -78,7 +91,7 @@
          transform="matrix(0.43589747,0,0,0.43589747,28.487179,275)"
          d="M 61,487.5 C 61,493.29899 56.29899,498 50.5,498 44.70101,498 40,493.29899 40,487.5 40,481.70101 
44.70101,477 50.5,477 c 5.79899,0 10.5,4.70101 10.5,10.5 z"
          id="path99973"
-         
style="color:#000000;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
 />
+         
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;enable-background:accumulate"
 />
     </g>
   </g>
 </svg>
diff --git a/data/theme/gnome-shell-sass/widgets/_window-picker.scss 
b/data/theme/gnome-shell-sass/widgets/_window-picker.scss
index 4cf3e12e2f..2d97348f6b 100644
--- a/data/theme/gnome-shell-sass/widgets/_window-picker.scss
+++ b/data/theme/gnome-shell-sass/widgets/_window-picker.scss
@@ -58,3 +58,9 @@ $window_clone_border_size: 6px;
     background-color: darken($selected_bg_color, 5%);
   }
 }
+
+// drag and drop indicator
+.workspace-dnd-placeholder {
+  background-image: url("resource:///org/gnome/shell/theme/dash-placeholder.svg");
+  background-position: center;
+}
diff --git a/js/ui/workspacesView.js b/js/ui/workspacesView.js
index fddeaface8..21648e26cc 100644
--- a/js/ui/workspacesView.js
+++ b/js/ui/workspacesView.js
@@ -3,6 +3,7 @@
 
 const { Clutter, Gio, GLib, GObject, Meta, Shell, St } = imports.gi;
 
+const DND = imports.ui.dnd;
 const Main = imports.ui.main;
 const SwipeTracker = imports.ui.swipeTracker;
 const OverviewControls = imports.ui.overviewControls;
@@ -11,6 +12,7 @@ const Workspace = imports.ui.workspace;
 var { ANIMATION_TIME } = imports.ui.overview;
 var WORKSPACE_SWITCH_TIME = 250;
 var SCROLL_TIMEOUT_TIME = 150;
+var WORKSPACE_KEEP_ALIVE_TIME = 100;
 
 const MUTTER_SCHEMA = 'org.gnome.mutter';
 
@@ -89,6 +91,71 @@ var WorkspacesViewBase = GObject.registerClass({
     }
 });
 
+var WorkspaceDragPlaceholder = GObject.registerClass(
+class WorkspaceDragPlaceholder extends St.Widget {
+    _init(monitorIndex) {
+        super._init({
+            style_class: 'workspace-dnd-placeholder',
+            visible: false,
+        });
+
+        this._monitorIndex = monitorIndex;
+        this._delegate = this;
+        this._position = -1;
+    }
+
+    acceptDrop(source, actor, x, y, time) {
+        if (this._position === -1)
+            return false;
+
+        const newWorkspaceIndex = this._position + 1;
+
+        Main.wm.insertWorkspace(newWorkspaceIndex);
+
+        const { workspaceManager } = global;
+        const workspace =
+            workspaceManager.get_workspace_by_index(newWorkspaceIndex);
+
+        const isWindow = !!source.metaWindow;
+        if (isWindow) {
+            // Move the window to our monitor first if necessary.
+            if (source.metaWindow.get_monitor() !== this._monitorIndex)
+                source.metaWindow.move_to_monitor(this._monitorIndex);
+            source.metaWindow.change_workspace_by_index(newWorkspaceIndex, true);
+            workspace.activate(time);
+        } else if (source.app && source.app.can_open_new_window()) {
+            if (source.animateLaunchAtPos)
+                source.animateLaunchAtPos(actor.x, actor.y);
+            source.app.open_new_window(newWorkspaceIndex);
+        } else if (!source.app && source.shellWorkspaceLaunch) {
+            // While unused in our own drag sources, shellWorkspaceLaunch allows
+            // extensions to define custom actions for their drag sources.
+            source.shellWorkspaceLaunch({
+                workspace: newWorkspaceIndex,
+                timestamp: time,
+            });
+        }
+
+        if (source.app || (!source.app && source.shellWorkspaceLaunch)) {
+            // This new workspace will be automatically removed if the application fails
+            // to open its first window within some time, as tracked by Shell.WindowTracker.
+            // Here, we only add a very brief timeout to avoid the _immediate_ removal of the
+            // workspace while we wait for the startup sequence to load.
+            Main.wm.keepWorkspaceAlive(workspace, WORKSPACE_KEEP_ALIVE_TIME);
+        }
+
+        return isWindow;
+    }
+
+    get position() {
+        return this._position;
+    }
+
+    set position(position) {
+        this._position = position;
+    }
+});
+
 var WorkspacesView = GObject.registerClass(
 class WorkspacesView extends WorkspacesViewBase {
     _init(monitorIndex, scrollAdjustment, snapAdjustment, overviewAdjustment) {
@@ -102,6 +169,8 @@ class WorkspacesView extends WorkspacesViewBase {
             this.queue_relayout();
         });
 
+        this._delegate = this;
+
         this._animating = false; // tweening
         this._gestureActive = false; // touch(pad) gestures
 
@@ -109,6 +178,9 @@ class WorkspacesView extends WorkspacesViewBase {
         this._onScrollId = this._scrollAdjustment.connect('notify::value',
             this._onScrollAdjustmentChanged.bind(this));
 
+        this._placeholder = new WorkspaceDragPlaceholder(this._monitorIndex);
+        this.add_child(this._placeholder);
+
         this._workspaces = [];
         this._updateWorkspaces();
         this._updateWorkspacesId =
@@ -128,6 +200,19 @@ class WorkspacesView extends WorkspacesViewBase {
                                           this._activeWorkspaceChanged.bind(this));
     }
 
+    _removeDragMonitor() {
+        if (!this._dragMonitor)
+            return;
+
+        DND.removeDragMonitor(this._dragMonitor);
+        delete this._dragMonitor;
+    }
+
+    _resetDropTarget() {
+        this._placeholder.hide();
+        this._placeholder.position = -1;
+    }
+
     _getHorizontalSnapBox(box, spacing, vertical) {
         const { nWorkspaces } = global.workspaceManager;
         const [width, height] = box.get_size();
@@ -292,7 +377,7 @@ class WorkspacesView extends WorkspacesViewBase {
         if (rtl)
             workspaces.reverse();
 
-        workspaces.forEach(child => {
+        workspaces.forEach((child, index) => {
             if (snapProgress === 0)
                 box = horizontalBox;
             else if (snapProgress === 1)
@@ -302,6 +387,26 @@ class WorkspacesView extends WorkspacesViewBase {
 
             child.allocate_align_fill(box, 0.5, 0.5, false, false);
 
+            // Drop placeholder
+            if (this._placeholder.visible &&
+                this._placeholder.position === index) {
+                const spacing =
+                    Math.interpolate(horizontalSpacing, verticalSpacing, snapProgress);
+                const placeholderBox = box.copy();
+                if (vertical) {
+                    placeholderBox.y1 = box.y2;
+                    placeholderBox.set_size(
+                        box.get_width(),
+                        spacing);
+                } else {
+                    placeholderBox.x1 = box.x2;
+                    placeholderBox.set_size(
+                        spacing,
+                        box.get_height());
+                }
+                this._placeholder.allocate(placeholderBox);
+            }
+
             if (vertical) {
                 verticalBox.set_origin(
                     verticalBox.x1,
@@ -392,9 +497,17 @@ class WorkspacesView extends WorkspacesViewBase {
         this._scrollToActive();
     }
 
+    _dragEnd() {
+        super._dragEnd();
+        this._removeDragMonitor();
+        this._resetDropTarget();
+    }
+
     _onDestroy() {
         super._onDestroy();
 
+        this._removeDragMonitor();
+
         this._scrollAdjustment.disconnect(this._onScrollId);
         this._snapAdjustment.disconnect(this._snapNotifyId);
         global.window_manager.disconnect(this._switchWorkspaceNotifyId);
@@ -413,6 +526,71 @@ class WorkspacesView extends WorkspacesViewBase {
         this._scrollToActive();
     }
 
+    _getWorkspaceTarget(x, y) {
+        if (this.get_n_children() < 2)
+            return [false, -1];
+
+        const vertical = global.workspaceManager.layout_rows === -1;
+        const spacing = Math.interpolate(
+            this._getSpacing(this.allocation, Clutter.Orientation.HORIZONTAL, vertical),
+            this._getSpacing(this.allocation, Clutter.Orientation.VERTICAL, vertical),
+            this._snapAdjustment.value);
+
+        for (let i = 0; i < this._workspaces.length; i++) {
+            const workspace = this._workspaces[i];
+            const { allocation } = workspace;
+
+            const [workspaceWidth, workspaceHeight] = allocation.get_size();
+            const [workspaceX, workspaceY] = allocation.get_origin();
+
+            if (y < workspaceY ||
+                y > workspaceY + workspaceHeight ||
+                x < workspaceX)
+                break;
+
+            if (y >= workspaceY &&
+                y < workspaceY + workspaceHeight &&
+                x > workspaceX + workspaceWidth &&
+                x <= workspaceX + workspaceWidth + spacing)
+                return [true, i];
+        }
+
+        return [false, -1];
+    }
+
+    _updateWorkpaceDropTarget(x, y) {
+        const [isBetween, previousWorkspace] = this._getWorkspaceTarget(x, y);
+        this._placeholder.visible = isBetween;
+        this._placeholder.position = previousWorkspace;
+
+        return isBetween;
+    }
+
+    handleDragOver(source, actor, x, y) {
+        const inBetween = this._updateWorkpaceDropTarget(x, y);
+
+        if (!this._dragMonitor) {
+            this._dragMonitor = {
+                dragMotion: dragEvent => {
+                    const [result, localX, localY] =
+                        this.transform_stage_point(dragEvent.x, dragEvent.y);
+
+                    if (!result)
+                        return DND.DragMotionResult.CONTINUE;
+
+                    if (!this._updateWorkpaceDropTarget(localX, localY))
+                        this._removeDragMonitor();
+                    return DND.DragMotionResult.CONTINUE;
+                },
+            };
+            DND.addDragMonitor(this._dragMonitor);
+        }
+
+        return inBetween
+            ? DND.DragMotionResult.MOVE_DROP
+            : DND.DragMotionResult.CONTINUE;
+    }
+
     // sync the workspaces' positions to the value of the scroll adjustment
     // and change the active workspace if appropriate
     _onScrollAdjustmentChanged() {


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