on xenial help sign in
Desktop stage: Support rotatesWindowContents (LP: #1547481)
commitefe95729fd39c1fc26c0f3ee9c85e710be4dfa41 authorAlbert Astals Cid <albert.astals@canonical.com> dateFri 17 Jun 2016, 01:13 (10 years ago) parenta578eb35184744
6 files changed+118−47
plugins/Dash/CardAttributes.qml+18−26 qml/Stages/DecoratedWindow.qml+34−10 qml/Stages/DesktopStage.qml+37−10 tests/mocks/Unity/Application/ApplicationManager.cpp+12 tests/mocks/Unity/Launcher/MockLauncherModel.cpp+3 tests/qmltests/tst_OrientedShell.qml+14−1
plugins/Dash/CardAttributes.qml+18−26
@@ -43,36 +43,28 @@ GridLayout {
4343 Layout.column: index % grid.columns
4444 Layout.row: index / grid.columns
4545 Layout.columnSpan: index == repeater.count - 1 && grid.columns == 3 && column == 1 ? 2 : 1
46− Layout.maximumWidth: Math.max(iconLoader.width, labelLoader.x + labelLoader.implicitWidth)
46+ Layout.maximumWidth: Math.max(icon.width, label.x + label.implicitWidth)
4747 Layout.fillWidth: true
4848 height: units.gu(2)
49− Loader {
50− id: iconLoader
51− readonly property string source: "icon" in modelData && modelData["icon"] || ""
52− active: source !== ""
53− sourceComponent: Icon {
54− height: units.gu(2)
55− // FIXME Workaround for bug https://bugs.launchpad.net/ubuntu/+source/ubuntu-ui-toolkit/+bug/1421293
56− width: implicitWidth > 0 && implicitHeight > 0 ? (implicitWidth / implicitHeight * height) : implicitWidth
57− source: parent.source
58− color: grid.color
59− }
49+ Icon {
50+ id: icon
51+ height: units.gu(2)
52+ // FIXME Workaround for bug https://bugs.launchpad.net/ubuntu/+source/ubuntu-ui-toolkit/+bug/1421293
53+ width: implicitWidth > 0 && implicitHeight > 0 ? (implicitWidth / implicitHeight * height) : implicitWidth
54+ source: "icon" in modelData && modelData["icon"] || ""
55+ color: grid.color
6056 }
61− Loader {
62− id: labelLoader
57+ Label {
58+ id: label
6359 width: parent.width - x
64− readonly property string value: "value" in modelData && modelData["value"] || ""
65− active: value !== ""
66− sourceComponent: Label {
67− anchors.verticalCenter: parent.verticalCenter
68− text: parent.value
69− elide: Text.ElideRight
70− maximumLineCount: 1
71− font.weight: "style" in modelData && modelData["style"] === "highlighted" ? Font.Bold : Font.Light
72− fontSize: "small"
73− font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale)
74− color: grid.color
75− }
60+ anchors.verticalCenter: parent.verticalCenter
61+ text: "value" in modelData && modelData["value"] || "";
62+ elide: Text.ElideRight
63+ maximumLineCount: 1
64+ font.weight: "style" in modelData && modelData["style"] === "highlighted" ? Font.Bold : Font.Light
65+ fontSize: "small"
66+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale)
67+ color: grid.color
7668 }
7769 }
7870 }
qml/Stages/DecoratedWindow.qml+34−10
@@ -23,8 +23,8 @@ import Unity.Application 0.1
2323FocusScope {
2424 id: root
2525
26− width: applicationWindow.width
27− height: (decorationShown ? decoration.height : 0) + applicationWindow.height
26+ width: !counterRotate ? applicationWindow.width : applicationWindow.height
27+ height: visibleDecorationHeight + (!counterRotate ? applicationWindow.height : applicationWindow.width)
2828
2929 property alias application: applicationWindow.application
3030 property alias surface: applicationWindow.surface
@@ -36,15 +36,18 @@ FocusScope {
3636 property bool highlightShown: false
3737 property real shadowOpacity: 1
3838
39− property alias requestedWidth: applicationWindow.requestedWidth
39+ property real requestedWidth
4040 property real requestedHeight
41+ property alias surfaceOrientationAngle: applicationWindow.surfaceOrientationAngle
42+ readonly property real visibleDecorationHeight: root.decorationShown ? decoration.height : 0
43+ readonly property bool counterRotate: surfaceOrientationAngle != 0 && surfaceOrientationAngle != 180
4144
42− property alias minimumWidth: applicationWindow.minimumWidth
43− readonly property int minimumHeight: (root.decorationShown ? decoration.height : 0) + applicationWindow.minimumHeight
44− property alias maximumWidth: applicationWindow.maximumWidth
45− readonly property int maximumHeight: (root.decorationShown ? decoration.height : 0) + applicationWindow.maximumHeight
46− property alias widthIncrement: applicationWindow.widthIncrement
47− property alias heightIncrement: applicationWindow.heightIncrement
45+ readonly property int minimumWidth: !counterRotate ? applicationWindow.minimumWidth : applicationWindow.minimumHeight
46+ readonly property int minimumHeight: visibleDecorationHeight + (!counterRotate ? applicationWindow.minimumHeight : applicationWindow.minimumWidth)
47+ readonly property int maximumWidth: !counterRotate ? applicationWindow.maximumWidth : applicationWindow.maximumHeight
48+ readonly property int maximumHeight: visibleDecorationHeight + (!counterRotate ? applicationWindow.maximumHeight : applicationWindow.maximumWidth)
49+ readonly property int widthIncrement: !counterRotate ? applicationWindow.widthIncrement : applicationWindow.heightIncrement
50+ readonly property int heightIncrement: !counterRotate ? applicationWindow.heightIncrement : applicationWindow.widthIncrement
4851
4952 signal closeClicked()
5053 signal maximizeClicked()
@@ -102,8 +105,29 @@ FocusScope {
102105 anchors.top: parent.top
103106 anchors.topMargin: decoration.height
104107 anchors.left: parent.left
105− requestedHeight: root.requestedHeight - (root.decorationShown ? decoration.height : 0)
108+ readonly property real requestedHeightMinusDecoration: root.requestedHeight - root.visibleDecorationHeight
109+ requestedHeight: !counterRotate ? requestedHeightMinusDecoration : root.requestedWidth
110+ requestedWidth: !counterRotate ? root.requestedWidth : requestedHeightMinusDecoration
106111 interactive: true
107112 focus: true
113+
114+ transform: Rotation {
115+ readonly property int rotationAngle: applicationWindow.application &&
116+ applicationWindow.application.rotatesWindowContents
117+ ? ((360 - applicationWindow.surfaceOrientationAngle) % 360) : 0
118+ origin.x: {
119+ if (rotationAngle == 90) return applicationWindow.height / 2;
120+ else if (rotationAngle == 270) return applicationWindow.width / 2;
121+ else if (rotationAngle == 180) return applicationWindow.width / 2;
122+ else return 0;
123+ }
124+ origin.y: {
125+ if (rotationAngle == 90) return applicationWindow.height / 2;
126+ else if (rotationAngle == 270) return applicationWindow.width / 2;
127+ else if (rotationAngle == 180) return applicationWindow.height / 2;
128+ else return 0;
129+ }
130+ angle: rotationAngle
131+ }
108132 }
109133}
qml/Stages/DesktopStage.qml+37−10
@@ -268,14 +268,37 @@ AbstractStage {
268268
269269 width: decoratedWindow.width
270270 height: decoratedWindow.height
271+
272+ Connections {
273+ target: root
274+ onShellOrientationAngleChanged: {
275+ // at this point decoratedWindow.surfaceOrientationAngle is the old shellOrientationAngle
276+ if (application && application.rotatesWindowContents) {
277+ if (state == "normal") {
278+ var angleDiff = decoratedWindow.surfaceOrientationAngle - shellOrientationAngle;
279+ angleDiff = (360 + angleDiff) % 360;
280+ if (angleDiff === 90 || angleDiff === 270) {
281+ var aux = decoratedWindow.requestedHeight;
282+ decoratedWindow.requestedHeight = decoratedWindow.requestedWidth + decoratedWindow.visibleDecorationHeight;
283+ decoratedWindow.requestedWidth = aux - decoratedWindow.visibleDecorationHeight;
284+ }
285+ }
286+ decoratedWindow.surfaceOrientationAngle = shellOrientationAngle;
287+ } else {
288+ decoratedWindow.surfaceOrientationAngle = 0;
289+ }
290+ }
291+ }
292+
293+ readonly property alias application: decoratedWindow.application
294+ readonly property alias minimumWidth: decoratedWindow.minimumWidth
295+ readonly property alias minimumHeight: decoratedWindow.minimumHeight
296+ readonly property alias maximumWidth: decoratedWindow.maximumWidth
297+ readonly property alias maximumHeight: decoratedWindow.maximumHeight
298+ readonly property alias widthIncrement: decoratedWindow.widthIncrement
299+ readonly property alias heightIncrement: decoratedWindow.heightIncrement
271300 property int requestedWidth: -1
272301 property int requestedHeight: -1
273− property alias minimumWidth: decoratedWindow.minimumWidth
274− property alias minimumHeight: decoratedWindow.minimumHeight
275− property alias maximumWidth: decoratedWindow.maximumWidth
276− property alias maximumHeight: decoratedWindow.maximumHeight
277− property alias widthIncrement: decoratedWindow.widthIncrement
278− property alias heightIncrement: decoratedWindow.heightIncrement
279302
280303 readonly property bool maximized: windowState & WindowStateStorage.WindowStateMaximized
281304 readonly property bool maximizedLeft: windowState & WindowStateStorage.WindowStateMaximizedLeft
@@ -286,8 +309,6 @@ AbstractStage {
286309 readonly property alias fullscreen: decoratedWindow.fullscreen
287310
288311 property int windowState: WindowStateStorage.WindowStateNormal
289−
290− readonly property var application: model.application
291312 property bool animationsEnabled: true
292313 property alias title: decoratedWindow.title
293314 readonly property string appName: model.application ? model.application.name : ""
@@ -338,6 +359,12 @@ AbstractStage {
338359 }
339360 }
340361 Component.onCompleted: {
362+ if (application && application.rotatesWindowContents) {
363+ decoratedWindow.surfaceOrientationAngle = shellOrientationAngle;
364+ } else {
365+ decoratedWindow.surfaceOrientationAngle = 0;
366+ }
367+
341368 // NB: We're differentiating if this delegate was created in response to a new entry in the model
342369 // or if the Repeater is just populating itself with delegates to match the model it received.
343370 if (!appRepeater.startingUp) {
@@ -445,8 +472,8 @@ AbstractStage {
445472 name: "fullscreen"; when: decoratedWindow.fullscreen && !appDelegate.minimized
446473 PropertyChanges {
447474 target: appDelegate;
448− x: 0;
449− y: -PanelState.panelHeight
475+ x: rotation == 0 ? 0 : (parent.width - width) / 2 + (shellOrientationAngle == 90 ? -PanelState.panelHeight : PanelState.panelHeight)
476+ y: rotation == 0 ? -PanelState.panelHeight : (parent.height - height) / 2
450477 requestedWidth: appContainer.width;
451478 requestedHeight: appContainer.height;
452479 }
tests/mocks/Unity/Application/ApplicationManager.cpp+12
@@ -327,6 +327,18 @@ void ApplicationManager::buildListOfAvailableApplications()
327327 application->setRotatesWindowContents(true);
328328 m_availableApplications.append(application);
329329
330+ application = new ApplicationInfo(this);
331+ application->setAppId("camera-app2");
332+ application->setName("Camera2");
333+ application->setScreenshotId("camera");
334+ application->setIconId("camera");
335+ application->setSupportedOrientations(Qt::PortraitOrientation
336+ | Qt::LandscapeOrientation
337+ | Qt::InvertedPortraitOrientation
338+ | Qt::InvertedLandscapeOrientation);
339+ application->setRotatesWindowContents(true);
340+ m_availableApplications.append(application);
341+
330342 application = new ApplicationInfo(this);
331343 application->setAppId("gallery-app");
332344 application->setName("Gallery");
tests/mocks/Unity/Launcher/MockLauncherModel.cpp+3
@@ -33,6 +33,9 @@ MockLauncherModel::MockLauncherModel(QObject* parent): LauncherModelInterface(pa
3333 item->setProgress(10);
3434 item->setPinned(true);
3535 m_list.append(item);
36+ item = new MockLauncherItem("camera-app2", "/usr/share/applications/camera-app2.desktop", "Camera2", "camera", this);
37+ item->setPinned(true);
38+ m_list.append(item);
3639 item = new MockLauncherItem("gallery-app", "/usr/share/applications/gallery-app.desktop", "Gallery", "gallery", this);
3740 item->setProgress(50);
3841 item->setCountVisible(true);
tests/qmltests/tst_OrientedShell.qml+14−1
@@ -28,6 +28,7 @@ import Unity.InputInfo 0.1
2828import Utils 0.1
2929
3030import "../../qml"
31+import "Stages"
3132
3233Rectangle {
3334 id: root
@@ -423,6 +424,8 @@ Rectangle {
423424 }
424425 }
425426 }
427+
428+ SurfaceManagerControls { }
426429 }
427430 }
428431
@@ -619,12 +622,22 @@ Rectangle {
619622 function test_appRotatesWindowContents_data() {
620623 return [
621624 {tag: "mako", deviceName: "mako", orientationAngleAfterRotation: 90},
625+ {tag: "mako_windowed", deviceName: "mako", orientationAngleAfterRotation: 90, windowed: true},
622626 {tag: "manta", deviceName: "manta", orientationAngleAfterRotation: 90},
623− {tag: "flo", deviceName: "flo", orientationAngleAfterRotation: 180}
627+ {tag: "manta_windowed", deviceName: "manta", orientationAngleAfterRotation: 90, windowed: true},
628+ {tag: "flo", deviceName: "flo", orientationAngleAfterRotation: 180},
629+ {tag: "flo_windowed", deviceName: "flo", orientationAngleAfterRotation: 180, windowed: true}
624630 ];
625631 }
626632 function test_appRotatesWindowContents(data) {
627633 loadShell(data.deviceName);
634+
635+ if (data.windowed) {
636+ usageModeSelector.selectWindowed();
637+ } else {
638+ usageModeSelector.selectStaged();
639+ }
640+
628641 var cameraSurfaceId = topLevelSurfaceList.nextId;
629642 var cameraApp = ApplicationManager.startApplication("camera-app");
630643 verify(cameraApp);