Lomiri
Loading...
Searching...
No Matches
VirtualTouchPad.qml
1/*
2 * Copyright (C) 2015 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
17import QtQuick 2.12
18import QtQuick.Layouts 1.1
19import Lomiri.Components 1.3
20import Qt.labs.settings 1.0
21import UInput 0.1
22import "../Components"
23
24Item {
25 id: root
26
27 property bool oskEnabled: false
28
29 Component.onCompleted: {
30 UInput.createMouse();
31 if (!settings.touchpadTutorialHasRun) {
32 root.runTutorial()
33 }
34 }
35 Component.onDestruction: UInput.removeMouse()
36
37 function runTutorial() {
38 // If the tutorial animation is started too early, e.g. in Component.onCompleted,
39 // root width & height might be reported as 0x0 still. As animations read their
40 // values at startup and won't update them, lets make sure to only start once
41 // we have some actual size.
42 if (root.width > 0 && root.height > 0) {
43 tutorial.start();
44 } else {
45 tutorialTimer.start();
46 }
47 }
48
49 Timer {
50 id: tutorialTimer
51 interval: 50
52 repeat: false
53 running: false
54 onTriggered: root.runTutorial();
55 }
56
57 readonly property bool pressed: point1.pressed || point2.pressed || leftButton.pressed || rightButton.pressed
58
59 property var settings: Settings {
60 objectName: "virtualTouchPadSettings"
61 property bool touchpadTutorialHasRun: false
62 property bool oskEnabled: true
63 }
64
65 MultiPointTouchArea {
66 objectName: "touchPadArea"
67 anchors.fill: parent
68 enabled: !tutorial.running || tutorial.paused
69
70 // FIXME: Once we have Qt DPR support, this should be Qt.styleHints.startDragDistance
71 readonly property int clickThreshold: internalGu * 1.5
72 property bool isClick: false
73 property bool isDoubleClick: false
74 property bool isDrag: false
75
76 onPressed: {
77 if (tutorial.paused) {
78 tutorial.resume();
79 return;
80 }
81
82 // If double-tapping *really* fast, it could happen that we end up having only point2 pressed
83 // Make sure we check for both combos, only point1 or only point2
84 if (((point1.pressed && !point2.pressed) || (!point1.pressed && point2.pressed))
85 && clickTimer.running) {
86 clickTimer.stop();
87 UInput.pressMouse(UInput.ButtonLeft)
88 isDoubleClick = true;
89 }
90 isClick = true;
91 }
92
93 onUpdated: {
94 switch (touchPoints.length) {
95 case 1:
96 moveMouse(touchPoints);
97 return;
98 case 2:
99 scroll(touchPoints);
100 return;
101 }
102 }
103
104 onReleased: {
105 if (isDoubleClick || isDrag) {
106 UInput.releaseMouse(UInput.ButtonLeft)
107 isDoubleClick = false;
108 }
109 if (isClick) {
110 clickTimer.scheduleClick(point1.pressed ? UInput.ButtonRight : UInput.ButtonLeft)
111 }
112 isClick = false;
113 isDrag = false;
114 }
115
116 Timer {
117 id: clickTimer
118 repeat: false
119 interval: 200
120 property int button: UInput.ButtonLeft
121 onTriggered: {
122 UInput.pressMouse(button);
123 UInput.releaseMouse(button);
124 }
125 function scheduleClick(button) {
126 clickTimer.button = button;
127 clickTimer.start();
128 }
129 }
130
131 function moveMouse(touchPoints) {
132 var tp = touchPoints[0];
133 if (isClick &&
134 (Math.abs(tp.x - tp.startX) > clickThreshold ||
135 Math.abs(tp.y - tp.startY) > clickThreshold)) {
136 isClick = false;
137 isDrag = true;
138 }
139
140 UInput.moveMouse(tp.x - tp.previousX, tp.y - tp.previousY);
141 }
142
143 function scroll(touchPoints) {
144 var dh = 0;
145 var dv = 0;
146 var tp = touchPoints[0];
147 if (isClick &&
148 (Math.abs(tp.x - tp.startX) > clickThreshold ||
149 Math.abs(tp.y - tp.startY) > clickThreshold)) {
150 isClick = false;
151 }
152 dh += tp.x - tp.previousX;
153 dv += tp.y - tp.previousY;
154
155 tp = touchPoints[1];
156 if (isClick &&
157 (Math.abs(tp.x - tp.startX) > clickThreshold ||
158 Math.abs(tp.y - tp.startY) > clickThreshold)) {
159 isClick = false;
160 }
161 dh += tp.x - tp.previousX;
162 dv += tp.y - tp.previousY;
163
164 // As we added up the movement of the two fingers, let's divide it again by 2
165 dh /= 2;
166 dv /= 2;
167
168 UInput.scrollMouse(dh, dv);
169 }
170
171 touchPoints: [
172 TouchPoint {
173 id: point1
174 },
175 TouchPoint {
176 id: point2
177 }
178 ]
179 }
180
181 RowLayout {
182 anchors { left: parent.left; right: parent.right; bottom: parent.bottom; margins: -internalGu * 1 }
183 height: internalGu * 10
184 spacing: internalGu * 1
185
186 MouseArea {
187 id: leftButton
188 objectName: "leftButton"
189 Layout.fillWidth: true
190 Layout.fillHeight: true
191 onPressed: UInput.pressMouse(UInput.ButtonLeft);
192 onReleased: UInput.releaseMouse(UInput.ButtonLeft);
193 property bool highlight: false
194 LomiriShape {
195 anchors.fill: parent
196 backgroundColor: leftButton.highlight || leftButton.pressed ? LomiriColors.ash : LomiriColors.inkstone
197 Behavior on backgroundColor { ColorAnimation { duration: LomiriAnimation.FastDuration } }
198 }
199 }
200
201 MouseArea {
202 id: rightButton
203 objectName: "rightButton"
204 Layout.fillWidth: true
205 Layout.fillHeight: true
206 onPressed: UInput.pressMouse(UInput.ButtonRight);
207 onReleased: UInput.releaseMouse(UInput.ButtonRight);
208 property bool highlight: false
209 LomiriShape {
210 anchors.fill: parent
211 backgroundColor: rightButton.highlight || rightButton.pressed ? LomiriColors.ash : LomiriColors.inkstone
212 Behavior on backgroundColor { ColorAnimation { duration: LomiriAnimation.FastDuration } }
213 }
214 }
215 }
216
217 AbstractButton {
218 id: oskButton
219 objectName: "oskButton"
220 anchors { right: parent.right; top: parent.top; margins: internalGu * 2 }
221 height: internalGu * 6
222 width: height
223
224 onClicked: {
225 settings.oskEnabled = !settings.oskEnabled
226 }
227
228 Rectangle {
229 anchors.fill: parent
230 radius: width / 2
231 color: LomiriColors.inkstone
232 }
233
234 Icon {
235 anchors.fill: parent
236 anchors.margins: internalGu * 1.5
237 name: "input-keyboard-symbolic"
238 }
239 }
240
241 InputMethod {
242 id: inputMethod
243 // Don't resize when there is only one screen to avoid resize clashing with the InputMethod in the Shell.
244 enabled: root.oskEnabled && settings.oskEnabled && !tutorial.running
245 objectName: "inputMethod"
246 anchors.fill: parent
247 }
248
249 Label {
250 id: tutorialLabel
251 objectName: "tutorialLabel"
252 anchors { left: parent.left; top: parent.top; right: parent.right; margins: internalGu * 4; topMargin: internalGu * 10 }
253 opacity: 0
254 visible: opacity > 0
255 font.pixelSize: 2 * internalGu
256 color: "white"
257 wrapMode: Text.WordWrap
258 }
259
260 Icon {
261 id: tutorialImage
262 objectName: "tutorialImage"
263 height: internalGu * 8
264 width: height
265 name: "input-touchpad-symbolic"
266 color: "white"
267 opacity: 0
268 visible: opacity > 0
269 anchors { top: tutorialLabel.bottom; horizontalCenter: parent.horizontalCenter; margins: internalGu * 2 }
270 }
271
272 Item {
273 id: tutorialFinger1
274 objectName: "tutorialFinger1"
275 width: internalGu * 5
276 height: width
277 property real scale: 1
278 opacity: 0
279 visible: opacity > 0
280 Rectangle {
281 width: parent.width * parent.scale
282 height: width
283 anchors.centerIn: parent
284 radius: width / 2
285 color: LomiriColors.inkstone
286 }
287 }
288
289 Item {
290 id: tutorialFinger2
291 objectName: "tutorialFinger2"
292 width: internalGu * 5
293 height: width
294 property real scale: 1
295 opacity: 0
296 visible: opacity > 0
297 Rectangle {
298 width: parent.width * parent.scale
299 height: width
300 anchors.centerIn: parent
301 radius: width / 2
302 color: LomiriColors.inkstone
303 }
304 }
305
306 SequentialAnimation {
307 id: tutorial
308 objectName: "tutorialAnimation"
309
310 PropertyAction { targets: [leftButton, rightButton, oskButton]; property: "enabled"; value: false }
311 PropertyAction { targets: [leftButton, rightButton, oskButton]; property: "opacity"; value: 0 }
312 PropertyAction { target: tutorialLabel; property: "text"; value: i18n.tr("Your device is now connected to an external display. Use this screen as a touch pad to interact with the pointer.") }
313 LomiriNumberAnimation { targets: [tutorialLabel, tutorialImage]; property: "opacity"; to: 1; duration: LomiriAnimation.FastDuration }
314 PropertyAction { target: tutorial; property: "paused"; value: true }
315 PauseAnimation { duration: 500 } // it takes a bit until pausing actually takes effect
316 LomiriNumberAnimation { targets: [tutorialLabel, tutorialImage]; property: "opacity"; to: 0; duration: LomiriAnimation.FastDuration }
317
318 LomiriNumberAnimation { target: leftButton; property: "opacity"; to: 1 }
319 LomiriNumberAnimation { target: rightButton; property: "opacity"; to: 1 }
320
321 PauseAnimation { duration: LomiriAnimation.SleepyDuration }
322 PropertyAction { target: tutorialLabel; property: "text"; value: i18n.tr("Tap left button to click.") }
323 LomiriNumberAnimation { target: tutorialLabel; property: "opacity"; to: 1; duration: LomiriAnimation.FastDuration }
324 SequentialAnimation {
325 loops: 2
326 PropertyAction { target: leftButton; property: "highlight"; value: true }
327 PauseAnimation { duration: LomiriAnimation.FastDuration }
328 PropertyAction { target: leftButton; property: "highlight"; value: false }
329 PauseAnimation { duration: LomiriAnimation.SleepyDuration }
330 }
331 LomiriNumberAnimation { target: tutorialLabel; property: "opacity"; to: 0; duration: LomiriAnimation.FastDuration }
332
333 PauseAnimation { duration: LomiriAnimation.SleepyDuration }
334 PropertyAction { target: tutorialLabel; property: "text"; value: i18n.tr("Tap right button to right click.") }
335 LomiriNumberAnimation { target: tutorialLabel; property: "opacity"; to: 1; duration: LomiriAnimation.FastDuration }
336 SequentialAnimation {
337 loops: 2
338 PropertyAction { target: rightButton; property: "highlight"; value: true }
339 PauseAnimation { duration: LomiriAnimation.FastDuration }
340 PropertyAction { target: rightButton; property: "highlight"; value: false }
341 PauseAnimation { duration: LomiriAnimation.SleepyDuration }
342 }
343 LomiriNumberAnimation { target: tutorialLabel; property: "opacity"; to: 0; duration: LomiriAnimation.FastDuration }
344
345 PauseAnimation { duration: LomiriAnimation.SleepyDuration }
346 PropertyAction { target: tutorialLabel; property: "text"; value: i18n.tr("Swipe with two fingers to scroll.") }
347 LomiriNumberAnimation { target: tutorialLabel; property: "opacity"; to: 1; duration: LomiriAnimation.FastDuration }
348 PropertyAction { target: tutorialFinger1; property: "x"; value: root.width / 2 - tutorialFinger1.width - internalGu * 1 }
349 PropertyAction { target: tutorialFinger2; property: "x"; value: root.width / 2 + tutorialFinger1.width + internalGu * 1 - tutorialFinger2.width }
350 PropertyAction { target: tutorialFinger1; property: "y"; value: root.height / 2 - internalGu * 10 }
351 PropertyAction { target: tutorialFinger2; property: "y"; value: root.height / 2 - internalGu * 10 }
352 SequentialAnimation {
353 ParallelAnimation {
354 LomiriNumberAnimation { target: tutorialFinger1; property: "opacity"; to: 1; duration: LomiriAnimation.FastDuration }
355 LomiriNumberAnimation { target: tutorialFinger2; property: "opacity"; to: 1; duration: LomiriAnimation.FastDuration }
356 LomiriNumberAnimation { target: tutorialFinger1; property: "scale"; from: 0; to: 1; duration: LomiriAnimation.FastDuration }
357 LomiriNumberAnimation { target: tutorialFinger2; property: "scale"; from: 0; to: 1; duration: LomiriAnimation.FastDuration }
358 }
359 ParallelAnimation {
360 LomiriNumberAnimation { target: tutorialFinger1; property: "y"; to: root.height / 2 + internalGu * 10; duration: LomiriAnimation.SleepyDuration }
361 LomiriNumberAnimation { target: tutorialFinger2; property: "y"; to: root.height / 2 + internalGu * 10; duration: LomiriAnimation.SleepyDuration }
362 }
363 ParallelAnimation {
364 LomiriNumberAnimation { target: tutorialFinger1; property: "opacity"; to: 0; duration: LomiriAnimation.FastDuration }
365 LomiriNumberAnimation { target: tutorialFinger2; property: "opacity"; to: 0; duration: LomiriAnimation.FastDuration }
366 LomiriNumberAnimation { target: tutorialFinger1; property: "scale"; from: 1; to: 0; duration: LomiriAnimation.FastDuration }
367 LomiriNumberAnimation { target: tutorialFinger2; property: "scale"; from: 1; to: 0; duration: LomiriAnimation.FastDuration }
368 }
369 PauseAnimation { duration: LomiriAnimation.SlowDuration }
370 ParallelAnimation {
371 LomiriNumberAnimation { target: tutorialFinger1; property: "opacity"; to: 1; duration: LomiriAnimation.FastDuration }
372 LomiriNumberAnimation { target: tutorialFinger2; property: "opacity"; to: 1; duration: LomiriAnimation.FastDuration }
373 LomiriNumberAnimation { target: tutorialFinger1; property: "scale"; from: 0; to: 1; duration: LomiriAnimation.FastDuration }
374 LomiriNumberAnimation { target: tutorialFinger2; property: "scale"; from: 0; to: 1; duration: LomiriAnimation.FastDuration }
375 }
376 ParallelAnimation {
377 LomiriNumberAnimation { target: tutorialFinger1; property: "y"; to: root.height / 2 - internalGu * 10; duration: LomiriAnimation.SleepyDuration }
378 LomiriNumberAnimation { target: tutorialFinger2; property: "y"; to: root.height / 2 - internalGu * 10; duration: LomiriAnimation.SleepyDuration }
379 }
380 ParallelAnimation {
381 LomiriNumberAnimation { target: tutorialFinger1; property: "opacity"; to: 0; duration: LomiriAnimation.FastDuration }
382 LomiriNumberAnimation { target: tutorialFinger2; property: "opacity"; to: 0; duration: LomiriAnimation.FastDuration }
383 LomiriNumberAnimation { target: tutorialFinger1; property: "scale"; from: 1; to: 0; duration: LomiriAnimation.FastDuration }
384 LomiriNumberAnimation { target: tutorialFinger2; property: "scale"; from: 1; to: 0; duration: LomiriAnimation.FastDuration }
385 }
386 PauseAnimation { duration: LomiriAnimation.SlowDuration }
387 }
388 LomiriNumberAnimation { target: tutorialLabel; property: "opacity"; to: 0; duration: LomiriAnimation.FastDuration }
389
390 PauseAnimation { duration: LomiriAnimation.SleepyDuration }
391 PropertyAction { target: tutorialLabel; property: "text"; value: i18n.tr("Find more settings in the system settings.") }
392 LomiriNumberAnimation { target: tutorialLabel; property: "opacity"; to: 1; duration: LomiriAnimation.FastDuration }
393 PauseAnimation { duration: 2000 }
394 LomiriNumberAnimation { target: tutorialLabel; property: "opacity"; to: 0; duration: LomiriAnimation.FastDuration }
395
396 LomiriNumberAnimation { target: oskButton; property: "opacity"; to: 1 }
397 PropertyAction { targets: [leftButton, rightButton, oskButton]; property: "enabled"; value: true }
398
399 PropertyAction { target: settings; property: "touchpadTutorialHasRun"; value: true }
400 }
401}