@@ -17,13 +17,15 @@
1717 */
1818
1919import QtQuick 2.3
20+import QtQuick.Layouts 1.1
2021import Ubuntu.Components 1.1
2122import Unity.Application 0.1
2223import "../Components/PanelState"
24+import Utils 0.1
25+import Ubuntu.Gestures 0.1
2326
2427Rectangle {
2528 id: root
26−
2729 anchors.fill: parent
2830
2931 // Controls to be set from outside
@@ -50,6 +52,7 @@ Rectangle {
5052 readonly property bool orientationChangesEnabled: false
5153
5254 property alias background: wallpaper.source
55+ property bool altTabPressed: false
5356
5457 CrossFadeImage {
5558 id: wallpaper
@@ -61,15 +64,18 @@ Rectangle {
6164 Connections {
6265 target: ApplicationManager
6366 onApplicationAdded: {
64− ApplicationManager.requestFocusApplication(ApplicationManager.get(ApplicationManager.count-1).appId)
67+ if (root.state == "altTab") {
68+ root.state = "";
69+ }
70+
71+ ApplicationManager.requestFocusApplication(appId)
6572 }
6673
6774 onFocusRequested: {
6875 var appIndex = priv.indexOf(appId);
6976 var appDelegate = appRepeater.itemAt(appIndex);
70− if (appDelegate.state === "minimized") {
71− appDelegate.state = "normal"
72− }
77+ appDelegate.minimized = false;
78+ appDelegate.focus = true;
7379 ApplicationManager.focusApplication(appId);
7480 }
7581 }
@@ -104,8 +110,8 @@ Rectangle {
104110 onClose: {
105111 ApplicationManager.stopApplication(ApplicationManager.focusedApplicationId)
106112 }
107− onMinimize: appRepeater.itemAt(0).state = "minimized"
108− onMaximize: appRepeater.itemAt(0).state = "normal"
113+ onMinimize: appRepeater.itemAt(0).minimize();
114+ onMaximize: appRepeater.itemAt(0).unmaximize();
109115 }
110116
111117 Binding {
@@ -114,67 +120,502 @@ Rectangle {
114120 value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.state === "maximized"
115121 }
116122
117− Repeater {
118− id: appRepeater
119− model: ApplicationManager
123+ Rectangle {
124+ id: spreadBackground
125+ anchors.fill: parent
126+ color: "#55000000"
127+ visible: false
128+ }
120129
121− delegate: FocusScope {
122− id: appDelegate
123− z: ApplicationManager.count - index
124− y: units.gu(3)
125− width: units.gu(60)
126− height: units.gu(50)
130+ FocusScope {
131+ id: appContainer
132+ objectName: "appContainer"
133+ anchors.fill: parent
127134
128− onFocusChanged: {
129− if (focus) {
130− ApplicationManager.requestFocusApplication(model.appId);
131− }
135+ Keys.onPressed: {
136+ switch (event.key) {
137+ case Qt.Key_Left:
138+ case Qt.Key_Backtab:
139+ selectPrevious(event.isAutoRepeat)
140+ break;
141+ case Qt.Key_Right:
142+ case Qt.Key_Tab:
143+ selectNext(event.isAutoRepeat)
144+ break;
145+ case Qt.Key_Escape:
146+ appRepeater.highlightedIndex = -1
147+ case Qt.Key_Enter:
148+ case Qt.Key_Return:
149+ case Qt.Key_Space:
150+ root.state = ""
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+ }
165+
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+ appRepeater.itemAt(appRepeater.highlightedIndex).focus = true;
132182 }
183+ }
184+
185+ Repeater {
186+ id: appRepeater
187+ model: ApplicationManager
188+ objectName: "appRepeater"
189+
190+ property int highlightedIndex: -1
191+ property int closingIndex: -1
192+
193+ delegate: FocusScope {
194+ id: appDelegate
195+ z: ApplicationManager.count - index
196+ y: units.gu(3)
197+ width: units.gu(60)
198+ height: units.gu(50)
199+
200+ readonly property int minWidth: units.gu(10)
201+ readonly property int minHeight: units.gu(10)
202+
203+ property bool maximized: false
204+ property bool minimized: false
205+
206+ onFocusChanged: {
207+ if (focus && ApplicationManager.focusedApplicationId !== model.appId) {
208+ ApplicationManager.requestFocusApplication(model.appId);
209+ decoratedWindow.forceActiveFocus();
210+ }
211+ }
212+ Component.onCompleted: {
213+ if (ApplicationManager.focusedApplicationId == model.appId) {
214+ decoratedWindow.forceActiveFocus();
215+ }
216+ }
217+
218+ function maximize() {
219+ minimized = false;
220+ maximized = true;
221+ }
222+ function minimize() {
223+ maximized = false;
224+ minimized = true;
225+ }
226+ function unmaximize() {
227+ minimized = false;
228+ maximized = false;
229+ }
230+
231+ Behavior on x {
232+ id: closeBehavior
233+ enabled: appRepeater.closingIndex >= 0
234+ UbuntuNumberAnimation {
235+ onRunningChanged: if (!running) appRepeater.closingIndex = -1
236+ }
237+ }
238+
239+ states: [
240+ State {
241+ name: "normal"; when: !appDelegate.maximized && !appDelegate.minimized && root.state !== "altTab"
242+ },
243+ State {
244+ name: "maximized"; when: appDelegate.maximized && (root.state !== "altTab" || (root.state == "altTab" && !root.workspacesUpdated))
245+ PropertyChanges { target: appDelegate; x: 0; y: 0; width: root.width; height: root.height }
246+ },
247+ State {
248+ name: "minimized"; when: appDelegate.minimized && (root.state !== "altTab" || (root.state == "altTab" && !root.workspacesUpdated))
249+ PropertyChanges { target: appDelegate; x: -appDelegate.width / 2; scale: units.gu(5) / appDelegate.width; opacity: 0 }
250+ },
251+ State {
252+ name: "altTab"; when: root.state == "altTab" && root.workspacesUpdated
253+ PropertyChanges {
254+ target: appDelegate
255+ x: spreadMaths.animatedX
256+ y: spreadMaths.animatedY + (appDelegate.height - decoratedWindow.height)
257+ angle: spreadMaths.animatedAngle
258+ itemScale: spreadMaths.scale
259+ itemScaleOriginY: decoratedWindow.height / 2;
260+ z: index
261+ visible: spreadMaths.itemVisible
262+ }
263+ PropertyChanges {
264+ target: decoratedWindow
265+ decorationShown: false
266+ highlightShown: index == appRepeater.highlightedIndex
267+ state: "transformed"
268+ width: spreadMaths.spreadHeight
269+ height: spreadMaths.spreadHeight
270+ shadowOpacity: spreadMaths.shadowOpacity
271+ }
272+ PropertyChanges {
273+ target: tileInfo
274+ visible: true
275+ opacity: spreadMaths.tileInfoOpacity
276+ }
277+ PropertyChanges {
278+ target: spreadSelectArea
279+ enabled: true
280+ }
281+ PropertyChanges {
282+ target: windowMoveResizeArea
283+ enabled: false
284+ }
285+ }
286+ ]
287+ transitions: [
288+ Transition {
289+ from: "maximized,minimized,normal,"
290+ to: "maximized,minimized,normal,"
291+ PropertyAnimation { target: appDelegate; properties: "x,y,opacity,width,height,scale" }
292+ }
293+ ]
294+ property real angle: 0
295+ property real itemScale: 1
296+ property int itemScaleOriginX: 0
297+ property int itemScaleOriginY: 0
298+
299+ SpreadMaths {
300+ id: spreadMaths
301+ flickable: spreadFlickable
302+ itemIndex: index
303+ totalItems: Math.max(6, ApplicationManager.count)
304+ sceneHeight: root.height
305+ itemHeight: appDelegate.height
306+ }
307+
308+ WindowMoveResizeArea {
309+ id: windowMoveResizeArea
310+ target: appDelegate
311+ minWidth: appDelegate.minWidth
312+ minHeight: appDelegate.minHeight
313+ resizeHandleWidth: units.gu(2)
314+ windowId: model.appId // FIXME: Change this to point to windowId once we have such a thing
315+
316+ onPressed: appDelegate.focus = true;
317+ }
318+
319+ DecoratedWindow {
320+ id: decoratedWindow
321+ objectName: "decoratedWindow"
322+ anchors.left: appDelegate.left
323+ anchors.top: appDelegate.top
324+ windowWidth: appDelegate.width
325+ windowHeight: appDelegate.height
326+ application: ApplicationManager.get(index)
327+ active: ApplicationManager.focusedApplicationId === model.appId
328+ focus: false
329+
330+ onClose: ApplicationManager.stopApplication(model.appId)
331+ onMaximize: appDelegate.maximize()
332+ onMinimize: appDelegate.minimize()
333+
334+ transform: [
335+ Scale {
336+ origin.x: itemScaleOriginX
337+ origin.y: itemScaleOriginY
338+ xScale: itemScale
339+ yScale: itemScale
340+ },
341+ Rotation {
342+ origin { x: 0; y: (decoratedWindow.height - (decoratedWindow.height * itemScale / 2)) }
343+ axis { x: 0; y: 1; z: 0 }
344+ angle: appDelegate.angle
345+ }
346+ ]
347+
348+ MouseArea {
349+ id: spreadSelectArea
350+ anchors.fill: parent
351+ anchors.margins: -units.gu(2)
352+ enabled: false
353+ hoverEnabled: enabled
354+
355+ // There is a bug in MouseArea where containsMouse doesn't
356+ // return to false if the MouseArea is disabled while
357+ // containing the mouse. Let's manage the property our own.
358+ property bool upperThirdContainsMouse: false
359+ onContainsMouseChanged: evaluateContainsMouse()
360+ onMouseYChanged: evaluateContainsMouse()
361+ function evaluateContainsMouse() {
362+ if (containsMouse) {
363+ appRepeater.highlightedIndex = index
364+ }
133365
134− readonly property int minWidth: units.gu(10)
135− readonly property int minHeight: units.gu(10)
136−
137− states: [
138− State {
139− name: "normal"
140− },
141− State {
142− name: "maximized"
143− PropertyChanges { target: appDelegate; x: 0; y: 0; width: root.width; height: root.height }
144− },
145− State {
146− name: "minimized"
147− PropertyChanges { target: appDelegate; x: -appDelegate.width / 2; scale: units.gu(5) / appDelegate.width; opacity: 0 }
366+ if (containsMouse && mouseY < height / 3) {
367+ spreadSelectArea.upperThirdContainsMouse = true
368+ } else {
369+ spreadSelectArea.upperThirdContainsMouse = false;
370+ }
371+ }
372+ onEnabledChanged: {
373+ if (!enabled) {
374+ spreadSelectArea.upperThirdContainsMouse = false
375+ }
376+ }
377+
378+ onClicked: {
379+ root.state = ""
380+ }
381+ }
148382 }
149− ]
150− transitions: [
151− Transition {
152− PropertyAnimation { target: appDelegate; properties: "x,y,opacity,width,height,scale" }
383+
384+ Image {
385+ id: closeImage
386+ anchors { left: parent.left; top: parent.top; leftMargin: -height / 2; topMargin: -height / 2 + spreadMaths.closeIconOffset }
387+ source: "graphics/window-close.svg"
388+ visible: spreadSelectArea.upperThirdContainsMouse
389+ height: units.gu(1.5)
390+ width: height
391+ sourceSize.width: width
392+ sourceSize.height: height
153393 }
154− ]
155394
156− WindowMoveResizeArea {
157− target: appDelegate
158− minWidth: appDelegate.minWidth
159− minHeight: appDelegate.minHeight
160− resizeHandleWidth: units.gu(0.5)
161− windowId: model.appId // FIXME: Change this to point to windowId once we have such a thing
395+ MouseArea {
396+ id: closeMouseArea
397+ objectName: "closeMouseArea"
398+ anchors.fill: closeImage
399+ anchors.margins: -units.gu(2)
400+ enabled: spreadSelectArea.upperThirdContainsMouse
401+ onClicked: {
402+ appRepeater.closingIndex = index;
403+ ApplicationManager.stopApplication(model.appId)
404+ }
405+ }
406+
407+ MouseArea {
408+ id: tileInfo
409+ objectName: "tileInfo"
410+ anchors { left: parent.left; top: decoratedWindow.bottom; topMargin: units.gu(5) }
411+ width: units.gu(30)
412+ height: titleInfoColumn.height
413+ visible: false
414+ hoverEnabled: true
415+
416+ onContainsMouseChanged: {
417+ if (containsMouse) {
418+ appRepeater.highlightedIndex = index
419+ }
420+ }
162421
163− onPressed: appDelegate.focus = true;
422+ onClicked: {
423+ root.state = ""
424+ }
425+
426+ ColumnLayout {
427+ id: titleInfoColumn
428+ anchors { left: parent.left; top: parent.top; right: parent.right }
429+ spacing: units.gu(1)
430+
431+ UbuntuShape {
432+ Layout.preferredHeight: Math.min(units.gu(6), root.height * .05)
433+ Layout.preferredWidth: height * 8 / 7.6
434+ image: Image {
435+ anchors.fill: parent
436+ source: model.icon
437+ }
438+ }
439+ Label {
440+ Layout.fillWidth: true
441+ Layout.preferredHeight: units.gu(6)
442+ text: model.name
443+ wrapMode: Text.WordWrap
444+ maximumLineCount: 2
445+ }
446+ }
447+ }
164448 }
449+ }
450+ }
451+
452+ FloatingFlickable {
453+ id: spreadFlickable
454+ anchors.fill: parent
455+ contentWidth: Math.max(6, ApplicationManager.count) * Math.min(height / 4, width / 5)
456+ enabled: false
457+
458+ function snapTo(contentX) {
459+ snapAnimation.stop();
460+ snapAnimation.to = contentX
461+ snapAnimation.start();
462+ }
463+
464+ UbuntuNumberAnimation {
465+ id: snapAnimation
466+ target: spreadFlickable
467+ property: "contentX"
468+ }
469+ }
470+
471+ Item {
472+ id: workspaceSelector
473+ anchors {
474+ left: parent.left
475+ top: parent.top
476+ right: parent.right
477+ topMargin: units.gu(3.5) // TODO: should be root.panelHeight
478+ }
479+ height: root.height * 0.25
480+ visible: false
481+
482+ RowLayout {
483+ anchors.fill: parent
484+ spacing: units.gu(1)
485+ Item { Layout.fillWidth: true }
486+ Repeater {
487+ model: 1 // TODO: will be a workspacemodel in the future
488+ Item {
489+ Layout.fillHeight: true
490+ Layout.preferredWidth: ((height - units.gu(6)) * root.width / root.height)
491+ Image {
492+ source: root.background
493+ anchors {
494+ left: parent.left
495+ right: parent.right
496+ verticalCenter: parent.verticalCenter
497+ }
498+ height: parent.height * 0.75
499+
500+ // FIXME: This is temporary until we can have multiple Items per surface
501+ ShaderEffect {
502+ anchors.fill: parent
503+
504+ property var source: ShaderEffectSource {
505+ id: shaderEffectSource
506+ live: false
507+ sourceItem: appContainer
508+ Connections { target: root; onUpdateWorkspaces: shaderEffectSource.scheduleUpdate() }
509+ }
510+
511+ fragmentShader: "
512+ varying highp vec2 qt_TexCoord0;
513+ uniform sampler2D source;
514+ void main(void)
515+ {
516+ highp vec4 sourceColor = texture2D(source, qt_TexCoord0);
517+ gl_FragColor = sourceColor;
518+ }"
519+ }
520+ }
521+
522+ // TODO: This is the bar for the currently selected workspace
523+ // Enable this once the workspace stuff is implemented
524+// Rectangle {
525+// anchors { left: parent.left; right: parent.right; bottom: parent.bottom }
526+// height: units.dp(2)
527+// color: UbuntuColors.orange
528+// visible: index == 0 // TODO: should be active workspace index
529+// }
530+ }
165531
166− DecoratedWindow {
167− id: decoratedWindow
168− objectName: "decoratedWindow_" + appId
169− anchors.fill: parent
170− application: ApplicationManager.get(index)
171− active: ApplicationManager.focusedApplicationId === model.appId
172− focus: true
173−
174− onClose: ApplicationManager.stopApplication(model.appId)
175− onMaximize: appDelegate.state = (appDelegate.state == "maximized" ? "normal" : "maximized")
176− onMinimize: appDelegate.state = "minimized"
177532 }
533+ // TODO: This is the "new workspace" button. Enable this once workspaces are implemented
534+// Item {
535+// Layout.fillHeight: true
536+// Layout.preferredWidth: ((height - units.gu(6)) * root.width / root.height)
537+// Rectangle {
538+// anchors {
539+// left: parent.left
540+// right: parent.right
541+// verticalCenter: parent.verticalCenter
542+// }
543+// height: parent.height * 0.75
544+// color: "#22ffffff"
545+
546+// Label {
547+// anchors.centerIn: parent
548+// font.pixelSize: parent.height / 2
549+// text: "+"
550+// }
551+// }
552+// }
553+ Item { Layout.fillWidth: true }
554+ }
555+ }
556+
557+ Label {
558+ id: currentSelectedLabel
559+ anchors { bottom: parent.bottom; bottomMargin: root.height * 0.625; horizontalCenter: parent.horizontalCenter }
560+ text: appRepeater.highlightedIndex >= 0 ? ApplicationManager.get(appRepeater.highlightedIndex).name : ""
561+ visible: false
562+ fontSize: "large"
563+ }
564+
565+ states: [
566+ State {
567+ name: "altTab"; when: root.altTabPressed
568+ PropertyChanges { target: workspaceSelector; visible: true }
569+ PropertyChanges { target: spreadFlickable; enabled: true }
570+ PropertyChanges { target: currentSelectedLabel; visible: true }
571+ PropertyChanges { target: spreadBackground; visible: true }
572+ PropertyChanges { target: appContainer; focus: true }
573+ }
574+ ]
575+ signal updateWorkspaces();
576+ property bool workspacesUpdated: false
577+ transitions: [
578+ Transition {
579+ from: "*"
580+ to: "altTab"
581+ SequentialAnimation {
582+ PropertyAction { target: appRepeater; property: "highlightedIndex"; value: Math.min(ApplicationManager.count - 1, 1) }
583+ PauseAnimation { duration: 50 }
584+ PropertyAction { target: workspaceSelector; property: "visible" }
585+ ScriptAction { script: root.updateWorkspaces() }
586+ // FIXME: Updating of shaderEffectSource take a bit of time. This is temporary until we can paint multiple items per surface
587+ PauseAnimation { duration: 10 }
588+ PropertyAction { target: root; property: "workspacesUpdated"; value: true }
589+ PropertyAction { target: spreadFlickable; property: "visible" }
590+ PropertyAction { targets: [currentSelectedLabel,spreadBackground]; property: "visible" }
591+ PropertyAction { target: spreadFlickable; property: "contentX"; value: 0 }
592+ }
593+ },
594+ Transition {
595+ from: "*"
596+ to: "*"
597+ PropertyAnimation { property: "opacity" }
598+ PropertyAction { target: root; property: "workspacesUpdated"; value: false }
599+ ScriptAction { script: { appContainer.focusSelected() } }
600+ PropertyAction { target: appRepeater; property: "highlightedIndex"; value: -1 }
178601 }
602+
603+ ]
604+
605+ MouseArea {
606+ anchors {
607+ top: parent.top
608+ right: parent.right
609+ bottom: parent.bottom
610+ }
611+ // TODO: Make this a push to edge thing like the launcher when we can,
612+ // for now, yes, we want 1 pixel, regardless of the scaling
613+ width: 1
614+ hoverEnabled: true
615+ onContainsMouseChanged: {
616+ if (containsMouse) {
617+ root.state = "altTab"
618+ }
619+ }
179620 }
180621}