on xenial help sign in
Optimize wallpapers for load times and memory use
a81421aDalton Durst
qml/Components/Lockscreen.qml+2
@@ -1,5 +1,6 @@
1/*
1/*
2 * Copyright (C) 2013-2017 Canonical, Ltd.
2 * Copyright (C) 2013-2017 Canonical, Ltd.
3+ * Copyright (C) 2021 UBports Foundation
3 *
4 *
4 * This program is free software; you can redistribute it and/or modify
5 * 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 * it under the terms of the GNU General Public License as published by
@@ -53,6 +54,7 @@ Showable {
53 property int maxPinLength: -1
54 property int maxPinLength: -1
54
55
55 property url background: ""
56 property url background: ""
57+ property alias backgroundSourceSize: backgroundImage.sourceSize
56 // Use this to put a black overlay above the background
58 // Use this to put a black overlay above the background
57 // 0: normal background, 1: black background
59 // 0: normal background, 1: black background
58 property real darkenBackground: 0
60 property real darkenBackground: 0
qml/Components/Wallpaper.qml+57−2
@@ -1,5 +1,6 @@
1/*
1/*
2 * Copyright (C) 2013-2016 Canonical, Ltd.
2 * Copyright (C) 2013-2016 Canonical, Ltd.
3+ * Copyright (C) 2021 UBports Foundation
3 *
4 *
4 * This program is free software; you can redistribute it and/or modify
5 * 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 * it under the terms of the GNU General Public License as published by
@@ -20,12 +21,66 @@ import Ubuntu.Components 1.3
20
21
21Item {
22Item {
22 id: root
23 id: root
24+ objectName: "Wallpaper"
23− property url source
25+ property url source: ""
26+
27+ // sourceSize should be set to the largest width or height that this
28+ // Wallpaper will likely be set to.
29+ // In most cases inside Lomiri, this should be set to the width or height
30+ // of the shell, whichever is larger.
31+ // However, care should be taken to avoid changing sourceSize once
32+ // it's set.
33+ property real sourceSize: -1.0
24
34
25 Image {
35 Image {
36+ id: image
37+ objectName: "wallpaperImage"
26 anchors.fill: parent
38 anchors.fill: parent
27 fillMode: Image.PreserveAspectCrop
39 fillMode: Image.PreserveAspectCrop
28
40
29− source: root.source
41+ // Setting this to true stops the UI from pausing when the shell's
42+ // dimensions change but causes the image to flash on screen.
43+ asynchronous: false
44+
45+ property real oldSourceSize: 0.0
46+ property real intermediarySourceSize: {
47+ // If minSourceSize is defined by root's parent, it will be 0 until
48+ // the binding resolves. Otherwise, it'll be -1.0. Abuse this
49+ // behavior to avoid changing sourceSize more than once during
50+ // startup
51+ if (root.sourceSize === 0.0) {
52+ developerSanityTimer.start();
53+ // -1.0 signals that the wallpaper shouldn't render yet.
54+ return -1.0
55+ }
56+ developerSanityTimer.stop();
57+ if (root.sourceSize === -1.0 && root.source !== "") {
58+ console.warn(`${root.objectName}'s sourceSize is unset. It will use a lot of memory.`);
59+ return 0.0
60+ }
61+
62+ return root.sourceSize
63+ }
64+
65+ onIntermediarySourceSizeChanged: {
66+ if (oldSourceSize !== 0.0 && oldSourceSize !== -1.0 && root.source !== "") {
67+ console.warn(`${root.objectName} source size is changing from ${oldSourceSize} to ${intermediarySourceSize}. This will cause an expensive image reload.`);
68+ }
69+ oldSourceSize = intermediarySourceSize;
70+ }
71+
72+ sourceSize: Qt.size(intermediarySourceSize, intermediarySourceSize)
73+ source: intermediarySourceSize !== -1.0 ? root.source : ""
74+ }
75+
76+ Timer {
77+ id: developerSanityTimer
78+ interval: 2000
79+ running: false
80+ onTriggered: {
81+ if (root.sourceSize === 0.0 && root.source !== "") {
82+ console.warn(`${root.objectName} has not received its sourceSize yet. It will not render.`);
83+ }
84+ }
30 }
85 }
31}
86}
qml/Components/WallpaperResolver.qml+1
@@ -1,5 +1,6 @@
1/*
1/*
2 * Copyright (C) 2015 Canonical, Ltd.
2 * Copyright (C) 2015 Canonical, Ltd.
3+ * Copyright (C) 2021 UBports Foundation
3 *
4 *
4 * This program is free software; you can redistribute it and/or modify
5 * 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 * it under the terms of the GNU General Public License as published by
qml/Greeter/CoverPage.qml+1
@@ -26,6 +26,7 @@ Showable {
26 property real dragHandleLeftMargin
26 property real dragHandleLeftMargin
27 property real launcherOffset
27 property real launcherOffset
28 property alias background: greeterBackground.source
28 property alias background: greeterBackground.source
29+ property alias backgroundSourceSize: greeterBackground.sourceSize
29 property alias hasCustomBackground: backgroundShade.visible
30 property alias hasCustomBackground: backgroundShade.visible
30 property real panelHeight
31 property real panelHeight
31 property var infographicModel
32 property var infographicModel
qml/Greeter/Greeter.qml+7
@@ -35,6 +35,7 @@ Showable {
35
35
36 property url background
36 property url background
37 property bool hasCustomBackground
37 property bool hasCustomBackground
38+ property real backgroundSourceSize
38
39
39 // How far to offset the top greeter layer during a launcher left-drag
40 // How far to offset the top greeter layer during a launcher left-drag
40 property real launcherOffset
41 property real launcherOffset
@@ -412,6 +413,12 @@ Showable {
412 value: root.background
413 value: root.background
413 }
414 }
414
415
416+ Binding {
417+ target: loader.item
418+ property: "backgroundSourceSize"
419+ value: root.backgroundSourceSize
420+ }
421+
415 Binding {
422 Binding {
416 target: loader.item
423 target: loader.item
417 property: "hasCustomBackground"
424 property: "hasCustomBackground"
qml/Greeter/NarrowView.qml+3
@@ -30,6 +30,7 @@ FocusScope {
30 property alias delayMinutes: delayedLockscreen.delayMinutes
30 property alias delayMinutes: delayedLockscreen.delayMinutes
31 property real panelHeight
31 property real panelHeight
32 property url background
32 property url background
33+ property real backgroundSourceSize
33 property bool hasCustomBackground
34 property bool hasCustomBackground
34 property bool locked
35 property bool locked
35 property alias alphanumeric: loginList.alphanumeric
36 property alias alphanumeric: loginList.alphanumeric
@@ -113,6 +114,7 @@ FocusScope {
113 fill: parent
114 fill: parent
114 }
115 }
115 source: root.background
116 source: root.background
117+ sourceSize: root.backgroundSourceSize
116 }
118 }
117
119
118 // Darken background to match CoverPage
120 // Darken background to match CoverPage
@@ -175,6 +177,7 @@ FocusScope {
175 draggable: !root.waiting
177 draggable: !root.waiting
176 onTease: root.tease()
178 onTease: root.tease()
177 onClicked: hide()
179 onClicked: hide()
180+ backgroundSourceSize: root.backgroundSourceSize
178
181
179 onShowProgressChanged: {
182 onShowProgressChanged: {
180 if (showProgress === 0) {
183 if (showProgress === 0) {
qml/Greeter/WideView.qml+1
@@ -26,6 +26,7 @@ FocusScope {
26 focus: true
26 focus: true
27
27
28 property alias background: coverPage.background
28 property alias background: coverPage.background
29+ property alias backgroundSourceSize: coverPage.backgroundSourceSize
29 property alias panelHeight: coverPage.panelHeight
30 property alias panelHeight: coverPage.panelHeight
30 property alias hasCustomBackground: coverPage.hasCustomBackground
31 property alias hasCustomBackground: coverPage.hasCustomBackground
31 property alias dragHandleLeftMargin: coverPage.dragHandleLeftMargin
32 property alias dragHandleLeftMargin: coverPage.dragHandleLeftMargin
qml/Launcher/Drawer.qml+2
@@ -33,6 +33,7 @@ FocusScope {
33 readonly property Item searchTextField: searchField
33 readonly property Item searchTextField: searchField
34 readonly property real delegateWidth: units.gu(10)
34 readonly property real delegateWidth: units.gu(10)
35 property url background
35 property url background
36+ property alias backgroundSourceSize: background.sourceSize
36 visible: x > -width
37 visible: x > -width
37 property var fullyOpen: x === 0
38 property var fullyOpen: x === 0
38 property var fullyClosed: x === -width
39 property var fullyClosed: x === -width
@@ -159,6 +160,7 @@ FocusScope {
159
160
160 Wallpaper {
161 Wallpaper {
161 id: background
162 id: background
163+ objectName: "drawerBackground"
162 anchors.fill: parent
164 anchors.fill: parent
163 source: root.background
165 source: root.background
164 }
166 }
qml/Launcher/Launcher.qml+1
@@ -35,6 +35,7 @@ FocusScope {
35 property bool drawerEnabled: true
35 property bool drawerEnabled: true
36 property alias privateMode: panel.privateMode
36 property alias privateMode: panel.privateMode
37 property url background
37 property url background
38+ property alias backgroundSourceSize: drawer.backgroundSourceSize
38
39
39 property int panelWidth: units.gu(10)
40 property int panelWidth: units.gu(10)
40 property int dragAreaWidth: units.gu(1)
41 property int dragAreaWidth: units.gu(1)
qml/Shell.qml+18−1
@@ -15,7 +15,7 @@
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
16 */
17
17
18−import QtQuick 2.4
18+import QtQuick 2.8
19import QtQuick.Window 2.2
19import QtQuick.Window 2.2
20import AccountsService 0.1
20import AccountsService 0.1
21import Unity.Application 0.1
21import Unity.Application 0.1
@@ -71,6 +71,20 @@ StyledItem {
71 property bool hasTouchscreen: false
71 property bool hasTouchscreen: false
72 property bool supportsMultiColorLed: true
72 property bool supportsMultiColorLed: true
73
73
74+ // The largest dimension, in pixels, of all of the screens this Shell is
75+ // operating on.
76+ // If a script sets the shell to 240x320 when it was 320x240, we could
77+ // end up in a situation where our dimensions are 240x240 for a short time.
78+ // Notifying the Wallpaper of both events would make it reload the image
79+ // twice. So, we use a Binding { delayed: true }.
80+ property real largestScreenDimension
81+ Binding {
82+ target: shell
83+ delayed: true
84+ property: "largestScreenDimension"
85+ value: Math.max(nativeWidth, nativeHeight)
86+ }
87+
74 // Used by tests
88 // Used by tests
75 property alias lightIndicators: indicatorsModel.light
89 property alias lightIndicators: indicatorsModel.light
76
90
@@ -308,6 +322,7 @@ StyledItem {
308
322
309 dragAreaWidth: shell.edgeSize
323 dragAreaWidth: shell.edgeSize
310 background: wallpaperResolver.background
324 background: wallpaperResolver.background
325+ backgroundSourceSize: shell.largestScreenDimension
311
326
312 applicationManager: ApplicationManager
327 applicationManager: ApplicationManager
313 topLevelSurfaceList: topLevelSurfaceList
328 topLevelSurfaceList: topLevelSurfaceList
@@ -423,6 +438,7 @@ StyledItem {
423 tabletMode: shell.usageScenario != "phone"
438 tabletMode: shell.usageScenario != "phone"
424 forcedUnlock: wizard.active || shell.mode === "full-shell"
439 forcedUnlock: wizard.active || shell.mode === "full-shell"
425 background: wallpaperResolver.background
440 background: wallpaperResolver.background
441+ backgroundSourceSize: shell.largestScreenDimension
426 hasCustomBackground: wallpaperResolver.hasCustomBackground
442 hasCustomBackground: wallpaperResolver.hasCustomBackground
427 allowFingerprint: !dialogs.hasActiveDialog &&
443 allowFingerprint: !dialogs.hasActiveDialog &&
428 !notifications.topmostIsFullscreen &&
444 !notifications.topmostIsFullscreen &&
@@ -588,6 +604,7 @@ StyledItem {
588 drawerEnabled: !greeter.active && tutorial.launcherLongSwipeEnabled
604 drawerEnabled: !greeter.active && tutorial.launcherLongSwipeEnabled
589 privateMode: greeter.active
605 privateMode: greeter.active
590 background: wallpaperResolver.background
606 background: wallpaperResolver.background
607+ backgroundSourceSize: shell.largestScreenDimension
591
608
592 // It can be assumed that the Launcher and Panel would overlap if
609 // It can be assumed that the Launcher and Panel would overlap if
593 // the Panel is open and taking up the full width of the shell
610 // the Panel is open and taking up the full width of the shell
qml/Stage/Stage.qml+3
@@ -1,5 +1,6 @@
1/*
1/*
2 * Copyright (C) 2014-2017 Canonical, Ltd.
2 * Copyright (C) 2014-2017 Canonical, Ltd.
3+ * Copyright (C) 2021 UBports Foundation
3 *
4 *
4 * This program is free software; you can redistribute it and/or modify
5 * 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 * it under the terms of the GNU General Public License as published by
@@ -36,6 +37,7 @@ FocusScope {
36 property QtObject topLevelSurfaceList
37 property QtObject topLevelSurfaceList
37 property bool altTabPressed
38 property bool altTabPressed
38 property url background
39 property url background
40+ property alias backgroundSourceSize: wallpaper.sourceSize
39 property int dragAreaWidth
41 property int dragAreaWidth
40 property real nativeHeight
42 property real nativeHeight
41 property real nativeWidth
43 property real nativeWidth
@@ -625,6 +627,7 @@ FocusScope {
625
627
626 Wallpaper {
628 Wallpaper {
627 id: wallpaper
629 id: wallpaper
630+ objectName: "stageBackground"
628 anchors.fill: parent
631 anchors.fill: parent
629 source: root.background
632 source: root.background
630 // Make sure it's the lowest item. Due to the left edge drag we sometimes need
633 // Make sure it's the lowest item. Due to the left edge drag we sometimes need
tests/data/unity/backgrounds/20x30.pngnew file

Binary file not shown.

tests/qmltests/CMakeLists.txt+1
@@ -23,6 +23,7 @@ add_unity8_qmltest(Components Rating)
23add_unity8_qmltest(Components SharingPicker)
23add_unity8_qmltest(Components SharingPicker)
24add_unity8_qmltest(Components Showable)
24add_unity8_qmltest(Components Showable)
25add_unity8_qmltest(Components VirtualTouchPad)
25add_unity8_qmltest(Components VirtualTouchPad)
26+add_unity8_qmltest(Components Wallpaper)
26add_unity8_qmltest(Components WallpaperResolver)
27add_unity8_qmltest(Components WallpaperResolver)
27add_unity8_qmltest(Components ZoomableImage)
28add_unity8_qmltest(Components ZoomableImage)
28add_unity8_qmltest(Greeter Clock)
29add_unity8_qmltest(Greeter Clock)
tests/qmltests/Components/tst_Wallpaper.qmlnew file+104
@@ -0,0 +1,104 @@
1+/*
2+ * Copyright (C) 2021 UBports Foundation
3+ *
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.
7+ *
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.
12+ *
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/>.
15+ */
16+
17+import QtQuick 2.4
18+import QtTest 1.0
19+import Ubuntu.Components 1.3
20+import Unity.Test 0.1
21+
22+import "../../../qml/Components"
23+
24+
25+Item {
26+ id: root
27+ width: 150
28+ height: 100
29+
30+ property url defaultBackground: "../../data/unity/backgrounds/20x30.png"
31+ property real sourceSize: 0
32+
33+ Component {
34+ id: wallpaperComponent
35+ Wallpaper {
36+ anchors.fill: parent
37+ source: root.defaultBackground
38+ sourceSize: root.sourceSize
39+ }
40+ }
41+
42+ Component {
43+ id: unsetSourceSizeWallpaperComponent
44+ Wallpaper {
45+ anchors.fill: parent
46+ source: root.defaultBackground
47+ }
48+ }
49+
50+ UnityTestCase {
51+ id: testCase
52+ name: "Wallpaper"
53+ when: windowShown
54+
55+ function init() {
56+ root.sourceSize = 0;
57+ }
58+
59+ // Ensures that the Wallpaper's sourceSize causes the image to be
60+ // scaled while matching its original aspect ratio.
61+ function test_wallpaperGetsSourceSize() {
62+ root.sourceSize = 10;
63+ var wallpaper = createTemporaryObject(wallpaperComponent, root);
64+ var image = findChild(wallpaper, "wallpaperImage");
65+ verify(image);
66+ tryCompare(image, "status", Image.Ready);
67+ tryCompare(image, "implicitWidth", 10.0);
68+ tryCompare(image, "implicitHeight", 15.0);
69+ }
70+
71+ function test_warningOnSizeChange() {
72+ root.sourceSize = 1;
73+ var wallpaper = createTemporaryObject(wallpaperComponent, root);
74+ ignoreWarning(new RegExp(".*source size is changing.*"));
75+ root.sourceSize = 10;
76+ }
77+
78+ // Ensures the Wallpaper's sourceSize is unbounded and the image loads
79+ // when sourceSize is unset
80+ function test_sizeUnset() {
81+ ignoreWarning(new RegExp(".*sourceSize is unset.*"));
82+ var wallpaper = createTemporaryObject(unsetSourceSizeWallpaperComponent, root);
83+ var image = findChild(wallpaper, "wallpaperImage");
84+ // Reading the property is enough to make sure the warning happened
85+ tryCompare(image, "status", Image.Ready);
86+ tryCompare(image, "implicitWidth", 20.0);
87+ tryCompare(image, "implicitHeight", 30.0);
88+ }
89+
90+ function test_warningOnSizeBindingFailed() {
91+ // When a binding fails, it will be undefined or zero. Since
92+ // a 'real' property is read as zero when it is undefined anyway,
93+ // use that.
94+ root.sourceSize = 0;
95+ ignoreWarning(new RegExp(".*will not render.*"));
96+ var wallpaper = createTemporaryObject(wallpaperComponent, root);
97+ // A SignalSpy waiting for the signal that causes the warning would
98+ // be more correct, but hooking up the SignalSpy is sometimes slower
99+ // than the event triggering the warning and we end up waiting
100+ // forever.
101+ wait(3000);
102+ }
103+ }
104+}
tests/qmltests/tst_Shell.qml+1−1
@@ -1,6 +1,6 @@
1/*
1/*
2 * Copyright (C) 2013-2016 Canonical, Ltd.
2 * Copyright (C) 2013-2016 Canonical, Ltd.
3− * Copyright (C) 2019 UBports Foundation
3+ * Copyright (C) 2019-2021 UBports Foundation
4 *
4 *
5 * This program is free software; you can redistribute it and/or modify
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
6 * it under the terms of the GNU General Public License as published by