implements PLN-0064 retro hub shell UI

This commit is contained in:
bQUARKz 2026-05-15 17:56:59 +01:00
parent 414bdd0333
commit 1393eeb54f
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
3 changed files with 127 additions and 23 deletions

View File

@ -1,5 +1,6 @@
use crate::{CrashReport, SystemOS}; use crate::{CrashReport, SystemOS};
use prometeu_hal::color::Color; use prometeu_hal::color::Color;
use prometeu_hal::gfx_bridge::GfxBridge;
use prometeu_hal::primitives::Rect; use prometeu_hal::primitives::Rect;
use prometeu_hal::{HardwareBridge, InputSignals}; use prometeu_hal::{HardwareBridge, InputSignals};
use prometeu_vm::VirtualMachine; use prometeu_vm::VirtualMachine;
@ -7,6 +8,21 @@ use prometeu_vm::VirtualMachine;
const SHELL_A_BUTTON: Rect = Rect { x: 112, y: 108, w: 112, h: 48 }; const SHELL_A_BUTTON: Rect = Rect { x: 112, y: 108, w: 112, h: 48 };
const SHELL_B_BUTTON: Rect = Rect { x: 256, y: 108, w: 112, h: 48 }; const SHELL_B_BUTTON: Rect = Rect { x: 256, y: 108, w: 112, h: 48 };
const CLOSE_BUTTON: Rect = Rect { x: 420, y: 24, w: 16, h: 16 }; const CLOSE_BUTTON: Rect = Rect { x: 420, y: 24, w: 16, h: 16 };
const VIEWPORT: Rect = Rect { x: 0, y: 0, w: 480, h: 270 };
const HOME_PANEL: Rect = Rect { x: 48, y: 58, w: 384, h: 154 };
const TITLE_PANEL: Rect = Rect { x: 72, y: 34, w: 336, h: 28 };
#[cfg(test)]
const SHELL_FRAME: Rect = Rect { x: 40, y: 20, w: 400, h: 220 };
const COLOR_BG: Color = Color::rgb(12, 14, 22);
const COLOR_PANEL: Color = Color::rgb(25, 31, 48);
const COLOR_PANEL_DARK: Color = Color::rgb(8, 10, 16);
const COLOR_BORDER: Color = Color::rgb(180, 218, 222);
const COLOR_HILITE: Color = Color::rgb(252, 226, 122);
const COLOR_TEXT: Color = Color::rgb(230, 236, 224);
const COLOR_MUTED: Color = Color::rgb(91, 112, 128);
const COLOR_BUTTON: Color = Color::rgb(45, 59, 82);
const COLOR_BUTTON_ACTIVE: Color = Color::rgb(72, 91, 122);
/// PrometeuHub: Launcher and system UI environment. /// PrometeuHub: Launcher and system UI environment.
pub struct PrometeuHub; pub struct PrometeuHub;
@ -17,6 +33,18 @@ pub enum NativeShellApp {
ShellB, ShellB,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct HubButtonSpec {
app: NativeShellApp,
rect: Rect,
label: &'static str,
}
const HOME_BUTTONS: [HubButtonSpec; 2] = [
HubButtonSpec { app: NativeShellApp::ShellA, rect: SHELL_A_BUTTON, label: "ShellA" },
HubButtonSpec { app: NativeShellApp::ShellB, rect: SHELL_B_BUTTON, label: "ShellB" },
];
impl NativeShellApp { impl NativeShellApp {
pub fn app_id(self) -> u32 { pub fn app_id(self) -> u32 {
match self { match self {
@ -90,13 +118,22 @@ impl PrometeuHub {
} }
pub fn render(&mut self, os: &mut SystemOS, hw: &mut dyn HardwareBridge) { pub fn render(&mut self, os: &mut SystemOS, hw: &mut dyn HardwareBridge) {
let pointer_x = hw.touch().x();
let pointer_y = hw.touch().y();
if os.windows().window_count() == 0 {
render_home(hw.gfx_mut(), pointer_x, pointer_y);
return;
}
for window in os.windows().windows() { for window in os.windows().windows() {
hw.gfx_mut().fill_rect( render_shell_window(
window.viewport.x, hw.gfx_mut(),
window.viewport.y, window.viewport,
window.viewport.w,
window.viewport.h,
window.color, window.color,
&window.title,
pointer_x,
pointer_y,
); );
} }
} }
@ -135,19 +172,67 @@ fn action_for_click(in_shell: bool, x: i32, y: i32, pressed: bool) -> Option<Sys
return rect_contains(CLOSE_BUTTON, x, y).then_some(SystemProfileAction::CloseShell); return rect_contains(CLOSE_BUTTON, x, y).then_some(SystemProfileAction::CloseShell);
} }
if rect_contains(SHELL_A_BUTTON, x, y) { HOME_BUTTONS
Some(SystemProfileAction::LaunchNativeShell(NativeShellApp::ShellA)) .iter()
} else if rect_contains(SHELL_B_BUTTON, x, y) { .find(|button| rect_contains(button.rect, x, y))
Some(SystemProfileAction::LaunchNativeShell(NativeShellApp::ShellB)) .map(|button| SystemProfileAction::LaunchNativeShell(button.app))
} else {
None
}
} }
fn rect_contains(rect: Rect, x: i32, y: i32) -> bool { fn rect_contains(rect: Rect, x: i32, y: i32) -> bool {
x >= rect.x && x < rect.x + rect.w && y >= rect.y && y < rect.y + rect.h x >= rect.x && x < rect.x + rect.w && y >= rect.y && y < rect.y + rect.h
} }
fn render_home(gfx: &mut dyn GfxBridge, pointer_x: i32, pointer_y: i32) {
gfx.fill_rect(VIEWPORT.x, VIEWPORT.y, VIEWPORT.w, VIEWPORT.h, COLOR_BG);
draw_panel(gfx, TITLE_PANEL, COLOR_PANEL_DARK, COLOR_BORDER);
gfx.draw_text(TITLE_PANEL.x + 58, TITLE_PANEL.y + 9, "PROMETEU HUB", COLOR_TEXT);
draw_panel(gfx, HOME_PANEL, COLOR_PANEL, COLOR_BORDER);
gfx.draw_text(HOME_PANEL.x + 26, HOME_PANEL.y + 22, "Native Shell Apps", COLOR_TEXT);
gfx.draw_text(HOME_PANEL.x + 26, HOME_PANEL.y + 40, "first retro ui slice", COLOR_MUTED);
for button in HOME_BUTTONS {
let hovered = rect_contains(button.rect, pointer_x, pointer_y);
draw_button(gfx, button.rect, button.label, hovered);
}
}
fn render_shell_window(
gfx: &mut dyn GfxBridge,
viewport: Rect,
color: Color,
title: &str,
pointer_x: i32,
pointer_y: i32,
) {
gfx.fill_rect(VIEWPORT.x, VIEWPORT.y, VIEWPORT.w, VIEWPORT.h, COLOR_BG);
draw_panel(gfx, viewport, COLOR_PANEL_DARK, COLOR_BORDER);
gfx.fill_rect(viewport.x + 4, viewport.y + 24, viewport.w - 8, viewport.h - 28, color);
gfx.draw_text(viewport.x + 10, viewport.y + 8, title, COLOR_TEXT);
gfx.draw_text(viewport.x + 18, viewport.y + 44, "native fake shell", COLOR_PANEL_DARK);
let close_hovered = rect_contains(CLOSE_BUTTON, pointer_x, pointer_y);
let close_fill = if close_hovered { COLOR_HILITE } else { COLOR_BUTTON };
gfx.fill_rect(CLOSE_BUTTON.x, CLOSE_BUTTON.y, CLOSE_BUTTON.w, CLOSE_BUTTON.h, close_fill);
gfx.draw_rect(CLOSE_BUTTON.x, CLOSE_BUTTON.y, CLOSE_BUTTON.w, CLOSE_BUTTON.h, COLOR_BORDER);
gfx.draw_text(CLOSE_BUTTON.x + 5, CLOSE_BUTTON.y + 4, "x", COLOR_TEXT);
}
fn draw_panel(gfx: &mut dyn GfxBridge, rect: Rect, fill: Color, border: Color) {
gfx.fill_rect(rect.x, rect.y, rect.w, rect.h, fill);
gfx.draw_rect(rect.x, rect.y, rect.w, rect.h, border);
gfx.draw_rect(rect.x + 2, rect.y + 2, rect.w - 4, rect.h - 4, COLOR_MUTED);
}
fn draw_button(gfx: &mut dyn GfxBridge, rect: Rect, label: &str, hovered: bool) {
let fill = if hovered { COLOR_BUTTON_ACTIVE } else { COLOR_BUTTON };
let border = if hovered { COLOR_HILITE } else { COLOR_BORDER };
gfx.fill_rect(rect.x, rect.y, rect.w, rect.h, fill);
gfx.draw_rect(rect.x, rect.y, rect.w, rect.h, border);
gfx.draw_rect(rect.x + 3, rect.y + 3, rect.w - 6, rect.h - 6, COLOR_MUTED);
gfx.draw_text(rect.x + 28, rect.y + 18, label, COLOR_TEXT);
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -155,7 +240,7 @@ mod tests {
#[test] #[test]
fn shell_a_button_click_emits_launch_action() { fn shell_a_button_click_emits_launch_action() {
assert_eq!( assert_eq!(
action_for_click(false, SHELL_A_BUTTON.x, SHELL_A_BUTTON.y, true), action_for_click(false, HOME_BUTTONS[0].rect.x, HOME_BUTTONS[0].rect.y, true),
Some(SystemProfileAction::LaunchNativeShell(NativeShellApp::ShellA)) Some(SystemProfileAction::LaunchNativeShell(NativeShellApp::ShellA))
); );
} }
@ -198,4 +283,23 @@ mod tests {
); );
assert_eq!(action_for_click(false, CLOSE_BUTTON.x, CLOSE_BUTTON.y, true), None); assert_eq!(action_for_click(false, CLOSE_BUTTON.x, CLOSE_BUTTON.y, true), None);
} }
#[test]
fn home_layout_exposes_exactly_two_manual_shell_buttons() {
assert_eq!(HOME_BUTTONS.len(), 2);
assert_eq!(HOME_BUTTONS[0].label, "ShellA");
assert_eq!(HOME_BUTTONS[0].app, NativeShellApp::ShellA);
assert_eq!(HOME_BUTTONS[1].label, "ShellB");
assert_eq!(HOME_BUTTONS[1].app, NativeShellApp::ShellB);
}
#[test]
fn shell_close_button_sits_inside_shell_frame() {
assert!(rect_contains(SHELL_FRAME, CLOSE_BUTTON.x, CLOSE_BUTTON.y));
assert!(rect_contains(
SHELL_FRAME,
CLOSE_BUTTON.x + CLOSE_BUTTON.w - 1,
CLOSE_BUTTON.y + CLOSE_BUTTON.h - 1
));
}
} }

