QML: fix couch focus/menu scoping and overlays

This commit is contained in:
Marco Allegretti 2026-01-29 19:49:45 +01:00
parent 444ef65a78
commit 5b993cff6b
9 changed files with 1143 additions and 122 deletions

View file

@ -54,10 +54,37 @@ Kirigami.OverlaySheet {
onOpened: playButton.forceActiveFocus() onOpened: playButton.forceActiveFocus()
Shortcut {
enabled: detailsSheet.opened
sequence: "E"
onActivated: {
let app = applicationWindow()
if (app && app.currentConfirmDialog && app.currentConfirmDialog()) return
detailsSheet.editRequested()
}
}
Shortcut {
enabled: detailsSheet.opened
sequence: "F"
onActivated: {
let app = applicationWindow()
if (app && app.currentConfirmDialog && app.currentConfirmDialog()) return
if (game) game.favorite = !game.favorite
}
}
function isDescendant(item, ancestor) { function isDescendant(item, ancestor) {
let p = item let p = item
while (p) { while (p) {
if (p === ancestor) return true if (p === ancestor) return true
if (ancestor.contentItem && p === ancestor.contentItem) return true
if (ancestor.header && p === ancestor.header) return true
if (p.visualParent !== undefined && p.visualParent !== null) {
if (detailsSheet.isDescendant(p.visualParent, ancestor)) return true
} else if (p.popup !== undefined && p.popup !== null && p.popup.visualParent !== undefined && p.popup.visualParent !== null) {
if (detailsSheet.isDescendant(p.popup.visualParent, ancestor)) return true
}
p = p.parent p = p.parent
} }
return false return false
@ -81,6 +108,8 @@ Kirigami.OverlaySheet {
Connections { Connections {
target: GamepadManager target: GamepadManager
function onNavigateLeft() { function onNavigateLeft() {
let app = applicationWindow()
if (app && app.currentConfirmDialog && app.currentConfirmDialog()) return
if (!detailsSheet.opened) return if (!detailsSheet.opened) return
let w = applicationWindow() let w = applicationWindow()
if (!w || !w.activeFocusItem) return if (!w || !w.activeFocusItem) return
@ -88,6 +117,8 @@ Kirigami.OverlaySheet {
detailsSheet.focusNextInChain(false) detailsSheet.focusNextInChain(false)
} }
function onNavigateRight() { function onNavigateRight() {
let app = applicationWindow()
if (app && app.currentConfirmDialog && app.currentConfirmDialog()) return
if (!detailsSheet.opened) return if (!detailsSheet.opened) return
let w = applicationWindow() let w = applicationWindow()
if (!w || !w.activeFocusItem) return if (!w || !w.activeFocusItem) return
@ -95,6 +126,8 @@ Kirigami.OverlaySheet {
detailsSheet.focusNextInChain(true) detailsSheet.focusNextInChain(true)
} }
function onNavigateUp() { function onNavigateUp() {
let app = applicationWindow()
if (app && app.currentConfirmDialog && app.currentConfirmDialog()) return
if (!detailsSheet.opened) return if (!detailsSheet.opened) return
let w = applicationWindow() let w = applicationWindow()
if (!w || !w.activeFocusItem) return if (!w || !w.activeFocusItem) return
@ -102,6 +135,8 @@ Kirigami.OverlaySheet {
detailsSheet.focusNextInChain(false) detailsSheet.focusNextInChain(false)
} }
function onNavigateDown() { function onNavigateDown() {
let app = applicationWindow()
if (app && app.currentConfirmDialog && app.currentConfirmDialog()) return
if (!detailsSheet.opened) return if (!detailsSheet.opened) return
let w = applicationWindow() let w = applicationWindow()
if (!w || !w.activeFocusItem) return if (!w || !w.activeFocusItem) return
@ -109,6 +144,8 @@ Kirigami.OverlaySheet {
detailsSheet.focusNextInChain(true) detailsSheet.focusNextInChain(true)
} }
function onSelectPressed() { function onSelectPressed() {
let app = applicationWindow()
if (app && app.currentConfirmDialog && app.currentConfirmDialog()) return
if (!detailsSheet.opened) return if (!detailsSheet.opened) return
let w = applicationWindow() let w = applicationWindow()
if (!w || !w.activeFocusItem) return if (!w || !w.activeFocusItem) return

View file

@ -86,10 +86,23 @@ Kirigami.Dialog {
property string selectedCoverPath: "" property string selectedCoverPath: ""
readonly property bool anyMenuOpen: runnerCombo && runnerCombo.popup && runnerCombo.popup.visible
function closeCurrentMenu() {
if (runnerCombo && runnerCombo.popup && runnerCombo.popup.visible) {
runnerCombo.popup.close()
}
}
function isDescendant(item, ancestor) { function isDescendant(item, ancestor) {
let p = item let p = item
while (p) { while (p) {
if (p === ancestor || (ancestor.contentItem && p === ancestor.contentItem)) return true if (p === ancestor || (ancestor.contentItem && p === ancestor.contentItem)) return true
if (p.visualParent !== undefined && p.visualParent !== null) {
if (dialog.isDescendant(p.visualParent, ancestor)) return true
} else if (p.popup !== undefined && p.popup !== null && p.popup.visualParent !== undefined && p.popup.visualParent !== null) {
if (dialog.isDescendant(p.popup.visualParent, ancestor)) return true
}
p = p.parent p = p.parent
} }
return false return false
@ -118,7 +131,7 @@ Kirigami.Dialog {
item.toggle() item.toggle()
return return
} }
if (item.hasOwnProperty("checked")) { if (item.checkable !== undefined && item.checkable && item.checked !== undefined) {
item.checked = !item.checked item.checked = !item.checked
return return
} }
@ -203,6 +216,8 @@ Kirigami.Dialog {
Connections { Connections {
target: GamepadManager target: GamepadManager
function onNavigateUp() { function onNavigateUp() {
let app = applicationWindow()
if (app && app.currentConfirmDialog && app.currentConfirmDialog()) return
if (!dialog.opened) return if (!dialog.opened) return
let w = applicationWindow() let w = applicationWindow()
if (!w || !w.activeFocusItem) return if (!w || !w.activeFocusItem) return
@ -210,6 +225,8 @@ Kirigami.Dialog {
dialog.focusNextInChain(false) dialog.focusNextInChain(false)
} }
function onNavigateDown() { function onNavigateDown() {
let app = applicationWindow()
if (app && app.currentConfirmDialog && app.currentConfirmDialog()) return
if (!dialog.opened) return if (!dialog.opened) return
let w = applicationWindow() let w = applicationWindow()
if (!w || !w.activeFocusItem) return if (!w || !w.activeFocusItem) return
@ -217,6 +234,8 @@ Kirigami.Dialog {
dialog.focusNextInChain(true) dialog.focusNextInChain(true)
} }
function onNavigateLeft() { function onNavigateLeft() {
let app = applicationWindow()
if (app && app.currentConfirmDialog && app.currentConfirmDialog()) return
if (!dialog.opened) return if (!dialog.opened) return
let w = applicationWindow() let w = applicationWindow()
if (!w || !w.activeFocusItem) return if (!w || !w.activeFocusItem) return
@ -224,6 +243,8 @@ Kirigami.Dialog {
dialog.focusNextInChain(false) dialog.focusNextInChain(false)
} }
function onNavigateRight() { function onNavigateRight() {
let app = applicationWindow()
if (app && app.currentConfirmDialog && app.currentConfirmDialog()) return
if (!dialog.opened) return if (!dialog.opened) return
let w = applicationWindow() let w = applicationWindow()
if (!w || !w.activeFocusItem) return if (!w || !w.activeFocusItem) return
@ -231,6 +252,8 @@ Kirigami.Dialog {
dialog.focusNextInChain(true) dialog.focusNextInChain(true)
} }
function onSelectPressed() { function onSelectPressed() {
let app = applicationWindow()
if (app && app.currentConfirmDialog && app.currentConfirmDialog()) return
if (!dialog.opened) return if (!dialog.opened) return
let w = applicationWindow() let w = applicationWindow()
if (!w || !w.activeFocusItem) return if (!w || !w.activeFocusItem) return

View file

@ -25,6 +25,12 @@ FocusScope {
readonly property int gameCount: proxyModel.count readonly property int gameCount: proxyModel.count
property url focusedCoverUrl: "" property url focusedCoverUrl: ""
readonly property bool anyMenuOpen: searchHeader.anyMenuOpen
function closeCurrentMenu() {
searchHeader.closeCurrentMenu()
}
function focusSearch() { function focusSearch() {
searchField.forceActiveFocus() searchField.forceActiveFocus()
} }
@ -35,6 +41,14 @@ FocusScope {
} }
function restoreFocus() { function restoreFocus() {
let w = applicationWindow()
if (w && w.hasOwnProperty("pendingSidebarOpen") && w.pendingSidebarOpen) {
w.pendingSidebarOpen = false
if (w.globalDrawer && typeof w.globalDrawer.open === "function") {
w.globalDrawer.open()
return
}
}
if (libraryRoot.searchActive) { if (libraryRoot.searchActive) {
libraryRoot.focusSearch() libraryRoot.focusSearch()
} else { } else {
@ -176,6 +190,16 @@ FocusScope {
} }
Keys.onDownPressed: gameGrid.forceActiveFocus() Keys.onDownPressed: gameGrid.forceActiveFocus()
Connections {
target: GamepadManager
function onNavigateDown() {
let w = applicationWindow()
if (w && w.currentConfirmDialog && w.currentConfirmDialog()) return
if (!searchField.activeFocus) return
gameGrid.forceActiveFocus()
}
}
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -20,6 +20,96 @@ ColumnLayout {
property var pendingDisableImportApply: null property var pendingDisableImportApply: null
property string pendingDisableImportName: "" property string pendingDisableImportName: ""
readonly property bool anyConfirmOpen: disableImportConfirmDialog.opened
|| fetchAllCoversConfirmDialog.opened
|| removeMissingConfirmDialog.opened
|| clearConfirmDialog.opened
|| resetConfirmDialog.opened
readonly property bool anyMenuOpen: uiModeMenu.visible
function currentConfirmDialog() {
if (disableImportConfirmDialog.opened) return disableImportConfirmDialog
if (fetchAllCoversConfirmDialog.opened) return fetchAllCoversConfirmDialog
if (removeMissingConfirmDialog.opened) return removeMissingConfirmDialog
if (clearConfirmDialog.opened) return clearConfirmDialog
if (resetConfirmDialog.opened) return resetConfirmDialog
return null
}
function closeCurrentConfirmDialog() {
let d = currentConfirmDialog()
if (!d) return
if (typeof d.reject === "function") {
d.reject()
} else {
d.close()
}
}
function closeCurrentMenu() {
if (uiModeMenu.visible) {
uiModeMenu.close()
}
}
function isDescendant(item, ancestor) {
let p = item
while (p) {
if (p === ancestor) return true
if (ancestor.contentItem && p === ancestor.contentItem) return true
if (p.visualParent !== undefined && p.visualParent !== null) {
if (settingsPage.isDescendant(p.visualParent, ancestor)) return true
} else if (p.popup !== undefined && p.popup !== null && p.popup.visualParent !== undefined && p.popup.visualParent !== null) {
if (settingsPage.isDescendant(p.popup.visualParent, ancestor)) return true
}
p = p.parent
}
return false
}
function focusNextInMenu(forward) {
let w = applicationWindow()
if (!w || !w.activeFocusItem) return
let next = w.activeFocusItem
for (let i = 0; i < 50; i++) {
next = next.nextItemInFocusChain(forward)
if (!next) return
if (settingsPage.isDescendant(next, uiModeMenu)) {
next.forceActiveFocus()
return
}
}
}
function activateFocusedInMenu() {
let w = applicationWindow()
if (!w || !w.activeFocusItem) return
let item = w.activeFocusItem
if (typeof item.triggered === "function") {
item.triggered()
return
}
if (typeof item.clicked === "function") {
item.clicked()
return
}
}
function focusNoButton(dialog) {
Qt.callLater(function() {
if (!dialog) return
if (typeof dialog.standardButton === "function") {
let noButton = dialog.standardButton(Kirigami.Dialog.No)
if (noButton) {
noButton.forceActiveFocus()
return
}
}
dialog.forceActiveFocus()
})
}
function requestDisableImport(sourceName, applyFn) { function requestDisableImport(sourceName, applyFn) {
pendingDisableImportName = sourceName pendingDisableImportName = sourceName
pendingDisableImportApply = applyFn pendingDisableImportApply = applyFn
@ -48,7 +138,7 @@ ColumnLayout {
text: i18n("UI mode") text: i18n("UI mode")
description: { description: {
if (App.config.uiMode === Config.Desktop) return i18n("Desktop") if (App.config.uiMode === Config.Desktop) return i18n("Desktop")
if (App.config.uiMode === Config.Handheld) return i18n("Handheld") if (App.config.uiMode === Config.Couch) return i18n("Couch")
return i18n("Automatic") return i18n("Automatic")
} }
icon.name: "view-fullscreen" icon.name: "view-fullscreen"
@ -56,8 +146,12 @@ ColumnLayout {
QQC2.Menu { QQC2.Menu {
id: uiModeMenu id: uiModeMenu
focus: true
onOpened: Qt.callLater(function() { uiModeAuto.forceActiveFocus() })
QQC2.MenuItem { QQC2.MenuItem {
id: uiModeAuto
text: i18n("Automatic") text: i18n("Automatic")
checkable: true checkable: true
checked: App.config.uiMode === Config.Auto checked: App.config.uiMode === Config.Auto
@ -65,6 +159,7 @@ ColumnLayout {
} }
QQC2.MenuItem { QQC2.MenuItem {
id: uiModeDesktop
text: i18n("Desktop") text: i18n("Desktop")
checkable: true checkable: true
checked: App.config.uiMode === Config.Desktop checked: App.config.uiMode === Config.Desktop
@ -72,15 +167,55 @@ ColumnLayout {
} }
QQC2.MenuItem { QQC2.MenuItem {
text: i18n("Handheld") id: uiModeCouch
text: i18n("Couch")
checkable: true checkable: true
checked: App.config.uiMode === Config.Handheld checked: App.config.uiMode === Config.Couch
onTriggered: App.config.uiMode = Config.Handheld onTriggered: App.config.uiMode = Config.Couch
} }
} }
} }
} }
Connections {
target: GamepadManager
function onNavigateUp() {
if (!uiModeMenu.visible) return
let w = applicationWindow()
if (!w || !w.activeFocusItem) return
if (!settingsPage.isDescendant(w.activeFocusItem, uiModeMenu)) return
settingsPage.focusNextInMenu(false)
}
function onNavigateDown() {
if (!uiModeMenu.visible) return
let w = applicationWindow()
if (!w || !w.activeFocusItem) return
if (!settingsPage.isDescendant(w.activeFocusItem, uiModeMenu)) return
settingsPage.focusNextInMenu(true)
}
function onNavigateLeft() {
if (!uiModeMenu.visible) return
let w = applicationWindow()
if (!w || !w.activeFocusItem) return
if (!settingsPage.isDescendant(w.activeFocusItem, uiModeMenu)) return
settingsPage.focusNextInMenu(false)
}
function onNavigateRight() {
if (!uiModeMenu.visible) return
let w = applicationWindow()
if (!w || !w.activeFocusItem) return
if (!settingsPage.isDescendant(w.activeFocusItem, uiModeMenu)) return
settingsPage.focusNextInMenu(true)
}
function onSelectPressed() {
if (!uiModeMenu.visible) return
let w = applicationWindow()
if (!w || !w.activeFocusItem) return
if (!settingsPage.isDescendant(w.activeFocusItem, uiModeMenu)) return
settingsPage.activateFocusedInMenu()
}
}
FormCard.FormHeader { FormCard.FormHeader {
Layout.topMargin: Kirigami.Units.mediumSpacing Layout.topMargin: Kirigami.Units.mediumSpacing
Layout.fillWidth: true Layout.fillWidth: true
@ -553,6 +688,7 @@ FormCard.FormHeader {
title: i18n("Disable Import Source") title: i18n("Disable Import Source")
subtitle: i18n("Disabling %1 will remove all games imported from that source. Are you sure?", settingsPage.pendingDisableImportName) subtitle: i18n("Disabling %1 will remove all games imported from that source. Are you sure?", settingsPage.pendingDisableImportName)
standardButtons: Kirigami.Dialog.Yes | Kirigami.Dialog.No standardButtons: Kirigami.Dialog.Yes | Kirigami.Dialog.No
onOpened: settingsPage.focusNoButton(disableImportConfirmDialog)
onAccepted: { onAccepted: {
if (settingsPage.pendingDisableImportApply) { if (settingsPage.pendingDisableImportApply) {
settingsPage.pendingDisableImportApply() settingsPage.pendingDisableImportApply()
@ -573,6 +709,7 @@ FormCard.FormHeader {
? i18n("This will download cover art for all games and may replace existing covers. Continue?") ? i18n("This will download cover art for all games and may replace existing covers. Continue?")
: i18n("This will download cover art for games that are missing covers. Continue?") : i18n("This will download cover art for games that are missing covers. Continue?")
standardButtons: Kirigami.Dialog.Yes | Kirigami.Dialog.No standardButtons: Kirigami.Dialog.Yes | Kirigami.Dialog.No
onOpened: settingsPage.focusNoButton(fetchAllCoversConfirmDialog)
onAccepted: App.steamGridDB.fetchAllCovers() onAccepted: App.steamGridDB.fetchAllCovers()
} }
@ -581,6 +718,7 @@ FormCard.FormHeader {
title: i18n("Remove Missing Games") title: i18n("Remove Missing Games")
subtitle: i18n("This will remove games whose executables cannot be found. This cannot be undone. Continue?") subtitle: i18n("This will remove games whose executables cannot be found. This cannot be undone. Continue?")
standardButtons: Kirigami.Dialog.Yes | Kirigami.Dialog.No standardButtons: Kirigami.Dialog.Yes | Kirigami.Dialog.No
onOpened: settingsPage.focusNoButton(removeMissingConfirmDialog)
onAccepted: App.removeMissingGames() onAccepted: App.removeMissingGames()
} }
@ -589,6 +727,7 @@ FormCard.FormHeader {
title: i18n("Clear Library") title: i18n("Clear Library")
subtitle: i18n("Are you sure you want to remove all games?") subtitle: i18n("Are you sure you want to remove all games?")
standardButtons: Kirigami.Dialog.Yes | Kirigami.Dialog.No standardButtons: Kirigami.Dialog.Yes | Kirigami.Dialog.No
onOpened: settingsPage.focusNoButton(clearConfirmDialog)
onAccepted: App.clearLibrary() onAccepted: App.clearLibrary()
} }
@ -597,6 +736,7 @@ FormCard.FormHeader {
title: i18n("Reset Application") title: i18n("Reset Application")
subtitle: i18n("This will remove all games and reset all settings to defaults. Are you sure?") subtitle: i18n("This will remove all games and reset all settings to defaults. Are you sure?")
standardButtons: Kirigami.Dialog.Yes | Kirigami.Dialog.No standardButtons: Kirigami.Dialog.Yes | Kirigami.Dialog.No
onOpened: settingsPage.focusNoButton(resetConfirmDialog)
onAccepted: { onAccepted: {
App.clearLibrary() App.clearLibrary()
App.config.resetToDefaults() App.config.resetToDefaults()

View file

@ -16,6 +16,8 @@ ColumnLayout {
property string currentSourceName: i18n("All Games") property string currentSourceName: i18n("All Games")
property bool suppressAutoClose: false property bool suppressAutoClose: false
readonly property bool hasSidebarFocus: sourceList.activeFocus || importAction.activeFocus || settingsAction.activeFocus || aboutAction.activeFocus
readonly property int adaptiveFocusRingWidth: 1 readonly property int adaptiveFocusRingWidth: 1
signal sourceSelected(string source) signal sourceSelected(string source)
@ -45,6 +47,17 @@ ColumnLayout {
sourceList.positionViewAtIndex(i, ListView.Contain) sourceList.positionViewAtIndex(i, ListView.Contain)
} }
function applySourceById(sourceId) {
for (let i = 0; i < sourceModel.count; i++) {
let item = sourceModel.get(i)
if (item && item.sourceId === sourceId) {
applySourceAtIndex(i)
return
}
}
applySourceAtIndex(0)
}
function cycleSource(delta) { function cycleSource(delta) {
if (sourceModel.count <= 0) return if (sourceModel.count <= 0) return
let i = sourceList.currentIndex let i = sourceList.currentIndex
@ -97,9 +110,31 @@ ColumnLayout {
Connections { Connections {
target: GamepadManager target: GamepadManager
function onNavigateUp() { if (sourceList.activeFocus) sourceList.decrementCurrentIndex() } function onNavigateUp() {
function onNavigateDown() { if (sourceList.activeFocus) sourceList.incrementCurrentIndex() } let w = applicationWindow()
function onSelectPressed() { if (sourceList.activeFocus) sidebarRoot.activateCurrentItem() } if (w && w.currentConfirmDialog && w.currentConfirmDialog()) return
if (!sourceList.activeFocus) return
if (sourceList.count > 0 && sourceList.currentIndex <= 0) {
aboutAction.forceActiveFocus()
} else {
sourceList.decrementCurrentIndex()
}
}
function onNavigateDown() {
let w = applicationWindow()
if (w && w.currentConfirmDialog && w.currentConfirmDialog()) return
if (!sourceList.activeFocus) return
if (sourceList.count > 0 && sourceList.currentIndex >= sourceList.count - 1) {
importAction.forceActiveFocus()
} else {
sourceList.incrementCurrentIndex()
}
}
function onSelectPressed() {
let w = applicationWindow()
if (w && w.currentConfirmDialog && w.currentConfirmDialog()) return
if (sourceList.activeFocus) sidebarRoot.activateCurrentItem()
}
} }
delegate: QQC2.ItemDelegate { delegate: QQC2.ItemDelegate {
@ -294,6 +329,8 @@ ColumnLayout {
Connections { Connections {
target: GamepadManager target: GamepadManager
function onNavigateUp() { function onNavigateUp() {
let w = applicationWindow()
if (w && w.currentConfirmDialog && w.currentConfirmDialog()) return
if (aboutAction.activeFocus) { if (aboutAction.activeFocus) {
settingsAction.forceActiveFocus() settingsAction.forceActiveFocus()
} else if (settingsAction.activeFocus) { } else if (settingsAction.activeFocus) {
@ -304,13 +341,20 @@ ColumnLayout {
} }
} }
function onNavigateDown() { function onNavigateDown() {
let w = applicationWindow()
if (w && w.currentConfirmDialog && w.currentConfirmDialog()) return
if (importAction.activeFocus) { if (importAction.activeFocus) {
settingsAction.forceActiveFocus() settingsAction.forceActiveFocus()
} else if (settingsAction.activeFocus) { } else if (settingsAction.activeFocus) {
aboutAction.forceActiveFocus() aboutAction.forceActiveFocus()
} else if (aboutAction.activeFocus) {
sourceList.forceActiveFocus()
sourceList.currentIndex = 0
} }
} }
function onSelectPressed() { function onSelectPressed() {
let w = applicationWindow()
if (w && w.currentConfirmDialog && w.currentConfirmDialog()) return
if (importAction.activeFocus) { if (importAction.activeFocus) {
importAction.clicked() importAction.clicked()
} else if (settingsAction.activeFocus) { } else if (settingsAction.activeFocus) {

View file

@ -7,9 +7,19 @@ import org.kde.alakarte
RowLayout { RowLayout {
id: root id: root
spacing: Kirigami.Units.largeSpacing spacing: uiMode === Config.Couch ? Kirigami.Units.largeSpacing * 1.25 : Kirigami.Units.largeSpacing
readonly property bool useGamepadHints: GamepadManager.connected property int uiMode: Config.Auto
property int activeInput: InputManager.KeyboardMouse
readonly property bool useGamepadHints: {
if (!GamepadManager.connected) return false
if (uiMode === Config.Couch) {
if (activeInput === InputManager.KeyboardMouse && InputManager.hasSeenKeyboardMouse) return false
return true
}
return activeInput === InputManager.Gamepad
}
readonly property int style: GamepadManager.controllerStyle readonly property int style: GamepadManager.controllerStyle
property string context: "library" property string context: "library"
@ -49,6 +59,9 @@ RowLayout {
function iconForAux(action) { function iconForAux(action) {
if (!useGamepadHints) return "" if (!useGamepadHints) return ""
if (action === "dpad") {
return "qrc:/qt/qml/org/kde/alakarte/qml/icons/gamepad/generic/dpad.svg"
}
if (action === "lb") { if (action === "lb") {
return "qrc:/qt/qml/org/kde/alakarte/qml/icons/gamepad/generic/lb.svg" return "qrc:/qt/qml/org/kde/alakarte/qml/icons/gamepad/generic/lb.svg"
} }
@ -72,12 +85,25 @@ RowLayout {
} }
function keyboardLabel(action) { function keyboardLabel(action) {
if (root.context === "confirm" || root.context === "confirm_remove") {
switch (action) {
case "navigate": return "Tab"
case "confirm": return "Enter"
case "back": return "Esc"
case "menu": return ""
default: return ""
}
}
if (root.context === "library") { if (root.context === "library") {
switch (action) { switch (action) {
case "navigate": return i18n("Arrows")
case "confirm": return "Space" case "confirm": return "Space"
case "back": return "Esc" case "back": return ""
case "details": return "Enter" case "details": return "Enter"
case "search": return "Ctrl+F" case "search": return "Ctrl+F"
case "lb": return "Ctrl+PgUp"
case "rb": return "Ctrl+PgDown"
case "menu": return "Ctrl+," case "menu": return "Ctrl+,"
default: return "" default: return ""
} }
@ -85,6 +111,7 @@ RowLayout {
if (root.context === "edit") { if (root.context === "edit") {
switch (action) { switch (action) {
case "navigate": return "Tab"
case "confirm": return "Enter" case "confirm": return "Enter"
case "back": return "Esc" case "back": return "Esc"
default: return "" default: return ""
@ -93,8 +120,11 @@ RowLayout {
if (root.context === "details") { if (root.context === "details") {
switch (action) { switch (action) {
case "navigate": return "Tab"
case "confirm": return "Enter" case "confirm": return "Enter"
case "back": return "Esc" case "back": return "Esc"
case "details": return "F"
case "search": return "E"
case "menu": return "Ctrl+," case "menu": return "Ctrl+,"
default: return "" default: return ""
} }
@ -102,6 +132,19 @@ RowLayout {
if (root.context === "sidebar") { if (root.context === "sidebar") {
switch (action) { switch (action) {
case "navigate": return i18n("Arrows/Tab")
case "confirm": return "Enter"
case "back": return "Esc"
case "lb": return "Ctrl+PgUp"
case "rb": return "Ctrl+PgDown"
case "menu": return "Ctrl+,"
default: return ""
}
}
if (root.context === "settings") {
switch (action) {
case "navigate": return "Tab"
case "confirm": return "Enter" case "confirm": return "Enter"
case "back": return "Esc" case "back": return "Esc"
case "menu": return "Ctrl+," case "menu": return "Ctrl+,"
@ -109,8 +152,9 @@ RowLayout {
} }
} }
if (root.context === "settings" || root.context === "import" || root.context === "sidebar") { if (root.context === "settings" || root.context === "import" || root.context === "sidebar" || root.context === "about") {
switch (action) { switch (action) {
case "navigate": return "Tab"
case "confirm": return "Enter" case "confirm": return "Enter"
case "back": return "Esc" case "back": return "Esc"
default: return "" default: return ""
@ -121,53 +165,90 @@ RowLayout {
} }
function actionLabel(action) { function actionLabel(action) {
if (root.context === "confirm" || root.context === "confirm_remove") {
switch (action) {
case "navigate": return (useGamepadHints || keyboardLabel("navigate") !== "") ? i18n("Navigate") : ""
case "confirm": return i18n("Select")
case "back": return i18n("Cancel")
case "menu": return (useGamepadHints && uiMode === Config.Couch) ? i18n("Menu") : ""
default: return ""
}
}
if (root.context === "library") { if (root.context === "library") {
switch (action) { switch (action) {
case "navigate": return (useGamepadHints || keyboardLabel("navigate") !== "") ? i18n("Navigate") : ""
case "confirm": return i18n("Play") case "confirm": return i18n("Play")
case "back": return i18n("Back") case "back": return ""
case "details": return i18n("Details") case "details": return i18n("Details")
case "search": return i18n("Search") case "search": return i18n("Search")
case "lb": return i18n("Prev Source") case "lb": return i18n("Prev Source")
case "rb": return i18n("Next Source") case "rb": return i18n("Next Source")
case "menu": return i18n("Settings") case "menu": return (useGamepadHints && uiMode === Config.Couch) ? i18n("Menu") : i18n("Settings")
default: return ""
}
}
if (root.context === "settings") {
switch (action) {
case "navigate": return (useGamepadHints || keyboardLabel("navigate") !== "") ? i18n("Navigate") : ""
case "confirm": return i18n("Select")
case "back": return i18n("Back")
case "menu": return (useGamepadHints && uiMode === Config.Couch) ? i18n("Menu") : i18n("Close")
default: return "" default: return ""
} }
} }
if (root.context === "edit") { if (root.context === "edit") {
switch (action) { switch (action) {
case "navigate": return (useGamepadHints || keyboardLabel("navigate") !== "") ? i18n("Navigate") : ""
case "confirm": return i18n("Select") case "confirm": return i18n("Select")
case "back": return i18n("Back") case "back": return i18n("Back")
case "menu": return (useGamepadHints && uiMode === Config.Couch) ? i18n("Menu") : ""
default: return "" default: return ""
} }
} }
if (root.context === "details") { if (root.context === "details") {
switch (action) { switch (action) {
case "confirm": return i18n("Play") case "navigate": return (useGamepadHints || keyboardLabel("navigate") !== "") ? i18n("Navigate") : ""
case "confirm": return i18n("Select")
case "back": return i18n("Back") case "back": return i18n("Back")
case "details": return i18n("Favorite") case "details": return i18n("Favorite")
case "search": return i18n("Edit") case "search": return i18n("Edit")
case "menu": return i18n("Settings") case "menu": return (useGamepadHints && uiMode === Config.Couch) ? i18n("Menu") : i18n("Settings")
default: return "" default: return ""
} }
} }
if (root.context === "sidebar") { if (root.context === "sidebar") {
switch (action) { switch (action) {
case "navigate": return (useGamepadHints || keyboardLabel("navigate") !== "") ? i18n("Navigate") : ""
case "confirm": return i18n("Select") case "confirm": return i18n("Select")
case "back": return i18n("Back") case "back": return i18n("Back")
case "lb": return i18n("Prev Source") case "lb": return i18n("Prev Source")
case "rb": return i18n("Next Source") case "rb": return i18n("Next Source")
case "menu": return i18n("Settings") case "menu": return (useGamepadHints && uiMode === Config.Couch) ? i18n("Close") : i18n("Settings")
default: return "" default: return ""
} }
} }
if (root.context === "settings" || root.context === "import" || root.context === "sidebar") { if (root.context === "settings" || root.context === "import" || root.context === "sidebar") {
switch (action) { switch (action) {
case "navigate": return (useGamepadHints || keyboardLabel("navigate") !== "") ? i18n("Navigate") : ""
case "confirm": return i18n("Select") case "confirm": return i18n("Select")
case "back": return i18n("Back") case "back": return i18n("Back")
case "menu": return (useGamepadHints && uiMode === Config.Couch) ? i18n("Menu") : ""
default: return ""
}
}
if (root.context === "about") {
switch (action) {
case "navigate": return (useGamepadHints || keyboardLabel("navigate") !== "") ? i18n("Navigate") : ""
case "confirm": return i18n("Select")
case "back": return i18n("Back")
case "menu": return (useGamepadHints && uiMode === Config.Couch) ? i18n("Menu") : ""
default: return "" default: return ""
} }
} }
@ -195,7 +276,7 @@ RowLayout {
border.width: 1 border.width: 1
border.color: Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.2) border.color: Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.2)
Layout.preferredHeight: Kirigami.Units.gridUnit * 1.4 Layout.preferredHeight: uiMode === Config.Couch ? Kirigami.Units.gridUnit * 1.8 : Kirigami.Units.gridUnit * 1.4
Layout.preferredWidth: useGamepadHints Layout.preferredWidth: useGamepadHints
? Layout.preferredHeight ? Layout.preferredHeight
: Math.max(keyText.implicitWidth + Kirigami.Units.mediumSpacing * 2, Layout.preferredHeight) : Math.max(keyText.implicitWidth + Kirigami.Units.mediumSpacing * 2, Layout.preferredHeight)
@ -215,7 +296,7 @@ RowLayout {
anchors.centerIn: parent anchors.centerIn: parent
text: parent.parent.keyLabel text: parent.parent.keyLabel
font.bold: true font.bold: true
font.pointSize: Kirigami.Theme.smallFont.pointSize font.pointSize: uiMode === Config.Couch ? Kirigami.Theme.defaultFont.pointSize : Kirigami.Theme.smallFont.pointSize
color: Kirigami.Theme.textColor color: Kirigami.Theme.textColor
visible: !useGamepadHints visible: !useGamepadHints
} }
@ -224,11 +305,18 @@ RowLayout {
QQC2.Label { QQC2.Label {
text: parent.label text: parent.label
color: Kirigami.Theme.textColor color: Kirigami.Theme.textColor
font.pointSize: Kirigami.Theme.smallFont.pointSize font.pointSize: uiMode === Config.Couch ? Kirigami.Theme.defaultFont.pointSize : Kirigami.Theme.smallFont.pointSize
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
} }
} }
HintItem {
action: "navigate"
label: actionLabel("navigate")
iconSource: iconForAux("dpad")
keyLabel: keyboardLabel("navigate")
}
HintItem { HintItem {
action: "confirm" action: "confirm"
label: actionLabel("confirm") label: actionLabel("confirm")
@ -261,20 +349,22 @@ RowLayout {
action: "lb" action: "lb"
label: actionLabel("lb") label: actionLabel("lb")
iconSource: (root.context === "library" || root.context === "sidebar") ? iconForAux("lb") : "" iconSource: (root.context === "library" || root.context === "sidebar") ? iconForAux("lb") : ""
keyLabel: "" keyLabel: keyboardLabel("lb")
} }
HintItem { HintItem {
action: "rb" action: "rb"
label: actionLabel("rb") label: actionLabel("rb")
iconSource: (root.context === "library" || root.context === "sidebar") ? iconForAux("rb") : "" iconSource: (root.context === "library" || root.context === "sidebar") ? iconForAux("rb") : ""
keyLabel: "" keyLabel: keyboardLabel("rb")
} }
HintItem { HintItem {
action: "menu" action: "menu"
label: actionLabel("menu") label: actionLabel("menu")
iconSource: (root.context === "library" || root.context === "details" || root.context === "sidebar") ? iconForAux("menu") : "" iconSource: (useGamepadHints && uiMode === Config.Couch)
? iconForAux("menu")
: ((root.context === "library" || root.context === "details" || root.context === "sidebar" || root.context === "settings") ? iconForAux("menu") : "")
keyLabel: keyboardLabel("menu") keyLabel: keyboardLabel("menu")
} }
} }

View file

@ -74,11 +74,29 @@ GridView {
Connections { Connections {
target: GamepadManager target: GamepadManager
function onNavigateUp() { if (gridView.activeFocus) gridView.navigateUp() } function onNavigateUp() {
function onNavigateDown() { if (gridView.activeFocus) gridView.navigateDown() } let w = applicationWindow()
function onNavigateLeft() { if (gridView.activeFocus) gridView.navigateLeft() } if (w && w.currentConfirmDialog && w.currentConfirmDialog()) return
function onNavigateRight() { if (gridView.activeFocus) gridView.navigateRight() } if (gridView.activeFocus) gridView.navigateUp()
}
function onNavigateDown() {
let w = applicationWindow()
if (w && w.currentConfirmDialog && w.currentConfirmDialog()) return
if (gridView.activeFocus) gridView.navigateDown()
}
function onNavigateLeft() {
let w = applicationWindow()
if (w && w.currentConfirmDialog && w.currentConfirmDialog()) return
if (gridView.activeFocus) gridView.navigateLeft()
}
function onNavigateRight() {
let w = applicationWindow()
if (w && w.currentConfirmDialog && w.currentConfirmDialog()) return
if (gridView.activeFocus) gridView.navigateRight()
}
function onSelectPressed() { function onSelectPressed() {
let w = applicationWindow()
if (w && w.currentConfirmDialog && w.currentConfirmDialog()) return
if (!gridView.activeFocus) { if (!gridView.activeFocus) {
return return
} }

View file

@ -13,6 +13,57 @@ QQC2.ToolBar {
property alias searchField: searchFieldContainer.data property alias searchField: searchFieldContainer.data
property int currentSortMode: 0 property int currentSortMode: 0
readonly property bool anyMenuOpen: sortMenu.visible
function closeCurrentMenu() {
if (sortMenu.visible) {
sortMenu.close()
}
}
function isDescendant(item, ancestor) {
let p = item
while (p) {
if (p === ancestor) return true
if (ancestor.contentItem && p === ancestor.contentItem) return true
if (p.visualParent !== undefined && p.visualParent !== null) {
if (searchHeader.isDescendant(p.visualParent, ancestor)) return true
} else if (p.popup !== undefined && p.popup !== null && p.popup.visualParent !== undefined && p.popup.visualParent !== null) {
if (searchHeader.isDescendant(p.popup.visualParent, ancestor)) return true
}
p = p.parent
}
return false
}
function focusNextInMenu(forward) {
let w = applicationWindow()
if (!w || !w.activeFocusItem) return
let next = w.activeFocusItem
for (let i = 0; i < 50; i++) {
next = next.nextItemInFocusChain(forward)
if (!next) return
if (searchHeader.isDescendant(next, sortMenu)) {
next.forceActiveFocus()
return
}
}
}
function activateFocusedInMenu() {
let w = applicationWindow()
if (!w || !w.activeFocusItem) return
let item = w.activeFocusItem
if (typeof item.triggered === "function") {
item.triggered()
return
}
if (typeof item.clicked === "function") {
item.clicked()
return
}
}
signal searchChanged(string text) signal searchChanged(string text)
signal sortChanged(int mode) signal sortChanged(int mode)
@ -47,8 +98,12 @@ QQC2.ToolBar {
QQC2.Menu { QQC2.Menu {
id: sortMenu id: sortMenu
focus: true
onOpened: Qt.callLater(function() { sortModeLastPlayed.forceActiveFocus() })
QQC2.MenuItem { QQC2.MenuItem {
id: sortModeLastPlayed
text: i18n("Last Played") text: i18n("Last Played")
checkable: true checkable: true
checked: searchHeader.currentSortMode === 0 checked: searchHeader.currentSortMode === 0
@ -59,6 +114,7 @@ QQC2.ToolBar {
} }
QQC2.MenuItem { QQC2.MenuItem {
id: sortModeName
text: i18n("Name") text: i18n("Name")
checkable: true checkable: true
checked: searchHeader.currentSortMode === 1 checked: searchHeader.currentSortMode === 1
@ -69,6 +125,7 @@ QQC2.ToolBar {
} }
QQC2.MenuItem { QQC2.MenuItem {
id: sortModePlayTime
text: i18n("Play Time") text: i18n("Play Time")
checkable: true checkable: true
checked: searchHeader.currentSortMode === 2 checked: searchHeader.currentSortMode === 2
@ -80,4 +137,43 @@ QQC2.ToolBar {
} }
} }
} }
Connections {
target: GamepadManager
function onNavigateUp() {
if (!sortMenu.visible) return
let w = applicationWindow()
if (!w || !w.activeFocusItem) return
if (!searchHeader.isDescendant(w.activeFocusItem, sortMenu)) return
searchHeader.focusNextInMenu(false)
}
function onNavigateDown() {
if (!sortMenu.visible) return
let w = applicationWindow()
if (!w || !w.activeFocusItem) return
if (!searchHeader.isDescendant(w.activeFocusItem, sortMenu)) return
searchHeader.focusNextInMenu(true)
}
function onNavigateLeft() {
if (!sortMenu.visible) return
let w = applicationWindow()
if (!w || !w.activeFocusItem) return
if (!searchHeader.isDescendant(w.activeFocusItem, sortMenu)) return
searchHeader.focusNextInMenu(false)
}
function onNavigateRight() {
if (!sortMenu.visible) return
let w = applicationWindow()
if (!w || !w.activeFocusItem) return
if (!searchHeader.isDescendant(w.activeFocusItem, sortMenu)) return
searchHeader.focusNextInMenu(true)
}
function onSelectPressed() {
if (!sortMenu.visible) return
let w = applicationWindow()
if (!w || !w.activeFocusItem) return
if (!searchHeader.isDescendant(w.activeFocusItem, sortMenu)) return
searchHeader.activateFocusedInMenu()
}
}
} }