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