mirror of
https://invent.kde.org/marcoa/a-la-karte.git
synced 2026-02-09 21:13:08 +00:00
372 lines
14 KiB
QML
372 lines
14 KiB
QML
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||
|
|
// SPDX-FileCopyrightText: 2024 A-La-Karte Contributors
|
||
|
|
|
||
|
|
import QtQuick
|
||
|
|
import QtQuick.Controls as QQC2
|
||
|
|
import QtQuick.Layouts
|
||
|
|
import org.kde.kirigami as Kirigami
|
||
|
|
import org.kde.alakarte
|
||
|
|
import "components"
|
||
|
|
|
||
|
|
ColumnLayout {
|
||
|
|
id: sidebarRoot
|
||
|
|
spacing: 0
|
||
|
|
|
||
|
|
property string currentSource: "all"
|
||
|
|
property string currentSourceName: i18n("All Games")
|
||
|
|
|
||
|
|
readonly property int adaptiveFocusRingWidth: 1
|
||
|
|
|
||
|
|
signal sourceSelected(string source)
|
||
|
|
signal hiddenGamesRequested()
|
||
|
|
signal settingsRequested()
|
||
|
|
signal importRequested()
|
||
|
|
signal aboutRequested()
|
||
|
|
|
||
|
|
function focusList() {
|
||
|
|
sourceList.forceActiveFocus()
|
||
|
|
}
|
||
|
|
|
||
|
|
function activateCurrentItem() {
|
||
|
|
if (sourceList.currentItem && sourceList.currentItem.clicked) {
|
||
|
|
sourceList.currentItem.clicked()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function iconInfoForPlatform(platformName) {
|
||
|
|
let p = (platformName || "").toLowerCase()
|
||
|
|
|
||
|
|
// Prefer icon-theme names to stay consistent with the user's Plasma theme.
|
||
|
|
if (p.includes("steam")) return { source: "com.valvesoftware.Steam", fallback: "steam", resourceFallback: Qt.resolvedUrl("icons/brand/steam-symbolic.svg") }
|
||
|
|
if (p.includes("itch")) return { source: "io.itch.itch", fallback: "itch", resourceFallback: Qt.resolvedUrl("icons/brand/itchdotio-symbolic.svg") }
|
||
|
|
if (p.includes("retroarch")) return { source: "org.libretro.RetroArch", fallback: "retroarch", resourceFallback: Qt.resolvedUrl("icons/brand/retroarch-symbolic.svg") }
|
||
|
|
if (p.includes("lutris")) return { source: "lutris", fallback: "applications-games" }
|
||
|
|
if (p.includes("heroic")) return { source: "com.heroicgameslauncher.hgl", fallback: "heroic" }
|
||
|
|
if (p.includes("bottles")) return { source: "com.usebottles.bottles", fallback: "application-x-executable" }
|
||
|
|
if (p.includes("flatpak")) return { source: "flatpak-discover", fallback: "applications-games" }
|
||
|
|
if (p.includes("desktop")) return { source: "user-desktop", fallback: "computer" }
|
||
|
|
if (p.includes("legendary")) return { source: "legendary", fallback: "applications-games" }
|
||
|
|
if (p.includes("manual")) return { source: "applications-other", fallback: "applications-games" }
|
||
|
|
if (p.includes("other")) return { source: "applications-other", fallback: "applications-games" }
|
||
|
|
|
||
|
|
return { source: "applications-games", fallback: "applications-games" }
|
||
|
|
}
|
||
|
|
|
||
|
|
QQC2.ScrollView {
|
||
|
|
Layout.fillWidth: true
|
||
|
|
Layout.fillHeight: true
|
||
|
|
|
||
|
|
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
|
||
|
|
|
||
|
|
ListView {
|
||
|
|
id: sourceList
|
||
|
|
model: sourceModel
|
||
|
|
currentIndex: 0
|
||
|
|
|
||
|
|
leftMargin: Kirigami.Units.largeSpacing
|
||
|
|
rightMargin: Kirigami.Units.largeSpacing
|
||
|
|
topMargin: Kirigami.Units.smallSpacing
|
||
|
|
bottomMargin: Kirigami.Units.smallSpacing
|
||
|
|
spacing: Kirigami.Units.smallSpacing
|
||
|
|
|
||
|
|
keyNavigationEnabled: true
|
||
|
|
activeFocusOnTab: true
|
||
|
|
|
||
|
|
Connections {
|
||
|
|
target: GamepadManager
|
||
|
|
function onNavigateUp() { if (sourceList.activeFocus) sourceList.decrementCurrentIndex() }
|
||
|
|
function onNavigateDown() { if (sourceList.activeFocus) sourceList.incrementCurrentIndex() }
|
||
|
|
function onSelectPressed() { if (sourceList.activeFocus) sidebarRoot.activateCurrentItem() }
|
||
|
|
}
|
||
|
|
|
||
|
|
delegate: QQC2.ItemDelegate {
|
||
|
|
id: sourceDelegate
|
||
|
|
width: ListView.view.width - ListView.view.leftMargin - ListView.view.rightMargin
|
||
|
|
highlighted: ListView.isCurrentItem
|
||
|
|
|
||
|
|
hoverEnabled: true
|
||
|
|
|
||
|
|
background: Rectangle {
|
||
|
|
radius: Kirigami.Units.smallSpacing
|
||
|
|
color: sourceDelegate.highlighted ? Kirigami.Theme.highlightColor : Kirigami.Theme.alternateBackgroundColor
|
||
|
|
opacity: sourceDelegate.highlighted ? 0.18 : (sourceDelegate.hovered ? 0.08 : 0.0)
|
||
|
|
border.width: (sourceList.activeFocus && ListView.isCurrentItem) ? sidebarRoot.adaptiveFocusRingWidth : 0
|
||
|
|
border.color: Kirigami.Theme.highlightColor
|
||
|
|
|
||
|
|
Behavior on opacity {
|
||
|
|
NumberAnimation { duration: Kirigami.Units.shortDuration }
|
||
|
|
}
|
||
|
|
|
||
|
|
Behavior on border.width {
|
||
|
|
NumberAnimation { duration: Kirigami.Units.shortDuration }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
leftPadding: Kirigami.Units.mediumSpacing
|
||
|
|
rightPadding: Kirigami.Units.mediumSpacing
|
||
|
|
topPadding: Kirigami.Units.smallSpacing
|
||
|
|
bottomPadding: Kirigami.Units.smallSpacing
|
||
|
|
|
||
|
|
contentItem: RowLayout {
|
||
|
|
spacing: Kirigami.Units.mediumSpacing
|
||
|
|
|
||
|
|
IconWithResourceFallback {
|
||
|
|
primary: model.icon
|
||
|
|
secondary: model.fallbackIcon ? model.fallbackIcon : "applications-games"
|
||
|
|
resourceFallback: model.resourceFallback ? model.resourceFallback : ""
|
||
|
|
Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
|
||
|
|
Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium
|
||
|
|
}
|
||
|
|
|
||
|
|
QQC2.Label {
|
||
|
|
text: model.name
|
||
|
|
Layout.fillWidth: true
|
||
|
|
elide: Text.ElideRight
|
||
|
|
}
|
||
|
|
|
||
|
|
QQC2.Label {
|
||
|
|
text: model.count
|
||
|
|
visible: model.count > 0
|
||
|
|
color: Kirigami.Theme.disabledTextColor
|
||
|
|
font.pointSize: Kirigami.Theme.smallFont.pointSize
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
onClicked: {
|
||
|
|
sourceList.currentIndex = index
|
||
|
|
sidebarRoot.currentSource = model.sourceId
|
||
|
|
sidebarRoot.currentSourceName = model.name
|
||
|
|
sidebarRoot.sourceSelected(model.sourceId)
|
||
|
|
}
|
||
|
|
|
||
|
|
Keys.onReturnPressed: clicked()
|
||
|
|
Keys.onEnterPressed: clicked()
|
||
|
|
}
|
||
|
|
|
||
|
|
section.property: "section"
|
||
|
|
section.delegate: Kirigami.ListSectionHeader {
|
||
|
|
width: ListView.view.width - ListView.view.leftMargin - ListView.view.rightMargin
|
||
|
|
text: section
|
||
|
|
leftPadding: Kirigami.Units.mediumSpacing
|
||
|
|
rightPadding: Kirigami.Units.mediumSpacing
|
||
|
|
topPadding: Kirigami.Units.smallSpacing
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Kirigami.Separator {
|
||
|
|
Layout.fillWidth: true
|
||
|
|
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||
|
|
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||
|
|
}
|
||
|
|
|
||
|
|
QQC2.ItemDelegate {
|
||
|
|
id: importAction
|
||
|
|
Layout.fillWidth: true
|
||
|
|
leftPadding: Kirigami.Units.largeSpacing
|
||
|
|
rightPadding: Kirigami.Units.largeSpacing
|
||
|
|
hoverEnabled: true
|
||
|
|
activeFocusOnTab: true
|
||
|
|
|
||
|
|
background: Rectangle {
|
||
|
|
radius: Kirigami.Units.smallSpacing
|
||
|
|
color: parent.activeFocus ? Kirigami.Theme.highlightColor : Kirigami.Theme.alternateBackgroundColor
|
||
|
|
opacity: parent.activeFocus ? 0.18 : (parent.hovered ? 0.08 : 0.0)
|
||
|
|
border.width: parent.activeFocus ? sidebarRoot.adaptiveFocusRingWidth : 0
|
||
|
|
border.color: Kirigami.Theme.highlightColor
|
||
|
|
|
||
|
|
Behavior on opacity {
|
||
|
|
NumberAnimation { duration: Kirigami.Units.shortDuration }
|
||
|
|
}
|
||
|
|
|
||
|
|
Behavior on border.width {
|
||
|
|
NumberAnimation { duration: Kirigami.Units.shortDuration }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
icon.name: "document-import"
|
||
|
|
icon.width: Kirigami.Units.iconSizes.smallMedium
|
||
|
|
icon.height: Kirigami.Units.iconSizes.smallMedium
|
||
|
|
text: i18n("Import Games")
|
||
|
|
onClicked: sidebarRoot.importRequested()
|
||
|
|
}
|
||
|
|
|
||
|
|
QQC2.ItemDelegate {
|
||
|
|
id: settingsAction
|
||
|
|
Layout.fillWidth: true
|
||
|
|
leftPadding: Kirigami.Units.largeSpacing
|
||
|
|
rightPadding: Kirigami.Units.largeSpacing
|
||
|
|
hoverEnabled: true
|
||
|
|
activeFocusOnTab: true
|
||
|
|
|
||
|
|
background: Rectangle {
|
||
|
|
radius: Kirigami.Units.smallSpacing
|
||
|
|
color: parent.activeFocus ? Kirigami.Theme.highlightColor : Kirigami.Theme.alternateBackgroundColor
|
||
|
|
opacity: parent.activeFocus ? 0.18 : (parent.hovered ? 0.08 : 0.0)
|
||
|
|
border.width: parent.activeFocus ? sidebarRoot.adaptiveFocusRingWidth : 0
|
||
|
|
border.color: Kirigami.Theme.highlightColor
|
||
|
|
|
||
|
|
Behavior on opacity {
|
||
|
|
NumberAnimation { duration: Kirigami.Units.shortDuration }
|
||
|
|
}
|
||
|
|
|
||
|
|
Behavior on border.width {
|
||
|
|
NumberAnimation { duration: Kirigami.Units.shortDuration }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
icon.name: "configure"
|
||
|
|
icon.width: Kirigami.Units.iconSizes.smallMedium
|
||
|
|
icon.height: Kirigami.Units.iconSizes.smallMedium
|
||
|
|
text: i18n("Settings")
|
||
|
|
onClicked: sidebarRoot.settingsRequested()
|
||
|
|
}
|
||
|
|
|
||
|
|
QQC2.ItemDelegate {
|
||
|
|
id: aboutAction
|
||
|
|
Layout.fillWidth: true
|
||
|
|
leftPadding: Kirigami.Units.largeSpacing
|
||
|
|
rightPadding: Kirigami.Units.largeSpacing
|
||
|
|
hoverEnabled: true
|
||
|
|
activeFocusOnTab: true
|
||
|
|
|
||
|
|
background: Rectangle {
|
||
|
|
radius: Kirigami.Units.smallSpacing
|
||
|
|
color: parent.activeFocus ? Kirigami.Theme.highlightColor : Kirigami.Theme.alternateBackgroundColor
|
||
|
|
opacity: parent.activeFocus ? 0.18 : (parent.hovered ? 0.08 : 0.0)
|
||
|
|
border.width: parent.activeFocus ? sidebarRoot.adaptiveFocusRingWidth : 0
|
||
|
|
border.color: Kirigami.Theme.highlightColor
|
||
|
|
|
||
|
|
Behavior on opacity {
|
||
|
|
NumberAnimation { duration: Kirigami.Units.shortDuration }
|
||
|
|
}
|
||
|
|
|
||
|
|
Behavior on border.width {
|
||
|
|
NumberAnimation { duration: Kirigami.Units.shortDuration }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
icon.name: "help-about"
|
||
|
|
icon.width: Kirigami.Units.iconSizes.smallMedium
|
||
|
|
icon.height: Kirigami.Units.iconSizes.smallMedium
|
||
|
|
text: i18n("About")
|
||
|
|
onClicked: sidebarRoot.aboutRequested()
|
||
|
|
}
|
||
|
|
|
||
|
|
Connections {
|
||
|
|
target: GamepadManager
|
||
|
|
function onNavigateUp() {
|
||
|
|
if (aboutAction.activeFocus) {
|
||
|
|
settingsAction.forceActiveFocus()
|
||
|
|
} else if (settingsAction.activeFocus) {
|
||
|
|
importAction.forceActiveFocus()
|
||
|
|
} else if (importAction.activeFocus) {
|
||
|
|
sourceList.forceActiveFocus()
|
||
|
|
sourceList.currentIndex = Math.max(0, sourceList.count - 1)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function onNavigateDown() {
|
||
|
|
if (importAction.activeFocus) {
|
||
|
|
settingsAction.forceActiveFocus()
|
||
|
|
} else if (settingsAction.activeFocus) {
|
||
|
|
aboutAction.forceActiveFocus()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function onSelectPressed() {
|
||
|
|
if (importAction.activeFocus) {
|
||
|
|
importAction.clicked()
|
||
|
|
} else if (settingsAction.activeFocus) {
|
||
|
|
settingsAction.clicked()
|
||
|
|
} else if (aboutAction.activeFocus) {
|
||
|
|
aboutAction.clicked()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
ListModel {
|
||
|
|
id: sourceModel
|
||
|
|
|
||
|
|
Component.onCompleted: refresh()
|
||
|
|
|
||
|
|
function refresh() {
|
||
|
|
clear()
|
||
|
|
|
||
|
|
let allCount = 0
|
||
|
|
let hiddenCount = 0
|
||
|
|
let favoritesCount = 0
|
||
|
|
let sources = {}
|
||
|
|
|
||
|
|
for (let i = 0; i < App.gameModel.rowCount(); i++) {
|
||
|
|
let game = App.gameModel.gameAt(i)
|
||
|
|
if (game) {
|
||
|
|
if (game.hidden) {
|
||
|
|
hiddenCount++
|
||
|
|
} else {
|
||
|
|
allCount++
|
||
|
|
if (game.favorite) {
|
||
|
|
favoritesCount++
|
||
|
|
}
|
||
|
|
let platform = game.platform
|
||
|
|
if (!sources[platform]) {
|
||
|
|
sources[platform] = 0
|
||
|
|
}
|
||
|
|
sources[platform]++
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
append({
|
||
|
|
name: i18n("All Games"),
|
||
|
|
sourceId: "all",
|
||
|
|
icon: "view-list-icons",
|
||
|
|
count: allCount,
|
||
|
|
section: i18n("Library")
|
||
|
|
})
|
||
|
|
|
||
|
|
append({
|
||
|
|
name: i18n("Favorites"),
|
||
|
|
sourceId: "favorites",
|
||
|
|
icon: "bookmark-new",
|
||
|
|
count: favoritesCount,
|
||
|
|
section: i18n("Library")
|
||
|
|
})
|
||
|
|
|
||
|
|
append({
|
||
|
|
name: i18n("Hidden"),
|
||
|
|
sourceId: "hidden",
|
||
|
|
icon: "view-hidden",
|
||
|
|
count: hiddenCount,
|
||
|
|
section: i18n("Library")
|
||
|
|
})
|
||
|
|
|
||
|
|
for (let platform in sources) {
|
||
|
|
let iconInfo = sidebarRoot.iconInfoForPlatform(platform)
|
||
|
|
|
||
|
|
append({
|
||
|
|
name: platform,
|
||
|
|
sourceId: platform,
|
||
|
|
icon: iconInfo.source,
|
||
|
|
fallbackIcon: iconInfo.fallback,
|
||
|
|
resourceFallback: iconInfo.resourceFallback ? iconInfo.resourceFallback : "",
|
||
|
|
count: sources[platform],
|
||
|
|
section: i18n("Sources")
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Connections {
|
||
|
|
target: App.gameModel
|
||
|
|
function onCountChanged() {
|
||
|
|
sourceModel.refresh()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Connections {
|
||
|
|
target: App
|
||
|
|
function onImportCompleted() {
|
||
|
|
sourceModel.refresh()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|