[gnome-shell/wip/rewindow: 5/10] workspace: Use all available space for windows in window selector



commit 3fd988046d60928e935dba9ed898f98d94d5b8b9
Author: Jasper St. Pierre <jstpierre mecheye net>
Date:   Wed Aug 8 14:27:46 2012 -0300

    workspace: Use all available space for windows in window selector
    
    Change the layout strategy to be more like the mockups. With less than
    two rows of windows, we try to fit every window in a non-aligned situation;
    with more than three rows of windows, we try to fit every window in an
    aligned situation.
    
    Based heavily on a patch from Pierre-Eric Pelloux-Prayer <pelloux gmail com>
    
    https://bugzilla.gnome.org/show_bug.cgi?id=582650

 data/theme/gnome-shell.css |    5 +
 js/ui/workspace.js         |  585 ++++++++++++++++++++++++--------------------
 2 files changed, 329 insertions(+), 261 deletions(-)
---
diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css
index e482803..23416e1 100644
--- a/data/theme/gnome-shell.css
+++ b/data/theme/gnome-shell.css
@@ -615,6 +615,11 @@ StButton.popup-menu-item:insensitive {
     -st-background-image-shadow: 2px 2px 6px rgba(0,0,0,0.5);
 }
 
+.window-picker {
+    -horizontal-spacing: 30px;
+    -vertical-spacing: 10px;
+}
+
 /* Dash */
 
 #dash {
diff --git a/js/ui/workspace.js b/js/ui/workspace.js
index 55977f6..dd500b1 100644
--- a/js/ui/workspace.js
+++ b/js/ui/workspace.js
@@ -33,21 +33,8 @@ const DRAGGING_WINDOW_OPACITY = 100;
 const BUTTON_LAYOUT_SCHEMA = 'org.gnome.shell.overrides';
 const BUTTON_LAYOUT_KEY = 'button-layout';
 
-// Define a layout scheme for small window counts. For larger
-// counts we fall back to an algorithm. We need more schemes here
-// unless we have a really good algorithm.
-
-// Each triplet is [xCenter, yCenter, scale] where the scale
-// is relative to the width of the workspace.
-const POSITIONS = {
-        1: [[0.5, 0.5, 0.95]],
-        2: [[0.25, 0.5, 0.48], [0.75, 0.5, 0.48]],
-        3: [[0.25, 0.25, 0.48],  [0.75, 0.25, 0.48],  [0.5, 0.75, 0.48]],
-        4: [[0.25, 0.25, 0.47],   [0.75, 0.25, 0.47], [0.75, 0.75, 0.47], [0.25, 0.75, 0.47]],
-        5: [[0.165, 0.25, 0.32], [0.495, 0.25, 0.32], [0.825, 0.25, 0.32], [0.25, 0.75, 0.32], [0.75, 0.75, 0.32]]
-};
-// Used in _orderWindowsPermutations, 5! = 120 which is probably the highest we can go
-const POSITIONING_PERMUTATIONS_MAX = 5;
+const LAYOUT_SCALE_WEIGHT = 1;
+const LAYOUT_RATIO_WEIGHT = 0.01;
 
 function _interpolate(start, end, step) {
     return start + (end - start) * step;
@@ -504,10 +491,6 @@ const WindowOverlay = new Lang.Class({
                            transition: 'easeOutQuad' });
     },
 
-    chromeWidth: function () {
-        return this.closeButton.width - this.closeButton._overlap;
-    },
-
     chromeHeights: function () {
         return [this.closeButton.height - this.closeButton._overlap,
                 this.title.height + this.title._spacing];
@@ -668,6 +651,241 @@ const WindowPositionFlags = {
     ANIMATE: 1 << 1
 };
 
+const LayoutStrategy = new Lang.Class({
+    Name: 'LayoutStrategy',
+    Abstract: true,
+
+    _init: function(monitor, rowSpacing, columnSpacing, bottomPadding) {
+        this._monitor = monitor;
+        this._rowSpacing = rowSpacing;
+        this._columnSpacing = columnSpacing;
+        this._bottomPadding = bottomPadding;
+    },
+
+    _newRow: function() {
+        // Row properties:
+        //
+        // * x, y are the position of row, relative to area
+        //
+        // * width, height are the scaled versions of fullWidth, fullHeight
+        //
+        // * width also has the spacing in between windows. It's not in
+        //   fullWidth, as the spacing is constant, whereas fullWidth is
+        //   meant to be scaled
+        //
+        // * neither height/fullHeight have any sort of spacing or padding
+        //
+        // * if cellWidth is present, all windows in the row will occupy
+        //   the space of cellWidth, centered.
+        return { x: 0, y: 0,
+                 width: 0, height: 0,
+                 fullWidth: 0, fullHeight: 0,
+                 cellWidth: 0,
+                 windows: [] };
+    },
+
+    _computeWindowSizeAndScale: function(window, scale) {
+        let width = window.actor.width;
+        let height = window.actor.height;
+        let ratio;
+
+        if (width > height)
+            ratio = width / this._monitor.width;
+        else
+            ratio = height / this._monitor.height;
+
+        let fancyScale = (2 / (1 + ratio)) * scale;
+        return [width * fancyScale, height * fancyScale, fancyScale];
+    },
+
+    computeScaleAndRatio: function(layout, area) {
+        let hspacing = (layout.maxColumns - 1) * this._columnSpacing;
+        let vspacing = (layout.numRows - 1) * this._rowSpacing + this._bottomPadding;
+
+        let spacedWidth = area.width - hspacing;
+        let spacedHeight = area.height - vspacing;
+
+        let horizontalScale = spacedWidth / layout.gridWidth;
+        let verticalScale = spacedHeight / layout.gridHeight;
+
+        // Thumbnails should be less than 70% of the original size
+        let scale = Math.min(horizontalScale, verticalScale, WINDOW_CLONE_MAXIMUM_SCALE);
+        let ratio = Math.abs(layout.gridWidth / layout.gridHeight - spacedWidth / spacedHeight);
+
+        layout.scale = scale;
+        layout.ratio = ratio;
+    },
+
+    computeWindowSlots: function(layout, area) {
+        this._computeRowSizes(layout);
+
+        let { rows: rows, scale: scale, state: state } = layout;
+
+        let slots = [];
+
+        let y = 0;
+        for (let i = 0; i < rows.length; i++) {
+            let row = rows[i];
+            row.x = area.x + (area.width - row.width) / 2;
+            row.y = area.y + y;
+            y += row.height + this._rowSpacing;
+        }
+
+        let height = y - this._rowSpacing + this._bottomPadding;
+        let baseY = (area.height - height) / 2;
+        for (let i = 0; i < rows.length; i++) {
+            let row = rows[i];
+            row.y += baseY;
+        }
+
+        for (let i = 0; i < rows.length; i++) {
+            let row = rows[i];
+            let baseX = row.x;
+            for (let j = 0; j < row.windows.length; j++) {
+                let window = row.windows[j];
+
+                let [width, height, s] = this._computeWindowSizeAndScale(window, scale);
+                let y = row.y + row.height - height;
+
+                let x = baseX;
+                if (row.cellWidth) {
+                    x += (row.cellWidth - width) / 2;
+                    width = row.cellWidth;
+                }
+
+                slots.push([x, y, s]);
+                baseX += width + this._columnSpacing;
+            }
+        }
+        return slots;
+    }
+});
+
+const UnalignedLayoutStrategy = new Lang.Class({
+    Name: 'UnalignedLayoutStrategy',
+    Extends: LayoutStrategy,
+
+    _computeRowSizes: function(layout) {
+        let { rows: rows, scale: scale } = layout;
+        for (let i = 0; i < rows.length; i++) {
+            let row = rows[i];
+            row.width = row.fullWidth * scale + (row.windows.length - 1) * this._columnSpacing;
+            row.height = row.fullHeight * scale;
+        }
+    },
+
+    _keepSameRow: function(row, window, width, idealRowWidth) {
+        if (row.fullWidth + width <= idealRowWidth)
+            return true;
+
+        let oldRatio = row.fullWidth / idealRowWidth;
+        let newRatio = (row.fullWidth + width) / idealRowWidth;
+
+        if (Math.abs(1 - newRatio) < Math.abs(1 - oldRatio))
+            return true;
+
+        return false;
+    },
+
+    computeLayout: function(windows, layout) {
+        let numRows = layout.numRows;
+
+        let rows = [];
+        let totalWidth = 0;
+        for (let i = 0; i < windows.length; i++) {
+            totalWidth += windows[i].actor.width;
+        }
+
+        let idealRowWidth = totalWidth / numRows;
+        let windowIdx = 0;
+        for (let i = 0; i < numRows; i++) {
+            let col = 0;
+            let row = this._newRow();
+            rows.push(row);
+
+            for (; windowIdx < windows.length; windowIdx++) {
+                let window = windows[windowIdx];
+                let [width, height] = this._computeWindowSizeAndScale(window, 1);
+                row.fullHeight = Math.max(row.fullHeight, height);
+
+                // either new width is < idealWidth or new width is nearer from idealWidth then oldWidth
+                if (this._keepSameRow(row, window, width, idealRowWidth) || (i == numRows - 1)) {
+                    row.windows.push(window);
+                    row.fullWidth += width;
+                } else {
+                    break;
+                }
+            }
+        }
+
+        let gridHeight = 0;
+        let maxRow;
+        for (let i = 0; i < numRows; i++) {
+            let row = rows[i];
+            if (!maxRow || row.fullWidth > maxRow.fullWidth)
+                maxRow = row;
+            gridHeight += row.fullHeight;
+        }
+
+        layout.rows = rows;
+        layout.maxColumns = maxRow.windows.length;
+        layout.gridWidth = maxRow.fullWidth;
+        layout.gridHeight = gridHeight;
+    }
+});
+
+const GridLayoutStrategy = new Lang.Class({
+    Name: 'GridLayoutStrategy',
+    Extends: LayoutStrategy,
+
+    _computeRowSizes: function(layout) {
+        let { rows: rows, scale: scale } = layout;
+
+        let gridWidth = layout.numColumns * layout.maxWindowWidth;
+        let hspacing = (layout.numColumns - 1) * this._columnSpacing;
+        for (let i = 0; i < rows.length; i++) {
+            let row = rows[i];
+            row.fullWidth = layout.gridWidth;
+            row.fullHeight = layout.maxWindowHeight;
+
+            row.width = row.fullWidth * scale + hspacing;
+            row.height = row.fullHeight * scale;
+            row.cellWidth = layout.maxWindowWidth * scale;
+        }
+    },
+
+    computeLayout: function(windows, layout) {
+        let { numRows: numRows, numColumns: numColumns } = layout;
+        let rows = [];
+        let windowIdx = 0;
+
+        let maxWindowWidth = 0;
+        let maxWindowHeight = 0;
+        for (let i = 0; i < numRows; i++) {
+            let row = this._newRow();
+            rows.push(row);
+            for (; windowIdx < windows.length; windowIdx++) {
+                if (row.windows.length >= numColumns)
+                    break;
+
+                let window = windows[windowIdx];
+                row.windows.push(window);
+
+                let [width, height] = this._computeWindowSizeAndScale(window, 1);
+                maxWindowWidth = Math.max(maxWindowWidth, width);
+                maxWindowHeight = Math.max(maxWindowHeight, height);
+            }
+        }
+
+        layout.rows = rows;
+        layout.maxColumns = numColumns;
+        layout.gridWidth = numColumns * maxWindowWidth;
+        layout.gridHeight = numRows * maxWindowHeight;
+        layout.maxWindowWidth = maxWindowWidth;
+        layout.maxWindowHeight = maxWindowHeight;
+    }
+});
+
 /**
  * @metaWorkspace: a #Meta.Workspace, or null
  */
@@ -689,7 +907,7 @@ const Workspace = new Lang.Class({
         // Without this the drop area will be overlapped.
         this._windowOverlaysGroup.set_size(0, 0);
 
-        this.actor = new Clutter.Group();
+        this.actor = new St.Widget({ style_class: 'window-picker' });
         this.actor.set_size(0, 0);
 
         this._dropRect = new Clutter.Rectangle({ opacity: 0 });
@@ -763,225 +981,6 @@ const Workspace = new Lang.Class({
         return this._windows.length == 0;
     },
 
-    // Only use this for n <= 20 say
-    _factorial: function(n) {
-        let result = 1;
-        for (let i = 2; i <= n; i++)
-            result *= i;
-        return result;
-    },
-
-    /**
-     * _permutation:
-     * @permutationIndex: An integer from [0, list.length!)
-     * @list: (inout): Array of objects to permute; will be modified in place
-     *
-     * Given an integer between 0 and length of array, re-order the array in-place
-     * into a permutation denoted by the index.
-     */
-    _permutation: function(permutationIndex, list) {
-        for (let j = 2; j <= list.length; j++) {
-            let firstIndex = (permutationIndex % j);
-            let secondIndex = j - 1;
-            // Swap
-            let tmp = list[firstIndex];
-            list[firstIndex] = list[secondIndex];
-            list[secondIndex] = tmp;
-            permutationIndex = Math.floor(permutationIndex / j);
-        }
-    },
-
-    /**
-     * _forEachPermutations:
-     * @list: Array
-     * @func: Function which takes a single array argument
-     *
-     * Call @func with each permutation of @list as an argument.
-     */
-    _forEachPermutations: function(list, func) {
-        let nCombinations = this._factorial(list.length);
-        for (let i = 0; i < nCombinations; i++) {
-            let listCopy = list.concat();
-            this._permutation(i, listCopy);
-            func(listCopy);
-        }
-    },
-
-    /**
-     * _computeWindowMotion:
-     * @actor: A #WindowClone's #ClutterActor
-     * @slot: An element of #POSITIONS
-     * @slotGeometry: Layout of @slot
-     *
-     * Returns a number corresponding to how much perceived motion
-     * would be involved in moving the window to the given slot.
-     * Currently this is the square of the distance between the
-     * centers.
-     */
-    _computeWindowMotion: function (actor, slot) {
-        let [xCenter, yCenter, fraction] = slot;
-        let xDelta, yDelta, distanceSquared;
-        let actorWidth, actorHeight;
-
-        let x = actor.x;
-        let y = actor.y;
-        let scale = actor.scale_x;
-
-        if (actor._delegate.inDrag) {
-            x = actor._delegate.dragOrigX;
-            y = actor._delegate.dragOrigY;
-            scale = actor._delegate.dragOrigScale;
-        }
-
-        actorWidth = actor.width * scale;
-        actorHeight = actor.height * scale;
-        xDelta = x + actorWidth / 2.0 - xCenter * this._width - this._x;
-        yDelta = y + actorHeight / 2.0 - yCenter * this._height - this._y;
-        distanceSquared = xDelta * xDelta + yDelta * yDelta;
-
-        return distanceSquared;
-    },
-
-    /**
-     * _orderWindowsPermutations:
-     *
-     * Iterate over all permutations of the windows, and determine the
-     * permutation which has the least total motion.
-     */
-    _orderWindowsPermutations: function (clones, slots) {
-        let minimumMotionPermutation = null;
-        let minimumMotion = -1;
-        let permIndex = 0;
-        this._forEachPermutations(clones, Lang.bind(this, function (permutation) {
-            let motion = 0;
-            for (let i = 0; i < permutation.length; i++) {
-                let cloneActor = permutation[i].actor;
-                let slot = slots[i];
-
-                let delta = this._computeWindowMotion(cloneActor, slot);
-
-                motion += delta;
-
-                // Bail out early if we're already larger than the
-                // previous best
-                if (minimumMotionPermutation != null &&
-                    motion > minimumMotion)
-                    continue;
-            }
-
-            if (minimumMotionPermutation == null || motion < minimumMotion) {
-                minimumMotionPermutation = permutation;
-                minimumMotion = motion;
-            }
-            permIndex++;
-        }));
-        return minimumMotionPermutation;
-    },
-
-    /**
-     * _orderWindowsGreedy:
-     *
-     * Iterate over available slots in order, placing into each one the window
-     * we find with the smallest motion to that slot.
-     */
-    _orderWindowsGreedy: function(clones, slots) {
-        let result = [];
-        let slotIndex = 0;
-        // Copy since we mutate below
-        let clonesCopy = clones.concat();
-        for (let i = 0; i < slots.length; i++) {
-            let slot = slots[i];
-            let minimumMotionIndex = -1;
-            let minimumMotion = -1;
-            for (let j = 0; j < clonesCopy.length; j++) {
-                let cloneActor = clonesCopy[j].actor;
-                let delta = this._computeWindowMotion(cloneActor, slot);
-                if (minimumMotionIndex == -1 || delta < minimumMotion) {
-                    minimumMotionIndex = j;
-                    minimumMotion = delta;
-                }
-            }
-            result.push(clonesCopy[minimumMotionIndex]);
-            clonesCopy.splice(minimumMotionIndex, 1);
-        }
-        return result;
-    },
-
-    /**
-     * _orderWindowsByMotionAndStartup:
-     * @windows: Array of #MetaWindow
-     * @slots: Array of slots
-     *
-     * Returns a copy of @windows, ordered in such a way that they require least motion
-     * to move to the final screen coordinates of @slots.  Ties are broken in a stable
-     * fashion by the order in which the windows were created.
-     */
-    _orderWindowsByMotionAndStartup: function(clones, slots) {
-        clones.sort(function(w1, w2) {
-            return w2.metaWindow.get_stable_sequence() - w1.metaWindow.get_stable_sequence();
-        });
-        if (clones.length <= POSITIONING_PERMUTATIONS_MAX)
-            return this._orderWindowsPermutations(clones, slots);
-        else
-            return this._orderWindowsGreedy(clones, slots);
-    },
-
-    /**
-     * _getSlotGeometry:
-     * @slot: A layout slot
-     *
-     * Returns: the screen-relative [x, y, width, height]
-     * of a given window layout slot.
-     */
-    _getSlotGeometry: function(slot) {
-        let [xCenter, yCenter, fraction] = slot;
-
-        let width = this._width * fraction;
-        let height = this._height * fraction;
-
-        let x = this._x + xCenter * this._width - width / 2 ;
-        let y = this._y + yCenter * this._height - height / 2;
-
-        return [x, y, width, height];
-    },
-
-    /**
-     * _computeWindowLayout:
-     * @metaWindow: A #MetaWindow
-     * @slot: A layout slot
-     *
-     * Given a window and slot to fit it in, compute its
-     * screen-relative [x, y, scale] where scale applies
-     * to both X and Y directions.
-     */
-    _computeWindowLayout: function(metaWindow, slot) {
-        let [x, y, width, height] = this._getSlotGeometry(slot);
-
-        let rect = metaWindow.get_outer_rect();
-        let buttonOuterHeight, captionHeight;
-        let buttonOuterWidth = 0;
-
-        if (this._windowOverlays[0]) {
-            [buttonOuterHeight, captionHeight] = this._windowOverlays[0].chromeHeights();
-            buttonOuterWidth = this._windowOverlays[0].chromeWidth();
-        } else
-            [buttonOuterHeight, captionHeight] = [0, 0];
-
-        let scale = Math.min((width - buttonOuterWidth) / rect.width,
-                             (height - buttonOuterHeight - captionHeight) / rect.height,
-                             WINDOW_CLONE_MAXIMUM_SCALE);
-
-        x = Math.floor(x + (width - scale * rect.width) / 2);
-
-        // We want to center the window in case we have just one
-        if (metaWindow.get_workspace().n_windows == 1)
-            y = Math.floor(y + (height - scale * rect.height) / 2);
-        else
-            y = Math.floor(y + height - rect.height * scale - captionHeight);
-
-        return [x, y, scale];
-    },
-
     setReservedSlot: function(clone) {
         if (this._reservedSlot == clone)
             return;
@@ -1032,8 +1031,7 @@ const Workspace = new Lang.Class({
         let animate = flags & WindowPositionFlags.ANIMATE;
 
         // Start the animations
-        let slots = this._computeAllWindowSlots(clones.length);
-        clones = this._orderWindowsByMotionAndStartup(clones, slots);
+        let slots = this._computeAllWindowSlots(clones);
 
         let currentWorkspace = global.screen.get_active_workspace();
         let isOnCurrentWorkspace = this.metaWorkspace == null || this.metaWorkspace == currentWorkspace;
@@ -1050,10 +1048,11 @@ const Workspace = new Lang.Class({
             if (clone.inDrag)
                 continue;
 
-            let [x, y, scale] = this._computeWindowLayout(metaWindow, slot);
+            let [x, y, scale] = slot;
 
             if (overlay && initialPositioning)
                 overlay.hide();
+
             if (animate && isOnCurrentWorkspace) {
                 if (!metaWindow.showing_on_its_workspace()) {
                     /* Hidden windows should fade in and grow
@@ -1077,6 +1076,8 @@ const Workspace = new Lang.Class({
 
                 this._animateClone(clone, overlay, x, y, scale, initialPositioning);
             } else {
+                // cancel any active tweens (otherwise they might override our changes)
+                Tweener.removeTweens(clone.actor);
                 clone.actor.set_position(x, y);
                 clone.actor.set_scale(scale, scale);
                 this._updateWindowOverlayPositions(clone, overlay, x, y, scale, false);
@@ -1118,11 +1119,11 @@ const Workspace = new Lang.Class({
     },
 
     _updateWindowOverlayPositions: function(clone, overlay, x, y, scale, animate) {
+        if (!overlay)
+            return;
+
         let [cloneWidth, cloneHeight] = clone.actor.get_size();
-        cloneWidth = scale * cloneWidth;
-        cloneHeight = scale * cloneHeight;
-        if (overlay)
-            overlay.updatePositions(x, y, cloneWidth, cloneHeight, animate);
+        overlay.updatePositions(x, y, cloneWidth * scale, cloneHeight * scale, animate);
     },
 
     _showWindowOverlay: function(clone, overlay, fade) {
@@ -1472,29 +1473,91 @@ const Workspace = new Lang.Class({
         }
     },
 
-    _computeWindowSlot : function(windowIndex, numberOfWindows) {
-        if (numberOfWindows in POSITIONS)
-            return POSITIONS[numberOfWindows][windowIndex];
+    _isBetterLayout: function(oldLayout, newLayout) {
+        if (oldLayout.scale === undefined || newLayout.scale === undefined)
+            return true;
 
-        // If we don't have a predefined scheme for this window count,
-        // arrange the windows in a grid pattern.
-        let gridWidth = Math.ceil(Math.sqrt(numberOfWindows));
-        let gridHeight = Math.ceil(numberOfWindows / gridWidth);
+        let ratioPower = (newLayout.ratio - oldLayout.ratio) * LAYOUT_RATIO_WEIGHT;
+        let scalePower = (newLayout.scale * newLayout.scale - oldLayout.scale * oldLayout.scale) * LAYOUT_SCALE_WEIGHT;
 
-        let fraction = 0.95 * (1. / gridWidth);
+        if (newLayout.scale >= oldLayout.scale && newLayout.ratio <= oldLayout.ratio) {
+            // Win win -- better scale and better ratio
+            return true;
+        } else if (newLayout.scale >= oldLayout.scale && newLayout.ratio > oldLayout.ratio) {
+            // Keep new layout only if scale gain outweights aspect ratio loss
+            return scalePower > ratioPower;
+        } else if (newLayout.scale < oldLayout.scale && newLayout.ratio <= oldLayout.ratio) {
+            // Keep new layout only if aspect ratio gain outweights scale loss
+            return ratioPower > scalePower;
+        } else {
+            // Lose -- worse scale and ratio
+            return false;
+        }
+    },
 
-        let xCenter = (.5 / gridWidth) + ((windowIndex) % gridWidth) / gridWidth;
-        let yCenter = (.5 / gridHeight) + Math.floor((windowIndex / gridWidth)) / gridHeight;
+    _computeLayout: function(windows, area, rowSpacing, columnSpacing, bottomPadding) {
+        // We look for the largest scale that allows us to fit the
+        // largest row/tallest column on the workspace.
 
-        return [xCenter, yCenter, fraction];
+        let lastLayout = {};
+
+        for (let numRows = 1; ; numRows++) {
+            let numColumns = Math.ceil(windows.length / numRows);
+
+            // If adding a new row does not change column count just stop
+            // (for instance: 9 windows, with 3 rows -> 3 columns, 4 rows ->
+            // 3 columns as well => just use 3 rows then)
+            if (numColumns == lastLayout.numColumns)
+                break;
+
+            let strategyClass = numRows > 2 ? GridLayoutStrategy : UnalignedLayoutStrategy;
+            let strategy = new strategyClass(this._monitor, rowSpacing, columnSpacing, bottomPadding);
+
+            let layout = { strategy: strategy, numRows: numRows, numColumns: numColumns };
+            strategy.computeLayout(windows, layout);
+            strategy.computeScaleAndRatio(layout, area);
+
+            if (this._isBetterLayout(lastLayout, layout) || (numRows == 0)) {
+                lastLayout = layout;
+            } else {
+                break;
+            }
+        }
+
+        return lastLayout;
     },
 
-    _computeAllWindowSlots: function(totalWindows) {
-        let slots = [];
+    _computeAllWindowSlots: function(windows) {
+        let totalWindows = windows.length;
+        let node = this.actor.get_theme_node();
+
+        // Window grid spacing
+        let columnSpacing = node.get_length('-horizontal-spacing');
+        let rowSpacing = node.get_length('-vertical-spacing');
+
+        if (!totalWindows)
+            return [];
+
+        let maxCaptionHeight = 0;
+        let maxCloseButtonHeight = 0;
         for (let i = 0; i < totalWindows; i++) {
-            slots.push(this._computeWindowSlot(i, totalWindows));
+            let overlay = this._windowOverlays[i];
+            if (overlay) {
+                let [closeButtonHeight, captionHeight] = overlay.chromeHeights();
+                maxCloseButtonHeight = Math.max(maxCloseButtonHeight, closeButtonHeight);
+                maxCaptionHeight = Math.max(maxCaptionHeight, captionHeight);
+            }
         }
-        return slots;
+        rowSpacing += maxCaptionHeight;
+
+        let area = { x: this._x, y: this._y, width: this._width, height: this._height };
+        area.y += maxCloseButtonHeight;
+        area.height -= maxCloseButtonHeight;
+
+        let layout = this._computeLayout(windows, area, rowSpacing, columnSpacing, maxCaptionHeight);
+        let strategy = layout.strategy;
+
+        return strategy.computeWindowSlots(layout, area);
     },
 
     _onCloneSelected : function (clone, time) {



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