Unity 8
Spread.qml
1 /*
2  * Copyright (C) 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 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 Ubuntu.Components 1.3
19 import "MathUtils.js" as MathUtils
20 
21 Item {
22  id: root
23 
24  // Information about the environment
25  property int highlightedIndex: -1
26  property var model: null
27  property int leftMargin: 0
28  property var spreadFlickable
29 
30  // some config options
31  property real contentMargin: 0.1 * root.height
32  property real contentTopMargin: contentMargin + root.y + windowTitle.height
33  property real contentBottomMargin: contentMargin
34  property real windowTitleTopMargin: contentMargin - windowTitle.height
35  property int stackItemCount: 3
36  property real leftRotationAngle: 22
37  property real rightRotationAngle: 32
38  property real leftStackScale: .82
39  property real rightStackScale: 1
40  property real rightEdgeBreakPoint: Math.min(units.gu(40) / root.width, .35)
41 
42  signal leaveSpread()
43  signal closeCurrentApp();
44 
45  // Calculated stuff
46  readonly property int totalItemCount: model.count
47  readonly property real leftStackXPos: 0.03 * root.width + leftMargin
48  readonly property real rightStackXPos: root.width - 1.5 * leftStackXPos + leftMargin
49 
50  readonly property real stackHeight: spreadItemHeight - appInfoHeight
51  readonly property real stackWidth: Math.min(leftStackXPos/3, units.gu(1.5))
52 
53  readonly property real spreadWidth: rightStackXPos - leftStackXPos
54  readonly property real spreadHeight: root.height
55  readonly property real spreadItemHeight: spreadHeight - contentMargin * 2
56  readonly property real spreadItemWidth: stackHeight
57 
58  readonly property real dynamicLeftRotationAngle: leftRotationAngle * rotationAngleFactor
59  readonly property real dynamicRightRotationAngle: rightRotationAngle * rotationAngleFactor
60 
61  readonly property real appInfoHeight: {
62  var screenHeightReferencePoint = 40 // ref screen height in gu
63  var valueAtReferencePoint = 0.17 // of screen height at the reference point
64  var appInfoHeightValueChange = -0.0014 // units / gu
65  var minAppInfoHeight = 0.08
66  var maxAppInfoHeight = 0.2
67  var screenHeightInGU = root.height / units.gu(1) // screenHeight in gu
68 
69  return MathUtils.clamp(valueAtReferencePoint + appInfoHeightValueChange * (screenHeightInGU - screenHeightReferencePoint), minAppInfoHeight, maxAppInfoHeight) * root.height
70  }
71 
72  property real rotationAngleFactor: {
73  var spreadHeightReferencePoint = 28 // reference spread height in gu
74  var valueAtReferencePoint = 1.3
75  var rotationAngleValueChange = -0.008 // units / gu
76  var minRotationAngleFactor = 0.6
77  var maxRotationAngleFactor = 1.5
78  var spreadHeightInGU = spreadHeight / units.gu(1)
79 
80  return MathUtils.clamp(valueAtReferencePoint + rotationAngleValueChange * (spreadHeightInGU - spreadHeightReferencePoint), minRotationAngleFactor, maxRotationAngleFactor)
81  }
82  readonly property real itemOverlap: {
83  var spreadAspectRatioReferencePoint = 1.0 // ref screen height in gu
84  var valueAtReferencePoint = 0.74 // of screen height at the reference point
85  var itemOverlapValueChange = -0.068
86  var minOverlap = 0.55
87  var maxOverlap = 0.82
88  var spreadAspectRatio = spreadWidth / stackHeight // spread stack aspect ratio (app info not included)
89 
90  return MathUtils.clamp(valueAtReferencePoint + itemOverlapValueChange * (spreadAspectRatio - spreadAspectRatioReferencePoint), minOverlap, maxOverlap)
91  }
92 
93  readonly property real visibleItemCount: (spreadWidth / spreadItemWidth) / (1 - itemOverlap)
94 
95  readonly property real spreadTotalWidth: Math.max(2,totalItemCount) * spreadWidth / visibleItemCount
96 
97  readonly property real centeringOffset: Math.max(spreadWidth - spreadTotalWidth + (leftStackXPos - leftMargin) * 2, 0) / (2 * spreadWidth)
98 
99  readonly property var curve: BezierCurve {
100  controlPoint2: {'x': 0.19, 'y': 0.00}
101  controlPoint3: {'x': 0.91, 'y': 1.00}
102  }
103 
104  Label {
105  id: windowTitle
106 
107  width: Math.min(implicitWidth, 0.5*root.width)
108  elide: Qt.ElideMiddle
109  anchors.horizontalCenter: parent.horizontalCenter
110  y: windowTitleTopMargin
111  readonly property var highlightedSurface: root.model ? root.model.surfaceAt(root.highlightedIndex) : null
112  readonly property var highlightedApp: root.model ? root.model.applicationAt(root.highlightedIndex) : null
113  text: root.highlightedIndex >= 0 && highlightedSurface && highlightedSurface.name != "" ? highlightedSurface.name :
114  highlightedApp ? highlightedApp.name : ""
115  fontSize: root.height < units.gu(85) ? 'medium' : 'large'
116  color: "white"
117  opacity: root.highlightedIndex >= 0 ? 1 : 0
118  Behavior on opacity { UbuntuNumberAnimation { } }
119  }
120 
121  readonly property int itemCount: root.model.count
122  onItemCountChanged: {
123  if (highlightedIndex >= itemCount) {
124  highlightedIndex = itemCount - 1
125  }
126  }
127 
128  Keys.onPressed: {
129  switch (event.key) {
130  case Qt.Key_Left:
131  case Qt.Key_Backtab:
132  selectPrevious(event.isAutoRepeat)
133  event.accepted = true;
134  break;
135  case Qt.Key_Right:
136  case Qt.Key_Tab:
137  selectNext(event.isAutoRepeat)
138  event.accepted = true;
139  break;
140  case Qt.Key_Q:
141  closeCurrentApp();
142  break;
143  case Qt.Key_Escape:
144  highlightedIndex = -1
145  // Falling through intentionally
146  case Qt.Key_Enter:
147  case Qt.Key_Return:
148  case Qt.Key_Space:
149  root.leaveSpread();
150  event.accepted = true;
151  }
152  }
153 
154 
155  function selectNext(isAutoRepeat) {
156  if (isAutoRepeat && highlightedIndex >= totalItemCount -1) {
157  return; // AutoRepeat is not allowed to wrap around
158  }
159 
160  highlightedIndex = (highlightedIndex + 1) % totalItemCount;
161  spreadFlickable.snap(highlightedIndex)
162  }
163 
164  function selectPrevious(isAutoRepeat) {
165  if (isAutoRepeat && highlightedIndex == 0) {
166  return; // AutoRepeat is not allowed to wrap around
167  }
168 
169  highlightedIndex = highlightedIndex - 1 >= 0 ? highlightedIndex - 1 : totalItemCount - 1;
170  spreadFlickable.snap(highlightedIndex)
171  }
172 }