Rework Game Center into console-style capsule rails

Restructure the overlay around landscape 16:9 media capsules, clearer focus borders, tighter search/filter rails, and stronger handheld vs big-screen spacing hierarchy.
This commit is contained in:
Marco Allegretti 2026-06-01 15:49:44 +02:00
parent 434f46403c
commit 75a0f7a21e

View file

@ -41,6 +41,15 @@ Window {
readonly property int shortAnimationDuration: MobileShell.Motion.duration(MobileShell.Motion.EffectsFast) readonly property int shortAnimationDuration: MobileShell.Motion.duration(MobileShell.Motion.EffectsFast)
readonly property int longAnimationDuration: MobileShell.Motion.duration(MobileShell.Motion.EffectsDefault) readonly property int longAnimationDuration: MobileShell.Motion.duration(MobileShell.Motion.EffectsDefault)
readonly property int launchFadeDuration: MobileShell.Motion.duration(MobileShell.Motion.StandardAccel) readonly property int launchFadeDuration: MobileShell.Motion.duration(MobileShell.Motion.StandardAccel)
readonly property int shortestSide: Math.min(width, height)
readonly property bool compactMode: !ShellSettings.Settings.convergenceModeEnabled && shortestSide <= Kirigami.Units.gridUnit * 50
readonly property bool bigScreenMode: !compactMode
readonly property int horizontalPadding: compactMode ? Kirigami.Units.largeSpacing : Kirigami.Units.largeSpacing * 2
readonly property int verticalPadding: compactMode ? Kirigami.Units.largeSpacing : Kirigami.Units.largeSpacing * 2
readonly property real gridMinCellSize: compactMode ? Kirigami.Units.gridUnit * 6.8 : Kirigami.Units.gridUnit * 8.8
// Steam library assets heavily favor wide capsules and 16:9 media surfaces.
// Keep game tiles landscape-first to avoid mobile-style portrait cards.
readonly property real capsuleArtAspect: 16 / 9
function controlLegendText() { function controlLegendText() {
if (GamingShell.GamepadManager.hasGamepad) { if (GamingShell.GamepadManager.hasGamepad) {
@ -508,7 +517,7 @@ Window {
anchors.fill: parent anchors.fill: parent
Kirigami.Theme.inherit: false Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Window Kirigami.Theme.colorSet: Kirigami.Theme.Window
color: MobileShell.SurfaceColors.withAlpha(MobileShell.SurfaceColors.accentSurface(Kirigami.Theme.backgroundColor, 0.24, 0.12), 0.92) color: MobileShell.SurfaceColors.withAlpha(MobileShell.SurfaceColors.accentSurface(Kirigami.Theme.backgroundColor, 0.24, 0.12), root.bigScreenMode ? 0.94 : 0.9)
} }
FocusScope { FocusScope {
@ -521,8 +530,11 @@ Window {
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
anchors.margins: Kirigami.Units.largeSpacing * 2 anchors.leftMargin: root.horizontalPadding
spacing: Kirigami.Units.largeSpacing anchors.rightMargin: root.horizontalPadding
anchors.topMargin: root.verticalPadding
anchors.bottomMargin: root.verticalPadding
spacing: root.compactMode ? Kirigami.Units.smallSpacing : Kirigami.Units.largeSpacing
// ---- header ---- // ---- header ----
RowLayout { RowLayout {
@ -531,7 +543,7 @@ Window {
Kirigami.Heading { Kirigami.Heading {
text: i18n("Game Center") text: i18n("Game Center")
level: 1 level: root.compactMode ? 2 : 1
} }
Item { Layout.fillWidth: true } Item { Layout.fillWidth: true }
@ -586,7 +598,7 @@ Window {
QQC2.ToolButton { QQC2.ToolButton {
icon.name: "window-close" icon.name: "window-close"
text: i18n("Exit Gaming Mode") text: root.compactMode ? i18n("Exit") : i18n("Exit Gaming Mode")
display: QQC2.AbstractButton.TextBesideIcon display: QQC2.AbstractButton.TextBesideIcon
Keys.onReturnPressed: clicked() Keys.onReturnPressed: clicked()
Keys.onEnterPressed: clicked() Keys.onEnterPressed: clicked()
@ -598,6 +610,7 @@ Window {
RunningGamesView { RunningGamesView {
id: runningGames id: runningGames
Layout.fillWidth: true Layout.fillWidth: true
compactMode: root.compactMode
onTaskActivated: { onTaskActivated: {
GamingShell.GameLauncherProvider.clearPendingLaunch() GamingShell.GameLauncherProvider.clearPendingLaunch()
root.gameStarted() root.gameStarted()
@ -652,7 +665,9 @@ Window {
ListView { ListView {
id: recentList id: recentList
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: Kirigami.Units.gridUnit * 5 readonly property int cardWidth: root.compactMode ? Kirigami.Units.gridUnit * 8 : Kirigami.Units.gridUnit * 10
readonly property int artHeight: Math.round(cardWidth / root.capsuleArtAspect)
Layout.preferredHeight: artHeight + Kirigami.Units.gridUnit * 1.7
orientation: ListView.Horizontal orientation: ListView.Horizontal
spacing: Kirigami.Units.largeSpacing spacing: Kirigami.Units.largeSpacing
clip: true clip: true
@ -690,7 +705,7 @@ Window {
Keys.onDownPressed: grid.forceActiveFocus() Keys.onDownPressed: grid.forceActiveFocus()
delegate: QQC2.ItemDelegate { delegate: QQC2.ItemDelegate {
width: Kirigami.Units.gridUnit * 7 width: recentList.cardWidth
height: recentList.height height: recentList.height
required property var modelData required property var modelData
@ -704,16 +719,26 @@ Window {
background: Rectangle { background: Rectangle {
radius: Kirigami.Units.cornerRadius radius: Kirigami.Units.cornerRadius
color: parent.isCurrent color: parent.isCurrent
? Kirigami.Theme.highlightColor ? Qt.rgba(Kirigami.Theme.highlightColor.r, Kirigami.Theme.highlightColor.g, Kirigami.Theme.highlightColor.b, 0.22)
: (parent.hovered ? Kirigami.Theme.hoverColor : "transparent") : (parent.hovered
? Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.08)
: "transparent")
border.color: parent.isCurrent ? Kirigami.Theme.highlightColor : "transparent"
border.width: parent.isCurrent ? 2 : 0
} }
contentItem: ColumnLayout { contentItem: ColumnLayout {
spacing: Kirigami.Units.smallSpacing spacing: Kirigami.Units.smallSpacing
Image { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.preferredHeight: recentList.artHeight
radius: Kirigami.Units.cornerRadius
clip: true
color: Qt.rgba(Kirigami.Theme.alternateBackgroundColor.r, Kirigami.Theme.alternateBackgroundColor.g, Kirigami.Theme.alternateBackgroundColor.b, 0.8)
Image {
anchors.fill: parent
source: hasArt ? "file://" + modelData.artwork : "" source: hasArt ? "file://" + modelData.artwork : ""
fillMode: Image.PreserveAspectCrop fillMode: Image.PreserveAspectCrop
visible: hasArt visible: hasArt
@ -721,19 +746,20 @@ Window {
} }
Kirigami.Icon { Kirigami.Icon {
Layout.alignment: Qt.AlignHCenter anchors.centerIn: parent
implicitWidth: Kirigami.Units.iconSizes.large implicitWidth: Kirigami.Units.iconSizes.large
implicitHeight: Kirigami.Units.iconSizes.large implicitHeight: Kirigami.Units.iconSizes.large
source: modelData.icon source: modelData.icon
visible: !hasArt visible: !hasArt
} }
}
PC3.Label { PC3.Label {
Layout.fillWidth: true Layout.fillWidth: true
text: modelData.name text: modelData.name
maximumLineCount: 1 maximumLineCount: 1
elide: Text.ElideRight elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignLeft
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.85 font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.85
color: parent.parent.isCurrent color: parent.parent.isCurrent
? Kirigami.Theme.highlightedTextColor ? Kirigami.Theme.highlightedTextColor
@ -761,9 +787,14 @@ Window {
} }
// ---- search + filter ---- // ---- search + filter ----
RowLayout { Item {
Layout.fillWidth: true Layout.fillWidth: true
spacing: Kirigami.Units.largeSpacing implicitHeight: searchFilterStack.implicitHeight
ColumnLayout {
id: searchFilterStack
anchors.fill: parent
spacing: Kirigami.Units.smallSpacing
Kirigami.SearchField { Kirigami.SearchField {
id: searchField id: searchField
@ -783,6 +814,7 @@ Window {
QQC2.TabBar { QQC2.TabBar {
id: sourceFilterBar id: sourceFilterBar
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Repeater { Repeater {
@ -808,6 +840,7 @@ Window {
} }
} }
} }
}
// ---- game grid ---- // ---- game grid ----
@ -819,11 +852,12 @@ Window {
model: GamingShell.GameLauncherProvider model: GamingShell.GameLauncherProvider
readonly property real minCellSize: Kirigami.Units.gridUnit * 8 readonly property real minCellSize: root.gridMinCellSize
readonly property int columns: Math.max(2, Math.floor(width / minCellSize)) readonly property int columns: Math.max(2, Math.floor(width / minCellSize))
cellWidth: Math.floor(width / columns) cellWidth: Math.floor(width / columns)
cellHeight: Math.floor(cellWidth * 1.5) + Kirigami.Units.gridUnit * 2 readonly property int artHeight: Math.round(cellWidth / root.capsuleArtAspect)
cellHeight: artHeight + (root.compactMode ? Kirigami.Units.gridUnit * 1.9 : Kirigami.Units.gridUnit * 2.2)
keyNavigationEnabled: true keyNavigationEnabled: true
highlightMoveDuration: 0 highlightMoveDuration: 0
@ -898,7 +932,7 @@ Window {
QQC2.ItemDelegate { QQC2.ItemDelegate {
anchors.fill: parent anchors.fill: parent
anchors.margins: Kirigami.Units.smallSpacing anchors.margins: root.compactMode ? 0 : Kirigami.Units.smallSpacing
padding: 0 padding: 0
readonly property bool isCurrent: GridView.isCurrentItem && grid.activeFocus readonly property bool isCurrent: GridView.isCurrentItem && grid.activeFocus
@ -906,24 +940,30 @@ Window {
background: Rectangle { background: Rectangle {
Kirigami.Theme.colorSet: Kirigami.Theme.Button Kirigami.Theme.colorSet: Kirigami.Theme.Button
color: parent.isCurrent color: parent.isCurrent
? Kirigami.Theme.highlightColor ? Qt.rgba(Kirigami.Theme.highlightColor.r, Kirigami.Theme.highlightColor.g,
: (parent.hovered ? Kirigami.Theme.hoverColor : "transparent") Kirigami.Theme.highlightColor.b, 0.22)
: (parent.hovered
? Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g,
Kirigami.Theme.textColor.b, 0.08)
: "transparent")
radius: Kirigami.Units.cornerRadius radius: Kirigami.Units.cornerRadius
border.color: parent.isCurrent ? Kirigami.Theme.highlightColor : "transparent"
border.width: parent.isCurrent ? 2 : 0
} }
contentItem: Item { contentItem: Item {
// ---- cover art tile ----
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
spacing: 0 spacing: Kirigami.Units.smallSpacing
visible: hasArt
Rectangle { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.preferredHeight: grid.artHeight
radius: Kirigami.Units.cornerRadius radius: Kirigami.Units.cornerRadius
clip: true clip: true
color: "transparent" color: Qt.rgba(Kirigami.Theme.alternateBackgroundColor.r,
Kirigami.Theme.alternateBackgroundColor.g,
Kirigami.Theme.alternateBackgroundColor.b, 0.85)
Image { Image {
anchors.fill: parent anchors.fill: parent
@ -931,6 +971,15 @@ Window {
fillMode: Image.PreserveAspectCrop fillMode: Image.PreserveAspectCrop
smooth: true smooth: true
asynchronous: true asynchronous: true
visible: hasArt
}
Kirigami.Icon {
anchors.centerIn: parent
implicitWidth: root.compactMode ? Kirigami.Units.iconSizes.large : Kirigami.Units.iconSizes.huge
implicitHeight: implicitWidth
source: icon
visible: !hasArt
} }
Rectangle { Rectangle {
@ -954,76 +1003,35 @@ Window {
} }
} }
// Title beneath artwork ColumnLayout {
Layout.fillWidth: true
spacing: 0
PC3.Label { PC3.Label {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
text: name text: name
maximumLineCount: 1 maximumLineCount: 1
elide: Text.ElideRight elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
leftPadding: Kirigami.Units.smallSpacing leftPadding: Kirigami.Units.smallSpacing
rightPadding: Kirigami.Units.smallSpacing rightPadding: Kirigami.Units.smallSpacing
color: parent.parent.parent.isCurrent color: parent.parent.parent.isCurrent
? Kirigami.Theme.highlightedTextColor ? Kirigami.Theme.highlightedTextColor
: Kirigami.Theme.textColor : Kirigami.Theme.textColor
} }
}
// ---- fallback icon tile ----
ColumnLayout {
anchors.fill: parent
anchors.margins: Kirigami.Units.smallSpacing
visible: !hasArt
spacing: Kirigami.Units.smallSpacing
Item { Layout.fillHeight: true }
Kirigami.Icon {
Layout.alignment: Qt.AlignHCenter
implicitWidth: Kirigami.Units.iconSizes.huge
implicitHeight: Kirigami.Units.iconSizes.huge
source: icon
scale: parent.parent.parent.isCurrent ? 1.08 : 1.0
Behavior on scale {
MobileShell.MotionNumberAnimation { type: MobileShell.Motion.EffectsFast; duration: root.shortAnimationDuration }
}
}
PC3.Label { PC3.Label {
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true Layout.fillWidth: true
text: name visible: lastPlayedText.length > 0
maximumLineCount: 2 text: lastPlayedText
wrapMode: Text.Wrap maximumLineCount: 1
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight elide: Text.ElideRight
color: parent.parent.parent.isCurrent leftPadding: Kirigami.Units.smallSpacing
? Kirigami.Theme.highlightedTextColor rightPadding: Kirigami.Units.smallSpacing
: Kirigami.Theme.textColor opacity: 0.65
} font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.78
Rectangle {
Layout.alignment: Qt.AlignHCenter
visible: source !== "desktop"
radius: height / 2
color: root.sourceChipColor(source)
implicitHeight: sourceChipLabel.implicitHeight + Kirigami.Units.smallSpacing
implicitWidth: sourceChipLabel.implicitWidth + Kirigami.Units.largeSpacing
PC3.Label {
id: sourceChipLabel
anchors.centerIn: parent
text: root.sourceLabel(source)
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.72
font.weight: Font.DemiBold
color: "white"
} }
} }
Item { Layout.fillHeight: true }
} }
} }
@ -1074,6 +1082,7 @@ Window {
Repeater { Repeater {
model: GamingShell.GamepadManager model: GamingShell.GamepadManager
visible: root.bigScreenMode
RowLayout { RowLayout {
spacing: Kirigami.Units.smallSpacing spacing: Kirigami.Units.smallSpacing
@ -1113,7 +1122,7 @@ Window {
PC3.Label { PC3.Label {
Layout.fillWidth: true Layout.fillWidth: true
text: root.controlLegendText() text: root.controlLegendText()
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.75 font.pointSize: Kirigami.Theme.defaultFont.pointSize * (root.compactMode ? 0.7 : 0.75)
opacity: 0.5 opacity: 0.5
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignRight horizontalAlignment: Text.AlignRight
@ -1126,6 +1135,7 @@ Window {
GamingQuickSettings { GamingQuickSettings {
id: quickSettings id: quickSettings
z: 50 z: 50
compactMode: root.compactMode
} }
// Launch transition: brief fade to black, then dismiss // Launch transition: brief fade to black, then dismiss