diff --git a/src/qml/ConsoleCategoryRail.qml b/src/qml/ConsoleCategoryRail.qml index bc125bf..cc632c5 100644 --- a/src/qml/ConsoleCategoryRail.qml +++ b/src/qml/ConsoleCategoryRail.qml @@ -4,73 +4,96 @@ import QtQuick import QtQuick.Controls as QQC2 import QtQuick.Layouts -import org.mauikit.controls as Maui +import org.kde.kirigami as Kirigami import org.kde.alakarte +import "components" Item { id: root - property string currentCategory: "all" + property string currentSource: "all" - signal categorySelected(string categoryId) + signal sourceSelected(string source) + signal settingsRequested() + signal importRequested() function selectNext() { - if (rail.currentIndex < categoryModel.count - 1) { - rail.currentIndex++ - categorySelected(categoryModel.get(rail.currentIndex).categoryId) - } + if (sourceModel.count <= 0) return + let i = (tabList.currentIndex + 1) % sourceModel.count + _applyIndex(i) } function selectPrevious() { - if (rail.currentIndex > 0) { - rail.currentIndex-- - categorySelected(categoryModel.get(rail.currentIndex).categoryId) + if (sourceModel.count <= 0) return + let i = tabList.currentIndex - 1 + if (i < 0) i = sourceModel.count - 1 + _applyIndex(i) + } + + function _applyIndex(i) { + tabList.currentIndex = i + let item = sourceModel.get(i) + if (item) { + root.currentSource = item.sourceId + root.sourceSelected(item.sourceId) + tabList.positionViewAtIndex(i, ListView.Contain) } } - function _platformIcon(name) { - let p = (name || "").toLowerCase() - if (p.includes("steam")) return ":/icons/brand/steam-symbolic.svg" - if (p.includes("itch")) return ":/icons/brand/itchdotio-symbolic.svg" - if (p.includes("retroarch")) return ":/icons/brand/retroarch-symbolic.svg" - if (p.includes("lutris")) return "applications-games" - if (p.includes("heroic")) return "applications-games" - if (p.includes("bottles")) return "application-x-executable" - if (p.includes("flatpak")) return "applications-games" - if (p.includes("desktop")) return "computer" - return "applications-games" + function iconInfoForPlatform(platformName) { + let p = (platformName || "").toLowerCase() + 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: "applications-games", resourceFallback: Qt.resolvedUrl("icons/brand/itchdotio-symbolic.svg") } + if (p.includes("retroarch")) return { source: "org.libretro.RetroArch", fallback: "applications-games", resourceFallback: Qt.resolvedUrl("icons/brand/retroarch-symbolic.svg") } + if (p.includes("lutris")) return { source: "lutris", fallback: "applications-games", resourceFallback: "" } + if (p.includes("heroic")) return { source: "com.heroicgameslauncher.hgl", fallback: "applications-games", resourceFallback: "" } + if (p.includes("bottles")) return { source: "com.usebottles.bottles", fallback: "application-x-executable", resourceFallback: "" } + if (p.includes("flatpak")) return { source: "flatpak-discover", fallback: "applications-games", resourceFallback: "" } + if (p.includes("desktop")) return { source: "user-desktop", fallback: "computer", resourceFallback: "" } + if (p.includes("legendary")) return { source: "legendary", fallback: "applications-games", resourceFallback: "" } + return { source: "applications-games", fallback: "applications-games", resourceFallback: "" } } - GameSortFilterModel { - id: allGames - sourceModel: App.gameModel - showHidden: false + onCurrentSourceChanged: { + for (let i = 0; i < sourceModel.count; ++i) { + if (sourceModel.get(i).sourceId === currentSource) { + tabList.currentIndex = i + return + } + } } ListModel { - id: categoryModel + id: sourceModel + Component.onCompleted: refresh() - Component.onCompleted: rebuild() - - function rebuild() { + function refresh() { clear() - append({ label: i18n("All"), categoryId: "all", iconSource: "view-list-icons" }) - append({ label: i18n("Favorites"), categoryId: "favorites", iconSource: "starred-symbolic" }) + let allCount = 0, favCount = 0, hiddenCount = 0 + let sources = {} - let seen = {} - for (let i = 0; i < allGames.count; ++i) { - let g = allGames.get(i) - if (!g) continue - let p = g.platform || "" - if (p && !seen[p]) { - seen[p] = true - append({ label: p, categoryId: p, iconSource: root._platformIcon(p) }) - } + for (let i = 0; i < App.gameModel.rowCount(); i++) { + let game = App.gameModel.gameAt(i) + if (!game) continue + if (game.hidden) { hiddenCount++; continue } + allCount++ + if (game.favorite) favCount++ + let p = game.platform + sources[p] = (sources[p] || 0) + 1 + } + + append({ name: i18n("All"), sourceId: "all", icon: "view-list-icons", fallback: "applications-games", resourceFallback: "", count: allCount }) + append({ name: i18n("Favorites"), sourceId: "favorites", icon: "bookmark-new", fallback: "bookmark-new", resourceFallback: "", count: favCount }) + append({ name: i18n("Hidden"), sourceId: "hidden", icon: "view-hidden", fallback: "view-hidden", resourceFallback: "", count: hiddenCount }) + + for (let platform in sources) { + let info = root.iconInfoForPlatform(platform) + append({ name: platform, sourceId: platform, icon: info.source, fallback: info.fallback, resourceFallback: info.resourceFallback, count: sources[platform] }) } for (let j = 0; j < count; ++j) { - if (get(j).categoryId === root.currentCategory) { - rail.currentIndex = j + if (get(j).sourceId === root.currentSource) { + tabList.currentIndex = j break } } @@ -79,83 +102,112 @@ Item { Connections { target: App.gameModel - function onCountChanged() { categoryModel.rebuild() } + function onCountChanged() { sourceModel.refresh() } + } + Connections { + target: App + function onImportCompleted() { sourceModel.refresh() } } - onCurrentCategoryChanged: { - for (let j = 0; j < categoryModel.count; ++j) { - if (categoryModel.get(j).categoryId === currentCategory) { - rail.currentIndex = j - return + RowLayout { + anchors.fill: parent + spacing: 0 + + QQC2.ScrollView { + Layout.fillWidth: true + Layout.fillHeight: true + QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff + QQC2.ScrollBar.vertical.policy: QQC2.ScrollBar.AlwaysOff + + ListView { + id: tabList + model: sourceModel + orientation: ListView.Horizontal + spacing: Kirigami.Units.smallSpacing + leftMargin: Kirigami.Units.largeSpacing + rightMargin: Kirigami.Units.largeSpacing + topMargin: Kirigami.Units.smallSpacing + bottomMargin: Kirigami.Units.smallSpacing + clip: true + + delegate: QQC2.ItemDelegate { + id: tabDelegate + width: implicitWidth + height: tabList.height - tabList.topMargin - tabList.bottomMargin + + readonly property bool isActive: model.sourceId === root.currentSource + + background: Rectangle { + radius: Kirigami.Units.smallSpacing + color: tabDelegate.isActive ? Kirigami.Theme.highlightColor + : (tabDelegate.hovered ? Kirigami.Theme.alternateBackgroundColor : "transparent") + opacity: tabDelegate.isActive ? 0.22 : (tabDelegate.hovered ? 0.12 : 0.0) + + Rectangle { + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + height: 2 + radius: 1 + color: Kirigami.Theme.highlightColor + visible: tabDelegate.isActive + } + + Behavior on opacity { NumberAnimation { duration: Kirigami.Units.shortDuration } } + } + + leftPadding: Kirigami.Units.mediumSpacing + rightPadding: Kirigami.Units.mediumSpacing + + contentItem: RowLayout { + spacing: Kirigami.Units.smallSpacing + + IconWithResourceFallback { + primary: model.icon + secondary: model.fallback + resourceFallback: model.resourceFallback ? Qt.resolvedUrl(model.resourceFallback) : "" + Layout.preferredWidth: Kirigami.Units.iconSizes.small + Layout.preferredHeight: Kirigami.Units.iconSizes.small + color: tabDelegate.isActive ? Kirigami.Theme.highlightColor : Kirigami.Theme.textColor + } + + QQC2.Label { + text: model.name + color: tabDelegate.isActive ? Kirigami.Theme.highlightColor : Kirigami.Theme.textColor + font.bold: tabDelegate.isActive + elide: Text.ElideRight + maximumLineCount: 1 + } + } + + onClicked: { + root.currentSource = model.sourceId + root.sourceSelected(model.sourceId) + } + } } } - } - Rectangle { - anchors.fill: parent - color: Qt.rgba(0, 0, 0, 0.55) + Kirigami.Separator { Layout.fillHeight: true } - ListView { - id: rail - anchors { - fill: parent - leftMargin: 24 - rightMargin: 24 - } - orientation: ListView.Horizontal - spacing: 8 - clip: true - model: categoryModel - keyNavigationEnabled: true - highlightMoveDuration: 200 + QQC2.ToolButton { + icon.name: "document-import" + onClicked: root.importRequested() + Layout.fillHeight: true + flat: true + QQC2.ToolTip.text: i18n("Import Games") + QQC2.ToolTip.visible: hovered + QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay + } - delegate: QQC2.ItemDelegate { - id: pill - width: implicitContentWidth + 32 - height: rail.height - topPadding: 0 - bottomPadding: 0 - leftPadding: 16 - rightPadding: 16 - - readonly property bool isCurrent: ListView.isCurrentItem - - background: Rectangle { - radius: height / 2 - color: pill.isCurrent - ? Maui.Theme.highlightColor - : (pill.hovered ? Qt.rgba(1, 1, 1, 0.12) : Qt.rgba(1, 1, 1, 0.06)) - border.color: pill.isCurrent ? Maui.Theme.highlightColor : Qt.rgba(1, 1, 1, 0.18) - border.width: 1 - - Behavior on color { ColorAnimation { duration: 120 } } - } - - contentItem: RowLayout { - spacing: 6 - - Maui.Icon { - source: model.iconSource - Layout.preferredWidth: 16 - Layout.preferredHeight: 16 - color: "white" - opacity: pill.isCurrent ? 1.0 : 0.75 - } - - QQC2.Label { - text: model.label - color: "white" - opacity: pill.isCurrent ? 1.0 : 0.75 - font.pixelSize: 14 - font.weight: pill.isCurrent ? Font.DemiBold : Font.Normal - } - } - - onClicked: { - rail.currentIndex = index - root.categorySelected(model.categoryId) - } - } + QQC2.ToolButton { + icon.name: "configure" + onClicked: root.settingsRequested() + Layout.fillHeight: true + flat: true + QQC2.ToolTip.text: i18n("Settings") + QQC2.ToolTip.visible: hovered + QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay } } } diff --git a/src/qml/ConsoleGameDetail.qml b/src/qml/ConsoleGameDetail.qml index de92137..b3ca345 100644 --- a/src/qml/ConsoleGameDetail.qml +++ b/src/qml/ConsoleGameDetail.qml @@ -5,8 +5,9 @@ import QtQuick import QtQuick.Controls as QQC2 import QtQuick.Layouts import QtQuick.Effects -import org.mauikit.controls as Maui +import org.kde.kirigami as Kirigami import org.kde.alakarte +import "components" Item { id: detailRoot @@ -202,7 +203,7 @@ Item { contentItem: RowLayout { spacing: 8 - Maui.Icon { + Kirigami.Icon { source: "media-playback-start" width: 22; height: 22 color: "white" @@ -218,9 +219,9 @@ Item { background: Rectangle { radius: 8 color: playBtn.activeFocus - ? Maui.Theme.highlightColor + ? Kirigami.Theme.highlightColor : (playBtn.hovered ? Qt.rgba(1,1,1,0.22) : Qt.rgba(1,1,1,0.14)) - border.color: playBtn.activeFocus ? Maui.Theme.highlightColor : Qt.rgba(1,1,1,0.28) + border.color: playBtn.activeFocus ? Kirigami.Theme.highlightColor : Qt.rgba(1,1,1,0.28) border.width: playBtn.activeFocus ? 0 : 1 Behavior on color { ColorAnimation { duration: 100 } } } @@ -239,7 +240,7 @@ Item { KeyNavigation.left: playBtn KeyNavigation.right: editBtn - contentItem: Maui.Icon { + contentItem: Kirigami.Icon { source: detailRoot.game && detailRoot.game.favorite ? "starred-symbolic" : "non-starred-symbolic" width: 22; height: 22 color: detailRoot.game && detailRoot.game.favorite ? "#f5c518" : "white" @@ -271,7 +272,7 @@ Item { KeyNavigation.left: favoriteBtn KeyNavigation.right: closeBtn - contentItem: Maui.Icon { + contentItem: Kirigami.Icon { source: "document-edit" width: 20; height: 20 color: "white" @@ -301,7 +302,7 @@ Item { icon.name: "window-close" KeyNavigation.left: editBtn - contentItem: Maui.Icon { + contentItem: Kirigami.Icon { source: "window-close" width: 20; height: 20 color: "white" @@ -327,36 +328,11 @@ Item { } } - Item { Layout.preferredHeight: 32 } + Item { Layout.preferredHeight: 16 } - RowLayout { - spacing: 28 - - Repeater { - model: [ - { icon: ":/icons/gamepad/generic/south.svg", label: i18n("Play") }, - { icon: ":/icons/gamepad/generic/east.svg", label: i18n("Back") }, - { icon: ":/icons/gamepad/generic/north.svg", label: i18n("Edit") } - ] - - RowLayout { - spacing: 6 - visible: GamepadManager.connected - - Image { - source: modelData.icon - width: 22; height: 22 - fillMode: Image.PreserveAspectFit - smooth: true - } - - QQC2.Label { - text: modelData.label - color: Qt.rgba(1, 1, 1, 0.65) - font.pixelSize: 13 - } - } - } + BottomHintBar { + uiMode: Config.Couch + context: "details" } Item { Layout.fillHeight: true; Layout.preferredHeight: 1 } diff --git a/src/qml/GameCard.qml b/src/qml/GameCard.qml index 0dd5d43..38f2d6e 100644 --- a/src/qml/GameCard.qml +++ b/src/qml/GameCard.qml @@ -5,7 +5,7 @@ import QtQuick import QtQuick.Controls as QQC2 import QtQuick.Layouts import QtQuick.Effects -import org.mauikit.controls as Maui +import org.kde.kirigami as Kirigami import org.kde.alakarte Item { @@ -41,9 +41,9 @@ Item { layer.effect: MultiEffect { shadowEnabled: true shadowColor: Qt.rgba( - Maui.Theme.highlightColor.r, - Maui.Theme.highlightColor.g, - Maui.Theme.highlightColor.b, 0.55) + Kirigami.Theme.highlightColor.r, + Kirigami.Theme.highlightColor.g, + Kirigami.Theme.highlightColor.b, 0.55) shadowBlur: 0.8 shadowHorizontalOffset: 0 shadowVerticalOffset: 6 @@ -78,7 +78,7 @@ Item { smooth: true } - Maui.Icon { + Kirigami.Icon { anchors.centerIn: parent source: "applications-games" width: parent.width * 0.38 @@ -133,7 +133,7 @@ Item { } } - Maui.Icon { + Kirigami.Icon { anchors.top: parent.top anchors.right: parent.right anchors.margins: 8 @@ -149,7 +149,7 @@ Item { anchors.fill: coverFrame radius: 8 color: "transparent" - border.color: Maui.Theme.highlightColor + border.color: Kirigami.Theme.highlightColor border.width: gameCard.isCurrent ? 3 : 0 opacity: gameCard.isCurrent ? 1.0 : 0.0 Behavior on opacity { NumberAnimation { duration: 120 } } diff --git a/src/qml/Main.qml b/src/qml/Main.qml index 14ff37d..b631a20 100644 --- a/src/qml/Main.qml +++ b/src/qml/Main.qml @@ -7,6 +7,7 @@ import QtQuick.Layouts import QtQuick.Effects import org.mauikit.controls as Maui import org.kde.alakarte +import "components" Maui.ApplicationWindow { id: root @@ -21,7 +22,6 @@ Maui.ApplicationWindow { property bool detailVisible: false property bool searchVisible: false property bool settingsVisible: false - property bool importVisible: false property string searchText: "" color: "#0d0d14" @@ -99,8 +99,8 @@ Maui.ApplicationWindow { RowLayout { anchors.fill: parent - anchors.leftMargin: 20 - anchors.rightMargin: 12 + anchors.leftMargin: 12 + anchors.rightMargin: 0 Image { source: "qrc:/icons/sc-apps-org.kde.alakarte.svg" @@ -110,55 +110,26 @@ Maui.ApplicationWindow { smooth: true } - Item { Layout.preferredWidth: 8 } + Item { Layout.preferredWidth: 4 } ConsoleCategoryRail { id: categoryRail Layout.fillWidth: true Layout.fillHeight: true - currentCategory: root.currentCategory - onCategorySelected: function(cat) { - root.currentCategory = cat - } + currentSource: root.currentCategory + onSourceSelected: function(src) { root.currentCategory = src } + onSettingsRequested: root.settingsVisible = true + onImportRequested: App.importAllGames() } QQC2.ToolButton { - id: searchBtn icon.name: "edit-find" - icon.color: "white" + flat: true Layout.preferredWidth: 40 Layout.preferredHeight: 40 onClicked: root.searchVisible = !root.searchVisible - background: Rectangle { - radius: 6 - color: searchBtn.pressed ? Qt.rgba(1,1,1,0.18) : (searchBtn.hovered ? Qt.rgba(1,1,1,0.12) : "transparent") - } - } - - QQC2.ToolButton { - id: settingsBtn - icon.name: "configure" - icon.color: "white" - Layout.preferredWidth: 40 - Layout.preferredHeight: 40 - onClicked: root.settingsVisible = true - background: Rectangle { - radius: 6 - color: settingsBtn.pressed ? Qt.rgba(1,1,1,0.18) : (settingsBtn.hovered ? Qt.rgba(1,1,1,0.12) : "transparent") - } - } - - QQC2.ToolButton { - id: importBtn - icon.name: "document-import" - icon.color: "white" - Layout.preferredWidth: 40 - Layout.preferredHeight: 40 - onClicked: root.importVisible = true - background: Rectangle { - radius: 6 - color: importBtn.pressed ? Qt.rgba(1,1,1,0.18) : (importBtn.hovered ? Qt.rgba(1,1,1,0.12) : "transparent") - } + QQC2.ToolTip.text: i18n("Search") + QQC2.ToolTip.visible: hovered } } } @@ -172,14 +143,6 @@ Maui.ApplicationWindow { Layout.topMargin: 4 visible: root.searchVisible placeholderText: i18n("Search games…") - color: "white" - font.pixelSize: 15 - background: Rectangle { - radius: 8 - color: Qt.rgba(1, 1, 1, 0.10) - border.color: Qt.rgba(1, 1, 1, 0.22) - border.width: 1 - } onTextChanged: root.searchText = text Keys.onEscapePressed: { root.searchVisible = false @@ -196,54 +159,24 @@ Maui.ApplicationWindow { filterSource: root.currentCategory filterText: root.searchText - onGameFocused: function(game) { - root.focusedGame = game - } + onGameFocused: function(game) { root.focusedGame = game } onGameSelected: function(game) { root.selectedGame = game root.detailVisible = true } - onGameLaunched: function(game) { - App.launcher.launchGame(game) - } + onGameLaunched: function(game) { App.launcher.launchGame(game) } } Rectangle { Layout.fillWidth: true - height: 38 color: Qt.rgba(0, 0, 0, 0.55) - visible: GamepadManager.connected + implicitHeight: hintBar.implicitHeight + 12 - RowLayout { + BottomHintBar { + id: hintBar anchors.centerIn: parent - spacing: 32 - - Repeater { - model: [ - { icon: ":/icons/gamepad/generic/south.svg", label: i18n("Select / Details") }, - { icon: ":/icons/gamepad/generic/east.svg", label: i18n("Back") }, - { icon: ":/icons/gamepad/generic/lb.svg", label: i18n("Prev Category") }, - { icon: ":/icons/gamepad/generic/rb.svg", label: i18n("Next Category") }, - { icon: ":/icons/gamepad/generic/menu.svg", label: i18n("Settings") } - ] - - RowLayout { - spacing: 5 - - Image { - source: modelData.icon - width: 18; height: 18 - fillMode: Image.PreserveAspectFit - smooth: true - } - - QQC2.Label { - text: modelData.label - color: Qt.rgba(1, 1, 1, 0.6) - font.pixelSize: 12 - } - } - } + uiMode: Config.Couch + context: root.detailVisible ? "details" : "library" } } } @@ -299,20 +232,8 @@ Maui.ApplicationWindow { onLoaded: item.forceActiveFocus() } - Loader { - id: importLoader - anchors.fill: parent - z: 20 - active: root.importVisible - sourceComponent: DiagnosticsSheet { - onClosed: root.importVisible = false - } - onLoaded: item.forceActiveFocus() - } - - Maui.ContextualMenu { + GameEditDialog { id: gameEditDialog - property var game: null } Connections { @@ -320,13 +241,11 @@ Maui.ApplicationWindow { function onBackPressed() { if (root.settingsVisible) { root.settingsVisible = false; return } - if (root.importVisible) { root.importVisible = false; return } - if (root.searchVisible) { + if (root.searchVisible) { root.searchVisible = false root.searchText = "" searchField.text = "" libraryView.restoreFocus() - return } } @@ -336,9 +255,7 @@ Maui.ApplicationWindow { function onSearchPressed() { root.searchVisible = !root.searchVisible - if (root.searchVisible) { - Qt.callLater(function() { searchField.forceActiveFocus() }) - } + if (root.searchVisible) Qt.callLater(function() { searchField.forceActiveFocus() }) } function onLeftBumperPressed() { @@ -349,9 +266,4 @@ Maui.ApplicationWindow { if (!root.detailVisible) categoryRail.selectNext() } } - - Connections { - target: App - function onImportingChanged() {} - } }