// 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 QtQuick.Effects import org.kde.kirigami as Kirigami import org.kde.alakarte import "components" FocusScope { id: libraryRoot property string filterSource: "all" property bool searchActive: false property int focusedIndex: 0 property int adaptiveCardSize: App.config.gridSize property bool isTouchDevice: false signal gameSelected(var game) signal gameLaunched(var game) readonly property int gameCount: proxyModel.count property url focusedCoverUrl: "" function focusSearch() { searchField.forceActiveFocus() } function clearSearch() { searchField.text = "" proxyModel.filterText = "" } function restoreFocus() { if (libraryRoot.searchActive) { libraryRoot.focusSearch() } else { if (libraryRoot.focusedIndex >= 0 && libraryRoot.focusedIndex < proxyModel.count) { gameGrid.currentIndex = libraryRoot.focusedIndex } gameGrid.forceActiveFocus() } } function launchFocusedGame() { if (!gameGrid || !proxyModel) return let game = proxyModel.get(gameGrid.currentIndex) if (game) { libraryRoot.gameLaunched(game) } } function openDetailsForFocusedGame() { if (!gameGrid || !proxyModel) return let game = proxyModel.get(gameGrid.currentIndex) if (game) { libraryRoot.gameSelected(game) } } onSearchActiveChanged: { if (!libraryRoot.searchActive) { libraryRoot.clearSearch() Qt.callLater(function() { gameGrid.forceActiveFocus() }) } } Item { anchors.fill: parent anchors.margins: 0 Item { anchors.fill: parent visible: libraryRoot.gameCount > 0 Image { id: backgroundCoverA anchors.fill: parent source: "" fillMode: Image.PreserveAspectCrop asynchronous: true visible: source.toString().length > 0 smooth: true mipmap: App.config.highQualityImages opacity: 0.0 Behavior on opacity { NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic } } layer.enabled: true layer.effect: MultiEffect { blurEnabled: true blur: 0.9 blurMax: 64 } } Image { id: backgroundCoverB anchors.fill: parent source: "" fillMode: Image.PreserveAspectCrop asynchronous: true visible: source.toString().length > 0 smooth: true mipmap: App.config.highQualityImages opacity: 0.0 Behavior on opacity { NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic } } layer.enabled: true layer.effect: MultiEffect { blurEnabled: true blur: 0.9 blurMax: 64 } } Rectangle { anchors.fill: parent color: Qt.rgba(0, 0, 0, 0.55) } } ColumnLayout { anchors.fill: parent spacing: Kirigami.Units.smallSpacing SearchHeader { id: searchHeader Layout.fillWidth: true visible: libraryRoot.searchActive searchField: searchField onSearchChanged: function(text) { proxyModel.filterText = text } onSortChanged: function(mode) { proxyModel.sortMode = mode } Kirigami.SearchField { id: searchField Layout.fillWidth: true placeholderText: i18n("Search games...") onTextChanged: proxyModel.filterText = text Keys.onEscapePressed: { text = "" let w = applicationWindow() if (w && w.hasOwnProperty("searchActive")) { w.searchActive = false } else { libraryRoot.searchActive = false } libraryRoot.restoreFocus() } Keys.onDownPressed: gameGrid.forceActiveFocus() } } GameGridView { id: gameGrid Layout.fillWidth: true Layout.fillHeight: true cardSize: libraryRoot.adaptiveCardSize onCurrentIndexChanged: { if (gameGrid.activeFocus) { libraryRoot.focusedIndex = currentIndex } let game = proxyModel.get(currentIndex) let url = (game && game.coverUrl) ? game.coverUrl : "" if (url === libraryRoot.focusedCoverUrl) return if (backgroundCoverA.opacity > 0.1) { backgroundCoverB.source = url backgroundCoverB.opacity = 0.0 backgroundCoverA.opacity = 0.0 Qt.callLater(function() { backgroundCoverB.opacity = 0.22 }) } else { backgroundCoverA.source = url backgroundCoverA.opacity = 0.0 backgroundCoverB.opacity = 0.0 Qt.callLater(function() { backgroundCoverA.opacity = 0.22 }) } libraryRoot.focusedCoverUrl = url } model: GameSortFilterModel { id: proxyModel sourceModel: App.gameModel showHidden: libraryRoot.filterSource === "hidden" favoritesOnly: libraryRoot.filterSource === "favorites" filterSource: { if (libraryRoot.filterSource === "all") return "" if (libraryRoot.filterSource === "favorites") return "" if (libraryRoot.filterSource === "hidden") return "" return libraryRoot.filterSource } } delegate: Item { width: gameGrid.cellWidth height: gameGrid.cellHeight function clicked() { gameGrid.currentIndex = index libraryRoot.focusedIndex = index card.clicked() } function play() { gameGrid.currentIndex = index libraryRoot.focusedIndex = index card.playClicked() } GameCard { id: card width: gameGrid.cardSize height: Math.round(gameGrid.cardSize * 1.4) anchors.centerIn: parent game: model.gameObject focused: gameGrid.currentIndex === index && gameGrid.activeFocus onClicked: libraryRoot.gameSelected(model.gameObject) onDoubleClicked: libraryRoot.gameLaunched(model.gameObject) onPlayClicked: libraryRoot.gameLaunched(model.gameObject) Keys.onReturnPressed: libraryRoot.gameSelected(model.gameObject) Keys.onEnterPressed: libraryRoot.gameSelected(model.gameObject) Keys.onSpacePressed: libraryRoot.gameLaunched(model.gameObject) } } Keys.onPressed: function(event) { if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { let game = proxyModel.get(currentIndex) if (game) { libraryRoot.gameSelected(game) } event.accepted = true } else if (event.key === Qt.Key_Space) { let game = proxyModel.get(currentIndex) if (game) { libraryRoot.gameLaunched(game) } event.accepted = true } } EmptyState { anchors.centerIn: parent visible: proxyModel.count === 0 && !App.importing icon: proxyModel.filterText.length > 0 ? "edit-find" : (libraryRoot.filterSource === "favorites" ? "bookmark-new" : (libraryRoot.filterSource === "hidden" ? "view-hidden" : "applications-games")) title: proxyModel.filterText.length > 0 ? i18n("No games found") : (libraryRoot.filterSource === "favorites" ? i18n("No favorites yet") : (libraryRoot.filterSource === "hidden" ? i18n("No hidden games") : i18n("Your library is empty"))) description: proxyModel.filterText.length > 0 ? i18n("Try adjusting your search") : (libraryRoot.filterSource === "favorites" ? i18n("Mark games as favorites to see them here") : (libraryRoot.filterSource === "hidden" ? i18n("Hidden games will appear here") : i18n("Import games to get started"))) actionText: (proxyModel.filterText.length > 0 || libraryRoot.filterSource === "favorites" || libraryRoot.filterSource === "hidden") ? "" : i18n("Import Games") onActionTriggered: App.importAllGames() } QQC2.BusyIndicator { anchors.centerIn: parent running: App.importing visible: App.importing } } } } }