mirror of
https://invent.kde.org/marcoa/a-la-karte.git
synced 2026-03-27 01:03:09 +00:00
- Force isMask on header Kirigami.Icon contentItems (Search/Import/Settings) so white color is applied regardless of icon theme - Remove MultiEffect shadow from GameCard (was bleeding far outside card bounds) - Add gridActive prop to GameCard so selection highlight depends on actual flickable focus, not wrapper focus - Two-tier selection: full highlight when grid focused, dim border when grid unfocused (card stays visible) - Focus transfer: gamepad Up from top row emits headerFocusRequested; Down from rail/buttons calls libraryView.restoreFocus() - Gate gamepad navigation guards on grid.flickable.activeFocus (Maui.GridBrowser internal GridView)
188 lines
6.4 KiB
QML
188 lines
6.4 KiB
QML
// 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 org.kde.kirigami as Kirigami
|
|
import org.kde.alakarte
|
|
|
|
Item {
|
|
id: gameCard
|
|
|
|
property var game
|
|
property bool gridActive: false
|
|
signal clicked()
|
|
signal playClicked()
|
|
|
|
readonly property bool isCurrent: GridView.isCurrentItem
|
|
readonly property bool isGridFocused: gridActive
|
|
readonly property bool isHighlighted: isCurrent && isGridFocused
|
|
|
|
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.isHighlighted ? 1.08 : ((gameCard.isCurrent || hoverHandler.hovered) ? 1.03 : 1.0)
|
|
Behavior on scale {
|
|
NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
|
|
}
|
|
|
|
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.isHighlighted
|
|
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.isHighlighted ? 3 : (gameCard.isCurrent ? 2 : 0)
|
|
opacity: gameCard.isHighlighted ? 1.0 : (gameCard.isCurrent ? 0.55 : 0.0)
|
|
Behavior on opacity { NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic } }
|
|
Behavior on border.width { NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic } }
|
|
}
|
|
|
|
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()
|
|
}
|
|
}
|
|
}
|
|
}
|