Unity 8
ApplicationWindow.qml
1 /*
2  * Copyright 2014-2016 Canonical Ltd.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
12  *
13  * You should have received a copy of the GNU Lesser 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 Ubuntu.Components 1.3
19 import Unity.Application 0.1
20 
21 FocusScope {
22  id: root
23  implicitWidth: surfaceContainer.implicitWidth
24  implicitHeight: surfaceContainer.implicitHeight
25 
26  // to be read from outside
27  property alias interactive: surfaceContainer.interactive
28  property bool orientationChangesEnabled: d.supportsSurfaceResize ? d.surfaceOldEnoughToBeResized : true
29  readonly property string title: surface && surface.name !== "" ? surface.name : d.name
30 
31  // overridable from outside
32  property bool fullscreen: {
33  if (surface) {
34  return surface.state === Mir.FullscreenState;
35  } else if (application) {
36  return application.fullscreen;
37  } else {
38  return false;
39  }
40  }
41 
42  // to be set from outside
43  property QtObject surface
44  property QtObject application
45  property int surfaceOrientationAngle
46  property alias resizeSurface: surfaceContainer.resizeSurface
47  property int requestedWidth: -1
48  property int requestedHeight: -1
49 
50  readonly property int minimumWidth: surface ? surface.minimumWidth : 0
51  readonly property int minimumHeight: surface ? surface.minimumHeight : 0
52  readonly property int maximumWidth: surface ? surface.maximumWidth : 0
53  readonly property int maximumHeight: surface ? surface.maximumHeight : 0
54  readonly property int widthIncrement: surface ? surface.widthIncrement : 0
55  readonly property int heightIncrement: surface ? surface.heightIncrement : 0
56 
57  onSurfaceChanged: {
58  // The order in which the instructions are executed here matters, to that the correct state
59  // transitions in stateGroup take place.
60  // More specifically, the moment surfaceContainer.surface gets updated relative to the
61  // other instructions.
62  if (surface) {
63  surfaceContainer.surface = surface;
64  d.liveSurface = surface.live;
65  d.hadSurface = false;
66  surfaceInitTimer.start();
67  } else {
68  if (d.surfaceInitialized) {
69  d.hadSurface = true;
70  }
71  d.surfaceInitialized = false;
72  surfaceContainer.surface = null;
73  }
74  }
75 
76  QtObject {
77  id: d
78 
79  property bool liveSurface: false;
80  property var con: Connections {
81  target: root.surface
82  onLiveChanged: d.liveSurface = root.surface.live
83  }
84  // using liveSurface instead of root.surface.live because with the latter
85  // this expression is not reevaluated when root.surface changes
86  readonly property bool needToTakeScreenshot: root.surface && d.surfaceInitialized && !d.liveSurface
87  && applicationState !== ApplicationInfoInterface.Running
88  onNeedToTakeScreenshotChanged: {
89  if (needToTakeScreenshot && screenshotImage.status === Image.Null) {
90  screenshotImage.take();
91  }
92  }
93 
94  // helpers so that we don't have to check for the existence of an application everywhere
95  // (in order to avoid breaking qml binding due to a javascript exception)
96  readonly property string name: root.application ? root.application.name : ""
97  readonly property url icon: root.application ? root.application.icon : ""
98  readonly property int applicationState: root.application ? root.application.state : -1
99  readonly property string splashTitle: root.application ? root.application.splashTitle : ""
100  readonly property url splashImage: root.application ? root.application.splashImage : ""
101  readonly property bool splashShowHeader: root.application ? root.application.splashShowHeader : true
102  readonly property color splashColor: root.application ? root.application.splashColor : "#00000000"
103  readonly property color splashColorHeader: root.application ? root.application.splashColorHeader : "#00000000"
104  readonly property color splashColorFooter: root.application ? root.application.splashColorFooter : "#00000000"
105 
106  // Whether the Application had a surface before but lost it.
107  property bool hadSurface: false
108 
109  //FIXME - this is a hack to avoid the first few rendered frames as they
110  // might show the UI accommodating due to surface resizes on startup.
111  // Remove this when possible
112  property bool surfaceInitialized: false
113 
114  readonly property bool supportsSurfaceResize:
115  application &&
116  ((application.supportedOrientations & Qt.PortraitOrientation)
117  || (application.supportedOrientations & Qt.InvertedPortraitOrientation))
118  &&
119  ((application.supportedOrientations & Qt.LandscapeOrientation)
120  || (application.supportedOrientations & Qt.InvertedLandscapeOrientation))
121 
122  property bool surfaceOldEnoughToBeResized: false
123 
124  property var focusedSurface: {
125  if (promptSurfacesRepeater.count == 0) {
126  return surfaceContainer;
127  } else {
128  return promptSurfacesRepeater.itemAt(promptSurfacesRepeater.count - 1);
129  }
130  }
131  onFocusedSurfaceChanged: {
132  focusedSurface.focus = true;
133  }
134  }
135 
136  Binding {
137  target: root.application
138  property: "initialSurfaceSize"
139  value: Qt.size(root.requestedWidth, root.requestedHeight)
140  }
141 
142  Timer {
143  id: surfaceInitTimer
144  interval: 100
145  onTriggered: {
146  if (root.surface && root.surface.live) {d.surfaceInitialized = true;}
147  }
148  }
149 
150  Timer {
151  id: surfaceIsOldTimer
152  interval: 1000
153  onTriggered: { if (stateGroup.state === "surface") { d.surfaceOldEnoughToBeResized = true; } }
154  }
155 
156  Image {
157  id: screenshotImage
158  objectName: "screenshotImage"
159  anchors.fill: parent
160  antialiasing: !root.interactive
161 
162  function take() {
163  // Save memory by using a half-resolution (thus quarter size) screenshot.
164  // Do not make this a binding, we can only take the screenshot once!
165  surfaceContainer.grabToImage(
166  function(result) {
167  screenshotImage.source = result.url;
168  },
169  Qt.size(root.width / 2, root.height / 2));
170  }
171  }
172 
173  Loader {
174  id: splashLoader
175  visible: active
176  active: false
177  anchors.fill: parent
178  sourceComponent: Component {
179  Splash {
180  id: splash
181  title: d.splashTitle ? d.splashTitle : d.name
182  imageSource: d.splashImage
183  icon: d.icon
184  showHeader: d.splashShowHeader
185  backgroundColor: d.splashColor
186  headerColor: d.splashColorHeader
187  footerColor: d.splashColorFooter
188  }
189  }
190  }
191 
192  SurfaceContainer {
193  id: surfaceContainer
194  requestedWidth: root.requestedWidth
195  requestedHeight: root.requestedHeight
196  surfaceOrientationAngle: application && application.rotatesWindowContents ? root.surfaceOrientationAngle : 0
197  }
198 
199  Repeater {
200  id: promptSurfacesRepeater
201  objectName: "promptSurfacesRepeater"
202  // show only along with the top-most application surface
203  model: {
204  if (root.application && root.surface === root.application.surfaceList.first) {
205  return root.application.promptSurfaceList;
206  } else {
207  return null;
208  }
209  }
210  delegate: SurfaceContainer {
211  interactive: index == (promptSurfacesRepeater.count - 1) && root.interactive
212  surface: model.surface
213  width: root.width
214  height: root.height
215  isPromptSurface: true
216  }
217  }
218 
219  // SurfaceContainer size drives ApplicationWindow size
220  Binding {
221  target: root; property: "width"
222  value: stateGroup.state === "surface" ? surfaceContainer.width : root.requestedWidth
223  when: root.requestedWidth >= 0
224  }
225  Binding {
226  target: root; property: "height"
227  value: stateGroup.state === "surface" ? surfaceContainer.height : root.requestedHeight
228  when: root.requestedHeight >= 0
229  }
230 
231  // ApplicationWindow size drives SurfaceContainer size
232  Binding {
233  target: surfaceContainer; property: "width"; value: root.width
234  when: root.requestedWidth < 0
235  }
236  Binding {
237  target: surfaceContainer; property: "height"; value: root.height
238  when: root.requestedHeight < 0
239  }
240 
241  StateGroup {
242  id: stateGroup
243  objectName: "applicationWindowStateGroup"
244  states: [
245  State {
246  name: "void"
247  when:
248  d.hadSurface && (!root.surface || !d.surfaceInitialized)
249  &&
250  screenshotImage.status !== Image.Ready
251  },
252  State {
253  name: "splashScreen"
254  when:
255  !d.hadSurface && (!root.surface || !d.surfaceInitialized)
256  &&
257  screenshotImage.status !== Image.Ready
258  },
259  State {
260  name: "surface"
261  when:
262  (root.surface && d.surfaceInitialized)
263  &&
264  (d.liveSurface ||
265  (d.applicationState !== ApplicationInfoInterface.Running
266  && screenshotImage.status !== Image.Ready))
267  },
268  State {
269  name: "screenshot"
270  when:
271  screenshotImage.status === Image.Ready
272  &&
273  (d.applicationState !== ApplicationInfoInterface.Running
274  || !root.surface || !d.surfaceInitialized)
275  },
276  State {
277  // This is a dead end. From here we expect the surface to be removed from the model
278  // shortly after we stop referencing to it in our SurfaceContainer.
279  name: "closed"
280  when:
281  // The surface died while the application is running. It must have been closed
282  // by the shell or the application decided to destroy it by itself
283  root.surface && d.surfaceInitialized && !d.liveSurface
284  && d.applicationState === ApplicationInfoInterface.Running
285  }
286  ]
287 
288  transitions: [
289  Transition {
290  from: ""; to: "splashScreen"
291  PropertyAction { target: splashLoader; property: "active"; value: true }
292  PropertyAction { target: surfaceContainer
293  property: "visible"; value: false }
294  },
295  Transition {
296  from: "splashScreen"; to: "surface"
297  SequentialAnimation {
298  PropertyAction { target: surfaceContainer
299  property: "opacity"; value: 0.0 }
300  PropertyAction { target: surfaceContainer
301  property: "visible"; value: true }
302  UbuntuNumberAnimation { target: surfaceContainer; property: "opacity";
303  from: 0.0; to: 1.0
304  duration: UbuntuAnimation.BriskDuration }
305  ScriptAction { script: {
306  splashLoader.active = false;
307  surfaceIsOldTimer.start();
308  } }
309  }
310  },
311  Transition {
312  from: "surface"; to: "splashScreen"
313  SequentialAnimation {
314  ScriptAction { script: {
315  surfaceIsOldTimer.stop();
316  d.surfaceOldEnoughToBeResized = false;
317  splashLoader.active = true;
318  surfaceContainer.visible = true;
319  } }
320  UbuntuNumberAnimation { target: splashLoader; property: "opacity";
321  from: 0.0; to: 1.0
322  duration: UbuntuAnimation.BriskDuration }
323  PropertyAction { target: surfaceContainer
324  property: "visible"; value: false }
325  }
326  },
327  Transition {
328  from: "surface"; to: "screenshot"
329  SequentialAnimation {
330  ScriptAction { script: {
331  surfaceIsOldTimer.stop();
332  d.surfaceOldEnoughToBeResized = false;
333  screenshotImage.visible = true;
334  } }
335  UbuntuNumberAnimation { target: screenshotImage; property: "opacity";
336  from: 0.0; to: 1.0
337  duration: UbuntuAnimation.BriskDuration }
338  ScriptAction { script: {
339  surfaceContainer.visible = false;
340  surfaceContainer.surface = null;
341  d.hadSurface = true;
342  } }
343  }
344  },
345  Transition {
346  from: "screenshot"; to: "surface"
347  SequentialAnimation {
348  PropertyAction { target: surfaceContainer
349  property: "visible"; value: true }
350  UbuntuNumberAnimation { target: screenshotImage; property: "opacity";
351  from: 1.0; to: 0.0
352  duration: UbuntuAnimation.BriskDuration }
353  ScriptAction { script: {
354  screenshotImage.visible = false;
355  screenshotImage.source = "";
356  surfaceIsOldTimer.start();
357  } }
358  }
359  },
360  Transition {
361  from: "splashScreen"; to: "screenshot"
362  SequentialAnimation {
363  PropertyAction { target: screenshotImage
364  property: "visible"; value: true }
365  UbuntuNumberAnimation { target: screenshotImage; property: "opacity";
366  from: 0.0; to: 1.0
367  duration: UbuntuAnimation.BriskDuration }
368  PropertyAction { target: splashLoader; property: "active"; value: false }
369  }
370  },
371  Transition {
372  from: "surface"; to: "void"
373  ScriptAction { script: {
374  surfaceIsOldTimer.stop();
375  d.surfaceOldEnoughToBeResized = false;
376  surfaceContainer.visible = false;
377  } }
378  },
379  Transition {
380  from: "void"; to: "surface"
381  SequentialAnimation {
382  PropertyAction { target: surfaceContainer; property: "opacity"; value: 0.0 }
383  PropertyAction { target: surfaceContainer; property: "visible"; value: true }
384  UbuntuNumberAnimation { target: surfaceContainer; property: "opacity";
385  from: 0.0; to: 1.0
386  duration: UbuntuAnimation.BriskDuration }
387  ScriptAction { script: {
388  surfaceIsOldTimer.start();
389  } }
390  }
391  },
392  Transition {
393  to: "closed"
394  SequentialAnimation {
395  ScriptAction { script: {
396  surfaceContainer.visible = false;
397  surfaceContainer.surface = null;
398  d.hadSurface = true;
399  } }
400  }
401  }
402  ]
403  }
404 
405 }