View File

@ -34,4 +34,4 @@
{"type":"discussion","id":"DSC-0031","status":"done","ticket":"runtime-mode-separation-game-system","title":"Agenda - Runtime Mode Separation: Game and System","created_at":"2026-05-11","updated_at":"2026-05-14","tags":["runtime","firmware","hub","system-apps","game-mode","scheduler"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0040","file":"discussion/lessons/DSC-0031-runtime-mode-separation-game-system/LSN-0040-system-pipeline-separation.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14"}]} {"type":"discussion","id":"DSC-0031","status":"done","ticket":"runtime-mode-separation-game-system","title":"Agenda - Runtime Mode Separation: Game and System","created_at":"2026-05-11","updated_at":"2026-05-14","tags":["runtime","firmware","hub","system-apps","game-mode","scheduler"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0040","file":"discussion/lessons/DSC-0031-runtime-mode-separation-game-system/LSN-0040-system-pipeline-separation.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14"}]}
{"type":"discussion","id":"DSC-0032","status":"done","ticket":"system-os-lifecycle-process-task-contract","title":"Agenda - SystemOS Lifecycle, Process and Task Contract","created_at":"2026-05-14","updated_at":"2026-05-15","tags":["runtime","os","lifecycle","process","task","shell","firmware"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0041","file":"discussion/lessons/DSC-0032-system-os-lifecycle-process-task-contract/LSN-0041-systemos-lifecycle-authority.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"type":"discussion","id":"DSC-0032","status":"done","ticket":"system-os-lifecycle-process-task-contract","title":"Agenda - SystemOS Lifecycle, Process and Task Contract","created_at":"2026-05-14","updated_at":"2026-05-15","tags":["runtime","os","lifecycle","process","task","shell","firmware"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0041","file":"discussion/lessons/DSC-0032-system-os-lifecycle-process-task-contract/LSN-0041-systemos-lifecycle-authority.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]}
{"type":"discussion","id":"DSC-0033","status":"done","ticket":"system-os-service-ownership-and-module-layout","title":"Agenda - SystemOS Service Ownership and Module Layout","created_at":"2026-05-14","updated_at":"2026-05-15","tags":["runtime","os","services","module-layout","vm","window-manager","logging"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0042","file":"discussion/lessons/DSC-0033-system-os-service-ownership-and-module-layout/LSN-0042-systemos-service-ownership-boundary.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"type":"discussion","id":"DSC-0033","status":"done","ticket":"system-os-service-ownership-and-module-layout","title":"Agenda - SystemOS Service Ownership and Module Layout","created_at":"2026-05-14","updated_at":"2026-05-15","tags":["runtime","os","services","module-layout","vm","window-manager","logging"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0042","file":"discussion/lessons/DSC-0033-system-os-service-ownership-and-module-layout/LSN-0042-systemos-service-ownership-boundary.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]}
{"type":"discussion","id":"DSC-0036","status":"in_progress","ticket":"prometeu-hub-ui-direction","title":"Agenda - Prometeu Hub UI Direction","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["hub","ui","shell","system-apps","lifecycle","design-system"],"agendas":[{"id":"AGD-0036","file":"AGD-0036-prometeu-hub-ui-direction.md","status":"accepted","created_at":"2026-05-15","updated_at":"2026-05-15"}],"decisions":[{"id":"DEC-0028","file":"DEC-0028-prometeu-hub-initial-retro-shell-ui-slice.md","status":"accepted","created_at":"2026-05-15","updated_at":"2026-05-15","ref_agenda":"AGD-0036"}],"plans":[{"id":"PLN-0062","file":"PLN-0062-native-shell-launch-and-lifecycle-wiring.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0028"]},{"id":"PLN-0063","file":"PLN-0063-hub-mouse-input-and-click-routing.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0028"]},{"id":"PLN-0064","file":"PLN-0064-retro-hub-home-and-fake-shell-ui.md","status":"open","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0028"]},{"id":"PLN-0065","file":"PLN-0065-pixel-operator-font-integration.md","status":"open","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0028"]},{"id":"PLN-0066","file":"PLN-0066-hub-ui-slice-validation-and-lesson.md","status":"open","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0028"]}],"lessons":[]} {"type":"discussion","id":"DSC-0036","status":"in_progress","ticket":"prometeu-hub-ui-direction","title":"Agenda - Prometeu Hub UI Direction","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["hub","ui","shell","system-apps","lifecycle","design-system"],"agendas":[{"id":"AGD-0036","file":"AGD-0036-prometeu-hub-ui-direction.md","status":"accepted","created_at":"2026-05-15","updated_at":"2026-05-15"}],"decisions":[{"id":"DEC-0028","file":"DEC-0028-prometeu-hub-initial-retro-shell-ui-slice.md","status":"accepted","created_at":"2026-05-15","updated_at":"2026-05-15","ref_agenda":"AGD-0036"}],"plans":[{"id":"PLN-0062","file":"PLN-0062-native-shell-launch-and-lifecycle-wiring.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0028"]},{"id":"PLN-0063","file":"PLN-0063-hub-mouse-input-and-click-routing.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0028"]},{"id":"PLN-0064","file":"PLN-0064-retro-hub-home-and-fake-shell-ui.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0028"]},{"id":"PLN-0065","file":"PLN-0065-pixel-operator-font-integration.md","status":"open","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0028"]},{"id":"PLN-0066","file":"PLN-0066-hub-ui-slice-validation-and-lesson.md","status":"open","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0028"]}],"lessons":[]}

