[gnome-shell] Rewrite Dash, remove hardcoded width/height from GenericDisplay



commit 85b4b97b7b968123e31f93b125d644b1800c44b8
Author: Colin Walters <walters verbum org>
Date:   Fri Jul 31 22:12:01 2009 -0400

    Rewrite Dash, remove hardcoded width/height from GenericDisplay
    
    This patch is a near-total rewrite of the Dash.  First, the dash
    code moves into a separate file, dash.js.
    
    Inside dash.js, the components are more broken up into separate
    classes; in particular there's now a Pane class and a MoreLink
    class.  Instead of each section of the dash, when activated,
    attempting to close all N-1 other sections, instead there
    is the concept of a single "active pane", and when e.g. activating
    the More link for documents, if we know there's an active pane
    which happens to be the apps, close it.
    
    Many redundant containers were removed from the dash, and all
    manual width, height and x/y offsets are entirely gone.  We move
    the visual apperance closer to the design by using the view-more.svg,
    etc.
    
    To complete the removal of height/width calculations from the dash,
    we also had to do the same for GenericDisplay.  Also clean up
    the positioning inside overlay.js so calculation of children's
    positioning is inside a single function that flows from screen.width
    and screen.height, so in the future we can stop passing the width
    into the Dash constructor and call this once and work on screen
    resizing.

 js/ui/appDisplay.js     |   26 +-
 js/ui/dash.js           |  505 +++++++++++++++++++++++++++++
 js/ui/docDisplay.js     |   25 +-
 js/ui/genericDisplay.js |  188 +++++------
 js/ui/overlay.js        |  805 +++++++----------------------------------------
 5 files changed, 718 insertions(+), 831 deletions(-)
---
diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index 9a4c2a7..e5437bd 100644
--- a/js/ui/appDisplay.js
+++ b/js/ui/appDisplay.js
@@ -35,17 +35,16 @@ const MAX_ITEMS = 30;
 /* This class represents a single display item containing information about an application.
  *
  * appInfo - AppInfo object containing information about the application
- * availableWidth - total width available for the item
  */
