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

372 lines
14 KiB
QML
Raw Normal View History

// 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()
}
}
}