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