on xenial help sign in
Merge pull request #391 from ubports/xenial_-_drawer-input-field-changes

Fix edge cases in Drawer

commite71457d35d793f6e66e51c938bbbfe343497219f authorRodney <6557070+dobey@users.noreply.github.com> dateTue 1 Jun 2021, 14:41 (5 years ago) parentbc4a0a6fcc3d49
3 files changed+133−21
qml/Launcher/Drawer.qml+23−17 qml/Launcher/Launcher.qml+15−4 tests/qmltests/Launcher/tst_Drawer.qml+95
qml/Launcher/Drawer.qml+23−17
@@ -1,6 +1,6 @@
11/*
22 * Copyright (C) 2016 Canonical, Ltd.
3− * Copyright (C) 2020 UBports Foundation.
3+ * Copyright (C) 2020-2021 UBports Foundation
44 *
55 * This program is free software; you can redistribute it and/or modify
66 * it under the terms of the GNU General Public License as published by
@@ -56,16 +56,7 @@ FocusScope {
5656 property var oldSelectionEnd: null
5757
5858 anchors {
59− onRightMarginChanged: {
60− if (fullyOpen && hadFocus) {
61− // See onDraggingHorizontallyChanged below
62− searchField.focus = hadFocus;
63− searchField.select(oldSelectionStart, oldSelectionEnd);
64− } else if (fullyClosed || fullyOpen) {
65− searchField.text = "";
66− resetOldFocus();
67− }
68− }
59+ onRightMarginChanged: refocusInputAfterUserLetsGo()
6960 }
7061
7162 Behavior on anchors.rightMargin {
@@ -77,10 +68,8 @@ FocusScope {
7768 }
7869
7970 onDraggingHorizontallyChanged: {
71+ // See refocusInputAfterUserLetsGo()
8072 if (draggingHorizontally) {
81− // Remove (and put back using anchors.onRightMarginChanged) the
82− // focus for the searchfield in order to hide the copy/paste
83− // popover when we move the drawer
8473 hadFocus = searchField.focus;
8574 oldSelectionStart = searchField.selectionStart;
8675 oldSelectionEnd = searchField.selectionEnd;
@@ -91,6 +80,7 @@ FocusScope {
9180 } else {
9281 openRequested();
9382 }
83+ refocusInputAfterUserLetsGo();
9484 }
9585 }
9686
@@ -106,9 +96,24 @@ FocusScope {
10696 hadFocus = false;
10797 oldSelectionStart = null;
10898 oldSelectionEnd = null;
109− appList.currentIndex = 0;
110− searchField.focus = false;
111− appList.focus = false;
99+ }
100+
101+ function refocusInputAfterUserLetsGo() {
102+ if (!draggingHorizontally) {
103+ if (fullyOpen && hadFocus) {
104+ searchField.focus = hadFocus;
105+ searchField.select(oldSelectionStart, oldSelectionEnd);
106+ } else if (fullyOpen || fullyClosed) {
107+ resetOldFocus();
108+ }
109+
110+ if (fullyClosed) {
111+ searchField.text = "";
112+ appList.currentIndex = 0;
113+ searchField.focus = false;
114+ appList.focus = false;
115+ }
116+ }
112117 }
113118
114119 function focusInput() {
@@ -202,6 +207,7 @@ FocusScope {
202207 function reset() {
203208 root.draggingHorizontally = false;
204209 handle.active = false;
210+ root.dragDistance = 0;
205211 }
206212
207213 Handle {
qml/Launcher/Launcher.qml+15−4
@@ -1,5 +1,6 @@
11/*
22 * Copyright (C) 2013-2015 Canonical, Ltd.
3+ * Copyright (C) 2021 UBports Foundation
34 *
45 * This program is free software; you can redistribute it and/or modify
56 * it under the terms of the GNU General Public License as published by
@@ -566,10 +567,12 @@ FocusScope {
566567 name: "" // hidden state. Must be the default state ("") because "when:" falls back to this.
567568 PropertyChanges {
568569 target: panel
570+ restoreEntryValues: false
569571 x: -root.panelWidth
570572 }
571573 PropertyChanges {
572574 target: drawer
575+ restoreEntryValues: false
573576 anchors.rightMargin: 0
574577 focus: false
575578 }
@@ -578,32 +581,38 @@ FocusScope {
578581 name: "visible"
579582 PropertyChanges {
580583 target: panel
584+ restoreEntryValues: false
581585 x: -root.x // so we never go past panelWidth, even when teased by tutorial
582586 focus: true
583587 }
584588 PropertyChanges {
585589 target: drawer
590+ restoreEntryValues: false
586591 anchors.rightMargin: 0
587592 focus: false
588593 }
589594 },
590595 State {
591596 name: "drawer"
592− extend: "visible"
597+ PropertyChanges {
598+ target: panel
599+ restoreEntryValues: false
600+ x: -root.x // so we never go past panelWidth, even when teased by tutorial
601+ focus: false
602+ }
593603 PropertyChanges {
594604 target: drawer
605+ restoreEntryValues: false
595606 anchors.rightMargin: -drawer.width + root.x // so we never go past panelWidth, even when teased by tutorial
596607 focus: true
597608 }
598− PropertyChanges {
599− target: panel
600− }
601609 },
602610 State {
603611 name: "visibleTemporary"
604612 extend: "visible"
605613 PropertyChanges {
606614 target: root
615+ restoreEntryValues: false
607616 autohideEnabled: true
608617 }
609618 },
@@ -612,6 +621,7 @@ FocusScope {
612621 when: teaseTimer.running && teaseTimer.mode == "teasing"
613622 PropertyChanges {
614623 target: panel
624+ restoreEntryValues: false
615625 x: -root.panelWidth + units.gu(2)
616626 }
617627 },
@@ -620,6 +630,7 @@ FocusScope {
620630 when: teaseTimer.running && teaseTimer.mode == "hinting"
621631 PropertyChanges {
622632 target: panel
633+ restoreEntryValues: false
623634 x: 0
624635 }
625636 }
tests/qmltests/Launcher/tst_Drawer.qml+95
@@ -1,5 +1,6 @@
11/*
22 * Copyright 2013-2016 Canonical Ltd.
3+ * Copyright (C) 2021 UBports Foundation
34 *
45 * This program is free software; you can redistribute it and/or modify
56 * it under the terms of the GNU General Public License as published by
@@ -421,5 +422,99 @@ StyledItem {
421422
422423 tryCompare(searchField, "focus", false);
423424 }
425+
426+ function test_dragDistanceReset() {
427+ // Regression test: If the user dragged the Drawer open further than
428+ // necessary, dragging the Drawer closed would take an equal amount
429+ // of distance to start the animation as the original overshoot.
430+
431+ var drawer = dragDrawerIntoView();
432+ var dragY = drawer.height / 2;
433+ // Drag the Drawer most of the way closed, then *way* open
434+ mousePress(root, drawer.width - units.gu(1), dragY);
435+ mouseMove(root, 10, dragY, 500);
436+ mouseMove(root, units.gu(100), dragY, 500);
437+ mouseRelease(root, units.gu(100), dragY);
438+
439+ // Ensure the Drawer's margin changes correctly on the next drag closed
440+ mousePress(root, drawer.width - units.gu(1), dragY);
441+ mouseMove(root, 1, dragY);
442+ // If the Drawer's position is within 2gu of hidden, it's probably okay.
443+ tryVerify(function () {return drawer.x < -(drawer.width - units.gu(2))});
444+ // But it should not be completely hidden.
445+ tryCompare(drawer, "visible", true);
446+ mouseRelease(root, 1, dragY);
447+ }
448+
449+ function test_userCancellingExitWithSearch_data() {
450+ return [
451+ {tag: "drag all the way back to fullyOpen", fullyOpen: true, cursorPosition: 3, selectedText: ""},
452+ {tag: "do not drag all the way back to fullyOpen", fullyOpen: false, cursorPosition: 3, selectedText: ""},
453+ {tag: "move cursor and drag all the way back to fullyOpen", fullyOpen: true, cursorPosition: 2, selectionStart: 2, selectionEnd: 2, selectedText: ""},
454+ {tag: "move cursor and do not drag all the way back to fullyOpen", fullyOpen: false, cursorPosition: 2, selectionStart: 2, selectionEnd: 2, selectedText: ""},
455+ {tag: "select text and drag all the way back to fullyOpen", fullyOpen: true, cursorPosition: 2, selectionStart: 0, selectionEnd: 2, selectedText: "ca"},
456+ {tag: "select text and do not drag all the way back to fullyOpen", fullyOpen: false, cursorPosition: 2, selectionStart: 0, selectionEnd: 2, selectedText: "ca"}
457+ ]
458+ }
459+
460+ function test_userCancellingExitWithSearch(data) {
461+ // * If I open the Drawer, start searching, start closing the
462+ // drawer, then change my mind and cancel my closing gesture, my
463+ // search remains.
464+ // * If I start searching, place my cursor at a different point in
465+ // the text field or select text, then start closing the Drawer:
466+ // - The keyboard text selection handles and copy/paste dialog
467+ // become invisible or move with the text box. Our keyboard
468+ // isn't quite fancy enough for the handles to move with the
469+ // search box, so we remove the search field's focus to remove
470+ // the handles.
471+ // - If I cancel my Drawer close action, my search, cursor
472+ // position, and selection rectangles are retained.
473+ var drawer = dragDrawerIntoView();
474+ var searchField = drawer.searchTextField;
475+ var dragY = drawer.height / 2;
476+ if (data.fullyOpen) {
477+ var dragEndPointX = drawer.width + units.gu(20);
478+ } else {
479+ var dragEndPointX = drawer.width - units.gu(10);
480+ }
481+
482+ typeString("cam");
483+ tryCompare(searchField, "displayText", "cam");
484+ tryCompare(searchField, "focus", true);
485+
486+ if (data.selectionStart !== undefined) {
487+ searchField.select(data.selectionStart, data.selectionEnd);
488+ }
489+
490+ // Simply clicking the drag handle shouldn't be enough to remove focus
491+ mousePress(root, drawer.width - units.gu(1), dragY);
492+ tryCompare(searchField, "focus", true);
493+
494+ // The search field should not have focus during the drag so the
495+ // cursor or drag handles don't float in place.
496+ mouseMove(root, 10, dragY, 500);
497+ tryCompare(drawer, "fullyOpen", false);
498+ tryCompare(searchField, "focus", false);
499+ mouseMove(root, dragEndPointX, dragY, 500);
500+ tryCompare(drawer, "fullyOpen", data.fullyOpen);
501+
502+ // The search field should not get focus back until the user lets go
503+ tryCompare(searchField, "focus", false);
504+ mouseRelease(root, dragEndPointX, dragY);
505+ // Wait for animations to stop so anything that results from them
506+ // can resolve
507+ wait(1000);
508+ tryCompare(drawer, "fullyOpen", true);
509+ tryCompare(searchField, "displayText", "cam");
510+ tryCompare(searchField, "focus", true);
511+ tryCompare(searchField, "cursorPosition", data.cursorPosition);
512+ tryCompare(searchField, "selectedText", data.selectedText);
513+ if (data.selectionStart) {
514+ // The selected area should be the same as it was before the drag
515+ tryCompare(searchField, "selectionStart", data.selectionStart);
516+ tryCompare(searchField, "selectionEnd", data.selectionEnd);
517+ }
518+ }
424519 }
425520}