[the-board: 5/10] [ui] Implement selection context toolbar



commit 27114153f9a80b532c3c01bee41a697883a6cc91
Author: Lucas Rocha <lucasr gnome org>
Date:   Mon Jan 24 00:49:41 2011 +0000

    [ui] Implement selection context toolbar
    
    It supports top/bottom/left/right alignment, vertical/horizontal
    distribution and removal operations on selected things.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=636628

 src/js/ui/mainWindow.js |   99 ++++++++++++++++-
 src/js/ui/page.js       |  281 +++++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 365 insertions(+), 15 deletions(-)
---
diff --git a/src/js/ui/mainWindow.js b/src/js/ui/mainWindow.js
index 9defe57..2475d52 100644
--- a/src/js/ui/mainWindow.js
+++ b/src/js/ui/mainWindow.js
@@ -70,6 +70,7 @@ MainWindow.prototype = {
 
         this._createContext();
         this._createMainToolbar();
+        this._createSelectionToolbar();
         this._createSpinner();
 
         // show loading screen as soon as possible
@@ -224,6 +225,25 @@ MainWindow.prototype = {
         this._updateActiveToolbar();
     },
 
+    _createSelectionToolbar : function() {
+        this._selectionToolbar =
+            new Toolbar.Toolbar({ title: Gettext.gettext("Selection"),
+                                  visible: false });
+
+        this._selectionToolbar.actor.depth = _LAYER_TOOLBOX;
+
+        this._createToolBoxAlign();
+        this._createToolBoxDistribute();
+        this._createToolBoxRemove();
+
+        this._contentBox.append(this._selectionToolbar.actor,
+                                Tb.BoxPackFlags.FIXED);
+
+        this._contentBox.set_fixed_child_align(this._selectionToolbar.actor,
+                                               Tb.BoxAlignment.CENTER,
+                                               Tb.BoxAlignment.START);
+    },
+
     _createSpinner : function() {
         this._spinnerBox =
             new Tb.Box({ orientation: Tb.BoxOrientation.VERTICAL,
@@ -254,6 +274,59 @@ MainWindow.prototype = {
         this._mainToolbar.addToolBox(this._toolBoxPages);
     },
 
+    _createToolBoxAlign : function() {
+        this._toolBoxAlign =
+            new ToolBox.ToolBox({ title: Gettext.gettext("Align") });
+
+        this._toolBoxAlign.addButton({ label: Gettext.gettext("Left"),
+                                       actionName: "align",
+                                       actionArgs: { alignment: Page.Alignment.LEFT } });
+
+        this._toolBoxAlign.addButton({ label: Gettext.gettext("Right"),
+                                       actionName: "align",
+                                       actionArgs: { alignment: Page.Alignment.RIGHT } });
+
+        this._toolBoxAlign.addButton({ label: Gettext.gettext("Top"),
+                                       actionName: "align",
+                                       actionArgs: { alignment: Page.Alignment.TOP } });
+
+        this._toolBoxAlign.addButton({ label: Gettext.gettext("Bottom"),
+                                       actionName: "align",
+                                       actionArgs: { alignment: Page.Alignment.BOTTOM } });
+
+        this._selectionToolbar.addToolBox(this._toolBoxAlign);
+    },
+
+    _createToolBoxDistribute : function() {
+        this._toolBoxDistribute =
+            new ToolBox.ToolBox({ title: Gettext.gettext("Distribute") });
+
+        let verticalArgs =
+            { label: Gettext.gettext("Vertical"),
+              actionName: "distribute",
+              actionArgs: { orientation: Page.Orientation.VERTICAL } };
+
+        this._toolBoxDistribute.addButton(verticalArgs);
+
+        let horizontalArgs =
+            { label: Gettext.gettext("Horizontal"),
+              actionName: "distribute",
+              actionArgs: { orientation: Page.Orientation.HORIZONTAL } };
+
+        this._toolBoxDistribute.addButton(horizontalArgs);
+
+        this._selectionToolbar.addToolBox(this._toolBoxDistribute);
+    },
+
+    _createToolBoxRemove : function() {
+        this._toolBoxRemove = new ToolBox.ToolBox();
+
+        this._toolBoxRemove.addButton({ label: Gettext.gettext("Remove"),
+                                        actionName: "remove" });
+
+        this._selectionToolbar.addToolBox(this._toolBoxRemove);
+    },
+
     _createToolBoxThings : function() {
         this._toolBoxThings =
             new ToolBoxThings.ToolBoxThings({ mainWindow: this });
@@ -365,6 +438,9 @@ MainWindow.prototype = {
         if (this._currentPage &&
             this._currentPage.activeThing) {
             activeToolbar = this._thingToolbar;
+        } else if (this._currentPage &&
+                   this._currentPage.selectedThings.length > 1) {
+            activeToolbar = this._selectionToolbar;
         } else {
             activeToolbar = this._mainToolbar;
         }
@@ -677,10 +753,11 @@ MainWindow.prototype = {
             return;
         }
 
-        if (this._currentPage &&
-            this._currentPage.activeThing) {
-            this._currentPage.activeThing.doAction(actionName, actionArgs);
+        if (!this._currentPage) {
+            return;
         }
+
+        this._currentPage.doAction(actionName, actionArgs);
     },
 
     _onClipboardTextReceived : function(clipboard, text, data) {
@@ -804,6 +881,10 @@ MainWindow.prototype = {
         }
     },
 
+    _onPageSelectedThingsChanged : function() {
+        this._updateActiveToolbar();
+    },
+
     setCurrentPage : function(pageModel) {
         if (this._currentPage &&
             this._currentPage.model.id == pageModel.id) {
@@ -821,6 +902,9 @@ MainWindow.prototype = {
 
             oldCurrentPage.disconnect(this._pageLoadedChangedId);
             delete this._pageLoadedChangedId;
+
+            oldCurrentPage.disconnect(this._pageSelectedThingsChangedId);
+            delete this._pageSelectedThingsChangedId;
         }
 
         this._currentPage = new Page.Page({ context: this._context,
@@ -842,6 +926,10 @@ MainWindow.prototype = {
             this._currentPage.connect("loaded-changed",
                                       Lang.bind(this, this._onPageLoadedChanged));
 
+        this._pageSelectedThingsChangedId =
+            this._currentPage.connect("selected-things-changed",
+                                      Lang.bind(this, this._onPageSelectedThingsChanged));
+
         if (oldCurrentPage) {
             let onModelSaved = function() {
                 this._setSavingOldCurrentPage(false);
@@ -900,6 +988,11 @@ MainWindow.prototype = {
             delete this._pageLoadedChangedId;
         }
 
+        if (this._pageSelectedThingsChangedId) {
+            this._currentPage.disconnect(this._pageSelectedThingsChangedId);
+            delete this._pageSelectedThingsChangedId;
+        }
+
         if (this._mainBox) {
             this._mainBox.destroy();
             delete this._mainBox;
diff --git a/src/js/ui/page.js b/src/js/ui/page.js
index 3b44605..68e121f 100644
--- a/src/js/ui/page.js
+++ b/src/js/ui/page.js
@@ -25,6 +25,9 @@ const _LAYER_SELECTION    = 0.4;
 const _ADD_THING_TIME = 0.5;
 const _ADD_THING_TRANSITION = 'easeOutCubic';
 
+const _MOVE_THING_TIME = 0.5;
+const _MOVE_THING_TRANSITION = 'easeOutCubic';
+
 const _SET_BACKGROUND_TIME = 0.3;
 const _SET_BACKGROUND_TRANSITION = 'easeOutCubic';
 
@@ -38,6 +41,21 @@ const _NEW_THING_ROWS_INC = 10;
 const _N_COLS_SLIDE_IN = 3;
 const _N_ROWS_SLIDE_IN = 3;
 
+const _DISTRIBUTE_VERTICAL_SPACING = 10;
+const _DISTRIBUTE_HORIZONTAL_SPACING = 20;
+
+let Alignment = {
+    LEFT   : 0,
+    RIGHT  : 1,
+    TOP    : 2,
+    BOTTOM : 3
+};
+
+let Orientation = {
+    VERTICAL   : 0,
+    HORIZONTAL : 1
+};
+
 function Page(args) {
     this._init(args);
 }
@@ -421,6 +439,236 @@ Page.prototype = {
         }
     },
 
+    _animateMoveThing : function(thing, x, y) {
+        let [deltaX, deltaY] =
+            this._clampMoveDelta(thing.area,
+                                 x - thing.area.x1,
+                                 y - thing.area.y1);
+
+        thing.actor.anchorX = deltaX;
+        thing.actor.anchorY = deltaY;
+
+        thing.setPosition(thing.area.x1 + deltaX,
+                          thing.area.y1 + deltaY);
+
+        thing.emit("save");
+
+        Tweener.addTween(thing.actor,
+                         { anchorX: 0,
+                           anchorY: 0,
+                           time: _MOVE_THING_TIME,
+                           transition: _MOVE_THING_TRANSITION });
+    },
+
+    _alignSelectedThings : function(alignment) {
+        let alignmentCoord = -1;
+
+        let updateAlignmentCoord = function(thing) {
+            let area = thing.area;
+
+            switch(alignment) {
+            case Alignment.LEFT:
+                if (alignmentCoord < 0 ||
+                    alignmentCoord > area.x1) {
+                    alignmentCoord = area.x1;
+                }
+                break;
+
+            case Alignment.RIGHT:
+                if (alignmentCoord < 0 ||
+                    alignmentCoord < area.x2) {
+                    alignmentCoord = area.x2;
+                }
+                break;
+
+            case Alignment.TOP:
+                if (alignmentCoord < 0 ||
+                    alignmentCoord > area.y1) {
+                    alignmentCoord = area.y1;
+                }
+                break;
+
+            case Alignment.BOTTOM:
+                if (alignmentCoord < 0 ||
+                    alignmentCoord < area.y2) {
+                    alignmentCoord = area.y2;
+                }
+                break;
+            }
+        };
+
+        this._selectedThings.forEach(updateAlignmentCoord);
+
+        let moveThing = function(thing) {
+            let area = thing.area;
+
+            let x, y;
+
+            switch(alignment) {
+            case Alignment.LEFT:
+                x = alignmentCoord;
+                y = area.y1;
+                break;
+
+            case Alignment.RIGHT:
+                x = alignmentCoord - (area.x2 - area.x1);
+                y = area.y1;
+                break;
+
+            case Alignment.TOP:
+                x = area.x1;
+                y = alignmentCoord;
+                break;
+
+            case Alignment.BOTTOM:
+                x = area.x1;
+                y = alignmentCoord - (area.y2 - area.y1);
+                break;
+            }
+
+            this._animateMoveThing(thing, x, y);
+        };
+
+        this._selectedThings.forEach(Lang.bind(this, moveThing));
+
+        this._updateDragArea();
+    },
+
+    _distributeSelectedThings : function(orientation) {
+        let sortByCoord = function(thingA, thingB) {
+            switch(orientation) {
+            case Orientation.VERTICAL:
+                return thingA.area.x1 - thingB.area.x1;
+                break;
+
+            case Orientation.HORIZONTAL:
+                return thingA.area.y1 - thingB.area.y1;
+                break;
+            }
+
+            return 0;
+        };
+
+        this._selectedThings.sort(sortByCoord);
+
+        let pageWidth = this._mainBox.allocation.x2 -
+                        this._mainBox.allocation.x1;
+
+        let pageHeight = this._mainBox.allocation.y2 -
+                         this._mainBox.allocation.y1;
+
+        let centerCoord = -1;
+        let currentCoord = -1;
+        let totalSize = 0;
+
+        let updateCoords = function(thing) {
+            switch(orientation) {
+            case Orientation.VERTICAL:
+                totalSize += thing.area.y2 - thing.area.y1 +
+                             _DISTRIBUTE_VERTICAL_SPACING;
+                break;
+
+            case Orientation.HORIZONTAL:
+                totalSize += thing.area.x2 - thing.area.x1 +
+                             _DISTRIBUTE_HORIZONTAL_SPACING;
+                break;
+            }
+        };
+
+        this._selectedThings.forEach(updateCoords);
+
+        let moveThing = function(thing) {
+            let area = thing.area;
+
+            let x, y;
+
+            switch(orientation) {
+            case Orientation.VERTICAL:
+                if (currentCoord < 0) {
+                    if (totalSize > pageHeight - area.y1) {
+                        currentCoord = pageHeight - totalSize;
+                    } else {
+                        currentCoord = area.y1;
+                    }
+                }
+
+                let width = area.x2 - area.x1;
+                let verticalCenter = area.x1 + (width / 2);
+
+                if (centerCoord < 0) {
+                    centerCoord = verticalCenter;
+                }
+
+                x = area.x1 + (centerCoord - verticalCenter);
+                y = currentCoord;
+
+                currentCoord += area.y2 - area.y1 +
+                                _DISTRIBUTE_VERTICAL_SPACING;
+
+                break;
+
+            case Orientation.HORIZONTAL:
+                if (currentCoord < 0) {
+                    if (totalSize > pageWidth - area.x1) {
+                        currentCoord = pageWidth - totalSize;
+                    } else {
+                        currentCoord = area.x1;
+                    }
+                }
+
+                let height = area.y2 - area.y1;
+                let horizontalCenter = area.y1 + (height / 2);
+
+                if (centerCoord < 0) {
+                    centerCoord = horizontalCenter;
+                }
+
+                x = currentCoord;
+                y = area.y1 + (centerCoord - horizontalCenter);
+
+                currentCoord += area.x2 - area.x1 +
+                                _DISTRIBUTE_HORIZONTAL_SPACING;
+
+                break;
+            }
+
+            this._animateMoveThing(thing, x, y);
+        };
+
+        this._selectedThings.forEach(Lang.bind(this, moveThing));
+
+        this._updateDragArea();
+    },
+
+    _removeSelectedThings : function() {
+        // Store current list of selected things
+        let selectedThings = this._selectedThings;
+
+        // Clean up the list of selected things
+        // as they are not removed
+        this._selectedThings = [];
+
+        // Actually remove them from page
+        selectedThings.forEach(Lang.bind(this, this.removeThing));
+    },
+
+    _clampMoveDelta : function(area, deltaX, deltaY) {
+        let minDeltaX = -area.x1;
+        let maxDeltaX = this._mainBox.allocation.x2 -
+                        this._mainBox.allocation.x1 -
+                        area.x2;
+
+        let minDeltaY = -area.y1;
+        let maxDeltaY = this._mainBox.allocation.y2 -
+                        this._mainBox.allocation.y1 -
+                        area.y2;
+
+        deltaX = MathUtil.clamp(deltaX, minDeltaX, maxDeltaX);
+        deltaY = MathUtil.clamp(deltaY, minDeltaY, maxDeltaY);
+
+        return [deltaX, deltaY];
+    },
+
     _onMainBoxClicked : function(o, event) {
         if (this._cancelSelectionOnClick) {
             this.unselectAllThings();
@@ -497,18 +745,8 @@ Page.prototype = {
     },
 
     _onThingDragMotion : function(thing, deltaX, deltaY) {
-        let minDeltaX = -this._dragArea.x1;
-        let maxDeltaX = this._mainBox.allocation.x2 -
-                        this._mainBox.allocation.x1 -
-                        this._dragArea.x2;
-
-        let minDeltaY = -this._dragArea.y1;
-        let maxDeltaY = this._mainBox.allocation.y2 -
-                        this._mainBox.allocation.y1 -
-                        this._dragArea.y2;
-
-        deltaX = MathUtil.clamp(deltaX, minDeltaX, maxDeltaX);
-        deltaY = MathUtil.clamp(deltaY, minDeltaY, maxDeltaY);
+        [deltaX, deltaY] =
+            this._clampMoveDelta(this._dragArea, deltaX, deltaY);
 
         this._dragArea.x1 += deltaX;
         this._dragArea.x2 += deltaX;
@@ -616,6 +854,11 @@ Page.prototype = {
         let indexToRemove = this._things.indexOf(thing);
         this._things.splice(indexToRemove, 1);
 
+        // Ensure the removed thing is unselected
+        // before being destroyed to avoid any dangling
+        // reference to it
+        thing.selected = false;
+
         thing.disconnect(thing._Page_activateId);
         thing.disconnect(thing._Page_deactivateId);
         thing.disconnect(thing._Page_dragBeginId);
@@ -687,6 +930,20 @@ Page.prototype = {
         this._things.forEach(unselectThing);
     },
 
+    doAction : function(actionName, actionArgs) {
+        if (this._activeThing) {
+            this._activeThing.doAction(actionName, actionArgs);
+        } else if (this._selectedThings.length > 1) {
+            if (actionName == "align") {
+                this._alignSelectedThings(actionArgs.alignment);
+            } else if (actionName == "distribute") {
+                this._distributeSelectedThings(actionArgs.orientation);
+            } else if (actionName == "remove") {
+                this._removeSelectedThings();
+            }
+        }
+    },
+
     destroy : function() {
         this._destroyAllThings();
 



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