2 * Copyright (C) 2014-2015 Canonical, Ltd.
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.
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.
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/>.
16 * Authors: Michael Zanetti <michael.zanetti@canonical.com>
20 import QtQuick.Layouts 1.1
21 import Ubuntu.Components 1.1
22 import Unity.Application 0.1
23 import "../Components/PanelState"
25 import Ubuntu.Gestures 0.1
31 // Controls to be set from outside
32 property int dragAreaWidth // just to comply with the interface shared between stages
33 property real maximizedAppTopMargin
34 property bool interactive
35 property bool spreadEnabled // just to comply with the interface shared between stages
36 property real inverseProgress: 0 // just to comply with the interface shared between stages
37 property int shellOrientationAngle: 0
38 property int shellOrientation
39 property int shellPrimaryOrientation
40 property int nativeOrientation
41 property bool beingResized: false
42 property bool keepDashRunning: true
43 property bool suspended: false
45 // functions to be called from outside
46 function updateFocusedAppOrientation() { /* TODO */ }
47 function updateFocusedAppOrientationAnimated() { /* TODO */}
49 // To be read from outside
50 readonly property var mainApp: ApplicationManager.focusedApplicationId
51 ? ApplicationManager.findApplication(ApplicationManager.focusedApplicationId)
53 property int mainAppWindowOrientationAngle: 0
54 readonly property bool orientationChangesEnabled: false
56 property alias background: wallpaper.source
57 property bool altTabPressed: false
62 sourceSize { height: root.height; width: root.width }
63 fillMode: Image.PreserveAspectCrop
67 target: ApplicationManager
69 if (root.state == "altTab") {
73 ApplicationManager.requestFocusApplication(appId)
77 var appIndex = priv.indexOf(appId);
78 var appDelegate = appRepeater.itemAt(appIndex);
79 appDelegate.minimized = false;
80 appDelegate.focus = true;
81 ApplicationManager.focusApplication(appId);
88 readonly property string focusedAppId: ApplicationManager.focusedApplicationId
89 readonly property var focusedAppDelegate: {
90 var index = indexOf(focusedAppId);
91 return index >= 0 && index < appRepeater.count ? appRepeater.itemAt(index) : null
94 onFocusedAppDelegateChanged: {
95 if (focusedAppDelegate) {
96 focusedAppDelegate.focus = true;
100 function indexOf(appId) {
101 for (var i = 0; i < ApplicationManager.count; i++) {
102 if (ApplicationManager.get(i).appId == appId) {
113 ApplicationManager.stopApplication(ApplicationManager.focusedApplicationId)
115 onMinimize: appRepeater.itemAt(0).minimize();
116 onMaximize: appRepeater.itemAt(0).unmaximize();
121 property: "buttonsVisible"
122 value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.state === "maximized"
134 objectName: "appContainer"
141 selectPrevious(event.isAutoRepeat)
145 selectNext(event.isAutoRepeat)
148 appRepeater.highlightedIndex = -1
156 function selectNext(isAutoRepeat) {
157 if (isAutoRepeat && appRepeater.highlightedIndex >= ApplicationManager.count -1) {
158 return; // AutoRepeat is not allowed to wrap around
161 appRepeater.highlightedIndex = (appRepeater.highlightedIndex + 1) % ApplicationManager.count;
162 var newContentX = ((spreadFlickable.contentWidth) / (ApplicationManager.count + 1)) * Math.max(0, Math.min(ApplicationManager.count - 5, appRepeater.highlightedIndex - 3));
163 if (spreadFlickable.contentX < newContentX || appRepeater.highlightedIndex == 0) {
164 spreadFlickable.snapTo(newContentX)
168 function selectPrevious(isAutoRepeat) {
169 if (isAutoRepeat && appRepeater.highlightedIndex == 0) {
170 return; // AutoRepeat is not allowed to wrap around
173 var newIndex = appRepeater.highlightedIndex - 1 >= 0 ? appRepeater.highlightedIndex - 1 : ApplicationManager.count - 1;
174 appRepeater.highlightedIndex = newIndex;
175 var newContentX = ((spreadFlickable.contentWidth) / (ApplicationManager.count + 1)) * Math.max(0, Math.min(ApplicationManager.count - 5, appRepeater.highlightedIndex - 1));
176 if (spreadFlickable.contentX > newContentX || newIndex == ApplicationManager.count -1) {
177 spreadFlickable.snapTo(newContentX)
181 function focusSelected() {
182 if (appRepeater.highlightedIndex != -1) {
183 appRepeater.itemAt(appRepeater.highlightedIndex).focus = true;
189 model: ApplicationManager
190 objectName: "appRepeater"
192 property int highlightedIndex: -1
193 property int closingIndex: -1
195 function indexOf(delegateItem) {
196 for (var i = 0; i < appRepeater.count; i++) {
197 if (appRepeater.itemAt(i) === delegateItem) {
204 delegate: FocusScope {
206 z: ApplicationManager.count - index
211 property int windowWidth: 0
212 property int windowHeight: 0
213 // We don't want to resize the actual application when we're transforming things for the spread only
214 onWidthChanged: if (appDelegate.state !== "altTab") windowWidth = width
215 onHeightChanged: if (appDelegate.state !== "altTab") windowHeight = height
217 readonly property int minWidth: units.gu(10)
218 readonly property int minHeight: units.gu(10)
220 property bool maximized: false
221 property bool minimized: false
224 if (focus && ApplicationManager.focusedApplicationId !== model.appId) {
225 ApplicationManager.requestFocusApplication(model.appId);
226 decoratedWindow.forceActiveFocus();
229 Component.onCompleted: {
230 if (ApplicationManager.focusedApplicationId == model.appId) {
231 decoratedWindow.forceActiveFocus();
236 target: ApplicationManager.get(index)
237 property: "requestedState"
238 // TODO: figure out some lifecycle policy, like suspending minimized apps
239 // if running on a tablet or something.
240 // TODO: If the device has a dozen suspended apps because it was running
241 // in staged mode, when it switches to Windowed mode it will suddenly
242 // resume all those apps at once. We might want to avoid that.
243 value: ApplicationInfoInterface.RequestedRunning // Always running for now
246 function maximize() {
250 function minimize() {
254 function unmaximize() {
261 enabled: appRepeater.closingIndex >= 0
262 UbuntuNumberAnimation {
263 onRunningChanged: if (!running) appRepeater.closingIndex = -1
269 name: "normal"; when: !appDelegate.maximized && !appDelegate.minimized && root.state !== "altTab"
272 name: "maximized"; when: appDelegate.maximized && (root.state !== "altTab" || (root.state == "altTab" && !root.workspacesUpdated))
273 PropertyChanges { target: appDelegate; x: 0; y: 0; width: root.width; height: root.height }
276 name: "minimized"; when: appDelegate.minimized && (root.state !== "altTab" || (root.state == "altTab" && !root.workspacesUpdated))
277 PropertyChanges { target: appDelegate; x: -appDelegate.width / 2; scale: units.gu(5) / appDelegate.width; opacity: 0 }
280 name: "altTab"; when: root.state == "altTab" && root.workspacesUpdated
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;
291 visible: spreadMaths.itemVisible
294 target: decoratedWindow
295 decorationShown: false
296 highlightShown: index == appRepeater.highlightedIndex
298 width: spreadMaths.spreadHeight
299 height: spreadMaths.spreadHeight
300 shadowOpacity: spreadMaths.shadowOpacity
301 anchors.topMargin: units.gu(2)
306 opacity: spreadMaths.tileInfoOpacity
309 target: spreadSelectArea
313 target: windowMoveResizeArea
320 from: "maximized,minimized,normal,"
321 to: "maximized,minimized,normal,"
322 PropertyAnimation { target: appDelegate; properties: "x,y,opacity,width,height,scale" }
327 PropertyAction { target: appDelegate; properties: "y,angle,z,itemScale,itemScaleOriginY" }
328 PropertyAction { target: decoratedWindow; properties: "anchors.topMargin" }
330 target: appDelegate; properties: "x"
332 duration: rightEdgePushArea.containsMouse ? UbuntuAnimation.FastDuration :0
333 easing: UbuntuAnimation.StandardEasing
337 property real angle: 0
338 property real itemScale: 1
339 property int itemScaleOriginX: 0
340 property int itemScaleOriginY: 0
344 flickable: spreadFlickable
346 totalItems: Math.max(6, ApplicationManager.count)
347 sceneHeight: root.height
348 itemHeight: appDelegate.height
351 WindowMoveResizeArea {
352 id: windowMoveResizeArea
354 minWidth: appDelegate.minWidth
355 minHeight: appDelegate.minHeight
356 resizeHandleWidth: units.gu(2)
357 windowId: model.appId // FIXME: Change this to point to windowId once we have such a thing
359 onPressed: appDelegate.focus = true;
364 objectName: "decoratedWindow"
365 anchors.left: appDelegate.left
366 anchors.top: appDelegate.top
367 windowWidth: appDelegate.windowWidth
368 windowHeight: appDelegate.windowHeight
369 application: ApplicationManager.get(index)
370 active: ApplicationManager.focusedApplicationId === model.appId
373 onClose: ApplicationManager.stopApplication(model.appId)
374 onMaximize: appDelegate.maximize()
375 onMinimize: appDelegate.minimize()
379 origin.x: itemScaleOriginX
380 origin.y: itemScaleOriginY
385 origin { x: 0; y: (decoratedWindow.height - (decoratedWindow.height * itemScale / 2)) }
386 axis { x: 0; y: 1; z: 0 }
387 angle: appDelegate.angle
394 anchors.margins: -units.gu(2)
397 appRepeater.highlightedIndex = index;
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)
415 sourceSize.width: width
416 sourceSize.height: height
420 objectName: "closeMouseArea"
421 anchors.fill: closeImage
422 anchors.margins: -units.gu(2)
424 appRepeater.closingIndex = index;
425 ApplicationManager.stopApplication(model.appId)
432 objectName: "tileInfo"
433 anchors { left: parent.left; top: decoratedWindow.bottom; topMargin: units.gu(5) }
435 height: titleInfoColumn.height
439 onContainsMouseChanged: {
441 appRepeater.highlightedIndex = index
451 anchors { left: parent.left; top: parent.top; right: parent.right }
455 Layout.preferredHeight: Math.min(units.gu(6), root.height * .05)
456 Layout.preferredWidth: height * 8 / 7.6
463 Layout.fillWidth: true
464 Layout.preferredHeight: units.gu(6)
466 wrapMode: Text.WordWrap
477 anchors.fill: appContainer
478 propagateComposedEvents: true
482 property int scrollAreaWidth: root.width / 3
483 property bool progressiveScrollingEnabled: false
486 mouse.accepted = false
487 if (hoverMouseArea.pressed) return;
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)
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)
500 if (spreadFlickable.contentWidth > spreadFlickable.minContentWidth) {
501 var margins = spreadFlickable.width * 0.05;
503 if (!progressiveScrollingEnabled && mouseX < spreadFlickable.width - scrollAreaWidth) {
504 progressiveScrollingEnabled = true
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))
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))
520 onPressed: mouse.accepted = false
525 objectName: "spreadFlickable"
527 property int minContentWidth: 6 * Math.min(height / 4, width / 5)
528 contentWidth: Math.max(6, ApplicationManager.count) * Math.min(height / 4, width / 5)
531 function snapTo(contentX) {
532 snapAnimation.stop();
533 snapAnimation.to = contentX
534 snapAnimation.start();
537 UbuntuNumberAnimation {
539 target: spreadFlickable
545 id: workspaceSelector
550 topMargin: units.gu(3.5) // TODO: should be root.panelHeight
552 height: root.height * 0.25
558 Item { Layout.fillWidth: true }
560 model: 1 // TODO: will be a workspacemodel in the future
562 Layout.fillHeight: true
563 Layout.preferredWidth: ((height - units.gu(6)) * root.width / root.height)
565 source: root.background
569 verticalCenter: parent.verticalCenter
571 height: parent.height * 0.75
573 // FIXME: This is temporary until we can have multiple Items per surface
577 property var source: ShaderEffectSource {
578 id: shaderEffectSource
580 sourceItem: appContainer
581 Connections { target: root; onUpdateWorkspaces: shaderEffectSource.scheduleUpdate() }
585 varying highp vec2 qt_TexCoord0;
586 uniform sampler2D source;
589 highp vec4 sourceColor = texture2D(source, qt_TexCoord0);
590 gl_FragColor = sourceColor;
595 // TODO: This is the bar for the currently selected workspace
596 // Enable this once the workspace stuff is implemented
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
606 // TODO: This is the "new workspace" button. Enable this once workspaces are implemented
608 // Layout.fillHeight: true
609 // Layout.preferredWidth: ((height - units.gu(6)) * root.width / root.height)
613 // right: parent.right
614 // verticalCenter: parent.verticalCenter
616 // height: parent.height * 0.75
617 // color: "#22ffffff"
620 // anchors.centerIn: parent
621 // font.pixelSize: parent.height / 2
626 Item { Layout.fillWidth: true }
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 : ""
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: appContainer; focus: true }
646 PropertyChanges { target: hoverMouseArea; enabled: true }
649 signal updateWorkspaces();
650 property bool workspacesUpdated: false
655 SequentialAnimation {
656 PropertyAction { target: hoverMouseArea; property: "progressiveScrollingEnabled"; value: false }
657 PropertyAction { target: appRepeater; property: "highlightedIndex"; value: Math.min(ApplicationManager.count - 1, 1) }
658 PauseAnimation { duration: 50 }
659 PropertyAction { target: workspaceSelector; property: "visible" }
660 ScriptAction { script: root.updateWorkspaces() }
661 // FIXME: Updating of shaderEffectSource take a bit of time. This is temporary until we can paint multiple items per surface
662 PauseAnimation { duration: 10 }
663 PropertyAction { target: root; property: "workspacesUpdated"; value: true }
664 PropertyAction { target: spreadFlickable; property: "visible" }
665 PropertyAction { targets: [currentSelectedLabel,spreadBackground]; property: "visible" }
666 PropertyAction { target: spreadFlickable; property: "contentX"; value: 0 }
672 PropertyAnimation { property: "opacity" }
673 PropertyAction { target: root; property: "workspacesUpdated"; value: false }
674 ScriptAction { script: { appContainer.focusSelected() } }
675 PropertyAction { target: appRepeater; property: "highlightedIndex"; value: -1 }
681 id: rightEdgePushArea
685 bottom: parent.bottom
687 // TODO: Make this a push to edge thing like the launcher when we can,
688 // for now, yes, we want 1 pixel, regardless of the scaling
691 onContainsMouseChanged: {
693 root.state = "altTab";