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

199 lines
6.4 KiB
QML
Raw Normal View History

// 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.kde.kirigami as Kirigami
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(
Kirigami.Theme.highlightColor.r,
Kirigami.Theme.highlightColor.g,
Kirigami.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
}
Kirigami.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 }
}
}
Kirigami.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: Kirigami.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()
}
}
}
}