Compare commits
27 Commits
citadel
...
wip/re-sea
Author | SHA1 | Date | |
---|---|---|---|
|
3124e82838 | ||
|
6c4daaaa71 | ||
|
3768e85673 | ||
|
d1c72e1e4c | ||
|
2498497cc1 | ||
|
7c3c6da368 | ||
|
09e7ab5611 | ||
|
53cff07eef | ||
|
cfed6e401d | ||
|
12b041a569 | ||
|
f2dd94776c | ||
|
c0f9c52ba6 | ||
|
2ee8b0e427 | ||
|
b250a72fcf | ||
|
cf08745f6c | ||
|
58023c81ad | ||
|
7e8968da52 | ||
|
fce1d8157e | ||
|
3b5de01ed6 | ||
|
a61da42aa7 | ||
|
81acfdbfc3 | ||
|
159b789443 | ||
|
c07b715a3a | ||
|
1bb09fd0f1 | ||
|
4441541da6 | ||
|
afee4d6925 | ||
|
b62bd0e62f |
@ -34,6 +34,7 @@ dist_theme_DATA = \
|
||||
theme/gnome-shell.css \
|
||||
theme/logged-in-indicator.svg \
|
||||
theme/message-tray-background.png \
|
||||
theme/more-results.svg \
|
||||
theme/noise-texture.png \
|
||||
theme/panel-button-border.svg \
|
||||
theme/panel-button-highlight-narrow.svg \
|
||||
|
@ -75,11 +75,13 @@
|
||||
<!--
|
||||
LaunchSearch:
|
||||
@terms: Array of search terms, which the provider should treat as logical AND.
|
||||
@timestamp: A timestamp of the user interaction that triggered this call
|
||||
|
||||
Asks the search provider to launch a full search in the application for the provided terms.
|
||||
-->
|
||||
<method name="LaunchSearch">
|
||||
<arg type="as" name="terms" direction="in" />
|
||||
<arg type="u" name="timestamp" direction="in" />
|
||||
</method>
|
||||
</interface>
|
||||
</node>
|
||||
|
@ -49,7 +49,7 @@ stage {
|
||||
.switcher-list,
|
||||
.app-well-app > .overview-icon,
|
||||
.show-apps > .overview-icon,
|
||||
.search-result-content > .overview-icon {
|
||||
.grid-search-result .overview-icon {
|
||||
font-size: 9pt;
|
||||
font-weight: bold;
|
||||
}
|
||||
@ -616,12 +616,12 @@ StScrollBar StButton#vhandle:active {
|
||||
spacing: 40px;
|
||||
}
|
||||
|
||||
.window-caption {
|
||||
spacing: 25px;
|
||||
#overview-group {
|
||||
spacing: 32px;
|
||||
}
|
||||
|
||||
.workspace-controls {
|
||||
visible-width: 32px; /* Amount visible before hovering */
|
||||
.window-caption {
|
||||
spacing: 25px;
|
||||
}
|
||||
|
||||
.workspace-thumbnails-background {
|
||||
@ -641,6 +641,7 @@ StScrollBar StButton#vhandle:active {
|
||||
|
||||
.workspace-thumbnails {
|
||||
spacing: 11px;
|
||||
visible-width: 96px;
|
||||
}
|
||||
|
||||
.workspace-thumbnail-indicator {
|
||||
@ -754,25 +755,33 @@ StScrollBar StButton#vhandle:active {
|
||||
/* Search Results */
|
||||
|
||||
#searchResults {
|
||||
padding: 20px 10px 10px 10px;
|
||||
spacing: 18px;
|
||||
}
|
||||
|
||||
#searchResultsContent {
|
||||
padding-right: 20px;
|
||||
spacing: 36px;
|
||||
}
|
||||
spacing: 16px;
|
||||
|
||||
#searchResultsContent:rtl {
|
||||
padding-right: 0px;
|
||||
/* for scrollbars */
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.search-section-header {
|
||||
padding: 4px 12px;
|
||||
spacing: 4px;
|
||||
color: #6f6f6f;
|
||||
font-size: .8em;
|
||||
.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: 32px;
|
||||
}
|
||||
|
||||
.search-statustext {
|
||||
@ -781,16 +790,8 @@ StScrollBar StButton#vhandle:active {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.search-section-results {
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.search-section-list-results {
|
||||
spacing: 4px;
|
||||
}
|
||||
|
||||
.results-container {
|
||||
spacing: 4px;
|
||||
.list-search-results {
|
||||
spacing: 3px;
|
||||
}
|
||||
|
||||
/* Text labels are an odd number of pixels tall. The uneven top and bottom
|
||||
@ -819,7 +820,7 @@ StScrollBar StButton#vhandle:active {
|
||||
-x-offset: 8px;
|
||||
}
|
||||
|
||||
/* Application Launchers and Grid */
|
||||
/* Application Launchers, Grid and List results */
|
||||
|
||||
.icon-grid {
|
||||
spacing: 36px;
|
||||
@ -832,15 +833,10 @@ StScrollBar StButton#vhandle:active {
|
||||
}
|
||||
|
||||
.all-app {
|
||||
padding: 16px 25px 16px 16px;
|
||||
padding: 16px;
|
||||
spacing: 20px;
|
||||
}
|
||||
|
||||
.all-app:rtl {
|
||||
padding-right: 16px;
|
||||
padding-left: 25px;
|
||||
}
|
||||
|
||||
.app-filter {
|
||||
font-weight: bold;
|
||||
height: 2.85em;
|
||||
@ -871,9 +867,34 @@ StScrollBar StButton#vhandle:active {
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.list-search-result-content {
|
||||
spacing: 12px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.list-search-result-title {
|
||||
font-weight: bold;
|
||||
font-size: 14pt;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.list-search-result-description {
|
||||
font-weight: bold;
|
||||
font-size: 12pt;
|
||||
color: #eeeeec;
|
||||
}
|
||||
|
||||
.search-provider-icon-more {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-image: url("more-results.svg");
|
||||
}
|
||||
|
||||
.app-well-app > .overview-icon,
|
||||
.show-apps > .overview-icon,
|
||||
.search-result-content > .overview-icon {
|
||||
.search-provider-icon,
|
||||
.list-search-result,
|
||||
.grid-search-result .overview-icon {
|
||||
border-radius: 4px;
|
||||
padding: 3px;
|
||||
border: 1px rgba(0,0,0,0);
|
||||
@ -889,7 +910,9 @@ StScrollBar StButton#vhandle:active {
|
||||
|
||||
.app-well-app:hover > .overview-icon,
|
||||
.show-apps:hover > .overview-icon,
|
||||
.search-result-content:hover > .overview-icon {
|
||||
.search-provider-icon:hover,
|
||||
.list-search-result:hover,
|
||||
.grid-search-result:hover .overview-icon {
|
||||
background-color: rgba(255,255,255,0.1);
|
||||
text-shadow: black 0px 2px 2px;
|
||||
transition-duration: 100;
|
||||
@ -924,10 +947,14 @@ StScrollBar StButton#vhandle:active {
|
||||
}
|
||||
|
||||
.app-well-app:focus > .overview-icon,
|
||||
.search-result-content:focus > .overview-icon,
|
||||
.grid-search-result:focus .overview-icon,
|
||||
.show-apps:focus > .overview-icon,
|
||||
.search-provider-icon:focus,
|
||||
.list-search-result:focus,
|
||||
.app-well-app:selected > .overview-icon,
|
||||
.search-result-content:selected > .overview-icon {
|
||||
.grid-search-result:selected .overview-icon,
|
||||
.search-provider-icon:selected,
|
||||
.list-search-result:selected {
|
||||
background-color: rgba(255,255,255,0.33);
|
||||
}
|
||||
|
||||
|
93
data/theme/more-results.svg
Normal file
93
data/theme/more-results.svg
Normal file
@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="16"
|
||||
height="16"
|
||||
id="svg12430"
|
||||
version="1.1"
|
||||
inkscape:version="0.48+devel r11908 custom"
|
||||
sodipodi:docname="more-results.svg">
|
||||
<defs
|
||||
id="defs12432" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#7a7a7a"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="1"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1"
|
||||
inkscape:cx="7.4498765"
|
||||
inkscape:cy="9.9072581"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="g14642-3-0"
|
||||
showgrid="false"
|
||||
borderlayer="true"
|
||||
inkscape:showpageshadow="false"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1376"
|
||||
inkscape:window-x="1600"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid13002" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata12435">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-1036.3622)">
|
||||
<g
|
||||
style="display:inline"
|
||||
transform="translate(-141.99984,638.37113)"
|
||||
inkscape:label="zoom-in"
|
||||
id="g14642-3-0">
|
||||
<path
|
||||
sodipodi:type="inkscape:offset"
|
||||
inkscape:radius="0"
|
||||
inkscape:original="M 145.1875 400 C 144.5248 400 144 400.54899 144 401.21875 L 144 410.78125 C 144 411.45101 144.5248 412 145.1875 412 L 154.8125 412 C 155.4752 412 156 411.45101 156 410.78125 L 156 401.21875 C 156 400.54899 155.4752 400 154.8125 400 L 145.1875 400 z M 149 403 L 151 403 L 151 405 L 153 405 L 153 407 L 151 407 L 151 409 L 149 409 L 149 407 L 147 407 L 147 405 L 149 405 L 149 403 z "
|
||||
xlink:href="#rect11749-5-0-1-8"
|
||||
style="color:#bebebe;fill:#000000;fill-opacity:0.38207546;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible"
|
||||
id="path13004"
|
||||
inkscape:href="#rect11749-5-0-1-8"
|
||||
d="M 145.1875,400 C 144.5248,400 144,400.54899 144,401.21875 l 0,9.5625 c 0,0.66976 0.5248,1.21875 1.1875,1.21875 l 9.625,0 c 0.6627,0 1.1875,-0.54899 1.1875,-1.21875 l 0,-9.5625 C 156,400.54899 155.4752,400 154.8125,400 L 145.1875,400 z m 3.8125,3 2,0 0,2 2,0 0,2 -2,0 0,2 -2,0 0,-2 -2,0 0,-2 2,0 L 149,403 Z"
|
||||
transform="translate(0,1)" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="color:#bebebe;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible"
|
||||
d="M 145.1875,400 C 144.5248,400 144,400.54899 144,401.21875 l 0,9.5625 c 0,0.66976 0.5248,1.21875 1.1875,1.21875 l 9.625,0 c 0.6627,0 1.1875,-0.54899 1.1875,-1.21875 l 0,-9.5625 C 156,400.54899 155.4752,400 154.8125,400 L 145.1875,400 z m 3.8125,3 2,0 0,2 2,0 0,2 -2,0 0,2 -2,0 0,-2 -2,0 0,-2 2,0 L 149,403 Z"
|
||||
id="rect11749-5-0-1-8" />
|
||||
<rect
|
||||
style="fill:none;stroke:none"
|
||||
id="rect3620-5-4"
|
||||
width="15.981825"
|
||||
height="16"
|
||||
x="142"
|
||||
y="398"
|
||||
rx="0"
|
||||
ry="0" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.7 KiB |
@ -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 \
|
||||
@ -62,6 +63,7 @@ nobase_dist_js_DATA = \
|
||||
ui/main.js \
|
||||
ui/messageTray.js \
|
||||
ui/modalDialog.js \
|
||||
ui/separator.js \
|
||||
ui/sessionMode.js \
|
||||
ui/shellEntry.js \
|
||||
ui/shellMountOperation.js \
|
||||
|
@ -19,7 +19,6 @@ 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 Tweener = imports.ui.tweener;
|
||||
const Workspace = imports.ui.workspace;
|
||||
const Params = imports.misc.params;
|
||||
@ -312,11 +311,10 @@ const AllAppDisplay = new Lang.Class({
|
||||
|
||||
const AppSearchProvider = new Lang.Class({
|
||||
Name: 'AppSearchProvider',
|
||||
Extends: Search.SearchProvider,
|
||||
|
||||
_init: function() {
|
||||
this.parent(_("APPLICATIONS"));
|
||||
this._appSys = Shell.AppSystem.get_default();
|
||||
this.id = 'applications';
|
||||
},
|
||||
|
||||
getResultMetas: function(apps, callback) {
|
||||
@ -369,13 +367,10 @@ const AppSearchProvider = new Lang.Class({
|
||||
|
||||
const SettingsSearchProvider = new Lang.Class({
|
||||
Name: 'SettingsSearchProvider',
|
||||
Extends: Search.SearchProvider,
|
||||
|
||||
_init: function() {
|
||||
this.parent(_("SETTINGS"));
|
||||
|
||||
this._appSys = Shell.AppSystem.get_default();
|
||||
this.appInfo = Gio.DesktopAppInfo.new('gnome-control-center.desktop');
|
||||
this._appSys = Shell.AppSystem.get_default();
|
||||
},
|
||||
|
||||
getResultMetas: function(prefs, callback) {
|
||||
@ -384,9 +379,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() { return null; }
|
||||
});
|
||||
}
|
||||
callback(metas);
|
||||
@ -404,12 +397,6 @@ const SettingsSearchProvider = new Lang.Class({
|
||||
pref.activate();
|
||||
},
|
||||
|
||||
createResultActor: function (resultMeta, terms) {
|
||||
let app = resultMeta['id'];
|
||||
let icon = new AppWellIcon(app);
|
||||
return icon.actor;
|
||||
},
|
||||
|
||||
launchSearch: function(terms) {
|
||||
// FIXME: this should be a remote search provider
|
||||
this.appInfo.launch([], global.create_app_launch_context());
|
||||
|
60
js/ui/centerLayout.js
Normal file
60
js/ui/centerLayout.js
Normal file
@ -0,0 +1,60 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Lang = imports.lang;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
|
||||
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 [leftMinWidth, leftNaturalWidth] = left.get_preferred_width(availHeight);
|
||||
let [centerMinWidth, centerNaturalWidth] = center.get_preferred_width(availHeight);
|
||||
let [rightMinWidth, rightNaturalWidth] = right.get_preferred_width(availHeight);
|
||||
|
||||
let sideWidth = (availWidth - centerMinWidth) / 2;
|
||||
|
||||
let childBox = new Clutter.ActorBox();
|
||||
childBox.y1 = box.y1;
|
||||
childBox.y2 = box.y1 + availHeight;
|
||||
|
||||
let leftSide = Math.min(Math.floor(sideWidth), leftNaturalWidth);
|
||||
let rightSide = Math.min(Math.floor(sideWidth), rightNaturalWidth);
|
||||
|
||||
if (rtl) {
|
||||
childBox.x1 = availWidth - leftSide;
|
||||
childBox.x2 = availWidth;
|
||||
} else {
|
||||
childBox.x1 = 0;
|
||||
childBox.x2 = leftSide;
|
||||
}
|
||||
childBox.x1 += box.x1;
|
||||
left.allocate(childBox, flags);
|
||||
|
||||
let maxSide = Math.max(leftSide, rightSide);
|
||||
let sideWidth = Math.max((availWidth - centerNaturalWidth) / 2, maxSide);
|
||||
|
||||
childBox.x1 = box.x1 + Math.ceil(sideWidth);
|
||||
childBox.x2 = box.x2 - Math.ceil(sideWidth);
|
||||
center.allocate(childBox, flags);
|
||||
|
||||
if (rtl) {
|
||||
childBox.x1 = 0;
|
||||
childBox.x2 = rightSide;
|
||||
} else {
|
||||
childBox.x1 = availWidth - rightSide;
|
||||
childBox.x2 = availWidth;
|
||||
}
|
||||
childBox.x1 += box.x1;
|
||||
right.allocate(childBox, flags);
|
||||
}
|
||||
});
|
@ -371,6 +371,7 @@ const Dash = new Lang.Class({
|
||||
this._maxHeight = -1;
|
||||
this.iconSize = 64;
|
||||
this._shownInitially = false;
|
||||
this._ignoreHeight = false;
|
||||
|
||||
this._dragPlaceholder = null;
|
||||
this._dragPlaceholderPos = -1;
|
||||
@ -396,7 +397,10 @@ const Dash = new Lang.Class({
|
||||
this.actor = new St.Bin({ child: this._container });
|
||||
this.actor.connect('notify::height', Lang.bind(this,
|
||||
function() {
|
||||
if (this._maxHeight != this.actor.height)
|
||||
if (this._ignoreHeight)
|
||||
return;
|
||||
|
||||
if (this._maxHeight != this.actor.height);
|
||||
this._queueRedisplay();
|
||||
this._maxHeight = this.actor.height;
|
||||
}));
|
||||
@ -421,6 +425,8 @@ const Dash = new Lang.Class({
|
||||
Lang.bind(this, this._onDragCancelled));
|
||||
Main.overview.connect('window-drag-end',
|
||||
Lang.bind(this, this._onDragEnd));
|
||||
Main.overview.connect('showing',
|
||||
Lang.bind(this, this._onOverviewShowing));
|
||||
},
|
||||
|
||||
_onDragBegin: function() {
|
||||
@ -923,6 +929,65 @@ const Dash = new Lang.Class({
|
||||
}));
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
_computeTranslation: function() {
|
||||
let rtl = (this.actor.get_text_direction() == Clutter.TextDirection.RTL);
|
||||
|
||||
if (rtl)
|
||||
return this.actor.width;
|
||||
else
|
||||
return - this.actor.width;
|
||||
},
|
||||
|
||||
_onOverviewShowing: function() {
|
||||
// reset any translation and make sure the actor is visible when
|
||||
// entering the overview
|
||||
this.slideX = 0;
|
||||
this.actor.show();
|
||||
},
|
||||
|
||||
get slideX() {
|
||||
return this._slideX;
|
||||
},
|
||||
|
||||
set slideX(value) {
|
||||
this._slideX = value;
|
||||
this.actor.translation_x = this._slideX;
|
||||
|
||||
if (this._slideX > 0) {
|
||||
let rect = new Clutter.Rect();
|
||||
rect.size.width = this.actor.width - this._slideX;
|
||||
rect.size.height = this.actor.height;
|
||||
this.actor.clip_rect = rect;
|
||||
} else {
|
||||
this.actor.clip_rect = null;
|
||||
}
|
||||
},
|
||||
|
||||
show: function() {
|
||||
this.actor.show();
|
||||
Tweener.addTween(this, { slideX: 0,
|
||||
transition: 'easeOutQuad',
|
||||
time: DASH_ANIMATION_TIME,
|
||||
onComplete: Lang.bind(this,
|
||||
function() {
|
||||
this._ignoreHeight = false;
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
hide: function() {
|
||||
this._ignoreHeight = true;
|
||||
let hiddenX = this._computeTranslation();
|
||||
Tweener.addTween(this, { slideX: hiddenX,
|
||||
transition: 'easeOutQuad',
|
||||
time: DASH_ANIMATION_TIME,
|
||||
onComplete: Lang.bind(this,
|
||||
function() {
|
||||
this.actor.hide();
|
||||
})
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -234,7 +234,7 @@ const _Draggable = new Lang.Class({
|
||||
this._dragY = this._dragStartY = stageY;
|
||||
|
||||
if (this.actor._delegate && this.actor._delegate.getDragActor) {
|
||||
this._dragActor = this.actor._delegate.getDragActor(this._dragStartX, this._dragStartY);
|
||||
this._dragActor = this.actor._delegate.getDragActor();
|
||||
this._dragActor.reparent(Main.uiGroup);
|
||||
this._dragActor.raise_top();
|
||||
Shell.util_set_hidden_from_pick(this._dragActor, true);
|
||||
|
@ -1507,7 +1507,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() {
|
||||
@ -1790,6 +1790,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) {
|
||||
@ -2036,14 +2041,14 @@ 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;
|
||||
|
||||
let notificationsVisible = this._notificationState != State.HIDDEN;
|
||||
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 GLSL_DIM_EFFECT_DECLARATIONS = '';
|
||||
@ -100,6 +98,13 @@ const ShellInfo = new Lang.Class({
|
||||
}
|
||||
});
|
||||
|
||||
const ControlsChange = {
|
||||
BEFORE_PAGE: 1,
|
||||
AFTER_PAGE: 2,
|
||||
DND_START: 3,
|
||||
DND_END: 4
|
||||
};
|
||||
|
||||
const Overview = new Lang.Class({
|
||||
Name: 'Overview',
|
||||
|
||||
@ -136,21 +141,25 @@ 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._overview = new St.BoxLayout({ name: 'overview',
|
||||
accessible_name: _("Overview"),
|
||||
reactive: true,
|
||||
vertical: true });
|
||||
this._overview._delegate = this;
|
||||
|
||||
let layout = new CenterLayout.CenterLayout();
|
||||
this._group = new St.Widget({ name: 'overview-group',
|
||||
layout_manager: layout });
|
||||
|
||||
this._spacing = 0;
|
||||
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();
|
||||
}
|
||||
}));
|
||||
|
||||
@ -169,12 +178,11 @@ const Overview = new Lang.Class({
|
||||
// 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();
|
||||
|
||||
@ -186,6 +194,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;
|
||||
@ -213,6 +223,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
|
||||
@ -221,16 +238,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() {
|
||||
@ -241,10 +255,99 @@ 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);
|
||||
|
||||
Main.ctrlAltTabManager.addGroup(this._thumbnailsBox.actor, _("Workspaces"), 'view-list-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,
|
||||
x_fill: true,
|
||||
y_fill: true });
|
||||
this._overview.add_actor(this._messageTrayGhost);
|
||||
|
||||
this._viewSelector.connect('after-page-change', Lang.bind(this,
|
||||
function() {
|
||||
this._setSideControlsVisibility(ControlsChange.AFTER_PAGE);
|
||||
}));
|
||||
this._viewSelector.connect('before-page-change', Lang.bind(this,
|
||||
function() {
|
||||
this._setSideControlsVisibility(ControlsChange.BEFORE_PAGE);
|
||||
}));
|
||||
|
||||
this.connect('item-drag-begin', Lang.bind(this,
|
||||
function() {
|
||||
this._setSideControlsVisibility(ControlsChange.DND_START);
|
||||
}));
|
||||
this.connect('item-drag-cancelled', Lang.bind(this,
|
||||
function() {
|
||||
this._setSideControlsVisibility(ControlsChange.DND_END);
|
||||
}));
|
||||
this.connect('item-drag-end', Lang.bind(this,
|
||||
function() {
|
||||
this._setSideControlsVisibility(ControlsChange.DND_END);
|
||||
}));
|
||||
|
||||
Main.layoutManager.connect('monitors-changed', Lang.bind(this, this._relayout));
|
||||
this._relayout();
|
||||
},
|
||||
|
||||
_setSideControlsVisibility: function(changeType) {
|
||||
// Ignore the case when we're leaving the overview, since
|
||||
// actors will be made visible again when entering the overview
|
||||
// next time, and animating them while doing so is just
|
||||
// unnecesary noise
|
||||
if (!this.visible || this.animationInProgress)
|
||||
return;
|
||||
|
||||
let appsActive = this._viewSelector.getAppsActive();
|
||||
let searchActive = this._viewSelector.getSearchActive();
|
||||
let dashVisible = !searchActive || (changeType == ControlsChange.DND_START);
|
||||
let thumbnailsVisible = (!searchActive && !appsActive) || (changeType == ControlsChange.DND_START);
|
||||
let trayVisible = !searchActive;
|
||||
let trayGhostVisible = trayVisible || dashVisible;
|
||||
|
||||
if ((changeType == ControlsChange.BEFORE_PAGE) ||
|
||||
(changeType == ControlsChange.DND_START)) {
|
||||
if (dashVisible)
|
||||
this._dash.show();
|
||||
if (thumbnailsVisible)
|
||||
this._thumbnailsBox.show();
|
||||
}
|
||||
|
||||
if ((changeType == ControlsChange.BEFORE_PAGE) ||
|
||||
(changeType == ControlsChange.DND_END)) {
|
||||
if (!dashVisible) {
|
||||
this._dash.hide();
|
||||
}
|
||||
if (!thumbnailsVisible)
|
||||
this._thumbnailsBox.hide();
|
||||
}
|
||||
|
||||
if (changeType == ControlsChange.BEFORE_PAGE ||
|
||||
changeType == ControlsChange.DND_START) {
|
||||
if (trayGhostVisible)
|
||||
this._messageTrayGhost.show();
|
||||
if (trayVisible)
|
||||
Main.messageTray.show();
|
||||
else
|
||||
Main.messageTray.hide();
|
||||
} else if (changeType == ControlsChange.AFTER_PAGE ||
|
||||
changeType == ControlsChange.DND_END) {
|
||||
if (!trayGhostVisible)
|
||||
this._messageTrayGhost.hide();
|
||||
}
|
||||
},
|
||||
|
||||
addSearchProvider: function(provider) {
|
||||
this._viewSelector.addSearchProvider(provider);
|
||||
},
|
||||
@ -337,7 +440,7 @@ const Overview = new Lang.Class({
|
||||
if (this.isDummy)
|
||||
return;
|
||||
|
||||
this._group.add_action(action);
|
||||
this._overview.add_action(action);
|
||||
},
|
||||
|
||||
_getDesktopClone: function() {
|
||||
@ -363,41 +466,27 @@ 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 ////
|
||||
@ -481,12 +570,13 @@ 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._messageTrayGhost.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,
|
||||
@ -578,7 +668,7 @@ const Overview = new Lang.Class({
|
||||
|
||||
if (this._shown) {
|
||||
if (!this._modal) {
|
||||
if (Main.pushModal(this._group,
|
||||
if (Main.pushModal(this._overview,
|
||||
{ keybindingMode: Main.KeybindingMode.OVERVIEW }))
|
||||
this._modal = true;
|
||||
else
|
||||
@ -586,13 +676,13 @@ const Overview = new Lang.Class({
|
||||
}
|
||||
} 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)
|
||||
@ -610,7 +700,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,
|
||||
@ -652,7 +742,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;
|
||||
@ -937,12 +938,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._sessionStyle = null;
|
||||
@ -962,7 +998,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)
|
||||
@ -971,9 +1006,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);
|
||||
@ -984,83 +1016,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;
|
||||
|
@ -1,6 +1,5 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Cairo = imports.cairo;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
@ -15,6 +14,7 @@ const BoxPointer = imports.ui.boxpointer;
|
||||
const GrabHelper = imports.ui.grabHelper;
|
||||
const Main = imports.ui.main;
|
||||
const Params = imports.misc.params;
|
||||
const Separator = imports.ui.separator;
|
||||
const Tweener = imports.ui.tweener;
|
||||
|
||||
const SLIDER_SCROLL_STEP = 0.05; /* Slider scrolling step in % */
|
||||
@ -405,29 +405,8 @@ 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));
|
||||
},
|
||||
|
||||
_onRepaint: function(area) {
|
||||
let cr = area.get_context();
|
||||
let themeNode = area.get_theme_node();
|
||||
let [width, height] = area.get_surface_size();
|
||||
let margin = themeNode.get_length('-margin-horizontal');
|
||||
let gradientHeight = themeNode.get_length('-gradient-height');
|
||||
let startColor = themeNode.get_color('-gradient-start');
|
||||
let endColor = themeNode.get_color('-gradient-end');
|
||||
|
||||
let gradientWidth = (width - margin * 2);
|
||||
let gradientOffset = (height - gradientHeight) / 2;
|
||||
let pattern = new Cairo.LinearGradient(margin, gradientOffset, width - margin, gradientOffset + gradientHeight);
|
||||
pattern.addColorStopRGBA(0, startColor.red / 255, startColor.green / 255, startColor.blue / 255, startColor.alpha / 255);
|
||||
pattern.addColorStopRGBA(0.5, endColor.red / 255, endColor.green / 255, endColor.blue / 255, endColor.alpha / 255);
|
||||
pattern.addColorStopRGBA(1, startColor.red / 255, startColor.green / 255, startColor.blue / 255, startColor.alpha / 255);
|
||||
cr.setSource(pattern);
|
||||
cr.rectangle(margin, gradientOffset, gradientWidth, gradientHeight);
|
||||
cr.fill();
|
||||
this._separator = new Separator.HorizontalSeparator({ style_class: 'popup-separator-menu-item' });
|
||||
this.addActor(this._separator.actor, { span: -1, expand: true });
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -50,6 +50,7 @@ const SearchProvider2Iface = <interface name="org.gnome.Shell.SearchProvider2">
|
||||
</method>
|
||||
<method name="LaunchSearch">
|
||||
<arg type="as" direction="in" />
|
||||
<arg type="u" direction="in" />
|
||||
</method>
|
||||
</interface>;
|
||||
|
||||
@ -166,7 +167,6 @@ function remoteProvidersLoaded(loadState) {
|
||||
|
||||
const RemoteSearchProvider = new Lang.Class({
|
||||
Name: 'RemoteSearchProvider',
|
||||
Extends: Search.SearchProvider,
|
||||
|
||||
_init: function(appInfo, dbusName, dbusPath, proxyType) {
|
||||
if (!proxyType)
|
||||
@ -175,7 +175,10 @@ const RemoteSearchProvider = new Lang.Class({
|
||||
this.proxy = new proxyType(Gio.DBus.session,
|
||||
dbusName, dbusPath, Lang.bind(this, this._onProxyConstructed));
|
||||
|
||||
this.parent(appInfo.get_name().toUpperCase(), appInfo, true);
|
||||
this.appInfo = appInfo;
|
||||
this.id = appInfo.get_id();
|
||||
this.isRemoteProvider = true;
|
||||
|
||||
this._cancellable = new Gio.Cancellable();
|
||||
},
|
||||
|
||||
@ -195,9 +198,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) {
|
||||
@ -290,6 +291,6 @@ const RemoteSearchProvider2 = new Lang.Class({
|
||||
},
|
||||
|
||||
launchSearch: function(terms) {
|
||||
this.proxy.LaunchSearchRemote(terms);
|
||||
this.proxy.LaunchSearchRemote(terms, global.get_current_time());
|
||||
}
|
||||
});
|
||||
|
181
js/ui/search.js
181
js/ui/search.js
@ -1,187 +1,10 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Lang = imports.lang;
|
||||
const Signals = imports.signals;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Util = imports.misc.util;
|
||||
|
||||
const FileUtils = imports.misc.fileUtils;
|
||||
const Main = imports.ui.main;
|
||||
|
||||
const SEARCH_PROVIDERS_SCHEMA = 'org.gnome.desktop.search-providers';
|
||||
|
||||
// Not currently referenced by the search API, but
|
||||
// this enumeration can be useful for provider
|
||||
// implementations.
|
||||
const MatchType = {
|
||||
NONE: 0,
|
||||
SUBSTRING: 1,
|
||||
PREFIX: 2
|
||||
};
|
||||
|
||||
const SearchResultDisplay = new Lang.Class({
|
||||
Name: 'SearchResultDisplay',
|
||||
|
||||
_init: function(provider) {
|
||||
this.provider = provider;
|
||||
this.actor = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* renderResults:
|
||||
* @results: List of identifier strings
|
||||
* @terms: List of search term strings
|
||||
*
|
||||
* Display the given search matches which resulted
|
||||
* from the given terms. It's expected that not
|
||||
* all results will fit in the space for the container
|
||||
* actor; in this case, show as many as makes sense
|
||||
* for your result type.
|
||||
*
|
||||
* The terms are useful for search match highlighting.
|
||||
*/
|
||||
renderResults: function(results, terms) {
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
|
||||
/**
|
||||
* clear:
|
||||
* Remove all results from this display.
|
||||
*/
|
||||
clear: function() {
|
||||
this.actor.destroy_all_children();
|
||||
},
|
||||
|
||||
/**
|
||||
* getVisibleResultCount:
|
||||
*
|
||||
* Returns: The number of actors visible.
|
||||
*/
|
||||
getVisibleResultCount: function() {
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* SearchProvider:
|
||||
*
|
||||
* Subclass this object to add a new result type
|
||||
* to the search system, then call registerProvider()
|
||||
* in SearchSystem with an instance.
|
||||
* Search is asynchronous and uses the
|
||||
* getInitialResultSet()/getSubsearchResultSet() methods.
|
||||
*/
|
||||
const SearchProvider = new Lang.Class({
|
||||
Name: 'SearchProvider',
|
||||
|
||||
_init: function(title, appInfo, isRemoteProvider) {
|
||||
this.title = title;
|
||||
this.appInfo = appInfo;
|
||||
this.searchSystem = null;
|
||||
this.isRemoteProvider = !!isRemoteProvider;
|
||||
this.canLaunchSearch = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* getInitialResultSet:
|
||||
* @terms: Array of search terms, treated as logical AND
|
||||
*
|
||||
* Called when the user first begins a search (most likely
|
||||
* therefore a single term of length one or two), or when
|
||||
* a new term is added.
|
||||
*
|
||||
* Should "return" an array of result identifier strings representing
|
||||
* items which match the given search terms. This
|
||||
* is expected to be a substring match on the metadata for a given
|
||||
* item. Ordering of returned results is up to the discretion of the provider,
|
||||
* but you should follow these heruistics:
|
||||
*
|
||||
* * Put items where the term matches multiple criteria (e.g. name and
|
||||
* description) before single matches
|
||||
* * Put items which match on a prefix before non-prefix substring matches
|
||||
*
|
||||
* We say "return" above, but in order to make the query asynchronous, use
|
||||
* this.searchSystem.pushResults();. The return value should be ignored.
|
||||
*
|
||||
* This function should be fast; do not perform unindexed full-text searches
|
||||
* or network queries.
|
||||
*/
|
||||
getInitialResultSet: function(terms) {
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
|
||||
/**
|
||||
* getSubsearchResultSet:
|
||||
* @previousResults: Array of item identifiers
|
||||
* @newTerms: Updated search terms
|
||||
*
|
||||
* Called when a search is performed which is a "subsearch" of
|
||||
* the previous search; i.e. when every search term has exactly
|
||||
* one corresponding term in the previous search which is a prefix
|
||||
* of the new term.
|
||||
*
|
||||
* This allows search providers to only search through the previous
|
||||
* result set, rather than possibly performing a full re-query.
|
||||
*
|
||||
* Similar to getInitialResultSet, the return value for this will
|
||||
* be ignored; use this.searchSystem.pushResults();.
|
||||
*/
|
||||
getSubsearchResultSet: function(previousResults, newTerms) {
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
|
||||
/**
|
||||
* getResultMetas:
|
||||
* @ids: Result identifier strings
|
||||
*
|
||||
* Call callback with array of objects with 'id', 'name', (both strings) and
|
||||
* 'createIcon' (function(size) returning a Clutter.Texture) properties
|
||||
* with the same number of members as @ids
|
||||
*/
|
||||
getResultMetas: function(ids, callback) {
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
|
||||
/**
|
||||
* createResultActor:
|
||||
* @resultMeta: Object with result metadata
|
||||
* @terms: Array of search terms, should be used for highlighting
|
||||
*
|
||||
* Search providers may optionally override this to render a
|
||||
* particular serch result in a custom fashion. The default
|
||||
* implementation will show the icon next to the name.
|
||||
*
|
||||
* The actor should be an instance of St.Widget, with the style class
|
||||
* 'search-result-content'.
|
||||
*/
|
||||
createResultActor: function(resultMeta, terms) {
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* activateResult:
|
||||
* @id: Result identifier string
|
||||
*
|
||||
* Called when the user chooses a given result.
|
||||
*/
|
||||
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);
|
||||
|
||||
const SearchSystem = new Lang.Class({
|
||||
Name: 'SearchSystem',
|
||||
|
||||
@ -273,7 +96,7 @@ const SearchSystem = new Lang.Class({
|
||||
results.push([provider, []]);
|
||||
provider.getSubsearchResultSet(previousResults, terms);
|
||||
} catch (error) {
|
||||
log('A ' + error.name + ' has occured in ' + provider.title + ': ' + error.message);
|
||||
log('A ' + error.name + ' has occured in ' + provider.id + ': ' + error.message);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -283,7 +106,7 @@ const SearchSystem = new Lang.Class({
|
||||
results.push([provider, []]);
|
||||
provider.getInitialResultSet(terms);
|
||||
} catch (error) {
|
||||
log('A ' + error.name + ' has occured in ' + provider.title + ': ' + error.message);
|
||||
log('A ' + error.name + ' has occured in ' + provider.id + ': ' + error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,10 +11,11 @@ const DND = imports.ui.dnd;
|
||||
const IconGrid = imports.ui.iconGrid;
|
||||
const Main = imports.ui.main;
|
||||
const Overview = imports.ui.overview;
|
||||
const Separator = imports.ui.separator;
|
||||
const Search = imports.ui.search;
|
||||
|
||||
const MAX_SEARCH_RESULTS_ROWS = 1;
|
||||
|
||||
const MAX_LIST_SEARCH_RESULTS_ROWS = 3;
|
||||
const MAX_GRID_SEARCH_RESULTS_ROWS = 1;
|
||||
|
||||
const SearchResult = new Lang.Class({
|
||||
Name: 'SearchResult',
|
||||
@ -23,33 +24,102 @@ const SearchResult = new Lang.Class({
|
||||
this.provider = provider;
|
||||
this.metaInfo = metaInfo;
|
||||
this.terms = terms;
|
||||
this.actor = new St.Button({ style_class: 'search-result',
|
||||
reactive: true,
|
||||
|
||||
this.actor = new St.Button({ reactive: true,
|
||||
can_focus: true,
|
||||
track_hover: true,
|
||||
x_align: St.Align.START,
|
||||
y_fill: true });
|
||||
|
||||
this.actor._delegate = this;
|
||||
this._dragActorSource = null;
|
||||
this.actor.connect('clicked', Lang.bind(this, this.activate));
|
||||
},
|
||||
|
||||
activate: function() {
|
||||
this.provider.activateResult(this.metaInfo.id, this.terms);
|
||||
Main.overview.toggle();
|
||||
},
|
||||
|
||||
setSelected: function(selected) {
|
||||
if (selected)
|
||||
this.actor.add_style_pseudo_class('selected');
|
||||
else
|
||||
this.actor.remove_style_pseudo_class('selected');
|
||||
}
|
||||
});
|
||||
|
||||
const ListSearchResult = new Lang.Class({
|
||||
Name: 'ListSearchResult',
|
||||
Extends: SearchResult,
|
||||
|
||||
ICON_SIZE: 64,
|
||||
|
||||
_init: function(provider, metaInfo, terms) {
|
||||
this.parent(provider, metaInfo, terms);
|
||||
|
||||
this.actor.style_class = 'list-search-result';
|
||||
this.actor.x_fill = true;
|
||||
|
||||
let content = new St.BoxLayout({ style_class: 'list-search-result-content',
|
||||
vertical: false });
|
||||
this.actor.set_child(content);
|
||||
|
||||
// An icon for, or thumbnail of, content
|
||||
let icon = this.metaInfo['createIcon'](this.ICON_SIZE);
|
||||
if (icon) {
|
||||
content.add(icon);
|
||||
}
|
||||
|
||||
let details = new St.BoxLayout({ 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: 'list-search-result-title',
|
||||
text: this.metaInfo['name'] })
|
||||
details.add(title, { x_fill: false,
|
||||
y_fill: false,
|
||||
x_align: St.Align.START,
|
||||
y_align: St.Align.START });
|
||||
|
||||
// TODO: should highlight terms in the description here
|
||||
if (this.metaInfo['description']) {
|
||||
let description = new St.Label({ style_class: 'list-search-result-description',
|
||||
text: '"' + this.metaInfo['description'] + '"' });
|
||||
details.add(description, { x_fill: false,
|
||||
y_fill: false,
|
||||
x_align: St.Align.START,
|
||||
y_align: St.Align.END });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const GridSearchResult = new Lang.Class({
|
||||
Name: 'GridSearchResult',
|
||||
Extends: SearchResult,
|
||||
|
||||
_init: function(provider, metaInfo, terms) {
|
||||
this.parent(provider, metaInfo, terms);
|
||||
|
||||
this.actor.style_class = 'grid-search-result';
|
||||
|
||||
let content = provider.createResultActor(metaInfo, terms);
|
||||
let dragSource = null;
|
||||
|
||||
if (content == null) {
|
||||
content = new St.Bin({ style_class: 'search-result-content',
|
||||
reactive: true,
|
||||
can_focus: true,
|
||||
track_hover: true,
|
||||
accessible_role: Atk.Role.PUSH_BUTTON });
|
||||
content = new St.Bin();
|
||||
let icon = new IconGrid.BaseIcon(this.metaInfo['name'],
|
||||
{ createIcon: this.metaInfo['createIcon'] });
|
||||
content.set_child(icon.actor);
|
||||
this._dragActorSource = icon.icon;
|
||||
content.label_actor = icon.label;
|
||||
dragSource = icon.icon;
|
||||
} else {
|
||||
if (content._delegate && content._delegate.getDragActorSource)
|
||||
this._dragActorSource = content._delegate.getDragActorSource();
|
||||
dragSource = content._delegate.getDragActorSource();
|
||||
}
|
||||
this._content = content;
|
||||
this.actor.set_child(content);
|
||||
|
||||
this.actor.connect('clicked', Lang.bind(this, this._onResultClicked));
|
||||
this.actor.set_child(content);
|
||||
|
||||
let draggable = DND.makeDraggable(this.actor);
|
||||
draggable.connect('drag-begin',
|
||||
@ -64,32 +134,18 @@ const SearchResult = new Lang.Class({
|
||||
Lang.bind(this, function() {
|
||||
Main.overview.endItemDrag(this);
|
||||
}));
|
||||
},
|
||||
|
||||
setSelected: function(selected) {
|
||||
if (selected)
|
||||
this._content.add_style_pseudo_class('selected');
|
||||
else
|
||||
this._content.remove_style_pseudo_class('selected');
|
||||
},
|
||||
|
||||
activate: function() {
|
||||
this.provider.activateResult(this.metaInfo.id, this.terms);
|
||||
Main.overview.toggle();
|
||||
},
|
||||
|
||||
_onResultClicked: function(actor) {
|
||||
this.activate();
|
||||
if (!dragSource)
|
||||
// not exactly right, but alignment problems are hard to notice
|
||||
dragSource = content;
|
||||
this._dragActorSource = dragSource;
|
||||
},
|
||||
|
||||
getDragActorSource: function() {
|
||||
if (this._dragActorSource)
|
||||
return this._dragActorSource;
|
||||
// not exactly right, but alignment problems are hard to notice
|
||||
return this._content;
|
||||
return this._dragActorSource;
|
||||
},
|
||||
|
||||
getDragActor: function(stageX, stageY) {
|
||||
getDragActor: function() {
|
||||
return this.metaInfo['createIcon'](Main.overview.dashIconSize);
|
||||
},
|
||||
|
||||
@ -101,47 +157,48 @@ const SearchResult = new Lang.Class({
|
||||
}
|
||||
});
|
||||
|
||||
const ListSearchResults = new Lang.Class({
|
||||
Name: 'ListSearchResults',
|
||||
|
||||
const GridSearchResults = new Lang.Class({
|
||||
Name: 'GridSearchResults',
|
||||
Extends: Search.SearchResultDisplay,
|
||||
_init: function(provider) {
|
||||
this.provider = provider;
|
||||
|
||||
_init: function(provider, grid) {
|
||||
this.parent(provider);
|
||||
|
||||
this._grid = grid || new IconGrid.IconGrid({ rowLimit: MAX_SEARCH_RESULTS_ROWS,
|
||||
xAlign: St.Align.START });
|
||||
this.actor = new St.Bin({ x_align: St.Align.START });
|
||||
|
||||
this.actor.set_child(this._grid.actor);
|
||||
this._width = 0;
|
||||
this.actor.connect('notify::width', Lang.bind(this, function() {
|
||||
this._width = this.actor.width;
|
||||
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.actor = new St.BoxLayout({ style_class: 'search-section-content' });
|
||||
this.providerIcon = new ProviderIcon(provider);
|
||||
this.providerIcon.connect('clicked', Lang.bind(this,
|
||||
function() {
|
||||
provider.launchSearch(this._terms);
|
||||
Main.overview.toggle();
|
||||
}));
|
||||
}));
|
||||
|
||||
this.actor.add(this.providerIcon, { x_fill: false,
|
||||
y_fill: false,
|
||||
x_align: St.Align.START,
|
||||
y_align: St.Align.START });
|
||||
|
||||
this._content = new St.BoxLayout({ style_class: 'list-search-results',
|
||||
vertical: true });
|
||||
this.actor.add_actor(this._content);
|
||||
|
||||
this._notDisplayedResult = [];
|
||||
this._terms = [];
|
||||
this._pendingClear = false;
|
||||
},
|
||||
|
||||
getResultsForDisplay: function() {
|
||||
let alreadyVisible = this._pendingClear ? 0 : this._grid.visibleItemsCount();
|
||||
let canDisplay = this._grid.childrenInRow(this._width) * this._grid.getRowLimit()
|
||||
- alreadyVisible;
|
||||
let alreadyVisible = this._pendingClear ? 0 : this.getVisibleResultCount();
|
||||
let canDisplay = MAX_LIST_SEARCH_RESULTS_ROWS - alreadyVisible;
|
||||
|
||||
let numResults = Math.min(this._notDisplayedResult.length, canDisplay);
|
||||
|
||||
return this._notDisplayedResult.splice(0, numResults);
|
||||
let newResults = this._notDisplayedResult.splice(0, canDisplay);
|
||||
return newResults;
|
||||
},
|
||||
|
||||
getVisibleResultCount: function() {
|
||||
return this._grid.visibleItemsCount();
|
||||
return this._content.get_n_children();
|
||||
},
|
||||
|
||||
hasMoreResults: function() {
|
||||
return this._notDisplayedResult.length > 0;
|
||||
},
|
||||
|
||||
setResults: function(results, terms) {
|
||||
@ -153,7 +210,68 @@ 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 ListSearchResult(this.provider, metas[i], this._terms);
|
||||
this._content.add_actor(display.actor);
|
||||
}
|
||||
},
|
||||
|
||||
clear: function () {
|
||||
this._content.destroy_all_children();
|
||||
this._pendingClear = false;
|
||||
},
|
||||
|
||||
getFirstResult: function() {
|
||||
if (this.getVisibleResultCount() > 0)
|
||||
return this._content.get_child_at_index(0)._delegate;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
const GridSearchResults = new Lang.Class({
|
||||
Name: 'GridSearchResults',
|
||||
|
||||
_init: function(provider) {
|
||||
this.provider = provider;
|
||||
|
||||
this._grid = new IconGrid.IconGrid({ rowLimit: MAX_GRID_SEARCH_RESULTS_ROWS,
|
||||
xAlign: St.Align.START });
|
||||
this.actor = new St.Bin({ x_align: St.Align.MIDDLE });
|
||||
|
||||
this.actor.set_child(this._grid.actor);
|
||||
|
||||
this._notDisplayedResult = [];
|
||||
this._terms = [];
|
||||
this._pendingClear = false;
|
||||
},
|
||||
|
||||
getResultsForDisplay: function() {
|
||||
let alreadyVisible = this._pendingClear ? 0 : this._grid.visibleItemsCount();
|
||||
let canDisplay = this._grid.childrenInRow(this.actor.width) * this._grid.getRowLimit()
|
||||
- alreadyVisible;
|
||||
|
||||
let newResults = this._notDisplayedResult.splice(0, canDisplay);
|
||||
return newResults;
|
||||
},
|
||||
|
||||
getVisibleResultCount: function() {
|
||||
return this._grid.visibleItemsCount();
|
||||
},
|
||||
|
||||
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 GridSearchResult(this.provider, metas[i], this._terms);
|
||||
this._grid.addItem(display.actor);
|
||||
}
|
||||
},
|
||||
@ -224,19 +342,27 @@ 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 providerIcon = null;
|
||||
let resultDisplay = null;
|
||||
|
||||
let resultDisplayBin = new St.Bin({ style_class: 'search-section-results',
|
||||
if (provider.appInfo) {
|
||||
resultDisplay = new ListSearchResults(provider);
|
||||
providerIcon = resultDisplay.providerIcon;
|
||||
} else {
|
||||
resultDisplay = new GridSearchResults(provider);
|
||||
}
|
||||
|
||||
let resultDisplayBin = new St.Bin({ child: resultDisplay.actor,
|
||||
x_fill: true,
|
||||
y_fill: true });
|
||||
providerBox.add(resultDisplayBin, { expand: true });
|
||||
let resultDisplay = new GridSearchResults(provider);
|
||||
resultDisplayBin.set_child(resultDisplay.actor);
|
||||
|
||||
let separator = new Separator.HorizontalSeparator({ style_class: 'search-section-separator' });
|
||||
providerBox.add(separator.actor);
|
||||
|
||||
this._providerMeta.push({ provider: provider,
|
||||
actor: providerBox,
|
||||
icon: providerIcon,
|
||||
resultDisplay: resultDisplay });
|
||||
this._content.add(providerBox);
|
||||
},
|
||||
@ -343,6 +469,11 @@ 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.canLaunchSearch;
|
||||
|
||||
provider.getResultMetas(results, Lang.bind(this, function(metas) {
|
||||
this._clearDisplayForProvider(provider);
|
||||
meta.actor.show();
|
||||
@ -388,10 +519,37 @@ const SearchResults = new Lang.Class({
|
||||
|
||||
let from = this._defaultResult ? this._defaultResult.actor : null;
|
||||
this.actor.navigate_focus(from, direction, false);
|
||||
if (this._defaultResult) {
|
||||
// The default result appears focused, so navigate directly to the
|
||||
// next result.
|
||||
this.actor.navigate_focus(global.stage.key_focus, direction, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const ProviderIcon = new Lang.Class({
|
||||
Name: 'ProviderIcon',
|
||||
Extends: St.Button,
|
||||
|
||||
PROVIDER_ICON_SIZE: 48,
|
||||
|
||||
_init: function(provider) {
|
||||
this.provider = provider;
|
||||
this.parent({ style_class: 'search-provider-icon',
|
||||
reactive: true,
|
||||
can_focus: true,
|
||||
track_hover: true });
|
||||
|
||||
this._content = new St.Widget({ layout_manager: new Clutter.BinLayout() });
|
||||
this.set_child(this._content);
|
||||
|
||||
let rtl = (this.get_text_direction() == Clutter.TextDirection.RTL);
|
||||
|
||||
this.moreIcon = new St.Widget({ style_class: 'search-provider-icon-more',
|
||||
visible: false,
|
||||
x_align: rtl ? Clutter.ActorAlign.START : Clutter.ActorAlign.END,
|
||||
y_align: Clutter.ActorAlign.END,
|
||||
x_expand: true,
|
||||
y_expand: true });
|
||||
|
||||
let icon = new St.Icon({ icon_size: this.PROVIDER_ICON_SIZE,
|
||||
gicon: provider.appInfo.get_icon() });
|
||||
this._content.add_actor(icon);
|
||||
this._content.add_actor(this.moreIcon);
|
||||
}
|
||||
});
|
||||
|
34
js/ui/separator.js
Normal file
34
js/ui/separator.js
Normal file
@ -0,0 +1,34 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Cairo = imports.cairo;
|
||||
const Lang = imports.lang;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const HorizontalSeparator = new Lang.Class({
|
||||
Name: 'HorizontalSeparator',
|
||||
|
||||
_init: function (params) {
|
||||
this.actor = new St.DrawingArea(params);
|
||||
this.actor.connect('repaint', Lang.bind(this, this._onRepaint));
|
||||
},
|
||||
|
||||
_onRepaint: function(area) {
|
||||
let cr = area.get_context();
|
||||
let themeNode = area.get_theme_node();
|
||||
let [width, height] = area.get_surface_size();
|
||||
let margin = themeNode.get_length('-margin-horizontal');
|
||||
let gradientHeight = themeNode.get_length('-gradient-height');
|
||||
let startColor = themeNode.get_color('-gradient-start');
|
||||
let endColor = themeNode.get_color('-gradient-end');
|
||||
|
||||
let gradientWidth = (width - margin * 2);
|
||||
let gradientOffset = (height - gradientHeight) / 2;
|
||||
let pattern = new Cairo.LinearGradient(margin, gradientOffset, width - margin, gradientOffset + gradientHeight);
|
||||
pattern.addColorStopRGBA(0, startColor.red / 255, startColor.green / 255, startColor.blue / 255, startColor.alpha / 255);
|
||||
pattern.addColorStopRGBA(0.5, endColor.red / 255, endColor.green / 255, endColor.blue / 255, endColor.alpha / 255);
|
||||
pattern.addColorStopRGBA(1, startColor.red / 255, startColor.green / 255, startColor.blue / 255, startColor.alpha / 255);
|
||||
cr.setSource(pattern);
|
||||
cr.rectangle(margin, gradientOffset, gradientWidth, gradientHeight);
|
||||
cr.fill();
|
||||
}
|
||||
});
|
@ -52,8 +52,7 @@ const ViewSelector = new Lang.Class({
|
||||
|
||||
this._activePage = null;
|
||||
|
||||
this.active = false;
|
||||
this._searchPending = false;
|
||||
this._searchActive = false;
|
||||
this._searchTimeoutId = 0;
|
||||
|
||||
this._searchSystem = new Search.SearchSystem();
|
||||
@ -119,9 +118,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 () {
|
||||
@ -138,17 +134,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 });
|
||||
|
||||
Main.wm.addKeybinding('toggle-application-view',
|
||||
new Gio.Settings({ schema: SHELL_KEYBINDINGS_SCHEMA }),
|
||||
Meta.KeyBindingFlags.NONE,
|
||||
@ -223,11 +208,16 @@ const ViewSelector = new Lang.Class({
|
||||
});
|
||||
}
|
||||
|
||||
this.emit('before-page-change');
|
||||
page.show();
|
||||
Tweener.addTween(page,
|
||||
{ opacity: 255,
|
||||
time: 0.1,
|
||||
transition: 'easeOutQuad'
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: Lang.bind(this,
|
||||
function() {
|
||||
this.emit('after-page-change');
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
@ -237,7 +227,7 @@ const ViewSelector = new Lang.Class({
|
||||
},
|
||||
|
||||
_onShowAppsButtonToggled: function() {
|
||||
if (this.active)
|
||||
if (this._searchActive)
|
||||
this.reset();
|
||||
else
|
||||
this._showPage(this._showAppsButton.checked ? this._appsPage
|
||||
@ -258,7 +248,7 @@ const ViewSelector = new Lang.Class({
|
||||
let symbol = event.get_key_symbol();
|
||||
|
||||
if (symbol == Clutter.Escape) {
|
||||
if (this.active)
|
||||
if (this._searchActive)
|
||||
this.reset();
|
||||
else if (this._showAppsButton.checked)
|
||||
this._resetShowAppsButton();
|
||||
@ -266,9 +256,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._searchActive)) {
|
||||
this.startSearch(event);
|
||||
} else if (!this.active) {
|
||||
} else if (!this._searchActive) {
|
||||
if (symbol == Clutter.Tab || symbol == Clutter.Down) {
|
||||
this._activePage.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
|
||||
return true;
|
||||
@ -342,13 +332,15 @@ const ViewSelector = new Lang.Class({
|
||||
},
|
||||
|
||||
_onTextChanged: function (se, prop) {
|
||||
let searchPreviouslyActive = this.active;
|
||||
this.active = this._entry.get_text() != '';
|
||||
this._searchPending = this.active && !searchPreviouslyActive;
|
||||
if (this._searchPending) {
|
||||
let searchPreviouslyActive = this._searchActive;
|
||||
this._searchActive = this._entry.get_text() != '';
|
||||
|
||||
let startSearch = this._searchActive && !searchPreviouslyActive;
|
||||
if (startSearch) {
|
||||
this._searchResults.startingSearch();
|
||||
}
|
||||
if (this.active) {
|
||||
|
||||
if (this._searchActive) {
|
||||
this._entry.set_secondary_icon(this._activeIcon);
|
||||
|
||||
if (this._iconClickedId == 0) {
|
||||
@ -364,14 +356,14 @@ const ViewSelector = new Lang.Class({
|
||||
|
||||
this._entry.set_secondary_icon(this._inactiveIcon);
|
||||
this._searchCancelled();
|
||||
}
|
||||
if (!this.active) {
|
||||
|
||||
if (this._searchTimeoutId > 0) {
|
||||
Mainloop.source_remove(this._searchTimeoutId);
|
||||
this._searchTimeoutId = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._searchTimeoutId > 0)
|
||||
return;
|
||||
this._searchTimeoutId = Mainloop.timeout_add(150, Lang.bind(this, this._doSearch));
|
||||
@ -393,7 +385,7 @@ const ViewSelector = new Lang.Class({
|
||||
}
|
||||
this._searchResults.activateDefault();
|
||||
return true;
|
||||
} else if (this.active) {
|
||||
} else if (this._searchActive) {
|
||||
let arrowNext, nextDirection;
|
||||
if (entry.get_text_direction() == Clutter.TextDirection.RTL) {
|
||||
arrowNext = Clutter.Left;
|
||||
@ -467,6 +459,10 @@ const ViewSelector = new Lang.Class({
|
||||
RemoteSearch.loadRemoteSearchProviders(Lang.bind(this, this.addSearchProvider));
|
||||
},
|
||||
|
||||
getAppsActive: function() {
|
||||
return this._showAppsButton.checked;
|
||||
},
|
||||
|
||||
addSearchProvider: function(provider) {
|
||||
if (!this._shouldUseSearchProvider(provider))
|
||||
return;
|
||||
@ -478,6 +474,10 @@ const ViewSelector = new Lang.Class({
|
||||
removeSearchProvider: function(provider) {
|
||||
this._searchSystem.unregisterProvider(provider);
|
||||
this._searchResults.destroyProviderMeta(provider);
|
||||
},
|
||||
|
||||
getSearchActive: function() {
|
||||
return this._searchActive;
|
||||
}
|
||||
});
|
||||
Signals.addSignalMethods(ViewSelector.prototype);
|
||||
|
@ -10,7 +10,6 @@ const IconGrid = imports.ui.iconGrid;
|
||||
const Layout = imports.ui.layout;
|
||||
const Main = imports.ui.main;
|
||||
const Panel = imports.ui.panel;
|
||||
const Search = imports.ui.search;
|
||||
|
||||
// we could make these gsettings
|
||||
const FISH_NAME = 'wanda';
|
||||
@ -71,8 +70,7 @@ const WandaIconBin = new Lang.Class({
|
||||
Name: 'WandaIconBin',
|
||||
|
||||
_init: function(fish, label, params) {
|
||||
this.actor = new St.Bin({ style_class: 'search-result-content',
|
||||
reactive: true,
|
||||
this.actor = new St.Bin({ reactive: true,
|
||||
track_hover: true });
|
||||
this.icon = new WandaIcon(fish, label, params);
|
||||
|
||||
@ -133,10 +131,9 @@ function capitalize(str) {
|
||||
|
||||
const WandaSearchProvider = new Lang.Class({
|
||||
Name: 'WandaSearchProvider',
|
||||
Extends: Search.SearchProvider,
|
||||
|
||||
_init: function() {
|
||||
this.parent(_("Your favorite Easter Egg"));
|
||||
this.id = 'wanda';
|
||||
},
|
||||
|
||||
getResultMetas: function(fish, callback) {
|
||||
|
@ -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 Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Meta = imports.gi.Meta;
|
||||
@ -27,6 +28,8 @@ const WORKSPACE_CUT_SIZE = 10;
|
||||
|
||||
const WORKSPACE_KEEP_ALIVE_TIME = 100;
|
||||
|
||||
const OVERRIDE_SCHEMA = 'org.gnome.shell.overrides';
|
||||
|
||||
const WindowClone = new Lang.Class({
|
||||
Name: 'WindowClone',
|
||||
|
||||
@ -493,6 +496,8 @@ const ThumbnailsBox = new Lang.Class({
|
||||
_init: function() {
|
||||
this.actor = new Shell.GenericContainer({ reactive: true,
|
||||
style_class: 'workspace-thumbnails',
|
||||
can_focus: true,
|
||||
track_hover: 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));
|
||||
@ -521,6 +526,7 @@ const ThumbnailsBox = new Lang.Class({
|
||||
this._indicator = indicator;
|
||||
this.actor.add_actor(indicator);
|
||||
|
||||
this._inDrag = false;
|
||||
this._dropWorkspace = -1;
|
||||
this._dropPlaceholderPos = -1;
|
||||
this._dropPlaceholder = new St.Bin({ style_class: 'placeholder' });
|
||||
@ -541,6 +547,18 @@ 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.layoutManager.connect('monitors-changed', Lang.bind(this, this._updateTranslation));
|
||||
this.actor.connect('notify::hover', Lang.bind(this, this._updateTranslation));
|
||||
|
||||
Main.overview.connect('showing',
|
||||
Lang.bind(this, this._createThumbnails));
|
||||
Main.overview.connect('hidden',
|
||||
Lang.bind(this, this._destroyThumbnails));
|
||||
|
||||
Main.overview.connect('item-drag-begin',
|
||||
Lang.bind(this, this._onDragBegin));
|
||||
@ -554,6 +572,50 @@ const ThumbnailsBox = new Lang.Class({
|
||||
Lang.bind(this, this._onDragEnd));
|
||||
Main.overview.connect('window-drag-cancelled',
|
||||
Lang.bind(this, this._onDragCancelled));
|
||||
|
||||
this._settings = new Gio.Settings({ schema: OVERRIDE_SCHEMA });
|
||||
this._settings.connect('changed::dynamic-workspaces',
|
||||
Lang.bind(this, this._updateSwitcherVisibility));
|
||||
},
|
||||
|
||||
_getInitialTranslation: function() {
|
||||
// Always show the pager when hover, during a drag, or if workspaces are
|
||||
// actually used, e.g. there are windows on more than one
|
||||
let alwaysZoomOut = this.actor.hover || this._inDrag || global.screen.n_workspaces > 2;
|
||||
|
||||
if (!alwaysZoomOut) {
|
||||
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) {
|
||||
alwaysZoomOut = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (alwaysZoomOut)
|
||||
return 0;
|
||||
|
||||
let visibleWidth = this.actor.get_theme_node().get_length('visible-width');
|
||||
let rtl = (this.actor.get_text_direction() == Clutter.TextDirection.RTL);
|
||||
return rtl ? (visibleWidth - this.actor.width) : (this.actor.width - visibleWidth);
|
||||
},
|
||||
|
||||
_updateTranslation: function() {
|
||||
Tweener.addTween(this, { slideX: this._getInitialTranslation(),
|
||||
time: SLIDE_ANIMATION_TIME,
|
||||
transition: 'easeOutQuad' });
|
||||
},
|
||||
|
||||
_updateSwitcherVisibility: function() {
|
||||
this.actor.visible =
|
||||
this._settings.get_boolean('dynamic-workspaces') ||
|
||||
global.screen.n_workspaces > 1;
|
||||
},
|
||||
|
||||
_onButtonRelease: function(actor, event) {
|
||||
@ -573,6 +635,9 @@ const ThumbnailsBox = new Lang.Class({
|
||||
},
|
||||
|
||||
_onDragBegin: function() {
|
||||
this._inDrag = true;
|
||||
this._updateTranslation();
|
||||
|
||||
this._dragCancelled = false;
|
||||
this._dragMonitor = {
|
||||
dragMotion: Lang.bind(this, this._onDragMotion)
|
||||
@ -595,6 +660,8 @@ const ThumbnailsBox = new Lang.Class({
|
||||
_endDrag: function() {
|
||||
this._clearDragPlaceholder();
|
||||
DND.removeDragMonitor(this._dragMonitor);
|
||||
this._inDrag = false;
|
||||
this._updateTranslation();
|
||||
},
|
||||
|
||||
_onDragMotion: function(dragEvent) {
|
||||
@ -719,10 +786,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;
|
||||
@ -744,19 +817,101 @@ const ThumbnailsBox = new Lang.Class({
|
||||
};
|
||||
|
||||
this.addThumbnails(0, global.screen.n_workspaces);
|
||||
|
||||
// reset any translation and make sure the actor is visible when
|
||||
// entering the overview
|
||||
this.slideX = this._getInitialTranslation();
|
||||
this.actor.show();
|
||||
|
||||
this._updateSwitcherVisibility();
|
||||
},
|
||||
|
||||
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 = [];
|
||||
},
|
||||
|
||||
_computeTranslation: function() {
|
||||
let rtl = (this.actor.get_text_direction() == Clutter.TextDirection.RTL);
|
||||
|
||||
if (rtl)
|
||||
return - this.actor.width;
|
||||
else
|
||||
return this.actor.width;
|
||||
},
|
||||
|
||||
get slideX() {
|
||||
return this._slideX;
|
||||
},
|
||||
|
||||
set slideX(value) {
|
||||
this._slideX = value;
|
||||
this.actor.translation_x = this._slideX;
|
||||
|
||||
if (this._slideX > 0) {
|
||||
let rect = new Clutter.Rect();
|
||||
rect.size.width = this._background.width - this._slideX;
|
||||
rect.size.height = this._background.height;
|
||||
this.actor.clip_rect = rect;
|
||||
} else {
|
||||
this.actor.clip_rect = null;
|
||||
}
|
||||
},
|
||||
|
||||
show: function() {
|
||||
this.actor.show();
|
||||
this._updateTranslation();
|
||||
},
|
||||
|
||||
hide: function() {
|
||||
let hiddenX = this._computeTranslation();
|
||||
Tweener.addTween(this, { slideX: 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);
|
||||
}
|
||||
|
||||
this._updateSwitcherVisibility();
|
||||
},
|
||||
|
||||
addThumbnails: function(start, count) {
|
||||
for (let k = start; k < start + count; k++) {
|
||||
let metaWorkspace = global.screen.get_workspace_by_index(k);
|
||||
@ -802,7 +957,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);
|
||||
},
|
||||
@ -1157,5 +1312,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;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -23,8 +23,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',
|
||||
@ -439,9 +437,7 @@ const WorkspacesDisplay = new Lang.Class({
|
||||
|
||||
_init: function() {
|
||||
this.actor = new Shell.GenericContainer();
|
||||
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('allocate', Lang.bind(this, this._updateWorkspacesGeometry));
|
||||
this.actor.connect('parent-set', Lang.bind(this, this._parentSet));
|
||||
this.actor.set_clip_to_allocation(true);
|
||||
|
||||
@ -472,25 +468,8 @@ const WorkspacesDisplay = new Lang.Class({
|
||||
Main.overview.addAction(panAction);
|
||||
this.actor.bind_property('mapped', panAction, 'enabled', GObject.BindingFlags.SYNC_CREATE);
|
||||
|
||||
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;
|
||||
|
||||
@ -503,26 +482,6 @@ 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));
|
||||
|
||||
@ -537,10 +496,6 @@ const WorkspacesDisplay = new Lang.Class({
|
||||
this._notifyOpacityId = 0;
|
||||
this._swipeScrollBeginId = 0;
|
||||
this._swipeScrollEndId = 0;
|
||||
|
||||
this._settings = new Gio.Settings({ schema: OVERRIDE_SCHEMA });
|
||||
this._settings.connect('changed::dynamic-workspaces',
|
||||
Lang.bind(this, this._updateSwitcherVisibility));
|
||||
},
|
||||
|
||||
_onPan: function(action) {
|
||||
@ -550,38 +505,11 @@ const WorkspacesDisplay = new Lang.Class({
|
||||
return false;
|
||||
},
|
||||
|
||||
_updateSwitcherVisibility: function() {
|
||||
this._thumbnailsBox.actor.visible =
|
||||
this._settings.get_boolean('dynamic-workspaces') ||
|
||||
global.screen.n_workspaces > 1;
|
||||
},
|
||||
|
||||
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)
|
||||
@ -602,8 +530,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() {
|
||||
@ -613,14 +539,8 @@ 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) {
|
||||
@ -736,76 +656,6 @@ 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();
|
||||
},
|
||||
|
||||
_parentSet: function(actor, oldParent) {
|
||||
if (oldParent && this._notifyOpacityId)
|
||||
oldParent.disconnect(this._notifyOpacityId);
|
||||
@ -842,24 +692,19 @@ 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 overviewSpacing = Main.overview._spacing;
|
||||
let widthAdjust = this._zoomOut ? controlsNatural : controlsVisible;
|
||||
widthAdjust += overviewSpacing;
|
||||
width -= widthAdjust;
|
||||
width -= overviewSpacing;
|
||||
if (rtl)
|
||||
x += widthAdjust;
|
||||
x += overviewSpacing;
|
||||
|
||||
let monitors = Main.layoutManager.monitors;
|
||||
let m = 0;
|
||||
@ -883,25 +728,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;
|
||||
|
||||
@ -926,8 +758,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)
|
||||
@ -950,43 +780,11 @@ const WorkspacesDisplay = new Lang.Class({
|
||||
lostWorkspaces[l].destroy();
|
||||
}
|
||||
}
|
||||
|
||||
this._thumbnailsBox.removeThumbnails(removedIndex, removedNum);
|
||||
}
|
||||
|
||||
for (let i = 0; i < this._workspacesViews.length; i++)
|
||||
this._workspacesViews[i].updateWorkspaces(oldNumWorkspaces,
|
||||
newNumWorkspaces);
|
||||
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() {
|
||||
@ -1004,33 +802,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();
|
Loading…
Reference in New Issue
Block a user