mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-07-30 08:14:45 +00:00
homescreens/folio: Add keyboard navigation on pages
Implements the rest of https://invent.kde.org/plasma/plasma-mobile/-/issues/219
This commit is contained in:
parent
5c404c0f0d
commit
dc1fa9b187
7 changed files with 430 additions and 48 deletions
|
|
@ -20,7 +20,7 @@ import org.kde.kirigami 2.19 as Kirigami
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: root
|
id: root
|
||||||
onClicked: root.requestedClose()
|
onClicked: root.requestedClose(false)
|
||||||
|
|
||||||
function requestFocus() {
|
function requestFocus() {
|
||||||
queryField.forceActiveFocus();
|
queryField.forceActiveFocus();
|
||||||
|
|
@ -30,11 +30,11 @@ MouseArea {
|
||||||
queryField.text = "";
|
queryField.text = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
signal requestedClose()
|
signal requestedClose(triggeredByKeyEvent: bool)
|
||||||
|
|
||||||
Keys.onPressed: (event) => {
|
Keys.onPressed: (event) => {
|
||||||
if (event.key === Qt.Key_Escape) {
|
if (event.key === Qt.Key_Escape) {
|
||||||
root.requestedClose();
|
root.requestedClose(true);
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -77,7 +77,7 @@ MouseArea {
|
||||||
if (event.key === Qt.Key_Down) {
|
if (event.key === Qt.Key_Down) {
|
||||||
if (listView.count === 0) {
|
if (listView.count === 0) {
|
||||||
// Close if listview has no elements
|
// Close if listview has no elements
|
||||||
root.requestedClose();
|
root.requestedClose(true);
|
||||||
} else {
|
} else {
|
||||||
// Focus on listview if there are elements
|
// Focus on listview if there are elements
|
||||||
listView.forceActiveFocus();
|
listView.forceActiveFocus();
|
||||||
|
|
@ -111,7 +111,7 @@ MouseArea {
|
||||||
}
|
}
|
||||||
|
|
||||||
onActivated: {
|
onActivated: {
|
||||||
root.requestedClose();
|
root.requestedClose(false);
|
||||||
}
|
}
|
||||||
onUpdateQueryString: {
|
onUpdateQueryString: {
|
||||||
queryField.text = text
|
queryField.text = text
|
||||||
|
|
@ -153,7 +153,7 @@ MouseArea {
|
||||||
// Close search view if we press down with last item selected
|
// Close search view if we press down with last item selected
|
||||||
Keys.onPressed: (event) => {
|
Keys.onPressed: (event) => {
|
||||||
if (event.key === Qt.Key_Down && (model.index === listView.count - 1)) {
|
if (event.key === Qt.Key_Down && (model.index === listView.count - 1)) {
|
||||||
root.requestedClose();
|
root.requestedClose(true);
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -169,7 +169,7 @@ MouseArea {
|
||||||
listView.currentIndex = model.index;
|
listView.currentIndex = model.index;
|
||||||
listView.runCurrentIndex();
|
listView.runCurrentIndex();
|
||||||
|
|
||||||
root.requestedClose();
|
root.requestedClose(false);
|
||||||
}
|
}
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,17 @@ MouseArea {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onActiveFocusChanged: {
|
||||||
|
if (activeFocus) {
|
||||||
|
// Focus on first delegate when favorites bar focused
|
||||||
|
let firstDelegate = repeater.itemAt(0);
|
||||||
|
if (!firstDelegate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
firstDelegate.keyboardFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MobileShell.HapticsEffect {
|
MobileShell.HapticsEffect {
|
||||||
id: haptics
|
id: haptics
|
||||||
}
|
}
|
||||||
|
|
@ -79,7 +90,56 @@ MouseArea {
|
||||||
width: folio.HomeScreenState.pageCellWidth
|
width: folio.HomeScreenState.pageCellWidth
|
||||||
height: folio.HomeScreenState.pageCellHeight
|
height: folio.HomeScreenState.pageCellHeight
|
||||||
|
|
||||||
|
// Keyboard navigation to other delegates
|
||||||
|
Keys.onPressed: (event) => {
|
||||||
|
switch (event.key) {
|
||||||
|
case Qt.Key_Up:
|
||||||
|
if (!isLocationBottom) {
|
||||||
|
let nextDelegate = repeater.itemAt(delegate.index - 1);
|
||||||
|
if (nextDelegate) {
|
||||||
|
nextDelegate.keyboardFocus();
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Qt.Key_Down:
|
||||||
|
if (!isLocationBottom) {
|
||||||
|
let nextDelegate = repeater.itemAt(delegate.index + 1);
|
||||||
|
if (nextDelegate) {
|
||||||
|
nextDelegate.keyboardFocus();
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Qt.Key_Left:
|
||||||
|
if (isLocationBottom) {
|
||||||
|
let nextDelegate = repeater.itemAt(delegate.index - 1);
|
||||||
|
if (nextDelegate) {
|
||||||
|
nextDelegate.keyboardFocus();
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Qt.Key_Right:
|
||||||
|
if (isLocationBottom) {
|
||||||
|
let nextDelegate = repeater.itemAt(delegate.index + 1);
|
||||||
|
if (nextDelegate) {
|
||||||
|
nextDelegate.keyboardFocus();
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function keyboardFocus() {
|
||||||
|
if (loader.item) {
|
||||||
|
loader.item.keyboardFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
|
id: loader
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
sourceComponent: {
|
sourceComponent: {
|
||||||
|
|
|
||||||
|
|
@ -36,12 +36,36 @@ Folio.DelegateTouchArea {
|
||||||
folio.HomeScreenState.closeFolder();
|
folio.HomeScreenState.closeFolder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Keys.onPressed: (event) => {
|
||||||
|
switch (event.key) {
|
||||||
|
case Qt.Key_Escape:
|
||||||
|
case Qt.Key_Back:
|
||||||
|
// Close view
|
||||||
|
root.close();
|
||||||
|
event.accepted = true;
|
||||||
|
break;
|
||||||
|
case Qt.Key_Up:
|
||||||
|
case Qt.Key_Down:
|
||||||
|
case Qt.Key_Left:
|
||||||
|
case Qt.Key_Right:
|
||||||
|
// Keyboard focus on first item
|
||||||
|
if (delegateRepeater.count > 0) {
|
||||||
|
delegateRepeater.itemAt(0).keyboardFocus();
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: folio.HomeScreenState
|
target: folio.HomeScreenState
|
||||||
|
|
||||||
function onFolderAboutToOpen(x, y) {
|
function onFolderAboutToOpen(x, y) {
|
||||||
root.folderPositionX = x - folio.HomeScreenState.viewLeftPadding;
|
root.folderPositionX = x - folio.HomeScreenState.viewLeftPadding;
|
||||||
root.folderPositionY = y - folio.HomeScreenState.viewTopPadding;
|
root.folderPositionY = y - folio.HomeScreenState.viewTopPadding;
|
||||||
|
|
||||||
|
// Focus view when opened
|
||||||
|
root.forceActiveFocus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -73,6 +97,70 @@ Folio.DelegateTouchArea {
|
||||||
folio.HomeScreenState.folderPageContentHeight = (folderBackground.height - margin * 2);
|
folio.HomeScreenState.folderPageContentHeight = (folderBackground.height - margin * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function keyboardNavigateForDelegate(key, column, row, page) {
|
||||||
|
let dx = 0;
|
||||||
|
let dy = 0;
|
||||||
|
switch (key) {
|
||||||
|
case Qt.Key_Up: { dy = -1; break; }
|
||||||
|
case Qt.Key_Down: { dy = 1; break; }
|
||||||
|
case Qt.Key_Left: { dx = -1; break; }
|
||||||
|
case Qt.Key_Right: { dx = 1; break; }
|
||||||
|
default: return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let x = column + dx;
|
||||||
|
let y = row + dy;
|
||||||
|
|
||||||
|
// Loop in direction to find next delegate
|
||||||
|
while (x >= 0 && x < folio.HomeScreenState.folderGridLength
|
||||||
|
&& y >= 0 && y < folio.HomeScreenState.folderGridLength) {
|
||||||
|
|
||||||
|
// Find delegate at x, y
|
||||||
|
for (let i = 0; i < delegateRepeater.count; ++i) {
|
||||||
|
let cDelegate = delegateRepeater.itemAt(i);
|
||||||
|
if (cDelegate.columnValue === x && cDelegate.rowValue === y && cDelegate.pageValue === page) {
|
||||||
|
// Delegate matches, focus on it and return
|
||||||
|
cDelegate.keyboardFocus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
x += dx;
|
||||||
|
y += dy;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Behavior if no delegate is found to navigate to
|
||||||
|
switch (key) {
|
||||||
|
case Qt.Key_Up:
|
||||||
|
case Qt.Key_Down:
|
||||||
|
break;
|
||||||
|
case Qt.Key_Left: {
|
||||||
|
// Go to the left page if a delegate exists there
|
||||||
|
let cDelegate = delegateRepeater.itemAt((page - 1)
|
||||||
|
* folio.HomeScreenState.folderGridLength
|
||||||
|
* folio.HomeScreenState.folderGridLength);
|
||||||
|
if (cDelegate) {
|
||||||
|
cDelegate.keyboardFocus();
|
||||||
|
folio.HomeScreenState.goToFolderPage(page - 1, false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Qt.Key_Right: {
|
||||||
|
// Go to the right page if a delegate exists there
|
||||||
|
let cDelegate = delegateRepeater.itemAt((page + 1)
|
||||||
|
* folio.HomeScreenState.folderGridLength
|
||||||
|
* folio.HomeScreenState.folderGridLength);
|
||||||
|
if (cDelegate) {
|
||||||
|
cDelegate.keyboardFocus();
|
||||||
|
folio.HomeScreenState.goToFolderPage(page + 1, false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: folio.HomeScreenState
|
target: folio.HomeScreenState
|
||||||
|
|
||||||
|
|
@ -168,6 +256,7 @@ Folio.DelegateTouchArea {
|
||||||
x: folio.HomeScreenState.folderViewX
|
x: folio.HomeScreenState.folderViewX
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
|
id: delegateRepeater
|
||||||
model: root.folder ? root.folder.applications : []
|
model: root.folder ? root.folder.applications : []
|
||||||
|
|
||||||
delegate: Item {
|
delegate: Item {
|
||||||
|
|
@ -193,15 +282,10 @@ Folio.DelegateTouchArea {
|
||||||
property double columnValue: model.columnIndex
|
property double columnValue: model.columnIndex
|
||||||
property double rowValue: model.rowIndex
|
property double rowValue: model.rowIndex
|
||||||
property double pageValue: model.pageIndex
|
property double pageValue: model.pageIndex
|
||||||
Behavior on columnValue {
|
|
||||||
NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutQuad }
|
Behavior on columnValue { NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutQuad } }
|
||||||
}
|
Behavior on rowValue { NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutQuad } }
|
||||||
Behavior on rowValue {
|
Behavior on pageValue { NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutQuad } }
|
||||||
NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutQuad }
|
|
||||||
}
|
|
||||||
Behavior on pageValue {
|
|
||||||
NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutQuad }
|
|
||||||
}
|
|
||||||
|
|
||||||
// multiply the index values by the cell size to get the actual position
|
// multiply the index values by the cell size to get the actual position
|
||||||
readonly property int positionColumn: folderCellSize * columnValue
|
readonly property int positionColumn: folderCellSize * columnValue
|
||||||
|
|
@ -215,6 +299,28 @@ Folio.DelegateTouchArea {
|
||||||
width: cellWidth
|
width: cellWidth
|
||||||
height: cellHeight
|
height: cellHeight
|
||||||
|
|
||||||
|
// Implement keyboard arrow navigation
|
||||||
|
Keys.onPressed: (event) => {
|
||||||
|
switch (event.key) {
|
||||||
|
case Qt.Key_Up:
|
||||||
|
case Qt.Key_Down:
|
||||||
|
case Qt.Key_Left:
|
||||||
|
case Qt.Key_Right:
|
||||||
|
event.accepted = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
root.keyboardNavigateForDelegate(event.key, columnValue, rowValue, pageValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
function keyboardFocus() {
|
||||||
|
if (delegateLoader.item) {
|
||||||
|
delegateLoader.item.keyboardFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: delegateLoader
|
id: delegateLoader
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,8 @@ Item {
|
||||||
onBottomMarginChanged: folio.HomeScreenState.viewBottomPadding = root.bottomMargin
|
onBottomMarginChanged: folio.HomeScreenState.viewBottomPadding = root.bottomMargin
|
||||||
onLeftMarginChanged: folio.HomeScreenState.viewLeftPadding = root.leftMargin
|
onLeftMarginChanged: folio.HomeScreenState.viewLeftPadding = root.leftMargin
|
||||||
onRightMarginChanged: folio.HomeScreenState.viewRightPadding = root.rightMargin
|
onRightMarginChanged: folio.HomeScreenState.viewRightPadding = root.rightMargin
|
||||||
|
onWidthChanged: folio.HomeScreenState.viewWidth = width
|
||||||
|
onHeightChanged: folio.HomeScreenState.viewHeight = height
|
||||||
|
|
||||||
signal wallpaperSelectorTriggered()
|
signal wallpaperSelectorTriggered()
|
||||||
|
|
||||||
|
|
@ -75,6 +77,16 @@ Item {
|
||||||
Plasmoid.internalAction("configure").trigger();
|
Plasmoid.internalAction("configure").trigger();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Keys.onPressed: (event) => {
|
||||||
|
// The root is focused when we aren't in key navigation mode
|
||||||
|
// Begin key navigation when arrow keys are pressed
|
||||||
|
if (event.key === Qt.Key_Up || event.key === Qt.Key_Down
|
||||||
|
|| event.key === Qt.Key_Left || event.key === Qt.Key_Right) {
|
||||||
|
homeScreenPages.forceActiveFocus();
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// determine how tall an app label is, for delegate measurements
|
// determine how tall an app label is, for delegate measurements
|
||||||
DelegateLabel {
|
DelegateLabel {
|
||||||
id: appLabelMetrics
|
id: appLabelMetrics
|
||||||
|
|
@ -88,20 +100,23 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// determine screen dimensions
|
|
||||||
Item {
|
|
||||||
id: screenDimensions
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
onWidthChanged: folio.HomeScreenState.viewWidth = width;
|
|
||||||
onHeightChanged: folio.HomeScreenState.viewHeight = height;
|
|
||||||
}
|
|
||||||
|
|
||||||
// a way of stopping focus
|
// a way of stopping focus
|
||||||
FocusScope {
|
FocusScope {
|
||||||
id: noFocus
|
id: noFocus
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Listen to view changes
|
||||||
|
Connections {
|
||||||
|
target: folio.HomeScreenState
|
||||||
|
|
||||||
|
function onViewStateChanged() {
|
||||||
|
if (folio.HomeScreenState.viewState === Folio.HomeScreenState.PageView) {
|
||||||
|
// Focus root when on page view
|
||||||
|
root.forceActiveFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// area that can be swiped
|
// area that can be swiped
|
||||||
MobileShell.SwipeArea {
|
MobileShell.SwipeArea {
|
||||||
id: swipeArea
|
id: swipeArea
|
||||||
|
|
@ -211,6 +226,50 @@ Item {
|
||||||
homeScreenState.pageHeight = homeScreenPages.height;
|
homeScreenState.pageHeight = homeScreenPages.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keyboard navigation from pages
|
||||||
|
Keys.onPressed: (event) => {
|
||||||
|
switch (event.key) {
|
||||||
|
case Qt.Key_Up:
|
||||||
|
// Open search widget when going up
|
||||||
|
folio.HomeScreenState.openSearchWidget();
|
||||||
|
event.accepted = true;
|
||||||
|
break;
|
||||||
|
case Qt.Key_Down:
|
||||||
|
// Focus on favorites bar or app drawer, depending on its physical location
|
||||||
|
if (folio.HomeScreenState.favouritesBarLocation === Folio.HomeScreenState.Bottom) {
|
||||||
|
favouritesBar.forceActiveFocus();
|
||||||
|
} else {
|
||||||
|
folio.HomeScreenState.openAppDrawer();
|
||||||
|
}
|
||||||
|
event.accepted = true;
|
||||||
|
break;
|
||||||
|
case Qt.Key_Left:
|
||||||
|
if (folio.HomeScreenState.favouritesBarLocation === Folio.HomeScreenState.Left) {
|
||||||
|
// If favorites bar is on the left, navigate to it
|
||||||
|
favouritesBar.forceActiveFocus();
|
||||||
|
} else {
|
||||||
|
// Otherwise go to page on the left
|
||||||
|
folio.HomeScreenState.goToPage(folio.HomeScreenState.currentPage - 1, false);
|
||||||
|
homeScreenPages.focusCurrentPageForKeyboardNav();
|
||||||
|
}
|
||||||
|
event.accepted = true;
|
||||||
|
break;
|
||||||
|
case Qt.Key_Right:
|
||||||
|
if (folio.HomeScreenState.favouritesBarLocation === Folio.HomeScreenState.Right) {
|
||||||
|
// If favorites bar is on the right, navigate to it
|
||||||
|
favouritesBar.forceActiveFocus();
|
||||||
|
} else {
|
||||||
|
// Otherwise go to page on the right
|
||||||
|
folio.HomeScreenState.goToPage(folio.HomeScreenState.currentPage + 1, false);
|
||||||
|
homeScreenPages.focusCurrentPageForKeyboardNav();
|
||||||
|
}
|
||||||
|
event.accepted = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
transform: [
|
transform: [
|
||||||
Scale {
|
Scale {
|
||||||
// animation when settings opens
|
// animation when settings opens
|
||||||
|
|
@ -298,6 +357,42 @@ Item {
|
||||||
anchors.leftMargin: root.leftMargin
|
anchors.leftMargin: root.leftMargin
|
||||||
anchors.rightMargin: root.rightMargin
|
anchors.rightMargin: root.rightMargin
|
||||||
|
|
||||||
|
// Keyboard navigation on favorites bar
|
||||||
|
Keys.onPressed: (event) => {
|
||||||
|
switch (event.key) {
|
||||||
|
case Qt.Key_Up:
|
||||||
|
// Focus on homescreen pages or search widget depending on physical position
|
||||||
|
if (folio.HomeScreenState.favouritesBarLocation === Folio.HomeScreenState.Bottom) {
|
||||||
|
homeScreenPages.forceActiveFocus();
|
||||||
|
} else {
|
||||||
|
folio.HomeScreenState.openSearchWidget();
|
||||||
|
}
|
||||||
|
event.accepted = true;
|
||||||
|
break;
|
||||||
|
case Qt.Key_Down:
|
||||||
|
// Open app drawer
|
||||||
|
folio.HomeScreenState.openAppDrawer();
|
||||||
|
event.accepted = true;
|
||||||
|
break;
|
||||||
|
case Qt.Key_Left:
|
||||||
|
if (folio.HomeScreenState.favoritesBarLocation === Folio.HomeScreenState.Left) {
|
||||||
|
// Go to left page if mounted on the left
|
||||||
|
folio.HomeScreenState.goToPage(folio.HomeScreenState.currentPage - 1, false);
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Qt.Key_Right:
|
||||||
|
if (folio.HomeScreenState.favoritesBarLocation === Folio.HomeScreenState.Right) {
|
||||||
|
// Go to right page if mounted on the right
|
||||||
|
folio.HomeScreenState.goToPage(folio.HomeScreenState.currentPage + 1, false);
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
states: [
|
states: [
|
||||||
State {
|
State {
|
||||||
name: "bottom"
|
name: "bottom"
|
||||||
|
|
@ -499,13 +594,11 @@ Item {
|
||||||
function onSearchWidgetOpenProgressChanged() {
|
function onSearchWidgetOpenProgressChanged() {
|
||||||
if (homeScreenState.searchWidgetOpenProgress === 1.0) {
|
if (homeScreenState.searchWidgetOpenProgress === 1.0) {
|
||||||
searchWidget.requestFocus();
|
searchWidget.requestFocus();
|
||||||
} else if (!root.activeFocus) {
|
|
||||||
root.forceActiveFocus();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onRequestedClose: {
|
onRequestedClose: (triggeredByKeyEvent) => {
|
||||||
homeScreenState.closeSearchWidget();
|
homeScreenState.closeSearchWidget();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,80 @@ Item {
|
||||||
property var pageModel
|
property var pageModel
|
||||||
property var homeScreen
|
property var homeScreen
|
||||||
|
|
||||||
|
onActiveFocusChanged: {
|
||||||
|
if (activeFocus) {
|
||||||
|
// Focus on first delegate when this page is focused
|
||||||
|
let firstDelegate = findFirstDelegate();
|
||||||
|
if (!firstDelegate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
firstDelegate.keyboardFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the first delegate on the page, or null if none exist
|
||||||
|
function findFirstDelegate() {
|
||||||
|
let firstDelegate = delegateRepeater.itemAt(0);
|
||||||
|
if (!firstDelegate) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < delegateRepeater.count; ++i) {
|
||||||
|
let delegate = delegateRepeater.itemAt(i);
|
||||||
|
|
||||||
|
const isAppOrFolder = delegate.pageDelegate.type === Folio.FolioDelegate.Application || delegate.pageDelegate.type === Folio.FolioDelegate.Folder;
|
||||||
|
|
||||||
|
// If it's on an earlier row, or on the same row but earlier column
|
||||||
|
if (isAppOrFolder
|
||||||
|
&& (delegate.row < firstDelegate.row
|
||||||
|
|| (delegate.column < firstDelegate.column
|
||||||
|
&& delegate.row === firstDelegate.row))) {
|
||||||
|
firstDelegate = delegate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return firstDelegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the next application/folder delegate on the page from the given delegate
|
||||||
|
// in a certain direction, or null if none exist.
|
||||||
|
function findNextAppDelegate(delegate, direction: MobileShell.Direction) {
|
||||||
|
let dx = 0;
|
||||||
|
let dy = 0;
|
||||||
|
switch (direction) {
|
||||||
|
case MobileShell.Direction.Up:
|
||||||
|
dy = -1;
|
||||||
|
break;
|
||||||
|
case MobileShell.Direction.Down:
|
||||||
|
dy = 1;
|
||||||
|
break;
|
||||||
|
case MobileShell.Direction.Left:
|
||||||
|
dx = -1;
|
||||||
|
break;
|
||||||
|
case MobileShell.Direction.Right:
|
||||||
|
dx = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let x = delegate.column + dx;
|
||||||
|
let y = delegate.row + dy;
|
||||||
|
|
||||||
|
// Loop in direction to find delegate
|
||||||
|
while (x >= 0 && x < folio.HomeScreenState.pageColumns && y >= 0 && y < folio.HomeScreenState.pageRows) {
|
||||||
|
for (let i = 0; i < delegateRepeater.count; ++i) {
|
||||||
|
let delegate = delegateRepeater.itemAt(i);
|
||||||
|
|
||||||
|
if (delegate.row === y && delegate.column === x
|
||||||
|
&& (delegate.pageDelegate.type === Folio.FolioDelegate.Application
|
||||||
|
|| delegate.pageDelegate.type === Folio.FolioDelegate.Folder)) {
|
||||||
|
return delegate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
x += dx;
|
||||||
|
y += dy;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
MobileShell.HapticsEffect {
|
MobileShell.HapticsEffect {
|
||||||
id: haptics
|
id: haptics
|
||||||
}
|
}
|
||||||
|
|
@ -89,6 +163,7 @@ Item {
|
||||||
|
|
||||||
// repeater of all delegates in the page
|
// repeater of all delegates in the page
|
||||||
Repeater {
|
Repeater {
|
||||||
|
id: delegateRepeater
|
||||||
model: root.pageModel
|
model: root.pageModel
|
||||||
|
|
||||||
delegate: Item {
|
delegate: Item {
|
||||||
|
|
@ -132,6 +207,39 @@ Item {
|
||||||
// folio.HomeScreenState.snapPage();
|
// folio.HomeScreenState.snapPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keyboard navigation between delegates
|
||||||
|
Keys.onPressed: (event) => {
|
||||||
|
let direction = MobileShell.Direction.Up;
|
||||||
|
switch (event.key) {
|
||||||
|
case Qt.Key_Up:
|
||||||
|
direction = MobileShell.Direction.Up;
|
||||||
|
break;
|
||||||
|
case Qt.Key_Down:
|
||||||
|
direction = MobileShell.Direction.Down;
|
||||||
|
break;
|
||||||
|
case Qt.Key_Left:
|
||||||
|
direction = MobileShell.Direction.Left;
|
||||||
|
break;
|
||||||
|
case Qt.Key_Right:
|
||||||
|
direction = MobileShell.Direction.Right;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let nextDelegate = root.findNextAppDelegate(delegate, direction);
|
||||||
|
if (nextDelegate) {
|
||||||
|
nextDelegate.keyboardFocus();
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function keyboardFocus() {
|
||||||
|
if (loader.item) {
|
||||||
|
loader.item.keyboardFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: loader
|
id: loader
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,20 @@ MouseArea {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onActiveFocusChanged: {
|
||||||
|
if (activeFocus) {
|
||||||
|
// When this component is focused, move focus to current page (keyboard navigation)
|
||||||
|
focusCurrentPageForKeyboardNav();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function focusCurrentPageForKeyboardNav() {
|
||||||
|
const currentPage = pageRepeater.itemAt(folio.HomeScreenState.currentPage);
|
||||||
|
if (currentPage) {
|
||||||
|
currentPage.forceActiveFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MobileShell.HapticsEffect {
|
MobileShell.HapticsEffect {
|
||||||
id: haptics
|
id: haptics
|
||||||
}
|
}
|
||||||
|
|
@ -40,6 +54,7 @@ MouseArea {
|
||||||
}
|
}
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
|
id: pageRepeater
|
||||||
model: folio.PageListModel
|
model: folio.PageListModel
|
||||||
|
|
||||||
delegate: HomeScreenPage {
|
delegate: HomeScreenPage {
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,6 @@ ContainmentItem {
|
||||||
folio.FolioSettings.load();
|
folio.FolioSettings.load();
|
||||||
folio.FavouritesModel.load();
|
folio.FavouritesModel.load();
|
||||||
folio.PageListModel.load();
|
folio.PageListModel.load();
|
||||||
|
|
||||||
// ensure the gestures work immediately on load
|
|
||||||
forceActiveFocus();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
property MobileShell.MaskManager maskManager: MobileShell.MaskManager {
|
property MobileShell.MaskManager maskManager: MobileShell.MaskManager {
|
||||||
|
|
@ -167,6 +164,9 @@ ContainmentItem {
|
||||||
leftMargin: homeScreen.leftMargin
|
leftMargin: homeScreen.leftMargin
|
||||||
rightMargin: homeScreen.rightMargin
|
rightMargin: homeScreen.rightMargin
|
||||||
|
|
||||||
|
// Ensure is the focused item at start
|
||||||
|
Component.onCompleted: forceActiveFocus()
|
||||||
|
|
||||||
onWallpaperSelectorTriggered: wallpaperSelectorLoader.active = true
|
onWallpaperSelectorTriggered: wallpaperSelectorLoader.active = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue