Compare commits
	
		
			33 Commits
		
	
	
		
			citadel-45
			...
			wip/re-sea
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 555d45f06c | ||
|   | 0a8713770b | ||
|   | 7d3ea1ac68 | ||
|   | 7785710964 | ||
|   | 3730dc01cf | ||
|   | 66bf0df737 | ||
|   | 4590b33f2e | ||
|   | 6538f60322 | ||
|   | 4b0ba8b7b8 | ||
|   | bc8965fe63 | ||
|   | e7fcce3484 | ||
|   | b1e4d3335c | ||
|   | a3a3f24ed3 | ||
|   | 96191a9c96 | ||
|   | f4814d200b | ||
|   | 52156930d3 | ||
|   | cbaa999ced | ||
|   | 9491f6bd23 | ||
|   | afe8198d4b | ||
|   | 6aa8f14285 | ||
|   | e073670c4d | ||
|   | 17a3d2c63f | ||
|   | ca38e05ed4 | ||
|   | 9841e56ebf | ||
|   | c0d3a14ac2 | ||
|   | d485fcf9ec | ||
|   | ce2c5106f8 | ||
|   | 5e96c3dfb4 | ||
|   | a72b642f3e | ||
|   | 8507d3c4e4 | ||
|   | c985fdccba | ||
|   | 1d7c2b1c26 | ||
|   | ab60c628e7 | 
| @@ -113,9 +113,13 @@ | ||||
|           <doc:summary> | ||||
|             <doc:para> | ||||
|               A dictionary describing the given search result, containing | ||||
|               'id', 'name' (both strings) and either 'icon' (a serialized | ||||
|               GIcon) or 'icon-data' (raw image data as (iiibiiay) - width, | ||||
|               height, rowstride, has-alpha, bits per sample, channels, data) | ||||
|               'id' and 'name' (both strings). Optionally, either 'gicon' (a | ||||
|               serialized GIcon) or 'icon-data' (raw image data as (iiibiiay) | ||||
|               - width, height, rowstride, has-alpha, bits per sample, | ||||
|               channels, data) can be specified if the result can be better | ||||
|               served with a thumbnail of the content (such as with images). | ||||
|               A 'description' field (string) may also be specified if more | ||||
|               context would help the user find the desired result. | ||||
|             </doc:para> | ||||
|           </doc:summary> | ||||
|         </doc:doc> | ||||
| @@ -143,5 +147,25 @@ | ||||
|         </doc:doc> | ||||
|       </arg> | ||||
|     </method> | ||||
|  | ||||
|     <method name="LaunchSearch"> | ||||
|       <doc:doc> | ||||
|         <doc:description> | ||||
|           <doc:para> | ||||
|             Called when the user clicks on the provider icon. The provider | ||||
|             application should open and run the active search term itself. | ||||
|           </doc:para> | ||||
|         </doc:description> | ||||
|       </doc:doc> | ||||
|       <arg type="as" direction="in"> | ||||
|         <doc:doc> | ||||
|           <doc:summary> | ||||
|             <doc:para> | ||||
|               The current search term(s). | ||||
|             </doc:para> | ||||
|           </doc:summary> | ||||
|         </doc:doc> | ||||
|       </arg> | ||||
|     </method> | ||||
|   </interface> | ||||
| </node> | ||||
|   | ||||
| @@ -577,6 +577,10 @@ StScrollBar StButton#vhandle:active { | ||||
|     spacing: 40px; | ||||
| } | ||||
|  | ||||
| #overview-group { | ||||
|     spacing: 12px; | ||||
| } | ||||
|  | ||||
| .window-caption { | ||||
|     spacing: 25px; | ||||
| } | ||||
| @@ -711,7 +715,7 @@ StScrollBar StButton#vhandle:active { | ||||
|  | ||||
| #searchResultsContent { | ||||
|     padding-right: 20px; | ||||
|     spacing: 36px; | ||||
|     spacing: 16px; | ||||
| } | ||||
|  | ||||
| #searchResultsContent:rtl { | ||||
| @@ -719,6 +723,25 @@ StScrollBar StButton#vhandle:active { | ||||
|     padding-left: 20px; | ||||
| } | ||||
|  | ||||
| .search-section { | ||||
|     /* This should be equal to #searchResultsContent spacing */ | ||||
|     spacing: 16px; | ||||
| } | ||||
|  | ||||
| .search-section-separator { | ||||
|     -gradient-height: 1px; | ||||
|     -gradient-start: rgba(255,255,255,0); | ||||
|     -gradient-end: rgba(255,255,255,0.5); | ||||
|     -margin-horizontal: 1.5em; | ||||
|     height: 1px; | ||||
| } | ||||
|  | ||||
| .search-section-content { | ||||
|     /* This is the space between the provider icon and the results container */ | ||||
|     spacing: 25px; | ||||
| } | ||||
|  | ||||
| .search-statustext, | ||||
| .search-section-header { | ||||
|     padding: 4px 12px; | ||||
|     spacing: 4px; | ||||
| @@ -744,6 +767,14 @@ StScrollBar StButton#vhandle:active { | ||||
|     spacing: 4px; | ||||
| } | ||||
|  | ||||
| .search-providers-box { | ||||
|     spacing: 12px; | ||||
| } | ||||
|  | ||||
| .results-list { | ||||
|     spacing: 5px; | ||||
| } | ||||
|  | ||||
| /* Text labels are an odd number of pixels tall. The uneven top and bottom | ||||
|  * padding compensates for this and ensures that the label is vertically | ||||
|  * centered */ | ||||
| @@ -824,7 +855,10 @@ StScrollBar StButton#vhandle:active { | ||||
|  | ||||
| .app-well-app > .overview-icon, | ||||
| .show-apps > .overview-icon, | ||||
| .search-result-content > .overview-icon { | ||||
| .remove-favorite > .overview-icon, | ||||
| .search-section-icon-bin, | ||||
| .search-result, | ||||
| .grid-search-result-content > .overview-icon { | ||||
|     border-radius: 4px; | ||||
|     padding: 3px; | ||||
|     border: 1px rgba(0,0,0,0); | ||||
| @@ -840,7 +874,10 @@ StScrollBar StButton#vhandle:active { | ||||
|  | ||||
| .app-well-app:hover > .overview-icon, | ||||
| .show-apps:hover > .overview-icon, | ||||
| .search-result-content:hover > .overview-icon { | ||||
| .remove-favorite:hover > .overview-icon, | ||||
| .search-section-icon-bin:hover, | ||||
| .search-result:hover, | ||||
| .grid-search-result-content:hover > .overview-icon { | ||||
|     background-color: rgba(255,255,255,0.1); | ||||
|     text-shadow: black 0px 2px 2px; | ||||
|     transition-duration: 100; | ||||
| @@ -875,13 +912,39 @@ StScrollBar StButton#vhandle:active { | ||||
| } | ||||
|  | ||||
| .app-well-app:focus > .overview-icon, | ||||
| .search-result-content:focus > .overview-icon, | ||||
| .show-apps:focus > .overview-icon, | ||||
| .search-section-icon-bin:focus, | ||||
| .search-result:focus, | ||||
| .grid-search-result-content:focus > .overview-icon, | ||||
| .app-well-app:selected > .overview-icon, | ||||
| .search-result-content:selected > .overview-icon { | ||||
| .search-section-icon-bin:selected, | ||||
| .search-result:selected, | ||||
| .grid-search-result-content:selected > .overview-icon { | ||||
|     background-color: rgba(255,255,255,0.33); | ||||
| } | ||||
|  | ||||
| /* List Results */ | ||||
|  | ||||
| .search-result { | ||||
|     padding: 15px; | ||||
| } | ||||
|  | ||||
| .search-result-content { | ||||
|   spacing: 12px; | ||||
| } | ||||
|  | ||||
| .search-result-details { | ||||
|     font-weight: bold; | ||||
| } | ||||
|  | ||||
| .search-result-details-title { | ||||
|     font-size: 16pt; | ||||
| } | ||||
|  | ||||
| .search-result-details-description { | ||||
|     font-size: 14pt; | ||||
| } | ||||
|  | ||||
| /* LookingGlass */ | ||||
|  | ||||
| #LookingGlassDialog { | ||||
|   | ||||
| @@ -41,6 +41,7 @@ nobase_dist_js_DATA = 	\ | ||||
| 	ui/boxpointer.js	\ | ||||
| 	ui/calendar.js		\ | ||||
| 	ui/checkBox.js		\ | ||||
| 	ui/centerLayout.js	\ | ||||
| 	ui/ctrlAltTab.js	\ | ||||
| 	ui/dash.js		\ | ||||
| 	ui/dateMenu.js		\ | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const Gio = imports.gi.Gio; | ||||
| const GLib = imports.gi.GLib; | ||||
| const Gtk = imports.gi.Gtk; | ||||
| const GMenu = imports.gi.GMenu; | ||||
| @@ -371,6 +372,8 @@ const SettingsSearchProvider = new Lang.Class({ | ||||
|  | ||||
|         this._appSys = Shell.AppSystem.get_default(); | ||||
|         this._gnomecc = this._appSys.lookup_app('gnome-control-center.desktop'); | ||||
|         let appInfo = Gio.DesktopAppInfo.new('gnome-control-center.desktop'); | ||||
|         this.icon = appInfo.get_icon(); | ||||
|     }, | ||||
|  | ||||
|     getResultMetas: function(prefs, callback) { | ||||
| @@ -379,9 +382,7 @@ const SettingsSearchProvider = new Lang.Class({ | ||||
|             let pref = prefs[i]; | ||||
|             metas.push({ 'id': pref, | ||||
|                          'name': pref.get_name(), | ||||
|                          'createIcon': function(size) { | ||||
|                              return pref.create_icon_texture(size); | ||||
|                          } | ||||
|                          'createIcon': function(size) { return; } | ||||
|                        }); | ||||
|         } | ||||
|         callback(metas); | ||||
| @@ -461,15 +462,15 @@ const AppWellIcon = new Lang.Class({ | ||||
|         this._draggable.connect('drag-begin', Lang.bind(this, | ||||
|             function () { | ||||
|                 this._removeMenuTimeout(); | ||||
|                 Main.overview.beginItemDrag(this); | ||||
|                 Main.overview.beginAppDrag(this); | ||||
|             })); | ||||
|         this._draggable.connect('drag-cancelled', Lang.bind(this, | ||||
|             function () { | ||||
|                 Main.overview.cancelledItemDrag(this); | ||||
|                 Main.overview.cancelledAppDrag(this); | ||||
|             })); | ||||
|         this._draggable.connect('drag-end', Lang.bind(this, | ||||
|             function () { | ||||
|                Main.overview.endItemDrag(this); | ||||
|                Main.overview.endAppDrag(this); | ||||
|             })); | ||||
|  | ||||
|         this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); | ||||
|   | ||||
							
								
								
									
										93
									
								
								js/ui/centerLayout.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								js/ui/centerLayout.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,93 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Lang = imports.lang; | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const St = imports.gi.St; | ||||
|  | ||||
| function connectLayoutManager(layoutManager, styleChanged) { | ||||
|     let widget, styleChangedId; | ||||
|  | ||||
|     function _styleChanged() { | ||||
|         let themeNode = widget.get_theme_node(); | ||||
|         styleChanged(themeNode, widget); | ||||
|     } | ||||
|  | ||||
|     function actorChanged() { | ||||
|         if (widget) { | ||||
|             widget.disconnect(styleChangedId); | ||||
|             styleChangedId = 0; | ||||
|         } | ||||
|  | ||||
|         let actor = layoutManager.get_actor(); | ||||
|         if (actor && actor instanceof St.Widget) { | ||||
|             widget = actor; | ||||
|             styleChangedId = widget.connect('style-changed', _styleChanged); | ||||
|             _styleChanged(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     layoutManager.connect('notify::actor', actorChanged); | ||||
|     return layoutManager; | ||||
| } | ||||
|  | ||||
| function connectSpacing(layoutManager) { | ||||
|     return connectLayoutManager(layoutManager, function(themeNode, widget) { | ||||
|         layoutManager.spacing = themeNode.get_length('spacing'); | ||||
|     }); | ||||
| } | ||||
|  | ||||
| const CenterLayout = new Lang.Class({ | ||||
|     Name: 'CenterLayout', | ||||
|     Extends: Clutter.BoxLayout, | ||||
|  | ||||
|     vfunc_allocate: function(container, box, flags) { | ||||
|         let rtl = container.get_text_direction() == Clutter.TextDirection.RTL; | ||||
|  | ||||
|         let availWidth = box.x2 - box.x1; | ||||
|         let availHeight = box.y2 - box.y1; | ||||
|  | ||||
|         // Assume that these are the first three widgets and they are all visible. | ||||
|         let [left, center, right] = container.get_children(); | ||||
|  | ||||
|         // Only support horizontal layouts for now. | ||||
|         let [centerMinWidth, centerNaturalWidth] = center.get_preferred_width(availWidth); | ||||
|  | ||||
|         let sideWidth = (availWidth - centerNaturalWidth) / 2; | ||||
|  | ||||
|         let childBox = new Clutter.ActorBox(); | ||||
|         childBox.y1 = box.y1; | ||||
|         childBox.y2 = box.y1 + availHeight; | ||||
|  | ||||
|         if (left) { | ||||
|             let [leftMinWidth, leftNaturalWidth] = left.get_preferred_width(availWidth); | ||||
|             let leftSide = Math.min(Math.floor(sideWidth), leftNaturalWidth); | ||||
|             if (rtl) { | ||||
|                 childBox.x1 = availWidth - leftSide; | ||||
|                 childBox.x2 = availWidth; | ||||
|             } else { | ||||
|                 childBox.x1 = 0; | ||||
|                 childBox.x2 = leftSide; | ||||
|             } | ||||
|             childBox.x1 += box.x1; | ||||
|             left.allocate(childBox, flags); | ||||
|         } | ||||
|  | ||||
|         childBox.x1 = box.x1 + Math.ceil(sideWidth); | ||||
|         childBox.x2 = childBox.x1 + centerNaturalWidth; | ||||
|         center.allocate(childBox, flags); | ||||
|  | ||||
|         if (right) { | ||||
|             let [rightMinWidth, rightNaturalWidth] = right.get_preferred_width(availWidth); | ||||
|             let rightSide = Math.min(Math.floor(sideWidth), rightNaturalWidth); | ||||
|             if (rtl) { | ||||
|                 childBox.x1 = 0; | ||||
|                 childBox.x2 = rightSide; | ||||
|             } else { | ||||
|                 childBox.x1 = availWidth - rightSide; | ||||
|                 childBox.x2 = availWidth; | ||||
|             } | ||||
|             childBox.x1 += box.x1; | ||||
|             right.allocate(childBox, flags); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
| @@ -372,6 +372,9 @@ const Dash = new Lang.Class({ | ||||
|         this._maxHeight = -1; | ||||
|         this.iconSize = 64; | ||||
|         this._shownInitially = false; | ||||
|         this.visible = false; | ||||
|         this._hiddenX; | ||||
|         this._targetX; | ||||
|  | ||||
|         this._dragPlaceholder = null; | ||||
|         this._dragPlaceholderPos = -1; | ||||
| @@ -399,7 +402,8 @@ const Dash = new Lang.Class({ | ||||
|             function() { | ||||
|                 if (this._maxHeight != this.actor.height) | ||||
|                     this._queueRedisplay(); | ||||
|                 this._maxHeight = this.actor.height; | ||||
|                 if (this.actor.height > this._maxHeight) | ||||
|                     this._maxHeight = this.actor.height; | ||||
|             })); | ||||
|  | ||||
|         this._workId = Main.initializeDeferredWork(this._box, Lang.bind(this, this._redisplay)); | ||||
| @@ -410,11 +414,11 @@ const Dash = new Lang.Class({ | ||||
|         AppFavorites.getAppFavorites().connect('changed', Lang.bind(this, this._queueRedisplay)); | ||||
|         this._appSystem.connect('app-state-changed', Lang.bind(this, this._queueRedisplay)); | ||||
|  | ||||
|         Main.overview.connect('item-drag-begin', | ||||
|         Main.overview.connect('app-drag-begin', | ||||
|                               Lang.bind(this, this._onDragBegin)); | ||||
|         Main.overview.connect('item-drag-end', | ||||
|         Main.overview.connect('app-drag-end', | ||||
|                               Lang.bind(this, this._onDragEnd)); | ||||
|         Main.overview.connect('item-drag-cancelled', | ||||
|         Main.overview.connect('app-drag-cancelled', | ||||
|                               Lang.bind(this, this._onDragCancelled)); | ||||
|         Main.overview.connect('window-drag-begin', | ||||
|                               Lang.bind(this, this._onDragBegin)); | ||||
| @@ -546,6 +550,17 @@ const Dash = new Lang.Class({ | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _computeDashX: function() { | ||||
|         this._targetX = this.actor.get_x(); | ||||
|  | ||||
|         let rtl = (this.actor.get_text_direction() == Clutter.TextDirection.RTL); | ||||
|  | ||||
|         if (rtl) | ||||
|             this._hiddenX = this._targetX + this.actor.width; | ||||
|         else | ||||
|             this._hiddenX = -this.actor.width; | ||||
|     }, | ||||
|  | ||||
|     _adjustIconSize: function() { | ||||
|         // For the icon size, we only consider children which are "proper" | ||||
|         // icons (i.e. ignoring drag placeholders) and which are not | ||||
| @@ -641,6 +656,8 @@ const Dash = new Lang.Class({ | ||||
|                                } | ||||
|                              }); | ||||
|         } | ||||
|  | ||||
|         this._computeDashX(); | ||||
|     }, | ||||
|  | ||||
|     _redisplay: function () { | ||||
| @@ -757,6 +774,7 @@ const Dash = new Lang.Class({ | ||||
|         // of items, to avoid all items zooming in at once | ||||
|         if (!this._shownInitially) { | ||||
|             this._shownInitially = true; | ||||
|             this.visible = true; | ||||
|             return; | ||||
|         } | ||||
|  | ||||
| @@ -901,6 +919,34 @@ const Dash = new Lang.Class({ | ||||
|             })); | ||||
|  | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     show: function() { | ||||
|         if (this.visible) | ||||
|             return; | ||||
|  | ||||
|         this.visible = true; | ||||
|  | ||||
|         this.actor.show(); | ||||
|         Tweener.addTween(this.actor, { translation_x: this._targetX, | ||||
|                                        transition: 'easeOutQuad', | ||||
|                                        time: DASH_ANIMATION_TIME | ||||
|                                      }); | ||||
|     }, | ||||
|  | ||||
|     hide: function() { | ||||
|         if (!this.visible) | ||||
|             return; | ||||
|  | ||||
|         this.visible = false; | ||||
|  | ||||
|         Tweener.addTween(this.actor, { translation_x: this._hiddenX, | ||||
|                                        transition: 'easeOutQuad', | ||||
|                                        time: DASH_ANIMATION_TIME, | ||||
|                                        onComplete: Lang.bind(this, function () { | ||||
|                                                        this.actor.hide(); | ||||
|                                                    }) | ||||
|                                      }); | ||||
|     } | ||||
| }); | ||||
|  | ||||
|   | ||||
| @@ -198,7 +198,11 @@ const IconGrid = new Lang.Class({ | ||||
|  | ||||
|     _getPreferredHeight: function (grid, forWidth, alloc) { | ||||
|         let children = this._getVisibleChildren(); | ||||
|         let [nColumns, usedWidth] = this._computeLayout(forWidth); | ||||
|         let nColumns; | ||||
|         if (forWidth < 0) | ||||
|             nColumns = children.length; | ||||
|         else | ||||
|             nColumns = this._computeLayout(forWidth)[0]; | ||||
|         let nRows; | ||||
|         if (nColumns > 0) | ||||
|             nRows = Math.ceil(children.length / nColumns); | ||||
|   | ||||
| @@ -1498,7 +1498,7 @@ const MessageTray = new Lang.Class({ | ||||
|                 this._overviewVisible = true; | ||||
|                 this._grabHelper.ungrab(); // drop modal grab if necessary | ||||
|                 this.actor.add_style_pseudo_class('overview'); | ||||
|                 this._updateState(); | ||||
|                 this.show(); | ||||
|             })); | ||||
|         Main.overview.connect('hiding', Lang.bind(this, | ||||
|             function() { | ||||
| @@ -1736,6 +1736,11 @@ const MessageTray = new Lang.Class({ | ||||
|         this._updateState(); | ||||
|     }, | ||||
|  | ||||
|     show: function() { | ||||
|         this._traySummoned = true; | ||||
|         this._updateState(); | ||||
|     }, | ||||
|  | ||||
|     _onNotify: function(source, notification) { | ||||
|         if (this._summaryBoxPointerItem && this._summaryBoxPointerItem.source == source) { | ||||
|             if (this._summaryBoxPointerState == State.HIDING) { | ||||
| @@ -1978,7 +1983,7 @@ const MessageTray = new Lang.Class({ | ||||
|         } | ||||
|  | ||||
|         // Summary | ||||
|         let summarySummoned = this._pointerInSummary || this._overviewVisible ||  this._traySummoned; | ||||
|         let summarySummoned = this._pointerInSummary ||  this._traySummoned; | ||||
|         let summaryPinned = this._pointerInTray || summarySummoned || this._locked; | ||||
|         let summaryHovered = this._pointerInTray || this._pointerInSummary; | ||||
|  | ||||
| @@ -1986,7 +1991,7 @@ const MessageTray = new Lang.Class({ | ||||
|                                     this._notificationState == State.SHOWN); | ||||
|         let notificationsDone = !notificationsVisible && !notificationsPending; | ||||
|  | ||||
|         let summaryOptionalInOverview = this._overviewVisible && !this._locked && !summaryHovered; | ||||
|         let summaryOptionalInOverview = !this._locked && !summaryHovered; | ||||
|         let mustHideSummary = (notificationsPending && (notificationUrgent || summaryOptionalInOverview)) | ||||
|                               || notificationsVisible || !Main.sessionMode.hasNotifications; | ||||
|  | ||||
|   | ||||
| @@ -10,6 +10,7 @@ const St = imports.gi.St; | ||||
| const Shell = imports.gi.Shell; | ||||
| const Gdk = imports.gi.Gdk; | ||||
|  | ||||
| const CenterLayout = imports.ui.centerLayout; | ||||
| const Dash = imports.ui.dash; | ||||
| const DND = imports.ui.dnd; | ||||
| const Main = imports.ui.main; | ||||
| @@ -23,9 +24,6 @@ const WorkspaceThumbnail = imports.ui.workspaceThumbnail; | ||||
| // Time for initial animation going into Overview mode | ||||
| const ANIMATION_TIME = 0.25; | ||||
|  | ||||
| // XXX -- grab this automatically. Hard to do. | ||||
| const DASH_MAX_WIDTH = 96; | ||||
|  | ||||
| const DND_WINDOW_SWITCH_TIMEOUT = 1250; | ||||
|  | ||||
| const SwipeScrollDirection = { | ||||
| @@ -119,23 +117,18 @@ const Overview = new Lang.Class({ | ||||
|         this._desktopFade = new St.Bin(); | ||||
|         global.overlay_group.add_actor(this._desktopFade); | ||||
|  | ||||
|         this._spacing = 0; | ||||
|  | ||||
|         /* Translators: This is the main view to select | ||||
|            activities. See also note for "Activities" string. */ | ||||
|         this._group = new St.Widget({ name: 'overview', | ||||
|                                       accessible_name: _("Overview"), | ||||
|                                       reactive: true }); | ||||
|         this._group._delegate = this; | ||||
|         this._group.connect('style-changed', | ||||
|             Lang.bind(this, function() { | ||||
|                 let node = this._group.get_theme_node(); | ||||
|                 let spacing = node.get_length('spacing'); | ||||
|                 if (spacing != this._spacing) { | ||||
|                     this._spacing = spacing; | ||||
|                     this._relayout(); | ||||
|                 } | ||||
|             })); | ||||
|         this._overview = new St.BoxLayout({ name: 'overview', | ||||
|                                             accessible_name: _("Overview"), | ||||
|                                             reactive: true, | ||||
|                                             vertical: true }); | ||||
|         this._overview._delegate = this; | ||||
|  | ||||
|         let layout = new CenterLayout.CenterLayout(); | ||||
|         CenterLayout.connectSpacing(layout); | ||||
|         this._group = new St.Widget({ name: 'overview-group', | ||||
|                                       layout_manager: layout }); | ||||
|  | ||||
|         this._scrollDirection = SwipeScrollDirection.NONE; | ||||
|         this._scrollAdjustment = null; | ||||
| @@ -148,18 +141,19 @@ const Overview = new Lang.Class({ | ||||
|         this._modal = false;            // have a modal grab | ||||
|         this.animationInProgress = false; | ||||
|         this._hideInProgress = false; | ||||
|         this.searchActive = false; | ||||
|         this.appsActive = false; | ||||
|  | ||||
|         // During transitions, we raise this to the top to avoid having the overview | ||||
|         // area be reactive; it causes too many issues such as double clicks on | ||||
|         // Dash elements, or mouseover handlers in the workspaces. | ||||
|         this._coverPane = new Clutter.Rectangle({ opacity: 0, | ||||
|                                                   reactive: true }); | ||||
|         this._group.add_actor(this._coverPane); | ||||
|         this._overview.add_actor(this._coverPane); | ||||
|         this._coverPane.connect('event', Lang.bind(this, function (actor, event) { return true; })); | ||||
|  | ||||
|  | ||||
|         this._group.hide(); | ||||
|         global.overlay_group.add_actor(this._group); | ||||
|         this._overview.hide(); | ||||
|         global.overlay_group.add_actor(this._overview); | ||||
|  | ||||
|         this._coverPane.hide(); | ||||
|  | ||||
| @@ -171,6 +165,8 @@ const Overview = new Lang.Class({ | ||||
|         Main.xdndHandler.connect('drag-begin', Lang.bind(this, this._onDragBegin)); | ||||
|         Main.xdndHandler.connect('drag-end', Lang.bind(this, this._onDragEnd)); | ||||
|  | ||||
|         global.screen.connect('restacked', Lang.bind(this, this._onRestacked)); | ||||
|  | ||||
|         this._windowSwitchTimeoutId = 0; | ||||
|         this._windowSwitchTimestamp = 0; | ||||
|         this._lastActiveWorkspaceIndex = -1; | ||||
| @@ -193,6 +189,13 @@ const Overview = new Lang.Class({ | ||||
|  | ||||
|         this._shellInfo = new ShellInfo(); | ||||
|  | ||||
|         // Add a clone of the panel to the overview so spacing and such is | ||||
|         // automatic | ||||
|         this._panelGhost = new St.Bin({ child: new Clutter.Clone({ source: Main.panel.actor }), | ||||
|                                         reactive: false, | ||||
|                                         opacity: 0 }); | ||||
|         this._overview.add_actor(this._panelGhost); | ||||
|  | ||||
|         this._searchEntry = new St.Entry({ name: 'searchEntry', | ||||
|                                            /* Translators: this is the text displayed | ||||
|                                               in the search entry when no search is | ||||
| @@ -201,16 +204,13 @@ const Overview = new Lang.Class({ | ||||
|                                            hint_text: _("Type to search..."), | ||||
|                                            track_hover: true, | ||||
|                                            can_focus: true }); | ||||
|         this._group.add_actor(this._searchEntry); | ||||
|  | ||||
|         this._dash = new Dash.Dash(); | ||||
|         this._viewSelector = new ViewSelector.ViewSelector(this._searchEntry, | ||||
|                                                            this._dash.showAppsButton); | ||||
|         this._group.add_actor(this._viewSelector.actor); | ||||
|         this._group.add_actor(this._dash.actor); | ||||
|         this._searchEntryBin = new St.Bin({ child: this._searchEntry, | ||||
|                                             x_align: St.Align.MIDDLE }); | ||||
|         this._overview.add_actor(this._searchEntryBin); | ||||
|  | ||||
|         // TODO - recalculate everything when desktop size changes | ||||
|         this._dash.actor.add_constraint(this._viewSelector.constrainHeight); | ||||
|         this._dash = new Dash.Dash(); | ||||
|         this._group.add_actor(this._dash.actor); | ||||
|         this.dashIconSize = this._dash.iconSize; | ||||
|         this._dash.connect('icon-size-changed', | ||||
|                            Lang.bind(this, function() { | ||||
| @@ -221,6 +221,82 @@ const Overview = new Lang.Class({ | ||||
|         // the left of the overview | ||||
|         Main.ctrlAltTabManager.addGroup(this._dash.actor, _("Dash"), 'user-bookmarks-symbolic'); | ||||
|  | ||||
|         this._viewSelector = new ViewSelector.ViewSelector(this._searchEntry, | ||||
|                                                            this._dash.showAppsButton); | ||||
|         this._group.add_actor(this._viewSelector.actor); | ||||
|  | ||||
|         this._thumbnailsBox = new WorkspaceThumbnail.ThumbnailsBox(); | ||||
|         this._group.add_actor(this._thumbnailsBox.actor); | ||||
|  | ||||
|         // TODO: unique icon | ||||
|         Main.ctrlAltTabManager.addGroup(this._thumbnailsBox.actor, _("Workspaces"), 'user-bookmarks-symbolic'); | ||||
|  | ||||
|         // Add our same-line elements after the search entry | ||||
|         this._overview.add_actor(this._group); | ||||
|  | ||||
|         // Then account for message tray | ||||
|         this._messageTrayGhost = new St.Bin({ child: new Clutter.Clone({ source: Main.messageTray.actor }), | ||||
|                                               reactive: false, | ||||
|                                               opacity: 0 }); | ||||
|         this._overview.add_actor(this._messageTrayGhost); | ||||
|  | ||||
|         this._viewSelector.connect('search-begin', Lang.bind(this, | ||||
|             function() { | ||||
|                 this.searchActive = true; | ||||
|                 this._dash.hide(); | ||||
|                 this._thumbnailsBox.hide(); | ||||
|                 Main.messageTray.hide(); | ||||
|             })); | ||||
|         this._viewSelector.connect('search-cancelled', Lang.bind(this, | ||||
|             function() { | ||||
|                 this.searchActive = false; | ||||
|                 this._dash.show(); | ||||
|                 this._thumbnailsBox.show(); | ||||
|                 // search-cancelled is emitted if we leave the overview | ||||
|                 // directly from searching. That means the message tray will | ||||
|                 // be shown when we reach the workspace. Don't do this, only | ||||
|                 // show the message tray on search-cancelled if we are still | ||||
|                 // in the overview/not transitioning out of it. | ||||
|                 if (this.visible && !this.animationInProgress) | ||||
|                     Main.messageTray.show(); | ||||
|             })); | ||||
|         this._viewSelector.connect('apps-button-checked', Lang.bind(this, | ||||
|             function(vs, checked) { | ||||
|                 this.appsActive = checked; | ||||
|                 if (checked) { | ||||
|                     this._thumbnailsBox.hide(); | ||||
|                     Main.messageTray.hide(); | ||||
|                 } else { | ||||
|                     this._thumbnailsBox.show(); | ||||
|                     if (this.visible && !this.animationInProgress) | ||||
|                         Main.messageTray.show(); | ||||
|                 } | ||||
|             })); | ||||
|  | ||||
|         this.connect('app-drag-begin', | ||||
|                               Lang.bind(this, function () { | ||||
|                                   this._dash.show(); | ||||
|                                   this._thumbnailsBox.show(); | ||||
|                               })); | ||||
|         this.connect('app-drag-cancelled', | ||||
|                               Lang.bind(this, function () { | ||||
|                                   if (this.searchActive) { | ||||
|                                       this._dash.hide(); | ||||
|                                       this._thumbnailsBox.hide(); | ||||
|                                   } else if (this.appsActive) { | ||||
|                                       this._thumbnailsBox.hide(); | ||||
|                                   } | ||||
|                               })); | ||||
|         this.connect('app-drag-end', | ||||
|                               Lang.bind(this, function () { | ||||
|                                   if (this.searchActive) { | ||||
|                                       this._dash.hide(); | ||||
|                                       this._thumbnailsBox.hide(); | ||||
|                                   } else if (this.appsActive) { | ||||
|                                       this._thumbnailsBox.hide(); | ||||
|                                   } | ||||
|                               })); | ||||
|  | ||||
|         Main.layoutManager.connect('monitors-changed', Lang.bind(this, this._relayout)); | ||||
|         this._relayout(); | ||||
|     }, | ||||
| @@ -486,55 +562,41 @@ const Overview = new Lang.Class({ | ||||
|         this.hide(); | ||||
|  | ||||
|         let primary = Main.layoutManager.primaryMonitor; | ||||
|         let rtl = (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL); | ||||
|  | ||||
|         let contentY = Main.panel.actor.height; | ||||
|         let contentHeight = primary.height - contentY - Main.messageTray.actor.height; | ||||
|  | ||||
|         this._group.set_position(primary.x, primary.y); | ||||
|         this._group.set_size(primary.width, primary.height); | ||||
|         this._overview.set_position(primary.x, primary.y); | ||||
|         this._overview.set_size(primary.width, primary.height); | ||||
|  | ||||
|         this._coverPane.set_position(0, contentY); | ||||
|         this._coverPane.set_size(primary.width, contentHeight); | ||||
|     }, | ||||
|  | ||||
|         let searchWidth = this._searchEntry.get_width(); | ||||
|         let searchHeight = this._searchEntry.get_height(); | ||||
|         let searchX = (primary.width - searchWidth) / 2; | ||||
|         let searchY = contentY + this._spacing; | ||||
|     _onRestacked: function() { | ||||
|         let stack = global.get_window_actors(); | ||||
|         let stackIndices = {}; | ||||
|  | ||||
|         let dashWidth = DASH_MAX_WIDTH; | ||||
|         let dashY = searchY + searchHeight + this._spacing; | ||||
|         let dashX; | ||||
|         if (rtl) { | ||||
|             this._dash.actor.set_anchor_point_from_gravity(Clutter.Gravity.NORTH_EAST); | ||||
|             dashX = primary.width; | ||||
|         } else { | ||||
|             dashX = 0; | ||||
|         for (let i = 0; i < stack.length; i++) { | ||||
|             // Use the stable sequence for an integer to use as a hash key | ||||
|             stackIndices[stack[i].get_meta_window().get_stable_sequence()] = i; | ||||
|         } | ||||
|  | ||||
|         let viewX = rtl ? 0 : dashWidth + this._spacing; | ||||
|         let viewY = searchY + searchHeight + this._spacing; | ||||
|         let viewWidth = primary.width - dashWidth - this._spacing; | ||||
|         let viewHeight = contentHeight - this._spacing - viewY; | ||||
|  | ||||
|         this._searchEntry.set_position(searchX, searchY); | ||||
|         this._dash.actor.set_position(dashX, dashY); | ||||
|         this._viewSelector.actor.set_position(viewX, viewY); | ||||
|         this._viewSelector.actor.set_size(viewWidth, viewHeight); | ||||
|         this.emit('sync-window-stacking', stackIndices); | ||||
|     }, | ||||
|  | ||||
|     //// Public methods //// | ||||
|  | ||||
|     beginItemDrag: function(source) { | ||||
|         this.emit('item-drag-begin'); | ||||
|     beginAppDrag: function(source) { | ||||
|         this.emit('app-drag-begin'); | ||||
|     }, | ||||
|  | ||||
|     cancelledItemDrag: function(source) { | ||||
|         this.emit('item-drag-cancelled'); | ||||
|     cancelledAppDrag: function(source) { | ||||
|         this.emit('app-drag-cancelled'); | ||||
|     }, | ||||
|  | ||||
|     endItemDrag: function(source) { | ||||
|         this.emit('item-drag-end'); | ||||
|     endAppDrag: function(source) { | ||||
|         this.emit('app-drag-end'); | ||||
|     }, | ||||
|  | ||||
|     beginWindowDrag: function(source) { | ||||
| @@ -558,13 +620,13 @@ const Overview = new Lang.Class({ | ||||
|         if (this._shown) | ||||
|             return; | ||||
|         // Do this manually instead of using _syncInputMode, to handle failure | ||||
|         if (!Main.pushModal(this._group)) | ||||
|         if (!Main.pushModal(this._overview)) | ||||
|             return; | ||||
|         this._modal = true; | ||||
|         this._animateVisible(); | ||||
|         this._shown = true; | ||||
|  | ||||
|         this._buttonPressId = this._group.connect('button-press-event', | ||||
|         this._buttonPressId = this._overview.connect('button-press-event', | ||||
|             Lang.bind(this, this._onButtonPress)); | ||||
|     }, | ||||
|  | ||||
| @@ -608,12 +670,12 @@ const Overview = new Lang.Class({ | ||||
|         // Disable unredirection while in the overview | ||||
|         Meta.disable_unredirect_for_screen(global.screen); | ||||
|         global.window_group.hide(); | ||||
|         this._group.show(); | ||||
|         this._overview.show(); | ||||
|         this._background.show(); | ||||
|         this._viewSelector.show(); | ||||
|  | ||||
|         this._group.opacity = 0; | ||||
|         Tweener.addTween(this._group, | ||||
|         this._overview.opacity = 0; | ||||
|         Tweener.addTween(this._overview, | ||||
|                          { opacity: 255, | ||||
|                            transition: 'easeOutQuad', | ||||
|                            time: ANIMATION_TIME, | ||||
| @@ -667,7 +729,7 @@ const Overview = new Lang.Class({ | ||||
|         this._syncInputMode(); | ||||
|  | ||||
|         if (this._buttonPressId > 0) | ||||
|             this._group.disconnect(this._buttonPressId); | ||||
|             this._overview.disconnect(this._buttonPressId); | ||||
|         this._buttonPressId = 0; | ||||
|     }, | ||||
|  | ||||
| @@ -709,20 +771,20 @@ const Overview = new Lang.Class({ | ||||
|  | ||||
|         if (this._shown) { | ||||
|             if (!this._modal) { | ||||
|                 if (Main.pushModal(this._group)) | ||||
|                 if (Main.pushModal(this._overview)) | ||||
|                     this._modal = true; | ||||
|                 else | ||||
|                     this.hide(); | ||||
|             } | ||||
|         } else if (this._shownTemporarily) { | ||||
|             if (this._modal) { | ||||
|                 Main.popModal(this._group); | ||||
|                 Main.popModal(this._overview); | ||||
|                 this._modal = false; | ||||
|             } | ||||
|             global.stage_input_mode = Shell.StageInputMode.FULLSCREEN; | ||||
|         } else { | ||||
|             if (this._modal) { | ||||
|                 Main.popModal(this._group); | ||||
|                 Main.popModal(this._overview); | ||||
|                 this._modal = false; | ||||
|             } | ||||
|             else if (global.stage_input_mode == Shell.StageInputMode.FULLSCREEN) | ||||
| @@ -740,7 +802,7 @@ const Overview = new Lang.Class({ | ||||
|         this._viewSelector.zoomFromOverview(); | ||||
|  | ||||
|         // Make other elements fade out. | ||||
|         Tweener.addTween(this._group, | ||||
|         Tweener.addTween(this._overview, | ||||
|                          { opacity: 0, | ||||
|                            transition: 'easeOutQuad', | ||||
|                            time: ANIMATION_TIME, | ||||
| @@ -782,7 +844,7 @@ const Overview = new Lang.Class({ | ||||
|         this._viewSelector.hide(); | ||||
|         this._desktopFade.hide(); | ||||
|         this._background.hide(); | ||||
|         this._group.hide(); | ||||
|         this._overview.hide(); | ||||
|  | ||||
|         this.visible = false; | ||||
|         this.animationInProgress = false; | ||||
|   | ||||
							
								
								
									
										121
									
								
								js/ui/panel.js
									
									
									
									
									
								
							
							
						
						
									
										121
									
								
								js/ui/panel.js
									
									
									
									
									
								
							| @@ -15,6 +15,7 @@ const Signals = imports.signals; | ||||
| const Atk = imports.gi.Atk; | ||||
|  | ||||
|  | ||||
| const CenterLayout = imports.ui.centerLayout; | ||||
| const Config = imports.misc.config; | ||||
| const CtrlAltTab = imports.ui.ctrlAltTab; | ||||
| const DND = imports.ui.dnd; | ||||
| @@ -931,12 +932,47 @@ try { | ||||
|     log('NMApplet is not supported. It is possible that your NetworkManager version is too old'); | ||||
| } | ||||
|  | ||||
| const PanelLayout = new Lang.Class({ | ||||
|     Name: 'PanelLayout', | ||||
|     Extends: CenterLayout.CenterLayout, | ||||
|  | ||||
|     vfunc_allocate: function(container, box, flags) { | ||||
|         this.parent(container, box, flags); | ||||
|  | ||||
|         let availWidth = box.x2 - box.x1; | ||||
|         let availHeight = box.y2 - box.y1; | ||||
|  | ||||
|         let [left, center, right, leftCorner, rightCorner] = container.get_children(); | ||||
|         let childBox = new Clutter.ActorBox(); | ||||
|  | ||||
|         let cornerMinWidth, cornerMinHeight; | ||||
|         let cornerWidth, cornerHeight; | ||||
|  | ||||
|         [cornerMinWidth, cornerWidth] = leftCorner.get_preferred_width(-1); | ||||
|         [cornerMinHeight, cornerHeight] = leftCorner.get_preferred_height(-1); | ||||
|         childBox.x1 = 0; | ||||
|         childBox.x2 = cornerWidth; | ||||
|         childBox.y1 = availHeight; | ||||
|         childBox.y2 = availHeight + cornerHeight; | ||||
|         leftCorner.allocate(childBox, flags); | ||||
|  | ||||
|         [cornerMinWidth, cornerWidth] = rightCorner.get_preferred_width(-1); | ||||
|         [cornerMinHeight, cornerHeight] = rightCorner.get_preferred_height(-1); | ||||
|         childBox.x1 = availWidth - cornerWidth; | ||||
|         childBox.x2 = availWidth; | ||||
|         childBox.y1 = availHeight; | ||||
|         childBox.y2 = availHeight + cornerHeight; | ||||
|         rightCorner.allocate(childBox, flags); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const Panel = new Lang.Class({ | ||||
|     Name: 'Panel', | ||||
|  | ||||
|     _init : function() { | ||||
|         this.actor = new Shell.GenericContainer({ name: 'panel', | ||||
|                                                   reactive: true }); | ||||
|         this.actor = new St.Widget({ name: 'panel', | ||||
|                                      reactive: true, | ||||
|                                      layoutManager: new PanelLayout() }); | ||||
|         this.actor._delegate = this; | ||||
|  | ||||
|         this.statusArea = {}; | ||||
| @@ -961,7 +997,6 @@ const Panel = new Lang.Class({ | ||||
|             this._leftCorner = new PanelCorner(this._rightBox, St.Side.LEFT); | ||||
|         else | ||||
|             this._leftCorner = new PanelCorner(this._leftBox, St.Side.LEFT); | ||||
|  | ||||
|         this.actor.add_actor(this._leftCorner.actor); | ||||
|  | ||||
|         if (this.actor.get_text_direction() == Clutter.TextDirection.RTL) | ||||
| @@ -970,9 +1005,6 @@ const Panel = new Lang.Class({ | ||||
|             this._rightCorner = new PanelCorner(this._rightBox, St.Side.RIGHT); | ||||
|         this.actor.add_actor(this._rightCorner.actor); | ||||
|  | ||||
|         this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth)); | ||||
|         this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight)); | ||||
|         this.actor.connect('allocate', Lang.bind(this, this._allocate)); | ||||
|         this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress)); | ||||
|  | ||||
|         Main.layoutManager.panelBox.add(this.actor); | ||||
| @@ -983,83 +1015,6 @@ const Panel = new Lang.Class({ | ||||
|         this._updatePanel(); | ||||
|     }, | ||||
|  | ||||
|     _getPreferredWidth: function(actor, forHeight, alloc) { | ||||
|         alloc.min_size = -1; | ||||
|         alloc.natural_size = Main.layoutManager.primaryMonitor.width; | ||||
|     }, | ||||
|  | ||||
|     _getPreferredHeight: function(actor, forWidth, alloc) { | ||||
|         // We don't need to implement this; it's forced by the CSS | ||||
|         alloc.min_size = -1; | ||||
|         alloc.natural_size = -1; | ||||
|     }, | ||||
|  | ||||
|     _allocate: function(actor, box, flags) { | ||||
|         let allocWidth = box.x2 - box.x1; | ||||
|         let allocHeight = box.y2 - box.y1; | ||||
|  | ||||
|         let [leftMinWidth, leftNaturalWidth] = this._leftBox.get_preferred_width(-1); | ||||
|         let [centerMinWidth, centerNaturalWidth] = this._centerBox.get_preferred_width(-1); | ||||
|         let [rightMinWidth, rightNaturalWidth] = this._rightBox.get_preferred_width(-1); | ||||
|  | ||||
|         let sideWidth, centerWidth; | ||||
|         centerWidth = centerNaturalWidth; | ||||
|         sideWidth = (allocWidth - centerWidth) / 2; | ||||
|  | ||||
|         let childBox = new Clutter.ActorBox(); | ||||
|  | ||||
|         childBox.y1 = 0; | ||||
|         childBox.y2 = allocHeight; | ||||
|         if (this.actor.get_text_direction() == Clutter.TextDirection.RTL) { | ||||
|             childBox.x1 = allocWidth - Math.min(Math.floor(sideWidth), | ||||
|                                                 leftNaturalWidth); | ||||
|             childBox.x2 = allocWidth; | ||||
|         } else { | ||||
|             childBox.x1 = 0; | ||||
|             childBox.x2 = Math.min(Math.floor(sideWidth), | ||||
|                                    leftNaturalWidth); | ||||
|         } | ||||
|         this._leftBox.allocate(childBox, flags); | ||||
|  | ||||
|         childBox.x1 = Math.ceil(sideWidth); | ||||
|         childBox.y1 = 0; | ||||
|         childBox.x2 = childBox.x1 + centerWidth; | ||||
|         childBox.y2 = allocHeight; | ||||
|         this._centerBox.allocate(childBox, flags); | ||||
|  | ||||
|         childBox.y1 = 0; | ||||
|         childBox.y2 = allocHeight; | ||||
|         if (this.actor.get_text_direction() == Clutter.TextDirection.RTL) { | ||||
|             childBox.x1 = 0; | ||||
|             childBox.x2 = Math.min(Math.floor(sideWidth), | ||||
|                                    rightNaturalWidth); | ||||
|         } else { | ||||
|             childBox.x1 = allocWidth - Math.min(Math.floor(sideWidth), | ||||
|                                                 rightNaturalWidth); | ||||
|             childBox.x2 = allocWidth; | ||||
|         } | ||||
|         this._rightBox.allocate(childBox, flags); | ||||
|  | ||||
|         let cornerMinWidth, cornerMinHeight; | ||||
|         let cornerWidth, cornerHeight; | ||||
|  | ||||
|         [cornerMinWidth, cornerWidth] = this._leftCorner.actor.get_preferred_width(-1); | ||||
|         [cornerMinHeight, cornerHeight] = this._leftCorner.actor.get_preferred_height(-1); | ||||
|         childBox.x1 = 0; | ||||
|         childBox.x2 = cornerWidth; | ||||
|         childBox.y1 = allocHeight; | ||||
|         childBox.y2 = allocHeight + cornerHeight; | ||||
|         this._leftCorner.actor.allocate(childBox, flags); | ||||
|  | ||||
|         [cornerMinWidth, cornerWidth] = this._rightCorner.actor.get_preferred_width(-1); | ||||
|         [cornerMinHeight, cornerHeight] = this._rightCorner.actor.get_preferred_height(-1); | ||||
|         childBox.x1 = allocWidth - cornerWidth; | ||||
|         childBox.x2 = allocWidth; | ||||
|         childBox.y1 = allocHeight; | ||||
|         childBox.y2 = allocHeight + cornerHeight; | ||||
|         this._rightCorner.actor.allocate(childBox, flags); | ||||
|     }, | ||||
|  | ||||
|     _onButtonPress: function(actor, event) { | ||||
|         if (event.get_source() != actor) | ||||
|             return false; | ||||
|   | ||||
| @@ -404,9 +404,17 @@ const PopupSeparatorMenuItem = new Lang.Class({ | ||||
|         this.parent({ reactive: false, | ||||
|                       can_focus: false}); | ||||
|  | ||||
|         this._drawingArea = new St.DrawingArea({ style_class: 'popup-separator-menu-item' }); | ||||
|         this.addActor(this._drawingArea, { span: -1, expand: true }); | ||||
|         this._drawingArea.connect('repaint', Lang.bind(this, this._onRepaint)); | ||||
|         this._separator = new HorzSeparator({ style_class: 'popup-separator-menu-item' }); | ||||
|         this.addActor(this._separator.actor, { span: -1, expand: true }); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const HorzSeparator = new Lang.Class({ | ||||
|     Name: 'HorzSeparator', | ||||
|  | ||||
|     _init: function (params) { | ||||
|         this.actor = new St.DrawingArea(params); | ||||
|         this.actor.connect('repaint', Lang.bind(this, this._onRepaint)); | ||||
|     }, | ||||
|  | ||||
|     _onRepaint: function(area) { | ||||
|   | ||||
| @@ -27,6 +27,9 @@ const SearchProviderIface = <interface name="org.gnome.Shell.SearchProvider"> | ||||
| <method name="ActivateResult"> | ||||
|     <arg type="s" direction="in" /> | ||||
| </method> | ||||
| <method name="LaunchSearch"> | ||||
|     <arg type="as" direction="in" /> | ||||
| </method> | ||||
| </interface>; | ||||
|  | ||||
| var SearchProviderProxy = Gio.DBusProxy.makeProxyWrapper(SearchProviderIface); | ||||
| @@ -112,6 +115,7 @@ const RemoteSearchProvider = new Lang.Class({ | ||||
|  | ||||
|         this.parent(title.toUpperCase()); | ||||
|         this._cancellable = new Gio.Cancellable(); | ||||
|         this.icon = icon; | ||||
|     }, | ||||
|  | ||||
|     createIcon: function(size, meta) { | ||||
| @@ -126,9 +130,7 @@ const RemoteSearchProvider = new Lang.Class({ | ||||
|                                               width, height, rowStride, size); | ||||
|         } | ||||
|  | ||||
|         // Ugh, but we want to fall back to something ... | ||||
|         return new St.Icon({ icon_name: 'text-x-generic', | ||||
|                              icon_size: size }); | ||||
|         return null; | ||||
|     }, | ||||
|  | ||||
|     _getResultsFinished: function(results, error) { | ||||
| @@ -196,6 +198,10 @@ const RemoteSearchProvider = new Lang.Class({ | ||||
|  | ||||
|     activateResult: function(id) { | ||||
|         this._proxy.ActivateResultRemote(id); | ||||
|     }, | ||||
|  | ||||
|     launchSearch: function(terms) { | ||||
|         this._proxy.LaunchSearchRemote(terms); | ||||
|     } | ||||
| }); | ||||
|  | ||||
|   | ||||
| @@ -163,6 +163,16 @@ const SearchProvider = new Lang.Class({ | ||||
|      */ | ||||
|     activateResult: function(id) { | ||||
|         throw new Error('Not implemented'); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * launchSearch: | ||||
|      * @terms: Current search terms | ||||
|      * | ||||
|      * Called when the user clicks the provider icon. | ||||
|      */ | ||||
|     launchSearch: function(terms) { | ||||
|         throw new Error('Not implemented'); | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(SearchProvider.prototype); | ||||
| @@ -261,5 +271,11 @@ const SearchSystem = new Lang.Class({ | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     launchSearch: function(provider) { | ||||
|         let terms = this.getTerms(); | ||||
|         provider.launchSearch(terms); | ||||
|         Main.overview.toggle(); | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(SearchSystem.prototype); | ||||
|   | ||||
| @@ -4,25 +4,191 @@ const Clutter = imports.gi.Clutter; | ||||
| const Lang = imports.lang; | ||||
| const Gtk = imports.gi.Gtk; | ||||
| const Meta = imports.gi.Meta; | ||||
| const Signals = imports.signals; | ||||
| const St = imports.gi.St; | ||||
| const Atk = imports.gi.Atk; | ||||
|  | ||||
| const AppDisplay = imports.ui.appDisplay; | ||||
| const DND = imports.ui.dnd; | ||||
| const IconGrid = imports.ui.iconGrid; | ||||
| const Main = imports.ui.main; | ||||
| const Overview = imports.ui.overview; | ||||
| const PopupMenu = imports.ui.popupMenu; | ||||
| const Search = imports.ui.search; | ||||
|  | ||||
| const MAX_SEARCH_RESULTS_ROWS = 1; | ||||
| const MAX_SEARCH_RESULTS_ROWS = 3; | ||||
| const MAX_GRID_SEARCH_RESULTS_ROWS = 1; | ||||
|  | ||||
|  | ||||
| const SearchResult = new Lang.Class({ | ||||
|     Name: 'SearchResult', | ||||
| const ListSearchResult = new Lang.Class({ | ||||
|     Name: 'ListSearchResult', | ||||
|  | ||||
|     ICON_SIZE: 64, | ||||
|  | ||||
|     _init: function(provider, metaInfo, terms) { | ||||
|         this.provider = provider; | ||||
|         this.metaInfo = metaInfo; | ||||
|         this.actor = new St.Button({ style_class: 'search-result', | ||||
|                                      reactive: true, | ||||
|                                      can_focus: true, | ||||
|                                      track_hover: true, | ||||
|                                      x_align: St.Align.START, | ||||
|                                      x_fill: true, | ||||
|                                      y_fill: true }); | ||||
|         this.actor._delegate = this; | ||||
|  | ||||
|         let content = new St.BoxLayout({ style_class: 'search-result-content', | ||||
|                                          vertical: false }); | ||||
|         this._content = content; | ||||
|         this.actor.set_child(content); | ||||
|  | ||||
|         // An icon for, or thumbnail of, content | ||||
|         let icon = this.metaInfo['createIcon'](this.ICON_SIZE); | ||||
|         if (icon) { | ||||
|             let iconBin = new St.Bin({ style_class: 'search-result-icon', | ||||
|                                     child: icon }); | ||||
|             content.add(iconBin); | ||||
|         } | ||||
|  | ||||
|         let details = new St.BoxLayout({ style_class: 'search-result-details', | ||||
|                                          vertical: true }); | ||||
|         content.add(details, { x_fill: true, | ||||
|                                y_fill: false, | ||||
|                                x_align: St.Align.START, | ||||
|                                y_align: St.Align.MIDDLE }); | ||||
|  | ||||
|         let title = new St.Label({ style_class: 'search-result-details-title', | ||||
|                                    text: this.metaInfo['name'] }) | ||||
|         details.add(title, { x_fill: true, | ||||
|                              y_fill: false, | ||||
|                              x_align: St.Align.START, | ||||
|                              y_align: St.Align.START }); | ||||
|  | ||||
|         // TODO: could highlight terms in the description here, or should | ||||
|         // providers provide what should be highlighted? | ||||
|         if (this.metaInfo['description']) { | ||||
|             let description = new St.Label({ style_class: 'search-result-details-description', | ||||
|                                              text: this.metaInfo['description'] }); | ||||
|             details.add(description, { x_fill: false, | ||||
|                                        y_fill: true, | ||||
|                                        x_align: St.Align.START, | ||||
|                                        y_align: St.Align.END }); | ||||
|         } | ||||
|  | ||||
|         this.actor.connect('clicked', Lang.bind(this, this._onResultClicked)); | ||||
|     }, | ||||
|  | ||||
|     setSelected: function(selected) { | ||||
|         if (selected) | ||||
|             this.actor.add_style_pseudo_class('selected'); | ||||
|         else | ||||
|             this.actor.remove_style_pseudo_class('selected'); | ||||
|     }, | ||||
|  | ||||
|     activate: function() { | ||||
|         this.provider.activateResult(this.metaInfo.id); | ||||
|         Main.overview.toggle(); | ||||
|     }, | ||||
|  | ||||
|     _onResultClicked: function(actor) { | ||||
|         this.activate(); | ||||
|     } | ||||
| }); | ||||
|  | ||||
|  | ||||
| const ListSearchResults = new Lang.Class({ | ||||
|     Name: 'ListSearchResults', | ||||
|     Extends: Search.SearchResultDisplay, | ||||
|  | ||||
|     _init: function(provider) { | ||||
|         this.parent(provider); | ||||
|  | ||||
|         this.actor = new St.BoxLayout({ style_class: 'results-list', | ||||
|                                         vertical: true }); | ||||
|         this.actor.connect('notify::width', Lang.bind(this, function() { | ||||
|             Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() { | ||||
|                 let results = this.getResultsForDisplay(); | ||||
|                 if (results.length == 0) | ||||
|                     return; | ||||
|  | ||||
|                 provider.getResultMetas(results, Lang.bind(this, this.renderResults)); | ||||
|             })); | ||||
|         })); | ||||
|         this._notDisplayedResult = []; | ||||
|         this._terms = []; | ||||
|         this._pendingClear = false; | ||||
|     }, | ||||
|  | ||||
|     getResultsForDisplay: function() { | ||||
|         let alreadyVisible = this._pendingClear ? 0 : this.getVisibleResultCount(); | ||||
|         let canDisplay = MAX_SEARCH_RESULTS_ROWS - alreadyVisible; | ||||
|  | ||||
|         let numResults = Math.min(this._notDisplayedResult.length, canDisplay); | ||||
|  | ||||
|         return this._notDisplayedResult.splice(0, numResults); | ||||
|     }, | ||||
|  | ||||
|     getVisibleResultCount: function() { | ||||
|         return this.actor.get_n_children(); | ||||
|     }, | ||||
|  | ||||
|     hasMoreResults: function() { | ||||
|         return this._notDisplayedResult.length > 0; | ||||
|     }, | ||||
|  | ||||
|     setResults: function(results, terms) { | ||||
|         // copy the lists | ||||
|         this._notDisplayedResult = results.slice(0); | ||||
|         this._terms = terms.slice(0); | ||||
|         this._pendingClear = true; | ||||
|     }, | ||||
|  | ||||
|     renderResults: function(metas) { | ||||
|         for (let i = 0; i < metas.length; i++) { | ||||
|             let display = new ListSearchResult(this.provider, metas[i], this._terms); | ||||
|             this.addItem(display.actor); | ||||
|  | ||||
|             display.actor.connect('key-focus-in', Lang.bind(this, this._onFocusedProviderChanged)); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     clear: function () { | ||||
|         this.actor.destroy_all_children(); | ||||
|         this._pendingClear = false; | ||||
|     }, | ||||
|  | ||||
|     getFirstResult: function() { | ||||
|         if (this.getVisibleResultCount() > 0) | ||||
|             return this.getItemAtIndex(0)._delegate; | ||||
|         else | ||||
|             return null; | ||||
|     }, | ||||
|  | ||||
|     addItem: function(actor, index) { | ||||
|         if (index !== undefined) | ||||
|             this.actor.insert_child_at_index(actor, index); | ||||
|         else | ||||
|             this.actor.add_actor(actor); | ||||
|     }, | ||||
|  | ||||
|     getItemAtIndex: function(index) { | ||||
|         return this.actor.get_child_at_index(index); | ||||
|     }, | ||||
|  | ||||
|     _onFocusedProviderChanged: function() { | ||||
|         this.emit('focused-provider-changed'); | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(ListSearchResults.prototype); | ||||
|  | ||||
|  | ||||
| const GridSearchResult = new Lang.Class({ | ||||
|     Name: 'GridSearchResult', | ||||
|  | ||||
|     _init: function(provider, metaInfo, terms) { | ||||
|         this.provider = provider; | ||||
|         this.metaInfo = metaInfo; | ||||
|         this.actor = new St.Button({ style_class: 'grid-search-result', | ||||
|                                      reactive: true, | ||||
|                                      x_align: St.Align.START, | ||||
|                                      y_fill: true }); | ||||
| @@ -31,7 +197,7 @@ const SearchResult = new Lang.Class({ | ||||
|  | ||||
|         let content = provider.createResultActor(metaInfo, terms); | ||||
|         if (content == null) { | ||||
|             content = new St.Bin({ style_class: 'search-result-content', | ||||
|             content = new St.Bin({ style_class: 'grid-search-result-content', | ||||
|                                    reactive: true, | ||||
|                                    can_focus: true, | ||||
|                                    track_hover: true, | ||||
| @@ -45,7 +211,7 @@ const SearchResult = new Lang.Class({ | ||||
|             if (content._delegate && content._delegate.getDragActorSource) | ||||
|                 this._dragActorSource = content._delegate.getDragActorSource(); | ||||
|         } | ||||
|         this._content = content; | ||||
|         this.content = content; | ||||
|         this.actor.set_child(content); | ||||
|  | ||||
|         this.actor.connect('clicked', Lang.bind(this, this._onResultClicked)); | ||||
| @@ -53,23 +219,23 @@ const SearchResult = new Lang.Class({ | ||||
|         let draggable = DND.makeDraggable(this.actor); | ||||
|         draggable.connect('drag-begin', | ||||
|                           Lang.bind(this, function() { | ||||
|                               Main.overview.beginItemDrag(this); | ||||
|                               Main.overview.beginAppDrag(this); | ||||
|                           })); | ||||
|         draggable.connect('drag-cancelled', | ||||
|                           Lang.bind(this, function() { | ||||
|                               Main.overview.cancelledItemDrag(this); | ||||
|                               Main.overview.cancelledAppDrag(this); | ||||
|                           })); | ||||
|         draggable.connect('drag-end', | ||||
|                           Lang.bind(this, function() { | ||||
|                               Main.overview.endItemDrag(this); | ||||
|                               Main.overview.endAppDrag(this); | ||||
|                           })); | ||||
|     }, | ||||
|  | ||||
|     setSelected: function(selected) { | ||||
|         if (selected) | ||||
|             this._content.add_style_pseudo_class('selected'); | ||||
|             this.content.add_style_pseudo_class('selected'); | ||||
|         else | ||||
|             this._content.remove_style_pseudo_class('selected'); | ||||
|             this.content.remove_style_pseudo_class('selected'); | ||||
|     }, | ||||
|  | ||||
|     activate: function() { | ||||
| @@ -85,7 +251,7 @@ const SearchResult = new Lang.Class({ | ||||
|         if (this._dragActorSource) | ||||
|             return this._dragActorSource; | ||||
|         // not exactly right, but alignment problems are hard to notice | ||||
|         return this._content; | ||||
|         return this.content; | ||||
|     }, | ||||
|  | ||||
|     getDragActor: function(stageX, stageY) { | ||||
| @@ -108,11 +274,11 @@ const GridSearchResults = new Lang.Class({ | ||||
|     _init: function(provider, grid) { | ||||
|         this.parent(provider); | ||||
|  | ||||
|         this._grid = grid || new IconGrid.IconGrid({ rowLimit: MAX_SEARCH_RESULTS_ROWS, | ||||
|         this._grid = grid || new IconGrid.IconGrid({ rowLimit: MAX_GRID_SEARCH_RESULTS_ROWS, | ||||
|                                                      xAlign: St.Align.START }); | ||||
|         this.actor = new St.Bin({ x_align: St.Align.START }); | ||||
|  | ||||
|         this.actor = new St.Bin({ x_align: St.Align.MIDDLE }); | ||||
|         this.actor.set_child(this._grid.actor); | ||||
|  | ||||
|         this._width = 0; | ||||
|         this.actor.connect('notify::width', Lang.bind(this, function() { | ||||
|             this._width = this.actor.width; | ||||
| @@ -130,6 +296,7 @@ const GridSearchResults = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     getResultsForDisplay: function() { | ||||
|         this._grid.actor.ensure_style(); | ||||
|         let alreadyVisible = this._pendingClear ? 0 : this._grid.visibleItemsCount(); | ||||
|         let canDisplay = this._grid.childrenInRow(this._width) * this._grid.getRowLimit() | ||||
|                          - alreadyVisible; | ||||
| @@ -143,6 +310,10 @@ const GridSearchResults = new Lang.Class({ | ||||
|         return this._grid.visibleItemsCount(); | ||||
|     }, | ||||
|  | ||||
|     hasMoreResults: function() { | ||||
|         return this._notDisplayedResult.length > 0; | ||||
|     }, | ||||
|  | ||||
|     setResults: function(results, terms) { | ||||
|         // copy the lists | ||||
|         this._notDisplayedResult = results.slice(0); | ||||
| @@ -152,8 +323,10 @@ const GridSearchResults = new Lang.Class({ | ||||
|  | ||||
|     renderResults: function(metas) { | ||||
|         for (let i = 0; i < metas.length; i++) { | ||||
|             let display = new SearchResult(this.provider, metas[i], this._terms); | ||||
|             let display = new GridSearchResult(this.provider, metas[i], this._terms); | ||||
|             this._grid.addItem(display.actor); | ||||
|  | ||||
|             display.content.connect('key-focus-in', Lang.bind(this, this._onFocusedProviderChanged)); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @@ -167,11 +340,17 @@ const GridSearchResults = new Lang.Class({ | ||||
|             return this._grid.getItemAtIndex(0)._delegate; | ||||
|         else | ||||
|             return null; | ||||
|     }, | ||||
|  | ||||
|     _onFocusedProviderChanged: function() { | ||||
|         this.emit('focused-provider-changed'); | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(GridSearchResults.prototype); | ||||
|  | ||||
| const SearchResults = new Lang.Class({ | ||||
|     Name: 'SearchResults', | ||||
|  | ||||
| const SearchDisplay = new Lang.Class({ | ||||
|     Name: 'SearchDisplay', | ||||
|  | ||||
|     _init: function(searchSystem) { | ||||
|         this._searchSystem = searchSystem; | ||||
| @@ -222,19 +401,43 @@ const SearchResults = new Lang.Class({ | ||||
|     createProviderMeta: function(provider) { | ||||
|         let providerBox = new St.BoxLayout({ style_class: 'search-section', | ||||
|                                              vertical: true }); | ||||
|         let title = new St.Label({ style_class: 'search-section-header', | ||||
|                                    text: provider.title }); | ||||
|         providerBox.add(title, { x_fill: false, x_align: St.Align.START }); | ||||
|         let providerBoxContent = new St.BoxLayout({ style_class: 'search-section-content' }); | ||||
|         let isAppsProvider = (provider instanceof AppDisplay.AppSearchProvider); | ||||
|  | ||||
|         let providerIcon; | ||||
|         if (!isAppsProvider) { | ||||
|             let separator = new PopupMenu.HorzSeparator({ style_class: 'search-section-separator' }); | ||||
|             providerBox.add(separator.actor, { expand: true }); | ||||
|  | ||||
|             providerIcon = new ProviderIcon(provider); | ||||
|             providerIcon.connect('launch-search', Lang.bind(this, function(providerIcon) { | ||||
|                 this._searchSystem.launchSearch(providerIcon.provider); | ||||
|             })); | ||||
|             providerBoxContent.add(providerIcon.actor, { x_fill: false, | ||||
|                                                          y_fill: false, | ||||
|                                                          x_align: St.Align.START, | ||||
|                                                          y_align: St.Align.START }); | ||||
|         } | ||||
|  | ||||
|         let resultDisplayBin = new St.Bin({ style_class: 'search-section-results', | ||||
|                                             x_fill: true, | ||||
|                                             y_fill: true }); | ||||
|         providerBox.add(resultDisplayBin, { expand: true }); | ||||
|         let resultDisplay = new GridSearchResults(provider); | ||||
|         providerBoxContent.add(resultDisplayBin, { expand: true }); | ||||
|  | ||||
|         providerBox.add(providerBoxContent); | ||||
|  | ||||
|         let resultDisplay; | ||||
|         if (isAppsProvider) | ||||
|             resultDisplay = new GridSearchResults(provider); | ||||
|         else | ||||
|             resultDisplay = new ListSearchResults(provider); | ||||
|  | ||||
|         resultDisplay.connect('focused-provider-changed', Lang.bind(this, this._updateProviderIconCanFocus)); | ||||
|         resultDisplayBin.set_child(resultDisplay.actor); | ||||
|  | ||||
|         this._providerMeta.push({ provider: provider, | ||||
|                                   actor: providerBox, | ||||
|                                   icon: providerIcon, | ||||
|                                   resultDisplay: resultDisplay }); | ||||
|         this._content.add(providerBox); | ||||
|     }, | ||||
| @@ -255,6 +458,7 @@ const SearchResults = new Lang.Class({ | ||||
|             let meta = this._providerMeta[i]; | ||||
|             meta.resultDisplay.clear(); | ||||
|             meta.actor.hide(); | ||||
|             if (meta.icon) meta.icon.actor.can_focus = false; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @@ -262,6 +466,7 @@ const SearchResults = new Lang.Class({ | ||||
|         let meta = this._metaForProvider(provider); | ||||
|         meta.resultDisplay.clear(); | ||||
|         meta.actor.hide(); | ||||
|         if (meta.icon) meta.icon.actor.can_focus = false; | ||||
|     }, | ||||
|  | ||||
|     reset: function() { | ||||
| @@ -284,6 +489,11 @@ const SearchResults = new Lang.Class({ | ||||
|         return this._providerMeta[this._providers.indexOf(provider)]; | ||||
|     }, | ||||
|  | ||||
|     _metaForProvidersExcept: function(provider) { | ||||
|         let skip = this._providers.indexOf(provider); | ||||
|         return this._providerMeta.filter(function(meta, index) { return index != skip }, this); | ||||
|     }, | ||||
|  | ||||
|     _maybeSetInitialSelection: function() { | ||||
|         let newDefaultResult = null; | ||||
|  | ||||
| @@ -341,6 +551,9 @@ const SearchResults = new Lang.Class({ | ||||
|             meta.resultDisplay.setResults(providerResults, terms); | ||||
|             let results = meta.resultDisplay.getResultsForDisplay(); | ||||
|  | ||||
|             if (meta.icon) | ||||
|                 meta.icon.moreIcon.visible = meta.resultDisplay.hasMoreResults(); | ||||
|  | ||||
|             provider.getResultMetas(results, Lang.bind(this, function(metas) { | ||||
|                 this._clearDisplayForProvider(provider); | ||||
|                 meta.actor.show(); | ||||
| @@ -374,6 +587,15 @@ const SearchResults = new Lang.Class({ | ||||
|             this._defaultResult.setSelected(highlight); | ||||
|     }, | ||||
|  | ||||
|     _updateProviderIconCanFocus: function(resultDisplay) { | ||||
|         let meta = this._metaForProvider(resultDisplay.provider); | ||||
|         if (meta.icon) | ||||
|             meta.icon.actor.can_focus = true; | ||||
|  | ||||
|         let others = this._metaForProvidersExcept(resultDisplay.provider); | ||||
|         others.forEach(function(meta) { if (meta.icon) meta.icon.actor.can_focus = false; }); | ||||
|     }, | ||||
|  | ||||
|     navigateFocus: function(direction) { | ||||
|         let rtl = this.actor.get_text_direction() == Clutter.TextDirection.RTL; | ||||
|         if (direction == Gtk.DirectionType.TAB_BACKWARD || | ||||
| @@ -393,3 +615,65 @@ const SearchResults = new Lang.Class({ | ||||
|         } | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const ProviderIcon = new Lang.Class({ | ||||
|     Name: 'ProviderIcon', | ||||
|  | ||||
|     PROVIDER_ICON_SIZE: 48, | ||||
|  | ||||
|     MORE_ICON_SIZE: 16, | ||||
|  | ||||
|     _init: function(provider) { | ||||
|         this.provider = provider; | ||||
|  | ||||
|         this.actor = new St.Button({ style_class: 'search-section-icon-bin', | ||||
|                                      reactive: true, | ||||
|                                      track_hover: true }); | ||||
|         this.actor.connect('clicked', Lang.bind(this, this._onIconClicked)); | ||||
|  | ||||
|         this._content = new St.Widget({ layout_manager: new Clutter.BinLayout() }); | ||||
|         this.actor.set_child(this._content); | ||||
|  | ||||
|         let rtl = (this.actor.get_text_direction() == Clutter.TextDirection.RTL); | ||||
|  | ||||
|         this.moreIcon = new St.Icon({ style_class: 'search-section-icon-more', | ||||
|                                       icon_size: this.MORE_ICON_SIZE, | ||||
|                                       icon_name: 'list-add-symbolic', | ||||
|                                       visible: false, | ||||
|                                       x_align: rtl ? Clutter.ActorAlign.START : Clutter.ActorAlign.END, | ||||
|                                       y_align: Clutter.ActorAlign.END, | ||||
|                                       // HACK: without these, ClutterBinLayout | ||||
|                                       // ignores alignment properties on the actor | ||||
|                                       x_expand: true, | ||||
|                                       y_expand: true }); | ||||
|  | ||||
|         this._iconBin = new St.Bin({ style_class: 'search-section-icon', | ||||
|                                      width: this.PROVIDER_ICON_SIZE, | ||||
|                                      height: this.PROVIDER_ICON_SIZE }); | ||||
|  | ||||
|         this._content.add_actor(this._iconBin); | ||||
|         this._content.add_actor(this.moreIcon); | ||||
|  | ||||
|         this._createProviderIcon(); | ||||
|     }, | ||||
|  | ||||
|     _createProviderIcon: function() { | ||||
|         let icon = new St.Icon({ icon_size: this.PROVIDER_ICON_SIZE }); | ||||
|  | ||||
|         if (this.provider.icon) | ||||
|             icon.gicon = this.provider.icon; | ||||
|         else | ||||
|             icon.icon_name = 'application-x-executable'; | ||||
|  | ||||
|         this._iconBin.set_child(icon); | ||||
|     }, | ||||
|  | ||||
|     activate: function() { | ||||
|         this.emit('launch-search'); | ||||
|     }, | ||||
|  | ||||
|     _onIconClicked: function(actor) { | ||||
|         this.activate(); | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(ProviderIcon.prototype); | ||||
|   | ||||
| @@ -52,7 +52,7 @@ const ViewSelector = new Lang.Class({ | ||||
|  | ||||
|         this._activePage = null; | ||||
|  | ||||
|         this.active = false; | ||||
|         this.entryNonEmpty = false; | ||||
|         this._searchPending = false; | ||||
|         this._searchTimeoutId = 0; | ||||
|  | ||||
| @@ -90,7 +90,7 @@ const ViewSelector = new Lang.Class({ | ||||
|         this._appsPage = this._addPage(this._appDisplay.actor, null, | ||||
|                                        _("Applications"), 'system-run-symbolic'); | ||||
|  | ||||
|         this._searchResults = new SearchDisplay.SearchResults(this._searchSystem); | ||||
|         this._searchResults = new SearchDisplay.SearchDisplay(this._searchSystem); | ||||
|         this._searchPage = this._addPage(this._searchResults.actor, this._entry, | ||||
|                                          _("Search"), 'edit-find-symbolic'); | ||||
|  | ||||
| @@ -114,9 +114,6 @@ const ViewSelector = new Lang.Class({ | ||||
|  | ||||
|         global.focus_manager.add_group(this._searchResults.actor); | ||||
|  | ||||
|         Main.overview.connect('item-drag-begin', | ||||
|                               Lang.bind(this, this._resetShowAppsButton)); | ||||
|  | ||||
|         this._stageKeyPressId = 0; | ||||
|         Main.overview.connect('showing', Lang.bind(this, | ||||
|             function () { | ||||
| @@ -133,17 +130,6 @@ const ViewSelector = new Lang.Class({ | ||||
|                 } | ||||
|             })); | ||||
|  | ||||
|         // Public constraints which may be used to tie actors' height or | ||||
|         // vertical position to the current tab's content; as the content's | ||||
|         // height and position depend on the view selector's style properties | ||||
|         // (e.g. font size, padding, spacing, ...) it would be extremely hard | ||||
|         // and ugly to get these from the outside. While it would be possible | ||||
|         // to use position and height properties directly, outside code would | ||||
|         // need to ensure that the content is properly allocated before | ||||
|         // accessing the properties. | ||||
|         this.constrainHeight = new Clutter.BindConstraint({ source: this._pageArea, | ||||
|                                                             coordinate: Clutter.BindCoordinate.HEIGHT }); | ||||
|  | ||||
|         global.display.add_keybinding('toggle-application-view', | ||||
|                                       new Gio.Settings({ schema: SHELL_KEYBINDINGS_SCHEMA }), | ||||
|                                       Meta.KeyBindingFlags.NONE, | ||||
| @@ -230,7 +216,9 @@ const ViewSelector = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _onShowAppsButtonToggled: function() { | ||||
|         if (this.active) | ||||
|         this.emit('apps-button-checked', this._showAppsButton.checked); | ||||
|  | ||||
|         if (this.entryNonEmpty) | ||||
|             this.reset(); | ||||
|         else | ||||
|             this._showPage(this._showAppsButton.checked ? this._appsPage | ||||
| @@ -246,7 +234,7 @@ const ViewSelector = new Lang.Class({ | ||||
|         let symbol = event.get_key_symbol(); | ||||
|  | ||||
|         if (symbol == Clutter.Escape) { | ||||
|             if (this.active) | ||||
|             if (this.entryNonEmpty) | ||||
|                 this.reset(); | ||||
|             else if (this._showAppsButton.checked) | ||||
|                 this._resetShowAppsButton(); | ||||
| @@ -254,9 +242,9 @@ const ViewSelector = new Lang.Class({ | ||||
|                 Main.overview.hide(); | ||||
|             return true; | ||||
|         } else if (Clutter.keysym_to_unicode(symbol) || | ||||
|                    (symbol == Clutter.BackSpace && this.active)) { | ||||
|                    (symbol == Clutter.BackSpace && this.entryNonEmpty)) { | ||||
|             this.startSearch(event); | ||||
|         } else if (!this.active) { | ||||
|         } else if (!this.entryNonEmpty) { | ||||
|             if (symbol == Clutter.Tab || symbol == Clutter.Down) { | ||||
|                 this._activePage.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false); | ||||
|                 return true; | ||||
| @@ -269,6 +257,7 @@ const ViewSelector = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _searchCancelled: function() { | ||||
|         this.emit('search-cancelled'); | ||||
|         this._showPage(this._showAppsButton.checked ? this._appsPage | ||||
|                                                     : this._workspacesPage); | ||||
|  | ||||
| @@ -330,13 +319,14 @@ const ViewSelector = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _onTextChanged: function (se, prop) { | ||||
|         let searchPreviouslyActive = this.active; | ||||
|         this.active = this._entry.get_text() != ''; | ||||
|         this._searchPending = this.active && !searchPreviouslyActive; | ||||
|         let searchPreviouslyActive = this.entryNonEmpty; | ||||
|         this.entryNonEmpty = this._entry.get_text() != ''; | ||||
|         this._searchPending = this.entryNonEmpty && !searchPreviouslyActive; | ||||
|         if (this._searchPending) { | ||||
|             this._searchResults.startingSearch(); | ||||
|         } | ||||
|         if (this.active) { | ||||
|         if (this.entryNonEmpty) { | ||||
|             this.emit('search-begin'); | ||||
|             this._entry.set_secondary_icon(this._activeIcon); | ||||
|  | ||||
|             if (this._iconClickedId == 0) { | ||||
| @@ -353,7 +343,7 @@ const ViewSelector = new Lang.Class({ | ||||
|             this._entry.set_secondary_icon(this._inactiveIcon); | ||||
|             this._searchCancelled(); | ||||
|         } | ||||
|         if (!this.active) { | ||||
|         if (!this.entryNonEmpty) { | ||||
|             if (this._searchTimeoutId > 0) { | ||||
|                 Mainloop.source_remove(this._searchTimeoutId); | ||||
|                 this._searchTimeoutId = 0; | ||||
| @@ -381,7 +371,7 @@ const ViewSelector = new Lang.Class({ | ||||
|             } | ||||
|             this._searchResults.activateDefault(); | ||||
|             return true; | ||||
|         } else if (this.active) { | ||||
|         } else if (this.entryNonEmpty) { | ||||
|             let arrowNext, nextDirection; | ||||
|             if (entry.get_text_direction() == Clutter.TextDirection.RTL) { | ||||
|                 arrowNext = Clutter.Left; | ||||
|   | ||||
| @@ -1073,6 +1073,9 @@ const Workspace = new Lang.Class({ | ||||
|      *  ANIMATE - Indicates that we need animate changing position. | ||||
|      */ | ||||
|     positionWindows: function(flags) { | ||||
|         if (this.leavingOverview) | ||||
|             return; | ||||
|  | ||||
|         this._positionWindowsFlags |= flags; | ||||
|  | ||||
|         if (this._positionWindowsId > 0) | ||||
|   | ||||
| @@ -493,6 +493,7 @@ const ThumbnailsBox = new Lang.Class({ | ||||
|     _init: function() { | ||||
|         this.actor = new Shell.GenericContainer({ reactive: true, | ||||
|                                                   style_class: 'workspace-thumbnails', | ||||
|                                                   can_focus: true, | ||||
|                                                   request_mode: Clutter.RequestMode.WIDTH_FOR_HEIGHT }); | ||||
|         this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth)); | ||||
|         this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight)); | ||||
| @@ -526,6 +527,8 @@ const ThumbnailsBox = new Lang.Class({ | ||||
|         this._dropPlaceholder = new St.Bin({ style_class: 'placeholder' }); | ||||
|         this.actor.add_actor(this._dropPlaceholder); | ||||
|  | ||||
|         this.visible = true; | ||||
|  | ||||
|         this._targetScale = 0; | ||||
|         this._scale = 0; | ||||
|         this._pendingScaleUpdate = false; | ||||
| @@ -541,12 +544,21 @@ const ThumbnailsBox = new Lang.Class({ | ||||
|  | ||||
|         this.actor.connect('button-press-event', function() { return true; }); | ||||
|         this.actor.connect('button-release-event', Lang.bind(this, this._onButtonRelease)); | ||||
|         this.actor.connect('scroll-event', | ||||
|                            Lang.bind(this, this._onScrollEvent)); | ||||
|         this.actor.connect('key-release-event', | ||||
|                            Lang.bind(this, this._onKeyRelease)); | ||||
|  | ||||
|         Main.overview.connect('item-drag-begin', | ||||
|         Main.overview.connect('showing', | ||||
|                               Lang.bind(this, this._createThumbnails)); | ||||
|         Main.overview.connect('hidden', | ||||
|                               Lang.bind(this, this._destroyThumbnails)); | ||||
|  | ||||
|         Main.overview.connect('app-drag-begin', | ||||
|                               Lang.bind(this, this._onDragBegin)); | ||||
|         Main.overview.connect('item-drag-end', | ||||
|         Main.overview.connect('app-drag-end', | ||||
|                               Lang.bind(this, this._onDragEnd)); | ||||
|         Main.overview.connect('item-drag-cancelled', | ||||
|         Main.overview.connect('app-drag-cancelled', | ||||
|                               Lang.bind(this, this._onDragCancelled)); | ||||
|         Main.overview.connect('window-drag-begin', | ||||
|                               Lang.bind(this, this._onDragBegin)); | ||||
| @@ -719,10 +731,16 @@ const ThumbnailsBox = new Lang.Class({ | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     show: function() { | ||||
|     _createThumbnails: function() { | ||||
|         this._switchWorkspaceNotifyId = | ||||
|             global.window_manager.connect('switch-workspace', | ||||
|                                           Lang.bind(this, this._activeWorkspaceChanged)); | ||||
|         this._nWorkspacesNotifyId = | ||||
|             global.screen.connect('notify::n-workspaces', | ||||
|                                   Lang.bind(this, this._workspacesChanged)); | ||||
|         this._syncStackingId = | ||||
|             Main.overview.connect('sync-window-stacking', | ||||
|                                   Lang.bind(this, this.syncStacking)); | ||||
|  | ||||
|         this._targetScale = 0; | ||||
|         this._scale = 0; | ||||
| @@ -746,17 +764,86 @@ const ThumbnailsBox = new Lang.Class({ | ||||
|         this.addThumbnails(0, global.screen.n_workspaces); | ||||
|     }, | ||||
|  | ||||
|     hide: function() { | ||||
|     _destroyThumbnails: function() { | ||||
|         if (this._switchWorkspaceNotifyId > 0) { | ||||
|             global.window_manager.disconnect(this._switchWorkspaceNotifyId); | ||||
|             this._switchWorkspaceNotifyId = 0; | ||||
|         } | ||||
|         if (this._nWorkspacesNotifyId > 0) { | ||||
|             global.screen.disconnect(this._nWorkspacesNotifyId); | ||||
|             this._nWorkspacesNotifyId = 0; | ||||
|         } | ||||
|         if (this._syncStackingId > 0) { | ||||
|             Main.overview.disconnect(this._syncStackingId); | ||||
|             this._syncStackingId = 0; | ||||
|         } | ||||
|  | ||||
|         for (let w = 0; w < this._thumbnails.length; w++) | ||||
|             this._thumbnails[w].destroy(); | ||||
|         this._thumbnails = []; | ||||
|     }, | ||||
|  | ||||
|     _computeThumbnailsX: function() { | ||||
|         this._targetX = this.actor.get_x(); | ||||
|  | ||||
|         let rtl = (this.actor.get_text_direction() == Clutter.TextDirection.RTL); | ||||
|  | ||||
|         if (rtl) | ||||
|             this._hiddenX = -this.actor.width; | ||||
|         else | ||||
|             this._hiddenX = this._targetX + this.actor.width; | ||||
|     }, | ||||
|  | ||||
|     show: function() { | ||||
|         if (this.visible) | ||||
|             return; | ||||
|  | ||||
|         this.visible = true; | ||||
|  | ||||
|         this.actor.show(); | ||||
|         Tweener.addTween(this.actor, { translation_x: this._targetX, | ||||
|                                        transition: 'easeOutQuad', | ||||
|                                        time: SLIDE_ANIMATION_TIME | ||||
|                                      }); | ||||
|     }, | ||||
|  | ||||
|     hide: function() { | ||||
|         if (!this.visible) | ||||
|             return; | ||||
|  | ||||
|         this.visible = false; | ||||
|  | ||||
|         Tweener.addTween(this.actor, { translation_x: this._hiddenX, | ||||
|                                        transition: 'easeOutQuad', | ||||
|                                        time: SLIDE_ANIMATION_TIME, | ||||
|                                        onComplete: Lang.bind(this, function () { | ||||
|                                            this.actor.hide(); | ||||
|                                        }) | ||||
|                                      }); | ||||
|     }, | ||||
|  | ||||
|     _workspacesChanged: function() { | ||||
|         let oldNumWorkspaces = this._thumbnails.length; | ||||
|         let newNumWorkspaces = global.screen.n_workspaces; | ||||
|         let active = global.screen.get_active_workspace_index(); | ||||
|  | ||||
|         if (newNumWorkspaces > oldNumWorkspaces) { | ||||
|             this.addThumbnails(oldNumWorkspaces, newNumWorkspaces - oldNumWorkspaces); | ||||
|         } else { | ||||
|             let removedIndex; | ||||
|             let removedNum = oldNumWorkspaces - newNumWorkspaces; | ||||
|             for (let w = 0; w < oldNumWorkspaces; w++) { | ||||
|                 let metaWorkspace = global.screen.get_workspace_by_index(w); | ||||
|                 if (this._thumbnails[w].metaWorkspace != metaWorkspace) { | ||||
|                     removedIndex = w; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             this.removeThumbnails(removedIndex, removedNum); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     addThumbnails: function(start, count) { | ||||
|         for (let k = start; k < start + count; k++) { | ||||
|             let metaWorkspace = global.screen.get_workspace_by_index(k); | ||||
| @@ -802,7 +889,7 @@ const ThumbnailsBox = new Lang.Class({ | ||||
|         this._queueUpdateStates(); | ||||
|     }, | ||||
|  | ||||
|     syncStacking: function(stackIndices) { | ||||
|     syncStacking: function(actor, stackIndices) { | ||||
|         for (let i = 0; i < this._thumbnails.length; i++) | ||||
|             this._thumbnails[i].syncStacking(stackIndices); | ||||
|     }, | ||||
| @@ -924,6 +1011,8 @@ const ThumbnailsBox = new Lang.Class({ | ||||
|                                    onCompleteScope: this | ||||
|                                  }); | ||||
|             }); | ||||
|  | ||||
|         this._computeThumbnailsX(); | ||||
|     }, | ||||
|  | ||||
|     _queueUpdateStates: function() { | ||||
| @@ -1157,5 +1246,30 @@ const ThumbnailsBox = new Lang.Class({ | ||||
|                            }, | ||||
|                            onCompleteScope: this | ||||
|                          }); | ||||
|     }, | ||||
|  | ||||
|     _onScrollEvent: function (actor, event) { | ||||
|         switch (event.get_scroll_direction()) { | ||||
|         case Clutter.ScrollDirection.UP: | ||||
|             Main.wm.actionMoveWorkspace(Meta.MotionDirection.UP); | ||||
|             break; | ||||
|         case Clutter.ScrollDirection.DOWN: | ||||
|             Main.wm.actionMoveWorkspace(Meta.MotionDirection.DOWN); | ||||
|             break; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _onKeyRelease: function (actor, event) { | ||||
|         switch (event.get_key_symbol()) { | ||||
|         case Clutter.KEY_Up: | ||||
|             Main.wm.actionMoveWorkspace(Meta.MotionDirection.UP); | ||||
|             break; | ||||
|         case Clutter.KEY_Down: | ||||
|             Main.wm.actionMoveWorkspace(Meta.MotionDirection.DOWN); | ||||
|             break; | ||||
|         case Clutter.KEY_Return: | ||||
|             Main.overview.toggle(); | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| }); | ||||
|   | ||||
| @@ -22,8 +22,6 @@ const MAX_WORKSPACES = 16; | ||||
|  | ||||
| const OVERRIDE_SCHEMA = 'org.gnome.shell.overrides'; | ||||
|  | ||||
| const CONTROLS_POP_IN_TIME = 0.1; | ||||
|  | ||||
|  | ||||
| const WorkspacesView = new Lang.Class({ | ||||
|     Name: 'WorkspacesView', | ||||
| @@ -106,9 +104,9 @@ const WorkspacesView = new Lang.Class({ | ||||
|             global.window_manager.connect('switch-workspace', | ||||
|                                           Lang.bind(this, this._activeWorkspaceChanged)); | ||||
|  | ||||
|         this._itemDragBeginId = Main.overview.connect('item-drag-begin', | ||||
|         this._appDragBeginId = Main.overview.connect('app-drag-begin', | ||||
|                                                       Lang.bind(this, this._dragBegin)); | ||||
|         this._itemDragEndId = Main.overview.connect('item-drag-end', | ||||
|         this._appDragEndId = Main.overview.connect('app-drag-end', | ||||
|                                                      Lang.bind(this, this._dragEnd)); | ||||
|         this._windowDragBeginId = Main.overview.connect('window-drag-begin', | ||||
|                                                         Lang.bind(this, this._dragBegin)); | ||||
| @@ -327,13 +325,13 @@ const WorkspacesView = new Lang.Class({ | ||||
|         if (this._inDrag) | ||||
|             this._dragEnd(); | ||||
|  | ||||
|         if (this._itemDragBeginId > 0) { | ||||
|             Main.overview.disconnect(this._itemDragBeginId); | ||||
|             this._itemDragBeginId = 0; | ||||
|         if (this._appDragBeginId > 0) { | ||||
|             Main.overview.disconnect(this._appDragBeginId); | ||||
|             this._appDragBeginId = 0; | ||||
|         } | ||||
|         if (this._itemDragEndId > 0) { | ||||
|             Main.overview.disconnect(this._itemDragEndId); | ||||
|             this._itemDragEndId = 0; | ||||
|         if (this._appDragEndId > 0) { | ||||
|             Main.overview.disconnect(this._appDragEndId); | ||||
|             this._appDragEndId = 0; | ||||
|         } | ||||
|         if (this._windowDragBeginId > 0) { | ||||
|             Main.overview.disconnect(this._windowDragBeginId); | ||||
| @@ -460,25 +458,8 @@ const WorkspacesDisplay = new Lang.Class({ | ||||
|         this.actor.connect('parent-set', Lang.bind(this, this._parentSet)); | ||||
|         this.actor.set_clip_to_allocation(true); | ||||
|  | ||||
|         let controls = new St.Bin({ style_class: 'workspace-controls', | ||||
|                                     request_mode: Clutter.RequestMode.WIDTH_FOR_HEIGHT, | ||||
|                                     y_align: St.Align.START, | ||||
|                                     y_fill: true }); | ||||
|         this._controls = controls; | ||||
|         this.actor.add_actor(controls); | ||||
|  | ||||
|         controls.reactive = true; | ||||
|         controls.track_hover = true; | ||||
|         controls.connect('notify::hover', | ||||
|                          Lang.bind(this, this._onControlsHoverChanged)); | ||||
|         controls.connect('scroll-event', | ||||
|                          Lang.bind(this, this._onScrollEvent)); | ||||
|  | ||||
|         this._primaryIndex = Main.layoutManager.primaryIndex; | ||||
|  | ||||
|         this._thumbnailsBox = new WorkspaceThumbnail.ThumbnailsBox(); | ||||
|         controls.add_actor(this._thumbnailsBox.actor); | ||||
|  | ||||
|         this._workspacesViews = null; | ||||
|         this._primaryScrollAdjustment = null; | ||||
|  | ||||
| @@ -491,34 +472,14 @@ const WorkspacesDisplay = new Lang.Class({ | ||||
|         this._inDrag = false; | ||||
|         this._cancelledDrag = false; | ||||
|  | ||||
|         this._controlsInitiallyHovered = false; | ||||
|         this._alwaysZoomOut = false; | ||||
|         this._zoomOut = false; | ||||
|         this._zoomFraction = 0; | ||||
|  | ||||
|         this._updateAlwaysZoom(); | ||||
|  | ||||
|         // If we stop hiding the overview on layout changes, we will need to | ||||
|         // update the _workspacesViews here | ||||
|         Main.layoutManager.connect('monitors-changed', Lang.bind(this, this._updateAlwaysZoom)); | ||||
|  | ||||
|         Main.xdndHandler.connect('drag-begin', Lang.bind(this, function(){ | ||||
|             this._alwaysZoomOut = true; | ||||
|         })); | ||||
|  | ||||
|         Main.xdndHandler.connect('drag-end', Lang.bind(this, function(){ | ||||
|             this._alwaysZoomOut = false; | ||||
|             this._updateAlwaysZoom(); | ||||
|         })); | ||||
|  | ||||
|         global.screen.connect('notify::n-workspaces', | ||||
|                               Lang.bind(this, this._workspacesChanged)); | ||||
|  | ||||
|         this._switchWorkspaceNotifyId = 0; | ||||
|  | ||||
|         this._itemDragBeginId = 0; | ||||
|         this._itemDragCancelledId = 0; | ||||
|         this._itemDragEndId = 0; | ||||
|         this._appDragBeginId = 0; | ||||
|         this._appDragCancelledId = 0; | ||||
|         this._appDragEndId = 0; | ||||
|         this._windowDragBeginId = 0; | ||||
|         this._windowDragCancelledId = 0; | ||||
|         this._windowDragEndId = 0; | ||||
| @@ -538,41 +499,20 @@ const WorkspacesDisplay = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     show: function() { | ||||
|         if(!this._alwaysZoomOut) { | ||||
|             let [mouseX, mouseY] = global.get_pointer(); | ||||
|             let [x, y] = this._controls.get_transformed_position(); | ||||
|             let [width, height] = this._controls.get_transformed_size(); | ||||
|             let visibleWidth = this._controls.get_theme_node().get_length('visible-width'); | ||||
|             let rtl = (Clutter.get_default_text_direction () == Clutter.TextDirection.RTL); | ||||
|             if(rtl) | ||||
|                 x = x + width - visibleWidth; | ||||
|             if(mouseX > x - 0.5 && mouseX < x + visibleWidth + 0.5 && | ||||
|                mouseY > y - 0.5 && mouseY < y + height + 0.5) | ||||
|                 this._controlsInitiallyHovered = true; | ||||
|         } | ||||
|  | ||||
|         this._zoomOut = this._alwaysZoomOut; | ||||
|         this._zoomFraction = this._alwaysZoomOut ? 1 : 0; | ||||
|         this._updateZoom(); | ||||
|  | ||||
|         this._controls.show(); | ||||
|         this._thumbnailsBox.show(); | ||||
|         this._updateSwitcherVisibility(); | ||||
|  | ||||
|         this._updateWorkspacesViews(); | ||||
|  | ||||
|         this._restackedNotifyId = | ||||
|             global.screen.connect('restacked', | ||||
|             Main.overview.connect('sync-window-stacking', | ||||
|                                   Lang.bind(this, this._onRestacked)); | ||||
|  | ||||
|         if (this._itemDragBeginId == 0) | ||||
|             this._itemDragBeginId = Main.overview.connect('item-drag-begin', | ||||
|         if (this._appDragBeginId == 0) | ||||
|             this._appDragBeginId = Main.overview.connect('app-drag-begin', | ||||
|                                                           Lang.bind(this, this._dragBegin)); | ||||
|         if (this._itemDragCancelledId == 0) | ||||
|             this._itemDragCancelledId = Main.overview.connect('item-drag-cancelled', | ||||
|         if (this._appDragCancelledId == 0) | ||||
|             this._appDragCancelledId = Main.overview.connect('app-drag-cancelled', | ||||
|                                                               Lang.bind(this, this._dragCancelled)); | ||||
|         if (this._itemDragEndId == 0) | ||||
|             this._itemDragEndId = Main.overview.connect('item-drag-end', | ||||
|         if (this._appDragEndId == 0) | ||||
|             this._appDragEndId = Main.overview.connect('app-drag-end', | ||||
|                                                         Lang.bind(this, this._dragEnd)); | ||||
|         if (this._windowDragBeginId == 0) | ||||
|             this._windowDragBeginId = Main.overview.connect('window-drag-begin', | ||||
| @@ -583,8 +523,6 @@ const WorkspacesDisplay = new Lang.Class({ | ||||
|         if (this._windowDragEndId == 0) | ||||
|             this._windowDragEndId = Main.overview.connect('window-drag-end', | ||||
|                                                           Lang.bind(this, this._dragEnd)); | ||||
|  | ||||
|         this._onRestacked(); | ||||
|     }, | ||||
|  | ||||
|     zoomFromOverview: function() { | ||||
| @@ -594,27 +532,21 @@ const WorkspacesDisplay = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     hide: function() { | ||||
|         this._controls.hide(); | ||||
|         this._thumbnailsBox.hide(); | ||||
|  | ||||
|         if (!this._alwaysZoomOut) | ||||
|             this.zoomFraction = 0; | ||||
|  | ||||
|         if (this._restackedNotifyId > 0){ | ||||
|             global.screen.disconnect(this._restackedNotifyId); | ||||
|             Main.overview.disconnect(this._restackedNotifyId); | ||||
|             this._restackedNotifyId = 0; | ||||
|         } | ||||
|         if (this._itemDragBeginId > 0) { | ||||
|             Main.overview.disconnect(this._itemDragBeginId); | ||||
|             this._itemDragBeginId = 0; | ||||
|         if (this._appDragBeginId > 0) { | ||||
|             Main.overview.disconnect(this._appDragBeginId); | ||||
|             this._appDragBeginId = 0; | ||||
|         } | ||||
|         if (this._itemDragCancelledId > 0) { | ||||
|             Main.overview.disconnect(this._itemDragCancelledId); | ||||
|             this._itemDragCancelledId = 0; | ||||
|         if (this._appDragCancelledId > 0) { | ||||
|             Main.overview.disconnect(this._appDragCancelledId); | ||||
|             this._appDragCancelledId = 0; | ||||
|         } | ||||
|         if (this._itemDragEndId > 0) { | ||||
|             Main.overview.disconnect(this._itemDragEndId); | ||||
|             this._itemDragEndId = 0; | ||||
|         if (this._appDragEndId > 0) { | ||||
|             Main.overview.disconnect(this._appDragEndId); | ||||
|             this._appDragEndId = 0; | ||||
|         } | ||||
|         if (this._windowDragBeginId > 0) { | ||||
|             Main.overview.disconnect(this._windowDragBeginId); | ||||
| @@ -745,73 +677,15 @@ const WorkspacesDisplay = new Lang.Class({ | ||||
|         return this._getPrimaryView().getActiveWorkspace().hasMaximizedWindows(); | ||||
|     }, | ||||
|  | ||||
|     // zoomFraction property allows us to tween the controls sliding in and out | ||||
|     set zoomFraction(fraction) { | ||||
|         this._zoomFraction = fraction; | ||||
|         this.actor.queue_relayout(); | ||||
|     }, | ||||
|  | ||||
|     get zoomFraction() { | ||||
|         return this._zoomFraction; | ||||
|     }, | ||||
|  | ||||
|     _updateAlwaysZoom: function()  { | ||||
|         // Always show the pager if workspaces are actually used, | ||||
|         // e.g. there are windows on more than one | ||||
|         this._alwaysZoomOut = global.screen.n_workspaces > 2; | ||||
|  | ||||
|         if (this._alwaysZoomOut) | ||||
|             return; | ||||
|  | ||||
|         let monitors = Main.layoutManager.monitors; | ||||
|         let primary = Main.layoutManager.primaryMonitor; | ||||
|  | ||||
|         /* Look for any monitor to the right of the primary, if there is | ||||
|          * one, we always keep zoom out, otherwise its hard to reach | ||||
|          * the thumbnail area without passing into the next monitor. */ | ||||
|         for (let i = 0; i < monitors.length; i++) { | ||||
|             if (monitors[i].x >= primary.x + primary.width) { | ||||
|                 this._alwaysZoomOut = true; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _getPreferredWidth: function (actor, forHeight, alloc) { | ||||
|         // pass through the call in case the child needs it, but report 0x0 | ||||
|         this._controls.get_preferred_width(forHeight); | ||||
|     }, | ||||
|  | ||||
|     _getPreferredHeight: function (actor, forWidth, alloc) { | ||||
|         // pass through the call in case the child needs it, but report 0x0 | ||||
|         this._controls.get_preferred_height(forWidth); | ||||
|     }, | ||||
|  | ||||
|     _allocate: function (actor, box, flags) { | ||||
|         let childBox = new Clutter.ActorBox(); | ||||
|  | ||||
|         let totalWidth = box.x2 - box.x1; | ||||
|  | ||||
|         // width of the controls | ||||
|         let [controlsMin, controlsNatural] = this._controls.get_preferred_width(box.y2 - box.y1); | ||||
|  | ||||
|         // Amount of space on the screen we reserve for the visible control | ||||
|         let controlsVisible = this._controls.get_theme_node().get_length('visible-width'); | ||||
|         let controlsReserved = controlsVisible * (1 - this._zoomFraction) + controlsNatural * this._zoomFraction; | ||||
|  | ||||
|         let rtl = (Clutter.get_default_text_direction () == Clutter.TextDirection.RTL); | ||||
|         if (rtl) { | ||||
|             childBox.x2 = controlsReserved; | ||||
|             childBox.x1 = childBox.x2 - controlsNatural; | ||||
|         } else { | ||||
|             childBox.x1 = totalWidth - controlsReserved; | ||||
|             childBox.x2 = childBox.x1 + controlsNatural; | ||||
|         } | ||||
|  | ||||
|         childBox.y1 = 0; | ||||
|         childBox.y2 = box.y2- box.y1; | ||||
|         this._controls.allocate(childBox, flags); | ||||
|  | ||||
|         this._updateWorkspacesGeometry(); | ||||
|     }, | ||||
|  | ||||
| @@ -851,24 +725,15 @@ const WorkspacesDisplay = new Lang.Class({ | ||||
|         let width = fullWidth; | ||||
|         let height = fullHeight; | ||||
|  | ||||
|         let [controlsMin, controlsNatural] = this._controls.get_preferred_width(height); | ||||
|         let controlsVisible = this._controls.get_theme_node().get_length('visible-width'); | ||||
|  | ||||
|         let [x, y] = this.actor.get_transformed_position(); | ||||
|  | ||||
|         let rtl = (Clutter.get_default_text_direction () == Clutter.TextDirection.RTL); | ||||
|  | ||||
|         let clipWidth = width - controlsVisible; | ||||
|         let clipWidth = width; | ||||
|         let clipHeight = fullHeight; | ||||
|         let clipX = rtl ? x + controlsVisible : x; | ||||
|         let clipX = x; | ||||
|         let clipY = y + (fullHeight - clipHeight) / 2; | ||||
|  | ||||
|         let widthAdjust = this._zoomOut ? controlsNatural : controlsVisible; | ||||
|         widthAdjust += Main.overview._spacing; | ||||
|         width -= widthAdjust; | ||||
|         if (rtl) | ||||
|             x += widthAdjust; | ||||
|  | ||||
|         let monitors = Main.layoutManager.monitors; | ||||
|         let m = 0; | ||||
|         for (let i = 0; i < monitors.length; i++) { | ||||
| @@ -891,25 +756,12 @@ const WorkspacesDisplay = new Lang.Class({ | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _onRestacked: function() { | ||||
|         let stack = global.get_window_actors(); | ||||
|         let stackIndices = {}; | ||||
|  | ||||
|         for (let i = 0; i < stack.length; i++) { | ||||
|             // Use the stable sequence for an integer to use as a hash key | ||||
|             stackIndices[stack[i].get_meta_window().get_stable_sequence()] = i; | ||||
|         } | ||||
|  | ||||
|     _onRestacked: function(actor, stackIndices) { | ||||
|         for (let i = 0; i < this._workspacesViews.length; i++) | ||||
|             this._workspacesViews[i].syncStacking(stackIndices); | ||||
|  | ||||
|         this._thumbnailsBox.syncStacking(stackIndices); | ||||
|     }, | ||||
|  | ||||
|     _workspacesChanged: function() { | ||||
|         this._updateAlwaysZoom(); | ||||
|         this._updateZoom(); | ||||
|  | ||||
|         if (this._workspacesViews == null) | ||||
|             return; | ||||
|  | ||||
| @@ -934,8 +786,6 @@ const WorkspacesDisplay = new Lang.Class({ | ||||
|                 } | ||||
|                 m++; | ||||
|             } | ||||
|  | ||||
|             this._thumbnailsBox.addThumbnails(oldNumWorkspaces, newNumWorkspaces - oldNumWorkspaces); | ||||
|         } else { | ||||
|             // Assume workspaces are only removed sequentially | ||||
|             // (e.g. 2,3,4 - not 2,4,7) | ||||
| @@ -958,8 +808,6 @@ const WorkspacesDisplay = new Lang.Class({ | ||||
|                     lostWorkspaces[l].destroy(); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             this._thumbnailsBox.removeThumbnails(removedIndex, removedNum); | ||||
|         } | ||||
|  | ||||
|         for (let i = 0; i < this._workspacesViews.length; i++) | ||||
| @@ -968,35 +816,6 @@ const WorkspacesDisplay = new Lang.Class({ | ||||
|         this._updateSwitcherVisibility(); | ||||
|     }, | ||||
|  | ||||
|     _updateZoom : function() { | ||||
|         if (Main.overview.animationInProgress) | ||||
|             return; | ||||
|  | ||||
|         let shouldZoom = this._alwaysZoomOut || this._controls.hover; | ||||
|         if (shouldZoom != this._zoomOut) { | ||||
|             this._zoomOut = shouldZoom; | ||||
|             this._updateWorkspacesGeometry(); | ||||
|  | ||||
|             if (!this._workspacesViews) | ||||
|                 return; | ||||
|  | ||||
|             Tweener.addTween(this, | ||||
|                              { zoomFraction: this._zoomOut ? 1 : 0, | ||||
|                                time: WORKSPACE_SWITCH_TIME, | ||||
|                                transition: 'easeOutQuad' }); | ||||
|  | ||||
|             for (let i = 0; i < this._workspacesViews.length; i++) | ||||
|                 this._workspacesViews[i].updateWindowPositions(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _onControlsHoverChanged: function() { | ||||
|         if(!this._controls.hover) | ||||
|             this._controlsInitiallyHovered = false; | ||||
|         if(!this._controlsInitiallyHovered) | ||||
|             this._updateZoom(); | ||||
|     }, | ||||
|  | ||||
|     _dragBegin: function() { | ||||
|         this._inDrag = true; | ||||
|         this._cancelledDrag = false; | ||||
| @@ -1012,33 +831,11 @@ const WorkspacesDisplay = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _onDragMotion: function(dragEvent) { | ||||
|         let controlsHovered = this._controls.contains(dragEvent.targetActor); | ||||
|         this._controls.set_hover(controlsHovered); | ||||
|  | ||||
|         return DND.DragMotionResult.CONTINUE; | ||||
|     }, | ||||
|  | ||||
|     _dragEnd: function() { | ||||
|         this._inDrag = false; | ||||
|  | ||||
|         // We do this deferred because drag-end is emitted before dnd.js emits | ||||
|         // event/leave events that were suppressed during the drag. If we didn't | ||||
|         // defer this, we'd zoom out then immediately zoom in because of the | ||||
|         // enter event we received. That would normally be invisible but we | ||||
|         // might as well avoid it. | ||||
|         Meta.later_add(Meta.LaterType.BEFORE_REDRAW, | ||||
|                        Lang.bind(this, this._updateZoom)); | ||||
|     }, | ||||
|  | ||||
|     _onScrollEvent: function (actor, event) { | ||||
|         switch ( event.get_scroll_direction() ) { | ||||
|         case Clutter.ScrollDirection.UP: | ||||
|             Main.wm.actionMoveWorkspace(Meta.MotionDirection.UP); | ||||
|             break; | ||||
|         case Clutter.ScrollDirection.DOWN: | ||||
|             Main.wm.actionMoveWorkspace(Meta.MotionDirection.DOWN); | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(WorkspacesDisplay.prototype); | ||||
|   | ||||
							
								
								
									
										30
									
								
								tests/interactive/center-layout.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								tests/interactive/center-layout.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- | ||||
|  | ||||
| const Clutter = imports.gi.Clutter; | ||||
| const St = imports.gi.St; | ||||
|  | ||||
| const CenterLayout = imports.ui.centerLayout; | ||||
| const UI = imports.testcommon.ui; | ||||
|  | ||||
| function test() { | ||||
|     let stage = new Clutter.Stage({ user_resizable: true }); | ||||
|     UI.init(stage); | ||||
|  | ||||
|     //////////////////////////////////////////////////////////////////////////////// | ||||
|  | ||||
|     let container = new St.Widget({ style: 'border: 2px solid black;', | ||||
|                                     layout_manager: new CenterLayout.CenterLayout() }); | ||||
|     container.add_constraint(new Clutter.BindConstraint({ coordinate: Clutter.BindCoordinate.SIZE, source: stage })); | ||||
|     stage.add_actor(container); | ||||
|  | ||||
|     let left = new Clutter.Actor({ background_color: Clutter.Color.get_static(Clutter.StaticColor.RED), width: 300 }); | ||||
|     let center = new Clutter.Actor({ background_color: Clutter.Color.get_static(Clutter.StaticColor.BLUE), width: 100 }); | ||||
|     let right = new Clutter.Actor({ background_color: Clutter.Color.get_static(Clutter.StaticColor.YELLOW), width: 200 }); | ||||
|  | ||||
|     container.add_actor(left); | ||||
|     container.add_actor(center); | ||||
|     container.add_actor(right); | ||||
|  | ||||
|     UI.main(stage); | ||||
| } | ||||
| test(); | ||||
		Reference in New Issue
	
	Block a user