Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 38 additions & 6 deletions clients/deck/fixtures/sample_polaris_library.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,30 @@
"category": "fast_action",
"installed": true,
"cover_url": "/polaris/v1/games/game-123/cover",
"genres": ["Action", "Puzzle"],
"genres": [
"Action",
"Puzzle"
],
"last_launched": 1718187600000,
"mangohud": true,
"hdr_supported": true,
"launch_mode": {
"preferred_mode": "virtual_display",
"recommended_mode": "headless",
"allowed_modes": ["headless", "virtual_display"],
"allowed_modes": [
"headless",
"virtual_display"
],
"mode_reason": "Host default is headless."
},
"steam_launch": {
"available": true,
"mode": "big-picture",
"recommended_mode": "direct",
"allowed_modes": ["direct", "big-picture"],
"allowed_modes": [
"direct",
"big-picture"
],
"mode_reason": "Steam Input fallback."
}
},
Expand All @@ -50,23 +59,46 @@
"category": "fast_action",
"installed": true,
"cover_url": "/polaris/v1/games/game-456/cover",
"genres": ["Action", "Roguelike"],
"genres": [
"Action",
"Roguelike"
],
"last_launched": 1718191200000,
"mangohud": true,
"hdr_supported": false,
"launch_mode": {
"preferred_mode": "headless",
"recommended_mode": "virtual_display",
"allowed_modes": ["headless", "virtual_display"],
"allowed_modes": [
"headless",
"virtual_display"
],
"mode_reason": "Virtual display is available for this host."
},
"steam_launch": {
"available": true,
"mode": "direct",
"recommended_mode": "big-picture",
"allowed_modes": ["direct", "big-picture"],
"allowed_modes": [
"direct",
"big-picture"
],
"mode_reason": "Big Picture is controller-friendly."
}
}
],
"hosts": [
{
"id": "host-snapshot-primary",
"display_name": "Polaris Snapshot Primary",
"status_label": "Ready from read-only library snapshot",
"subtitle": "Read-only host snapshot fixture \u2014 not discovered from the network."
},
{
"id": "host-snapshot-living-room",
"display_name": "Polaris Snapshot Living Room",
"status_label": "Available from read-only library snapshot",
"subtitle": "Read-only host snapshot fixture \u2014 not discovered from the network."
}
]
}
145 changes: 105 additions & 40 deletions clients/deck/qml/Main.qml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ ApplicationWindow {
property var launchPreviewCopyAction: novaLaunchPreviewCopyAction

function selectedHostSubtitle() {
return "Demo host detail only — not discovered from the network."
return "Read-only host detail only — not discovered from the network."
}

function previewComponent(value) {
Expand Down Expand Up @@ -85,6 +85,36 @@ ApplicationWindow {
refreshLaunchPreviewBinding()
}

function focusSelectedLibraryItem() {
for (let i = 0; i < libraryGameRepeater.count; ++i) {
const gameItem = libraryGameRepeater.itemAt(i)
if (gameItem !== null && selectedGameForPreview && gameItem.objectName === selectedGameForPreview.id) {
gameItem.forceActiveFocus()
return
}
}
for (let i = 0; i < hostRepeater.count; ++i) {
const hostItem = hostRepeater.itemAt(i)
if (hostItem !== null && selectedHostForPreview && hostItem.objectName === selectedHostForPreview.id) {
hostItem.forceActiveFocus()
return
}
}
if (novaLibraryHosts.length === 0) {
emptyHostState.forceActiveFocus()
return
}
if (novaLibraryGames.length === 0 && emptyGameState.visible) {
emptyGameState.forceActiveFocus()
return
}
if (hostRepeater.itemAt(0) !== null) {
hostRepeater.itemAt(0).forceActiveFocus()
} else {
emptyHostState.forceActiveFocus()
}
}

function activateLaunchPreviewCopyFromController() {
const canCopyPreview = launchPreviewCopyAction.enabled
&& launchPreviewCopyAction.previewText.length > 0
Expand Down Expand Up @@ -123,7 +153,7 @@ ApplicationWindow {
focus: true
Component.onCompleted: Qt.callLater(function() {
refreshLaunchPreviewBinding()
if (novaDemoHosts.length > 0 && hostRepeater.itemAt(0) !== null) {
if (novaLibraryHosts.length > 0 && hostRepeater.itemAt(0) !== null) {
hostRepeater.itemAt(0).forceActiveFocus()
} else {
emptyHostState.forceActiveFocus()
Expand Down Expand Up @@ -164,7 +194,7 @@ ApplicationWindow {
spacing: deckPanelSpacing

Label {
text: "Demo hosts"
text: "Library hosts"
color: "#E9ECFF"
font.pixelSize: 28
font.bold: true
Expand All @@ -173,7 +203,7 @@ ApplicationWindow {
Rectangle {
id: emptyHostState
objectName: "host-empty-state"
visible: novaDemoHosts.length === 0
visible: novaLibraryHosts.length === 0
Layout.preferredWidth: hostColumnWidth
Layout.preferredHeight: visible ? 120 : 0
radius: 20
Expand Down Expand Up @@ -207,7 +237,7 @@ ApplicationWindow {

Repeater {
id: hostRepeater
model: novaDemoHosts
model: novaLibraryHosts

delegate: Rectangle {
required property int index
Expand All @@ -232,13 +262,13 @@ ApplicationWindow {
Keys.onEnterPressed: selectHostForPreview(modelData)
Keys.onSpacePressed: selectHostForPreview(modelData)
Keys.onDownPressed: {
const next = hostRepeater.itemAt(index + 1)
const next = hostRepeater.itemAt((index + 1) % hostRepeater.count)
if (next !== null) {
next.forceActiveFocus()
}
}
Keys.onUpPressed: {
const previous = hostRepeater.itemAt(index - 1)
const previous = hostRepeater.itemAt((index + hostRepeater.count - 1) % hostRepeater.count)
if (previous !== null) {
previous.forceActiveFocus()
}
Expand All @@ -261,6 +291,14 @@ ApplicationWindow {
color: "#B8C2F0"
font.pixelSize: 16
}

Label {
visible: selectedHostForPreview.id === modelData.id
text: "Selected host"
color: "#8AFFC1"
font.pixelSize: 12
font.bold: true
}
}
}
}
Expand All @@ -281,12 +319,51 @@ ApplicationWindow {

Label {
Layout.preferredWidth: sampleTextWidth
text: novaLibraryFixtureSource + (novaLibraryReadOnly ? " · read-only" : "")
text: novaLibraryFixtureSource + (novaLibraryReadOnly ? " · read-only · Read-only snapshot loaded" : " · Snapshot unavailable in this preview shell — no backend request will be made")
color: "#A8B0D8"
font.pixelSize: 13
wrapMode: Text.WordWrap
}

Rectangle {
id: emptyGameState
objectName: "game-empty-state"
visible: novaLibraryGames.length === 0
Layout.preferredWidth: sampleCardWidth
Layout.preferredHeight: visible ? 116 : 0
radius: 18
color: activeFocus ? "#202B55" : "#151D39"
border.color: activeFocus ? "#B8C2FF" : "#39466F"
border.width: activeFocus ? 5 : 2
focus: visible
activeFocusOnTab: visible
KeyNavigation.left: novaLibraryHosts.length > 0 ? hostRepeater.itemAt(0) : emptyHostState
KeyNavigation.right: hostDetailPanel
Keys.onLeftPressed: focusSelectedLibraryItem()
Keys.onRightPressed: hostDetailPanel.forceActiveFocus()

ColumnLayout {
anchors.fill: parent
anchors.margins: 16
spacing: 5

Label {
text: "No games in read-only snapshot"
color: "#E9ECFF"
font.pixelSize: 20
font.bold: true
}

Label {
Layout.preferredWidth: sampleTextWidth
text: "Snapshot unavailable in this preview shell — no backend request will be made."
color: "#A8B0D8"
font.pixelSize: 12
wrapMode: Text.WordWrap
}
}
}

Repeater {
id: libraryGameRepeater
model: novaLibraryGames
Expand Down Expand Up @@ -314,24 +391,18 @@ ApplicationWindow {
Keys.onEnterPressed: selectGameForPreview(modelData)
Keys.onSpacePressed: selectGameForPreview(modelData)
Keys.onDownPressed: {
const next = libraryGameRepeater.itemAt(index + 1)
const next = libraryGameRepeater.itemAt((index + 1) % libraryGameRepeater.count)
if (next !== null) {
next.forceActiveFocus()
}
}
Keys.onUpPressed: {
const previous = libraryGameRepeater.itemAt(index - 1)
const previous = libraryGameRepeater.itemAt((index + libraryGameRepeater.count - 1) % libraryGameRepeater.count)
if (previous !== null) {
previous.forceActiveFocus()
}
}
Keys.onLeftPressed: {
if (novaDemoHosts.length > 0 && hostRepeater.itemAt(0) !== null) {
hostRepeater.itemAt(0).forceActiveFocus()
} else {
emptyHostState.forceActiveFocus()
}
}
Keys.onLeftPressed: focusSelectedLibraryItem()

ColumnLayout {
anchors.fill: parent
Expand All @@ -356,6 +427,14 @@ ApplicationWindow {
color: "#A8B0D8"
font.pixelSize: 12
}

Label {
visible: selectedGameForPreview.id === modelData.id
text: "Selected game"
color: "#8AFFC1"
font.pixelSize: 11
font.bold: true
}
}
}
}
Expand All @@ -377,14 +456,10 @@ ApplicationWindow {
focus: true
activeFocusOnTab: true
KeyNavigation.left: hostRepeater.itemAt(0) !== null ? hostRepeater.itemAt(0) : emptyHostState
KeyNavigation.up: copyPreviewButton
KeyNavigation.down: launchCtaPlaceholder
Keys.onLeftPressed: {
if (novaDemoHosts.length > 0 && hostRepeater.itemAt(0) !== null) {
hostRepeater.itemAt(0).forceActiveFocus()
} else {
emptyHostState.forceActiveFocus()
}
}
Keys.onLeftPressed: focusSelectedLibraryItem()
Keys.onUpPressed: copyPreviewButton.forceActiveFocus()
Keys.onDownPressed: launchCtaPlaceholder.forceActiveFocus()

ColumnLayout {
Expand All @@ -393,7 +468,7 @@ ApplicationWindow {
spacing: 8

Label {
text: "Demo host detail"
text: "Read-only host detail"
color: "#7C88B8"
font.pixelSize: 16
}
Expand Down Expand Up @@ -448,13 +523,7 @@ ApplicationWindow {
Keys.onReturnPressed: activateLaunchPreviewCopyFromController()
Keys.onEnterPressed: activateLaunchPreviewCopyFromController()
Keys.onSpacePressed: activateLaunchPreviewCopyFromController()
Keys.onLeftPressed: {
if (novaDemoHosts.length > 0 && hostRepeater.itemAt(0) !== null) {
hostRepeater.itemAt(0).forceActiveFocus()
} else {
emptyHostState.forceActiveFocus()
}
}
Keys.onLeftPressed: focusSelectedLibraryItem()

ColumnLayout {
anchors.fill: parent
Expand Down Expand Up @@ -518,14 +587,10 @@ ApplicationWindow {
focusPolicy: Qt.StrongFocus
activeFocusOnTab: true
KeyNavigation.up: launchCtaPlaceholder
KeyNavigation.down: hostDetailPanel
Keys.onUpPressed: launchCtaPlaceholder.forceActiveFocus()
Keys.onLeftPressed: {
if (novaDemoHosts.length > 0 && hostRepeater.itemAt(0) !== null) {
hostRepeater.itemAt(0).forceActiveFocus()
} else {
emptyHostState.forceActiveFocus()
}
}
Keys.onDownPressed: hostDetailPanel.forceActiveFocus()
Keys.onLeftPressed: focusSelectedLibraryItem()
Keys.onReturnPressed: activateLaunchPreviewCopyFromController()
Keys.onEnterPressed: activateLaunchPreviewCopyFromController()
Keys.onSpacePressed: activateLaunchPreviewCopyFromController()
Expand All @@ -535,7 +600,7 @@ ApplicationWindow {
Label {
id: copyStatusLabel
Layout.preferredWidth: detailTextWidth
text: launchPreviewCopyAction.idleStatusLabel + " Press A on Copy to verify."
text: launchPreviewCopyAction.idleStatusLabel + " Press A on Copy to verify. A copies the preview URI locally only."
color: "#FFDDA8"
font.pixelSize: 12
wrapMode: Text.WordWrap
Expand Down
Loading
Loading