fix: restore console UI icon visibility, selection highlight, focus transfer

- 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)
This commit is contained in:
Marco Allegretti 2026-03-23 11:42:31 +01:00
parent b5e03fe856
commit 2df35e9b81
5 changed files with 195 additions and 37 deletions

View file

@ -16,6 +16,15 @@ Item {
signal sourceSelected(string source)
signal settingsRequested()
signal importRequested()
signal focusDownRequested()
function focusCurrent() {
if (tabList.currentItem) {
tabList.currentItem.forceActiveFocus()
return
}
tabList.forceActiveFocus()
}
function selectNext() {
if (sourceModel.count <= 0) return
@ -136,12 +145,25 @@ Item {
height: tabList.height - tabList.topMargin - tabList.bottomMargin
readonly property bool isActive: model.sourceId === root.currentSource
readonly property bool isFocused: tabDelegate.activeFocus || tabDelegate.hovered
background: Rectangle {
hoverEnabled: true
scale: tabDelegate.isFocused ? 1.02 : (tabDelegate.isActive ? 1.01 : 1.0)
Behavior on scale {
NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
}
background: Kirigami.ShadowedRectangle {
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)
: Kirigami.Theme.alternateBackgroundColor
opacity: tabDelegate.isActive ? 0.20 : (tabDelegate.isFocused ? 0.10 : 0.0)
border.width: tabDelegate.isFocused ? 2 : 0
border.color: Kirigami.Theme.highlightColor
shadow.size: tabDelegate.isFocused ? Kirigami.Units.mediumSpacing : 0
shadow.color: Qt.rgba(0, 0, 0, 0.28)
Rectangle {
anchors.bottom: parent.bottom
@ -153,7 +175,17 @@ Item {
visible: tabDelegate.isActive
}
Behavior on opacity { NumberAnimation { duration: Kirigami.Units.shortDuration } }
Behavior on opacity {
NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
}
Behavior on shadow.size {
NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
}
Behavior on border.width {
NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
}
}
leftPadding: Kirigami.Units.mediumSpacing
@ -184,6 +216,8 @@ Item {
root.currentSource = model.sourceId
root.sourceSelected(model.sourceId)
}
Keys.onDownPressed: root.focusDownRequested()
}
}
}
@ -191,23 +225,89 @@ Item {
Kirigami.Separator { Layout.fillHeight: true }
QQC2.ToolButton {
id: importBtn
icon.name: "document-import"
onClicked: root.importRequested()
Layout.fillHeight: true
flat: true
hoverEnabled: true
scale: activeFocus ? 1.02 : (hovered ? 1.01 : 1.0)
Behavior on scale {
NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
}
contentItem: Kirigami.Icon {
source: "document-import"
isMask: true
implicitWidth: Kirigami.Units.iconSizes.smallMedium
implicitHeight: Kirigami.Units.iconSizes.smallMedium
width: implicitWidth
height: implicitHeight
anchors.centerIn: parent
color: "white"
}
background: Kirigami.ShadowedRectangle {
radius: Kirigami.Units.smallSpacing
color: Kirigami.Theme.alternateBackgroundColor
opacity: parent.activeFocus ? 0.18 : (parent.hovered ? 0.10 : 0.0)
border.width: parent.activeFocus ? 2 : 0
border.color: Kirigami.Theme.highlightColor
shadow.size: parent.activeFocus ? Kirigami.Units.mediumSpacing : 0
shadow.color: Qt.rgba(0, 0, 0, 0.28)
Behavior on opacity { NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic } }
Behavior on shadow.size { NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic } }
Behavior on border.width { NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic } }
}
QQC2.ToolTip.text: i18n("Import Games")
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
Keys.onDownPressed: root.focusDownRequested()
}
QQC2.ToolButton {
id: settingsBtn
icon.name: "configure"
onClicked: root.settingsRequested()
Layout.fillHeight: true
flat: true
hoverEnabled: true
scale: activeFocus ? 1.02 : (hovered ? 1.01 : 1.0)
Behavior on scale {
NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
}
contentItem: Kirigami.Icon {
source: "configure"
isMask: true
implicitWidth: Kirigami.Units.iconSizes.smallMedium
implicitHeight: Kirigami.Units.iconSizes.smallMedium
width: implicitWidth
height: implicitHeight
anchors.centerIn: parent
color: "white"
}
background: Kirigami.ShadowedRectangle {
radius: Kirigami.Units.smallSpacing
color: Kirigami.Theme.alternateBackgroundColor
opacity: parent.activeFocus ? 0.18 : (parent.hovered ? 0.10 : 0.0)
border.width: parent.activeFocus ? 2 : 0
border.color: Kirigami.Theme.highlightColor
shadow.size: parent.activeFocus ? Kirigami.Units.mediumSpacing : 0
shadow.color: Qt.rgba(0, 0, 0, 0.28)
Behavior on opacity { NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic } }
Behavior on shadow.size { NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic } }
Behavior on border.width { NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic } }
}
QQC2.ToolTip.text: i18n("Settings")
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
Keys.onDownPressed: root.focusDownRequested()
}
}
}

