on xenial help sign in
Fix edge cases in Drawer searching

This fixes two strange behaviors arising from our custom handling of the Drawer search bar's focus, cursor position, and selection rectangle. In particular, fixes https://github.com/ubports/unity8/issues/389 However, I found another edge case which was intertwined but far more difficult to fix. If the user dragged the Drawer to cancel it, then dragged it all the way back to the fullyOpen position before letting go, focus was restored at that point. If the user dragged the Drawer back out of the fullyOpen position again (still not letting go), focus was removed again without being saved. So, slamming the drawer open and shut again without letting go would cause you to lose your spot.

commitfcc3d498e320e24d3fe052ce341338f4a8ce91b8 authorDalton Durst <dalton@ubports.com> dateMon 31 May 2021, 18:57 (5 years ago) parent5f84782
2 files changed+94−17
qml/Launcher/Drawer.qml+22−17 tests/qmltests/Launcher/tst_Drawer.qml+72
qml/Launcher/Drawer.qml+22−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() {
tests/qmltests/Launcher/tst_Drawer.qml+72
@@ -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
@@ -444,5 +445,76 @@ StyledItem {
444445 tryCompare(drawer, "visible", true);
445446 mouseRelease(root, 1, dragY);
446447 }
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+ }
447519 }
448520}