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 delegate: FocusScope {
197 z: ApplicationManager.count - index
202 readonly property int minWidth: units.gu(10)
203 readonly property int minHeight: units.gu(10)
205 property bool maximized: false
206 property bool minimized: false
209 if (focus && ApplicationManager.focusedApplicationId !== model.appId) {
210 ApplicationManager.requestFocusApplication(model.appId);
211 decoratedWindow.forceActiveFocus();
214 Component.onCompleted: {
215 if (ApplicationManager.focusedApplicationId == model.appId) {
216 decoratedWindow.forceActiveFocus();
221 target: ApplicationManager.get(index)
222 property: "requestedState"
223 // TODO: figure out some lifecycle policy, like suspending minimized apps
224 // if running on a tablet or something.
225 // TODO: If the device has a dozen suspended apps because it was running
226 // in staged mode, when it switches to Windowed mode it will suddenly
227 // resume all those apps at once. We might want to avoid that.
228 value: ApplicationInfoInterface.RequestedRunning // Always running for now
231 function maximize() {
235 function minimize() {
239 function unmaximize() {
246 enabled: appRepeater.closingIndex >= 0
247 UbuntuNumberAnimation {
248 onRunningChanged: if (!running) appRepeater.closingIndex = -1
254 name: "normal"; when: !appDelegate.maximized && !appDelegate.minimized && root.state !== "altTab"
257 name: "maximized"; when: appDelegate.maximized && (root.state !== "altTab" || (root.state == "altTab" && !root.workspacesUpdated))
258 PropertyChanges { target: appDelegate; x: 0; y: 0; width: root.width; height: root.height }
261 name: "minimized"; when: appDelegate.minimized && (root.state !== "altTab" || (root.state == "altTab" && !root.workspacesUpdated))
262 PropertyChanges { target: appDelegate; x: -appDelegate.width / 2; scale: units.gu(5) / appDelegate.width; opacity: 0 }
265 name: "altTab"; when: root.state == "altTab" && root.workspacesUpdated
268 x: spreadMaths.animatedX
269 y: spreadMaths.animatedY + (appDelegate.height - decoratedWindow.height)
270 angle: spreadMaths.animatedAngle
271 itemScale: spreadMaths.scale
272 itemScaleOriginY: decoratedWindow.height / 2;
274 visible: spreadMaths.itemVisible
277 target: decoratedWindow
278 decorationShown: false
279 highlightShown: index == appRepeater.highlightedIndex
281 width: spreadMaths.spreadHeight
282 height: spreadMaths.spreadHeight
283 shadowOpacity: spreadMaths.shadowOpacity
288 opacity: spreadMaths.tileInfoOpacity
291 target: spreadSelectArea
295 target: windowMoveResizeArea
302 from: "maximized,minimized,normal,"
303 to: "maximized,minimized,normal,"
304 PropertyAnimation { target: appDelegate; properties: "x,y,opacity,width,height,scale" }
307 property real angle: 0
308 property real itemScale: 1
309 property int itemScaleOriginX: 0
310 property int itemScaleOriginY: 0
314 flickable: spreadFlickable
316 totalItems: Math.max(6, ApplicationManager.count)
317 sceneHeight: root.height
318 itemHeight: appDelegate.height
321 WindowMoveResizeArea {
322 id: windowMoveResizeArea
324 minWidth: appDelegate.minWidth
325 minHeight: appDelegate.minHeight
326 resizeHandleWidth: units.gu(2)
327 windowId: model.appId // FIXME: Change this to point to windowId once we have such a thing
329 onPressed: appDelegate.focus = true;
334 objectName: "decoratedWindow"
335 anchors.left: appDelegate.left
336 anchors.top: appDelegate.top
337 windowWidth: appDelegate.width
338 windowHeight: appDelegate.height
339 application: ApplicationManager.get(index)
340 active: ApplicationManager.focusedApplicationId === model.appId
343 onClose: ApplicationManager.stopApplication(model.appId)
344 onMaximize: appDelegate.maximize()
345 onMinimize: appDelegate.minimize()
349 origin.x: itemScaleOriginX
350 origin.y: itemScaleOriginY
355 origin { x: 0; y: (decoratedWindow.height - (decoratedWindow.height * itemScale / 2)) }
356 axis { x: 0; y: 1; z: 0 }
357 angle: appDelegate.angle
364 anchors.margins: -units.gu(2)
366 hoverEnabled: enabled
368 // There is a bug in MouseArea where containsMouse doesn't
369 // return to false if the MouseArea is disabled while
370 // containing the mouse. Let's manage the property our own.
371 property bool upperThirdContainsMouse: false
372 onContainsMouseChanged: evaluateContainsMouse()
373 onMouseYChanged: evaluateContainsMouse()
374 function evaluateContainsMouse() {
376 appRepeater.highlightedIndex = index
379 if (containsMouse && mouseY < height / 3) {
380 spreadSelectArea.upperThirdContainsMouse = true
382 spreadSelectArea.upperThirdContainsMouse = false;
387 spreadSelectArea.upperThirdContainsMouse = false
399 anchors { left: parent.left; top: parent.top; leftMargin: -height / 2; topMargin: -height / 2 + spreadMaths.closeIconOffset }
400 source: "graphics/window-close.svg"
401 visible: spreadSelectArea.upperThirdContainsMouse
402 height: units.gu(1.5)
404 sourceSize.width: width
405 sourceSize.height: height
410 objectName: "closeMouseArea"
411 anchors.fill: closeImage
412 anchors.margins: -units.gu(2)
413 enabled: spreadSelectArea.upperThirdContainsMouse
415 appRepeater.closingIndex = index;
416 ApplicationManager.stopApplication(model.appId)
422 objectName: "tileInfo"
423 anchors { left: parent.left; top: decoratedWindow.bottom; topMargin: units.gu(5) }
425 height: titleInfoColumn.height
429 onContainsMouseChanged: {
431 appRepeater.highlightedIndex = index
441 anchors { left: parent.left; top: parent.top; right: parent.right }
445 Layout.preferredHeight: Math.min(units.gu(6), root.height * .05)
446 Layout.preferredWidth: height * 8 / 7.6
453 Layout.fillWidth: true
454 Layout.preferredHeight: units.gu(6)
456 wrapMode: Text.WordWrap
468 contentWidth: Math.max(6, ApplicationManager.count) * Math.min(height / 4, width / 5)
471 function snapTo(contentX) {
472 snapAnimation.stop();
473 snapAnimation.to = contentX
474 snapAnimation.start();
477 UbuntuNumberAnimation {
479 target: spreadFlickable
485 id: workspaceSelector
490 topMargin: units.gu(3.5) // TODO: should be root.panelHeight
492 height: root.height * 0.25
498 Item { Layout.fillWidth: true }
500 model: 1 // TODO: will be a workspacemodel in the future
502 Layout.fillHeight: true
503 Layout.preferredWidth: ((height - units.gu(6)) * root.width / root.height)
505 source: root.background
509 verticalCenter: parent.verticalCenter
511 height: parent.height * 0.75
513 // FIXME: This is temporary until we can have multiple Items per surface
517 property var source: ShaderEffectSource {
518 id: shaderEffectSource
520 sourceItem: appContainer
521 Connections { target: root; onUpdateWorkspaces: shaderEffectSource.scheduleUpdate() }
525 varying highp vec2 qt_TexCoord0;
526 uniform sampler2D source;
529 highp vec4 sourceColor = texture2D(source, qt_TexCoord0);
530 gl_FragColor = sourceColor;
535 // TODO: This is the bar for the currently selected workspace
536 // Enable this once the workspace stuff is implemented
538 // anchors { left: parent.left; right: parent.right; bottom: parent.bottom }
539 // height: units.dp(2)
540 // color: UbuntuColors.orange
541 // visible: index == 0 // TODO: should be active workspace index
546 // TODO: This is the "new workspace" button. Enable this once workspaces are implemented
548 // Layout.fillHeight: true
549 // Layout.preferredWidth: ((height - units.gu(6)) * root.width / root.height)
553 // right: parent.right
554 // verticalCenter: parent.verticalCenter
556 // height: parent.height * 0.75
557 // color: "#22ffffff"
560 // anchors.centerIn: parent
561 // font.pixelSize: parent.height / 2
566 Item { Layout.fillWidth: true }
571 id: currentSelectedLabel
572 anchors { bottom: parent.bottom; bottomMargin: root.height * 0.625; horizontalCenter: parent.horizontalCenter }
573 text: appRepeater.highlightedIndex >= 0 ? ApplicationManager.get(appRepeater.highlightedIndex).name : ""
580 name: "altTab"; when: root.altTabPressed
581 PropertyChanges { target: workspaceSelector; visible: true }
582 PropertyChanges { target: spreadFlickable; enabled: true }
583 PropertyChanges { target: currentSelectedLabel; visible: true }
584 PropertyChanges { target: spreadBackground; visible: true }
585 PropertyChanges { target: appContainer; focus: true }
588 signal updateWorkspaces();
589 property bool workspacesUpdated: false
594 SequentialAnimation {
595 PropertyAction { target: appRepeater; property: "highlightedIndex"; value: Math.min(ApplicationManager.count - 1, 1) }
596 PauseAnimation { duration: 50 }
597 PropertyAction { target: workspaceSelector; property: "visible" }
598 ScriptAction { script: root.updateWorkspaces() }
599 // FIXME: Updating of shaderEffectSource take a bit of time. This is temporary until we can paint multiple items per surface
600 PauseAnimation { duration: 10 }
601 PropertyAction { target: root; property: "workspacesUpdated"; value: true }
602 PropertyAction { target: spreadFlickable; property: "visible" }
603 PropertyAction { targets: [currentSelectedLabel,spreadBackground]; property: "visible" }
604 PropertyAction { target: spreadFlickable; property: "contentX"; value: 0 }
610 PropertyAnimation { property: "opacity" }
611 PropertyAction { target: root; property: "workspacesUpdated"; value: false }
612 ScriptAction { script: { appContainer.focusSelected() } }
613 PropertyAction { target: appRepeater; property: "highlightedIndex"; value: -1 }
622 bottom: parent.bottom
624 // TODO: Make this a push to edge thing like the launcher when we can,
625 // for now, yes, we want 1 pixel, regardless of the scaling
628 onContainsMouseChanged: {
630 root.state = "altTab"