View file

@ -201,6 +201,12 @@ Item {
focus: true
KeyNavigation.right: favoriteBtn
hoverEnabled: true
scale: activeFocus ? 1.03 : (hovered ? 1.01 : 1.0)
Behavior on scale {
NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
}
contentItem: RowLayout {
spacing: 8
Kirigami.Icon {
@ -223,7 +229,7 @@ Item {
: (playBtn.hovered ? Qt.rgba(1,1,1,0.22) : Qt.rgba(1,1,1,0.14))
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 } }
Behavior on color { ColorAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic } }
}
implicitWidth: 160
@ -240,6 +246,12 @@ Item {
KeyNavigation.left: playBtn
KeyNavigation.right: editBtn
hoverEnabled: true
scale: activeFocus ? 1.03 : (hovered ? 1.01 : 1.0)
Behavior on scale {
NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
}
contentItem: Kirigami.Icon {
source: detailRoot.game && detailRoot.game.favorite ? "starred-symbolic" : "non-starred-symbolic"
width: 22; height: 22
@ -253,7 +265,7 @@ Item {
: (favoriteBtn.hovered ? Qt.rgba(1,1,1,0.18) : Qt.rgba(1,1,1,0.10))
border.color: favoriteBtn.activeFocus ? "white" : Qt.rgba(1,1,1,0.25)
border.width: 1
Behavior on color { ColorAnimation { duration: 100 } }
Behavior on color { ColorAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic } }
}
implicitWidth: 52
@ -272,6 +284,12 @@ Item {
KeyNavigation.left: favoriteBtn
KeyNavigation.right: closeBtn
hoverEnabled: true
scale: activeFocus ? 1.03 : (hovered ? 1.01 : 1.0)
Behavior on scale {
NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
}
contentItem: Kirigami.Icon {
source: "document-edit"
width: 20; height: 20
@ -285,7 +303,7 @@ Item {
: (editBtn.hovered ? Qt.rgba(1,1,1,0.18) : Qt.rgba(1,1,1,0.10))
border.color: editBtn.activeFocus ? "white" : Qt.rgba(1,1,1,0.25)
border.width: 1
Behavior on color { ColorAnimation { duration: 100 } }
Behavior on color { ColorAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic } }
}
implicitWidth: 52
@ -302,6 +320,12 @@ Item {
icon.name: "window-close"
KeyNavigation.left: editBtn
hoverEnabled: true
scale: activeFocus ? 1.03 : (hovered ? 1.01 : 1.0)
Behavior on scale {
NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
}
contentItem: Kirigami.Icon {
source: "window-close"
width: 20; height: 20
@ -315,7 +339,7 @@ Item {
: (closeBtn.hovered ? Qt.rgba(1,1,1,0.18) : Qt.rgba(1,1,1,0.10))
border.color: closeBtn.activeFocus ? "white" : Qt.rgba(1,1,1,0.25)
border.width: 1
Behavior on color { ColorAnimation { duration: 100 } }
Behavior on color { ColorAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic } }
}
implicitWidth: 52

View file

@ -4,7 +4,6 @@
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import QtQuick.Effects
import org.kde.kirigami as Kirigami
import org.kde.alakarte
@ -12,10 +11,13 @@ 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
@ -32,21 +34,9 @@ Item {
width: parent.width - 16
height: parent.height - 16
scale: gameCard.isCurrent ? 1.08 : (hoverHandler.hovered ? 1.03 : 1.0)
scale: gameCard.isHighlighted ? 1.08 : ((gameCard.isCurrent || 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
NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
}
Rectangle {
@ -73,7 +63,7 @@ Item {
source: game ? game.coverUrl : ""
fillMode: Image.PreserveAspectCrop
asynchronous: true
playing: gameCard.isCurrent
playing: gameCard.isHighlighted
visible: gameCard.useAnimatedCover
smooth: true
}
@ -150,10 +140,10 @@ Item {
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 } }
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 }

View file

@ -17,12 +17,17 @@ FocusScope {
signal gameSelected(var game)
signal gameFocused(var game)
signal gameLaunched(var game)
signal headerFocusRequested()
readonly property int gameCount: proxyModel.count
function restoreFocus() {
if (grid.flickable) {
grid.flickable.forceActiveFocus()
} else {
grid.forceActiveFocus()
}
}
function focusedGame() {
if (grid.currentIndex < 0) return null
@ -85,6 +90,7 @@ FocusScope {
delegate: GameCard {
game: model.gameObject
gridActive: grid.flickable ? grid.flickable.activeFocus : grid.activeFocus
onClicked: {
grid.currentIndex = index
@ -109,26 +115,29 @@ FocusScope {
Connections {
target: GamepadManager
function onSelectPressed() {
if (!grid.activeFocus) return
if (!grid.flickable || !grid.flickable.activeFocus) return
libraryRoot.selectFocused()
}
function onNavigateUp() {
if (!grid.activeFocus) return
if (grid.currentIndex <= 0) return
if (!grid.flickable || !grid.flickable.activeFocus) return
let cols = Math.max(1, Math.floor(grid.width / grid.cellWidth))
if (grid.currentIndex < cols) {
libraryRoot.headerFocusRequested()
return
}
grid.currentIndex = Math.max(0, grid.currentIndex - cols)
}
function onNavigateDown() {
if (!grid.activeFocus) return
if (!grid.flickable || !grid.flickable.activeFocus) return
let cols = Math.max(1, Math.floor(grid.width / grid.cellWidth))
grid.currentIndex = Math.min(proxyModel.count - 1, grid.currentIndex + cols)
}
function onNavigateLeft() {
if (!grid.activeFocus) return
if (!grid.flickable || !grid.flickable.activeFocus) return
grid.currentIndex = Math.max(0, grid.currentIndex - 1)
}
function onNavigateRight() {
if (!grid.activeFocus) return
if (!grid.flickable || !grid.flickable.activeFocus) return
grid.currentIndex = Math.min(proxyModel.count - 1, grid.currentIndex + 1)
}
}

View file

@ -6,6 +6,7 @@ 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"
@ -120,16 +121,48 @@ Maui.ApplicationWindow {
onSourceSelected: function(src) { root.currentCategory = src }
onSettingsRequested: root.settingsVisible = true
onImportRequested: App.importAllGames()
onFocusDownRequested: libraryView.restoreFocus()
}
QQC2.ToolButton {
id: searchButton
icon.name: "edit-find"
flat: true
Layout.preferredWidth: 40
Layout.preferredHeight: 40
hoverEnabled: true
scale: activeFocus ? 1.02 : (hovered ? 1.01 : 1.0)
Behavior on scale {
NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
}
onClicked: root.searchVisible = !root.searchVisible
QQC2.ToolTip.text: i18n("Search")
QQC2.ToolTip.visible: hovered
contentItem: Kirigami.Icon {
source: "edit-find"
isMask: true
implicitWidth: Kirigami.Units.iconSizes.smallMedium
implicitHeight: Kirigami.Units.iconSizes.smallMedium
width: implicitWidth
height: implicitHeight
anchors.centerIn: parent
color: "white"
}
Keys.onDownPressed: libraryView.restoreFocus()
background: Rectangle {
radius: 8
color: parent.activeFocus
? Qt.rgba(Kirigami.Theme.highlightColor.r, Kirigami.Theme.highlightColor.g, Kirigami.Theme.highlightColor.b, 0.18)
: (parent.hovered ? Qt.rgba(1, 1, 1, 0.10) : Qt.rgba(1, 1, 1, 0.0))
border.width: parent.activeFocus ? 2 : 0
border.color: Kirigami.Theme.highlightColor
Behavior on color { ColorAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic } }
Behavior on border.width { NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic } }
}
}
}
}
@ -159,6 +192,8 @@ Maui.ApplicationWindow {
filterSource: root.currentCategory
filterText: root.searchText
onHeaderFocusRequested: categoryRail.focusCurrent()
onGameFocused: function(game) { root.focusedGame = game }
onGameSelected: function(game) {
root.selectedGame = game