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