// 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.Effects import org.mauikit.controls as Maui import org.kde.alakarte Item { id: gameCard property var game signal clicked() signal playClicked() readonly property bool isCurrent: GridView.isCurrentItem readonly property bool useAnimatedCover: App.config.animatedCovers && game && game.coverUrl && game.coverUrl.toString().toLowerCase().endsWith(".gif") readonly property int coverStatus: useAnimatedCover ? animatedCover.status : staticCover.status width: GridView.view ? GridView.view.cellWidth : 200 height: GridView.view ? GridView.view.cellHeight : 300 Item { id: cardContainer anchors.centerIn: parent width: parent.width - 16 height: parent.height - 16 scale: gameCard.isCurrent ? 1.08 : (hoverHandler.hovered ? 1.03 : 1.0) Behavior on scale { NumberAnimation { duration: 140; easing.type: Easing.OutCubic } } layer.enabled: gameCard.isCurrent layer.effect: MultiEffect { shadowEnabled: true shadowColor: Qt.rgba( Maui.Theme.highlightColor.r, Maui.Theme.highlightColor.g, Maui.Theme.highlightColor.b, 0.55) shadowBlur: 0.8 shadowHorizontalOffset: 0 shadowVerticalOffset: 6 } Rectangle { id: coverFrame anchors.fill: parent radius: 8 color: "#1a1a2a" clip: true Image { id: staticCover anchors.fill: parent source: game ? game.coverUrl : "" fillMode: Image.PreserveAspectCrop asynchronous: true visible: !gameCard.useAnimatedCover smooth: true mipmap: true } AnimatedImage { id: animatedCover anchors.fill: parent source: game ? game.coverUrl : "" fillMode: Image.PreserveAspectCrop asynchronous: true playing: gameCard.isCurrent visible: gameCard.useAnimatedCover smooth: true } Maui.Icon { anchors.centerIn: parent source: "applications-games" width: parent.width * 0.38 height: width color: "#44ffffff" visible: gameCard.coverStatus !== Image.Ready } Rectangle { anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom height: parent.height * 0.42 gradient: Gradient { GradientStop { position: 0.0; color: "transparent" } GradientStop { position: 0.55; color: Qt.rgba(0, 0, 0, 0.62) } GradientStop { position: 1.0; color: Qt.rgba(0, 0, 0, 0.92) } } } QQC2.Label { anchors { left: parent.left right: parent.right bottom: parent.bottom margins: 10 } text: game ? game.name : "" color: "white" font.pixelSize: 13 font.weight: Font.Medium elide: Text.ElideRight wrapMode: Text.WordWrap maximumLineCount: 2 opacity: gameCard.isCurrent || hoverHandler.hovered ? 1.0 : 0.85 } Rectangle { anchors.top: parent.top anchors.left: parent.left anchors.margins: 8 width: 9 height: 9 radius: 5 color: "#4caf50" visible: game && game.running SequentialAnimation on opacity { running: game && game.running loops: Animation.Infinite NumberAnimation { to: 0.25; duration: 700 } NumberAnimation { to: 1.0; duration: 700 } } } Maui.Icon { anchors.top: parent.top anchors.right: parent.right anchors.margins: 8 width: 18 height: 18 source: "starred-symbolic" color: "#f5c518" visible: game && game.favorite } } Rectangle { anchors.fill: coverFrame radius: 8 color: "transparent" border.color: Maui.Theme.highlightColor border.width: gameCard.isCurrent ? 3 : 0 opacity: gameCard.isCurrent ? 1.0 : 0.0 Behavior on opacity { NumberAnimation { duration: 120 } } Behavior on border.width { NumberAnimation { duration: 120 } } } HoverHandler { id: hoverHandler } TapHandler { onTapped: gameCard.clicked() onDoubleTapped: gameCard.playClicked() } TapHandler { acceptedButtons: Qt.RightButton onTapped: contextMenu.popup() } QQC2.Menu { id: contextMenu QQC2.MenuItem { text: i18n("Play") icon.name: "media-playback-start" onTriggered: gameCard.playClicked() } QQC2.MenuSeparator {} QQC2.MenuItem { text: game && game.favorite ? i18n("Remove from Favorites") : i18n("Add to Favorites") icon.name: game && game.favorite ? "bookmark-remove" : "starred-symbolic" onTriggered: if (game) game.favorite = !game.favorite } QQC2.MenuItem { text: game && game.hidden ? i18n("Show in Library") : i18n("Hide from Library") icon.name: game && game.hidden ? "view-visible" : "view-hidden" onTriggered: if (game) game.hidden = !game.hidden } QQC2.MenuSeparator {} QQC2.MenuItem { text: i18n("View Details") icon.name: "documentinfo" onTriggered: gameCard.clicked() } } } }