mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-29 07:03:08 +00:00
Add HUD toasts and quick-resume shortcut
Connect to gameLaunched and gameLaunchFailed signals. Launch toasts dismiss after 3 s; error toasts after 5 s with a red background. The window grows downward from the HUD pill to accommodate the toast pill. When recentGamesChanged fires, the most recently played game is stored in quickResumeGame. While set, the HUD pill widens leftward to show the game name and a play button that calls launchByStorageId directly — no need to open Game Center.
This commit is contained in:
parent
739cc33049
commit
0ce8d6eb46
1 changed files with 150 additions and 8 deletions
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls as QQC2
|
import QtQuick.Controls as QQC2
|
||||||
|
import QtQuick.Layouts
|
||||||
import QtQuick.Window
|
import QtQuick.Window
|
||||||
|
|
||||||
import org.kde.kirigami as Kirigami
|
import org.kde.kirigami as Kirigami
|
||||||
|
|
@ -18,11 +19,32 @@ Window {
|
||||||
// Guard against startup timing where Kirigami units may briefly be 0/NaN.
|
// Guard against startup timing where Kirigami units may briefly be 0/NaN.
|
||||||
// LayerShell surfaces must never be committed with zero size.
|
// LayerShell surfaces must never be committed with zero size.
|
||||||
readonly property real safeGridUnit: ((Kirigami.Units.gridUnit || 0) > 0) ? Kirigami.Units.gridUnit : 16
|
readonly property real safeGridUnit: ((Kirigami.Units.gridUnit || 0) > 0) ? Kirigami.Units.gridUnit : 16
|
||||||
width: safeGridUnit * 4
|
|
||||||
height: safeGridUnit * 2
|
property string toastMessage: ""
|
||||||
|
property bool toastError: false
|
||||||
|
readonly property bool toastActive: toastMessage.length > 0
|
||||||
|
|
||||||
|
// Most-recently-played game for quick resume. Populated from recentGames(1)
|
||||||
|
// and refreshed whenever the recent list changes.
|
||||||
|
property var quickResumeGame: null
|
||||||
|
readonly property bool hasQuickResume: quickResumeGame !== null
|
||||||
|
|
||||||
|
// Window grows leftward from top-right anchor:
|
||||||
|
// toast active → widest (needs room for message text)
|
||||||
|
// quick resume → medium (game name + controls)
|
||||||
|
// idle → compact (controls only)
|
||||||
|
width: toastActive ? safeGridUnit * 16 : (hasQuickResume ? safeGridUnit * 14 : safeGridUnit * 4)
|
||||||
|
height: toastActive ? safeGridUnit * 4 : safeGridUnit * 2
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
flags: Qt.FramelessWindowHint
|
flags: Qt.FramelessWindowHint
|
||||||
|
|
||||||
|
Behavior on width {
|
||||||
|
NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
|
||||||
|
}
|
||||||
|
Behavior on height {
|
||||||
|
NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
|
||||||
|
}
|
||||||
|
|
||||||
LayerShell.Window.scope: "gaming-hud"
|
LayerShell.Window.scope: "gaming-hud"
|
||||||
LayerShell.Window.layer: LayerShell.Window.LayerOverlay
|
LayerShell.Window.layer: LayerShell.Window.LayerOverlay
|
||||||
LayerShell.Window.anchors: LayerShell.Window.AnchorTop | LayerShell.Window.AnchorRight
|
LayerShell.Window.anchors: LayerShell.Window.AnchorTop | LayerShell.Window.AnchorRight
|
||||||
|
|
@ -38,16 +60,93 @@ Window {
|
||||||
NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutQuad }
|
NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutQuad }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: toastTimer
|
||||||
|
onTriggered: root.toastMessage = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: GamingShell.GameLauncherProvider
|
||||||
|
function onGameLaunched(name) {
|
||||||
|
root.toastMessage = i18n("Launching %1", name)
|
||||||
|
root.toastError = false
|
||||||
|
toastTimer.interval = 3000
|
||||||
|
toastTimer.restart()
|
||||||
|
}
|
||||||
|
function onGameLaunchFailed(name, error) {
|
||||||
|
root.toastMessage = error.length > 0 ? error : i18n("Failed to launch %1", name)
|
||||||
|
root.toastError = true
|
||||||
|
toastTimer.interval = 5000
|
||||||
|
toastTimer.restart()
|
||||||
|
}
|
||||||
|
function onRecentGamesChanged() {
|
||||||
|
const recent = GamingShell.GameLauncherProvider.recentGames(1)
|
||||||
|
root.quickResumeGame = recent.length > 0 ? recent[0] : null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
const recent = GamingShell.GameLauncherProvider.recentGames(1)
|
||||||
|
root.quickResumeGame = recent.length > 0 ? recent[0] : null
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- HUD pill (always visible, fills window width, grows leftward) ----
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
anchors.margins: Kirigami.Units.smallSpacing
|
anchors.margins: Kirigami.Units.smallSpacing
|
||||||
|
height: safeGridUnit * 2 - Kirigami.Units.smallSpacing * 2
|
||||||
radius: height / 2
|
radius: height / 2
|
||||||
color: Qt.rgba(0, 0, 0, 0.55)
|
color: Qt.rgba(0, 0, 0, 0.55)
|
||||||
|
|
||||||
Row {
|
RowLayout {
|
||||||
anchors.centerIn: parent
|
anchors {
|
||||||
spacing: Kirigami.Units.smallSpacing
|
fill: parent
|
||||||
|
leftMargin: Kirigami.Units.smallSpacing
|
||||||
|
rightMargin: Kirigami.Units.smallSpacing
|
||||||
|
}
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
// Quick-resume section — only visible when a recent game exists
|
||||||
|
QQC2.ToolButton {
|
||||||
|
visible: root.hasQuickResume
|
||||||
|
icon.name: "media-playback-start"
|
||||||
|
icon.color: "white"
|
||||||
|
display: QQC2.AbstractButton.IconOnly
|
||||||
|
QQC2.ToolTip.visible: hovered
|
||||||
|
QQC2.ToolTip.text: root.quickResumeGame ? i18n("Resume %1", root.quickResumeGame.name) : ""
|
||||||
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
|
onClicked: {
|
||||||
|
if (root.quickResumeGame) {
|
||||||
|
GamingShell.GameLauncherProvider.launchByStorageId(root.quickResumeGame.storageId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
visible: root.hasQuickResume
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: root.quickResumeGame ? root.quickResumeGame.name : ""
|
||||||
|
color: "white"
|
||||||
|
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.85
|
||||||
|
elide: Text.ElideRight
|
||||||
|
maximumLineCount: 1
|
||||||
|
leftPadding: Kirigami.Units.smallSpacing
|
||||||
|
}
|
||||||
|
|
||||||
|
// Separator between quick-resume and controls
|
||||||
|
Rectangle {
|
||||||
|
visible: root.hasQuickResume
|
||||||
|
width: 1
|
||||||
|
implicitHeight: Kirigami.Units.gridUnit
|
||||||
|
color: Qt.rgba(1, 1, 1, 0.25)
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
Layout.leftMargin: Kirigami.Units.smallSpacing
|
||||||
|
Layout.rightMargin: Kirigami.Units.smallSpacing
|
||||||
|
}
|
||||||
|
|
||||||
|
// Game Center button
|
||||||
QQC2.ToolButton {
|
QQC2.ToolButton {
|
||||||
icon.name: "input-gaming"
|
icon.name: "input-gaming"
|
||||||
icon.color: "white"
|
icon.color: "white"
|
||||||
|
|
@ -57,7 +156,7 @@ Window {
|
||||||
onClicked: root.openRequested()
|
onClicked: root.openRequested()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show primary gamepad battery when connected
|
// Primary gamepad battery
|
||||||
QQC2.Label {
|
QQC2.Label {
|
||||||
visible: GamingShell.GamepadManager.hasGamepad
|
visible: GamingShell.GamepadManager.hasGamepad
|
||||||
&& GamingShell.GamepadManager.primaryGamepad
|
&& GamingShell.GamepadManager.primaryGamepad
|
||||||
|
|
@ -67,7 +166,50 @@ Window {
|
||||||
: ""
|
: ""
|
||||||
color: "white"
|
color: "white"
|
||||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.8
|
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.8
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
rightPadding: Kirigami.Units.smallSpacing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- toast pill (slides in below HUD pill when active) ----
|
||||||
|
Rectangle {
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.margins: Kirigami.Units.smallSpacing
|
||||||
|
height: safeGridUnit * 2 - Kirigami.Units.smallSpacing * 2
|
||||||
|
radius: height / 2
|
||||||
|
color: root.toastError ? Qt.rgba(0.75, 0.1, 0.05, 0.9) : Qt.rgba(0, 0, 0, 0.55)
|
||||||
|
|
||||||
|
opacity: root.toastActive ? 1.0 : 0.0
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.InOutQuad }
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
right: parent.right
|
||||||
|
verticalCenter: parent.verticalCenter
|
||||||
|
leftMargin: Kirigami.Units.largeSpacing
|
||||||
|
rightMargin: Kirigami.Units.largeSpacing
|
||||||
|
}
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
Kirigami.Icon {
|
||||||
|
source: root.toastError ? "dialog-warning" : "media-playback-start"
|
||||||
|
implicitWidth: Kirigami.Units.iconSizes.small
|
||||||
|
implicitHeight: Kirigami.Units.iconSizes.small
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: root.toastMessage
|
||||||
|
color: "white"
|
||||||
|
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.85
|
||||||
|
elide: Text.ElideRight
|
||||||
|
maximumLineCount: 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue