- PR1: gamescope detection at boot — X11 backend, scale 1, kiosk fullscreen; tray + miniplayer suppressed (click-offset fix). - PR2: full keyboard navigation for the kiosk (3 zones, focus ring, scroll-into-view; typing never navigates). - PR3: native gamepad via gilrs — D-pad/stick/A/B to synthetic Slint keys with hysteresis, repeat and debounce; B suppresses Escape while a text field is focused (closes the Steam OSK). - PR4: Steam OSK via the steam://open/keyboard deeplink on text focus. - PR5: scripts/qbz-add-to-steam (stdlib-only, 19/19 unit tests) with artwork assets + pure-gamepad controller layout. Spec: qbz-nix-docs/steam-deck/STEAM_DECK_GAME_MODE_PLAN.md km
84 lines
3.9 KiB
Plaintext
84 lines
3.9 KiB
Plaintext
// KioskAlbumGrid — lightweight wrapping grid of KioskCards for the kiosk
|
|
// Search/Library album tabs (2.0.2 frente #3). Absolute-positioned cells inside
|
|
// a vertical Flickable; card width derives from the grid width so it reflows.
|
|
//
|
|
// WINDOWED (in-window pattern, mirrors the desktop Carousel): the `for` creates
|
|
// only cheap empty positioned slots for every album, but the heavy KioskCard
|
|
// (Image + text) mounts ONLY for slots whose band intersects the viewport. A
|
|
// plain full grid mounted every card at once and spiked CPU on entry — a Pi
|
|
// can't afford that. Binds `[AlbumCardItem]` so artwork pushes land on the cards.
|
|
import { Theme } from "../foundation/semantic-colors.slint";
|
|
import { AlbumCardItem, KioskNavState } from "../state.slint";
|
|
import { KioskCard } from "KioskCard.slint";
|
|
|
|
export component KioskAlbumGrid inherits Flickable {
|
|
in property <[AlbumCardItem]> albums;
|
|
in property <int> columns: 6;
|
|
in property <length> gap: 16px;
|
|
in property <length> pad: 16px;
|
|
callback open(string);
|
|
|
|
property <length> card-w: (self.width - 2 * root.pad - (root.columns - 1) * root.gap) / root.columns;
|
|
property <length> card-h: root.card-w + 46px;
|
|
property <float> rows: Math.ceil(root.albums.length / root.columns);
|
|
|
|
viewport-width: self.width;
|
|
viewport-height: root.rows * (root.card-h + root.gap) + root.pad;
|
|
|
|
// ---- Keyboard/gamepad nav (PR 2). The HOSTING VIEW publishes the grid
|
|
// geometry (tabs / columns / count) into KioskNavState; the grid only
|
|
// mirrors the focus index (ring + scroll-into-view) and answers the
|
|
// Enter pulse. The content index space skips `tabs` leading tab entries.
|
|
property <int> focused-item: KioskNavState.index - KioskNavState.tabs;
|
|
property <bool> item-focused: KioskNavState.nav-active
|
|
&& KioskNavState.zone == "content"
|
|
&& root.focused-item >= 0
|
|
&& root.focused-item < root.albums.length;
|
|
|
|
// Scroll the focused card's row into view. viewport-y is WRITTEN from
|
|
// this callback (plain imperative assignment on a settled layout), never
|
|
// read by a binding that feeds layout — the AlbumCollectionView
|
|
// "Recursion detected" panic class.
|
|
property <length> focus-top: root.pad + Math.floor(root.focused-item / root.columns) * (root.card-h + root.gap);
|
|
property <int> nav-index-probe: KioskNavState.index;
|
|
changed nav-index-probe => {
|
|
if (root.item-focused) {
|
|
if (root.focus-top < -self.viewport-y) {
|
|
self.viewport-y = -(root.focus-top - 8px);
|
|
} else if (root.focus-top + root.card-h > -self.viewport-y + self.height) {
|
|
self.viewport-y = -(root.focus-top + root.card-h - self.height + 8px);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Enter pulse → open the focused album.
|
|
property <int> act-probe: KioskNavState.activate-seq;
|
|
changed act-probe => {
|
|
if (root.item-focused) {
|
|
root.open(root.albums[root.focused-item].id);
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
for album[i] in root.albums: slot := Rectangle {
|
|
x: root.pad + Math.mod(i, root.columns) * (root.card-w + root.gap);
|
|
y: root.pad + Math.floor(i / root.columns) * (root.card-h + root.gap);
|
|
width: root.card-w;
|
|
height: root.card-h;
|
|
// Visible band (content coords) is [-viewport-y, -viewport-y+height];
|
|
// 240px margin keeps a card mounted just off-screen for smooth scroll.
|
|
property <bool> in-window: (self.y + self.height >= -root.viewport-y - 240px)
|
|
&& (self.y <= -root.viewport-y + root.height + 240px);
|
|
if slot.in-window: KioskCard {
|
|
width: root.card-w;
|
|
art-size: root.card-w;
|
|
album: album;
|
|
nav-focused: root.item-focused && root.focused-item == i;
|
|
clicked(id) => {
|
|
root.open(id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|