2026-01-18 12:13:07 +00:00
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2026 A-La-Karte Contributors
import QtQuick
import QtQuick . Controls as QQC2
import QtQuick . Layouts
import org . kde . kirigami as Kirigami
import org . kde . kirigamiaddons . formcard as FormCard
import org . kde . alakarte
import "components"
Kirigami . ApplicationWindow {
id: root
title: i18n ( "A-La-Karte" )
minimumWidth: Kirigami . Units . gridUnit * 25
minimumHeight: Kirigami . Units . gridUnit * 20
width: Kirigami . Units . gridUnit * 55
height: Kirigami . Units . gridUnit * 40
property var selectedGame: null
2026-01-25 09:03:46 +00:00
property var pendingRemoveGame: null
2026-01-18 12:13:07 +00:00
property string currentSource: "all"
property bool searchActive: false
2026-01-24 19:50:20 +00:00
property bool settingsLayerOpen: false
2026-01-29 18:49:45 +00:00
property bool pendingSidebarOpen: false
property bool pendingAboutOpen: false
2026-01-18 12:13:07 +00:00
2026-01-24 12:27:30 +00:00
readonly property string hintContext: {
if ( gameEditDialog . visible ) return "edit"
2026-01-29 18:49:45 +00:00
if ( removeGameConfirmDialog . opened ) return "confirm_remove"
{
let layerContent = root . settingsLayerContentItem ( )
if ( settingsLayerOpen && layerContent && layerContent . anyConfirmOpen ) return "confirm"
}
if ( settingsSheet . opened && settingsContent . anyConfirmOpen ) return "confirm"
2026-01-24 12:27:30 +00:00
if ( detailsSheet . opened ) return "details"
if ( importSheet . opened ) return "import"
2026-01-29 18:49:45 +00:00
if ( settingsSheet . opened ) return "settings"
2026-01-24 19:50:20 +00:00
if ( settingsLayerOpen ) return "settings"
2026-01-24 12:27:30 +00:00
if ( aboutSheet . opened ) return "about"
2026-01-29 18:49:45 +00:00
if ( sidebar . opened && ( sidebar . modal || sidebarView . hasSidebarFocus ) ) return "sidebar"
2026-01-24 12:27:30 +00:00
return "library"
}
2026-01-29 18:49:45 +00:00
function isDescendant ( item , ancestor ) {
let p = item
while ( p ) {
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 ( root . isDescendant ( p . visualParent , ancestor ) ) return true
} else if ( p . popup !== undefined && p . popup !== null && p . popup . visualParent !== undefined && p . popup . visualParent !== null ) {
if ( root . isDescendant ( p . popup . visualParent , ancestor ) ) return true
}
p = p . parent
}
return false
}
function currentConfirmDialog ( ) {
if ( removeGameConfirmDialog . opened ) return removeGameConfirmDialog
let layerContent = root . settingsLayerContentItem ( )
if ( settingsLayerOpen && layerContent && layerContent . anyConfirmOpen ) return layerContent . currentConfirmDialog ( )
if ( settingsSheet . opened && settingsContent . anyConfirmOpen ) return settingsContent . currentConfirmDialog ( )
return null
}
function settingsLayerContentItem ( ) {
if ( ! settingsLayerOpen ) return null
if ( ! root . pageStack || ! root . pageStack . layers ) return null
let item = root . pageStack . layers . currentItem
if ( ! item ) return null
if ( item . content !== undefined && item . content !== null ) return item . content
return null
}
function focusNextInDialog ( dialog , forward ) {
let w = applicationWindow ( )
if ( ! w || ! w . activeFocusItem || ! dialog ) return
if ( typeof dialog . standardButton === "function" ) {
let yesButton = dialog . standardButton ( Kirigami . Dialog . Yes )
let noButton = dialog . standardButton ( Kirigami . Dialog . No )
if ( yesButton && noButton ) {
if ( w . activeFocusItem === yesButton ) {
noButton . forceActiveFocus ( )
return
}
if ( w . activeFocusItem === noButton ) {
yesButton . forceActiveFocus ( )
return
}
( forward ? yesButton : noButton ) . forceActiveFocus ( )
return
}
}
let next = w . activeFocusItem
for ( let i = 0 ; i < 50 ; i ++ ) {
next = next . nextItemInFocusChain ( forward )
if ( ! next ) return
if ( root . isDescendant ( next , dialog ) ) {
next . forceActiveFocus ( )
return
}
}
}
function activateFocusedInDialog ( dialog ) {
let w = applicationWindow ( )
if ( ! w || ! w . activeFocusItem || ! dialog ) return
if ( ! root . isDescendant ( w . activeFocusItem , dialog ) ) {
if ( typeof dialog . standardButton === "function" ) {
let noButton = dialog . standardButton ( Kirigami . Dialog . No )
if ( noButton ) {
noButton . forceActiveFocus ( )
return
}
}
dialog . forceActiveFocus ( )
return
}
let item = w . activeFocusItem
if ( typeof item . clicked === "function" ) {
item . clicked ( )
}
}
2026-01-18 12:13:07 +00:00
function closeTopmost ( ) {
2026-01-29 18:49:45 +00:00
if ( libraryView && libraryView . anyMenuOpen ) {
libraryView . closeCurrentMenu ( )
return true
}
2026-01-18 12:13:07 +00:00
if ( gameEditDialog . visible ) {
2026-01-29 18:49:45 +00:00
if ( gameEditDialog . anyMenuOpen ) {
gameEditDialog . closeCurrentMenu ( )
return true
}
2026-01-18 12:13:07 +00:00
gameEditDialog . close ( )
return true
}
2026-01-29 18:49:45 +00:00
if ( removeGameConfirmDialog . opened ) {
removeGameConfirmDialog . close ( )
root . pendingRemoveGame = null
return true
}
let layerContent = root . settingsLayerContentItem ( )
if ( settingsLayerOpen && layerContent && layerContent . anyConfirmOpen ) {
layerContent . closeCurrentConfirmDialog ( )
return true
}
if ( settingsLayerOpen && layerContent && layerContent . anyMenuOpen ) {
layerContent . closeCurrentMenu ( )
return true
}
if ( settingsSheet . opened && settingsContent . anyConfirmOpen ) {
settingsContent . closeCurrentConfirmDialog ( )
return true
}
if ( settingsSheet . opened && settingsContent . anyMenuOpen ) {
settingsContent . closeCurrentMenu ( )
return true
}
if ( settingsSheet . opened ) {
settingsSheet . close ( )
return true
}
2026-01-18 12:13:07 +00:00
if ( detailsSheet . opened ) {
detailsSheet . close ( )
return true
}
if ( importSheet . opened ) {
importSheet . close ( )
return true
}
2026-01-19 23:15:28 +00:00
if ( aboutSheet . opened ) {
aboutSheet . close ( )
return true
}
2026-01-29 18:49:45 +00:00
if ( sidebar . opened && ( sidebar . modal || sidebarView . hasSidebarFocus ) ) {
if ( sidebar . modal ) {
sidebar . close ( )
} else {
Qt . callLater ( function ( ) { libraryView . restoreFocus ( ) } )
}
2026-01-18 12:13:07 +00:00
return true
}
if ( root . pageStack . layers . depth > 1 ) {
root . pageStack . layers . pop ( )
Qt . callLater ( function ( ) { libraryView . restoreFocus ( ) } )
return true
}
if ( searchActive ) {
searchActive = false
2026-01-24 12:27:30 +00:00
libraryView . clearSearch ( )
Qt . callLater ( function ( ) { libraryView . restoreFocus ( ) } )
2026-01-18 12:13:07 +00:00
return true
}
return false
}
function canOpenSettings ( ) {
return ! gameEditDialog . visible
&& ! detailsSheet . opened
&& ! importSheet . opened
2026-01-19 23:15:28 +00:00
&& ! aboutSheet . opened
2026-01-29 18:49:45 +00:00
&& ! settingsSheet . opened
2026-01-24 19:50:20 +00:00
&& ! ( sidebar . modal && sidebar . opened )
2026-01-18 12:13:07 +00:00
&& root . pageStack . layers . depth <= 1
}
2026-01-24 19:50:20 +00:00
function openSettings ( ) {
if ( settingsLayerOpen ) return
if ( ! root . canOpenSettings ( ) ) return
settingsLayerOpen = true
root . pageStack . layers . push ( settingsLayerComponent )
}
function closeSettings ( ) {
if ( ! settingsLayerOpen ) return
root . pageStack . layers . pop ( )
settingsLayerOpen = false
}
2026-01-24 12:27:30 +00:00
function uiModeLabel ( mode ) {
if ( mode === Config . Desktop ) return i18n ( "Desktop" )
2026-01-29 18:49:45 +00:00
if ( mode === Config . Couch ) return i18n ( "Couch" )
2026-01-24 12:27:30 +00:00
return i18n ( "Automatic" )
}
function cycleUiMode ( ) {
if ( App . config . uiMode === Config . Auto ) {
App . config . uiMode = Config . Desktop
} else if ( App . config . uiMode === Config . Desktop ) {
2026-01-29 18:49:45 +00:00
App . config . uiMode = Config . Couch
2026-01-24 12:27:30 +00:00
} else {
App . config . uiMode = Config . Auto
}
root . showPassiveNotification ( i18n ( "UI mode: %1" , uiModeLabel ( App . config . uiMode ) ) )
}
readonly property bool deviceIsMobile: Kirigami . Settings . isMobile
readonly property bool deviceIsTablet: Kirigami . Settings . tabletMode && ! Kirigami . Settings . isMobile
readonly property bool deviceIsTouchDevice: Kirigami . Settings . tabletMode || Kirigami . Settings . isMobile
2026-01-29 18:49:45 +00:00
readonly property int effectiveUiMode: {
if ( App . config . uiMode === Config . Auto ) {
if ( GamepadManager . connected ) {
if ( InputManager . activeInput === InputManager . Gamepad ) {
return Config . Couch
}
if ( ! InputManager . hasSeenKeyboardMouse ) {
return Config . Couch
}
}
return Config . Desktop
}
return App . config . uiMode
}
readonly property bool isCouchMode: effectiveUiMode === Config . Couch
readonly property real couchScale: isCouchMode ? 1.20 : 1.0
readonly property bool isMobile: deviceIsMobile
readonly property bool isTablet: deviceIsTablet
readonly property bool isTouchDevice: deviceIsTouchDevice
2026-01-18 12:13:07 +00:00
readonly property bool isNarrowScreen: width < Kirigami . Units . gridUnit * 30
2026-01-29 18:49:45 +00:00
readonly property int adaptiveCardSize: Math . round ( App . config . gridSize * couchScale )
2026-01-18 12:13:07 +00:00
pageStack.globalToolBar.style: Kirigami . ApplicationHeaderStyle . ToolBar
pageStack.columnView.columnResizeMode: Kirigami . ColumnView . SingleColumn
2026-01-29 18:49:45 +00:00
footer: Item {
implicitWidth: root . width
anchors.left: parent ? parent.left : undefined
anchors.right: parent ? parent.right : undefined
2026-01-24 12:27:30 +00:00
2026-01-29 18:49:45 +00:00
implicitHeight: footerBar . implicitHeight
height: implicitHeight
2026-01-24 12:27:30 +00:00
2026-01-29 18:49:45 +00:00
QQC2 . ToolBar {
id: footerBar
anchors.fill: parent
visible: true
leftPadding: 0
rightPadding: 0
topPadding: Kirigami . Units . smallSpacing
bottomPadding: Kirigami . Units . smallSpacing
contentItem: Item {
id: footerRoot
readonly property int footerMargins: root . isCouchMode ? Kirigami.Units.mediumSpacing : Kirigami . Units . smallSpacing
readonly property int footerMinHeight: Math . round ( Kirigami . Units . gridUnit * ( root . isCouchMode ? 2.2 : 1.6 ) )
2026-01-24 12:27:30 +00:00
2026-01-29 18:49:45 +00:00
implicitHeight: Math . max ( footerLayout . implicitHeight + footerMargins * 2 , footerMinHeight )
2026-01-24 12:27:30 +00:00
Kirigami . ShadowedRectangle {
2026-01-29 18:49:45 +00:00
id: footerBg
anchors.fill: parent
2026-01-24 12:27:30 +00:00
radius: Kirigami . Units . largeSpacing
color: Qt . rgba ( Kirigami . Theme . backgroundColor . r , Kirigami . Theme . backgroundColor . g , Kirigami . Theme . backgroundColor . b , 0.85 )
shadow.size: Kirigami . Units . smallSpacing
shadow.color: Qt . rgba ( 0 , 0 , 0 , 0.25 )
2026-01-29 18:49:45 +00:00
readonly property bool compactFooter: ( hintBar . implicitWidth + rightExtras . implicitWidth + Kirigami . Units . gridUnit * 2 ) > width
2026-01-24 12:27:30 +00:00
RowLayout {
2026-01-29 18:49:45 +00:00
id: footerLayout
anchors.fill: parent
anchors.margins: footerRoot . footerMargins
spacing: root . isCouchMode ? Kirigami.Units.largeSpacing : Kirigami . Units . mediumSpacing
2026-01-24 12:27:30 +00:00
2026-01-29 18:49:45 +00:00
Flickable {
id: hintFlick
clip: true
Layout.fillWidth: true
2026-01-24 12:27:30 +00:00
Layout.alignment: Qt . AlignVCenter
2026-01-29 18:49:45 +00:00
Layout.minimumHeight: Math . max ( hintBar . implicitHeight , footerRoot . footerMinHeight - footerRoot . footerMargins * 2 )
Layout.preferredHeight: Layout . minimumHeight
contentWidth: hintContent . width
contentHeight: hintContent . height
boundsBehavior: Flickable . StopAtBounds
flickableDirection: Flickable . HorizontalFlick
interactive: hintBar . implicitWidth > width
QQC2.ScrollBar.horizontal: QQC2 . ScrollBar { policy: QQC2 . ScrollBar . AlwaysOff }
Item {
id: hintContent
width: Math . max ( hintBar . implicitWidth , hintFlick . width )
height: Math . max ( hintBar . implicitHeight , hintFlick . height )
BottomHintBar {
id: hintBar
context: root . hintContext
uiMode: root . effectiveUiMode
activeInput: InputManager . activeInput
width: implicitWidth
height: implicitHeight
anchors.centerIn: parent
}
}
2026-01-24 12:27:30 +00:00
}
2026-01-29 18:49:45 +00:00
RowLayout {
id: rightExtras
spacing: Kirigami . Units . mediumSpacing
2026-01-24 12:27:30 +00:00
2026-01-29 18:49:45 +00:00
visible: ! footerBg . compactFooter
&& ! root . isCouchMode
2026-01-24 12:27:30 +00:00
2026-01-29 18:49:45 +00:00
QQC2 . Label {
text: i18np ( "%1 game" , "%1 games" , libraryView . gameCount )
color: Kirigami . Theme . disabledTextColor
font.pointSize: Kirigami . Theme . smallFont . pointSize
Layout.alignment: Qt . AlignVCenter
visible: root . hintContext === "library" && libraryView . gameCount > 0
}
2026-01-24 12:27:30 +00:00
2026-01-29 18:49:45 +00:00
QQC2 . Slider {
id: sizeSlider
from: 120
to: 280
stepSize: 20
value: App . config . gridSize
Layout.preferredWidth: Kirigami . Units . gridUnit * 8
Layout.alignment: Qt . AlignVCenter
visible: root . hintContext === "library" && libraryView . gameCount > 0
onMoved: App . config . gridSize = value
QQC2 . ToolTip {
parent: sizeSlider . handle
visible: sizeSlider . pressed
text: Math . round ( sizeSlider . value ) + " px"
}
}
2026-01-24 12:27:30 +00:00
}
}
}
2026-01-29 18:49:45 +00:00
}
2026-01-24 12:27:30 +00:00
}
}
2026-01-18 12:13:07 +00:00
Shortcut {
sequence: "Ctrl+F"
onActivated: {
2026-01-29 18:49:45 +00:00
if ( root . currentConfirmDialog ( ) ) return
if ( detailsSheet . opened || gameEditDialog . visible || importSheet . opened || settingsLayerOpen || settingsSheet . opened || aboutSheet . opened ) return
if ( sidebar . modal && sidebar . opened ) return
2026-01-18 12:13:07 +00:00
searchActive = true
Qt . callLater ( function ( ) { libraryView . focusSearch ( ) } )
}
}
Shortcut {
sequence: "Ctrl+I"
2026-01-29 18:49:45 +00:00
onActivated: {
if ( root . currentConfirmDialog ( ) ) return
importSheet . open ( )
}
2026-01-18 12:13:07 +00:00
}
Shortcut {
sequence: "Ctrl+N"
onActivated: {
2026-01-29 18:49:45 +00:00
if ( root . currentConfirmDialog ( ) ) return
2026-01-18 12:13:07 +00:00
gameEditDialog . game = null
gameEditDialog . open ( )
}
}
Shortcut {
2026-01-29 18:49:45 +00:00
sequence: "Ctrl+,"
onActivated: {
if ( root . currentConfirmDialog ( ) ) return
if ( settingsSheet . opened ) {
settingsSheet . close ( )
return
}
if ( settingsLayerOpen ) {
root . closeSettings ( )
return
}
if ( root . canOpenSettings ( ) ) {
root . openSettings ( )
return
}
if ( detailsSheet . opened ) {
detailsSheet . close ( )
Qt . callLater ( function ( ) { root . openSettings ( ) } )
return
}
if ( sidebar . modal && sidebar . opened ) {
sidebar . close ( )
Qt . callLater ( function ( ) { root . openSettings ( ) } )
return
}
}
2026-01-18 12:13:07 +00:00
}
Shortcut {
sequence: "Escape"
onActivated: root . closeTopmost ( )
}
Shortcut {
sequence: "F5"
2026-01-29 18:49:45 +00:00
onActivated: {
if ( root . currentConfirmDialog ( ) ) return
App . importAllGames ( )
}
2026-01-18 12:13:07 +00:00
}
Shortcut {
sequence: "Ctrl+H"
onActivated: {
2026-01-29 18:49:45 +00:00
if ( root . currentConfirmDialog ( ) ) return
if ( detailsSheet . opened || gameEditDialog . visible || importSheet . opened || settingsLayerOpen || settingsSheet . opened || aboutSheet . opened ) return
if ( root . hintContext !== "library" && root . hintContext !== "sidebar" ) return
let target = ( root . currentSource === "hidden" ) ? "all" : "hidden"
sidebarView . suppressAutoClose = true
sidebarView . applySourceById ( target )
Qt . callLater ( function ( ) { sidebarView . suppressAutoClose = false } )
}
}
Shortcut {
sequence: "Ctrl+PgUp"
onActivated: {
if ( root . currentConfirmDialog ( ) ) return
if ( detailsSheet . opened || gameEditDialog . visible || importSheet . opened || settingsLayerOpen || settingsSheet . opened || aboutSheet . opened ) return
if ( root . hintContext !== "library" && root . hintContext !== "sidebar" ) return
sidebarView . cycleSource ( - 1 )
}
}
Shortcut {
sequence: "Ctrl+PgDown"
onActivated: {
if ( root . currentConfirmDialog ( ) ) return
if ( detailsSheet . opened || gameEditDialog . visible || importSheet . opened || settingsLayerOpen || settingsSheet . opened || aboutSheet . opened ) return
if ( root . hintContext !== "library" && root . hintContext !== "sidebar" ) return
sidebarView . cycleSource ( 1 )
2026-01-18 12:13:07 +00:00
}
}
Shortcut {
sequence: "Ctrl+D"
onActivated: {
2026-01-29 18:49:45 +00:00
if ( root . currentConfirmDialog ( ) ) return
if ( detailsSheet . opened || gameEditDialog . visible || importSheet . opened || settingsLayerOpen || settingsSheet . opened || aboutSheet . opened ) return
if ( sidebar . modal && sidebar . opened ) return
2026-01-18 12:13:07 +00:00
if ( root . selectedGame ) {
detailsSheet . open ( )
}
}
}
2026-01-24 12:27:30 +00:00
Shortcut {
sequence: "F9"
2026-01-29 18:49:45 +00:00
onActivated: {
if ( root . currentConfirmDialog ( ) ) return
root . cycleUiMode ( )
}
2026-01-24 12:27:30 +00:00
}
2026-01-18 12:13:07 +00:00
Connections {
target: GamepadManager
2026-01-29 18:49:45 +00:00
function onSelectPressed ( ) {
let d = root . currentConfirmDialog ( )
if ( ! d ) return
root . activateFocusedInDialog ( d )
}
2026-01-18 12:13:07 +00:00
function onBackPressed ( ) {
root . closeTopmost ( )
}
2026-01-24 12:27:30 +00:00
function onLeftBumperPressed ( ) {
2026-01-29 18:49:45 +00:00
if ( root . currentConfirmDialog ( ) ) return
if ( detailsSheet . opened || gameEditDialog . visible || importSheet . opened || settingsLayerOpen || settingsSheet . opened || aboutSheet . opened ) return
2026-01-24 12:27:30 +00:00
if ( root . hintContext !== "library" && root . hintContext !== "sidebar" ) return
sidebarView . cycleSource ( - 1 )
}
function onRightBumperPressed ( ) {
2026-01-29 18:49:45 +00:00
if ( root . currentConfirmDialog ( ) ) return
if ( detailsSheet . opened || gameEditDialog . visible || importSheet . opened || settingsLayerOpen || settingsSheet . opened || aboutSheet . opened ) return
2026-01-24 12:27:30 +00:00
if ( root . hintContext !== "library" && root . hintContext !== "sidebar" ) return
sidebarView . cycleSource ( 1 )
}
function onDetailsPressed ( ) {
2026-01-29 18:49:45 +00:00
if ( root . currentConfirmDialog ( ) ) return
if ( gameEditDialog . visible || importSheet . opened || settingsLayerOpen || settingsSheet . opened || aboutSheet . opened || ( sidebar . modal && sidebar . opened ) ) return
if ( root . hintContext !== "library" && root . hintContext !== "details" ) return
2026-01-24 12:27:30 +00:00
if ( detailsSheet . opened ) {
if ( root . selectedGame ) {
root . selectedGame . favorite = ! root . selectedGame . favorite
}
return
}
if ( ! libraryView || ! libraryView . visible ) return
libraryView . openDetailsForFocusedGame ( )
}
2026-01-18 12:13:07 +00:00
function onMenuPressed ( ) {
2026-01-29 18:49:45 +00:00
if ( root . isCouchMode ) {
if ( libraryView && libraryView . anyMenuOpen ) {
libraryView . closeCurrentMenu ( )
return
}
if ( removeGameConfirmDialog . opened ) {
root . pendingSidebarOpen = true
removeGameConfirmDialog . close ( )
root . pendingRemoveGame = null
Qt . callLater ( function ( ) { libraryView . restoreFocus ( ) } )
return
}
{
let layerContent = root . settingsLayerContentItem ( )
if ( settingsLayerOpen && layerContent && layerContent . anyConfirmOpen ) {
root . pendingSidebarOpen = true
root . closeSettings ( )
return
}
if ( settingsLayerOpen && layerContent && layerContent . anyMenuOpen ) {
layerContent . closeCurrentMenu ( )
return
}
}
if ( settingsSheet . opened && settingsContent . anyConfirmOpen ) {
root . pendingSidebarOpen = true
settingsSheet . close ( )
return
}
if ( settingsSheet . opened && settingsContent . anyMenuOpen ) {
settingsContent . closeCurrentMenu ( )
return
}
if ( settingsSheet . opened ) {
root . pendingSidebarOpen = true
settingsSheet . close ( )
return
}
if ( settingsLayerOpen ) {
root . pendingSidebarOpen = true
root . closeSettings ( )
return
}
if ( gameEditDialog . visible ) {
root . pendingSidebarOpen = true
gameEditDialog . close ( )
return
}
if ( importSheet . opened ) {
root . pendingSidebarOpen = true
importSheet . close ( )
return
}
if ( aboutSheet . opened ) {
root . pendingSidebarOpen = true
aboutSheet . close ( )
return
}
if ( detailsSheet . opened ) {
root . pendingSidebarOpen = true
detailsSheet . close ( )
return
}
if ( sidebar . modal ) {
if ( sidebar . opened ) {
sidebar . close ( )
} else {
sidebar . open ( )
}
return
}
}
if ( settingsSheet . opened ) {
settingsSheet . close ( )
return
}
2026-01-24 19:50:20 +00:00
if ( ! settingsLayerOpen ) {
2026-01-18 12:13:07 +00:00
if ( root . canOpenSettings ( ) ) {
2026-01-24 19:50:20 +00:00
root . openSettings ( )
2026-01-24 12:27:30 +00:00
} else if ( detailsSheet . opened ) {
detailsSheet . close ( )
2026-01-24 19:50:20 +00:00
Qt . callLater ( function ( ) { root . openSettings ( ) } )
2026-01-18 12:13:07 +00:00
} else {
root . closeTopmost ( )
}
} else {
2026-01-24 19:50:20 +00:00
root . closeSettings ( )
2026-01-18 12:13:07 +00:00
}
}
function onSearchPressed ( ) {
2026-01-29 18:49:45 +00:00
if ( root . currentConfirmDialog ( ) ) return
if ( root . hintContext !== "library" && root . hintContext !== "details" ) return
2026-01-24 12:27:30 +00:00
if ( detailsSheet . opened ) {
detailsSheet . editRequested ( )
return
}
2026-01-29 18:49:45 +00:00
if ( gameEditDialog . visible || importSheet . opened || settingsLayerOpen || settingsSheet . opened || aboutSheet . opened || ( sidebar . modal && sidebar . opened ) ) return
2026-01-18 12:13:07 +00:00
searchActive = true
Qt . callLater ( function ( ) { libraryView . focusSearch ( ) } )
}
2026-01-29 18:49:45 +00:00
function onNavigateUp ( ) {
let d = root . currentConfirmDialog ( )
if ( ! d ) return
root . focusNextInDialog ( d , false )
}
function onNavigateDown ( ) {
let d = root . currentConfirmDialog ( )
if ( ! d ) return
root . focusNextInDialog ( d , true )
}
function onNavigateLeft ( ) {
let d = root . currentConfirmDialog ( )
if ( ! d ) return
root . focusNextInDialog ( d , false )
}
function onNavigateRight ( ) {
let d = root . currentConfirmDialog ( )
if ( ! d ) return
root . focusNextInDialog ( d , true )
}
2026-01-18 12:13:07 +00:00
}
Connections {
target: root . pageStack . layers
function onDepthChanged ( ) {
if ( root . pageStack . layers . depth <= 1 ) {
2026-01-24 19:50:20 +00:00
root . settingsLayerOpen = false
2026-01-18 12:13:07 +00:00
Qt . callLater ( function ( ) { libraryView . restoreFocus ( ) } )
}
}
}
globalDrawer: Kirigami . OverlayDrawer {
id: sidebar
edge: Qt . LeftEdge
2026-01-29 18:49:45 +00:00
modal: root . isMobile || root . isNarrowScreen || root . isCouchMode
2026-01-18 12:13:07 +00:00
handleClosedIcon.source: "application-menu"
handleOpenIcon.source: "go-previous"
2026-01-29 18:49:45 +00:00
handleVisible: root . isMobile || root . isNarrowScreen || root . isCouchMode
2026-01-18 12:13:07 +00:00
width: {
if ( root . isMobile ) return Math . min ( root . width * 0.85 , Kirigami . Units . gridUnit * 20 )
if ( root . isTablet ) return Kirigami . Units . gridUnit * 16
return Kirigami . Units . gridUnit * 14
}
Binding {
target: sidebar
property: "drawerOpen"
value: true
when: ! sidebar . modal
}
onOpened: sidebarView . focusList ( )
2026-01-29 18:49:45 +00:00
onClosed: {
if ( root . pendingAboutOpen ) {
root . pendingAboutOpen = false
Qt . callLater ( function ( ) { aboutSheet . open ( ) } )
return
}
Qt . callLater ( function ( ) { libraryView . restoreFocus ( ) } )
}
2026-01-18 12:13:07 +00:00
leftPadding: 0
rightPadding: 0
topPadding: 0
bottomPadding: 0
contentItem: ColumnLayout {
spacing: 0
Kirigami . AbstractApplicationHeader {
Layout.fillWidth: true
contentItem: Item {
anchors.fill: parent
anchors.margins: Kirigami . Units . largeSpacing
RowLayout {
anchors.fill: parent
spacing: Kirigami . Units . mediumSpacing
IconWithResourceFallback {
primary: "org.kde.alakarte"
secondary: ""
resourceFallback: Qt . resolvedUrl ( "icons/app/org.kde.alakarte.svg" )
mask: false
Layout.preferredWidth: Kirigami . Units . iconSizes . medium
Layout.preferredHeight: Kirigami . Units . iconSizes . medium
}
Kirigami . Heading {
text: i18n ( "A-La-Karte" )
level: 2
Layout.fillWidth: true
}
}
}
}
SidebarView {
id: sidebarView
Layout.fillWidth: true
Layout.fillHeight: true
onSourceSelected: function ( source ) {
root . currentSource = source
if ( sidebar . modal ) {
2026-01-24 12:27:30 +00:00
if ( ! sidebarView . suppressAutoClose ) {
sidebar . close ( )
Qt . callLater ( function ( ) { libraryView . restoreFocus ( ) } )
}
2026-01-18 12:13:07 +00:00
}
}
onSettingsRequested: {
if ( sidebar . modal ) {
sidebar . close ( )
}
2026-01-24 19:50:20 +00:00
root . openSettings ( )
2026-01-18 12:13:07 +00:00
}
onImportRequested: {
if ( sidebar . modal ) {
sidebar . close ( )
}
importSheet . open ( )
}
onAboutRequested: {
if ( sidebar . modal ) {
2026-01-29 18:49:45 +00:00
root . pendingAboutOpen = true
2026-01-18 12:13:07 +00:00
sidebar . close ( )
2026-01-29 18:49:45 +00:00
return
2026-01-18 12:13:07 +00:00
}
2026-01-19 23:15:28 +00:00
aboutSheet . open ( )
2026-01-18 12:13:07 +00:00
}
}
}
}
pageStack.initialPage: Kirigami . Page {
id: mainPage
title: sidebarView . currentSourceName
padding: Kirigami . Units . largeSpacing
actions: [
Kirigami . Action {
icon.name: "search"
text: i18n ( "Search" )
checkable: true
checked: root . searchActive
onTriggered: {
root . searchActive = ! root . searchActive
if ( root . searchActive ) {
Qt . callLater ( function ( ) { libraryView . focusSearch ( ) } )
} else {
2026-01-24 12:27:30 +00:00
libraryView . clearSearch ( )
2026-01-18 12:13:07 +00:00
libraryView . restoreFocus ( )
}
}
} ,
Kirigami . Action {
icon.name: "list-add"
text: i18n ( "Add Game" )
onTriggered: {
gameEditDialog . game = null
gameEditDialog . open ( )
}
} ,
Kirigami . Action {
icon.name: "document-import"
text: i18n ( "Import Games" )
2026-01-24 12:27:30 +00:00
visible: sidebar . modal
2026-01-18 12:13:07 +00:00
onTriggered: importSheet . open ( )
} ,
Kirigami . Action {
icon.name: "configure"
text: i18n ( "Settings" )
2026-01-24 12:27:30 +00:00
visible: sidebar . modal
2026-01-24 19:50:20 +00:00
onTriggered: root . openSettings ( )
2026-01-18 12:13:07 +00:00
}
]
LibraryView {
id: libraryView
anchors.fill: parent
filterSource: root . currentSource
searchActive: root . searchActive
adaptiveCardSize: root . adaptiveCardSize
isTouchDevice: root . isTouchDevice
onGameSelected: function ( game ) {
root . selectedGame = game
detailsSheet . open ( )
}
onGameLaunched: function ( game ) {
App . launcher . launchGame ( game )
}
}
}
GameDetailsSheet {
id: detailsSheet
game: root . selectedGame
onLaunchRequested: {
App . launcher . launchGame ( root . selectedGame )
}
onEditRequested: {
detailsSheet . close ( )
gameEditDialog . game = root . selectedGame
gameEditDialog . open ( )
}
onRemoveRequested: {
2026-01-25 09:03:46 +00:00
if ( ! root . selectedGame ) return
root . pendingRemoveGame = root . selectedGame
removeGameConfirmDialog . open ( )
2026-01-18 12:13:07 +00:00
}
onClosed: {
libraryView . restoreFocus ( )
}
}
2026-01-25 09:03:46 +00:00
Kirigami . PromptDialog {
id: removeGameConfirmDialog
title: i18n ( "Remove Game" )
subtitle: root . pendingRemoveGame
? i18n ( "Are you sure you want to remove '%1'?" , root . pendingRemoveGame . name )
: i18n ( "Are you sure you want to remove this game?" )
standardButtons: Kirigami . Dialog . Yes | Kirigami . Dialog . No
2026-01-29 18:49:45 +00:00
onOpened: {
Qt . callLater ( function ( ) {
if ( typeof removeGameConfirmDialog . standardButton === "function" ) {
let noButton = removeGameConfirmDialog . standardButton ( Kirigami . Dialog . No )
if ( noButton ) {
noButton . forceActiveFocus ( )
return
}
}
removeGameConfirmDialog . forceActiveFocus ( )
} )
}
2026-01-25 09:03:46 +00:00
onAccepted: {
if ( ! root . pendingRemoveGame ) return
let gameId = root . pendingRemoveGame . id
let gameName = root . pendingRemoveGame . name
App . removeGame ( root . pendingRemoveGame )
root . pendingRemoveGame = null
detailsSheet . close ( )
root . selectedGame = null
showPassiveNotification (
i18n ( "%1 removed" , gameName ) ,
"long" ,
i18n ( "Undo" ) ,
function ( ) { App . restoreGame ( gameId ) }
)
}
onRejected: {
root . pendingRemoveGame = null
}
}
2026-01-18 12:13:07 +00:00
Kirigami . OverlaySheet {
id: importSheet
title: i18n ( "Import Games" )
implicitWidth: {
if ( root . isMobile ) return applicationWindow ( ) . width
if ( root . isNarrowScreen ) return applicationWindow ( ) . width - Kirigami . Units . largeSpacing * 2
return Math . min ( applicationWindow ( ) . width - Kirigami . Units . gridUnit * 2 , Kirigami . Units . gridUnit * 32 )
}
implicitHeight: {
if ( root . isMobile ) return applicationWindow ( ) . height
return Math . min ( applicationWindow ( ) . height - Kirigami . Units . gridUnit * 2 , importContent . implicitHeight + Kirigami . Units . gridUnit * 2 )
}
onOpened: steamImportButton . forceActiveFocus ( )
onClosed: libraryView . restoreFocus ( )
function isDescendant ( item , ancestor ) {
let p = item
while ( p ) {
if ( p === ancestor ) return true
2026-01-24 12:27:30 +00:00
if ( ancestor . contentItem && p === ancestor . contentItem ) return true
if ( ancestor . header && p === ancestor . header ) return true
2026-01-29 18:49:45 +00:00
if ( p . visualParent !== undefined && p . visualParent !== null ) {
if ( importSheet . isDescendant ( p . visualParent , ancestor ) ) return true
} else if ( p . popup !== undefined && p . popup !== null && p . popup . visualParent !== undefined && p . popup . visualParent !== null ) {
if ( importSheet . isDescendant ( p . popup . visualParent , ancestor ) ) return true
}
2026-01-18 12:13:07 +00:00
p = p . parent
}
return false
}
function focusNextInChain ( 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 ( importSheet . isDescendant ( next , importContent ) ) {
next . forceActiveFocus ( )
importScroll . ensureItemVisible ( next )
return
}
}
}
function activateFocused ( ) {
let w = applicationWindow ( )
if ( ! w || ! w . activeFocusItem ) return
let item = w . activeFocusItem
if ( typeof item . toggle === "function" ) {
item . toggle ( )
return
}
2026-01-29 18:49:45 +00:00
if ( item . checkable !== undefined && item . checkable && item . checked !== undefined ) {
2026-01-18 12:13:07 +00:00
item . checked = ! item . checked
return
}
if ( typeof item . clicked === "function" ) {
item . clicked ( )
return
}
}
Connections {
target: GamepadManager
function onNavigateUp ( ) {
2026-01-29 18:49:45 +00:00
let app = applicationWindow ( )
if ( app && app . currentConfirmDialog && app . currentConfirmDialog ( ) ) return
2026-01-18 12:13:07 +00:00
if ( ! importSheet . opened || importScroll . activeFocus ) return
2026-01-24 12:27:30 +00:00
let w = applicationWindow ( )
if ( ! w || ! w . activeFocusItem ) return
if ( ! importSheet . isDescendant ( w . activeFocusItem , importSheet ) ) return
2026-01-18 12:13:07 +00:00
importSheet . focusNextInChain ( false )
}
function onNavigateDown ( ) {
2026-01-29 18:49:45 +00:00
let app = applicationWindow ( )
if ( app && app . currentConfirmDialog && app . currentConfirmDialog ( ) ) return
2026-01-18 12:13:07 +00:00
if ( ! importSheet . opened || importScroll . activeFocus ) return
2026-01-24 12:27:30 +00:00
let w = applicationWindow ( )
if ( ! w || ! w . activeFocusItem ) return
if ( ! importSheet . isDescendant ( w . activeFocusItem , importSheet ) ) return
importSheet . focusNextInChain ( true )
}
function onNavigateLeft ( ) {
2026-01-29 18:49:45 +00:00
let app = applicationWindow ( )
if ( app && app . currentConfirmDialog && app . currentConfirmDialog ( ) ) return
2026-01-24 12:27:30 +00:00
if ( ! importSheet . opened || importScroll . activeFocus ) return
let w = applicationWindow ( )
if ( ! w || ! w . activeFocusItem ) return
if ( ! importSheet . isDescendant ( w . activeFocusItem , importSheet ) ) return
importSheet . focusNextInChain ( false )
}
function onNavigateRight ( ) {
2026-01-29 18:49:45 +00:00
let app = applicationWindow ( )
if ( app && app . currentConfirmDialog && app . currentConfirmDialog ( ) ) return
2026-01-24 12:27:30 +00:00
if ( ! importSheet . opened || importScroll . activeFocus ) return
let w = applicationWindow ( )
if ( ! w || ! w . activeFocusItem ) return
if ( ! importSheet . isDescendant ( w . activeFocusItem , importSheet ) ) return
2026-01-18 12:13:07 +00:00
importSheet . focusNextInChain ( true )
}
function onSelectPressed ( ) {
2026-01-29 18:49:45 +00:00
let app = applicationWindow ( )
if ( app && app . currentConfirmDialog && app . currentConfirmDialog ( ) ) return
2026-01-18 12:13:07 +00:00
if ( ! importSheet . opened || importScroll . activeFocus ) return
2026-01-24 12:27:30 +00:00
let w = applicationWindow ( )
if ( ! w || ! w . activeFocusItem ) return
if ( ! importSheet . isDescendant ( w . activeFocusItem , importSheet ) ) return
2026-01-18 12:13:07 +00:00
importSheet . activateFocused ( )
}
}
contentItem: QQC2 . ScrollView {
id: importScroll
clip: true
leftPadding: Kirigami . Units . largeSpacing
rightPadding: Kirigami . Units . largeSpacing
topPadding: Kirigami . Units . largeSpacing
bottomPadding: Kirigami . Units . largeSpacing
QQC2.ScrollBar.horizontal.policy: QQC2 . ScrollBar . AlwaysOff
function ensureItemVisible ( item ) {
if ( ! item || ! importScroll . contentItem || ! importScroll . contentItem . contentItem ) return
let flick = importScroll . contentItem
let content = flick . contentItem
let p = item . mapToItem ( content , 0 , 0 )
let itemTop = p . y
let itemBottom = p . y + item . height
let top = flick . contentY
let bottom = flick . contentY + flick . height
if ( itemTop < top ) {
flick . contentY = Math . max ( 0 , itemTop )
} else if ( itemBottom > bottom ) {
flick . contentY = Math . max ( 0 , itemBottom - flick . height )
}
}
function scrollBy ( delta ) {
if ( ! importScroll . contentItem ) return
let maxY = Math . max ( 0 , importScroll . contentItem . contentHeight - importScroll . contentItem . height )
importScroll . contentItem . contentY = Math . max ( 0 , Math . min ( maxY , importScroll . contentItem . contentY + delta ) )
}
Connections {
target: GamepadManager
2026-01-29 18:49:45 +00:00
function onNavigateUp ( ) {
let app = applicationWindow ( )
if ( app && app . currentConfirmDialog && app . currentConfirmDialog ( ) ) return
if ( importScroll . activeFocus ) importScroll . scrollBy ( - Kirigami . Units . gridUnit * 2 )
}
function onNavigateDown ( ) {
let app = applicationWindow ( )
if ( app && app . currentConfirmDialog && app . currentConfirmDialog ( ) ) return
if ( importScroll . activeFocus ) importScroll . scrollBy ( Kirigami . Units . gridUnit * 2 )
}
2026-01-18 12:13:07 +00:00
}
ColumnLayout {
id: importContent
width: importScroll . availableWidth
spacing: Kirigami . Units . mediumSpacing
Kirigami . InlineMessage {
Layout.fillWidth: true
type: Kirigami . MessageType . Information
text: App . importStatus
visible: App . importing
}
FormCard . FormCard {
Layout.fillWidth: true
FormCard . FormButtonDelegate {
id: steamImportButton
text: i18n ( "Steam" )
description: i18n ( "Import from Steam library" )
icon.name: ""
leading: IconWithResourceFallback {
primary: "com.valvesoftware.Steam"
secondary: "steam"
resourceFallback: Qt . resolvedUrl ( "icons/brand/steam-symbolic.svg" )
}
2026-01-24 12:27:30 +00:00
enabled: ! App . importing && App . config . importSteam && ! App . gameModel . hasPlatformPrefix ( "Steam" ) && App . gameModel . count >= 0
2026-01-18 12:13:07 +00:00
onClicked: App . importFromSteam ( )
}
FormCard . FormDelegateSeparator { }
FormCard . FormButtonDelegate {
text: i18n ( "Lutris" )
description: i18n ( "Import from Lutris" )
icon.name: ""
leading: IconWithResourceFallback {
primary: "lutris"
secondary: "applications-games"
}
2026-01-24 12:27:30 +00:00
enabled: ! App . importing && App . config . importLutris && ! App . gameModel . hasPlatformPrefix ( "Lutris" ) && App . gameModel . count >= 0
2026-01-18 12:13:07 +00:00
onClicked: App . importFromLutris ( )
}
FormCard . FormDelegateSeparator { }
FormCard . FormButtonDelegate {
text: i18n ( "Heroic" )
description: i18n ( "Epic, GOG, Amazon games" )
icon.name: ""
leading: IconWithResourceFallback {
primary: "com.heroicgameslauncher.hgl"
secondary: "applications-games"
}
2026-01-24 12:27:30 +00:00
enabled: ! App . importing && App . config . importHeroic && ! App . gameModel . hasPlatformPrefix ( "Heroic" ) && App . gameModel . count >= 0
2026-01-18 12:13:07 +00:00
onClicked: App . importFromHeroic ( )
}
FormCard . FormDelegateSeparator { }
FormCard . FormButtonDelegate {
text: i18n ( "Desktop Entries" )
description: i18n ( "Games from system .desktop files" )
icon.name: ""
leading: IconWithResourceFallback {
primary: "user-desktop"
secondary: "computer"
}
2026-01-24 12:27:30 +00:00
enabled: ! App . importing && App . config . importDesktop && ! App . gameModel . hasPlatformPrefix ( "Desktop" ) && App . gameModel . count >= 0
2026-01-18 12:13:07 +00:00
onClicked: App . importFromDesktop ( )
}
FormCard . FormDelegateSeparator { }
FormCard . FormButtonDelegate {
text: i18n ( "Bottles" )
description: i18n ( "Wine applications from Bottles" )
icon.name: ""
leading: IconWithResourceFallback {
primary: "com.usebottles.bottles"
secondary: "application-x-executable"
}
2026-01-24 12:27:30 +00:00
enabled: ! App . importing && App . config . importBottles && ! App . gameModel . hasPlatformPrefix ( "Bottles" ) && App . gameModel . count >= 0
2026-01-18 12:13:07 +00:00
onClicked: App . importFromBottles ( )
}
FormCard . FormDelegateSeparator { }
FormCard . FormButtonDelegate {
text: i18n ( "Flatpak Games" )
description: i18n ( "Games installed via Flatpak" )
icon.name: ""
leading: IconWithResourceFallback {
primary: "flatpak-discover"
secondary: "applications-games"
}
2026-01-24 12:27:30 +00:00
enabled: ! App . importing && App . config . importFlatpak && ! App . gameModel . hasPlatformPrefix ( "Flatpak" ) && App . gameModel . count >= 0
2026-01-18 12:13:07 +00:00
onClicked: App . importFromFlatpak ( )
}
FormCard . FormDelegateSeparator { }
FormCard . FormButtonDelegate {
text: i18n ( "itch.io" )
description: i18n ( "Games from itch.io app" )
icon.name: ""
leading: IconWithResourceFallback {
primary: "io.itch.itch"
secondary: "itch"
resourceFallback: Qt . resolvedUrl ( "icons/brand/itchdotio-symbolic.svg" )
}
2026-01-24 12:27:30 +00:00
enabled: ! App . importing && App . config . importItch && ! App . gameModel . hasPlatformPrefix ( "itch.io" ) && App . gameModel . count >= 0
2026-01-18 12:13:07 +00:00
onClicked: App . importFromItch ( )
}
FormCard . FormDelegateSeparator { }
FormCard . FormButtonDelegate {
text: i18n ( "Legendary" )
description: i18n ( "Epic Games via Legendary CLI" )
icon.name: ""
leading: IconWithResourceFallback {
primary: "legendary"
secondary: "applications-games"
}
2026-01-24 12:27:30 +00:00
enabled: ! App . importing && App . config . importLegendary && ! App . gameModel . hasPlatformPrefix ( "Legendary" ) && App . gameModel . count >= 0
2026-01-18 12:13:07 +00:00
onClicked: App . importFromLegendary ( )
}
FormCard . FormDelegateSeparator { }
FormCard . FormButtonDelegate {
text: i18n ( "RetroArch" )
description: i18n ( "Games from RetroArch playlists" )
icon.name: ""
leading: IconWithResourceFallback {
primary: "org.libretro.RetroArch"
secondary: "retroarch"
resourceFallback: Qt . resolvedUrl ( "icons/brand/retroarch-symbolic.svg" )
}
2026-01-24 12:27:30 +00:00
enabled: ! App . importing && App . config . importRetroArch && ! App . gameModel . hasPlatformPrefix ( "RetroArch" ) && App . gameModel . count >= 0
2026-01-18 12:13:07 +00:00
onClicked: App . importFromRetroArch ( )
}
}
RowLayout {
Layout.alignment: Qt . AlignRight
spacing: Kirigami . Units . mediumSpacing
QQC2 . Button {
text: i18n ( "Import All" )
icon.name: "document-import"
2026-01-24 12:27:30 +00:00
enabled: ! App . importing && App . gameModel . count >= 0 && ( ( App . config . importSteam && ! App . gameModel . hasPlatformPrefix ( "Steam" ) )
|| ( App . config . importLutris && ! App . gameModel . hasPlatformPrefix ( "Lutris" ) )
|| ( App . config . importHeroic && ! App . gameModel . hasPlatformPrefix ( "Heroic" ) )
|| ( App . config . importDesktop && ! App . gameModel . hasPlatformPrefix ( "Desktop" ) )
|| ( App . config . importBottles && ! App . gameModel . hasPlatformPrefix ( "Bottles" ) )
|| ( App . config . importFlatpak && ! App . gameModel . hasPlatformPrefix ( "Flatpak" ) )
|| ( App . config . importItch && ! App . gameModel . hasPlatformPrefix ( "itch.io" ) )
|| ( App . config . importLegendary && ! App . gameModel . hasPlatformPrefix ( "Legendary" ) )
|| ( App . config . importRetroArch && ! App . gameModel . hasPlatformPrefix ( "RetroArch" ) ) )
2026-01-18 12:13:07 +00:00
onClicked: App . importAllGames ( )
}
QQC2 . Button {
text: i18n ( "Close" )
onClicked: importSheet . close ( )
}
}
}
}
2026-01-24 19:50:20 +00:00
Component {
id: settingsLayerComponent
Kirigami . Page {
id: settingsLayerPage
title: i18n ( "Settings" )
2026-01-29 18:49:45 +00:00
property Item content: settingsLayerContent
2026-01-24 19:50:20 +00:00
actions: [
Kirigami . Action {
icon.name: "go-previous"
text: i18n ( "Close" )
onTriggered: root . closeSettings ( )
}
]
Component.onCompleted: {
Qt . callLater ( function ( ) {
settingsLayerContent . focusFirstControl ( )
} )
}
function isDescendant ( item , ancestor ) {
let p = item
while ( p ) {
if ( p === ancestor ) return true
2026-01-29 18:49:45 +00:00
if ( p . visualParent !== undefined && p . visualParent !== null ) {
if ( settingsLayerPage . isDescendant ( p . visualParent , ancestor ) ) return true
} else if ( p . popup !== undefined && p . popup !== null && p . popup . visualParent !== undefined && p . popup . visualParent !== null ) {
if ( settingsLayerPage . isDescendant ( p . popup . visualParent , ancestor ) ) return true
}
2026-01-24 19:50:20 +00:00
p = p . parent
}
return false
}
function focusNextInChain ( 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 ( settingsLayerPage . isDescendant ( next , settingsLayerPage ) ) {
next . forceActiveFocus ( )
2026-01-29 18:49:45 +00:00
if ( settingsLayerPage . isActualDescendant ( next , settingsLayerContent ) ) {
2026-01-24 19:50:20 +00:00
settingsLayerScroll . ensureItemVisible ( next )
}
return
}
}
}
2026-01-29 18:49:45 +00:00
function isActualDescendant ( item , ancestor ) {
let p = item
while ( p ) {
if ( p === ancestor ) return true
p = p . parent
}
return false
}
2026-01-24 19:50:20 +00:00
function activateFocused ( ) {
let w = applicationWindow ( )
if ( ! w || ! w . activeFocusItem ) return
let item = w . activeFocusItem
if ( typeof item . toggle === "function" ) {
item . toggle ( )
return
}
2026-01-29 18:49:45 +00:00
if ( item . checkable !== undefined && item . checkable && item . checked !== undefined ) {
2026-01-24 19:50:20 +00:00
item . checked = ! item . checked
return
}
if ( typeof item . clicked === "function" ) {
item . clicked ( )
return
}
}
Connections {
target: GamepadManager
function onNavigateUp ( ) {
2026-01-29 18:49:45 +00:00
let app = applicationWindow ( )
if ( app && app . currentConfirmDialog && app . currentConfirmDialog ( ) ) return
if ( settingsLayerContent . anyConfirmOpen ) return
if ( settingsLayerContent . anyMenuOpen ) return
2026-01-24 19:50:20 +00:00
if ( ! root . settingsLayerOpen || settingsLayerScroll . activeFocus ) return
let w = applicationWindow ( )
if ( ! w || ! w . activeFocusItem ) return
if ( ! settingsLayerPage . isDescendant ( w . activeFocusItem , settingsLayerPage ) ) return
settingsLayerPage . focusNextInChain ( false )
}
function onNavigateDown ( ) {
2026-01-29 18:49:45 +00:00
let app = applicationWindow ( )
if ( app && app . currentConfirmDialog && app . currentConfirmDialog ( ) ) return
if ( settingsLayerContent . anyConfirmOpen ) return
if ( settingsLayerContent . anyMenuOpen ) return
if ( ! root . settingsLayerOpen || settingsLayerScroll . activeFocus ) return
let w = applicationWindow ( )
if ( ! w || ! w . activeFocusItem ) return
if ( ! settingsLayerPage . isDescendant ( w . activeFocusItem , settingsLayerPage ) ) return
settingsLayerPage . focusNextInChain ( true )
}
function onNavigateLeft ( ) {
let app = applicationWindow ( )
if ( app && app . currentConfirmDialog && app . currentConfirmDialog ( ) ) return
if ( settingsLayerContent . anyConfirmOpen ) return
if ( settingsLayerContent . anyMenuOpen ) return
if ( ! root . settingsLayerOpen || settingsLayerScroll . activeFocus ) return
let w = applicationWindow ( )
if ( ! w || ! w . activeFocusItem ) return
if ( ! settingsLayerPage . isDescendant ( w . activeFocusItem , settingsLayerPage ) ) return
settingsLayerPage . focusNextInChain ( false )
}
function onNavigateRight ( ) {
let app = applicationWindow ( )
if ( app && app . currentConfirmDialog && app . currentConfirmDialog ( ) ) return
if ( settingsLayerContent . anyConfirmOpen ) return
if ( settingsLayerContent . anyMenuOpen ) return
2026-01-24 19:50:20 +00:00
if ( ! root . settingsLayerOpen || settingsLayerScroll . activeFocus ) return
let w = applicationWindow ( )
if ( ! w || ! w . activeFocusItem ) return
if ( ! settingsLayerPage . isDescendant ( w . activeFocusItem , settingsLayerPage ) ) return
settingsLayerPage . focusNextInChain ( true )
}
function onSelectPressed ( ) {
2026-01-29 18:49:45 +00:00
let app = applicationWindow ( )
if ( app && app . currentConfirmDialog && app . currentConfirmDialog ( ) ) return
if ( settingsLayerContent . anyConfirmOpen ) return
if ( settingsLayerContent . anyMenuOpen ) return
2026-01-24 19:50:20 +00:00
if ( ! root . settingsLayerOpen || settingsLayerScroll . activeFocus ) return
let w = applicationWindow ( )
if ( ! w || ! w . activeFocusItem ) return
if ( ! settingsLayerPage . isDescendant ( w . activeFocusItem , settingsLayerPage ) ) return
settingsLayerPage . activateFocused ( )
}
}
QQC2 . ScrollView {
id: settingsLayerScroll
anchors.fill: parent
clip: true
readonly property int horizontalContentPadding: root . isMobile ? Kirigami.Units.largeSpacing : Kirigami . Units . gridUnit
readonly property int verticalContentPadding: root . isMobile ? Kirigami.Units.largeSpacing : Kirigami . Units . gridUnit
leftPadding: horizontalContentPadding
rightPadding: horizontalContentPadding
topPadding: verticalContentPadding
bottomPadding: verticalContentPadding
QQC2.ScrollBar.horizontal.policy: QQC2 . ScrollBar . AlwaysOff
function ensureItemVisible ( item ) {
if ( ! item || ! settingsLayerScroll . contentItem || ! settingsLayerScroll . contentItem . contentItem ) return
let flick = settingsLayerScroll . contentItem
let content = flick . contentItem
let p = item . mapToItem ( content , 0 , 0 )
let itemTop = p . y
let itemBottom = p . y + item . height
let top = flick . contentY
let bottom = flick . contentY + flick . height
if ( itemTop < top ) {
flick . contentY = Math . max ( 0 , itemTop )
} else if ( itemBottom > bottom ) {
flick . contentY = Math . max ( 0 , itemBottom - flick . height )
}
}
function scrollBy ( delta ) {
if ( ! settingsLayerScroll . contentItem ) return
let maxY = Math . max ( 0 , settingsLayerScroll . contentItem . contentHeight - settingsLayerScroll . contentItem . height )
settingsLayerScroll . contentItem . contentY = Math . max ( 0 , Math . min ( maxY , settingsLayerScroll . contentItem . contentY + delta ) )
}
Connections {
target: GamepadManager
2026-01-29 18:49:45 +00:00
function onNavigateUp ( ) {
let app = applicationWindow ( )
if ( app && app . currentConfirmDialog && app . currentConfirmDialog ( ) ) return
if ( root . settingsLayerOpen && settingsLayerScroll . activeFocus ) settingsLayerScroll . scrollBy ( - Kirigami . Units . gridUnit * 2 )
}
function onNavigateDown ( ) {
let app = applicationWindow ( )
if ( app && app . currentConfirmDialog && app . currentConfirmDialog ( ) ) return
if ( root . settingsLayerOpen && settingsLayerScroll . activeFocus ) settingsLayerScroll . scrollBy ( Kirigami . Units . gridUnit * 2 )
}
2026-01-24 19:50:20 +00:00
}
SettingsPage {
id: settingsLayerContent
width: settingsLayerScroll . availableWidth
}
}
}
}
2026-01-18 12:13:07 +00:00
Kirigami . OverlaySheet {
id: settingsSheet
title: i18n ( "Settings" )
2026-01-24 19:50:20 +00:00
parent: applicationWindow ( ) . overlay
readonly property Item contentArea: applicationWindow ( ) . pageStack
readonly property int sideMargin: root . isMobile ? 0 : Kirigami . Units . gridUnit * 2
readonly property int verticalMargin: root . isMobile ? 0 : Kirigami . Units . gridUnit
readonly property point contentAreaPos: {
if ( ! settingsSheet . contentArea ) return Qt . point ( 0 , 0 )
return settingsSheet . contentArea . mapToItem ( applicationWindow ( ) . overlay , 0 , 0 )
}
readonly property int usableLeft: settingsSheet . contentAreaPos . x
readonly property int usableTop: settingsSheet . contentAreaPos . y
readonly property int usableWidth: settingsSheet . contentArea ? settingsSheet.contentArea.width : applicationWindow ( ) . width
readonly property int usableHeight: settingsSheet . contentArea ? settingsSheet.contentArea.height : applicationWindow ( ) . height
readonly property int sheetAvailableWidth: Math . max ( 0 , settingsSheet . usableWidth - settingsSheet . sideMargin * 2 )
readonly property int sheetAvailableHeight: Math . max ( 0 , settingsSheet . usableHeight - settingsSheet . verticalMargin * 2 )
2026-01-19 23:15:28 +00:00
closePolicy: QQC2 . Popup . CloseOnEscape | QQC2 . Popup . CloseOnPressOutside
2026-01-24 19:50:20 +00:00
width: {
if ( root . isMobile ) return settingsSheet . sheetAvailableWidth
if ( root . isNarrowScreen ) return settingsSheet . sheetAvailableWidth
let desired = Math . round ( settingsSheet . sheetAvailableWidth * 0.82 )
return Math . min ( settingsSheet . sheetAvailableWidth , Math . max ( Kirigami . Units . gridUnit * 44 , Math . min ( desired , Kirigami . Units . gridUnit * 96 ) ) )
2026-01-18 12:13:07 +00:00
}
2026-01-24 19:50:20 +00:00
height: {
if ( root . isMobile ) return settingsSheet . sheetAvailableHeight
let contentH = settingsContent . implicitHeight + settingsScroll . topPadding + settingsScroll . bottomPadding + Kirigami . Units . gridUnit * 4
return Math . max ( Math . round ( settingsSheet . sheetAvailableHeight * 0.85 ) , Math . min ( contentH , settingsSheet . sheetAvailableHeight ) )
}
x: {
return settingsSheet . usableLeft + Math . round ( ( settingsSheet . usableWidth - width ) / 2 )
}
y: {
return settingsSheet . usableTop + settingsSheet . verticalMargin
2026-01-18 12:13:07 +00:00
}
onOpened: settingsContent . focusFirstControl ( )
onClosed: libraryView . restoreFocus ( )
2026-01-19 23:15:28 +00:00
header: Kirigami . ShadowedRectangle {
id: settingsHeader
2026-01-24 19:50:20 +00:00
implicitWidth: settingsSheet . width
2026-01-19 23:15:28 +00:00
implicitHeight: settingsHeaderRow . implicitHeight + Kirigami . Units . largeSpacing * 2
radius: Kirigami . Units . mediumSpacing
color: Kirigami . Theme . backgroundColor
shadow {
size: Kirigami . Units . smallSpacing
color: Qt . rgba ( 0 , 0 , 0 , 0.20 )
}
RowLayout {
id: settingsHeaderRow
anchors.fill: parent
anchors.margins: Kirigami . Units . largeSpacing
spacing: Kirigami . Units . mediumSpacing
Kirigami . Heading {
text: settingsSheet . title
level: 2
Layout.fillWidth: true
elide: Text . ElideRight
}
QQC2 . ToolButton {
text: i18n ( "Close" )
icon.name: "dialog-close"
display: QQC2 . AbstractButton . IconOnly
onClicked: settingsSheet . close ( )
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.text: text
}
}
}
2026-01-18 12:13:07 +00:00
function isDescendant ( item , ancestor ) {
let p = item
while ( p ) {
if ( p === ancestor ) return true
2026-01-24 12:27:30 +00:00
if ( ancestor . contentItem && p === ancestor . contentItem ) return true
if ( ancestor . header && p === ancestor . header ) return true
2026-01-29 18:49:45 +00:00
if ( p . visualParent !== undefined && p . visualParent !== null ) {
if ( settingsSheet . isDescendant ( p . visualParent , ancestor ) ) return true
} else if ( p . popup !== undefined && p . popup !== null && p . popup . visualParent !== undefined && p . popup . visualParent !== null ) {
if ( settingsSheet . isDescendant ( p . popup . visualParent , ancestor ) ) return true
}
2026-01-18 12:13:07 +00:00
p = p . parent
}
return false
}
function focusNextInChain ( 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
2026-01-19 23:15:28 +00:00
if ( settingsSheet . isDescendant ( next , settingsSheet ) ) {
2026-01-18 12:13:07 +00:00
next . forceActiveFocus ( )
2026-01-29 18:49:45 +00:00
if ( settingsSheet . isActualDescendant ( next , settingsContent ) ) {
2026-01-19 23:15:28 +00:00
settingsScroll . ensureItemVisible ( next )
}
2026-01-18 12:13:07 +00:00
return
}
}
}
2026-01-29 18:49:45 +00:00
function isActualDescendant ( item , ancestor ) {
let p = item
while ( p ) {
if ( p === ancestor ) return true
p = p . parent
}
return false
}
2026-01-18 12:13:07 +00:00
function activateFocused ( ) {
let w = applicationWindow ( )
if ( ! w || ! w . activeFocusItem ) return
let item = w . activeFocusItem
if ( typeof item . toggle === "function" ) {
item . toggle ( )
return
}
2026-01-29 18:49:45 +00:00
if ( item . checkable !== undefined && item . checkable && item . checked !== undefined ) {
2026-01-18 12:13:07 +00:00
item . checked = ! item . checked
return
}
if ( typeof item . clicked === "function" ) {
item . clicked ( )
return
}
}
Connections {
target: GamepadManager
function onNavigateUp ( ) {
2026-01-29 18:49:45 +00:00
let app = applicationWindow ( )
if ( app && app . currentConfirmDialog && app . currentConfirmDialog ( ) ) return
if ( settingsContent . anyConfirmOpen ) return
if ( settingsContent . anyMenuOpen ) return
2026-01-18 12:13:07 +00:00
if ( ! settingsSheet . opened || settingsScroll . activeFocus ) return
2026-01-29 18:49:45 +00:00
let w = applicationWindow ( )
if ( ! w || ! w . activeFocusItem ) return
if ( ! settingsSheet . isDescendant ( w . activeFocusItem , settingsSheet ) ) return
2026-01-18 12:13:07 +00:00
settingsSheet . focusNextInChain ( false )
}
function onNavigateDown ( ) {
2026-01-29 18:49:45 +00:00
let app = applicationWindow ( )
if ( app && app . currentConfirmDialog && app . currentConfirmDialog ( ) ) return
if ( settingsContent . anyConfirmOpen ) return
if ( settingsContent . anyMenuOpen ) return
if ( ! settingsSheet . opened || settingsScroll . activeFocus ) return
let w = applicationWindow ( )
if ( ! w || ! w . activeFocusItem ) return
if ( ! settingsSheet . isDescendant ( w . activeFocusItem , settingsSheet ) ) return
settingsSheet . focusNextInChain ( true )
}
function onNavigateLeft ( ) {
let app = applicationWindow ( )
if ( app && app . currentConfirmDialog && app . currentConfirmDialog ( ) ) return
if ( settingsContent . anyConfirmOpen ) return
if ( settingsContent . anyMenuOpen ) return
if ( ! settingsSheet . opened || settingsScroll . activeFocus ) return
let w = applicationWindow ( )
if ( ! w || ! w . activeFocusItem ) return
if ( ! settingsSheet . isDescendant ( w . activeFocusItem , settingsSheet ) ) return
settingsSheet . focusNextInChain ( false )
}
function onNavigateRight ( ) {
let app = applicationWindow ( )
if ( app && app . currentConfirmDialog && app . currentConfirmDialog ( ) ) return
if ( settingsContent . anyConfirmOpen ) return
if ( settingsContent . anyMenuOpen ) return
2026-01-18 12:13:07 +00:00
if ( ! settingsSheet . opened || settingsScroll . activeFocus ) return
2026-01-29 18:49:45 +00:00
let w = applicationWindow ( )
if ( ! w || ! w . activeFocusItem ) return
if ( ! settingsSheet . isDescendant ( w . activeFocusItem , settingsSheet ) ) return
2026-01-18 12:13:07 +00:00
settingsSheet . focusNextInChain ( true )
}
function onSelectPressed ( ) {
2026-01-29 18:49:45 +00:00
let app = applicationWindow ( )
if ( app && app . currentConfirmDialog && app . currentConfirmDialog ( ) ) return
if ( settingsContent . anyConfirmOpen ) return
if ( settingsContent . anyMenuOpen ) return
2026-01-18 12:13:07 +00:00
if ( ! settingsSheet . opened || settingsScroll . activeFocus ) return
2026-01-29 18:49:45 +00:00
let w = applicationWindow ( )
if ( ! w || ! w . activeFocusItem ) return
if ( ! settingsSheet . isDescendant ( w . activeFocusItem , settingsSheet ) ) return
2026-01-18 12:13:07 +00:00
settingsSheet . activateFocused ( )
}
}
contentItem: QQC2 . ScrollView {
id: settingsScroll
clip: true
2026-01-24 19:50:20 +00:00
readonly property int horizontalContentPadding: root . isMobile ? Kirigami.Units.largeSpacing : Kirigami . Units . gridUnit
readonly property int verticalContentPadding: root . isMobile ? Kirigami.Units.largeSpacing : Kirigami . Units . gridUnit
leftPadding: horizontalContentPadding
rightPadding: horizontalContentPadding
topPadding: verticalContentPadding
bottomPadding: verticalContentPadding
2026-01-18 12:13:07 +00:00
QQC2.ScrollBar.horizontal.policy: QQC2 . ScrollBar . AlwaysOff
function ensureItemVisible ( item ) {
if ( ! item || ! settingsScroll . contentItem || ! settingsScroll . contentItem . contentItem ) return
let flick = settingsScroll . contentItem
let content = flick . contentItem
let p = item . mapToItem ( content , 0 , 0 )
let itemTop = p . y
let itemBottom = p . y + item . height
let top = flick . contentY
let bottom = flick . contentY + flick . height
if ( itemTop < top ) {
flick . contentY = Math . max ( 0 , itemTop )
} else if ( itemBottom > bottom ) {
flick . contentY = Math . max ( 0 , itemBottom - flick . height )
}
}
function scrollBy ( delta ) {
if ( ! settingsScroll . contentItem ) return
let maxY = Math . max ( 0 , settingsScroll . contentItem . contentHeight - settingsScroll . contentItem . height )
settingsScroll . contentItem . contentY = Math . max ( 0 , Math . min ( maxY , settingsScroll . contentItem . contentY + delta ) )
}
Connections {
target: GamepadManager
2026-01-29 18:49:45 +00:00
function onNavigateUp ( ) {
let app = applicationWindow ( )
if ( app && app . currentConfirmDialog && app . currentConfirmDialog ( ) ) return
if ( settingsScroll . activeFocus ) settingsScroll . scrollBy ( - Kirigami . Units . gridUnit * 2 )
}
function onNavigateDown ( ) {
let app = applicationWindow ( )
if ( app && app . currentConfirmDialog && app . currentConfirmDialog ( ) ) return
if ( settingsScroll . activeFocus ) settingsScroll . scrollBy ( Kirigami . Units . gridUnit * 2 )
}
2026-01-18 12:13:07 +00:00
}
SettingsPage {
id: settingsContent
width: settingsScroll . availableWidth
}
}
}
2026-01-19 23:15:28 +00:00
Kirigami . OverlaySheet {
id: aboutSheet
title: i18n ( "About" )
2026-01-29 18:49:45 +00:00
parent: applicationWindow ( ) . overlay
2026-01-19 23:15:28 +00:00
closePolicy: QQC2 . Popup . CloseOnEscape | QQC2 . Popup . CloseOnPressOutside
implicitWidth: {
if ( root . isMobile ) return applicationWindow ( ) . width
if ( root . isNarrowScreen ) return applicationWindow ( ) . width - Kirigami . Units . largeSpacing * 2
return Math . min ( applicationWindow ( ) . width - Kirigami . Units . gridUnit * 2 , Kirigami . Units . gridUnit * 30 )
}
implicitHeight: {
if ( root . isMobile ) return applicationWindow ( ) . height
return Math . min ( applicationWindow ( ) . height - Kirigami . Units . gridUnit * 2 , Kirigami . Units . gridUnit * 42 )
}
2026-01-24 12:27:30 +00:00
onOpened: aboutCloseButton . forceActiveFocus ( )
2026-01-19 23:15:28 +00:00
onClosed: libraryView . restoreFocus ( )
2026-01-24 12:27:30 +00:00
function isDescendant ( item , ancestor ) {
let p = item
while ( p ) {
if ( p === ancestor ) return true
if ( ancestor . contentItem && p === ancestor . contentItem ) return true
if ( ancestor . header && p === ancestor . header ) return true
2026-01-29 18:49:45 +00:00
if ( p . visualParent !== undefined && p . visualParent !== null ) {
if ( aboutSheet . isDescendant ( p . visualParent , ancestor ) ) return true
} else if ( p . popup !== undefined && p . popup !== null && p . popup . visualParent !== undefined && p . popup . visualParent !== null ) {
if ( aboutSheet . isDescendant ( p . popup . visualParent , ancestor ) ) return true
}
2026-01-24 12:27:30 +00:00
p = p . parent
}
return false
}
function focusNextInChain ( 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 ( aboutSheet . isDescendant ( next , aboutSheet ) ) {
next . forceActiveFocus ( )
return
}
}
}
function activateFocused ( ) {
let w = applicationWindow ( )
if ( ! w || ! w . activeFocusItem ) return
let item = w . activeFocusItem
if ( typeof item . toggle === "function" ) {
item . toggle ( )
return
}
2026-01-29 18:49:45 +00:00
if ( item . checkable !== undefined && item . checkable && item . checked !== undefined ) {
2026-01-24 12:27:30 +00:00
item . checked = ! item . checked
return
}
if ( typeof item . clicked === "function" ) {
item . clicked ( )
return
}
}
Connections {
target: GamepadManager
function onNavigateUp ( ) {
2026-01-29 18:49:45 +00:00
let app = applicationWindow ( )
if ( app && app . currentConfirmDialog && app . currentConfirmDialog ( ) ) return
2026-01-24 12:27:30 +00:00
if ( ! aboutSheet . opened ) return
let w = applicationWindow ( )
if ( ! w || ! w . activeFocusItem ) return
if ( ! aboutSheet . isDescendant ( w . activeFocusItem , aboutSheet ) ) return
aboutSheet . focusNextInChain ( false )
}
function onNavigateDown ( ) {
2026-01-29 18:49:45 +00:00
let app = applicationWindow ( )
if ( app && app . currentConfirmDialog && app . currentConfirmDialog ( ) ) return
2026-01-24 12:27:30 +00:00
if ( ! aboutSheet . opened ) return
let w = applicationWindow ( )
if ( ! w || ! w . activeFocusItem ) return
if ( ! aboutSheet . isDescendant ( w . activeFocusItem , aboutSheet ) ) return
aboutSheet . focusNextInChain ( true )
}
function onNavigateLeft ( ) {
2026-01-29 18:49:45 +00:00
let app = applicationWindow ( )
if ( app && app . currentConfirmDialog && app . currentConfirmDialog ( ) ) return
2026-01-24 12:27:30 +00:00
if ( ! aboutSheet . opened ) return
let w = applicationWindow ( )
if ( ! w || ! w . activeFocusItem ) return
if ( ! aboutSheet . isDescendant ( w . activeFocusItem , aboutSheet ) ) return
aboutSheet . focusNextInChain ( false )
}
function onNavigateRight ( ) {
2026-01-29 18:49:45 +00:00
let app = applicationWindow ( )
if ( app && app . currentConfirmDialog && app . currentConfirmDialog ( ) ) return
2026-01-24 12:27:30 +00:00
if ( ! aboutSheet . opened ) return
let w = applicationWindow ( )
if ( ! w || ! w . activeFocusItem ) return
if ( ! aboutSheet . isDescendant ( w . activeFocusItem , aboutSheet ) ) return
aboutSheet . focusNextInChain ( true )
}
function onSelectPressed ( ) {
2026-01-29 18:49:45 +00:00
let app = applicationWindow ( )
if ( app && app . currentConfirmDialog && app . currentConfirmDialog ( ) ) return
2026-01-24 12:27:30 +00:00
if ( ! aboutSheet . opened ) return
let w = applicationWindow ( )
if ( ! w || ! w . activeFocusItem ) return
if ( ! aboutSheet . isDescendant ( w . activeFocusItem , aboutSheet ) ) return
aboutSheet . activateFocused ( )
}
}
2026-01-19 23:15:28 +00:00
header: Kirigami . ShadowedRectangle {
id: aboutHeader
implicitWidth: aboutSheet . implicitWidth
implicitHeight: aboutHeaderRow . implicitHeight + Kirigami . Units . largeSpacing * 2
radius: Kirigami . Units . mediumSpacing
color: Kirigami . Theme . backgroundColor
shadow {
size: Kirigami . Units . smallSpacing
color: Qt . rgba ( 0 , 0 , 0 , 0.20 )
}
RowLayout {
id: aboutHeaderRow
anchors.fill: parent
anchors.margins: Kirigami . Units . largeSpacing
spacing: Kirigami . Units . mediumSpacing
Kirigami . Heading {
text: aboutSheet . title
level: 2
Layout.fillWidth: true
elide: Text . ElideRight
}
QQC2 . ToolButton {
2026-01-24 12:27:30 +00:00
id: aboutCloseButton
2026-01-19 23:15:28 +00:00
text: i18n ( "Close" )
icon.name: "dialog-close"
display: QQC2 . AbstractButton . IconOnly
onClicked: aboutSheet . close ( )
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.text: text
}
}
}
contentItem: Item {
Loader {
id: aboutPageLoader
anchors.fill: parent
active: aboutSheet . opened
sourceComponent: FormCard . AboutPage {
title: i18n ( "About" )
aboutData: ( {
"displayName" : i18n ( "A-La-Karte" ) ,
"componentName" : "alakarte" ,
"shortDescription" : i18n ( "A unified game launcher for KDE Plasma" ) ,
"homepage" : "" ,
"bugAddress" : "" ,
"version" : Qt . application . version ,
"otherText" : "" ,
"authors" : [ ] ,
"credits" : [ ] ,
"translators" : [ ] ,
"licenses" : [
{
"name" : i18n ( "GNU General Public License v3.0 or later" ) ,
"text" : "This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n\nThe full license text is available at:\nhttps://www.gnu.org/licenses/gpl-3.0.txt" ,
"spdx" : "GPL-3.0-or-later"
}
] ,
"copyrightStatement" : "" ,
"desktopFileName" : "org.kde.alakarte"
} )
}
}
}
}
2026-01-18 12:13:07 +00:00
GameEditDialog {
id: gameEditDialog
parent: root . overlay
onClosed: libraryView . restoreFocus ( )
onGameCreated: function ( game ) {
showPassiveNotification ( i18n ( "Added %1" , game . name ) )
}
onGameUpdated: function ( game ) {
showPassiveNotification ( i18n ( "Updated %1" , game . name ) )
}
}
Connections {
target: App
function onImportCompleted ( count ) {
showPassiveNotification ( i18np ( "Imported %1 game" , "Imported %1 games" , count ) )
}
function onImportError ( error ) {
showPassiveNotification ( i18n ( "Import error: %1" , error ) , "long" )
}
}
Connections {
target: App . launcher
function onGameStarted ( game ) {
showPassiveNotification ( i18n ( "Launching %1..." , game . name ) )
}
function onGameError ( game , error ) {
showPassiveNotification ( i18n ( "Error launching %1: %2" , game . name , error ) , "long" )
}
}
Component.onCompleted: {
if ( App . gameModel . count === 0 ) {
importSheet . open ( )
}
}
}
2026-01-29 18:49:45 +00:00
}