From 75a0f7a21e54a507349465993a621380ce18c23f Mon Sep 17 00:00:00 2001 From: Marco Allegretti Date: Mon, 1 Jun 2026 15:49:44 +0200 Subject: [PATCH] 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. --- .../folio/qml/gaming/GameCenterOverlay.qml | 280 +++++++++--------- 1 file changed, 145 insertions(+), 135 deletions(-) diff --git a/containments/homescreens/folio/qml/gaming/GameCenterOverlay.qml b/containments/homescreens/folio/qml/gaming/GameCenterOverlay.qml index 841ce1fd..aa5c74b9 100644 --- a/containments/homescreens/folio/qml/gaming/GameCenterOverlay.qml +++ b/containments/homescreens/folio/qml/gaming/GameCenterOverlay.qml @@ -41,6 +41,15 @@ Window { readonly property int shortAnimationDuration: MobileShell.Motion.duration(MobileShell.Motion.EffectsFast) readonly property int longAnimationDuration: MobileShell.Motion.duration(MobileShell.Motion.EffectsDefault) 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() { if (GamingShell.GamepadManager.hasGamepad) { @@ -508,7 +517,7 @@ Window { anchors.fill: parent Kirigami.Theme.inherit: false 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 { @@ -521,8 +530,11 @@ Window { ColumnLayout { anchors.fill: parent - anchors.margins: Kirigami.Units.largeSpacing * 2 - spacing: Kirigami.Units.largeSpacing + anchors.leftMargin: root.horizontalPadding + anchors.rightMargin: root.horizontalPadding + anchors.topMargin: root.verticalPadding + anchors.bottomMargin: root.verticalPadding + spacing: root.compactMode ? Kirigami.Units.smallSpacing : Kirigami.Units.largeSpacing // ---- header ---- RowLayout { @@ -531,7 +543,7 @@ Window { Kirigami.Heading { text: i18n("Game Center") - level: 1 + level: root.compactMode ? 2 : 1 } Item { Layout.fillWidth: true } @@ -586,7 +598,7 @@ Window { QQC2.ToolButton { icon.name: "window-close" - text: i18n("Exit Gaming Mode") + text: root.compactMode ? i18n("Exit") : i18n("Exit Gaming Mode") display: QQC2.AbstractButton.TextBesideIcon Keys.onReturnPressed: clicked() Keys.onEnterPressed: clicked() @@ -598,6 +610,7 @@ Window { RunningGamesView { id: runningGames Layout.fillWidth: true + compactMode: root.compactMode onTaskActivated: { GamingShell.GameLauncherProvider.clearPendingLaunch() root.gameStarted() @@ -652,7 +665,9 @@ Window { ListView { id: recentList 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 spacing: Kirigami.Units.largeSpacing clip: true @@ -690,7 +705,7 @@ Window { Keys.onDownPressed: grid.forceActiveFocus() delegate: QQC2.ItemDelegate { - width: Kirigami.Units.gridUnit * 7 + width: recentList.cardWidth height: recentList.height required property var modelData @@ -704,28 +719,39 @@ Window { background: Rectangle { radius: Kirigami.Units.cornerRadius color: parent.isCurrent - ? Kirigami.Theme.highlightColor - : (parent.hovered ? Kirigami.Theme.hoverColor : "transparent") + ? Qt.rgba(Kirigami.Theme.highlightColor.r, Kirigami.Theme.highlightColor.g, 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") + border.color: parent.isCurrent ? Kirigami.Theme.highlightColor : "transparent" + border.width: parent.isCurrent ? 2 : 0 } contentItem: ColumnLayout { spacing: Kirigami.Units.smallSpacing - Image { + Rectangle { Layout.fillWidth: true - Layout.fillHeight: true - source: hasArt ? "file://" + modelData.artwork : "" - fillMode: Image.PreserveAspectCrop - visible: hasArt - asynchronous: 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) - Kirigami.Icon { - Layout.alignment: Qt.AlignHCenter - implicitWidth: Kirigami.Units.iconSizes.large - implicitHeight: Kirigami.Units.iconSizes.large - source: modelData.icon - visible: !hasArt + Image { + anchors.fill: parent + source: hasArt ? "file://" + modelData.artwork : "" + fillMode: Image.PreserveAspectCrop + visible: hasArt + asynchronous: true + } + + Kirigami.Icon { + anchors.centerIn: parent + implicitWidth: Kirigami.Units.iconSizes.large + implicitHeight: Kirigami.Units.iconSizes.large + source: modelData.icon + visible: !hasArt + } } PC3.Label { @@ -733,7 +759,7 @@ Window { text: modelData.name maximumLineCount: 1 elide: Text.ElideRight - horizontalAlignment: Text.AlignHCenter + horizontalAlignment: Text.AlignLeft font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.85 color: parent.parent.isCurrent ? Kirigami.Theme.highlightedTextColor @@ -761,49 +787,56 @@ Window { } // ---- search + filter ---- - RowLayout { + Item { Layout.fillWidth: true - spacing: Kirigami.Units.largeSpacing + implicitHeight: searchFilterStack.implicitHeight - Kirigami.SearchField { - id: searchField - Layout.fillWidth: true - placeholderText: i18n("Search games…") - onTextChanged: GamingShell.GameLauncherProvider.filterString = text + ColumnLayout { + id: searchFilterStack + anchors.fill: parent + spacing: Kirigami.Units.smallSpacing - Keys.onEscapePressed: { - if (text.length > 0) { - clear() - } else { - root.dismissRequested() + Kirigami.SearchField { + id: searchField + Layout.fillWidth: true + placeholderText: i18n("Search games…") + onTextChanged: GamingShell.GameLauncherProvider.filterString = text + + Keys.onEscapePressed: { + if (text.length > 0) { + clear() + } else { + root.dismissRequested() + } } + Keys.onDownPressed: grid.forceActiveFocus() } - Keys.onDownPressed: grid.forceActiveFocus() - } - QQC2.TabBar { - id: sourceFilterBar - Layout.alignment: Qt.AlignVCenter + QQC2.TabBar { + id: sourceFilterBar + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter - Repeater { - model: { - var tabs = [ - {label: i18n("All"), filter: ""}, - {label: i18n("Desktop"), filter: "desktop"}, - {label: i18n("Waydroid"),filter: "waydroid"} - ] - if (GamingShell.GameLauncherProvider.steamAvailable) - tabs.splice(1, 0, {label: "Steam", filter: "steam"}) - if (GamingShell.GameLauncherProvider.lutrisAvailable) - tabs.push({label: "Lutris", filter: "lutris"}) - if (GamingShell.GameLauncherProvider.heroicAvailable) - tabs.push({label: "Heroic", filter: "heroic"}) - return tabs - } - QQC2.TabButton { - text: modelData.label - width: implicitWidth - onClicked: GamingShell.GameLauncherProvider.sourceFilter = modelData.filter + Repeater { + model: { + var tabs = [ + {label: i18n("All"), filter: ""}, + {label: i18n("Desktop"), filter: "desktop"}, + {label: i18n("Waydroid"),filter: "waydroid"} + ] + if (GamingShell.GameLauncherProvider.steamAvailable) + tabs.splice(1, 0, {label: "Steam", filter: "steam"}) + if (GamingShell.GameLauncherProvider.lutrisAvailable) + tabs.push({label: "Lutris", filter: "lutris"}) + if (GamingShell.GameLauncherProvider.heroicAvailable) + tabs.push({label: "Heroic", filter: "heroic"}) + return tabs + } + QQC2.TabButton { + text: modelData.label + width: implicitWidth + onClicked: GamingShell.GameLauncherProvider.sourceFilter = modelData.filter + } } } } @@ -819,11 +852,12 @@ Window { 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)) 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 highlightMoveDuration: 0 @@ -898,7 +932,7 @@ Window { QQC2.ItemDelegate { anchors.fill: parent - anchors.margins: Kirigami.Units.smallSpacing + anchors.margins: root.compactMode ? 0 : Kirigami.Units.smallSpacing padding: 0 readonly property bool isCurrent: GridView.isCurrentItem && grid.activeFocus @@ -906,24 +940,30 @@ Window { background: Rectangle { Kirigami.Theme.colorSet: Kirigami.Theme.Button color: parent.isCurrent - ? Kirigami.Theme.highlightColor - : (parent.hovered ? Kirigami.Theme.hoverColor : "transparent") + ? Qt.rgba(Kirigami.Theme.highlightColor.r, Kirigami.Theme.highlightColor.g, + 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 + border.color: parent.isCurrent ? Kirigami.Theme.highlightColor : "transparent" + border.width: parent.isCurrent ? 2 : 0 } contentItem: Item { - // ---- cover art tile ---- ColumnLayout { anchors.fill: parent - spacing: 0 - visible: hasArt + spacing: Kirigami.Units.smallSpacing Rectangle { Layout.fillWidth: true - Layout.fillHeight: true + Layout.preferredHeight: grid.artHeight radius: Kirigami.Units.cornerRadius clip: true - color: "transparent" + color: Qt.rgba(Kirigami.Theme.alternateBackgroundColor.r, + Kirigami.Theme.alternateBackgroundColor.g, + Kirigami.Theme.alternateBackgroundColor.b, 0.85) Image { anchors.fill: parent @@ -931,6 +971,15 @@ Window { fillMode: Image.PreserveAspectCrop smooth: 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 { @@ -954,76 +1003,35 @@ Window { } } - // Title beneath artwork - PC3.Label { + ColumnLayout { Layout.fillWidth: true - Layout.preferredHeight: Kirigami.Units.gridUnit * 2 - text: name - maximumLineCount: 1 - elide: Text.ElideRight - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - leftPadding: Kirigami.Units.smallSpacing - rightPadding: Kirigami.Units.smallSpacing - color: parent.parent.parent.isCurrent - ? Kirigami.Theme.highlightedTextColor - : 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 { - Layout.alignment: Qt.AlignHCenter - Layout.fillWidth: true - text: name - maximumLineCount: 2 - wrapMode: Text.Wrap - horizontalAlignment: Text.AlignHCenter - elide: Text.ElideRight - color: parent.parent.parent.isCurrent - ? Kirigami.Theme.highlightedTextColor - : Kirigami.Theme.textColor - } - - 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 + spacing: 0 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" + Layout.fillWidth: true + text: name + maximumLineCount: 1 + elide: Text.ElideRight + horizontalAlignment: Text.AlignLeft + leftPadding: Kirigami.Units.smallSpacing + rightPadding: Kirigami.Units.smallSpacing + color: parent.parent.parent.isCurrent + ? Kirigami.Theme.highlightedTextColor + : Kirigami.Theme.textColor + } + + PC3.Label { + Layout.fillWidth: true + visible: lastPlayedText.length > 0 + text: lastPlayedText + maximumLineCount: 1 + elide: Text.ElideRight + leftPadding: Kirigami.Units.smallSpacing + rightPadding: Kirigami.Units.smallSpacing + opacity: 0.65 + font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.78 } } - - Item { Layout.fillHeight: true } } } @@ -1074,6 +1082,7 @@ Window { Repeater { model: GamingShell.GamepadManager + visible: root.bigScreenMode RowLayout { spacing: Kirigami.Units.smallSpacing @@ -1113,7 +1122,7 @@ Window { PC3.Label { Layout.fillWidth: true 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 wrapMode: Text.WordWrap horizontalAlignment: Text.AlignRight @@ -1126,6 +1135,7 @@ Window { GamingQuickSettings { id: quickSettings z: 50 + compactMode: root.compactMode } // Launch transition: brief fade to black, then dismiss