on xenial help sign in
further development on the Desktop Spread

* Refactor bits to keep the spread separate from the regular states * Add blurring to background * make the workspace preview work as per spec * Improve animations * fix bugs Fixes: #1488147 Approved by: Lukáš Tinkl

commitb61b5ad24e44f83914c941edc73af9973bd2aa00 authorMichael Zanetti <michael.zanetti@canonical.com> dateFri 25 Sep 2015, 12:13 (11 years ago) parent637b1467d7b2e0
14 files changed+942−584
plugins/GlobalShortcut/globalshortcut.cpp+1−1 qml/Components/BlurLayer.qml+69 qml/Stages/ApplicationWindow.qml+3 qml/Stages/DecoratedWindow.qml+10−51 qml/Stages/DesktopSpread.qml+537 qml/Stages/DesktopSpreadDelegate.qml+111 qml/Stages/DesktopStage.qml+49−469 qml/Stages/SessionContainer.qml+3 qml/Stages/SurfaceContainer.qml+3−2 qml/Stages/WindowMoveResizeArea.qml+1−1 tests/mocks/Unity/Application/MirSurface.cpp+12−5 tests/mocks/Unity/Application/MirSurfaceItem.cpp+8−8 tests/mocks/Unity/Application/Session.cpp+4−4 tests/qmltests/tst_Shell.qml+131−43
plugins/GlobalShortcut/globalshortcut.cpp+1−1
@@ -72,7 +72,7 @@ void GlobalShortcut::keyPressEvent(QKeyEvent * event)
7272void GlobalShortcut::setupFilterOnWindow(QQuickWindow *window)
7373{
7474 if (!window) {
75− qWarning() << Q_FUNC_INFO << "Failed to setup filter on window";
75+// qWarning() << Q_FUNC_INFO << "Failed to setup filter on window";
7676 return;
7777 }
7878
qml/Components/BlurLayer.qmlnew file+69
@@ -0,0 +1,69 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import QtGraphicalEffects 1.0
19+
20+Item {
21+ id: root
22+
23+ property alias source: shaderEffectSource.sourceItem
24+ property alias saturation: desaturateEffect.saturationValue
25+ property alias blurRadius: fastBlur.radius
26+
27+ readonly property alias ready: shaderEffectSource.ready
28+
29+ ShaderEffect {
30+ id: desaturateEffect
31+ anchors.fill: root
32+
33+ property real saturationValue: 1
34+
35+ property variant source: ShaderEffectSource {
36+ id: shaderEffectSource
37+ hideSource: root.visible
38+ live: !root.visible
39+ property bool ready: false;
40+ onLiveChanged: {
41+ if (!live) {
42+ ready = false;
43+ scheduleUpdate();
44+ }
45+ }
46+ onScheduledUpdateCompleted: ready = true;
47+ }
48+
49+ fragmentShader: "
50+ varying highp vec2 qt_TexCoord0;
51+ uniform sampler2D source;
52+ uniform lowp float saturationValue;
53+ void main(void)
54+ {
55+ highp vec4 sourceColor = texture2D(source, qt_TexCoord0);
56+ highp vec4 scaledColor = sourceColor * vec4(0.3, 0.59, 0.11, 1.0);
57+ lowp float luminance = scaledColor.r + scaledColor.g + scaledColor.b ;
58+ gl_FragColor = mix(vec4(luminance, luminance, luminance, sourceColor.a), sourceColor, saturationValue);
59+ }"
60+ }
61+
62+ FastBlur {
63+ id: fastBlur
64+ anchors.fill: parent
65+ source: desaturateEffect
66+ visible: radius > 0
67+ radius: 0
68+ }
69+}
qml/Stages/ApplicationWindow.qml+3
@@ -20,6 +20,8 @@ import Unity.Application 0.1
2020
2121FocusScope {
2222 id: root
23+ implicitWidth: sessionContainer.implicitWidth
24+ implicitHeight: sessionContainer.implicitHeight
2325
2426 // to be read from outside
2527 readonly property bool fullscreen: application ? application.fullscreen : false
@@ -29,6 +31,7 @@ FocusScope {
2931 // to be set from outside
3032 property QtObject application
3133 property int surfaceOrientationAngle
34+ property alias resizeSurface: sessionContainer.resizeSurface
3235
3336 QtObject {
3437 id: d
qml/Stages/DecoratedWindow.qml+10−51
@@ -31,37 +31,10 @@ FocusScope {
3131 property bool highlightShown: false
3232 property real shadowOpacity: 1
3333
34− property int windowWidth: width
35− property int windowHeight: height
36−
3734 signal close();
3835 signal maximize();
3936 signal minimize();
4037
41− state: "normal"
42− states: [
43− State {
44− name: "normal"
45− PropertyChanges {
46− target: root
47− width: windowWidth
48− height: windowHeight
49− }
50− },
51− State {
52− name: "transformed"
53− PropertyChanges {
54− target: applicationWindow
55− itemScale: Math.max(root.width / root.windowWidth, root.height / root.windowHeight)
56− interactive: false
57− }
58− PropertyChanges {
59− target: clipper
60− clip: true
61− }
62− }
63− ]
64−
6538 BorderImage {
6639 anchors {
6740 fill: root
@@ -98,29 +71,15 @@ FocusScope {
9871 visible: decorationShown
9972 }
10073
101− Item {
102− id: clipper
103− anchors.fill: parent
104−
105− ApplicationWindow {
106− id: applicationWindow
107− objectName: application ? "appWindow_" + application.appId : "appWindow_null"
108− anchors.top: parent.top
109− anchors.topMargin: root.decorationShown ? decoration.height : 0
110− anchors.left: parent.left
111− width: root.windowWidth
112− height: root.windowHeight - (root.decorationShown ? decoration.height : 0)
113− interactive: true
114− focus: true
115−
116− property real itemScale: 1
117− transform: [
118− Scale {
119− origin.x: 0; origin.y: 0
120− xScale: applicationWindow.itemScale
121− yScale: applicationWindow.itemScale
122− }
123− ]
124− }
74+ ApplicationWindow {
75+ id: applicationWindow
76+ objectName: application ? "appWindow_" + application.appId : "appWindow_null"
77+ anchors.top: parent.top
78+ anchors.topMargin: decoration.height
79+ anchors.left: parent.left
80+ width: root.width
81+ height: root.height - decoration.height
82+ interactive: true
83+ focus: true
12584 }
12685}
qml/Stages/DesktopSpread.qmlnew file+537
@@ -0,0 +1,537 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import QtQuick.Layouts 1.1
19+import Ubuntu.Components 1.2
20+import Ubuntu.Gestures 0.1
21+import Unity.Application 0.1
22+
23+FocusScope {
24+ id: root
25+
26+ property bool altTabPressed: false
27+ property Item workspace: null
28+
29+ readonly property alias highlightedIndex: spreadRepeater.highlightedIndex
30+
31+ onFocusChanged: {
32+ // When the spread comes active, we want to keep focus to the input handler below
33+ // Make sure nothing inside the ApplicationWindow grabs our focus!
34+ if (focus) {
35+ forceActiveFocus();
36+ }
37+ }
38+
39+ Keys.onPressed: {
40+ switch (event.key) {
41+ case Qt.Key_Left:
42+ case Qt.Key_Backtab:
43+ selectPrevious(event.isAutoRepeat)
44+ event.accepted = true;
45+ break;
46+ case Qt.Key_Right:
47+ case Qt.Key_Tab:
48+ selectNext(event.isAutoRepeat)
49+ event.accepted = true;
50+ break;
51+ case Qt.Key_Escape:
52+ spreadRepeater.highlightedIndex = -1
53+ // Falling through intentionally
54+ case Qt.Key_Enter:
55+ case Qt.Key_Return:
56+ case Qt.Key_Space:
57+ root.state = ""
58+ event.accepted = true;
59+ }
60+ }
61+
62+ function selectNext(isAutoRepeat) {
63+ if (isAutoRepeat && spreadRepeater.highlightedIndex >= ApplicationManager.count -1) {
64+ return; // AutoRepeat is not allowed to wrap around
65+ }
66+
67+ spreadRepeater.highlightedIndex = (spreadRepeater.highlightedIndex + 1) % ApplicationManager.count;
68+ var newContentX = ((spreadFlickable.contentWidth) / (ApplicationManager.count + 1)) * Math.max(0, Math.min(ApplicationManager.count - 5, spreadRepeater.highlightedIndex - 3));
69+ if (spreadFlickable.contentX < newContentX || spreadRepeater.highlightedIndex == 0) {
70+ spreadFlickable.snapTo(newContentX)
71+ }
72+ }
73+
74+ function selectPrevious(isAutoRepeat) {
75+ if (isAutoRepeat && spreadRepeater.highlightedIndex == 0) {
76+ return; // AutoRepeat is not allowed to wrap around
77+ }
78+
79+ var newIndex = spreadRepeater.highlightedIndex - 1 >= 0 ? spreadRepeater.highlightedIndex - 1 : ApplicationManager.count - 1;
80+ spreadRepeater.highlightedIndex = newIndex;
81+ var newContentX = ((spreadFlickable.contentWidth) / (ApplicationManager.count + 1)) * Math.max(0, Math.min(ApplicationManager.count - 5, spreadRepeater.highlightedIndex - 1));
82+ if (spreadFlickable.contentX > newContentX || newIndex == ApplicationManager.count -1) {
83+ spreadFlickable.snapTo(newContentX)
84+ }
85+ }
86+
87+ function focusSelected() {
88+ if (spreadRepeater.highlightedIndex != -1) {
89+ var application = ApplicationManager.get(spreadRepeater.highlightedIndex);
90+ ApplicationManager.focusApplication(application.appId);
91+ }
92+ }
93+
94+ function cancel() {
95+ spreadRepeater.highlightedIndex = -1;
96+ state = ""
97+ }
98+
99+ Item {
100+ id: spreadContainer
101+ objectName: "spreadContainer"
102+ anchors.fill: parent
103+ visible: false
104+
105+ property bool animateIn: false
106+
107+ Repeater {
108+ id: spreadRepeater
109+ objectName: "spreadRepeater"
110+ model: ApplicationManager
111+
112+ property int highlightedIndex: -1
113+ property int closingIndex: -1
114+
115+ function indexOf(delegateItem) {
116+ for (var i = 0; i < spreadRepeater.count; i++) {
117+ if (spreadRepeater.itemAt(i) === delegateItem) {
118+ return i;
119+ }
120+ }
121+ return -1;
122+ }
123+
124+ delegate: Item {
125+ id: spreadDelegate
126+ objectName: "spreadDelegate"
127+ width: units.gu(20)
128+ height: units.gu(20)
129+
130+ property real angle: 0
131+ property real itemScale: 1
132+ property int itemScaleOriginX: 0
133+ property int itemScaleOriginY: 0
134+
135+ Behavior on x {
136+ id: closeBehavior
137+ enabled: spreadRepeater.closingIndex >= 0
138+ UbuntuNumberAnimation {
139+ onRunningChanged: if (!running) spreadRepeater.closingIndex = -1
140+ }
141+ }
142+
143+ DesktopSpreadDelegate {
144+ id: clippedSpreadDelegate
145+ objectName: "clippedSpreadDelegate"
146+ anchors.left: parent.left
147+ anchors.top: parent.top
148+ application: ApplicationManager.get(index)
149+ width: spreadMaths.spreadHeight
150+ height: spreadMaths.spreadHeight
151+
152+ transform: [
153+ Scale {
154+ origin.x: itemScaleOriginX
155+ origin.y: itemScaleOriginY
156+ xScale: itemScale
157+ yScale: itemScale
158+ },
159+ Rotation {
160+ origin { x: 0; y: (clippedSpreadDelegate.height - (clippedSpreadDelegate.height * itemScale / 2)) }
161+ axis { x: 0; y: 1; z: 0 }
162+ angle: spreadDelegate.angle
163+ }
164+ ]
165+
166+ MouseArea {
167+ id: spreadSelectArea
168+ anchors.fill: parent
169+ anchors.margins: -units.gu(2)
170+ enabled: false
171+ onClicked: {
172+ spreadRepeater.highlightedIndex = index;
173+ root.state = "";
174+ }
175+ }
176+ }
177+
178+ SpreadMaths {
179+ id: spreadMaths
180+ flickable: spreadFlickable
181+ itemIndex: index
182+ totalItems: Math.max(6, ApplicationManager.count)
183+ sceneHeight: root.height
184+ itemHeight: spreadDelegate.height
185+ }
186+
187+ states: [
188+ State {
189+ name: "altTab"; when: root.state == "altTab" && spreadContainer.visible
190+ PropertyChanges {
191+ target: spreadDelegate
192+ x: spreadMaths.animatedX
193+ y: spreadMaths.animatedY + (spreadDelegate.height - clippedSpreadDelegate.height) - units.gu(2)
194+ width: spreadMaths.spreadHeight
195+ height: spreadMaths.sceneHeight
196+ angle: spreadMaths.animatedAngle
197+ itemScale: spreadMaths.scale
198+ itemScaleOriginY: clippedSpreadDelegate.height / 2;
199+ z: index
200+ visible: spreadMaths.itemVisible
201+ }
202+ PropertyChanges {
203+ target: clippedSpreadDelegate
204+ highlightShown: index == spreadRepeater.highlightedIndex
205+ state: "transformed"
206+ shadowOpacity: spreadMaths.shadowOpacity
207+ anchors.topMargin: units.gu(2)
208+ }
209+ PropertyChanges {
210+ target: tileInfo
211+ visible: true
212+ opacity: spreadMaths.tileInfoOpacity
213+ }
214+ PropertyChanges {
215+ target: spreadSelectArea
216+ enabled: true
217+ }
218+ }
219+ ]
220+ transitions: [
221+ Transition {
222+ from: ""
223+ to: "altTab"
224+ PropertyAction { target: spreadDelegate; properties: "y,height,width,angle,z,itemScale,itemScaleOriginY,visible" }
225+ PropertyAction { target: clippedSpreadDelegate; properties: "anchors.topMargin" }
226+ PropertyAnimation {
227+ target: spreadDelegate; properties: "x"
228+ from: root.width
229+ duration: spreadContainer.animateIn ? UbuntuAnimation.FastDuration :0
230+ easing: UbuntuAnimation.StandardEasing
231+ }
232+ }
233+ ]
234+
235+ MouseArea {
236+ id: tileInfo
237+ objectName: "tileInfo"
238+ anchors { left: parent.left; top: clippedSpreadDelegate.bottom; topMargin: units.gu(5) }
239+ property int nextItemX: spreadRepeater.count > index + 1 ? spreadRepeater.itemAt(index + 1).x : spreadDelegate.x + units.gu(30)
240+ width: Math.min(units.gu(30), nextItemX - spreadDelegate.x)
241+ height: titleInfoColumn.height
242+ visible: false
243+ hoverEnabled: true
244+
245+ onContainsMouseChanged: {
246+ if (containsMouse) {
247+ spreadRepeater.highlightedIndex = index
248+ }
249+ }
250+
251+ onClicked: {
252+ root.state = ""
253+ }
254+
255+ ColumnLayout {
256+ id: titleInfoColumn
257+ anchors { left: parent.left; top: parent.top; right: parent.right }
258+ spacing: units.gu(1)
259+
260+ UbuntuShape {
261+ Layout.preferredHeight: Math.min(units.gu(6), root.height * .05)
262+ Layout.preferredWidth: height * 8 / 7.6
263+ image: Image {
264+ anchors.fill: parent
265+ source: model.icon
266+ }
267+ }
268+ Label {
269+ Layout.fillWidth: true
270+ Layout.preferredHeight: units.gu(6)
271+ text: model.name
272+ wrapMode: Text.WordWrap
273+ elide: Text.ElideRight
274+ maximumLineCount: 2
275+ }
276+ }
277+ }
278+
279+ Image {
280+ id: closeImage
281+ anchors { left: parent.left; top: parent.top; leftMargin: -height / 2; topMargin: -height / 2 + spreadMaths.closeIconOffset + units.gu(2) }
282+ source: "graphics/window-close.svg"
283+ readonly property var mousePos: hoverMouseArea.mapToItem(spreadDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
284+ visible: index == spreadRepeater.highlightedIndex
285+ && mousePos.y < (clippedSpreadDelegate.height / 3)
286+ && mousePos.y > -units.gu(4)
287+ && mousePos.x > -units.gu(4)
288+ && mousePos.x < (clippedSpreadDelegate.width * 2 / 3)
289+ height: units.gu(1.5)
290+ width: height
291+ sourceSize.width: width
292+ sourceSize.height: height
293+
294+ MouseArea {
295+ id: closeMouseArea
296+ objectName: "closeMouseArea"
297+ anchors.fill: closeImage
298+ anchors.margins: -units.gu(2)
299+ onClicked: {
300+ spreadRepeater.closingIndex = index;
301+ ApplicationManager.stopApplication(model.appId)
302+ }
303+ }
304+ }
305+ }
306+ }
307+ }
308+
309+
310+ MouseArea {
311+ id: hoverMouseArea
312+ objectName: "hoverMouseArea"
313+ anchors.fill: spreadContainer
314+ propagateComposedEvents: true
315+ hoverEnabled: true
316+ enabled: false
317+ visible: enabled
318+
319+ property int scrollAreaWidth: root.width / 3
320+ property bool progressiveScrollingEnabled: false
321+
322+ onMouseXChanged: {
323+ mouse.accepted = false
324+
325+ if (hoverMouseArea.pressed) {
326+ return;
327+ }
328+
329+ // Find the hovered item and mark it active
330+ var mapped = mapToItem(spreadContainer, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
331+ var itemUnder = spreadContainer.childAt(mapped.x, mapped.y)
332+ if (itemUnder) {
333+ mapped = mapToItem(itemUnder, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
334+ var delegateChild = itemUnder.childAt(mapped.x, mapped.y)
335+ if (delegateChild && (delegateChild.objectName === "clippedSpreadDelegate" || delegateChild.objectName === "tileInfo")) {
336+ spreadRepeater.highlightedIndex = spreadRepeater.indexOf(itemUnder)
337+ }
338+ }
339+
340+ if (spreadFlickable.contentWidth > spreadFlickable.minContentWidth) {
341+ var margins = spreadFlickable.width * 0.05;
342+
343+ if (!progressiveScrollingEnabled && mouseX < spreadFlickable.width - scrollAreaWidth) {
344+ progressiveScrollingEnabled = true
345+ }
346+
347+ // do we need to scroll?
348+ if (mouseX < scrollAreaWidth + margins) {
349+ var progress = Math.min(1, (scrollAreaWidth + margins - mouseX) / (scrollAreaWidth - margins));
350+ var contentX = (1 - progress) * (spreadFlickable.contentWidth - spreadFlickable.width)
351+ spreadFlickable.contentX = Math.max(0, Math.min(spreadFlickable.contentX, contentX))
352+ }
353+ if (mouseX > spreadFlickable.width - scrollAreaWidth && progressiveScrollingEnabled) {
354+ var progress = Math.min(1, (mouseX - (spreadFlickable.width - scrollAreaWidth)) / (scrollAreaWidth - margins))
355+ var contentX = progress * (spreadFlickable.contentWidth - spreadFlickable.width)
356+ spreadFlickable.contentX = Math.min(spreadFlickable.contentWidth - spreadFlickable.width, Math.max(spreadFlickable.contentX, contentX))
357+ }
358+ }
359+ }
360+ onPressed: mouse.accepted = false
361+ }
362+
363+ FloatingFlickable {
364+ id: spreadFlickable
365+ objectName: "spreadFlickable"
366+ anchors.fill: parent
367+ property int minContentWidth: 6 * Math.min(height / 4, width / 5)
368+ contentWidth: Math.max(6, ApplicationManager.count) * Math.min(height / 4, width / 5)
369+ enabled: false
370+
371+ function snapTo(contentX) {
372+ snapAnimation.stop();
373+ snapAnimation.to = contentX
374+ snapAnimation.start();
375+ }
376+
377+ UbuntuNumberAnimation {
378+ id: snapAnimation
379+ target: spreadFlickable
380+ property: "contentX"
381+ }
382+ }
383+
384+ Item {
385+ id: workspaceSelector
386+ anchors {
387+ left: parent.left
388+ top: parent.top
389+ right: parent.right
390+ topMargin: units.gu(3.5)
391+ }
392+ height: root.height * 0.25
393+ visible: false
394+
395+ RowLayout {
396+ anchors.fill: parent
397+ spacing: units.gu(1)
398+ Item { Layout.fillWidth: true }
399+ Repeater {
400+ model: 1 // TODO: will be a workspacemodel in the future
401+ Item {
402+ Layout.fillHeight: true
403+ Layout.preferredWidth: ((height - units.gu(6)) * root.width / root.height)
404+ Image {
405+ source: root.background
406+ anchors {
407+ left: parent.left
408+ right: parent.right
409+ verticalCenter: parent.verticalCenter
410+ }
411+ height: parent.height * 0.75
412+
413+ ShaderEffect {
414+ anchors.fill: parent
415+
416+ property var source: ShaderEffectSource {
417+ id: shaderEffectSource
418+ sourceItem: appContainer
419+ }
420+
421+ fragmentShader: "
422+ varying highp vec2 qt_TexCoord0;
423+ uniform sampler2D source;
424+ void main(void)
425+ {
426+ highp vec4 sourceColor = texture2D(source, qt_TexCoord0);
427+ gl_FragColor = sourceColor;
428+ }"
429+ }
430+ }
431+
432+ // TODO: This is the bar for the currently selected workspace
433+ // Enable this once the workspace stuff is implemented
434+ // Rectangle {
435+ // anchors { left: parent.left; right: parent.right; bottom: parent.bottom }
436+ // height: units.dp(2)
437+ // color: UbuntuColors.orange
438+ // visible: index == 0 // TODO: should be active workspace index
439+ // }
440+ }
441+
442+ }
443+ // TODO: This is the "new workspace" button. Enable this once workspaces are implemented
444+ // Item {
445+ // Layout.fillHeight: true
446+ // Layout.preferredWidth: ((height - units.gu(6)) * root.width / root.height)
447+ // Rectangle {
448+ // anchors {
449+ // left: parent.left
450+ // right: parent.right
451+ // verticalCenter: parent.verticalCenter
452+ // }
453+ // height: parent.height * 0.75
454+ // color: "#22ffffff"
455+
456+ // Label {
457+ // anchors.centerIn: parent
458+ // font.pixelSize: parent.height / 2
459+ // text: "+"
460+ // }
461+ // }
462+ // }
463+ Item { Layout.fillWidth: true }
464+ }
465+ }
466+
467+ Label {
468+ id: currentSelectedLabel
469+ anchors { bottom: parent.bottom; bottomMargin: root.height * 0.625; horizontalCenter: parent.horizontalCenter }
470+ text: spreadRepeater.highlightedIndex >= 0 ? ApplicationManager.get(spreadRepeater.highlightedIndex).name : ""
471+ visible: false
472+ fontSize: "large"
473+ }
474+
475+ states: [
476+ State {
477+ name: "altTab"; when: root.altTabPressed
478+ PropertyChanges { target: blurLayer; saturation: 0.8; blurRadius: 60; visible: true }
479+ PropertyChanges { target: workspaceSelector; visible: true }
480+ PropertyChanges { target: spreadContainer; visible: true }
481+ PropertyChanges { target: spreadFlickable; enabled: spreadFlickable.contentWidth > spreadFlickable.minContentWidth }
482+ PropertyChanges { target: currentSelectedLabel; visible: true }
483+ PropertyChanges { target: spreadBackground; visible: true }
484+ PropertyChanges { target: hoverMouseArea; enabled: true }
485+ }
486+ ]
487+ transitions: [
488+ Transition {
489+ from: "*"
490+ to: "altTab"
491+ SequentialAnimation {
492+ PropertyAction { target: hoverMouseArea; property: "progressiveScrollingEnabled"; value: false }
493+ PropertyAction { target: spreadRepeater; property: "highlightedIndex"; value: Math.min(ApplicationManager.count - 1, 1) }
494+ PauseAnimation { duration: 140 }
495+ PropertyAction { target: workspaceSelector; property: "visible" }
496+ PropertyAction { target: spreadContainer; property: "visible" }
497+ ParallelAnimation {
498+ UbuntuNumberAnimation {
499+ target: blurLayer; properties: "saturation,blurRadius";
500+ duration: spreadContainer.animateIn ? UbuntuAnimation.FastDuration : 0
501+ }
502+ PropertyAction { target: spreadFlickable; property: "visible" }
503+ PropertyAction { targets: [currentSelectedLabel,spreadBackground]; property: "visible" }
504+ PropertyAction { target: spreadFlickable; property: "contentX"; value: 0 }
505+ }
506+ }
507+ },
508+ Transition {
509+ from: "*"
510+ to: "*"
511+ PropertyAnimation { property: "opacity" }
512+ ScriptAction { script: { root.focusSelected() } }
513+ PropertyAction { target: spreadRepeater; property: "highlightedIndex"; value: -1 }
514+ PropertyAction { target: spreadContainer; property: "animateIn"; value: false }
515+ }
516+
517+ ]
518+
519+ MouseArea {
520+ id: rightEdgePushArea
521+ anchors {
522+ top: parent.top
523+ right: parent.right
524+ bottom: parent.bottom
525+ }
526+ // TODO: Make this a push to edge thing like the launcher when we can,
527+ // for now, yes, we want 1 pixel, regardless of the scaling
528+ width: 1
529+ hoverEnabled: true
530+ onContainsMouseChanged: {
531+ if (containsMouse) {
532+ spreadContainer.animateIn = true;
533+ root.state = "altTab";
534+ }
535+ }
536+ }
537+}
qml/Stages/DesktopSpreadDelegate.qmlnew file+111
@@ -0,0 +1,111 @@
1+/*
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Authors: Michael Zanetti <michael.zanetti@canonical.com>
17+ */
18+
19+import QtQuick 2.3
20+import Ubuntu.Components 1.1
21+import Unity.Application 0.1
22+
23+Item {
24+ id: root
25+
26+ property alias window: applicationWindow
27+ property alias application: applicationWindow.application
28+
29+ property bool highlightShown: false
30+ property real shadowOpacity: 1
31+
32+ property int windowWidth: application.session && application.session.surface ? application.session.surface.size.width : 0
33+ property int windowHeight: application.session && application.session.surface ? application.session.surface.size.height : 0
34+
35+ state: "normal"
36+ states: [
37+ State {
38+ name: "normal"
39+ PropertyChanges {
40+ target: root
41+ width: windowWidth
42+ height: windowHeight
43+ }
44+ },
45+ State {
46+ name: "transformed"
47+ PropertyChanges {
48+ target: applicationWindow
49+ itemScale: Math.max(root.width / root.windowWidth, root.height / root.windowHeight)
50+ interactive: false
51+ }
52+ PropertyChanges {
53+ target: clipper
54+ clip: true
55+ }
56+ }
57+ ]
58+
59+ BorderImage {
60+ anchors {
61+ fill: root
62+ margins: -units.gu(2)
63+ }
64+ source: "graphics/dropshadow2gu.sci"
65+ opacity: root.shadowOpacity * .3
66+ }
67+
68+ Rectangle {
69+ id: selectionHighlight
70+ anchors.fill: parent
71+ anchors.margins: -units.gu(1)
72+ color: "white"
73+ opacity: highlightShown ? 0.15 : 0
74+ antialiasing: true
75+ }
76+
77+ Rectangle {
78+ anchors { left: selectionHighlight.left; right: selectionHighlight.right; bottom: selectionHighlight.bottom; }
79+ height: units.dp(2)
80+ color: UbuntuColors.orange
81+ visible: root.highlightShown
82+ antialiasing: true
83+ }
84+
85+ Item {
86+ id: clipper
87+ anchors.fill: parent
88+
89+ ApplicationWindow {
90+ id: applicationWindow
91+ objectName: application ? "appWindow_" + application.appId : "appWindow_null"
92+ anchors.top: parent.top
93+ anchors.topMargin: 0
94+ anchors.left: parent.left
95+ width: root.windowWidth
96+ height: root.windowHeight
97+ interactive: false
98+ resizeSurface: false
99+ focus: false
100+
101+ property real itemScale: 1
102+ transform: [
103+ Scale {
104+ origin.x: 0; origin.y: 0
105+ xScale: applicationWindow.itemScale
106+ yScale: applicationWindow.itemScale
107+ }
108+ ]
109+ }
110+ }
111+}
qml/Stages/DesktopStage.qml+49−469
@@ -20,6 +20,7 @@ import QtQuick 2.4
2020import QtQuick.Layouts 1.1
2121import Ubuntu.Components 1.1
2222import Unity.Application 0.1
23+import "../Components"
2324import "../Components/PanelState"
2425import Utils 0.1
2526import Ubuntu.Gestures 0.1
@@ -41,6 +42,8 @@ Rectangle {
4142 property bool beingResized: false
4243 property bool keepDashRunning: true
4344 property bool suspended: false
45+ property alias background: wallpaper.source
46+ property alias altTabPressed: spread.altTabPressed
4447
4548 // functions to be called from outside
4649 function updateFocusedAppOrientation() { /* TODO */ }
@@ -53,21 +56,11 @@ Rectangle {
5356 property int mainAppWindowOrientationAngle: 0
5457 readonly property bool orientationChangesEnabled: false
5558
56− property alias background: wallpaper.source
57− property bool altTabPressed: false
58−
59− CrossFadeImage {
60− id: wallpaper
61− anchors.fill: parent
62− sourceSize { height: root.height; width: root.width }
63− fillMode: Image.PreserveAspectCrop
64− }
65−
6659 Connections {
6760 target: ApplicationManager
6861 onApplicationAdded: {
69− if (root.state == "altTab") {
70− root.state = "";
62+ if (spread.state == "altTab") {
63+ spread.state = "";
7164 }
7265
7366 ApplicationManager.requestFocusApplication(appId)
@@ -77,7 +70,11 @@ Rectangle {
7770 var appIndex = priv.indexOf(appId);
7871 var appDelegate = appRepeater.itemAt(appIndex);
7972 appDelegate.minimized = false;
80− appDelegate.focus = true;
73+ ApplicationManager.focusApplication(appId)
74+
75+ if (spread.state == "altTab") {
76+ spread.cancel()
77+ }
8178 }
8279 }
8380
@@ -115,72 +112,17 @@ Rectangle {
115112 value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.state === "maximized"
116113 }
117114
118− Rectangle {
119− id: spreadBackground
120− anchors.fill: parent
121− color: "#55000000"
122− visible: false
123− }
124−
125115 FocusScope {
126116 id: appContainer
127117 objectName: "appContainer"
128118 anchors.fill: parent
129− focus: true
130−
131− Keys.onPressed: {
132− switch (event.key) {
133− case Qt.Key_Left:
134− case Qt.Key_Backtab:
135− selectPrevious(event.isAutoRepeat)
136− event.accepted = true;
137− break;
138− case Qt.Key_Right:
139− case Qt.Key_Tab:
140− selectNext(event.isAutoRepeat)
141− event.accepted = true;
142− break;
143− case Qt.Key_Escape:
144− appRepeater.highlightedIndex = -1
145− event.accepted = true;
146− case Qt.Key_Enter:
147− case Qt.Key_Return:
148− case Qt.Key_Space:
149− root.state = ""
150− event.accepted = true;
151− }
152− }
153−
154− function selectNext(isAutoRepeat) {
155− if (isAutoRepeat && appRepeater.highlightedIndex >= ApplicationManager.count -1) {
156− return; // AutoRepeat is not allowed to wrap around
157− }
158−
159− appRepeater.highlightedIndex = (appRepeater.highlightedIndex + 1) % ApplicationManager.count;
160− var newContentX = ((spreadFlickable.contentWidth) / (ApplicationManager.count + 1)) * Math.max(0, Math.min(ApplicationManager.count - 5, appRepeater.highlightedIndex - 3));
161− if (spreadFlickable.contentX < newContentX || appRepeater.highlightedIndex == 0) {
162− spreadFlickable.snapTo(newContentX)
163− }
164− }
119+ focus: spread.state !== "altTab"
165120
166− function selectPrevious(isAutoRepeat) {
167− if (isAutoRepeat && appRepeater.highlightedIndex == 0) {
168− return; // AutoRepeat is not allowed to wrap around
169− }
170−
171− var newIndex = appRepeater.highlightedIndex - 1 >= 0 ? appRepeater.highlightedIndex - 1 : ApplicationManager.count - 1;
172− appRepeater.highlightedIndex = newIndex;
173− var newContentX = ((spreadFlickable.contentWidth) / (ApplicationManager.count + 1)) * Math.max(0, Math.min(ApplicationManager.count - 5, appRepeater.highlightedIndex - 1));
174− if (spreadFlickable.contentX > newContentX || newIndex == ApplicationManager.count -1) {
175− spreadFlickable.snapTo(newContentX)
176− }
177− }
178−
179− function focusSelected() {
180− if (appRepeater.highlightedIndex != -1) {
181− var application = ApplicationManager.get(appRepeater.highlightedIndex);
182− ApplicationManager.requestFocusApplication(application.appId)
183− }
121+ CrossFadeImage {
122+ id: wallpaper
123+ anchors.fill: parent
124+ sourceSize { height: root.height; width: root.width }
125+ fillMode: Image.PreserveAspectCrop
184126 }
185127
186128 Repeater {
@@ -188,30 +130,13 @@ Rectangle {
188130 model: ApplicationManager
189131 objectName: "appRepeater"
190132
191− property int highlightedIndex: -1
192− property int closingIndex: -1
193−
194− function indexOf(delegateItem) {
195− for (var i = 0; i < appRepeater.count; i++) {
196− if (appRepeater.itemAt(i) === delegateItem) {
197− return i;
198− }
199− }
200− return -1;
201− }
202−
203133 delegate: FocusScope {
204134 id: appDelegate
205135 z: ApplicationManager.count - index
206136 y: units.gu(3)
207137 width: units.gu(60)
208138 height: units.gu(50)
209−
210− property int windowWidth: 0
211− property int windowHeight: 0
212− // We don't want to resize the actual application when we're transforming things for the spread only
213− onWidthChanged: if (appDelegate.state !== "altTab") windowWidth = width
214− onHeightChanged: if (appDelegate.state !== "altTab") windowHeight = height
139+ focus: model.appId === priv.focusedAppId
215140
216141 readonly property int minWidth: units.gu(10)
217142 readonly property int minHeight: units.gu(10)
@@ -225,13 +150,6 @@ Rectangle {
225150 }
226151 }
227152
228− Component.onCompleted: {
229− // Focus the top-most or AppMan-focused application on start up.
230− if (ApplicationManager.focusedApplicationId === model.appId && !focus) {
231− focus = true;
232− }
233− }
234−
235153 Binding {
236154 target: ApplicationManager.get(index)
237155 property: "requestedState"
@@ -256,63 +174,17 @@ Rectangle {
256174 maximized = false;
257175 }
258176
259− Behavior on x {
260− id: closeBehavior
261− enabled: appRepeater.closingIndex >= 0
262− UbuntuNumberAnimation {
263− onRunningChanged: if (!running) appRepeater.closingIndex = -1
264− }
265− }
266−
267177 states: [
268178 State {
269− name: "normal"; when: !appDelegate.maximized && !appDelegate.minimized && root.state !== "altTab"
179+ name: "normal"; when: !appDelegate.maximized && !appDelegate.minimized
270180 },
271181 State {
272− name: "maximized"; when: appDelegate.maximized && (root.state !== "altTab" || (root.state == "altTab" && !root.workspacesUpdated))
182+ name: "maximized"; when: appDelegate.maximized
273183 PropertyChanges { target: appDelegate; x: 0; y: 0; width: root.width; height: root.height }
274184 },
275185 State {
276− name: "minimized"; when: appDelegate.minimized && (root.state !== "altTab" || (root.state == "altTab" && !root.workspacesUpdated))
186+ name: "minimized"; when: appDelegate.minimized
277187 PropertyChanges { target: appDelegate; x: -appDelegate.width / 2; scale: units.gu(5) / appDelegate.width; opacity: 0 }
278− },
279− State {
280− name: "altTab"; when: root.state == "altTab" && root.workspacesUpdated
281− PropertyChanges {
282− target: appDelegate
283− x: spreadMaths.animatedX
284− y: spreadMaths.animatedY + (appDelegate.height - decoratedWindow.height) - units.gu(2)
285− width: spreadMaths.spreadHeight
286− height: spreadMaths.sceneHeight
287− angle: spreadMaths.animatedAngle
288− itemScale: spreadMaths.scale
289− itemScaleOriginY: decoratedWindow.height / 2;
290− z: index
291− visible: spreadMaths.itemVisible
292− }
293− PropertyChanges {
294− target: decoratedWindow
295− decorationShown: false
296− highlightShown: index == appRepeater.highlightedIndex
297− state: "transformed"
298− width: spreadMaths.spreadHeight
299− height: spreadMaths.spreadHeight
300− shadowOpacity: spreadMaths.shadowOpacity
301− anchors.topMargin: units.gu(2)
302− }
303− PropertyChanges {
304− target: tileInfo
305− visible: true
306− opacity: spreadMaths.tileInfoOpacity
307− }
308− PropertyChanges {
309− target: spreadSelectArea
310− enabled: true
311− }
312− PropertyChanges {
313− target: windowMoveResizeArea
314− enabled: false
315− }
316188 }
317189 ]
318190 transitions: [
@@ -334,18 +206,13 @@ Rectangle {
334206 }
335207 }
336208 ]
337− property real angle: 0
338− property real itemScale: 1
339− property int itemScaleOriginX: 0
340− property int itemScaleOriginY: 0
341−
342− SpreadMaths {
343− id: spreadMaths
344− flickable: spreadFlickable
345− itemIndex: index
346− totalItems: Math.max(6, ApplicationManager.count)
347− sceneHeight: root.height
348− itemHeight: appDelegate.height
209+
210+ Binding {
211+ id: previewBinding
212+ target: appDelegate
213+ property: "z"
214+ value: ApplicationManager.count + 1
215+ when: index == spread.highlightedIndex && blurLayer.ready
349216 }
350217
351218 WindowMoveResizeArea {
@@ -356,7 +223,7 @@ Rectangle {
356223 resizeHandleWidth: units.gu(2)
357224 windowId: model.appId // FIXME: Change this to point to windowId once we have such a thing
358225
359− onPressed: { appDelegate.focus = true; }
226+ onPressed: { ApplicationManager.focusApplication(model.appId) }
360227 }
361228
362229 DecoratedWindow {
@@ -364,8 +231,8 @@ Rectangle {
364231 objectName: "decoratedWindow"
365232 anchors.left: appDelegate.left
366233 anchors.top: appDelegate.top
367− windowWidth: appDelegate.windowWidth
368− windowHeight: appDelegate.windowHeight
234+ width: appDelegate.width
235+ height: appDelegate.height
369236 application: ApplicationManager.get(index)
370237 active: ApplicationManager.focusedApplicationId === model.appId
371238 focus: true
@@ -373,324 +240,37 @@ Rectangle {
373240 onClose: ApplicationManager.stopApplication(model.appId)
374241 onMaximize: appDelegate.maximize()
375242 onMinimize: appDelegate.minimize()
376−
377− transform: [
378− Scale {
379− origin.x: itemScaleOriginX
380− origin.y: itemScaleOriginY
381− xScale: itemScale
382− yScale: itemScale
383− },
384− Rotation {
385− origin { x: 0; y: (decoratedWindow.height - (decoratedWindow.height * itemScale / 2)) }
386− axis { x: 0; y: 1; z: 0 }
387− angle: appDelegate.angle
388− }
389− ]
390−
391− MouseArea {
392− id: spreadSelectArea
393− anchors.fill: parent
394− anchors.margins: -units.gu(2)
395− enabled: false
396− onClicked: {
397− appRepeater.highlightedIndex = index;
398− root.state = ""
399− }
400− }
401− }
402−
403− Image {
404− id: closeImage
405− anchors { left: parent.left; top: parent.top; leftMargin: -height / 2; topMargin: -height / 2 + spreadMaths.closeIconOffset + units.gu(2) }
406− source: "graphics/window-close.svg"
407− readonly property var mousePos: hoverMouseArea.mapToItem(appDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
408− visible: index == appRepeater.highlightedIndex
409− && mousePos.y < (decoratedWindow.height / 3)
410− && mousePos.y > -units.gu(4)
411− && mousePos.x > -units.gu(4)
412− && mousePos.x < (decoratedWindow.width * 2 / 3)
413− height: units.gu(1.5)
414− width: height
415− sourceSize.width: width
416− sourceSize.height: height
417−
418− MouseArea {
419− id: closeMouseArea
420− objectName: "closeMouseArea"
421− anchors.fill: closeImage
422− anchors.margins: -units.gu(2)
423− onClicked: {
424− appRepeater.closingIndex = index;
425− ApplicationManager.stopApplication(model.appId)
426− }
427− }
428− }
429−
430− MouseArea {
431− id: tileInfo
432− objectName: "tileInfo"
433− anchors { left: parent.left; top: decoratedWindow.bottom; topMargin: units.gu(5) }
434− width: units.gu(30)
435− height: titleInfoColumn.height
436− visible: false
437− hoverEnabled: true
438−
439− onContainsMouseChanged: {
440− if (containsMouse) {
441− appRepeater.highlightedIndex = index
442− }
443− }
444−
445− onClicked: {
446− root.state = ""
447− }
448−
449− ColumnLayout {
450− id: titleInfoColumn
451− anchors { left: parent.left; top: parent.top; right: parent.right }
452− spacing: units.gu(1)
453−
454− UbuntuShape {
455− Layout.preferredHeight: Math.min(units.gu(6), root.height * .05)
456− Layout.preferredWidth: height * 8 / 7.6
457− image: Image {
458− anchors.fill: parent
459− source: model.icon
460− }
461− }
462− Label {
463− Layout.fillWidth: true
464− Layout.preferredHeight: units.gu(6)
465− text: model.name
466− wrapMode: Text.WordWrap
467− maximumLineCount: 2
468− }
469− }
470243 }
471244 }
472245 }
473246 }
474247
475− MouseArea {
476− id: hoverMouseArea
477− anchors.fill: appContainer
478− propagateComposedEvents: true
479− hoverEnabled: true
480− enabled: false
481−
482− property int scrollAreaWidth: root.width / 3
483− property bool progressiveScrollingEnabled: false
484−
485− onMouseXChanged: {
486− mouse.accepted = false
487− if (hoverMouseArea.pressed) return;
488−
489− // Find the hovered item and mark it active
490− var mapped = mapToItem(appContainer, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
491− var itemUnder = appContainer.childAt(mapped.x, mapped.y)
492− if (itemUnder) {
493− mapped = mapToItem(itemUnder, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
494− var delegateChild = itemUnder.childAt(mapped.x, mapped.y)
495− if (delegateChild.objectName === "decoratedWindow" || delegateChild.objectName === "tileInfo") {
496− appRepeater.highlightedIndex = appRepeater.indexOf(itemUnder)
497− }
498− }
499−
500− if (spreadFlickable.contentWidth > spreadFlickable.minContentWidth) {
501− var margins = spreadFlickable.width * 0.05;
502−
503− if (!progressiveScrollingEnabled && mouseX < spreadFlickable.width - scrollAreaWidth) {
504− progressiveScrollingEnabled = true
505− }
506−
507− // do we need to scroll?
508− if (mouseX < scrollAreaWidth) {
509− var progress = Math.min(1, (scrollAreaWidth + margins - mouseX) / (scrollAreaWidth - margins));
510− var contentX = (1 - progress) * (spreadFlickable.contentWidth - spreadFlickable.width)
511− spreadFlickable.contentX = Math.max(0, Math.min(spreadFlickable.contentX, contentX))
512− }
513− if (mouseX > spreadFlickable.width - scrollAreaWidth && progressiveScrollingEnabled) {
514− var progress = Math.min(1, (mouseX - (spreadFlickable.width - scrollAreaWidth)) / (scrollAreaWidth - margins))
515− var contentX = progress * (spreadFlickable.contentWidth - spreadFlickable.width)
516− spreadFlickable.contentX = Math.min(spreadFlickable.contentWidth - spreadFlickable.width, Math.max(spreadFlickable.contentX, contentX))
517− }
518− }
519− }
520− onPressed: mouse.accepted = false
521− }
522−
523− FloatingFlickable {
524− id: spreadFlickable
525− objectName: "spreadFlickable"
248+ BlurLayer {
249+ id: blurLayer
526250 anchors.fill: parent
527− property int minContentWidth: 6 * Math.min(height / 4, width / 5)
528− contentWidth: Math.max(6, ApplicationManager.count) * Math.min(height / 4, width / 5)
529− enabled: false
530−
531− function snapTo(contentX) {
532− snapAnimation.stop();
533− snapAnimation.to = contentX
534− snapAnimation.start();
535− }
536−
537− UbuntuNumberAnimation {
538− id: snapAnimation
539− target: spreadFlickable
540− property: "contentX"
541− }
542− }
543−
544− Item {
545− id: workspaceSelector
546− anchors {
547− left: parent.left
548− top: parent.top
549− right: parent.right
550− topMargin: units.gu(3.5) // TODO: should be root.panelHeight
551− }
552− height: root.height * 0.25
251+ source: appContainer
553252 visible: false
554−
555− RowLayout {
556− anchors.fill: parent
557− spacing: units.gu(1)
558− Item { Layout.fillWidth: true }
559− Repeater {
560− model: 1 // TODO: will be a workspacemodel in the future
561− Item {
562− Layout.fillHeight: true
563− Layout.preferredWidth: ((height - units.gu(6)) * root.width / root.height)
564− Image {
565− source: root.background
566− anchors {
567− left: parent.left
568− right: parent.right
569− verticalCenter: parent.verticalCenter
570− }
571− height: parent.height * 0.75
572−
573− // FIXME: This is temporary until we can have multiple Items per surface
574− ShaderEffect {
575− anchors.fill: parent
576−
577− property var source: ShaderEffectSource {
578− id: shaderEffectSource
579− live: false
580− sourceItem: appContainer
581− Connections { target: root; onUpdateWorkspaces: shaderEffectSource.scheduleUpdate() }
582− }
583−
584− fragmentShader: "
585− varying highp vec2 qt_TexCoord0;
586− uniform sampler2D source;
587− void main(void)
588− {
589− highp vec4 sourceColor = texture2D(source, qt_TexCoord0);
590− gl_FragColor = sourceColor;
591− }"
592− }
593− }
594−
595− // TODO: This is the bar for the currently selected workspace
596− // Enable this once the workspace stuff is implemented
597−// Rectangle {
598−// anchors { left: parent.left; right: parent.right; bottom: parent.bottom }
599−// height: units.dp(2)
600−// color: UbuntuColors.orange
601−// visible: index == 0 // TODO: should be active workspace index
602−// }
603− }
604−
605− }
606− // TODO: This is the "new workspace" button. Enable this once workspaces are implemented
607−// Item {
608−// Layout.fillHeight: true
609−// Layout.preferredWidth: ((height - units.gu(6)) * root.width / root.height)
610−// Rectangle {
611−// anchors {
612−// left: parent.left
613−// right: parent.right
614−// verticalCenter: parent.verticalCenter
615−// }
616−// height: parent.height * 0.75
617−// color: "#22ffffff"
618−
619−// Label {
620−// anchors.centerIn: parent
621−// font.pixelSize: parent.height / 2
622−// text: "+"
623−// }
624−// }
625−// }
626− Item { Layout.fillWidth: true }
627− }
628253 }
629254
630− Label {
631− id: currentSelectedLabel
632− anchors { bottom: parent.bottom; bottomMargin: root.height * 0.625; horizontalCenter: parent.horizontalCenter }
633− text: appRepeater.highlightedIndex >= 0 ? ApplicationManager.get(appRepeater.highlightedIndex).name : ""
255+ Rectangle {
256+ id: spreadBackground
257+ anchors.fill: parent
258+ color: "#55000000"
634259 visible: false
635− fontSize: "large"
636260 }
637261
638− states: [
639− State {
640− name: "altTab"; when: root.altTabPressed
641− PropertyChanges { target: workspaceSelector; visible: true }
642− PropertyChanges { target: spreadFlickable; enabled: spreadFlickable.contentWidth > spreadFlickable.minContentWidth }
643− PropertyChanges { target: currentSelectedLabel; visible: true }
644− PropertyChanges { target: spreadBackground; visible: true }
645− PropertyChanges { target: hoverMouseArea; enabled: true }
646− }
647− ]
648− signal updateWorkspaces();
649− property bool workspacesUpdated: false
650− transitions: [
651− Transition {
652− from: "*"
653− to: "altTab"
654− SequentialAnimation {
655− PropertyAction { target: hoverMouseArea; property: "progressiveScrollingEnabled"; value: false }
656− PropertyAction { target: appRepeater; property: "highlightedIndex"; value: Math.min(ApplicationManager.count - 1, 1) }
657− PauseAnimation { duration: 50 }
658− PropertyAction { target: workspaceSelector; property: "visible" }
659− ScriptAction { script: root.updateWorkspaces() }
660− // FIXME: Updating of shaderEffectSource take a bit of time. This is temporary until we can paint multiple items per surface
661− PauseAnimation { duration: 10 }
662− PropertyAction { target: root; property: "workspacesUpdated"; value: true }
663− PropertyAction { target: spreadFlickable; property: "visible" }
664− PropertyAction { targets: [currentSelectedLabel,spreadBackground]; property: "visible" }
665− PropertyAction { target: spreadFlickable; property: "contentX"; value: 0 }
666− }
667− },
668− Transition {
669− from: "*"
670− to: "*"
671− PropertyAnimation { property: "opacity" }
672− PropertyAction { target: root; property: "workspacesUpdated"; value: false }
673− ScriptAction { script: { appContainer.focusSelected() } }
674− PropertyAction { target: appRepeater; property: "highlightedIndex"; value: -1 }
675− }
676−
677− ]
678−
679262 MouseArea {
680− id: rightEdgePushArea
681− anchors {
682− top: parent.top
683− right: parent.right
684− bottom: parent.bottom
685− }
686− // TODO: Make this a push to edge thing like the launcher when we can,
687− // for now, yes, we want 1 pixel, regardless of the scaling
688− width: 1
689− hoverEnabled: true
690− onContainsMouseChanged: {
691− if (containsMouse) {
692− root.state = "altTab";
693− }
694− }
263+ id: eventEater
264+ anchors.fill: parent
265+ visible: spreadBackground.visible
266+ enabled: visible
267+ }
268+
269+ DesktopSpread {
270+ id: spread
271+ objectName: "spread"
272+ anchors.fill: parent
273+ workspace: appContainer
274+ focus: state == "altTab"
695275 }
696276}
qml/Stages/SessionContainer.qml+3
@@ -20,11 +20,14 @@ import "Animations"
2020FocusScope {
2121 id: root
2222 objectName: "sessionContainer"
23+ implicitWidth: _surfaceContainer.implicitWidth
24+ implicitHeight: _surfaceContainer.implicitHeight
2325 property QtObject session
2426 readonly property var childSessions: session ? session.childSessions : null
2527 readonly property alias surface: _surfaceContainer.surface
2628 property alias interactive: _surfaceContainer.interactive
2729 property alias surfaceOrientationAngle: _surfaceContainer.surfaceOrientationAngle
30+ property alias resizeSurface: _surfaceContainer.resizeSurface
2831
2932 readonly property alias surfaceContainer: _surfaceContainer
3033 SurfaceContainer {
qml/Stages/SurfaceContainer.qml+3−2
@@ -28,6 +28,7 @@ FocusScope {
2828 property bool hadSurface: false
2929 property bool interactive
3030 property int surfaceOrientationAngle: 0
31+ property bool resizeSurface: true
3132
3233 onSurfaceChanged: {
3334 if (surface) {
@@ -52,8 +53,8 @@ FocusScope {
5253
5354 consumesInput: true
5455
55− surfaceWidth: width
56− surfaceHeight: height
56+ surfaceWidth: root.resizeSurface ? width : -1
57+ surfaceHeight: root.resizeSurface ? height : -1
5758
5859 anchors.fill: root
5960 enabled: root.interactive
qml/Stages/WindowMoveResizeArea.qml+1−1
@@ -104,6 +104,6 @@ MouseArea {
104104 }
105105
106106 Component.onDestruction: {
107− windowStateStorage.saveGeometry(root.windowId, Qt.rect(target.x, target.y, target.windowWidth, target.windowHeight))
107+ windowStateStorage.saveGeometry(root.windowId, Qt.rect(target.x, target.y, target.width, target.height))
108108 }
109109}
tests/mocks/Unity/Application/MirSurface.cpp+12−5
@@ -36,12 +36,12 @@ MirSurface::MirSurface(const QString& name,
3636 , m_width(-1)
3737 , m_height(-1)
3838{
39− qDebug() << "MirSurface::MirSurface() " << name;
39+// qDebug() << "MirSurface::MirSurface() " << name;
4040}
4141
4242MirSurface::~MirSurface()
4343{
44− qDebug() << "MirSurface::~MirSurface() " << name();
44+// qDebug() << "MirSurface::~MirSurface() " << name();
4545}
4646
4747QString MirSurface::name() const
@@ -75,7 +75,7 @@ bool MirSurface::live() const
7575
7676void MirSurface::setLive(bool live)
7777{
78− qDebug().nospace() << "MirSurface::setLive("<<live<<") " << name();
78+// qDebug().nospace() << "MirSurface::setLive("<<live<<") " << name();
7979 if (live == m_live)
8080 return;
8181
@@ -123,13 +123,13 @@ void MirSurface::setOrientationAngle(Mir::OrientationAngle angle)
123123void MirSurface::incrementViewCount()
124124{
125125 ++m_viewCount;
126− qDebug().nospace() << "MirSurface::incrementViewCount() viewCount(after)=" << m_viewCount << " " << name();
126+// qDebug().nospace() << "MirSurface::incrementViewCount() viewCount(after)=" << m_viewCount << " " << name();
127127}
128128
129129void MirSurface::decrementViewCount()
130130{
131131 --m_viewCount;
132− qDebug().nospace() << "MirSurface::decrementViewCount() viewCount(after)=" << m_viewCount << " " << name();
132+// qDebug().nospace() << "MirSurface::decrementViewCount() viewCount(after)=" << m_viewCount << " " << name();
133133
134134 Q_ASSERT(m_viewCount >= 0);
135135
@@ -170,13 +170,20 @@ int MirSurface::height() const
170170
171171void MirSurface::resize(int width, int height)
172172{
173+ bool changed = false;
173174 if (width != m_width) {
174175 m_width = width;
175176 Q_EMIT widthChanged();
177+ changed = true;
176178 }
177179
178180 if (m_height != height) {
179181 m_height = height;
180182 Q_EMIT heightChanged();
183+ changed = true;
184+ }
185+
186+ if (changed) {
187+ Q_EMIT sizeChanged(QSize(width, height));
181188 }
182189}
tests/mocks/Unity/Application/MirSurfaceItem.cpp+8−8
@@ -38,7 +38,7 @@ MirSurfaceItem::MirSurfaceItem(QQuickItem *parent)
3838 , m_touchPressCount(0)
3939 , m_touchReleaseCount(0)
4040{
41− qDebug() << "MirSurfaceItem::MirSurfaceItem() " << (void*)(this) << name();
41+// qDebug() << "MirSurfaceItem::MirSurfaceItem() " << (void*)(this) << name();
4242 setAcceptedMouseButtons(Qt::LeftButton | Qt::MiddleButton | Qt::RightButton |
4343 Qt::ExtraButton1 | Qt::ExtraButton2 | Qt::ExtraButton3 | Qt::ExtraButton4 |
4444 Qt::ExtraButton5 | Qt::ExtraButton6 | Qt::ExtraButton7 | Qt::ExtraButton8 |
@@ -48,7 +48,7 @@ MirSurfaceItem::MirSurfaceItem(QQuickItem *parent)
4848
4949MirSurfaceItem::~MirSurfaceItem()
5050{
51− qDebug() << "MirSurfaceItem::~MirSurfaceItem() " << (void*)(this) << name();
51+// qDebug() << "MirSurfaceItem::~MirSurfaceItem() " << (void*)(this) << name();
5252 setSurface(nullptr);
5353}
5454
@@ -136,7 +136,7 @@ void MirSurfaceItem::onComponentStatusChanged(QQmlComponent::Status status)
136136
137137void MirSurfaceItem::createQmlContentItem()
138138{
139− qDebug() << "MirSurfaceItem::createQmlContentItem()";
139+// qDebug() << "MirSurfaceItem::createQmlContentItem()";
140140
141141 m_qmlItem = qobject_cast<QQuickItem*>(m_qmlContentComponent->create());
142142 m_qmlItem->setParentItem(this);
@@ -178,9 +178,9 @@ void MirSurfaceItem::mouseReleaseEvent(QMouseEvent * event)
178178
179179void MirSurfaceItem::setSurface(MirSurfaceInterface* surface)
180180{
181− qDebug().nospace() << "MirSurfaceItem::setSurface() this=" << (void*)(this)
182− << " name=" << name()
183− << " surface=" << surface;
181+// qDebug().nospace() << "MirSurfaceItem::setSurface() this=" << (void*)(this)
182+// << " name=" << name()
183+// << " surface=" << surface;
184184
185185 if (m_qmlSurface == surface) {
186186 return;
@@ -268,7 +268,7 @@ int MirSurfaceItem::surfaceWidth() const
268268
269269void MirSurfaceItem::setSurfaceWidth(int value)
270270{
271− if (m_surfaceWidth != value) {
271+ if (value != -1 && m_surfaceWidth != value) {
272272 m_surfaceWidth = value;
273273 Q_EMIT surfaceWidthChanged(m_surfaceWidth);
274274 updateSurfaceSize();
@@ -282,7 +282,7 @@ int MirSurfaceItem::surfaceHeight() const
282282
283283void MirSurfaceItem::setSurfaceHeight(int value)
284284{
285− if (m_surfaceHeight != value) {
285+ if (value != -1 && m_surfaceHeight != value) {
286286 m_surfaceHeight = value;
287287 Q_EMIT surfaceHeightChanged(m_surfaceHeight);
288288 updateSurfaceSize();
tests/mocks/Unity/Application/Session.cpp+4−4
@@ -35,14 +35,14 @@ Session::Session(const QString &name,
3535 , m_parentSession(nullptr)
3636 , m_children(new SessionModel(this))
3737{
38− qDebug() << "Session::Session() " << this->name();
38+// qDebug() << "Session::Session() " << this->name();
3939
4040 QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
4141}
4242
4343Session::~Session()
4444{
45− qDebug() << "Session::~Session() " << name();
45+// qDebug() << "Session::~Session() " << name();
4646
4747 QList<Session*> children(m_children->list());
4848 for (Session* child : children) {
@@ -67,7 +67,7 @@ Session::~Session()
6767
6868void Session::release()
6969{
70− qDebug() << "Session::release " << name();
70+// qDebug() << "Session::release " << name();
7171 deleteLater();
7272}
7373
@@ -82,7 +82,7 @@ void Session::setApplication(ApplicationInfo* application)
8282
8383void Session::setSurface(MirSurface* surface)
8484{
85− qDebug() << "Session::setSurface - session=" << name() << "surface=" << surface;
85+// qDebug() << "Session::setSurface - session=" << name() << "surface=" << surface;
8686 if (m_surface == surface)
8787 return;
8888
tests/qmltests/tst_Shell.qml+131−43
@@ -322,8 +322,9 @@ Rectangle {
322322
323323 function loadDesktopShellWithApps() {
324324 loadShell("desktop");
325+ waitForRendering(shell)
325326 shell.usageScenario = "desktop"
326− waitForRendering(root)
327+ waitForRendering(shell)
327328 var app1 = ApplicationManager.startApplication("dialer-app")
328329 var app2 = ApplicationManager.startApplication("webbrowser-app")
329330 var app3 = ApplicationManager.startApplication("camera-app")
@@ -1407,17 +1408,18 @@ Rectangle {
14071408 keyClick(Qt.Key_Tab, Qt.ControlModifier)
14081409 tryCompare(app2.session.surface, "activeFocus", true)
14091410
1411+ var desktopSpread = findChild(shell, "spread")
14101412
1411− tryCompare(desktopStage, "state", "")
1413+ tryCompare(desktopSpread, "state", "")
14121414
14131415 // Just press Alt, make sure the spread comes up
14141416 keyPress(Qt.Key_Control);
14151417 keyClick(Qt.Key_Tab);
1416− tryCompare(desktopStage, "state", "altTab")
1418+ tryCompare(desktopSpread, "state", "altTab")
14171419
14181420 // Release control, check if spread disappears
14191421 keyRelease(Qt.Key_Control)
1420− tryCompare(desktopStage, "state", "")
1422+ tryCompare(desktopSpread, "state", "")
14211423
14221424 // Focus should have switched back now
14231425 tryCompare(app3.session.surface, "activeFocus", true)
@@ -1429,41 +1431,44 @@ Rectangle {
14291431 var desktopStage = findChild(shell, "stage");
14301432 verify(desktopStage !== null)
14311433
1432− var appContainer = findInvisibleChild(shell, "appContainer")
1433− verify(appContainer !== null)
1434+ var desktopSpread = findChild(shell, "spread");
1435+ verify(desktopSpread !== null)
14341436
1435− var appRepeater = findInvisibleChild(shell, "appRepeater")
1436− verify(appRepeater !== null)
1437+ var spreadContainer = findInvisibleChild(shell, "spreadContainer")
1438+ verify(spreadContainer !== null)
1439+
1440+ var spreadRepeater = findInvisibleChild(shell, "spreadRepeater")
1441+ verify(spreadRepeater !== null)
14371442
14381443 // remember the focused appId
14391444 var focused = ApplicationManager.get(ApplicationManager.findApplication(ApplicationManager.focusedApplicationId));
14401445
1441− tryCompare(desktopStage, "state", "")
1446+ tryCompare(desktopSpread, "state", "")
14421447
14431448 // Just press Alt, make sure the spread comes up
14441449 keyPress(Qt.Key_Control);
14451450 keyClick(Qt.Key_Tab);
1446− tryCompare(desktopStage, "state", "altTab")
1447− tryCompare(appRepeater, "highlightedIndex", 1)
1451+ tryCompare(desktopSpread, "state", "altTab")
1452+ tryCompare(spreadRepeater, "highlightedIndex", 1)
14481453 waitForRendering(shell)
14491454
14501455 // Now press and hold Tab, make sure the highlight moves all the way but stops at the last one
14511456 // We can't simulate a pressed key with keyPress() currently, so let's inject the events
14521457 // at API level. Jump for 10 times, verify that it's still at the last one and didn't wrap around.
14531458 for (var i = 0; i < 10; i++) {
1454− appContainer.selectNext(true); // true == isAutoRepeat
1459+ desktopSpread.selectNext(true); // true == isAutoRepeat
14551460 wait(0); // Trigger the event loop to make sure all the things happen
14561461 }
1457− tryCompare(appRepeater, "highlightedIndex", 6)
1462+ tryCompare(spreadRepeater, "highlightedIndex", 6)
14581463
14591464 // Now release it once, and verify that it does wrap around with an additional Tab press
14601465 keyRelease(Qt.Key_Tab);
14611466 keyClick(Qt.Key_Tab);
1462− tryCompare(appRepeater, "highlightedIndex", 0)
1467+ tryCompare(spreadRepeater, "highlightedIndex", 0)
14631468
14641469 // Release control, check if spread disappears
14651470 keyRelease(Qt.Key_Control)
1466− tryCompare(desktopStage, "state", "")
1471+ tryCompare(desktopSpread, "state", "")
14671472
14681473 // Make sure that after wrapping around once, we have the same one focused as at the beginning
14691474 tryCompare(focused.session.surface, "activeFocus", true)
@@ -1472,30 +1477,30 @@ Rectangle {
14721477 function test_altBackTabNavigation() {
14731478 loadDesktopShellWithApps();
14741479
1475− var appRepeater = findInvisibleChild(shell, "appRepeater");
1476− verify(appRepeater !== null);
1480+ var spreadRepeater = findInvisibleChild(shell, "spreadRepeater");
1481+ verify(spreadRepeater !== null);
14771482
14781483 keyPress(Qt.Key_Control)
14791484 keyClick(Qt.Key_Tab);
1480− tryCompare(appRepeater, "highlightedIndex", 1);
1485+ tryCompare(spreadRepeater, "highlightedIndex", 1);
14811486
14821487 keyClick(Qt.Key_Tab);
1483− tryCompare(appRepeater, "highlightedIndex", 2);
1488+ tryCompare(spreadRepeater, "highlightedIndex", 2);
14841489
14851490 keyClick(Qt.Key_Tab);
1486− tryCompare(appRepeater, "highlightedIndex", 3);
1491+ tryCompare(spreadRepeater, "highlightedIndex", 3);
14871492
14881493 keyClick(Qt.Key_Tab);
1489− tryCompare(appRepeater, "highlightedIndex", 4);
1494+ tryCompare(spreadRepeater, "highlightedIndex", 4);
14901495
14911496 keyClick(Qt.Key_Backtab);
1492− tryCompare(appRepeater, "highlightedIndex", 3);
1497+ tryCompare(spreadRepeater, "highlightedIndex", 3);
14931498
14941499 keyClick(Qt.Key_Backtab);
1495− tryCompare(appRepeater, "highlightedIndex", 2);
1500+ tryCompare(spreadRepeater, "highlightedIndex", 2);
14961501
14971502 keyClick(Qt.Key_Backtab);
1498− tryCompare(appRepeater, "highlightedIndex", 1);
1503+ tryCompare(spreadRepeater, "highlightedIndex", 1);
14991504
15001505 keyRelease(Qt.Key_Control);
15011506 }
@@ -1503,20 +1508,20 @@ Rectangle {
15031508 function test_highlightFollowsMouse() {
15041509 loadDesktopShellWithApps()
15051510
1506− var appRepeater = findInvisibleChild(shell, "appRepeater");
1507− verify(appRepeater !== null);
1511+ var spreadRepeater = findInvisibleChild(shell, "spreadRepeater");
1512+ verify(spreadRepeater !== null);
15081513
15091514 keyPress(Qt.Key_Control)
15101515 keyClick(Qt.Key_Tab);
15111516
1512− tryCompare(appRepeater, "highlightedIndex", 1);
1517+ tryCompare(spreadRepeater, "highlightedIndex", 1);
15131518
15141519 var x = 0;
15151520 var y = shell.height * .75;
15161521 mouseMove(shell, x, y)
15171522
15181523 for (var i = 0; i < 7; i++) {
1519− while (appRepeater.highlightedIndex != i && x <= 4000) {
1524+ while (spreadRepeater.highlightedIndex != i && x <= 4000) {
15201525 x+=10;
15211526 mouseMove(shell, x, y)
15221527 wait(0); // spin the loop so bindings get evaluated
@@ -1531,8 +1536,8 @@ Rectangle {
15311536 function test_closeFromSpread() {
15321537 loadDesktopShellWithApps()
15331538
1534− var appRepeater = findInvisibleChild(shell, "appRepeater");
1535− verify(appRepeater !== null);
1539+ var spreadRepeater = findInvisibleChild(shell, "spreadRepeater");
1540+ verify(spreadRepeater !== null);
15361541
15371542 keyPress(Qt.Key_Control)
15381543 keyClick(Qt.Key_Tab);
@@ -1540,14 +1545,14 @@ Rectangle {
15401545 appRemovedSpy.clear();
15411546
15421547 var closedAppId = ApplicationManager.get(2).appId;
1543− var appDelegate2 = appRepeater.itemAt(2);
1544− var closeMouseArea = findChild(appDelegate2, "closeMouseArea");
1548+ var spreadDelegate2 = spreadRepeater.itemAt(2);
1549+ var closeMouseArea = findChild(spreadDelegate2, "closeMouseArea");
15451550
15461551 // Move the mosue over tile 2 and verify the close button becomes visible
15471552 var x = 0;
15481553 var y = shell.height * .5;
15491554 mouseMove(shell, x, y)
1550− while (appRepeater.highlightedIndex !== 2 && x <= 4000) {
1555+ while (spreadRepeater.highlightedIndex !== 2 && x <= 4000) {
15511556 x+=10;
15521557 mouseMove(shell, x, y)
15531558 wait(0); // spin the loop so bindings get evaluated
@@ -1574,32 +1579,35 @@ Rectangle {
15741579 function test_selectFromSpreadWithMouse(data) {
15751580 loadDesktopShellWithApps()
15761581
1577− var appRepeater = findInvisibleChild(shell, "appRepeater");
1578− verify(appRepeater !== null);
1582+ var stage = findChild(shell, "stage");
1583+ var spread = findChild(stage, "spread");
1584+ waitForRendering(spread)
1585+
1586+ var spreadRepeater = findInvisibleChild(shell, "spreadRepeater");
1587+ verify(spreadRepeater !== null);
15791588
15801589 keyPress(Qt.Key_Control)
15811590 keyClick(Qt.Key_Tab);
15821591
15831592 var focusAppId = ApplicationManager.get(2).appId;
1584− var appDelegate2 = appRepeater.itemAt(2);
1585− var decoratedWindow = findChild(appDelegate2, "decoratedWindow");
1586− var stage = findChild(shell, "stage");
1593+ var spreadDelegate2 = spreadRepeater.itemAt(2);
1594+ var clippedSpreadDelegate = findChild(spreadDelegate2, "clippedSpreadDelegate");
15871595
1588− tryCompare(stage, "state", "altTab");
1596+ tryCompare(spread, "state", "altTab");
15891597
15901598 // Move the mouse over tile 2 and verify the highlight becomes visible
15911599 var x = 0;
15921600 var y = shell.height * (data.tileInfo ? .95 : 0.5)
15931601 mouseMove(shell, x, y)
1594− while (appRepeater.highlightedIndex !== 2 && x <= 4000) {
1602+ while (spreadRepeater.highlightedIndex !== 2 && x <= 4000) {
15951603 x+=10;
15961604 mouseMove(shell, x, y)
15971605 wait(0); // spin the loop so bindings get evaluated
15981606 }
1599− tryCompare(decoratedWindow, "highlightShown", true);
1607+ tryCompare(clippedSpreadDelegate, "highlightShown", true);
16001608
16011609 // Click the tile
1602− mouseClick(decoratedWindow, decoratedWindow.width / 2, decoratedWindow.height / 2)
1610+ mouseClick(clippedSpreadDelegate, clippedSpreadDelegate.width / 2, clippedSpreadDelegate.height / 2)
16031611
16041612 // Verify that we left the spread and app2 is the focused one now
16051613 tryCompare(stage, "state", "");
@@ -1618,7 +1626,6 @@ Rectangle {
16181626 keyClick(Qt.Key_Tab);
16191627
16201628 var spreadFlickable = findChild(shell, "spreadFlickable")
1621− print("have flickable", spreadFlickable, spreadFlickable.contentX)
16221629
16231630 compare(spreadFlickable.contentX, 0);
16241631
@@ -1643,5 +1650,86 @@ Rectangle {
16431650
16441651 keyRelease(Qt.Key_Control);
16451652 }
1653+
1654+ // This makes sure the hoverMouseArea is set to invisible AND disabled
1655+ // when not needed. Otherwise it'll eat mouse hover events for the rest of the shell/apps
1656+ function test_hoverMouseAreaDisabledAndInvisible() {
1657+ loadDesktopShellWithApps()
1658+
1659+ var hoverMouseArea = findChild(shell, "hoverMouseArea");
1660+ tryCompare(hoverMouseArea, "enabled", false)
1661+ tryCompare(hoverMouseArea, "visible", false)
1662+
1663+ keyPress(Qt.Key_Control)
1664+ keyClick(Qt.Key_Tab);
1665+
1666+ tryCompare(hoverMouseArea, "enabled", true)
1667+ tryCompare(hoverMouseArea, "visible", true)
1668+
1669+ keyRelease(Qt.Key_Control)
1670+
1671+ tryCompare(hoverMouseArea, "enabled", false)
1672+ tryCompare(hoverMouseArea, "visible", false)
1673+ }
1674+
1675+ function test_workspacePreviewsHighlightedApp() {
1676+ loadDesktopShellWithApps()
1677+
1678+ var targetZ = ApplicationManager.count + 1;
1679+
1680+ var spreadRepeater = findInvisibleChild(shell, "spreadRepeater");
1681+ verify(spreadRepeater !== null);
1682+
1683+ var appRepeater = findInvisibleChild(shell, "appRepeater");
1684+ verify(appRepeater !== null);
1685+
1686+ keyPress(Qt.Key_Control)
1687+ keyClick(Qt.Key_Tab);
1688+
1689+ tryCompare(spreadRepeater, "highlightedIndex", 1);
1690+ tryCompare(appRepeater.itemAt(1), "z", targetZ)
1691+
1692+ var x = 0;
1693+ var y = shell.height * .75;
1694+ mouseMove(shell, x, y)
1695+
1696+ for (var i = 0; i < 7; i++) {
1697+ while (spreadRepeater.highlightedIndex != i && x <= 4000) {
1698+ tryCompare(appRepeater.itemAt(spreadRepeater.highlightedIndex), "z", targetZ)
1699+ x+=10;
1700+ mouseMove(shell, x, y)
1701+ wait(0); // spin the loop so bindings get evaluated
1702+ }
1703+ }
1704+
1705+ verify(y < 4000);
1706+
1707+ keyRelease(Qt.Key_Control);
1708+ }
1709+
1710+ function test_focusAppFromLauncherExitsSpread() {
1711+ loadDesktopShellWithApps()
1712+
1713+ var desktopSpread = findChild(shell, "spread");
1714+ var launcher = findChild(shell, "launcher");
1715+ var bfb = findChild(launcher, "buttonShowDashHome");
1716+
1717+ keyPress(Qt.Key_Control)
1718+ keyClick(Qt.Key_Tab);
1719+
1720+
1721+ tryCompare(desktopSpread, "state", "altTab")
1722+
1723+ mouseMove(shell, 0, 0);
1724+ tryCompare(launcher, "state", "visibleTemporary")
1725+
1726+ mouseClick(bfb, bfb.width / 2, bfb.height / 2)
1727+ tryCompare(launcher, "state", "")
1728+ tryCompare(desktopSpread, "state", "")
1729+
1730+ tryCompare(ApplicationManager, "focusedApplicationId", "unity8-dash")
1731+
1732+ keyRelease(Qt.Key_Control);
1733+ }
16461734 }
16471735}