Unity 8
IndicatorsMenu.qml
1 /*
2  * Copyright (C) 2014 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.2
18 import Ubuntu.Components 1.1
19 import Ubuntu.Gestures 0.1
20 import "../Components"
21 import "Indicators"
22 
23 Showable {
24  id: root
25  property alias indicatorsModel: bar.indicatorsModel
26  property alias showDragHandle: __showDragHandle
27  property alias hideDragHandle: __hideDragHandle
28  property alias overFlowWidth: bar.overFlowWidth
29  property alias verticalVelocityThreshold: yVelocityCalculator.velocityThreshold
30  property int minimizedPanelHeight: units.gu(3)
31  property int expandedPanelHeight: units.gu(7)
32  property real openedHeight: units.gu(71)
33  readonly property real unitProgress: Math.max(0, (height - minimizedPanelHeight) / (openedHeight - minimizedPanelHeight))
34  readonly property bool fullyOpened: unitProgress >= 1
35  readonly property bool partiallyOpened: unitProgress > 0 && unitProgress < 1.0
36  readonly property bool fullyClosed: unitProgress == 0
37  property bool enableHint: true
38  property bool contentEnabled: true
39  property bool showOnClick: true
40  property color panelColor: "black"
41 
42  signal showTapped(point position)
43 
44  // TODO: Perhaps we need a animation standard for showing/hiding? Each showable seems to
45  // use its own values. Need to ask design about this.
46  showAnimation: StandardAnimation {
47  property: "height"
48  to: openedHeight
49  duration: UbuntuAnimation.BriskDuration
50  easing.type: Easing.OutCubic
51  }
52 
53  hideAnimation: StandardAnimation {
54  property: "height"
55  to: minimizedPanelHeight
56  duration: UbuntuAnimation.BriskDuration
57  easing.type: Easing.OutCubic
58  }
59 
60  height: minimizedPanelHeight
61 
62  onUnitProgressChanged: d.updateState()
63  clip: root.partiallyOpened
64 
65  IndicatorsLight {
66  id: indicatorLights
67  }
68 
69  // eater
70  MouseArea {
71  anchors.fill: parent
72  hoverEnabled: true
73  }
74 
75  MenuContent {
76  id: content
77  objectName: "menuContent"
78 
79  anchors {
80  left: parent.left
81  right: parent.right
82  top: bar.bottom
83  }
84  height: openedHeight - bar.height - handle.height
85  indicatorsModel: root.indicatorsModel
86  visible: root.unitProgress > 0
87  enabled: contentEnabled
88  currentMenuIndex: bar.currentItemIndex
89  }
90 
91  Handle {
92  id: handle
93  objectName: "handle"
94  anchors {
95  left: parent.left
96  right: parent.right
97  bottom: parent.bottom
98  }
99  height: units.gu(2)
100  active: d.activeDragHandle ? true : false
101 
102  //small shadow gradient at bottom of menu
103  Rectangle {
104  anchors {
105  left: parent.left
106  right: parent.right
107  bottom: parent.top
108  }
109  height: units.gu(0.5)
110  gradient: Gradient {
111  GradientStop { position: 0.0; color: "transparent" }
112  GradientStop { position: 1.0; color: "black" }
113  }
114  opacity: 0.3
115  }
116  }
117 
118  Rectangle {
119  anchors.fill: bar
120  color: panelColor
121  }
122 
123  IndicatorsBar {
124  id: bar
125  objectName: "indicatorsBar"
126 
127  anchors {
128  left: parent.left
129  right: parent.right
130  }
131  expanded: false
132  enableLateralChanges: false
133  lateralPosition: -1
134  unitProgress: root.unitProgress
135 
136  height: expanded ? expandedPanelHeight : minimizedPanelHeight
137  Behavior on height { NumberAnimation { duration: UbuntuAnimation.SnapDuration; easing: UbuntuAnimation.StandardEasing } }
138  }
139 
140  ScrollCalculator {
141  id: leftScroller
142  width: units.gu(5)
143  anchors.left: bar.left
144  height: bar.height
145 
146  forceScrollingPercentage: 0.33
147  stopScrollThreshold: units.gu(0.75)
148  direction: Qt.RightToLeft
149  lateralPosition: -1
150 
151  onScroll: bar.addScrollOffset(-scrollAmount);
152  }
153 
154  ScrollCalculator {
155  id: rightScroller
156  width: units.gu(5)
157  anchors.right: bar.right
158  height: bar.height
159 
160  forceScrollingPercentage: 0.33
161  stopScrollThreshold: units.gu(0.75)
162  direction: Qt.LeftToRight
163  lateralPosition: -1
164 
165  onScroll: bar.addScrollOffset(scrollAmount);
166  }
167 
168  MouseArea {
169  anchors.bottom: parent.bottom
170  anchors.left: parent.left
171  anchors.right: parent.right
172  height: minimizedPanelHeight
173  enabled: __showDragHandle.enabled && showOnClick
174  onClicked: {
175  bar.selectItemAt(mouseX)
176  root.show()
177  }
178  }
179 
180  DragHandle {
181  id: __showDragHandle
182  objectName: "showDragHandle"
183  anchors.bottom: parent.bottom
184  anchors.left: parent.left
185  anchors.right: parent.right
186  height: minimizedPanelHeight
187  direction: Direction.Downwards
188  enabled: !root.shown && root.available
189  autoCompleteDragThreshold: maxTotalDragDistance / 2
190  stretch: true
191 
192  onPressedChanged: {
193  if (pressed) {
194  touchPressTime = new Date().getTime();
195  } else {
196  var touchReleaseTime = new Date().getTime();
197  if (touchReleaseTime - touchPressTime <= 300) {
198  root.showTapped(Qt.point(touchSceneX, touchSceneY));
199  }
200  }
201  }
202  property var touchPressTime
203 
204  // using hint regulates minimum to hint displacement, but in fullscreen mode, we need to do it manually.
205  overrideStartValue: enableHint ? minimizedPanelHeight : expandedPanelHeight + handle.height
206  maxTotalDragDistance: openedHeight - (enableHint ? minimizedPanelHeight : expandedPanelHeight + handle.height)
207  hintDisplacement: enableHint ? expandedPanelHeight - minimizedPanelHeight + handle.height : 0
208  }
209 
210  MouseArea {
211  anchors.fill: __hideDragHandle
212  enabled: __hideDragHandle.enabled
213  onClicked: root.hide()
214  }
215 
216  DragHandle {
217  id: __hideDragHandle
218  objectName: "hideDragHandle"
219  anchors.fill: handle
220  direction: Direction.Upwards
221  enabled: root.shown && root.available
222  hintDisplacement: units.gu(3)
223  autoCompleteDragThreshold: maxTotalDragDistance / 6
224  stretch: true
225  maxTotalDragDistance: openedHeight - expandedPanelHeight - handle.height
226 
227  onTouchSceneXChanged: {
228  if (root.state === "locked") {
229  d.xDisplacementSinceLock += (touchSceneX - d.lastHideTouchSceneX)
230  d.lastHideTouchSceneX = touchSceneX;
231  }
232  }
233  }
234 
235  PanelVelocityCalculator {
236  id: yVelocityCalculator
237  velocityThreshold: d.hasCommitted ? 0.1 : 0.3
238  trackedValue: d.activeDragHandle ? d.activeDragHandle.touchSceneY : 0
239 
240  onVelocityAboveThresholdChanged: d.updateState()
241  }
242 
243  Connections {
244  target: showAnimation
245  onRunningChanged: {
246  if (showAnimation.running) {
247  root.state = "commit";
248  }
249  }
250  }
251 
252  Connections {
253  target: hideAnimation
254  onRunningChanged: {
255  if (hideAnimation.running) {
256  root.state = "initial";
257  }
258  }
259  }
260 
261  QtObject {
262  id: d
263  property var activeDragHandle: showDragHandle.dragging ? showDragHandle : hideDragHandle.dragging ? hideDragHandle : null
264  property bool hasCommitted: false
265  property real lastHideTouchSceneX: 0
266  property real xDisplacementSinceLock: 0
267  onXDisplacementSinceLockChanged: d.updateState()
268 
269  property real rowMappedLateralPosition: {
270  if (!d.activeDragHandle) return -1;
271  return d.activeDragHandle.mapToItem(bar, d.activeDragHandle.touchX, 0).x;
272  }
273 
274  function updateState() {
275  if (!showAnimation.running && !hideAnimation.running && d.activeDragHandle) {
276  if (unitProgress <= 0) {
277  root.state = "initial";
278  // lock indicator if we've been committed and aren't moving too much laterally or too fast up.
279  } else if (d.hasCommitted && (Math.abs(d.xDisplacementSinceLock) < units.gu(2) || yVelocityCalculator.velocityAboveThreshold)) {
280  root.state = "locked";
281  } else {
282  root.state = "reveal";
283  }
284  }
285  }
286  }
287 
288  states: [
289  State {
290  name: "initial"
291  PropertyChanges { target: d; hasCommitted: false; restoreEntryValues: false }
292  },
293  State {
294  name: "reveal"
295  StateChangeScript {
296  script: {
297  yVelocityCalculator.reset();
298  // initial item selection
299  if (!d.hasCommitted) bar.selectItemAt(d.activeDragHandle ? d.activeDragHandle.touchX : -1);
300  d.hasCommitted = false;
301  }
302  }
303  PropertyChanges {
304  target: bar
305  expanded: true
306  // changes to lateral touch position effect which indicator is selected
307  lateralPosition: d.rowMappedLateralPosition
308  // vertical velocity determines if changes in lateral position has an effect
309  enableLateralChanges: d.activeDragHandle &&
310  !yVelocityCalculator.velocityAboveThreshold
311  }
312  // left scroll bar handling
313  PropertyChanges {
314  target: leftScroller
315  lateralPosition: {
316  if (!d.activeDragHandle) return -1;
317  var mapped = d.activeDragHandle.mapToItem(leftScroller, d.activeDragHandle.touchX, 0);
318  return mapped.x;
319  }
320  }
321  // right scroll bar handling
322  PropertyChanges {
323  target: rightScroller
324  lateralPosition: {
325  if (!d.activeDragHandle) return -1;
326  var mapped = d.activeDragHandle.mapToItem(rightScroller, d.activeDragHandle.touchX, 0);
327  return mapped.x;
328  }
329  }
330  },
331  State {
332  name: "locked"
333  StateChangeScript {
334  script: {
335  d.xDisplacementSinceLock = 0;
336  d.lastHideTouchSceneX = hideDragHandle.touchSceneX;
337  }
338  }
339  PropertyChanges { target: bar; expanded: true }
340  },
341  State {
342  name: "commit"
343  extend: "locked"
344  PropertyChanges { target: bar; interactive: true }
345  PropertyChanges {
346  target: d;
347  hasCommitted: true
348  lastHideTouchSceneX: 0
349  xDisplacementSinceLock: 0
350  restoreEntryValues: false
351  }
352  }
353  ]
354  state: "initial"
355 }