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