shift-shell/containments/homescreens/folio/package/contents/ui/AppDrawerGrid.qml
Devin Lin 0bcab0ae3a homescreens/folio: Add keyboard navigation to app drawer and fix on
search

This commit is a subset of
https://invent.kde.org/plasma/plasma-mobile/-/merge_requests/694 to add
keyboard navigation to the app drawer.

Pressing the arrow keys while in the app drawer will allow you to
navigate between the apps and the search bar, and going up will exit the
app drawer. Escape/Back is also supported for unfocusing the search bar
and exiting the view.

This also fixes an issue in the keyboard navigation on the search screen
where it wouldn't close when there are no search results.
2025-07-10 17:04:08 -04:00

148 lines
4.9 KiB
QML

// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as Controls
import org.kde.plasma.plasmoid 2.0
import org.kde.plasma.components 3.0 as PC3
import org.kde.kirigami as Kirigami
import org.kde.plasma.private.mobileshell as MobileShell
import org.kde.plasma.private.mobileshell.state as MobileShellState
import org.kde.private.mobile.homescreen.folio 1.0 as Folio
import "./delegate"
MobileShell.GridView {
id: root
property Folio.HomeScreen folio
cacheBuffer: cellHeight * 20
reuseItems: true
layer.enabled: true
keyNavigationEnabled: true
highlightMoveDuration: 0
highlight: null // We supply our own highlight from the delegate
property var homeScreen
property real headerHeight
readonly property int reservedSpaceForLabel: folio.HomeScreenState.pageDelegateLabelHeight
readonly property real effectiveContentWidth: width - leftMargin - rightMargin
readonly property real horizontalMargin: Math.round(width * 0.05)
leftMargin: horizontalMargin
rightMargin: horizontalMargin
cellWidth: effectiveContentWidth / Math.min(Math.floor(effectiveContentWidth / (folio.FolioSettings.delegateIconSize + Kirigami.Units.largeSpacing * 3.5)), 8)
cellHeight: cellWidth + reservedSpaceForLabel
boundsBehavior: Flickable.DragAndOvershootBounds
readonly property int columns: Math.floor(effectiveContentWidth / cellWidth)
readonly property int rows: Math.ceil(root.count / columns)
// HACK: the first swipe from the top of the app drawer is done from HomeScreenState, not the flickable
// due to issues with Flickable getting its swipe stolen by SwipeArea
interactive: (dragging || !atYBeginning) // allow us to drag to the top
&& folio.HomeScreenState.swipeState !== Folio.HomeScreenState.SwipingAppDrawerGrid
Connections {
target: folio.HomeScreenState
function onSwipeStateChanged() {
if (folio.HomeScreenState.swipeState === Folio.HomeScreenState.SwipingAppDrawerGrid) {
velocityCalculator.startMeasure();
velocityCalculator.changePosition(root.contentY);
}
}
function onAppDrawerGridYChanged(y) {
const maxContentY = Math.max(0, root.contentHeight - root.height);
let contentY = root.contentY - y;
if (root.contentHeight < root.height) {
// prevent bottom overscroll only if contents are smaller than the view
contentY = Math.min(maxContentY, contentY);
}
root.contentY = contentY;
velocityCalculator.changePosition(root.contentY);
}
function onAppDrawerGridFlickRequested() {
root.flick(0, -velocityCalculator.velocity);
}
}
MobileShell.VelocityCalculator {
id: velocityCalculator
}
MobileShell.HapticsEffect {
id: haptics
}
model: folio.ApplicationListSearchModel
// Keyboard focus on app delegate when it is the selected item
onCurrentItemChanged: {
if (currentItem) {
currentItem.keyboardFocus();
}
}
delegate: AppDelegate {
id: appDelegate
folio: root.folio
shadow: false
application: model.delegate.application
width: root.cellWidth
height: root.cellHeight
onPressAndHold: {
// prevent editing if lock layout is enabled
if (folio.FolioSettings.lockLayout) return;
const mappedCoords = root.homeScreen.prepareStartDelegateDrag(model.delegate, appDelegate.delegateItem, true);
folio.HomeScreenState.closeAppDrawer();
haptics.buttonVibrate();
// we need to adjust because app drawer delegates have a different size than regular homescreen delegates
const centerX = mappedCoords.x + root.cellWidth / 2;
const centerY = mappedCoords.y + root.cellHeight / 2;
folio.HomeScreenState.startDelegateAppDrawerDrag(
centerX - folio.HomeScreenState.pageCellWidth / 2,
centerY - folio.HomeScreenState.pageCellHeight / 2,
appDelegate.pressPosition.x * (folio.HomeScreenState.pageCellWidth / root.cellWidth),
appDelegate.pressPosition.y * (folio.HomeScreenState.pageCellHeight / root.cellHeight),
model.delegate.application.storageId
);
}
}
PC3.ScrollBar.vertical: PC3.ScrollBar {
id: scrollBar
interactive: true
enabled: true
implicitWidth: Kirigami.Units.smallSpacing
Behavior on opacity {
OpacityAnimator {
duration: Kirigami.Units.longDuration * 2
easing.type: Easing.InOutQuad
}
}
contentItem: Rectangle {
radius: width / 2
color: Qt.rgba(1, 1, 1, 0.3)
}
}
}