-function AppDisplayItem(appInfo, availableWidth) {
-    this._init(appInfo, availableWidth);
+function AppDisplayItem(appInfo) {
+    this._init(appInfo);
 }
 
 AppDisplayItem.prototype = {
     __proto__:  GenericDisplay.GenericDisplayItem.prototype,
 
-    _init : function(appInfo, availableWidth) {
-        GenericDisplay.GenericDisplayItem.prototype._init.call(this, availableWidth);
+    _init : function(appInfo) {
+        GenericDisplay.GenericDisplayItem.prototype._init.call(this);
         this._appInfo = appInfo;
 
         this._setItemInfo(appInfo.get_name(), appInfo.get_description());
@@ -115,7 +114,6 @@ MenuItem.prototype = {
         this.actor.append(this._icon, Big.BoxPackFlags.NONE);
         this._text = new Clutter.Text({ color: GenericDisplay.ITEM_DISPLAY_NAME_COLOR,
                                         font_name: "Sans 14px",
-                                        ellipsize: Pango.EllipsizeMode.END,
                                         text: name });
 
         // We use individual boxes for the label and the arrow to ensure that they
@@ -164,18 +162,16 @@ Signals.addSignalMethods(MenuItem.prototype);
 /* This class represents a display containing a collection of application items.
  * The applications are sorted based on their popularity by default, and based on
  * their name if some search filter is applied.
- *
- * width - width available for the display
  */
-function AppDisplay(width) {
-    this._init(width);
+function AppDisplay() {
+    this._init();
 }
 
 AppDisplay.prototype = {
     __proto__:  GenericDisplay.GenericDisplay.prototype,
 
-    _init : function(width) {
-        GenericDisplay.GenericDisplay.prototype._init.call(this, width);
+    _init : function() {
+        GenericDisplay.GenericDisplay.prototype._init.call(this);
 
         this._menus = [];
         this._menuDisplays = [];
@@ -433,8 +429,8 @@ AppDisplay.prototype = {
     },
 
     // Creates an AppDisplayItem based on itemInfo, which is expected be an Shell.AppInfo object.
-    _createDisplayItem: function(itemInfo, width) {
-        return new AppDisplayItem(itemInfo, width);
+    _createDisplayItem: function(itemInfo) {
+        return new AppDisplayItem(itemInfo);
     }
 };
 
@@ -488,8 +484,6 @@ WellDisplayItem.prototype = {
                                     x_align: Big.BoxAlignment.CENTER });
         this._nameBox = nameBox;
 
-        this._wordWidth = Shell.Global.get().get_max_word_width(this.actor, appInfo.get_name(),
-                                                                "Sans 12px");
         this._name = new Clutter.Text({ color: GenericDisplay.ITEM_DISPLAY_NAME_COLOR,
                                         font_name: "Sans 12px",
                                         line_alignment: Pango.Alignment.CENTER,
diff --git a/js/ui/dash.js b/js/ui/dash.js
new file mode 100644
index 0000000..c09307c
--- /dev/null
+++ b/js/ui/dash.js
@@ -0,0 +1,505 @@
+/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
+
+const Big = imports.gi.Big;
+const Clutter = imports.gi.Clutter;
+const Gio = imports.gi.Gio;
+const Gtk = imports.gi.Gtk;
+const Mainloop = imports.mainloop;
+const Shell = imports.gi.Shell;
+const Signals = imports.signals;
+const Lang = imports.lang;
+
+const AppDisplay = imports.ui.appDisplay;
+const DocDisplay = imports.ui.docDisplay;
+const GenericDisplay = imports.ui.genericDisplay;
+const Button = imports.ui.button;
+const Main = imports.ui.main;
+
+const DEFAULT_PADDING = 4;
+const DASH_SECTION_PADDING = 6;
+const DASH_SECTION_SPACING = 12;
+const DASH_CORNER_RADIUS = 5;
+const DASH_SEARCH_BG_COLOR = new Clutter.Color();
+DASH_SEARCH_BG_COLOR.from_pixel(0xffffffff);
+const DASH_SECTION_COLOR = new Clutter.Color();
+DASH_SECTION_COLOR.from_pixel(0x846c3dff);
+const DASH_TEXT_COLOR = new Clutter.Color();
+DASH_TEXT_COLOR.from_pixel(0xffffffff);
+
+const PANE_BORDER_COLOR = new Clutter.Color();
+PANE_BORDER_COLOR.from_pixel(0x213b5dfa);
+const PANE_BORDER_WIDTH = 2;
+
+const PANE_BACKGROUND_COLOR = new Clutter.Color();
+PANE_BACKGROUND_COLOR.from_pixel(0x0d131ff4);
+
+
+function Pane() {
+    this._init();
+}
+
+Pane.prototype = {
+    _init: function () {
+        this._open = false;
+
+        this.actor = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
+                                   background_color: PANE_BACKGROUND_COLOR,
+                                   border: PANE_BORDER_WIDTH,
+                                   border_color: PANE_BORDER_COLOR,
+                                   padding: DEFAULT_PADDING,
+                                   reactive: true });
+        this.actor.connect('button-press-event', Lang.bind(this, function (a, e) {
+            // Eat button press events so they don't go through and close the pane
+            return true;
+        }));
+
+        let chromeTop = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
+                                      spacing: 6 });
+
+        let global = Shell.Global.get();
+        let closeIconUri = "file://" + global.imagedir + "close.svg";
+        let closeIcon = Shell.TextureCache.get_default().load_uri_sync(Shell.TextureCachePolicy.FOREVER,
+                                                                       closeIconUri,
+                                                                       16,
+                                                                       16);
+        closeIcon.reactive = true;
+        closeIcon.connect('button-press-event', Lang.bind(this, function (b, e) {
+            this.close();
+            return true;
+        }));
+        chromeTop.append(closeIcon, Big.BoxPackFlags.END);
+        this.actor.append(chromeTop, Big.BoxPackFlags.NONE);
+
+        this.content = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
+                                     spacing: DEFAULT_PADDING });
+        this.actor.append(this.content, Big.BoxPackFlags.EXPAND);
+
+        // Hidden by default
+        this.actor.hide();
+    },
+
+    open: function () {
+        if (this._open)
+            return;
+        this._open = true;
+        this.actor.show();
+        this.emit('open-state-changed', this._open);
+    },
+
+    close: function () {
+        if (!this._open)
+            return;
+        this._open = false;
+        this.actor.hide();
+        this.emit('open-state-changed', this._open);
+    },
+
+    destroyContent: function() {
+        let children = this.content.get_children();
+        for (let i = 0; i < children.length; i++) {
+            children[i].destroy();
+        }
+    },
+
+    toggle: function () {
+        if (this._open)
+            this.close();
+        else
+            this.open();
+    }
+}
+Signals.addSignalMethods(Pane.prototype);
+
+function ResultArea(displayClass, enableNavigation) {
+    this._init(displayClass, enableNavigation);
+}
+
+ResultArea.prototype = {
+    _init : function(displayClass, enableNavigation) {
+        this.actor = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL });
+        this.resultsContainer = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
+                                              spacing: DEFAULT_PADDING
+                                            });
+        this.actor.append(this.resultsContainer, Big.BoxPackFlags.EXPAND);
+        this.navContainer = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL });
+        this.resultsContainer.append(this.navContainer, Big.BoxPackFlags.NONE);
+
+        this.display = new displayClass();
+
+        this.navArea = this.display.getNavigationArea();
+        if (enableNavigation && this.navArea)
+            this.navContainer.append(this.navArea, Big.BoxPackFlags.EXPAND);
+
+        this.resultsContainer.append(this.display.actor, Big.BoxPackFlags.EXPAND);
+
+        this.controlBox = new Big.Box({ x_align: Big.BoxAlignment.CENTER });
+        this.controlBox.append(this.display.displayControl, Big.BoxPackFlags.NONE);
+        this.actor.append(this.controlBox, Big.BoxPackFlags.EXPAND);
+
+        this.display.load();
+    }
+}
+
+// Utility function shared between ResultPane and the DocDisplay in the main dash.
+// Connects to the detail signal of the display, and on-demand creates a new
+// pane.
+function createPaneForDetails(dash, display, detailsWidth) {
+    let detailPane = null;
+    display.connect('show-details', Lang.bind(this, function(display, index) {
+        if (detailPane == null) {
+            detailPane = new Pane();
+            detailPane.connect('open-state-changed', Lang.bind(this, function (pane, isOpen) {
+                if (!isOpen) {
+                    /* Ensure we don't keep around large preview textures */
+                    detailPane.destroyContent();
+                }
+            }));
+            dash._addPane(detailPane);
+        }
+
+        if (index >= 0) {
+            detailPane.destroyContent();
+            let details = display.createDetailsForIndex(index, detailsWidth, -1);
+            detailPane.content.append(details, Big.BoxPackFlags.EXPAND);
+            detailPane.open();
+        } else {
+            detailPane.close();
+        }
+    }));
+    return null;
+}
+
+function ResultPane(dash, detailsWidth) {
+    this._init(dash, detailsWidth);
+}
+
+ResultPane.prototype = {
+    __proto__: Pane.prototype,
+
+    _init: function(dash, detailsWidth) {
+        Pane.prototype._init.call(this);
+        this._dash = dash;
+        this._detailsWidth = detailsWidth;
+    },
+
+    // Create an instance of displayClass and pack it into this pane's
+    // content area.  Return the displayClass instance.
+    packResults: function(displayClass, enableNavigation) {
+        let resultArea = new ResultArea(displayClass, enableNavigation);
+
+        createPaneForDetails(this._dash, resultArea.display, this._detailsWidth);
+
+        this.content.append(resultArea.actor, Big.BoxPackFlags.EXPAND);
+        this.connect('open-state-changed', Lang.bind(this, function(pane, isOpen) {
+            resultArea.display.resetState();
+        }));
+        return resultArea.display;
+    }
+}
+
+function SearchEntry() {
+    this._init();
+}
+
+SearchEntry.prototype = {
+    _init : function() {
+        this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
+                                   y_align: Big.BoxAlignment.CENTER,
+                                   background_color: DASH_SEARCH_BG_COLOR,
+                                   corner_radius: 4,
+                                   spacing: DEFAULT_PADDING,
+                                   padding: DEFAULT_PADDING
+                                 });
+
+        let icon = new Gio.ThemedIcon({ name: 'gtk-find' });
+        let searchIconTexture = Shell.TextureCache.get_default().load_gicon(icon, 16);
+        this.actor.append(searchIconTexture, Big.BoxPackFlags.NONE);
+
+        this.pane = null;
+
+        // We need to initialize the text for the entry to have the cursor displayed
+        // in it. See http://bugzilla.openedhand.com/show_bug.cgi?id=1365
+        this.entry = new Clutter.Text({ font_name: "Sans 14px",
+                                        editable: true,
+                                        activatable: true,
+                                        singleLineMode: true,
+                                        text: ""
+                                      });
+        this.actor.append(this.entry, Big.BoxPackFlags.EXPAND);
+    },
+
+    setPane: function (pane) {
+        this._pane = pane;
+    }
+};
+Signals.addSignalMethods(SearchEntry.prototype);
+
+function MoreLink() {
+    this._init();
+}
+
+MoreLink.prototype = {
+    _init : function () {
+        this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
+                                   padding_left: DEFAULT_PADDING,
+                                   padding_right: DEFAULT_PADDING });
+        let global = Shell.Global.get();
+        let inactiveUri = "file://" + global.imagedir + "view-more.svg";
+        let activeUri = "file://" + global.imagedir + "view-more-activated.svg";
+        this._inactiveIcon = Shell.TextureCache.get_default().load_uri_sync(Shell.TextureCachePolicy.FOREVER,
+                                                                            inactiveUri, 29, 18);
+        this._activeIcon = Shell.TextureCache.get_default().load_uri_sync(Shell.TextureCachePolicy.FOREVER,
+                                                                          activeUri, 29, 18);
+        this._iconBox = new Big.Box({ reactive: true });
+        this._iconBox.append(this._inactiveIcon, Big.BoxPackFlags.NONE);
+        this.actor.append(this._iconBox, Big.BoxPackFlags.END);
+
+        this.pane = null;
+
+        this._iconBox.connect('button-press-event', Lang.bind(this, function (b, e) {
+            if (this.pane == null) {
+                // Ensure the pane is created; the activated handler will call setPane
+                this.emit('activated');
+            }
+            this._pane.toggle();
+            return true;
+        }));
+    },
+
+    setPane: function (pane) {
+        this._pane = pane;
+        this._pane.connect('open-state-changed', Lang.bind(this, function(pane, isOpen) {
+            this._iconBox.remove_all();
+            this._iconBox.append(isOpen ? this._activeIcon : this._inactiveIcon,
+                                 Big.BoxPackFlags.NONE);
+        }));
+    }
+}
+
+Signals.addSignalMethods(MoreLink.prototype);
+
+function SectionHeader(title) {
+    this._init(title);
+}
+
+SectionHeader.prototype = {
+    _init : function (title) {
+        this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL });
+        let text = new Clutter.Text({ color: DASH_SECTION_COLOR,
+                                      font_name: "Sans Bold 10px",
+                                      text: title });
+        this.moreLink = new MoreLink();
+        this.actor.append(text, Big.BoxPackFlags.EXPAND);
+        this.actor.append(this.moreLink.actor, Big.BoxPackFlags.END);
+    }
+}
+
+function Dash(displayGridColumnWidth) {
+    this._init(displayGridColumnWidth);
+}
+
+Dash.prototype = {
+    _init : function(displayGridColumnWidth) {
+        this._width = displayGridColumnWidth;
+
+        this._detailsWidth = displayGridColumnWidth * 2;
+
+        let global = Shell.Global.get();
+
+        // dash and the popup panes need to be reactive so that the clicks in unoccupied places on them
+        // are not passed to the transparent background underneath them. This background is used for the workspaces area when
+        // the additional dash panes are being shown and it handles clicks by closing the additional panes, so that the user
+        // can interact with the workspaces. However, this behavior is not desirable when the click is actually over a pane.
+        //
+        // We have to make the individual panes reactive instead of making the whole dash actor reactive because the width
+        // of the Group actor ends up including the width of its hidden children, so we were getting a reactive object as
+        // wide as the details pane that was blocking the clicks to the workspaces underneath it even when the details pane
+        // was actually hidden.
+        this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
+                                   width: this._width,
+                                   padding: DEFAULT_PADDING,
+                                   reactive: true });
+
+        this.dashContainer = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
+                                           spacing: DASH_SECTION_SPACING });
+        this.actor.append(this.dashContainer, Big.BoxPackFlags.EXPAND);
+
+        // The currently active popup display
+        this._activePane = null;
+
+        /***** Search *****/
+
+        this._searchPane = null;
+        this._searchActive = false;
+        this._searchEntry = new SearchEntry();
+        this.dashContainer.append(this._searchEntry.actor, Big.BoxPackFlags.NONE);
+
+        this._searchAreaApps = null;
+        this._searchAreaDocs = null;
+
+        this._searchQueued = false;
+        this._searchEntry.entry.connect('text-changed', Lang.bind(this, function (se, prop) {
+            this._searchActive = this._searchEntry.text != '';
+            if (this._searchQueued)
+                return;
+            if (this._searchPane == null) {
+                this._searchPane = new ResultPane(this, this._detailsWidth);
+                this._searchPane.content.append(new Clutter.Text({ color: DASH_SECTION_COLOR,
+                                                                   font_name: 'Sans Bold 10px',
+                                                                   text: "APPLICATIONS" }),
+                                                 Big.BoxPackFlags.NONE);
+                this._searchAreaApps = this._searchPane.packResults(AppDisplay.AppDisplay, false);
+                this._searchPane.content.append(new Clutter.Text({ color: DASH_SECTION_COLOR,
+                                                                   font_name: 'Sans Bold 10px',
+                                                                   text: "RECENT DOCUMENTS" }),
+                                                 Big.BoxPackFlags.NONE);
+                this._searchAreaDocs = this._searchPane.packResults(DocDisplay.DocDisplay, false);
+                this._addPane(this._searchPane);
+                this._searchEntry.setPane(this._searchPane);
+            }
+            this._searchQueued = true;
+            Mainloop.timeout_add(250, Lang.bind(this, function() {
+                // Strip leading and trailing whitespace
+                let text = this._searchEntry.entry.text.replace(/^\s+/g, "").replace(/\s+$/g, "");
+                this._searchQueued = false;
+                this._searchAreaApps.setSearch(text);
+                this._searchAreaDocs.setSearch(text);
+                if (text == '')
+                    this._searchPane.close();
+                else
+                    this._searchPane.open();
+                return false;
+            }));
+        }));
+        this._searchEntry.entry.connect('activate', Lang.bind(this, function (se) {
+            // only one of the displays will have an item selected, so it's ok to
+            // call activateSelected() on all of them
+            this._searchAreaApps.activateSelected();
+            this._searchAreaDocs.activateSelected();
+            return true;
+        }));
+        this._searchEntry.entry.connect('key-press-event', Lang.bind(this, function (se, e) {
+            let symbol = Shell.get_event_key_symbol(e);
+            if (symbol == Clutter.Escape) {
+                // Escape will keep clearing things back to the desktop. First, if
+                // we have active text, we remove it.
+                if (this._searchEntry.entry.text != '')
+                    this._searchEntry.entry.text = '';
+                // Next, if we're in one of the "more" modes or showing the details pane, close them
+                else if (this._activePane != null)
+                    this._activePane.close();
+                // Finally, just close the overlay entirely
+                else
+                    Main.overlay.hide();
+                return true;
+            } else if (symbol == Clutter.Up) {
+                if (!this._searchActive)
+                    return true;
+                // selectUp and selectDown wrap around in their respective displays
+                // too, but there doesn't seem to be any flickering if we first select
+                // something in one display, but then unset the selection, and move
+                // it to the other display, so it's ok to do that.
+                if (this._searchAreaDocs.hasSelected())
+                  this._searchAreaDocs.selectUp();
+                else if (this._searchAreaApps.hasItems())
+                  this._searchAreaApps.selectUp();
+                else
+                  this._searchAreaDocs.selectUp();
+                return true;
+            } else if (symbol == Clutter.Down) {
+                if (!this._searchActive)
+                    return true;
+                if (this._searchAreaDocs.hasSelected())
+                  this._searchAreaDocs.selectDown();
+                else if (this._searchAreaApps.hasItems())
+                  this._searchAreaApps.selectDown();
+                else
+                  this._searchAreaDocs.selectDown();
+                return true;
+            }
+            return false;
+        }));
+
+        /***** Applications *****/
+
+        let appsHeader = new SectionHeader("APPLICATIONS");
+        this._appsSection = new Big.Box({ spacing: DEFAULT_PADDING });
+        this._appsSection.append(appsHeader.actor, Big.BoxPackFlags.NONE);
+
+        this._appsContent = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL });
+        this._appsSection.append(this._appsContent, Big.BoxPackFlags.EXPAND);
+        this._appWell = new AppDisplay.AppWell(this._width);
+        this._appsContent.append(this._appWell.actor, Big.BoxPackFlags.EXPAND);
+
+        this._moreAppsPane = null;
+        appsHeader.moreLink.connect('activated', Lang.bind(this, function (link) {
+            if (this._moreAppsPane == null) {
+                this._moreAppsPane = new ResultPane(this, this._detailsWidth);
+                this._moreAppsPane.packResults(AppDisplay.AppDisplay, true);
+                this._addPane(this._moreAppsPane);
+                link.setPane(this._moreAppsPane);
+           }
+        }));
+
+        this.dashContainer.append(this._appsSection, Big.BoxPackFlags.NONE);
+
+        /***** Documents *****/
+
+        this._docsSection = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
+                                          spacing: DEFAULT_PADDING });
+        this._moreDocsPane = null;
+
+        let docsHeader = new SectionHeader("RECENT DOCUMENTS");
+        this._docsSection.append(docsHeader.actor, Big.BoxPackFlags.NONE);
+
+        this._docDisplay = new DocDisplay.DocDisplay();
+        this._docDisplay.load();
+        this._docsSection.append(this._docDisplay.actor, Big.BoxPackFlags.EXPAND);
+
+        createPaneForDetails(this, this._docDisplay, this._detailsWidth);
+
+        docsHeader.moreLink.connect('activated', Lang.bind(this, function (link) {
+            if (this._moreDocsPane == null) {
+                this._moreDocsPane = new ResultPane(this, this._detailsWidth);
+                this._moreDocsPane.packResults(DocDisplay.DocDisplay, true);
+                this._addPane(this._moreDocsPane);
+                link.setPane(this._moreDocsPane);
+           }
+        }));
+
+        this.dashContainer.append(this._docsSection, Big.BoxPackFlags.EXPAND);
+    },
+
+    show: function() {
+        let global = Shell.Global.get();
+        global.stage.set_key_focus(this._searchEntry.entry);
+    },
+
+    hide: function() {
+        this._firstSelectAfterOverlayShow = true;
+        if (this._searchEntry.entry.text != '')
+            this._searchEntry.entry.text = '';
+        if (this._activePane != null)
+            this._activePane.close();
+    },
+
+    closePanes: function () {
+        if (this._activePane != null)
+            this._activePane.close();
+    },
+
+    _addPane: function(pane) {
+        pane.connect('open-state-changed', Lang.bind(this, function (pane, isOpen) {
+            if (isOpen) {
+                if (pane != this._activePane && this._activePane != null) {
+                    this._activePane.close();
+                }
+                this._activePane = pane;
+            } else if (pane == this._activePane) {
+                this._activePane = null;
+            }
+        }));
+        Main.overlay.addPane(pane);
+    }
+};
+Signals.addSignalMethods(Dash.prototype);
diff --git a/js/ui/docDisplay.js b/js/ui/docDisplay.js
index fac319f..fa9ad3c 100644
--- a/js/ui/docDisplay.js
+++ b/js/ui/docDisplay.js
@@ -18,19 +18,18 @@ const Main = imports.ui.main;
  *
  * docInfo - DocInfo object containing information about the document
  * currentSeconds - current number of seconds since the epoch
