a-la-karte/src/qml/SettingsPage.qml

610 lines
22 KiB
QML
Raw Normal View History

// 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"
ColumnLayout {
id: settingsPage
spacing: Kirigami.Units.mediumSpacing
function focusFirstControl() {
showPlatformBadgesDelegate.forceActiveFocus()
}
2026-01-25 09:03:46 +00:00
property var pendingDisableImportApply: null
property var pendingDisableImportDelegate: null
property string pendingDisableImportName: ""
function requestDisableImport(delegate, sourceName, applyFn) {
pendingDisableImportDelegate = delegate
pendingDisableImportName = sourceName
pendingDisableImportApply = applyFn
disableImportConfirmDialog.open()
}
FormCard.FormHeader {
Layout.fillWidth: true
title: i18n("Appearance")
}
FormCard.FormCard {
Layout.fillWidth: true
FormCard.FormCheckDelegate {
id: showPlatformBadgesDelegate
text: i18n("Show platform badges")
description: i18n("Display platform icons on game cards")
checked: App.config.showPlatformBadges
onToggled: App.config.showPlatformBadges = checked
}
FormCard.FormDelegateSeparator {}
FormCard.FormButtonDelegate {
id: uiModeDelegate
text: i18n("UI mode")
description: {
if (App.config.uiMode === Config.Desktop) return i18n("Desktop")
if (App.config.uiMode === Config.Handheld) return i18n("Handheld")
return i18n("Automatic")
}
icon.name: "view-fullscreen"
onClicked: uiModeMenu.open()
QQC2.Menu {
id: uiModeMenu
QQC2.MenuItem {
text: i18n("Automatic")
checkable: true
checked: App.config.uiMode === Config.Auto
onTriggered: App.config.uiMode = Config.Auto
}
QQC2.MenuItem {
text: i18n("Desktop")
checkable: true
checked: App.config.uiMode === Config.Desktop
onTriggered: App.config.uiMode = Config.Desktop
}
QQC2.MenuItem {
text: i18n("Handheld")
checkable: true
checked: App.config.uiMode === Config.Handheld
onTriggered: App.config.uiMode = Config.Handheld
}
}
}
}
FormCard.FormHeader {
Layout.topMargin: Kirigami.Units.mediumSpacing
Layout.fillWidth: true
title: i18n("Import Sources")
}
FormCard.FormCard {
Layout.fillWidth: true
FormCard.FormCheckDelegate {
text: i18n("Steam")
icon.name: ""
leading: IconWithResourceFallback {
primary: "com.valvesoftware.Steam"
secondary: "steam"
resourceFallback: Qt.resolvedUrl("icons/brand/steam-symbolic.svg")
}
2026-01-25 09:03:46 +00:00
property bool restoring: false
checked: App.config.importSteam
2026-01-25 09:03:46 +00:00
onToggled: {
if (restoring) return
if (checked === App.config.importSteam) return
if (!checked && App.gameModel.hasPlatformPrefix("Steam")) {
restoring = true
checked = Qt.binding(function() { return App.config.importSteam })
restoring = false
settingsPage.requestDisableImport(this, i18n("Steam"), function() { App.config.importSteam = false })
return
}
App.config.importSteam = checked
restoring = true
checked = Qt.binding(function() { return App.config.importSteam })
restoring = false
}
}
FormCard.FormDelegateSeparator {}
FormCard.FormCheckDelegate {
text: i18n("Lutris")
icon.name: ""
leading: IconWithResourceFallback {
primary: "lutris"
secondary: "applications-games"
}
2026-01-25 09:03:46 +00:00
property bool restoring: false
checked: App.config.importLutris
2026-01-25 09:03:46 +00:00
onToggled: {
if (restoring) return
if (checked === App.config.importLutris) return
if (!checked && App.gameModel.hasPlatformPrefix("Lutris")) {
restoring = true
checked = Qt.binding(function() { return App.config.importLutris })
restoring = false
settingsPage.requestDisableImport(this, i18n("Lutris"), function() { App.config.importLutris = false })
return
}
App.config.importLutris = checked
restoring = true
checked = Qt.binding(function() { return App.config.importLutris })
restoring = false
}
}
FormCard.FormDelegateSeparator {}
FormCard.FormCheckDelegate {
text: i18n("Heroic Games Launcher")
description: i18n("Epic, GOG, and Amazon games")
icon.name: ""
leading: IconWithResourceFallback {
primary: "com.heroicgameslauncher.hgl"
secondary: "applications-games"
}
2026-01-25 09:03:46 +00:00
property bool restoring: false
checked: App.config.importHeroic
2026-01-25 09:03:46 +00:00
onToggled: {
if (restoring) return
if (checked === App.config.importHeroic) return
if (!checked && App.gameModel.hasPlatformPrefix("Heroic")) {
restoring = true
checked = Qt.binding(function() { return App.config.importHeroic })
restoring = false
settingsPage.requestDisableImport(this, i18n("Heroic Games Launcher"), function() { App.config.importHeroic = false })
return
}
App.config.importHeroic = checked
restoring = true
checked = Qt.binding(function() { return App.config.importHeroic })
restoring = false
}
}
FormCard.FormDelegateSeparator {}
FormCard.FormCheckDelegate {
text: i18n("Desktop Entries")
description: i18n("Games from .desktop files")
icon.name: ""
leading: IconWithResourceFallback {
primary: "user-desktop"
secondary: "computer"
}
2026-01-25 09:03:46 +00:00
property bool restoring: false
checked: App.config.importDesktop
2026-01-25 09:03:46 +00:00
onToggled: {
if (restoring) return
if (checked === App.config.importDesktop) return
if (!checked && App.gameModel.hasPlatformPrefix("Desktop")) {
restoring = true
checked = Qt.binding(function() { return App.config.importDesktop })
restoring = false
settingsPage.requestDisableImport(this, i18n("Desktop Entries"), function() { App.config.importDesktop = false })
return
}
App.config.importDesktop = checked
restoring = true
checked = Qt.binding(function() { return App.config.importDesktop })
restoring = false
}
}
FormCard.FormDelegateSeparator {}
FormCard.FormCheckDelegate {
text: i18n("Bottles")
description: i18n("Wine applications")
icon.name: ""
leading: IconWithResourceFallback {
primary: "com.usebottles.bottles"
secondary: "application-x-executable"
}
2026-01-25 09:03:46 +00:00
property bool restoring: false
checked: App.config.importBottles
2026-01-25 09:03:46 +00:00
onToggled: {
if (restoring) return
if (checked === App.config.importBottles) return
if (!checked && App.gameModel.hasPlatformPrefix("Bottles")) {
restoring = true
checked = Qt.binding(function() { return App.config.importBottles })
restoring = false
settingsPage.requestDisableImport(this, i18n("Bottles"), function() { App.config.importBottles = false })
return
}
App.config.importBottles = checked
restoring = true
checked = Qt.binding(function() { return App.config.importBottles })
restoring = false
}
}
FormCard.FormDelegateSeparator {}
FormCard.FormCheckDelegate {
text: i18n("Flatpak")
description: i18n("Flatpak game applications")
icon.name: ""
leading: IconWithResourceFallback {
primary: "flatpak-discover"
secondary: "applications-games"
}
2026-01-25 09:03:46 +00:00
property bool restoring: false
checked: App.config.importFlatpak
2026-01-25 09:03:46 +00:00
onToggled: {
if (restoring) return
if (checked === App.config.importFlatpak) return
if (!checked && App.gameModel.hasPlatformPrefix("Flatpak")) {
restoring = true
checked = Qt.binding(function() { return App.config.importFlatpak })
restoring = false
settingsPage.requestDisableImport(this, i18n("Flatpak"), function() { App.config.importFlatpak = false })
return
}
App.config.importFlatpak = checked
restoring = true
checked = Qt.binding(function() { return App.config.importFlatpak })
restoring = false
}
}
FormCard.FormDelegateSeparator {}
FormCard.FormCheckDelegate {
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-25 09:03:46 +00:00
property bool restoring: false
checked: App.config.importItch
2026-01-25 09:03:46 +00:00
onToggled: {
if (restoring) return
if (checked === App.config.importItch) return
if (!checked && App.gameModel.hasPlatformPrefix("itch.io")) {
restoring = true
checked = Qt.binding(function() { return App.config.importItch })
restoring = false
settingsPage.requestDisableImport(this, i18n("itch.io"), function() { App.config.importItch = false })
return
}
App.config.importItch = checked
restoring = true
checked = Qt.binding(function() { return App.config.importItch })
restoring = false
}
}
FormCard.FormDelegateSeparator {}
FormCard.FormCheckDelegate {
text: i18n("Legendary")
description: i18n("Epic Games via Legendary CLI")
icon.name: ""
leading: IconWithResourceFallback {
primary: "legendary"
secondary: "applications-games"
}
2026-01-25 09:03:46 +00:00
property bool restoring: false
checked: App.config.importLegendary
2026-01-25 09:03:46 +00:00
onToggled: {
if (restoring) return
if (checked === App.config.importLegendary) return
if (!checked && App.gameModel.hasPlatformPrefix("Legendary")) {
restoring = true
checked = Qt.binding(function() { return App.config.importLegendary })
restoring = false
settingsPage.requestDisableImport(this, i18n("Legendary"), function() { App.config.importLegendary = false })
return
}
App.config.importLegendary = checked
restoring = true
checked = Qt.binding(function() { return App.config.importLegendary })
restoring = false
}
}
FormCard.FormDelegateSeparator {}
FormCard.FormCheckDelegate {
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-25 09:03:46 +00:00
property bool restoring: false
checked: App.config.importRetroArch
2026-01-25 09:03:46 +00:00
onToggled: {
if (restoring) return
if (checked === App.config.importRetroArch) return
if (!checked && App.gameModel.hasPlatformPrefix("RetroArch")) {
restoring = true
checked = Qt.binding(function() { return App.config.importRetroArch })
restoring = false
settingsPage.requestDisableImport(this, i18n("RetroArch"), function() { App.config.importRetroArch = false })
return
}
App.config.importRetroArch = checked
restoring = true
checked = Qt.binding(function() { return App.config.importRetroArch })
restoring = false
}
}
FormCard.FormDelegateSeparator {}
FormCard.FormCheckDelegate {
text: i18n("Auto-import on startup")
description: i18n("Scan for new games when launching")
checked: App.config.autoImportOnStartup
onToggled: App.config.autoImportOnStartup = checked
}
}
FormCard.FormHeader {
Layout.topMargin: Kirigami.Units.mediumSpacing
Layout.fillWidth: true
title: i18n("Behavior")
}
FormCard.FormCard {
Layout.fillWidth: true
FormCard.FormCheckDelegate {
text: i18n("Exit after launching game")
description: i18n("Close A-La-Karte when a game starts")
checked: App.config.exitAfterLaunch
onToggled: App.config.exitAfterLaunch = checked
}
FormCard.FormDelegateSeparator {}
FormCard.FormCheckDelegate {
text: i18n("Click cover to launch")
description: i18n("Clicking the cover launches the game instead of showing details")
checked: App.config.coverLaunchesGame
onToggled: App.config.coverLaunchesGame = checked
}
}
FormCard.FormHeader {
Layout.topMargin: Kirigami.Units.mediumSpacing
Layout.fillWidth: true
title: i18n("Cover Art")
}
FormCard.FormCard {
Layout.fillWidth: true
FormCard.FormCheckDelegate {
text: i18n("High quality images")
description: i18n("Sharper covers at the cost of memory")
checked: App.config.highQualityImages
onToggled: App.config.highQualityImages = checked
}
FormCard.FormDelegateSeparator {}
FormCard.FormCheckDelegate {
text: i18n("Animated covers")
description: i18n("Play animated cover art when available")
checked: App.config.animatedCovers
onToggled: App.config.animatedCovers = checked
}
}
FormCard.FormHeader {
Layout.topMargin: Kirigami.Units.mediumSpacing
Layout.fillWidth: true
title: i18n("SteamGridDB")
}
FormCard.FormCard {
Layout.fillWidth: true
FormCard.FormCheckDelegate {
text: i18n("Enable SteamGridDB")
description: i18n("Automatically download cover art")
checked: App.steamGridDB.enabled
onToggled: App.steamGridDB.enabled = checked
}
FormCard.FormDelegateSeparator {}
FormCard.FormTextFieldDelegate {
label: i18n("SteamGridDB API Key")
text: App.steamGridDB.apiKey
placeholderText: i18n("Enter your API key")
echoMode: TextInput.Password
enabled: App.steamGridDB.enabled
onTextChanged: App.steamGridDB.apiKey = text
}
FormCard.FormDelegateSeparator {}
FormCard.FormCheckDelegate {
text: i18n("Prefer SteamGridDB covers")
description: i18n("Replace existing covers with SteamGridDB art")
checked: App.steamGridDB.preferSteamGridDB
enabled: App.steamGridDB.enabled
onToggled: App.steamGridDB.preferSteamGridDB = checked
}
FormCard.FormDelegateSeparator {}
FormCard.FormButtonDelegate {
text: i18n("Fetch All Covers")
description: App.steamGridDB.busy ? i18n("Fetching...") : i18n("Download covers for all games")
icon.name: "download"
enabled: App.steamGridDB.enabled && App.steamGridDB.apiKey.length > 0 && !App.steamGridDB.busy
2026-01-25 09:03:46 +00:00
onClicked: fetchAllCoversConfirmDialog.open()
}
FormCard.FormDelegateSeparator {}
FormCard.FormTextDelegate {
text: i18n("Get a free API key at steamgriddb.com")
textItem.font: Kirigami.Theme.smallFont
textItem.color: Kirigami.Theme.linkColor
TapHandler {
onTapped: Qt.openUrlExternally("https://www.steamgriddb.com/profile/preferences/api")
}
}
}
FormCard.FormHeader {
Layout.topMargin: Kirigami.Units.mediumSpacing
Layout.fillWidth: true
title: i18n("Library")
}
FormCard.FormCard {
Layout.fillWidth: true
FormCard.FormButtonDelegate {
text: i18n("Import All Games")
description: i18n("Scan all enabled sources")
icon.name: "document-import"
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")))
onClicked: App.importAllGames()
}
FormCard.FormDelegateSeparator {}
FormCard.FormButtonDelegate {
text: i18n("Remove Missing Games")
description: i18n("Remove games whose executables no longer exist")
icon.name: "edit-delete"
2026-01-25 09:03:46 +00:00
onClicked: removeMissingConfirmDialog.open()
}
FormCard.FormDelegateSeparator {}
FormCard.FormButtonDelegate {
text: i18n("Clear Library")
description: i18n("Remove all games")
icon.name: "edit-clear-all"
onClicked: clearConfirmDialog.open()
}
FormCard.FormDelegateSeparator {}
FormCard.FormButtonDelegate {
text: i18n("Reset Application")
description: i18n("Clear library and reset all settings to defaults")
icon.name: "edit-reset"
onClicked: resetConfirmDialog.open()
}
}
2026-01-25 09:03:46 +00:00
Kirigami.PromptDialog {
id: disableImportConfirmDialog
title: i18n("Disable Import Source")
subtitle: i18n("Disabling %1 will remove all games imported from that source. Are you sure?", settingsPage.pendingDisableImportName)
standardButtons: Kirigami.Dialog.Yes | Kirigami.Dialog.No
onAccepted: {
if (settingsPage.pendingDisableImportApply) {
settingsPage.pendingDisableImportApply()
}
settingsPage.pendingDisableImportApply = null
settingsPage.pendingDisableImportDelegate = null
settingsPage.pendingDisableImportName = ""
}
onRejected: {
settingsPage.pendingDisableImportApply = null
settingsPage.pendingDisableImportDelegate = null
settingsPage.pendingDisableImportName = ""
}
}
Kirigami.PromptDialog {
id: fetchAllCoversConfirmDialog
title: i18n("Fetch All Covers")
subtitle: App.steamGridDB.preferSteamGridDB
? 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?")
standardButtons: Kirigami.Dialog.Yes | Kirigami.Dialog.No
onAccepted: App.steamGridDB.fetchAllCovers()
}
Kirigami.PromptDialog {
id: removeMissingConfirmDialog
title: i18n("Remove Missing Games")
subtitle: i18n("This will remove games whose executables cannot be found. This cannot be undone. Continue?")
standardButtons: Kirigami.Dialog.Yes | Kirigami.Dialog.No
onAccepted: App.removeMissingGames()
}
Kirigami.PromptDialog {
id: clearConfirmDialog
title: i18n("Clear Library")
subtitle: i18n("Are you sure you want to remove all games?")
standardButtons: Kirigami.Dialog.Yes | Kirigami.Dialog.No
onAccepted: App.clearLibrary()
}
Kirigami.PromptDialog {
id: resetConfirmDialog
title: i18n("Reset Application")
subtitle: i18n("This will remove all games and reset all settings to defaults. Are you sure?")
standardButtons: Kirigami.Dialog.Yes | Kirigami.Dialog.No
onAccepted: {
App.clearLibrary()
App.config.resetToDefaults()
}
}
}