on xenial help sign in
Fix app thumbnails on rotated Shell
9e775f2Daniel d'Andrada
qml/Dash/Apps/RunningApplicationTile.qml+95−33
@@ -29,11 +29,11 @@ AbstractButton {
29 signal requestedActivationMode()
29 signal requestedActivationMode()
30 signal requestedTerminationMode()
30 signal requestedTerminationMode()
31
31
32− // Was: childrenRect.height
32+ width: thumbnail.width
33− // To avoid "binding loop" warning
34− height: shapedApplicationImage.height + labelContainer.height
33+ height: thumbnail.height + labelContainer.height
35
34
36− width: shapedApplicationImage.width <= units.gu(11) ? units.gu(11) : height
35+ // Orientation angle of the Shell UI, relative to the scene (QQuickView's) root item.
36+ property int orientationAngle
37
37
38 property bool terminationModeEnabled: false
38 property bool terminationModeEnabled: false
39
39
@@ -50,42 +50,104 @@ AbstractButton {
50 }
50 }
51 }
51 }
52
52
53+ Item {
54+ id: thumbnail
55+
56+ width: thumbnailHelper.flippedDimensions ? shapedApplicationImage.height : shapedApplicationImage.width
57+ height: thumbnailHelper.flippedDimensions ? shapedApplicationImage.width : shapedApplicationImage.height
58+
59+ onWidthChanged: {console.log("thumbnail.width = " + width);}
60+ onHeightChanged: {console.log("thumbnail.height = " + height);}
61+
62+
63+ Item {
64+ id: thumbnailHelper
65+
66+ property bool flippedDimensions: root.orientationAngle == 90 || root.orientationAngle == 270
67+
68+ // Compensate for the UI rotation that is done by the application itself
69+ // TODO: Only counter-rotate if the application UI is rotated in the first place
70+ // Currently we don't have this info, so we just assume every single app rotate
71+ // its UI
72+ transformOrigin: Item.TopLeft
73+ state: orientationAngle.toString()
74+ states: [
75+ State {
76+ name: "0"
77+ PropertyChanges {
78+ target: thumbnailHelper
79+ rotation: 0
80+ x: 0
81+ y: 0
82+ }
83+ },
84+ State {
85+ name: "90"
86+ PropertyChanges {
87+ target: thumbnailHelper
88+ rotation: -90
89+ x: 0
90+ y: shapedApplicationImage.width
91+ }
92+ },
93+ State {
94+ name: "180"
95+ PropertyChanges {
96+ target: thumbnailHelper
97+ rotation: -180
98+ x: shapedApplicationImage.width
99+ y: shapedApplicationImage.height
100+ }
101+ },
102+ State {
103+ name: "270"
104+ PropertyChanges {
105+ target: thumbnailHelper
106+ rotation: -270
107+ x: shapedApplicationImage.height
108+ y: 0
109+ }
110+ }
111+ ]
112+
53− UbuntuShape {
113+ UbuntuShape {
54− id: shapedApplicationImage
114+ id: shapedApplicationImage
55− anchors { top: parent.top; horizontalCenter: parent.horizontalCenter }
115+ x: 0
56−
116+ y: 0
57− height: units.gu(17)
117+ height: applicationImage.height
58− width: applicationImage.width
118+ width: applicationImage.width
59− radius: "medium"
119+ radius: "medium"
60−
120+
61− image: Image {
121+ image: Image {
62− id: applicationImage
122+ id: applicationImage
63− source: application.screenshot
123+ source: application.screenshot
64− // height : width = ss.height : ss.width
124+ // height : width = ss.height : ss.width
65− height: shapedApplicationImage.height
125+ height: units.gu(17)
66− fillMode: Image.PreserveAspectCrop
126+ fillMode: Image.PreserveAspectCrop
67− width: Math.min(height, height * sourceSize.width / sourceSize.height)
127+ width: Math.min(height, height * sourceSize.width / sourceSize.height)
68− }
128+ }
69
129
70− }
130+ }
71
131
72− UbuntuShape {
132+ UbuntuShape {
73− id: borderPressed
133+ id: borderPressed
74
134
75− anchors.fill: shapedApplicationImage
135+ anchors.fill: shapedApplicationImage
76− radius: "medium"
136+ radius: "medium"
77− borderSource: "radius_pressed.sci"
137+ borderSource: "radius_pressed.sci"
78− opacity: root.pressed ? 1.0 : 0.0
138+ opacity: root.pressed ? 1.0 : 0.0
79− Behavior on opacity { NumberAnimation { duration: 200; easing.type: Easing.OutQuint } }
139+ Behavior on opacity { NumberAnimation { duration: 200; easing.type: Easing.OutQuint } }
140+ }
141+ }
80 }
142 }
81
143
82 // FIXME: label code duplicated with Tile
144 // FIXME: label code duplicated with Tile
83 Item {
145 Item {
84 id: labelContainer
146 id: labelContainer
85 anchors {
147 anchors {
86− left: parent.left
148+ left: thumbnail.left
87− right: parent.right
149+ right: thumbnail.right
88− top: shapedApplicationImage.bottom
150+ top: thumbnail.bottom
89 }
151 }
90 height: units.gu(2)
152 height: units.gu(2)
91
153
@@ -110,9 +172,9 @@ AbstractButton {
110 CloseIcon {
172 CloseIcon {
111 objectName: "closeIcon " + model.name
173 objectName: "closeIcon " + model.name
112 anchors {
174 anchors {
113− left: shapedApplicationImage.left
175+ left: thumbnail.left
114 leftMargin: -units.gu(1)
176 leftMargin: -units.gu(1)
115− top: parent.top
177+ top: thumbnail.top
116 topMargin: -units.gu(1)
178 topMargin: -units.gu(1)
117 }
179 }
118 height: units.gu(6)
180 height: units.gu(6)
qml/Dash/Apps/RunningApplicationsGrid.qml+3
@@ -28,6 +28,8 @@ ResponsiveFlowView {
28 property alias enableHeightBehavior: heightBehaviour.enabled
28 property alias enableHeightBehavior: heightBehaviour.enabled
29 property bool enableHeightBehaviorOnNextCreation: model.count === 0
29 property bool enableHeightBehaviorOnNextCreation: model.count === 0
30
30
31+ property int orientationAngle
32+
31 Behavior on height {
33 Behavior on height {
32 id: heightBehaviour
34 id: heightBehaviour
33 enabled: false
35 enabled: false
@@ -63,6 +65,7 @@ ResponsiveFlowView {
63 RunningApplicationTile {
65 RunningApplicationTile {
64 id: runningAppTile
66 id: runningAppTile
65 objectName: "runningAppTile " + model.name
67 objectName: "runningAppTile " + model.name
68+ orientationAngle: root.orientationAngle
66 anchors {
69 anchors {
67 top: parent.top
70 top: parent.top
68 horizontalCenter: parent.horizontalCenter
71 horizontalCenter: parent.horizontalCenter
qml/Dash/Dash.qml+3
@@ -26,6 +26,8 @@ Showable {
26
26
27 visible: shown
27 visible: shown
28
28
29+ property int orientationAngle
30+
29 property ListModel searchHistory: SearchHistoryModel {}
31 property ListModel searchHistory: SearchHistoryModel {}
30 property bool searchable: !dashContent.previewOpen && !scopeItem.previewOpen
32 property bool searchable: !dashContent.previewOpen && !scopeItem.previewOpen
31
33
@@ -74,6 +76,7 @@ Showable {
74 objectName: "dashContent"
76 objectName: "dashContent"
75 width: parent.width
77 width: parent.width
76 height: parent.height
78 height: parent.height
79+ orientationAngle: dash.orientationAngle
77 model: filteredScopes
80 model: filteredScopes
78 scopes: scopes
81 scopes: scopes
79 searchHistory: dash.searchHistory
82 searchHistory: dash.searchHistory
qml/Dash/DashContent.qml+5
@@ -23,6 +23,7 @@ import "../Components"
23Item {
23Item {
24 id: dashContent
24 id: dashContent
25
25
26+ property int orientationAngle
26 property var model: null
27 property var model: null
27 property var scopes: null
28 property var scopes: null
28 readonly property alias currentIndex: dashContentList.currentIndex
29 readonly property alias currentIndex: dashContentList.currentIndex
@@ -153,6 +154,10 @@ Item {
153 item.isCurrent = Qt.binding(function() { return visible && ListView.isCurrentItem })
154 item.isCurrent = Qt.binding(function() { return visible && ListView.isCurrentItem })
154 item.tabBarHeight = dashPageHeader.implicitHeight;
155 item.tabBarHeight = dashPageHeader.implicitHeight;
155 dashContent.scopeLoaded(item.scope.id)
156 dashContent.scopeLoaded(item.scope.id)
157+
158+ if ("orientationAngle" in item) {
159+ item.orientationAngle = Qt.binding(function() { return dashContent.orientationAngle })
160+ }
156 }
161 }
157 Connections {
162 Connections {
158 target: isCurrent ? scope : null
163 target: isCurrent ? scope : null
qml/Dash/GenericScopeView.qml+6
@@ -25,6 +25,7 @@ import "../Components/ListItems" as ListItems
25FocusScope {
25FocusScope {
26 id: scopeView
26 id: scopeView
27
27
28+ property int orientationAngle
28 property Scope scope: null
29 property Scope scope: null
29 property SortFilterProxyModel categories: categoryFilter
30 property SortFilterProxyModel categories: categoryFilter
30 property bool isCurrent: false
31 property bool isCurrent: false
@@ -165,6 +166,11 @@ FocusScope {
165 } else {
166 } else {
166 item.model = Qt.binding(function() { return results })
167 item.model = Qt.binding(function() { return results })
167 }
168 }
169+
170+ if ("orientationAngle" in item) {
171+ item.orientationAngle = Qt.binding(function() { return scopeView.orientationAngle })
172+ }
173+
168 item.objectName = Qt.binding(function() { return categoryId })
174 item.objectName = Qt.binding(function() { return categoryId })
169 if (item.expandable) {
175 if (item.expandable) {
170 var shouldFilter = categoryId != categoryView.expandedCategoryId;
176 var shouldFilter = categoryId != categoryView.expandedCategoryId;
qml/Shell.qml+3−1
@@ -121,6 +121,8 @@ FocusScope {
121 id: dash
121 id: dash
122 objectName: "dash"
122 objectName: "dash"
123
123
124+ orientationAngle: shell.orientationAngle
125+
124 available: !greeter.shown && !lockscreen.shown
126 available: !greeter.shown && !lockscreen.shown
125 hides: [stages, launcher, panel.indicators]
127 hides: [stages, launcher, panel.indicators]
126 shown: disappearingAnimationProgress !== 1.0 && greeterWrapper.showProgress !== 1.0
128 shown: disappearingAnimationProgress !== 1.0 && greeterWrapper.showProgress !== 1.0
@@ -164,7 +166,7 @@ FocusScope {
164 if (!stages.empty && progress < stages.width - units.gu(10)) {
166 if (!stages.empty && progress < stages.width - units.gu(10)) {
165 stages.show();
167 stages.show();
166 }
168 }
167− stagesDragArea.progress = stages.width;
169+ stagesDragArea.progress = Qt.binding(function () { return stages.width; });
168 }
170 }
169 }
171 }
170 }
172 }
tests/qmltests/Dash/Apps/tst_RunningApplicationsGrid.qml+23−2
@@ -19,10 +19,11 @@ import QtTest 1.0
19import "../../../../qml/Dash/Apps"
19import "../../../../qml/Dash/Apps"
20import Unity.Test 0.1 as UT
20import Unity.Test 0.1 as UT
21import Unity.Application 0.1
21import Unity.Application 0.1
22+import Ubuntu.Components 1.1
22
23
23// Using Rectangle to have an opaque surface because AppManager paints app surfaces behind it.
24// Using Rectangle to have an opaque surface because AppManager paints app surfaces behind it.
24Rectangle {
25Rectangle {
25− width: units.gu(50)
26+ width: units.gu(70)
26 height: units.gu(40)
27 height: units.gu(40)
27
28
28 function resetRunningApplications() {
29 function resetRunningApplications() {
@@ -41,10 +42,30 @@ Rectangle {
41 // The component under test
42 // The component under test
42 RunningApplicationsGrid {
43 RunningApplicationsGrid {
43 id: runningApplicationsGrid
44 id: runningApplicationsGrid
45+ orientationAngle: orientationAngleSelector.selectedIndex * 90
46+ anchors.left: parent.left
47+ anchors.right: controlPanel.left
44− anchors.fill: parent
48+ anchors.top: parent.top
49+ anchors.bottom: parent.bottom
45 model: ApplicationManager
50 model: ApplicationManager
46 }
51 }
47
52
53+ Rectangle {
54+ id: controlPanel
55+ anchors.right: parent.right
56+ anchors.top: parent.top
57+ anchors.bottom: parent.bottom
58+ width: units.gu(20)
59+ color: "black"
60+
61+ OptionSelector {
62+ id: orientationAngleSelector
63+ model: ["0","90","180","270"]
64+ text: "Orientation Angle"
65+ width: parent.width
66+ }
67+ }
68+
48 UT.UnityTestCase {
69 UT.UnityTestCase {
49 name: "RunningApplicationsGrid"
70 name: "RunningApplicationsGrid"
50 when: windowShown
71 when: windowShown