mirror of
https://invent.kde.org/marcoa/a-la-karte.git
synced 2026-03-27 01:03:09 +00:00
2080 lines
82 KiB
QML
2080 lines
82 KiB
QML
// 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 QtQuick.Window
|
|
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 bool windowGeometryRestored: false
|
|
|
|
Timer {
|
|
id: windowGeometrySaveTimer
|
|
interval: 400
|
|
repeat: false
|
|
onTriggered: {
|
|
if (!root.windowGeometryRestored) return
|
|
if (root.isMobile) return
|
|
if (root.visibility === Window.Maximized || root.visibility === Window.FullScreen) return
|
|
App.config.setWindowSize(root.width, root.height)
|
|
}
|
|
}
|
|
|
|
onWidthChanged: {
|
|
if (!root.windowGeometryRestored) return
|
|
if (root.isMobile) return
|
|
windowGeometrySaveTimer.restart()
|
|
}
|
|
onHeightChanged: {
|
|
if (!root.windowGeometryRestored) return
|
|
if (root.isMobile) return
|
|
windowGeometrySaveTimer.restart()
|
|
}
|
|
|
|
onVisibilityChanged: {
|
|
if (!root.windowGeometryRestored) return
|
|
if (root.isMobile) return
|
|
App.config.windowMaximized = root.visibility === Window.Maximized
|
|
}
|
|
|
|
onClosing: function(close) {
|
|
if (root.isMobile) return
|
|
App.config.windowMaximized = root.visibility === Window.Maximized
|
|
if (root.visibility !== Window.Maximized && root.visibility !== Window.FullScreen) {
|
|
App.config.setWindowSize(root.width, root.height)
|
|
}
|
|
}
|
|
|
|
property var selectedGame: null
|
|
property var pendingRemoveGame: null
|
|
property string currentSource: "all"
|
|
property bool searchActive: false
|
|
property bool settingsLayerOpen: false
|
|
property bool pendingSidebarOpen: false
|
|
property bool pendingAboutOpen: false
|
|
|
|
readonly property string hintContext: {
|
|
if (gameEditDialog.visible) return "edit"
|
|
if (removeGameConfirmDialog.opened) return "confirm_remove"
|
|
{
|
|
let layerContent = root.settingsLayerContentItem()
|
|
if (settingsLayerOpen && layerContent && layerContent.anyConfirmOpen) return "confirm"
|
|
}
|
|
if (settingsSheet.opened && settingsContent.anyConfirmOpen) return "confirm"
|
|
if (diagnosticsSheet.opened) return "diagnostics"
|
|
if (detailsSheet.opened) return "details"
|
|
if (importSheet.opened) return "import"
|
|
if (settingsSheet.opened) return "settings"
|
|
if (settingsLayerOpen) return "settings"
|
|
if (aboutSheet.opened) return "about"
|
|
if (sidebar.opened && (sidebar.modal || sidebarView.hasSidebarFocus)) return "sidebar"
|
|
return "library"
|
|
}
|
|
|
|
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
|
|
if (gameEditDialog.visible && gameEditDialog.anyConfirmOpen) return gameEditDialog.currentConfirmDialog()
|
|
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()
|
|
}
|
|
}
|
|
|
|
function closeTopmost() {
|
|
if (libraryView && libraryView.anyMenuOpen) {
|
|
libraryView.closeCurrentMenu()
|
|
return true
|
|
}
|
|
if (gameEditDialog.visible) {
|
|
if (gameEditDialog.anyConfirmOpen) {
|
|
gameEditDialog.closeCurrentConfirmDialog()
|
|
return true
|
|
}
|
|
if (gameEditDialog.anyMenuOpen) {
|
|
gameEditDialog.closeCurrentMenu()
|
|
return true
|
|
}
|
|
gameEditDialog.close()
|
|
return true
|
|
}
|
|
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
|
|
}
|
|
if (diagnosticsSheet.opened) {
|
|
diagnosticsSheet.close()
|
|
return true
|
|
}
|
|
if (detailsSheet.opened) {
|
|
detailsSheet.close()
|
|
return true
|
|
}
|
|
if (importSheet.opened) {
|
|
importSheet.close()
|
|
return true
|
|
}
|
|
if (aboutSheet.opened) {
|
|
aboutSheet.close()
|
|
return true
|
|
}
|
|
if (sidebar.opened && (sidebar.modal || sidebarView.hasSidebarFocus)) {
|
|
if (sidebar.modal) {
|
|
sidebar.close()
|
|
} else {
|
|
Qt.callLater(function() { libraryView.restoreFocus() })
|
|
}
|
|
return true
|
|
}
|
|
if (root.pageStack.layers.depth > 1) {
|
|
root.pageStack.layers.pop()
|
|
Qt.callLater(function() { libraryView.restoreFocus() })
|
|
return true
|
|
}
|
|
if (searchActive) {
|
|
searchActive = false
|
|
libraryView.clearSearch()
|
|
Qt.callLater(function() { libraryView.restoreFocus() })
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
function canOpenSettings() {
|
|
return !gameEditDialog.visible
|
|
&& !detailsSheet.opened
|
|
&& !diagnosticsSheet.opened
|
|
&& !importSheet.opened
|
|
&& !aboutSheet.opened
|
|
&& !settingsSheet.opened
|
|
&& !(sidebar.modal && sidebar.opened)
|
|
&& root.pageStack.layers.depth <= 1
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
function uiModeLabel(mode) {
|
|
if (mode === Config.Desktop) return i18n("Desktop")
|
|
if (mode === Config.Couch) return i18n("Couch")
|
|
return i18n("Automatic")
|
|
}
|
|
|
|
function cycleUiMode() {
|
|
if (App.config.uiMode === Config.Auto) {
|
|
App.config.uiMode = Config.Desktop
|
|
} else if (App.config.uiMode === Config.Desktop) {
|
|
App.config.uiMode = Config.Couch
|
|
} 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
|
|
|
|
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
|
|
readonly property bool isNarrowScreen: width < Kirigami.Units.gridUnit * 30
|
|
|
|
readonly property int adaptiveCardSize: Math.round(App.config.gridSize * couchScale)
|
|
|
|
pageStack.globalToolBar.style: Kirigami.ApplicationHeaderStyle.ToolBar
|
|
pageStack.columnView.columnResizeMode: Kirigami.ColumnView.SingleColumn
|
|
|
|
footer: Item {
|
|
implicitWidth: root.width
|
|
anchors.left: parent ? parent.left : undefined
|
|
anchors.right: parent ? parent.right : undefined
|
|
|
|
implicitHeight: footerBar.implicitHeight
|
|
height: implicitHeight
|
|
|
|
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))
|
|
|
|
implicitHeight: Math.max(footerLayout.implicitHeight + footerMargins * 2, footerMinHeight)
|
|
|
|
Kirigami.ShadowedRectangle {
|
|
id: footerBg
|
|
anchors.fill: parent
|
|
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)
|
|
|
|
readonly property bool compactFooter: (hintBar.implicitWidth + rightExtras.implicitWidth + Kirigami.Units.gridUnit * 2) > width
|
|
|
|
RowLayout {
|
|
id: footerLayout
|
|
anchors.fill: parent
|
|
anchors.margins: footerRoot.footerMargins
|
|
spacing: root.isCouchMode ? Kirigami.Units.largeSpacing : Kirigami.Units.mediumSpacing
|
|
|
|
Flickable {
|
|
id: hintFlick
|
|
clip: true
|
|
Layout.fillWidth: true
|
|
Layout.alignment: Qt.AlignVCenter
|
|
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
|
|
}
|
|
}
|
|
}
|
|
|
|
RowLayout {
|
|
id: rightExtras
|
|
spacing: Kirigami.Units.mediumSpacing
|
|
|
|
visible: !footerBg.compactFooter
|
|
&& !root.isCouchMode
|
|
|
|
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
|
|
}
|
|
|
|
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"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Shortcut {
|
|
sequence: "Ctrl+F"
|
|
onActivated: {
|
|
if (root.currentConfirmDialog()) return
|
|
if (detailsSheet.opened || diagnosticsSheet.opened || gameEditDialog.visible || importSheet.opened || settingsLayerOpen || settingsSheet.opened || aboutSheet.opened) return
|
|
if (sidebar.modal && sidebar.opened) return
|
|
searchActive = true
|
|
Qt.callLater(function() { libraryView.focusSearch() })
|
|
}
|
|
}
|
|
|
|
Shortcut {
|
|
sequence: "Ctrl+I"
|
|
onActivated: {
|
|
if (root.currentConfirmDialog()) return
|
|
importSheet.open()
|
|
}
|
|
}
|
|
|
|
Shortcut {
|
|
sequence: "Ctrl+N"
|
|
onActivated: {
|
|
if (root.currentConfirmDialog()) return
|
|
gameEditDialog.game = null
|
|
gameEditDialog.open()
|
|
}
|
|
}
|
|
|
|
Shortcut {
|
|
sequence: "Ctrl+,"
|
|
onActivated: {
|
|
if (root.currentConfirmDialog()) return
|
|
if (settingsSheet.opened) {
|
|
settingsSheet.close()
|
|
return
|
|
}
|
|
if (settingsLayerOpen) {
|
|
root.closeSettings()
|
|
return
|
|
}
|
|
if (diagnosticsSheet.opened) {
|
|
diagnosticsSheet.close()
|
|
Qt.callLater(function() { root.openSettings() })
|
|
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
|
|
}
|
|
}
|
|
}
|
|
|
|
Shortcut {
|
|
sequence: "Escape"
|
|
onActivated: root.closeTopmost()
|
|
}
|
|
|
|
Shortcut {
|
|
sequence: "F5"
|
|
onActivated: {
|
|
if (root.currentConfirmDialog()) return
|
|
App.importAllGames()
|
|
}
|
|
}
|
|
|
|
Shortcut {
|
|
sequence: "Ctrl+H"
|
|
onActivated: {
|
|
if (root.currentConfirmDialog()) return
|
|
if (detailsSheet.opened || diagnosticsSheet.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 || diagnosticsSheet.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 || diagnosticsSheet.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+D"
|
|
onActivated: {
|
|
if (root.currentConfirmDialog()) return
|
|
if (detailsSheet.opened || diagnosticsSheet.opened || gameEditDialog.visible || importSheet.opened || settingsLayerOpen || settingsSheet.opened || aboutSheet.opened) return
|
|
if (sidebar.modal && sidebar.opened) return
|
|
if (root.selectedGame) {
|
|
detailsSheet.open()
|
|
}
|
|
}
|
|
}
|
|
|
|
Shortcut {
|
|
sequence: "F9"
|
|
onActivated: {
|
|
if (root.currentConfirmDialog()) return
|
|
root.cycleUiMode()
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: GamepadManager
|
|
function onSelectPressed() {
|
|
let d = root.currentConfirmDialog()
|
|
if (!d) return
|
|
root.activateFocusedInDialog(d)
|
|
}
|
|
function onBackPressed() {
|
|
root.closeTopmost()
|
|
}
|
|
function onLeftBumperPressed() {
|
|
if (root.currentConfirmDialog()) return
|
|
if (detailsSheet.opened || diagnosticsSheet.opened || gameEditDialog.visible || importSheet.opened || settingsLayerOpen || settingsSheet.opened || aboutSheet.opened) return
|
|
if (root.hintContext !== "library" && root.hintContext !== "sidebar") return
|
|
sidebarView.cycleSource(-1)
|
|
}
|
|
function onRightBumperPressed() {
|
|
if (root.currentConfirmDialog()) return
|
|
if (detailsSheet.opened || diagnosticsSheet.opened || gameEditDialog.visible || importSheet.opened || settingsLayerOpen || settingsSheet.opened || aboutSheet.opened) return
|
|
if (root.hintContext !== "library" && root.hintContext !== "sidebar") return
|
|
sidebarView.cycleSource(1)
|
|
}
|
|
function onDetailsPressed() {
|
|
if (root.currentConfirmDialog()) return
|
|
if (gameEditDialog.visible || diagnosticsSheet.opened || importSheet.opened || settingsLayerOpen || settingsSheet.opened || aboutSheet.opened || (sidebar.modal && sidebar.opened)) return
|
|
if (root.hintContext !== "library" && root.hintContext !== "details") return
|
|
if (detailsSheet.opened) {
|
|
if (root.selectedGame) {
|
|
root.selectedGame.favorite = !root.selectedGame.favorite
|
|
}
|
|
return
|
|
}
|
|
if (!libraryView || !libraryView.visible) return
|
|
libraryView.openDetailsForFocusedGame()
|
|
}
|
|
function onMenuPressed() {
|
|
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 (diagnosticsSheet.opened) {
|
|
root.pendingSidebarOpen = true
|
|
diagnosticsSheet.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
|
|
}
|
|
|
|
if (!settingsLayerOpen) {
|
|
if (root.canOpenSettings()) {
|
|
root.openSettings()
|
|
} else if (detailsSheet.opened) {
|
|
detailsSheet.close()
|
|
Qt.callLater(function() { root.openSettings() })
|
|
} else {
|
|
root.closeTopmost()
|
|
}
|
|
} else {
|
|
root.closeSettings()
|
|
}
|
|
}
|
|
function onSearchPressed() {
|
|
if (root.currentConfirmDialog()) return
|
|
if (root.hintContext !== "library" && root.hintContext !== "details") return
|
|
if (detailsSheet.opened) {
|
|
detailsSheet.editRequested()
|
|
return
|
|
}
|
|
if (gameEditDialog.visible || diagnosticsSheet.opened || importSheet.opened || settingsLayerOpen || settingsSheet.opened || aboutSheet.opened || (sidebar.modal && sidebar.opened)) return
|
|
searchActive = true
|
|
Qt.callLater(function() { libraryView.focusSearch() })
|
|
}
|
|
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)
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: root.pageStack.layers
|
|
function onDepthChanged() {
|
|
if (root.pageStack.layers.depth <= 1) {
|
|
root.settingsLayerOpen = false
|
|
Qt.callLater(function() { libraryView.restoreFocus() })
|
|
}
|
|
}
|
|
}
|
|
|
|
globalDrawer: Kirigami.OverlayDrawer {
|
|
id: sidebar
|
|
edge: Qt.LeftEdge
|
|
modal: root.isMobile || root.isNarrowScreen || root.isCouchMode
|
|
handleClosedIcon.source: "application-menu"
|
|
handleOpenIcon.source: "go-previous"
|
|
handleVisible: root.isMobile || root.isNarrowScreen || root.isCouchMode
|
|
|
|
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()
|
|
onClosed: {
|
|
if (root.pendingAboutOpen) {
|
|
root.pendingAboutOpen = false
|
|
Qt.callLater(function() { aboutSheet.open() })
|
|
return
|
|
}
|
|
Qt.callLater(function() { libraryView.restoreFocus() })
|
|
}
|
|
|
|
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) {
|
|
if (!sidebarView.suppressAutoClose) {
|
|
sidebar.close()
|
|
Qt.callLater(function() { libraryView.restoreFocus() })
|
|
}
|
|
}
|
|
}
|
|
|
|
onSettingsRequested: {
|
|
if (sidebar.modal) {
|
|
sidebar.close()
|
|
}
|
|
root.openSettings()
|
|
}
|
|
onImportRequested: {
|
|
if (sidebar.modal) {
|
|
sidebar.close()
|
|
}
|
|
importSheet.open()
|
|
}
|
|
onAboutRequested: {
|
|
if (sidebar.modal) {
|
|
root.pendingAboutOpen = true
|
|
sidebar.close()
|
|
return
|
|
}
|
|
aboutSheet.open()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 {
|
|
libraryView.clearSearch()
|
|
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")
|
|
visible: sidebar.modal
|
|
onTriggered: importSheet.open()
|
|
},
|
|
Kirigami.Action {
|
|
icon.name: "configure"
|
|
text: i18n("Settings")
|
|
visible: sidebar.modal
|
|
onTriggered: root.openSettings()
|
|
}
|
|
]
|
|
|
|
ColumnLayout {
|
|
anchors.fill: parent
|
|
spacing: 0
|
|
|
|
Kirigami.InlineMessage {
|
|
id: daemonBanner
|
|
Layout.fillWidth: true
|
|
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
|
type: Kirigami.MessageType.Error
|
|
visible: !App.launcher.daemonAvailable
|
|
text: i18n("Game Center daemon is not running. Game launching is unavailable.")
|
|
actions: [
|
|
Kirigami.Action {
|
|
text: i18n("Retry")
|
|
icon.name: "view-refresh"
|
|
onTriggered: App.launcher.retryDaemonConnection()
|
|
}
|
|
]
|
|
}
|
|
|
|
LibraryView {
|
|
id: libraryView
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
|
|
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()
|
|
}
|
|
|
|
onDiagnosticsRequested: {
|
|
diagnosticsSheet.game = root.selectedGame
|
|
diagnosticsSheet.open()
|
|
}
|
|
|
|
onRemoveRequested: {
|
|
if (!root.selectedGame) return
|
|
root.pendingRemoveGame = root.selectedGame
|
|
removeGameConfirmDialog.open()
|
|
}
|
|
|
|
onClosed: {
|
|
libraryView.restoreFocus()
|
|
}
|
|
}
|
|
|
|
DiagnosticsSheet {
|
|
id: diagnosticsSheet
|
|
game: root.selectedGame
|
|
|
|
onClosed: {
|
|
if (detailsSheet.opened) {
|
|
detailsSheet.forceActiveFocus()
|
|
return
|
|
}
|
|
libraryView.restoreFocus()
|
|
}
|
|
}
|
|
|
|
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
|
|
onOpened: {
|
|
Qt.callLater(function() {
|
|
if (typeof removeGameConfirmDialog.standardButton === "function") {
|
|
let noButton = removeGameConfirmDialog.standardButton(Kirigami.Dialog.No)
|
|
if (noButton) {
|
|
noButton.forceActiveFocus()
|
|
return
|
|
}
|
|
}
|
|
removeGameConfirmDialog.forceActiveFocus()
|
|
})
|
|
}
|
|
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
|
|
}
|
|
}
|
|
|
|
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
|
|
if (ancestor.contentItem && p === ancestor.contentItem) return true
|
|
if (ancestor.header && p === ancestor.header) return true
|
|
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
|
|
}
|
|
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
|
|
}
|
|
if (item.checkable !== undefined && item.checkable && item.checked !== undefined) {
|
|
item.checked = !item.checked
|
|
return
|
|
}
|
|
if (typeof item.clicked === "function") {
|
|
item.clicked()
|
|
return
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: GamepadManager
|
|
function onNavigateUp() {
|
|
let app = applicationWindow()
|
|
if (app && app.currentConfirmDialog && app.currentConfirmDialog()) return
|
|
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 onNavigateDown() {
|
|
let app = applicationWindow()
|
|
if (app && app.currentConfirmDialog && app.currentConfirmDialog()) return
|
|
if (!importSheet.opened || importScroll.activeFocus) return
|
|
let w = applicationWindow()
|
|
if (!w || !w.activeFocusItem) return
|
|
if (!importSheet.isDescendant(w.activeFocusItem, importSheet)) return
|
|
importSheet.focusNextInChain(true)
|
|
}
|
|
function onNavigateLeft() {
|
|
let app = applicationWindow()
|
|
if (app && app.currentConfirmDialog && app.currentConfirmDialog()) return
|
|
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() {
|
|
let app = applicationWindow()
|
|
if (app && app.currentConfirmDialog && app.currentConfirmDialog()) return
|
|
if (!importSheet.opened || importScroll.activeFocus) return
|
|
let w = applicationWindow()
|
|
if (!w || !w.activeFocusItem) return
|
|
if (!importSheet.isDescendant(w.activeFocusItem, importSheet)) return
|
|
importSheet.focusNextInChain(true)
|
|
}
|
|
function onSelectPressed() {
|
|
let app = applicationWindow()
|
|
if (app && app.currentConfirmDialog && app.currentConfirmDialog()) return
|
|
if (!importSheet.opened || importScroll.activeFocus) return
|
|
let w = applicationWindow()
|
|
if (!w || !w.activeFocusItem) return
|
|
if (!importSheet.isDescendant(w.activeFocusItem, importSheet)) return
|
|
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
|
|
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)
|
|
}
|
|
}
|
|
|
|
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")
|
|
}
|
|
enabled: !App.importing && App.config.importSteam && !App.gameModel.hasPlatformPrefix("Steam") && App.gameModel.count >= 0
|
|
onClicked: App.importFromSteam()
|
|
}
|
|
|
|
FormCard.FormDelegateSeparator {}
|
|
|
|
FormCard.FormButtonDelegate {
|
|
text: i18n("Lutris")
|
|
description: i18n("Import from Lutris")
|
|
icon.name: ""
|
|
leading: IconWithResourceFallback {
|
|
primary: "lutris"
|
|
secondary: "applications-games"
|
|
}
|
|
enabled: !App.importing && App.config.importLutris && !App.gameModel.hasPlatformPrefix("Lutris") && App.gameModel.count >= 0
|
|
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"
|
|
}
|
|
enabled: !App.importing && App.config.importHeroic && !App.gameModel.hasPlatformPrefix("Heroic") && App.gameModel.count >= 0
|
|
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"
|
|
}
|
|
enabled: !App.importing && App.config.importDesktop && !App.gameModel.hasPlatformPrefix("Desktop") && App.gameModel.count >= 0
|
|
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"
|
|
}
|
|
enabled: !App.importing && App.config.importBottles && !App.gameModel.hasPlatformPrefix("Bottles") && App.gameModel.count >= 0
|
|
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"
|
|
}
|
|
enabled: !App.importing && App.config.importFlatpak && !App.gameModel.hasPlatformPrefix("Flatpak") && App.gameModel.count >= 0
|
|
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")
|
|
}
|
|
enabled: !App.importing && App.config.importItch && !App.gameModel.hasPlatformPrefix("itch.io") && App.gameModel.count >= 0
|
|
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"
|
|
}
|
|
enabled: !App.importing && App.config.importLegendary && !App.gameModel.hasPlatformPrefix("Legendary") && App.gameModel.count >= 0
|
|
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")
|
|
}
|
|
enabled: !App.importing && App.config.importRetroArch && !App.gameModel.hasPlatformPrefix("RetroArch") && App.gameModel.count >= 0
|
|
onClicked: App.importFromRetroArch()
|
|
}
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.alignment: Qt.AlignRight
|
|
spacing: Kirigami.Units.mediumSpacing
|
|
|
|
QQC2.Button {
|
|
text: i18n("Import All")
|
|
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()
|
|
}
|
|
|
|
QQC2.Button {
|
|
text: i18n("Close")
|
|
onClicked: importSheet.close()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: settingsLayerComponent
|
|
|
|
Kirigami.Page {
|
|
id: settingsLayerPage
|
|
title: i18n("Settings")
|
|
|
|
property Item content: settingsLayerContent
|
|
|
|
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
|
|
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
|
|
}
|
|
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()
|
|
if (settingsLayerPage.isActualDescendant(next, settingsLayerContent)) {
|
|
settingsLayerScroll.ensureItemVisible(next)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
function isActualDescendant(item, ancestor) {
|
|
let p = item
|
|
while (p) {
|
|
if (p === ancestor) return true
|
|
p = p.parent
|
|
}
|
|
return false
|
|
}
|
|
|
|
function activateFocused() {
|
|
let w = applicationWindow()
|
|
if (!w || !w.activeFocusItem) return
|
|
let item = w.activeFocusItem
|
|
if (typeof item.toggle === "function") {
|
|
item.toggle()
|
|
return
|
|
}
|
|
if (item.checkable !== undefined && item.checkable && item.checked !== undefined) {
|
|
item.checked = !item.checked
|
|
return
|
|
}
|
|
if (typeof item.clicked === "function") {
|
|
item.clicked()
|
|
return
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: GamepadManager
|
|
function onNavigateUp() {
|
|
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 onNavigateDown() {
|
|
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
|
|
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() {
|
|
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.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
|
|
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)
|
|
}
|
|
}
|
|
|
|
SettingsPage {
|
|
id: settingsLayerContent
|
|
width: settingsLayerScroll.availableWidth
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Kirigami.OverlaySheet {
|
|
id: settingsSheet
|
|
title: i18n("Settings")
|
|
|
|
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)
|
|
|
|
closePolicy: QQC2.Popup.CloseOnEscape | QQC2.Popup.CloseOnPressOutside
|
|
|
|
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)))
|
|
}
|
|
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
|
|
}
|
|
|
|
onOpened: settingsContent.focusFirstControl()
|
|
onClosed: libraryView.restoreFocus()
|
|
|
|
header: Kirigami.ShadowedRectangle {
|
|
id: settingsHeader
|
|
implicitWidth: settingsSheet.width
|
|
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
|
|
}
|
|
}
|
|
}
|
|
|
|
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 (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
|
|
}
|
|
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 (settingsSheet.isDescendant(next, settingsSheet)) {
|
|
next.forceActiveFocus()
|
|
if (settingsSheet.isActualDescendant(next, settingsContent)) {
|
|
settingsScroll.ensureItemVisible(next)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
function isActualDescendant(item, ancestor) {
|
|
let p = item
|
|
while (p) {
|
|
if (p === ancestor) return true
|
|
p = p.parent
|
|
}
|
|
return false
|
|
}
|
|
|
|
function activateFocused() {
|
|
let w = applicationWindow()
|
|
if (!w || !w.activeFocusItem) return
|
|
let item = w.activeFocusItem
|
|
if (typeof item.toggle === "function") {
|
|
item.toggle()
|
|
return
|
|
}
|
|
if (item.checkable !== undefined && item.checkable && item.checked !== undefined) {
|
|
item.checked = !item.checked
|
|
return
|
|
}
|
|
if (typeof item.clicked === "function") {
|
|
item.clicked()
|
|
return
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: GamepadManager
|
|
function onNavigateUp() {
|
|
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 onNavigateDown() {
|
|
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
|
|
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 onSelectPressed() {
|
|
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.activateFocused()
|
|
}
|
|
}
|
|
|
|
contentItem: QQC2.ScrollView {
|
|
id: settingsScroll
|
|
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 || !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
|
|
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)
|
|
}
|
|
}
|
|
|
|
SettingsPage {
|
|
id: settingsContent
|
|
width: settingsScroll.availableWidth
|
|
}
|
|
}
|
|
}
|
|
|
|
Kirigami.OverlaySheet {
|
|
id: aboutSheet
|
|
title: i18n("About")
|
|
|
|
parent: applicationWindow().overlay
|
|
|
|
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)
|
|
}
|
|
|
|
onOpened: aboutCloseButton.forceActiveFocus()
|
|
onClosed: libraryView.restoreFocus()
|
|
|
|
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 (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
|
|
}
|
|
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
|
|
}
|
|
if (item.checkable !== undefined && item.checkable && item.checked !== undefined) {
|
|
item.checked = !item.checked
|
|
return
|
|
}
|
|
if (typeof item.clicked === "function") {
|
|
item.clicked()
|
|
return
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: GamepadManager
|
|
function onNavigateUp() {
|
|
let app = applicationWindow()
|
|
if (app && app.currentConfirmDialog && app.currentConfirmDialog()) return
|
|
if (!aboutSheet.opened) return
|
|
let w = applicationWindow()
|
|
if (!w || !w.activeFocusItem) return
|
|
if (!aboutSheet.isDescendant(w.activeFocusItem, aboutSheet)) return
|
|
aboutSheet.focusNextInChain(false)
|
|
}
|
|
function onNavigateDown() {
|
|
let app = applicationWindow()
|
|
if (app && app.currentConfirmDialog && app.currentConfirmDialog()) return
|
|
if (!aboutSheet.opened) return
|
|
let w = applicationWindow()
|
|
if (!w || !w.activeFocusItem) return
|
|
if (!aboutSheet.isDescendant(w.activeFocusItem, aboutSheet)) return
|
|
aboutSheet.focusNextInChain(true)
|
|
}
|
|
function onNavigateLeft() {
|
|
let app = applicationWindow()
|
|
if (app && app.currentConfirmDialog && app.currentConfirmDialog()) return
|
|
if (!aboutSheet.opened) return
|
|
let w = applicationWindow()
|
|
if (!w || !w.activeFocusItem) return
|
|
if (!aboutSheet.isDescendant(w.activeFocusItem, aboutSheet)) return
|
|
aboutSheet.focusNextInChain(false)
|
|
}
|
|
function onNavigateRight() {
|
|
let app = applicationWindow()
|
|
if (app && app.currentConfirmDialog && app.currentConfirmDialog()) return
|
|
if (!aboutSheet.opened) return
|
|
let w = applicationWindow()
|
|
if (!w || !w.activeFocusItem) return
|
|
if (!aboutSheet.isDescendant(w.activeFocusItem, aboutSheet)) return
|
|
aboutSheet.focusNextInChain(true)
|
|
}
|
|
function onSelectPressed() {
|
|
let app = applicationWindow()
|
|
if (app && app.currentConfirmDialog && app.currentConfirmDialog()) return
|
|
if (!aboutSheet.opened) return
|
|
let w = applicationWindow()
|
|
if (!w || !w.activeFocusItem) return
|
|
if (!aboutSheet.isDescendant(w.activeFocusItem, aboutSheet)) return
|
|
aboutSheet.activateFocused()
|
|
}
|
|
}
|
|
|
|
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 {
|
|
id: aboutCloseButton
|
|
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"
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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")
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: App.runnerManager
|
|
|
|
function onLastErrorChanged() {
|
|
if (!App.runnerManager) return
|
|
let msg = String(App.runnerManager.lastError || "")
|
|
if (msg.trim() === "") return
|
|
showPassiveNotification(i18n("Runner error: %1", msg), "long")
|
|
}
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
if (!root.isMobile) {
|
|
const savedW = App.config.windowWidth
|
|
const savedH = App.config.windowHeight
|
|
if (savedW > 0) root.width = Math.max(root.minimumWidth, savedW)
|
|
if (savedH > 0) root.height = Math.max(root.minimumHeight, savedH)
|
|
root.windowGeometryRestored = true
|
|
if (App.config.windowMaximized) {
|
|
root.showMaximized()
|
|
}
|
|
} else {
|
|
root.windowGeometryRestored = true
|
|
}
|
|
|
|
if (App.gameModel.count === 0) {
|
|
importSheet.open()
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|