- * availableWidth - total width available for the item
  */
-function DocDisplayItem(docInfo, currentSecs, availableWidth) {
-    this._init(docInfo, currentSecs, availableWidth);
+function DocDisplayItem(docInfo, currentSecs) {
+    this._init(docInfo, currentSecs);
 }
 
 DocDisplayItem.prototype = {
     __proto__:  GenericDisplay.GenericDisplayItem.prototype,
 
-    _init : function(docInfo, currentSecs, availableWidth) {
-        GenericDisplay.GenericDisplayItem.prototype._init.call(this, availableWidth);
+    _init : function(docInfo, currentSecs) {
+        GenericDisplay.GenericDisplayItem.prototype._init.call(this);
         this._docInfo = docInfo;
-       
+
         this._setItemInfo(docInfo.name, "");
 
         this._timeoutTime = -1;
@@ -91,18 +90,16 @@ DocDisplayItem.prototype = {
 
 /* This class represents a display containing a collection of document items.
  * The documents are sorted by how recently they were last visited.
- *
- * width - width available for the display
  */
-function DocDisplay(width) {
-    this._init(width);
+function DocDisplay() {
+    this._init();
 }
 
 DocDisplay.prototype = {
     __proto__:  GenericDisplay.GenericDisplay.prototype,
 
-    _init : function(width) {
-        GenericDisplay.GenericDisplay.prototype._init.call(this, width);
+    _init : function() {
+        GenericDisplay.GenericDisplay.prototype._init.call(this);
         let me = this;
 
         // We keep a single timeout callback for updating last visited times
@@ -203,9 +200,9 @@ DocDisplay.prototype = {
     },
 
     // Creates a DocDisplayItem based on itemInfo, which is expected to be a DocInfo object.
-    _createDisplayItem: function(itemInfo, width) {
+    _createDisplayItem: function(itemInfo) {
         let currentSecs = new Date().getTime() / 1000;
-        let docDisplayItem = new DocDisplayItem(itemInfo, currentSecs, width);
+        let docDisplayItem = new DocDisplayItem(itemInfo, currentSecs);
         this._updateTimeoutCallback(docDisplayItem, currentSecs);
         return docDisplayItem;
     },
diff --git a/js/ui/genericDisplay.js b/js/ui/genericDisplay.js
index 3cec349..2f4a9e4 100644
--- a/js/ui/genericDisplay.js
+++ b/js/ui/genericDisplay.js
@@ -15,6 +15,7 @@ const Tidy = imports.gi.Tidy;
 const Button = imports.ui.button;
 const DND = imports.ui.dnd;
 const Link = imports.ui.link;
+const Main = imports.ui.main;
 
 const ITEM_DISPLAY_NAME_COLOR = new Clutter.Color();
 ITEM_DISPLAY_NAME_COLOR.from_pixel(0xffffffff);
@@ -28,20 +29,19 @@ const DISPLAY_CONTROL_SELECTED_COLOR = new Clutter.Color();
 DISPLAY_CONTROL_SELECTED_COLOR.from_pixel(0x112288ff);
 const PREVIEW_BOX_BACKGROUND_COLOR = new Clutter.Color();
 PREVIEW_BOX_BACKGROUND_COLOR.from_pixel(0xADADADf0);
-const HOT_PINK_DEBUG = new Clutter.Color();
-HOT_PINK_DEBUG.from_pixel(0xFF8888FF);
+
+const DEFAULT_PADDING = 4;
 
 const ITEM_DISPLAY_HEIGHT = 50;
 const ITEM_DISPLAY_ICON_SIZE = 48;
-const ITEM_DISPLAY_PADDING_TOP = 1;
+const ITEM_DISPLAY_PADDING = 1;
 const ITEM_DISPLAY_PADDING_RIGHT = 2;
 const DEFAULT_COLUMN_GAP = 6;
-const LABEL_HEIGHT = 16;
 
 const PREVIEW_ICON_SIZE = 96;
 const PREVIEW_BOX_PADDING = 6;
-const PREVIEW_BOX_SPACING = 4;
-const PREVIEW_BOX_CORNER_RADIUS = 10; 
+const PREVIEW_BOX_SPACING = DEFAULT_PADDING;
+const PREVIEW_BOX_CORNER_RADIUS = 10;
 // how far relative to the full item width the preview box should be placed
 const PREVIEW_PLACING = 3/4;
 const PREVIEW_DETAILS_MIN_WIDTH = PREVIEW_ICON_SIZE * 2;
@@ -49,25 +49,25 @@ const PREVIEW_DETAILS_MIN_WIDTH = PREVIEW_ICON_SIZE * 2;
 const INFORMATION_BUTTON_SIZE = 16;
 
 /* This is a virtual class that represents a single display item containing
- * a name, a description, and an icon. It allows selecting an item and represents 
+ * a name, a description, and an icon. It allows selecting an item and represents
  * it by highlighting it with a different background color than the default.
- *
- * availableWidth - total width available for the item
  */
-function GenericDisplayItem(availableWidth) {
-    this._init(availableWidth);
+function GenericDisplayItem() {
+    this._init();
 }
 
 GenericDisplayItem.prototype = {
-    _init: function(availableWidth) {
-        this._availableWidth = availableWidth;
+    _init: function() {
+        this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
+                                   spacing: ITEM_DISPLAY_PADDING,
+                                   reactive: true,
+                                   background_color: ITEM_DISPLAY_BACKGROUND_COLOR,
+                                   corner_radius: 4,
+                                   height: ITEM_DISPLAY_HEIGHT });
 
-        this.actor = new Clutter.Group({ reactive: true,
-                                         width: availableWidth,
-                                         height: ITEM_DISPLAY_HEIGHT });
         this.actor._delegate = this;
-        this.actor.connect('button-release-event', 
-                           Lang.bind(this, 
+        this.actor.connect('button-release-event',
+                           Lang.bind(this,
                                      function() {
                                          // Activates the item by launching it
                                          this.emit('activate');
@@ -77,11 +77,16 @@ GenericDisplayItem.prototype = {
         let draggable = DND.makeDraggable(this.actor);
         draggable.connect('drag-begin', Lang.bind(this, this._onDragBegin));
 
-        this._bg = new Big.Box({ background_color: ITEM_DISPLAY_BACKGROUND_COLOR,
-                                 corner_radius: 4,
-                                 x: 0, y: 0,
-                                 width: availableWidth, height: ITEM_DISPLAY_HEIGHT });
-        this.actor.add_actor(this._bg);
+        this._infoContent = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
+                                          spacing: DEFAULT_PADDING });
+        this.actor.append(this._infoContent, Big.BoxPackFlags.EXPAND);
+
+        this._iconBox = new Big.Box();
+        this._infoContent.append(this._iconBox, Big.BoxPackFlags.NONE);
+
+        this._infoText = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
+                                       spacing: DEFAULT_PADDING });
+        this._infoContent.append(this._infoText, Big.BoxPackFlags.EXPAND);
 
         let global = Shell.Global.get();
         let infoIconUri = "file://" + global.imagedir + "info.svg";
@@ -90,10 +95,14 @@ GenericDisplayItem.prototype = {
                                                                       INFORMATION_BUTTON_SIZE,
                                                                       INFORMATION_BUTTON_SIZE);
         this._informationButton = new Button.iconButton(this.actor, INFORMATION_BUTTON_SIZE, infoIcon);
-        this._informationButton.actor.x = availableWidth - ITEM_DISPLAY_PADDING_RIGHT - INFORMATION_BUTTON_SIZE;
-        this._informationButton.actor.y = ITEM_DISPLAY_HEIGHT / 2 - INFORMATION_BUTTON_SIZE / 2;
-
-        // Connecting to the button-press-event for the information button ensures that the actor, 
+        let buttonBox = new Big.Box({ width: INFORMATION_BUTTON_SIZE + 2 * DEFAULT_PADDING,
+                                      height: INFORMATION_BUTTON_SIZE,
+                                      padding_left: DEFAULT_PADDING, padding_right: DEFAULT_PADDING,
+                                      y_align: Big.BoxAlignment.CENTER });
+        buttonBox.append(this._informationButton.actor, Big.BoxPackFlags.NONE);
+        this.actor.append(buttonBox, Big.BoxPackFlags.END);
+
+        // Connecting to the button-press-event for the information button ensures that the actor,
         // which is a draggable actor, does not get the button-press-event and doesn't initiate
         // the dragging, which then prevents us from getting the button-release-event for the button.
         this._informationButton.actor.connect('button-press-event',
@@ -105,11 +114,9 @@ GenericDisplayItem.prototype = {
                                               Lang.bind(this,
                                                         function() {
                                                             // Selects the item by highlighting it and displaying its details
-                                                            this.emit('select');
+                                                            this.emit('show-details');
                                                             return true;
                                                         }));
-        this.actor.add_actor(this._informationButton.actor);
-        this._informationButton.actor.lower_bottom();
 
         this._name = null;
         this._description = null;
@@ -170,18 +177,17 @@ GenericDisplayItem.prototype = {
            color = ITEM_DISPLAY_BACKGROUND_COLOR;
            this._informationButton.forceShow(false);
        }
-       this._bg.background_color = color;
+       this.actor.background_color = color;
     },
 
     /*
-     * Returns an actor containing item details. In the future details can have more information than what 
+     * Returns an actor containing item details. In the future details can have more information than what
      * the preview pop-up has and be item-type specific.
      *
      * availableWidth - width available for displaying details
-     * availableHeight - height available for displaying details
-     */ 
-    createDetailsActor: function(availableWidth, availableHeight) {
- 
+     */
+    createDetailsActor: function(availableWidth) {
+
         let details = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
                                     spacing: PREVIEW_BOX_SPACING,
                                     width: availableWidth });
@@ -196,7 +202,7 @@ GenericDisplayItem.prototype = {
         let detailsName = new Clutter.Text({ color: ITEM_DISPLAY_NAME_COLOR,
                                              font_name: "Sans bold 14px",
                                              line_wrap: true,
-                                             text: this._name.text});
+                                             text: this._name.text });
         textDetails.append(detailsName, Big.BoxPackFlags.NONE);
 
         let detailsDescription = new Clutter.Text({ color: ITEM_DISPLAY_NAME_COLOR,
@@ -210,7 +216,7 @@ GenericDisplayItem.prototype = {
         mainDetails.append(textDetails, Big.BoxPackFlags.EXPAND);
 
         let previewIcon = this._createPreviewIcon();
-        let largePreviewIcon = this._createLargePreviewIcon(availableWidth, Math.max(0, availableHeight - mainDetails.height - PREVIEW_BOX_SPACING));
+        let largePreviewIcon = this._createLargePreviewIcon(availableWidth, -1);
 
         if (previewIcon != null && largePreviewIcon == null) {
             mainDetails.prepend(previewIcon, Big.BoxPackFlags.NONE);
@@ -266,29 +272,24 @@ GenericDisplayItem.prototype = {
         }
 
         this._icon = this._createIcon();
-        this.actor.add_actor(this._icon);
+        this._iconBox.append(this._icon, Big.BoxPackFlags.NONE);
 
-        let textWidth = this._availableWidth - (ITEM_DISPLAY_ICON_SIZE + 4) - INFORMATION_BUTTON_SIZE - ITEM_DISPLAY_PADDING_RIGHT;
         this._name = new Clutter.Text({ color: ITEM_DISPLAY_NAME_COLOR,
                                         font_name: "Sans 14px",
-                                        width: textWidth,
                                         ellipsize: Pango.EllipsizeMode.END,
-                                        text: nameText,
-                                        x: ITEM_DISPLAY_ICON_SIZE + 4,
-                                        y: ITEM_DISPLAY_PADDING_TOP });
-        this.actor.add_actor(this._name);
+                                        text: nameText });
+        this._infoText.append(this._name, Big.BoxPackFlags.EXPAND);
+
         this._description = new Clutter.Text({ color: ITEM_DISPLAY_DESCRIPTION_COLOR,
                                                font_name: "Sans 12px",
-                                               width: textWidth,
                                                ellipsize: Pango.EllipsizeMode.END,
-                                               text: descriptionText ? descriptionText : "",
-                                               x: this._name.x,
-                                               y: this._name.height + 4 });
-        this.actor.add_actor(this._description);
+                                               text: descriptionText ? descriptionText : ""
+                                            });
+        this._infoText.append(this._description, Big.BoxPackFlags.EXPAND);
     },
 
     // Sets the description text for the item, including the description text
-    // in the details actors that have been created for the item. 
+    // in the details actors that have been created for the item.
     _setDescriptionText: function(text) {
         this._description.text = text;
         for (let i = 0; i < this._detailsDescriptions.length; i++) {
@@ -332,22 +333,18 @@ Signals.addSignalMethods(GenericDisplayItem.prototype);
 
 /* This is a virtual class that represents a display containing a collection of items
  * that can be filtered with a search string.
- *
- * width - width available for the display
  */
-function GenericDisplay(width) {
-    this._init(width);
+function GenericDisplay() {
+    this._init();
 }
 
 GenericDisplay.prototype = {
-    _init : function(width) {
+    _init : function() {
         this._search = '';
         this._expanded = false;
-        this._width = width;
 
         this._maxItemsPerPage = null;
-        this._list = new Shell.OverflowList({ width: this._width,
-                                              spacing: 6.0,
+        this._list = new Shell.OverflowList({ spacing: 6.0,
                                               item_height: ITEM_DISPLAY_HEIGHT });
 
         this._list.connect('notify::n-pages', Lang.bind(this, function (grid, alloc) {
@@ -364,6 +361,7 @@ GenericDisplay.prototype = {
         this._matchedItems = [];
         // map<itemId, GenericDisplayItem>
         this._displayedItems = {};
+        this._openDetailIndex = -1;
         this._selectedIndex = -1;
         // These two are public - .actor is the normal "actor subclass" property,
         // but we also expose a .displayControl actor which is separate.
@@ -372,24 +370,10 @@ GenericDisplay.prototype = {
         this.displayControl = new Big.Box({ background_color: ITEM_DISPLAY_BACKGROUND_COLOR,
                                             spacing: 12,
                                             orientation: Big.BoxOrientation.HORIZONTAL});
-
-        this._availableWidthForItemDetails = width;
-        this.selectedItemDetails = new Big.Box({});
     },
 
     //// Public methods ////
 
-    // Sets dimensions available for the item details display.
-    setAvailableDimensionsForItemDetails: function(availableWidth, availableHeight) {
-        this._availableWidthForItemDetails = availableWidth;
-        this._availableHeightForItemDetails = availableHeight;
-    },
-
-    // Returns dimensions available for the item details display.
-    getAvailableDimensionsForItemDetails: function() {
-        return [this._availableWidthForItemDetails, this._availableHeightForItemDetails];
-    },
-
     // Sets the search string and displays the matching items.
     setSearch: function(text) {
         this._search = text.toLowerCase();
@@ -400,8 +384,9 @@ GenericDisplay.prototype = {
     activateSelected: function() {
         if (this._selectedIndex != -1) {
             let selected = this._findDisplayedByIndex(this._selectedIndex);
-            selected.launch()
-            this.emit('activated');
+            selected.launch();
+            this.unsetSelected();
+            Main.overlay.hide();
         }
     },
 
@@ -463,17 +448,15 @@ GenericDisplay.prototype = {
         return this._list.displayedCount > 0;
     },
 
-    // Updates the displayed items and makes the display actor visible.
-    show: function() {
-        this._list.show();
+    // Load the initial state
+    load: function() {
         this._redisplay(true);
     },
 
-    // Hides the display actor.
-    hide: function() {
-        this._list.hide();
+    // Should be called when the display is closed
+    resetState: function() {
         this._filterReset();
-        this._removeAllDisplayItems();
+        this._openDetailIndex = -1;
     },
 
     // Returns an actor which acts as a sidebar; this is used for
@@ -482,6 +465,11 @@ GenericDisplay.prototype = {
         return null;
     },
 
+    createDetailsForIndex: function(index, width, height) {
+        let item = this._findDisplayedByIndex(index);
+        return item.createDetailsActor(width, height);
+    },
+
     //// Protected methods ////
 
     /*
@@ -538,7 +526,7 @@ GenericDisplay.prototype = {
         }
 
         let itemInfo = this._allItems[itemId];
-        let displayItem = this._createDisplayItem(itemInfo, this._width);
+        let displayItem = this._createDisplayItem(itemInfo);
 
         displayItem.connect('activate',
                             Lang.bind(this,
@@ -548,11 +536,18 @@ GenericDisplay.prototype = {
                                           this.activateSelected();
                                       }));
 
-        displayItem.connect('select', 
+        displayItem.connect('show-details',
                             Lang.bind(this,
                                       function() {
-                                          // update the selection
-                                          this._selectIndex(this._getIndexOfDisplayedActor(displayItem.actor));
+                                          let index = this._getIndexOfDisplayedActor(displayItem.actor);
+                                          /* Close the details pane if already open */
+                                          if (index == this._openDetailIndex) {
+                                              this._openDetailIndex = -1;
+                                              this.emit('show-details', -1);
+                                          } else {
+                                              this._openDetailIndex = index;
+                                              this.emit('show-details', index);
+                                          }
                                       }));
         this._list.add_actor(displayItem.actor);
         this._displayedItems[itemId] = displayItem;
@@ -564,11 +559,11 @@ GenericDisplay.prototype = {
         let displayItem = this._displayedItems[itemId];
         let displayItemIndex = this._getIndexOfDisplayedActor(displayItem.actor);
 
-        if (this.hasSelected() && (count == 1 || !this._list.visible)) {
+        if (this.hasSelected() && count == 1) {
             this.unsetSelected();
         } else if (this.hasSelected() && displayItemIndex < this._selectedIndex) {
             this.selectUp();
-        } 
+        }
 
         displayItem.destroy();
 
@@ -603,9 +598,6 @@ GenericDisplay.prototype = {
      *             their own while the user was browsing through the result pages.
      */
     _redisplay: function(resetPage) {
-        if (!this._list.visible)
-            return;
-
         this._refreshCache();
         if (!this._filterActive())
             this._setDefaultList();
@@ -729,7 +721,6 @@ GenericDisplay.prototype = {
                 let pageControl = new Link.Link({ color: (i == pageNumber) ? DISPLAY_CONTROL_SELECTED_COLOR : ITEM_DISPLAY_DESCRIPTION_COLOR,
                                                   font_name: "Sans Bold 16px",
                                                   text: (i+1) + "",
-                                                  height: LABEL_HEIGHT,
                                                   reactive: (i == pageNumber) ? false : true});
                 this.displayControl.append(pageControl.actor, Big.BoxPackFlags.NONE);
 
@@ -794,22 +785,13 @@ GenericDisplay.prototype = {
 
         // If the item is already selected, all we do is toggling the details pane.
         if (this._selectedIndex == index && index >= 0) {
-            this.emit('toggle-details');
+            this.emit('details', index);
             return;
         }
 
         // Cleanup from the previous item
         if (this._selectedIndex >= 0) {
             this._findDisplayedByIndex(this._selectedIndex).markSelected(false);
-
-            // Calling destroy() gets large image previews released as quickly as
-            // possible, if we just removed them, they might hang around for a while
-            // until the actor was garbage collected.
-            let children = this.selectedItemDetails.get_children();
-            for (let i = 0; i < children.length; i++)
-                children[i].destroy();
-
-            this.selectedItemDetails.remove_all();
         }
 
         this._selectedIndex = index;
@@ -819,8 +801,6 @@ GenericDisplay.prototype = {
         // Mark the new item as selected and create its details pane
         let item = this._findDisplayedByIndex(index);
         item.markSelected(true);
-        this.selectedItemDetails.append(item.createDetailsActor(this._availableWidthForItemDetails,
-            this._availableHeightForItemDetails), Big.BoxPackFlags.NONE);
         this.emit('selected');
     }
 };
diff --git a/js/ui/overlay.js b/js/ui/overlay.js
index 235b0ee..894d3c6 100644
--- a/js/ui/overlay.js
+++ b/js/ui/overlay.js
@@ -15,6 +15,7 @@ const GenericDisplay = imports.ui.genericDisplay;
 const Link = imports.ui.link;
 const Main = imports.ui.main;
 const Panel = imports.ui.panel;
+const Dash = imports.ui.dash;
 const Tweener = imports.ui.tweener;
 const Workspaces = imports.ui.workspaces;
 
@@ -25,19 +26,6 @@ ROOT_OVERLAY_COLOR.from_pixel(0x000000bb);
 // than 3/2, because the rule of thirds is used for positioning (see below).
 const BACKGROUND_SCALE = 2;
 
-const LABEL_HEIGHT = 16;
-const DASH_MIN_WIDTH = 250;
-const DASH_OUTER_PADDING = 4;
-const DASH_SECTION_PADDING = 6;
-const DASH_SECTION_SPACING = 6;
-const DASH_CORNER_RADIUS = 5;
-// This is the height of section components other than the item display.
-const DASH_SECTION_MISC_HEIGHT = (LABEL_HEIGHT + DASH_SECTION_SPACING) * 2 + DASH_SECTION_PADDING;
-const DASH_SEARCH_BG_COLOR = new Clutter.Color();
-DASH_SEARCH_BG_COLOR.from_pixel(0xffffffff);
-const DASH_TEXT_COLOR = new Clutter.Color();
-DASH_TEXT_COLOR.from_pixel(0xffffffff);
-
 // Time for initial animation going into overlay mode
 const ANIMATION_TIME = 0.25;
 
@@ -61,6 +49,8 @@ const ROWS_REGULAR_SCREEN = 8;
 const COLUMNS_WIDE_SCREEN = 5;
 const ROWS_WIDE_SCREEN = 10;
 
+const DEFAULT_PADDING = 4;
+
 // Padding around workspace grid / Spacing between Dash and Workspaces
 const WORKSPACE_GRID_PADDING = 12;
 
@@ -75,27 +65,6 @@ const STATE_ACTIVE = true;
 const STATE_PENDING_INACTIVE = false;
 const STATE_INACTIVE = false;
 
-// The dash has a slightly transparent blue background with a gradient.
-const DASH_LEFT_COLOR = new Clutter.Color();
-DASH_LEFT_COLOR.from_pixel(0x0d131fbb);
-const DASH_MIDDLE_COLOR = new Clutter.Color();
-DASH_MIDDLE_COLOR.from_pixel(0x0d131faa);
-const DASH_RIGHT_COLOR = new Clutter.Color();
-DASH_RIGHT_COLOR.from_pixel(0x0d131fcc);
-
-const DASH_BORDER_COLOR = new Clutter.Color();
-DASH_BORDER_COLOR.from_pixel(0x213b5dfa);
-
-const DASH_BORDER_WIDTH = 2;
-
-// The results and details panes have a somewhat transparent blue background with a gradient.
-const PANE_LEFT_COLOR = new Clutter.Color();
-PANE_LEFT_COLOR.from_pixel(0x0d131ff4);
-const PANE_MIDDLE_COLOR = new Clutter.Color();
-PANE_MIDDLE_COLOR.from_pixel(0x0d131ffa);
-const PANE_RIGHT_COLOR = new Clutter.Color();
-PANE_RIGHT_COLOR.from_pixel(0x0d131ff4);
-
 const SHADOW_COLOR = new Clutter.Color();
 SHADOW_COLOR.from_pixel(0x00000033);
 const TRANSPARENT_COLOR = new Clutter.Color();
@@ -109,630 +78,78 @@ let wideScreen = false;
 let displayGridColumnWidth = null;
 let displayGridRowHeight = null;
 
-function SearchEntry(width) {
-    this._init(width);
-}
-
-SearchEntry.prototype = {
-    _init : function() {
-        this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
-                                   y_align: Big.BoxAlignment.CENTER,
-                                   background_color: DASH_SEARCH_BG_COLOR,
-                                   corner_radius: 4,
-                                   spacing: 4,
-                                   padding_left: 4,
-                                   padding_right: 4,
-                                   height: 24
-                                 });
-
-        let icontheme = Gtk.IconTheme.get_default();
-        let searchIconTexture = new Clutter.Texture({});
-        let searchIconPath = icontheme.lookup_icon('gtk-find', 16, 0).get_filename();
-        searchIconTexture.set_from_file(searchIconPath);
-        this.actor.append(searchIconTexture, 0);
-
-        // We need to initialize the text for the entry to have the cursor displayed
-        // in it. See http://bugzilla.openedhand.com/show_bug.cgi?id=1365
-        this.entry = new Clutter.Text({ font_name: "Sans 14px",
-                                        editable: true,
-                                        activatable: true,
-                                        singleLineMode: true,
-                                        text: ""
-                                      });
-        this.entry.connect('text-changed', Lang.bind(this, function (e) {
-            let text = this.entry.text;
-        }));
-        this.actor.append(this.entry, Big.BoxPackFlags.EXPAND);
-    }
-};
-
-function ItemResults(resultsWidth, resultsHeight, displayClass, labelText) {
-    this._init(resultsWidth, resultsHeight, displayClass, labelText);
-}
-
-ItemResults.prototype = {
-    _init: function(resultsWidth, resultsHeight, displayClass, labelText) {
-        this._resultsWidth = resultsWidth;
-        this._resultsHeight = resultsHeight;
-
-        this.actor = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
-                                   height: resultsHeight,
-                                   padding: DASH_SECTION_PADDING + DASH_BORDER_WIDTH,
-                                   spacing: DASH_SECTION_SPACING });
-
-        this._resultsText = new Clutter.Text({ color: DASH_TEXT_COLOR,
-                                               font_name: "Sans Bold 14px",
-                                               text: labelText });
-
-        this.resultsContainer = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
-                                         spacing: 4 });
-        this.navContainer = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL });
-        this.resultsContainer.append(this.navContainer, Big.BoxPackFlags.NONE);
-
-        // LABEL_HEIGHT is the height of this._resultsText and GenericDisplay.LABEL_HEIGHT is the height
-        // of the display controls.
-        this._displayHeight = resultsHeight - LABEL_HEIGHT - GenericDisplay.LABEL_HEIGHT - DASH_SECTION_SPACING * 2;
-        this.display = new displayClass(resultsWidth);
-
-        this.navArea = this.display.getNavigationArea();
-        if (this.navArea)
-            this.navContainer.append(this.navArea, Big.BoxPackFlags.EXPAND);
-
-        this.resultsContainer.append(this.display.actor, Big.BoxPackFlags.EXPAND);
-
-        this.controlBox = new Big.Box({ x_align: Big.BoxAlignment.CENTER });
-        this.controlBox.append(this.display.displayControl, Big.BoxPackFlags.NONE);
-
-        this._unsetSearchMode();
-    },
-
-    _setSearchMode: function() {
-        if (this.navArea)
-            this.navArea.hide();
-        this.actor.height = this._resultsHeight /  NUMBER_OF_SECTIONS_IN_SEARCH;
-        let displayHeight = this._displayHeight - this._resultsHeight * (NUMBER_OF_SECTIONS_IN_SEARCH - 1) /  NUMBER_OF_SECTIONS_IN_SEARCH;
-        this.actor.remove_all();
-        this.actor.append(this._resultsText, Big.BoxPackFlags.NONE);
-        this.actor.append(this.resultsContainer, Big.BoxPackFlags.EXPAND);
-        this.actor.append(this.controlBox, Big.BoxPackFlags.END);
-    },
-
-    _unsetSearchMode: function() {
-        if (this.navArea)
-            this.navArea.show();
-        this.actor.height = this._resultsHeight;
-        this.actor.remove_all();
-        this.actor.append(this._resultsText, Big.BoxPackFlags.NONE);
-        this.actor.append(this.resultsContainer, Big.BoxPackFlags.EXPAND);
-        this.actor.append(this.controlBox, Big.BoxPackFlags.END);
-    }
-}
-
-function Dash() {
+function Overlay() {
     this._init();
 }
 
-Dash.prototype = {
+Overlay.prototype = {
     _init : function() {
         let me = this;
 
-        this._moreAppsMode = false;
-        this._moreDocsMode = false;
-
-        this._width = displayGridColumnWidth;
-
-        this._displayWidth = displayGridColumnWidth - DASH_SECTION_PADDING * 2;
-        this._resultsWidth = displayGridColumnWidth;
-        this._detailsWidth = displayGridColumnWidth * 2;
-
-        let global = Shell.Global.get();
-
-        let dashHeight = global.screen_height - Panel.PANEL_HEIGHT;
-        let resultsHeight = global.screen_height - Panel.PANEL_HEIGHT;
-        let detailsHeight = global.screen_height - Panel.PANEL_HEIGHT;
-
-        // Size the actor to 0x0 so as not to interfere with DND
-        this.actor = new Clutter.Group({ width: 0, height: 0 });
-        this.actor.height = global.screen_height;
-
-
-        // dashPane, as well as results and details panes need to be reactive so that the clicks in unoccupied places on them
-        // are not passed to the transparent background underneath them. This background is used for the workspaces area when
-        // the additional dash panes are being shown and it handles clicks by closing the additional panes, so that the user
-        // can interact with the workspaces. However, this behavior is not desirable when the click is actually over a pane.
-        //
-        // We have to make the individual panes reactive instead of making the whole dash actor reactive because the width
-        // of the Group actor ends up including the width of its hidden children, so we were getting a reactive object as
-        // wide as the details pane that was blocking the clicks to the workspaces underneath it even when the details pane
-        // was actually hidden. 
-        let dashPane = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
-                                     x: 0,
-                                     y: Panel.PANEL_HEIGHT,
-                                     width: this._width + SHADOW_WIDTH,
-                                     height: dashHeight,
-                                     reactive: true});
-
-        let dashBackground = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
-                                           width: this._width,
-                                           height: dashHeight });
-
-        dashPane.append(dashBackground, Big.BoxPackFlags.EXPAND);
-        
-        let dashLeft = Shell.create_horizontal_gradient(DASH_LEFT_COLOR,
-                                                        DASH_MIDDLE_COLOR);
-        let dashRight = Shell.create_horizontal_gradient(DASH_MIDDLE_COLOR,
-                                                         DASH_RIGHT_COLOR);
-        let dashShadow = Shell.create_horizontal_gradient(SHADOW_COLOR,
-                                                          TRANSPARENT_COLOR);
-        dashShadow.set_width(SHADOW_WIDTH);
-        
-        dashBackground.append(dashLeft, Big.BoxPackFlags.EXPAND);
-        dashBackground.append(dashRight, Big.BoxPackFlags.EXPAND);
-        dashPane.append(dashShadow, Big.BoxPackFlags.NONE);
-
-        this.actor.add_actor(dashPane);
-
-        this.dashOuterContainer = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
-                                               x: 0,
-                                               y: dashPane.y,
-                                               width: this._width,
-                                               height: dashHeight,
-                                               padding: DASH_OUTER_PADDING
-                                             });
-        this.actor.add_actor(this.dashOuterContainer);
-        this.dashContainer = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL });
-        this.dashOuterContainer.append(this.dashContainer, Big.BoxPackFlags.EXPAND);
-
-        this._searchEntry = new SearchEntry();
-        this.dashContainer.append(this._searchEntry.actor, Big.BoxPackFlags.NONE);
-
-        this._searchQueued = false;
-        this._searchEntry.entry.connect('text-changed', function (se, prop) {
-            if (me._searchQueued)
-                return;
-            me._searchQueued = true;
-            Mainloop.timeout_add(250, function() {
-                // Strip leading and trailing whitespace
-                let text = me._searchEntry.entry.text.replace(/^\s+/g, "").replace(/\s+$/g, "");
-                me._searchQueued = false;
-                me._resultsAppsSection.display.setSearch(text);
-                me._resultsDocsSection.display.setSearch(text);
-                if (text == '')
-                    me._unsetSearchMode();
-                else 
-                    me._setSearchMode();
-                   
-                return false;
-            });
-        });
-        this._searchEntry.entry.connect('activate', function (se) {
-            // only one of the displays will have an item selected, so it's ok to
-            // call activateSelected() on all of them
-            me._docDisplay.activateSelected();
-            me._resultsAppsSection.display.activateSelected();
-            me._resultsDocsSection.display.activateSelected();
-            return true;
-        });
-        this._searchEntry.entry.connect('key-press-event', function (se, e) {
-            let symbol = Shell.get_event_key_symbol(e);
-            if (symbol == Clutter.Escape) {
-                // Escape will keep clearing things back to the desktop. First, if
-                // we have active text, we remove it.
-                if (me._searchEntry.entry.text != '')
-                    me._searchEntry.entry.text = '';
-                // Next, if we're in one of the "more" modes or showing the details pane, close them
-                else if (me._resultsShowing())
-                    me.unsetMoreMode();
-                // Finally, just close the overlay entirely
-                else
-                    me.emit('activated');
-                return true;
-            } else if (symbol == Clutter.Up) {
-                if (!me._resultsShowing())
-                    return true;
-                // selectUp and selectDown wrap around in their respective displays
-                // too, but there doesn't seem to be any flickering if we first select
-                // something in one display, but then unset the selection, and move
-                // it to the other display, so it's ok to do that.
-                if (me._resultsDocsSection.display.hasSelected())
-                  me._resultsDocsSection.display.selectUp();
-                else if (me._resultsAppsSection.display.hasItems())
-                  me._resultsAppsSection.display.selectUp();
-                else
-                  me._resultsDocsSection.display.selectUp();
-                return true;
-            } else if (symbol == Clutter.Down) {
-                if (!me._resultsShowing())
-                    return true;
-                if (me._resultsDocsSection.display.hasSelected())
-                  me._resultsDocsSection.display.selectDown();
-                else if (me._resultsAppsSection.display.hasItems())
-                  me._resultsAppsSection.display.selectDown();
-                else
-                  me._resultsDocsSection.display.selectDown();
-                return true;
-            }
-            return false;
-        });
-
-        this._appsText = new Clutter.Text({ color: DASH_TEXT_COLOR,
-                                            font_name: "Sans Bold 14px",
-                                            text: "Applications",
-                                            height: LABEL_HEIGHT});
-        this._appsSection = new Big.Box({ padding_top: DASH_SECTION_PADDING,
-                                          spacing: DASH_SECTION_SPACING});
-        this._appsSection.append(this._appsText, Big.BoxPackFlags.NONE);
-
-        this._itemDisplayHeight = global.screen_height - this._appsSection.y - DASH_SECTION_MISC_HEIGHT * 2;
-        
-        this._appsContent = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL });
-        this._appsSection.append(this._appsContent, Big.BoxPackFlags.EXPAND);
-        this._appWell = new AppDisplay.AppWell(this._displayWidth);
-        this._appsContent.append(this._appWell.actor, Big.BoxPackFlags.EXPAND);
-
-        let moreAppsBox = new Big.Box({x_align: Big.BoxAlignment.END});
-        this._moreAppsLink = new Link.Link({ color: DASH_TEXT_COLOR,
-                                             font_name: "Sans Bold 14px",
-                                             text: "More...",
-                                             height: LABEL_HEIGHT });
-        moreAppsBox.append(this._moreAppsLink.actor, Big.BoxPackFlags.EXPAND);
-        this._appsSection.append(moreAppsBox, Big.BoxPackFlags.NONE);
-
-        this.dashContainer.append(this._appsSection, Big.BoxPackFlags.NONE);
-
-        this._appsSectionDefaultHeight = this._appsSection.height;
-
-        this._docsSection = new Big.Box({ padding_top: DASH_SECTION_PADDING,
-                                          padding_bottom: DASH_SECTION_PADDING,
-                                          spacing: DASH_SECTION_SPACING});
-
-        this._docsText = new Clutter.Text({ color: DASH_TEXT_COLOR,
-                                            font_name: "Sans Bold 14px",
-                                            text: "Recent Documents",
-                                            height: LABEL_HEIGHT});
-        this._docsSection.append(this._docsText, Big.BoxPackFlags.NONE);
-
-        this._docDisplay = new DocDisplay.DocDisplay(this._displayWidth);
-        this._docsSection.append(this._docDisplay.actor, Big.BoxPackFlags.EXPAND);
-
-        let moreDocsBox = new Big.Box({x_align: Big.BoxAlignment.END});
-        this._moreDocsLink = new Link.Link({ color: DASH_TEXT_COLOR,
-                                             font_name: "Sans Bold 14px",
-                                             text: "More...",
-                                             height: LABEL_HEIGHT });
-        moreDocsBox.append(this._moreDocsLink.actor, Big.BoxPackFlags.EXPAND);
-        this._docsSection.append(moreDocsBox, Big.BoxPackFlags.NONE);
-
-        this.dashContainer.append(this._docsSection, Big.BoxPackFlags.EXPAND);
-
-        this._docsSectionDefaultHeight = this._docsSection.height;
-
-        // The "More" or search results area
-        this._resultsAppsSection = new ItemResults(this._resultsWidth, resultsHeight, AppDisplay.AppDisplay, "Applications");
-        this._resultsDocsSection = new ItemResults(this._resultsWidth, resultsHeight, DocDisplay.DocDisplay, "Documents");
-
-        this._resultsPane = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
-                                          x: this._width,
-                                          y: Panel.PANEL_HEIGHT,
-                                          height: resultsHeight,
-                                          reactive: true });
-
-        let resultsBackground = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
-                                              height: resultsHeight,
-                                              corner_radius: DASH_CORNER_RADIUS,
-                                              border: DASH_BORDER_WIDTH,
-                                              border_color: DASH_BORDER_COLOR });
-        this._resultsPane.add_actor(resultsBackground);
-
-        let resultsLeft = Shell.create_horizontal_gradient(PANE_LEFT_COLOR,
-                                                           PANE_MIDDLE_COLOR);
-        let resultsRight = Shell.create_horizontal_gradient(PANE_MIDDLE_COLOR,
-                                                            PANE_RIGHT_COLOR);
-        let resultsShadow = Shell.create_horizontal_gradient(SHADOW_COLOR,
-                                                             TRANSPARENT_COLOR);
-        resultsShadow.set_width(SHADOW_WIDTH);
-
-        resultsBackground.append(resultsLeft, Big.BoxPackFlags.EXPAND);
-        resultsBackground.append(resultsRight, Big.BoxPackFlags.EXPAND);
-        this._resultsPane.add_actor(resultsShadow);
-        this._resultsPane.connect('notify::allocation', Lang.bind(this, function (b, a) {
-            let width = this._resultsPane.width;
-            resultsBackground.width = width;
-            resultsShadow.width = width;
-        }));
-
-        this.actor.add_actor(this._resultsPane);
-        this._resultsPane.hide();
-
-        this._detailsPane = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
-                                          y: Panel.PANEL_HEIGHT,
-                                          width: this._detailsWidth + SHADOW_WIDTH,
-                                          height: detailsHeight,
-                                          reactive: true });
-        this._firstSelectAfterOverlayShow = true;
-
-        let detailsBackground = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
-                                              width: this._detailsWidth,
-                                              height: detailsHeight,
-                                              corner_radius: DASH_CORNER_RADIUS,
-                                              border: DASH_BORDER_WIDTH,
-                                              border_color: DASH_BORDER_COLOR });
-
-        this._detailsPane.append(detailsBackground, Big.BoxPackFlags.EXPAND);
-
-        let detailsLeft = Shell.create_horizontal_gradient(PANE_LEFT_COLOR,
-                                                           PANE_MIDDLE_COLOR);
-        let detailsRight = Shell.create_horizontal_gradient(PANE_MIDDLE_COLOR,
-                                                            PANE_RIGHT_COLOR);
-        let detailsShadow = Shell.create_horizontal_gradient(SHADOW_COLOR,
-                                                             TRANSPARENT_COLOR);
-        detailsShadow.set_width(SHADOW_WIDTH);
-        
-        detailsBackground.append(detailsLeft, Big.BoxPackFlags.EXPAND);
-        detailsBackground.append(detailsRight, Big.BoxPackFlags.EXPAND);
-        this._detailsPane.append(detailsShadow, Big.BoxPackFlags.NONE);
-
-        this._detailsContent = new Big.Box({ padding: DASH_SECTION_PADDING + DASH_BORDER_WIDTH });
-        this._detailsPane.add_actor(this._detailsContent);
-
-        this.actor.add_actor(this._detailsPane);
-        this._detailsPane.hide();
-
-        let itemDetailsAvailableWidth = this._detailsWidth - DASH_SECTION_PADDING * 2 - DASH_BORDER_WIDTH * 2;
-        let itemDetailsAvailableHeight = detailsHeight - DASH_SECTION_PADDING * 2 - DASH_BORDER_WIDTH * 2;
-
-        this._docDisplay.setAvailableDimensionsForItemDetails(itemDetailsAvailableWidth, itemDetailsAvailableHeight);
-        this._resultsAppsSection.display.setAvailableDimensionsForItemDetails(itemDetailsAvailableWidth, itemDetailsAvailableHeight);
-        this._resultsDocsSection.display.setAvailableDimensionsForItemDetails(itemDetailsAvailableWidth, itemDetailsAvailableHeight);
-
-        /* Proxy the activated signals */
-        this._appWell.connect('activated', function(well) {
-            me.emit('activated');
-        });
-        this._docDisplay.connect('activated', function(docDisplay) {
-            me.emit('activated');
-        });
-        this._resultsAppsSection.display.connect('activated', function(resultsAppsDisplay) {
-            me.emit('activated');
-        });
-        this._resultsDocsSection.display.connect('activated', function(resultsDocsDisplay) {
-            me.emit('activated');
-        });
-        this._docDisplay.connect('selected', Lang.bind(this, function(docDisplay) {
-            this._resultsDocsSection.display.unsetSelected();
-            this._resultsAppsSection.display.unsetSelected();
-            this._showDetails();
-            this._detailsContent.remove_all();
-            this._detailsContent.append(this._docDisplay.selectedItemDetails, Big.BoxPackFlags.NONE);
-        }));
-        this._docDisplay.connect('toggle-details', Lang.bind(this, function(docDisplay) {
-            this._toggleDetails();
-        }));
-        this._resultsDocsSection.display.connect('selected', Lang.bind(this, function(resultsDocDisplay) {
-            this._docDisplay.unsetSelected();
-            this._resultsAppsSection.display.unsetSelected();
-            this._showDetails();
-            this._detailsContent.remove_all();
-            this._detailsContent.append(this._resultsDocsSection.display.selectedItemDetails, Big.BoxPackFlags.NONE);
-        }));
-        this._resultsDocsSection.display.connect('toggle-details', Lang.bind(this, function(resultsDocDisplay) {
-            this._toggleDetails();
-        }));
-        this._resultsAppsSection.display.connect('selected', Lang.bind(this, function(resultsAppDisplay) {
-            this._docDisplay.unsetSelected();
-            this._resultsDocsSection.display.unsetSelected();
-            this._showDetails();
-            this._detailsContent.remove_all();
-            this._detailsContent.append(this._resultsAppsSection.display.selectedItemDetails, Big.BoxPackFlags.NONE);
-        }));
-
-        this._moreAppsLink.connect('clicked',
-            function(o, event) {
-                if (me._moreAppsMode) {
-                    me._unsetMoreAppsMode();
-                }  else {
-                    me._setMoreAppsMode();
-                }
-            });
-
-        this._moreDocsLink.connect('clicked',
-            function(o, event) {
-                if (me._moreDocsMode) {
-                    me._unsetMoreDocsMode();
-                }  else {
-                    me._setMoreDocsMode();
-                }
-            });
-    },
-
-    show: function() {
         let global = Shell.Global.get();
 
-        this._appsContent.show();
-        this._docDisplay.show();
-        global.stage.set_key_focus(this._searchEntry.entry);
-    },
-
-    hide: function() {
-        this._firstSelectAfterOverlayShow = true;
-        this._appsContent.hide();
-        this._docDisplay.hide(); 
-        this._searchEntry.entry.text = '';
-        this.unsetMoreMode();
-    },
-
-    unsetMoreMode: function() {
-        this._unsetMoreAppsMode();
-        this._unsetMoreDocsMode();
-        if (this._detailsShowing()) { 
-             this._detailsPane.hide();
-             this.emit('panes-removed');
-        }
-        this._unsetSearchMode();
-    },
-
-    // Sets the 'More' mode for browsing applications.
-    _setMoreAppsMode: function() {
-        if (this._moreAppsMode)
-            return;
-
-        this._unsetMoreDocsMode();
-        this._unsetSearchMode();
-        this._moreAppsMode = true;
-
-        this._resultsAppsSection.display.show();
-        this._resultsPane.append(this._resultsAppsSection.actor, Big.BoxPackFlags.EXPAND);
-        this._resultsPane.show();
-        this._repositionDetails();
-
-        this._moreAppsLink.setText("Less...");
-        this.emit('panes-displayed');
-    },
-
-    // Unsets the 'More' mode for browsing applications.
-    _unsetMoreAppsMode: function() {
-        if (!this._moreAppsMode)
-            return;
-
-        this._moreAppsMode = false;
-
-        this._resultsPane.remove_actor(this._resultsAppsSection.actor);
-        this._resultsAppsSection.display.hide();
-        this._resultsPane.hide(); 
-
-        this._moreAppsLink.setText("More...");
-
-        this._hideDetails();
-    },   
- 
-    // Sets the 'More' mode for browsing documents.
-    _setMoreDocsMode: function() {
-        if (this._moreDocsMode)
-            return;
-
-        this._unsetMoreAppsMode();
-        this._unsetSearchMode();
-        this._moreDocsMode = true;
-
-        this._resultsDocsSection.display.show();
-        this._resultsPane.append(this._resultsDocsSection.actor, Big.BoxPackFlags.EXPAND);
-        this._resultsPane.show();
-        this._repositionDetails();
-
-        this._moreDocsLink.setText("Less...");
-
-        this.emit('panes-displayed');
-    },
-
-    // Unsets the 'More' mode for browsing documents. 
-    _unsetMoreDocsMode: function() {
-        if (!this._moreDocsMode)
-            return;
-
-        this._moreDocsMode = false;
-
-        this._resultsPane.hide();
-        this._resultsPane.remove_actor(this._resultsDocsSection.actor);
-        this._resultsDocsSection.display.hide();
-
-        this._moreDocsLink.setText("More...");
-
-        this._hideDetails();
-    },
-
-    _setSearchMode: function() {
-
-        if (this._resultsShowing())
-            return;
-
-        this._resultsAppsSection._setSearchMode();
-        this._resultsAppsSection.display.show();
-        this._resultsPane.append(this._resultsAppsSection.actor, Big.BoxPackFlags.EXPAND);
-
-        this._resultsDocsSection._setSearchMode();
-        this._resultsDocsSection.display.show();
-        this._resultsPane.append(this._resultsDocsSection.actor, Big.BoxPackFlags.EXPAND);
-
-        this._resultsPane.show();
-        this._repositionDetails();
+        this._group = new Clutter.Group();
+        this._group._delegate = this;
 
-        this.emit('panes-displayed');
-    },
+        this.visible = false;
+        this._hideInProgress = false;
 
-    _unsetSearchMode: function() {
+        this._recalculateGridSizes();
 
-        if (this._moreDocsMode || this._moreAppsMode || !this._resultsShowing())
-            return;
+        // A scaled root pixmap actor is used as a background. It is zoomed in
+        // to the lower right intersection of the lines that divide the image
+        // evenly in a 3x3 grid. This is based on the rule of thirds, a
+        // compositional rule of thumb in visual arts. The choice for the
+        // lower right point is based on a quick survey of GNOME wallpapers.
+        this._background = global.create_root_pixmap_actor();
+        this._group.add_actor(this._background);
 
-        this._resultsPane.hide();
-        this._repositionDetails();
+        this._activeDisplayPane = null;
 
-        this._resultsPane.remove_actor(this._resultsAppsSection.actor);
-        this._resultsAppsSection.display.hide();
-        this._resultsAppsSection._unsetSearchMode();
+        // Used to catch any clicks when we have an active pane; see the comments
+        // in addPane below.
+        this._transparentBackground = new Clutter.Rectangle({ opacity: 0,
+                                                              reactive: true });
+        this._group.add_actor(this._transparentBackground);
 
-        this._resultsPane.remove_actor(this._resultsDocsSection.actor);
-        this._resultsDocsSection.display.hide();
-        this._resultsDocsSection._unsetSearchMode();
-        this._resultsDocsSection.actor.set_y(0);
+        // Draw a semitransparent rectangle over the background for readability.
+        this._backOver = new Clutter.Rectangle({ color: ROOT_OVERLAY_COLOR });
+        this._group.add_actor(this._backOver);
 
-        this._hideDetails();
-    },
+        this._group.hide();
+        global.overlay_group.add_actor(this._group);
 
-    _repositionDetails: function () {
-        let x;
-        if (this._resultsPane.visible)
-            x = this._resultsPane.x + this._resultsPane.width;
-        else
-            x = this._width;
-        this._detailsPane.x = x;
-    },
+        // TODO - recalculate everything when desktop size changes
+        this._dash = new Dash.Dash(displayGridColumnWidth);
+        this._group.add_actor(this._dash.actor);
 
-    _showDetails: function () {
-        this._detailsPane.show();
-        this._repositionDetails();
-        this.emit('panes-displayed');
-    },
+        // Container to hold popup pane chrome.
+        this._paneContainer = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
+                                            spacing: 6
+                                          });
+        // Note here we explicitly don't set the paneContainer to be reactive yet; that's done
+        // inside the notify::visible handler on panes.
+        this._paneContainer.connect('button-release-event', Lang.bind(this, function(background) {
+            this._activeDisplayPane.close();
+            return true;
+        }));
+        this._group.add_actor(this._paneContainer);
 
-    _hideDetails: function() {
-        if (!this._detailsShowing)
-            return;
-        this._detailsPane.hide();
-        this.emit('panes-removed');
-     },
+        this._transparentBackground.lower_bottom();
+        this._paneContainer.lower_bottom();
 
-    _toggleDetails: function() {
-        if (this._detailsShowing())
-            this._hideDetails();
-        else
-            this._showDetails();
-    },
+        this._repositionChildren();
 
-    _detailsShowing: function() {
-        return this._detailsPane.visible;
+        this._workspaces = null;
     },
 
-    _resultsShowing: function() {
-        return this._resultsPane.visible;
-    }      
-};
-
-Signals.addSignalMethods(Dash.prototype);
-
-function Overlay() {
-    this._init();
-}
-
-Overlay.prototype = {
-    _init : function() {
-        let me = this;
-
+    _recalculateGridSizes: function () {
         let global = Shell.Global.get();
 
         wideScreen = (global.screen_width/global.screen_height > WIDE_SCREEN_CUT_OFF_RATIO);
 
         // We divide the screen into an imaginary grid which helps us determine the layout of
-        // different visual components.  
+        // different visual components.
         if (wideScreen) {
             displayGridColumnWidth = global.screen_width / COLUMNS_WIDE_SCREEN;
             displayGridRowHeight = global.screen_height / ROWS_WIDE_SCREEN;
@@ -740,87 +157,79 @@ Overlay.prototype = {
             displayGridColumnWidth = global.screen_width / COLUMNS_REGULAR_SCREEN;
             displayGridRowHeight = global.screen_height / ROWS_REGULAR_SCREEN;
         }
+    },
 
-        this._group = new Clutter.Group();
-        this._group._delegate = this;
+    _repositionChildren: function () {
+        let global = Shell.Global.get();
 
-        this.visible = false;
-        this._hideInProgress = false;
+        let contentHeight = global.screen_height - Panel.PANEL_HEIGHT;
 
-        // A scaled root pixmap actor is used as a background. It is zoomed in
-        // to the lower right intersection of the lines that divide the image
-        // evenly in a 3x3 grid. This is based on the rule of thirds, a
-        // compositional rule of thumb in visual arts. The choice for the
-        // lower right point is based on a quick survey of GNOME wallpapers.
-        let background = global.create_root_pixmap_actor();
-        background.width = global.screen_width * BACKGROUND_SCALE;
-        background.height = global.screen_height * BACKGROUND_SCALE;
-        background.x = -global.screen_width * (4 * BACKGROUND_SCALE - 3) / 6;
-        background.y = -global.screen_height * (4 * BACKGROUND_SCALE - 3) / 6;
-        this._group.add_actor(background);
-
-        // Transparent background is used to catch clicks outside of the dash panes when the panes
-        // are being displayed and the workspaces area should not be reactive. Catching such a
-        // click results in the panes being closed and the workspaces area becoming reactive again. 
-        this._transparentBackground = new Clutter.Rectangle({ opacity: 0,
-                                                              width: global.screen_width,
-                                                              height: global.screen_height - Panel.PANEL_HEIGHT,
-                                                              y: Panel.PANEL_HEIGHT,
-                                                              reactive: true });
-        this._group.add_actor(this._transparentBackground);
+        this._dash.actor.set_position(0, Panel.PANEL_HEIGHT);
+        this._dash.actor.set_size(displayGridColumnWidth, contentHeight);
 
-        // Draw a semitransparent rectangle over the background for readability.
-        let backOver = new Clutter.Rectangle({ color: ROOT_OVERLAY_COLOR,
-                                               width: global.screen_width,
-                                               height: global.screen_height - Panel.PANEL_HEIGHT,
-                                               y: Panel.PANEL_HEIGHT });
-        this._group.add_actor(backOver);
+        this._backOver.set_position(0, Panel.PANEL_HEIGHT);
+        this._backOver.set_size(global.screen_width, contentHeight);
 
-        this._group.hide();
-        global.overlay_group.add_actor(this._group);
+        let bgPositionFactor = (4 * BACKGROUND_SCALE - 3) / 6;
+        this._background.set_size(global.screen_width * BACKGROUND_SCALE,
+                                  global.screen_height * BACKGROUND_SCALE);
+        this._background.set_position(-global.screen_width * bgPositionFactor,
+                                      -global.screen_height * bgPositionFactor);
 
-        // TODO - recalculate everything when desktop size changes
-        this._dash = new Dash();
-        this._group.add_actor(this._dash.actor);
-        this._workspaces = null;
-        this._buttonEventHandlerId = null;
-        this._dash.connect('activated', function(dash) {
-            // TODO - have some sort of animation/effect while
-            // transitioning to the new app.  We definitely need
-            // startup-notification integration at least.
-            me.hide();
-        });
-        this._dash.connect('panes-displayed', function(dash) {
-            if (me._buttonEventHandlerId == null) {
-                me._transparentBackground.raise_top();
-                me._dash.actor.raise_top();
-                me._buttonEventHandlerId = me._transparentBackground.connect('button-release-event', function(background) {
-                    me._dash.unsetMoreMode();
+        this._paneContainer.set_position(this._dash.actor.x + this._dash.actor.width + DEFAULT_PADDING,
+                                         Panel.PANEL_HEIGHT);
+        // Dynamic width
+        this._paneContainer.height = contentHeight;
+
+        this._transparentBackground.set_position(this._paneContainer.x, this._paneContainer.y);
+        this._transparentBackground.set_size(global.screen_width - this._paneContainer.x,
+                                             this._paneContainer.height);
+    },
+
+    addPane: function (pane) {
+        pane.actor.width = displayGridColumnWidth * 2;
+        this._paneContainer.append(pane.actor, Big.BoxPackFlags.NONE);
+        // When a pane is displayed, we raise the transparent background to the top
+        // and connect to button-release-event on it, then raise the pane above that.
+        // The idea here is that clicking anywhere outside the pane should close it.
+        // When the active pane is closed, undo the effect.
+        let backgroundEventId = null;
+        pane.connect('open-state-changed', Lang.bind(this, function (pane, isOpen) {
+            if (isOpen) {
+                this._activeDisplayPane = pane;
+                this._transparentBackground.raise_top();
+                this._paneContainer.raise_top();
+                if (backgroundEventId != null)
+                    this._transparentBackground.disconnect(backgroundEventId);
+                backgroundEventId = this._transparentBackground.connect('button-release-event', Lang.bind(this, function () {
+                    this._activeDisplayPane.close();
                     return true;
-                }); 
-            }    
-        });
-        this._dash.connect('panes-removed', function(dash) {
-            if (me._buttonEventHandlerId != null) {
-                me._transparentBackground.lower_bottom();  
-                me._transparentBackground.disconnect(me._buttonEventHandlerId);  
-                me._buttonEventHandlerId = null;
+                }));
+            } else if (pane == this._activeDisplayPane) {
+                this._activeDisplayPane = null;
+                if (backgroundEventId != null) {
+                    this._transparentBackground.disconnect(backgroundEventId);
+                    backgroundEventId = null;
+                }
+                this._transparentBackground.lower_bottom();
+                this._paneContainer.lower_bottom();
             }
-        });
+        }));
     },
 
     //// Draggable target interface ////
 
-    // Unsets the expanded display mode if a GenericDisplayItem is being 
+    // Closes any active panes if a GenericDisplayItem is being
     // dragged over the overlay, i.e. as soon as it starts being dragged.
-    // This closes the additional panes and allows the user to place
-    // the item on any workspace.
+    // This allows the user to place the item on any workspace.
     handleDragOver : function(source, actor, x, y, time) {
-        if (source instanceof GenericDisplay.GenericDisplayItem) {
-            this._dash.unsetMoreMode();
+        if (source instanceof GenericDisplay.GenericDisplayItem
+            || source instanceof AppDisplay.WellDisplayItem) {
+            if (this._activeDisplayPane != null)
+                this._activeDisplayPane.close();
             return true;
         }
-  
+
         return false;
     },
 
@@ -901,7 +310,9 @@ Overlay.prototype = {
         let global = Shell.Global.get();
 
         this._hideInProgress = true;
-        // lower the Dash, so that workspaces display is on top and covers the Dash while it is sliding out
+        if (this._activeDisplayPane != null)
+            this._activeDisplayPane.close();
+        // lower the panes, so that workspaces display is on top while sliding out
         this._dash.actor.lower(this._workspaces.actor);
         this._workspaces.hide();
 



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