View File

@ -2,7 +2,7 @@
id: PLN-0064 id: PLN-0064
ticket: prometeu-hub-ui-direction ticket: prometeu-hub-ui-direction
title: Retro Hub Home and Fake Shell UI title: Retro Hub Home and Fake Shell UI
status: open status: done
created: 2026-05-15 created: 2026-05-15
ref_decisions: [DEC-0028] ref_decisions: [DEC-0028]
tags: [hub, ui, shell, system-apps, lifecycle, design-system] tags: [hub, ui, shell, system-apps, lifecycle, design-system]
@ -77,14 +77,14 @@ Replace the current debug-style window surface with the first Prometeu OS visual
## Acceptance Criteria ## Acceptance Criteria
- [ ] Hub/Home renders full-screen in the 270p viewport. - [x] Hub/Home renders full-screen in the 270p viewport.
- [ ] Hub/Home shows exactly two manual launch buttons: `ShellA` and `ShellB`. - [x] Hub/Home shows exactly two manual launch buttons: `ShellA` and `ShellB`.
- [ ] The visual style is retro/minimalist with strong borders and limited palette. - [x] The visual style is retro/minimalist with strong borders and limited palette.
- [ ] `ShellA` renders as a simple green native fake Shell app/window. - [x] `ShellA` renders as a simple green native fake Shell app/window.
- [ ] `ShellB` renders as a simple blue native fake Shell app/window. - [x] `ShellB` renders as a simple blue native fake Shell app/window.
- [ ] Each Shell app view exposes a visible close command. - [x] Each Shell app view exposes a visible close command.
- [ ] Local primitives are not exported as a general toolkit. - [x] Local primitives are not exported as a general toolkit.
- [ ] No Tabs/List/Status bar/Footer/help bar are added in this wave. - [x] No Tabs/List/Status bar/Footer/help bar are added in this wave.
## Tests ## Tests