Launcher: Restored keyboard navigation with PageUp/PageDown/Home/End + Vim Keys

Oddly Ctrl+J does not work for me...
This commit is contained in:
LemmyCook 2025-09-03 17:02:05 -04:00
parent 392f0e14b2
commit 65f73bb1ba
2 changed files with 95 additions and 56 deletions

View file

@ -42,6 +42,9 @@ NPanel {
property var plugins: [] property var plugins: []
property var activePlugin: null property var activePlugin: null
readonly property int badgeSize: Math.round(Style.baseWidgetSize * 1.6 * scaling)
readonly property int entryHeight: Math.round(badgeSize + Style.marginM * 2 * scaling)
// Public API for plugins // Public API for plugins
function setSearchText(text) { function setSearchText(text) {
searchText = text searchText = text
@ -115,30 +118,6 @@ NPanel {
} }
} }
// Navigation
function selectNext() {
if (results.length > 0) {
// Clamp the index to not exceed the last item
selectedIndex = Math.min(selectedIndex + 1, results.length - 1)
}
}
function selectPrev() {
if (results.length > 0) {
// Clamp the index to not go below the first item (0)
selectedIndex = Math.max(selectedIndex - 1, 0)
}
}
function activate() {
if (results.length > 0 && results[selectedIndex]) {
const item = results[selectedIndex]
if (item.onActivate) {
item.onActivate()
}
}
}
// Load plugins // Load plugins
Component.onCompleted: { Component.onCompleted: {
// Load applications plugin // Load applications plugin
@ -171,12 +150,56 @@ NPanel {
// UI // UI
panelContent: Rectangle { panelContent: Rectangle {
id: ui
color: Color.transparent color: Color.transparent
Component.onCompleted: { // ---------------------
// Navigation
function selectNext() {
if (results.length > 0) {
// Clamp the index to not exceed the last item
selectedIndex = Math.min(selectedIndex + 1, results.length - 1)
}
}
function selectPrevious() {
if (results.length > 0) {
// Clamp the index to not go below the first item (0)
selectedIndex = Math.max(selectedIndex - 1, 0)
}
}
function selectFirst() {
selectedIndex = 0 selectedIndex = 0
if (searchInput?.forceActiveFocus) { }
searchInput.forceActiveFocus()
function selectLast() {
if (results.length > 0) {
selectedIndex = results.length - 1
} else {
selectedIndex = 0
}
}
function selectNextPage() {
if (results.length > 0) {
const page = Math.max(1, Math.floor(resultsList.height / entryHeight))
selectedIndex = Math.min(selectedIndex + page, results.length - 1)
}
}
function selectPreviousPage() {
if (results.length > 0) {
const page = Math.max(1, Math.floor(resultsList.height / entryHeight))
selectedIndex = Math.max(selectedIndex - page, 0)
}
}
function activate() {
if (results.length > 0 && results[selectedIndex]) {
const item = results[selectedIndex]
if (item.onActivate) {
item.onActivate()
}
} }
} }
@ -185,47 +208,61 @@ NPanel {
anchors.margins: Style.marginL * scaling anchors.margins: Style.marginL * scaling
spacing: Style.marginM * scaling spacing: Style.marginM * scaling
FocusScope { Item {
id: searchInputWrap id: searchInputWrap
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: Math.round(Style.barHeight * scaling) Layout.preferredHeight: Math.round(Style.barHeight * scaling)
// This FocusScope should get focus when panel opens
focus: true
NTextInput { NTextInput {
id: searchInput id: searchInput
anchors.fill: parent anchors.fill: parent
// The input should have focus within the scope
focus: true
placeholderText: "Search entries... or use > for commands"
text: searchText
inputMaxWidth: Number.MAX_SAFE_INTEGER inputMaxWidth: Number.MAX_SAFE_INTEGER
function forceActiveFocus() { fontSize: Style.fontSizeL * scaling
// First ensure the scope has focus fontWeight: Style.fontWeightSemiBold
searchInputWrap.forceActiveFocus()
// Then focus the actual input text: searchText
if (inputItem && inputItem.visible) { placeholderText: "Search entries... or use > for commands"
inputItem.forceActiveFocus()
}
}
Component.onCompleted: { Component.onCompleted: {
if (inputItem) { if (searchInput.inputItem && searchInput.inputItem.visible) {
inputItem.font.pointSize = Style.fontSizeL * scaling searchInput.inputItem.forceActiveFocus()
inputItem.verticalAlignment = TextInput.AlignVCenter
} }
} }
onTextChanged: searchText = text onTextChanged: searchText = text
Keys.onDownPressed: root.selectNext()
Keys.onUpPressed: root.selectPrev()
Keys.onReturnPressed: root.activate()
Keys.onEscapePressed: root.close() Keys.onEscapePressed: root.close()
Keys.onReturnPressed: ui.activate()
Keys.onDownPressed: ui.selectNext()
Keys.onUpPressed: ui.selectPrevious()
Keys.onPressed: event => {
if (event.key === Qt.Key_PageDown) {
ui.selectNextPage()
event.accepted = true
} else if (event.key === Qt.Key_PageUp) {
ui.selectPreviousPage()
event.accepted = true
} else if (event.key === Qt.Key_Home) {
ui.selectFirst()
event.accepted = true
} else if (event.key === Qt.Key_End) {
ui.selectLast()
event.accepted = true
}
if (event.modifiers & Qt.ControlModifier) {
switch (event.key) {
case Qt.Key_K:
ui.selectPrevious()
event.accepted = true
break
case Qt.Key_J:
ui.selectNext()
event.accepted = true
break
}
}
}
} }
} }
@ -257,7 +294,6 @@ NPanel {
id: entry id: entry
property bool isSelected: mouseArea.containsMouse || (index === selectedIndex) property bool isSelected: mouseArea.containsMouse || (index === selectedIndex)
property int badgeSize: Style.baseWidgetSize * 1.6 * scaling
// Property to reliably track the current item's ID. // Property to reliably track the current item's ID.
// This changes whenever the delegate is recycled for a new item. // This changes whenever the delegate is recycled for a new item.
@ -272,7 +308,7 @@ NPanel {
} }
width: resultsList.width - Style.marginS * scaling width: resultsList.width - Style.marginS * scaling
height: badgeSize + Style.marginM * 2 * scaling height: entryHeight
radius: Style.radiusM * scaling radius: Style.radiusM * scaling
color: entry.isSelected ? Color.mTertiary : Color.mSurface color: entry.isSelected ? Color.mTertiary : Color.mSurface

View file

@ -11,10 +11,12 @@ ColumnLayout {
property string description: "" property string description: ""
property bool readOnly: false property bool readOnly: false
property bool enabled: true property bool enabled: true
property int inputMaxWidth: 420 * scaling property int inputMaxWidth: Math.round(420 * scaling)
property color labelColor: Color.mOnSurface property color labelColor: Color.mOnSurface
property color descriptionColor: Color.mOnSurfaceVariant property color descriptionColor: Color.mOnSurfaceVariant
property string fontFamily: Settings.data.ui.fontDefault property string fontFamily: Settings.data.ui.fontDefault
property real fontSize: Style.fontSizeS * scaling
property int fontWeight: Style.fontWeightRegular
property alias text: input.text property alias text: input.text
property alias placeholderText: input.placeholderText property alias placeholderText: input.placeholderText
@ -77,7 +79,8 @@ ColumnLayout {
placeholderTextColor: Color.mOnSurfaceVariant placeholderTextColor: Color.mOnSurfaceVariant
background: null background: null
font.family: fontFamily font.family: fontFamily
font.pointSize: Style.fontSizeS * scaling font.pointSize: fontSize
font.weight: fontWeight
onEditingFinished: root.editingFinished() onEditingFinished: root.editingFinished()
} }
} }