implements PLN-0063 hub mouse click routing
This commit is contained in:
parent
edfac5a99b
commit
414bdd0333
@ -437,6 +437,22 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hub_home_click_launches_shell_a_as_native_shell_task_window() {
|
||||
let (mut firmware, mut hardware) = hub_home_firmware();
|
||||
let signals = InputSignals { f_signal: true, x_pos: 112, y_pos: 108, ..Default::default() };
|
||||
|
||||
firmware.tick(&signals, &mut hardware);
|
||||
|
||||
let task_id = match &firmware.state {
|
||||
FirmwareState::ShellRunning(step) => step.task_id,
|
||||
other => panic!("expected ShellRunning state, got {:?}", other),
|
||||
};
|
||||
|
||||
assert!(firmware.os.windows().focused_window_belongs_to_task(task_id));
|
||||
assert_eq!(firmware.os.windows().windows()[0].title, "ShellA");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hub_home_launches_shell_b_as_native_shell_task_window() {
|
||||
let (mut firmware, mut hardware) = hub_home_firmware();
|
||||
@ -474,6 +490,22 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shell_running_close_click_uses_lifecycle_and_returns_hub_home() {
|
||||
let (mut firmware, mut hardware, _signals, task_id) = load_shell_running_firmware();
|
||||
let close_signals =
|
||||
InputSignals { f_signal: true, x_pos: 420, y_pos: 24, ..Default::default() };
|
||||
|
||||
firmware.tick(&close_signals, &mut hardware);
|
||||
|
||||
assert!(matches!(firmware.state, FirmwareState::HubHome(_)));
|
||||
assert_eq!(firmware.os.lifecycle().task_state(task_id), Some(TaskState::Closed));
|
||||
assert_eq!(
|
||||
firmware.os.lifecycle().process_state_for_task(task_id),
|
||||
Ok(ProcessState::Stopped)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shell_running_closes_task_and_returns_hub_home_without_focused_window() {
|
||||
let (mut firmware, mut hardware, signals, task_id) = load_shell_running_firmware();
|
||||
|
||||
@ -1,8 +1,13 @@
|
||||
use crate::{CrashReport, SystemOS};
|
||||
use prometeu_hal::color::Color;
|
||||
use prometeu_hal::primitives::Rect;
|
||||
use prometeu_hal::{HardwareBridge, InputSignals};
|
||||
use prometeu_vm::VirtualMachine;
|
||||
|
||||
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 CLOSE_BUTTON: Rect = Rect { x: 420, y: 24, w: 16, h: 16 };
|
||||
|
||||
/// PrometeuHub: Launcher and system UI environment.
|
||||
pub struct PrometeuHub;
|
||||
|
||||
@ -63,14 +68,21 @@ impl PrometeuHub {
|
||||
|
||||
pub fn gui_update(
|
||||
&mut self,
|
||||
_os: &mut SystemOS,
|
||||
os: &mut SystemOS,
|
||||
hw: &mut dyn HardwareBridge,
|
||||
) -> Option<SystemProfileAction> {
|
||||
hw.gfx_mut().clear(Color::BLACK);
|
||||
|
||||
if hw.pad().a().pressed {
|
||||
let in_shell = os.windows().focused_window().is_some();
|
||||
if let Some(action) =
|
||||
action_for_click(in_shell, hw.touch().x(), hw.touch().y(), hw.touch().f().pressed)
|
||||
{
|
||||
return Some(action);
|
||||
}
|
||||
|
||||
if !in_shell && hw.pad().a().pressed {
|
||||
Some(SystemProfileAction::LaunchNativeShell(NativeShellApp::ShellA))
|
||||
} else if hw.pad().b().pressed {
|
||||
} else if !in_shell && hw.pad().b().pressed {
|
||||
Some(SystemProfileAction::LaunchNativeShell(NativeShellApp::ShellB))
|
||||
} else {
|
||||
None
|
||||
@ -102,7 +114,7 @@ impl PrometeuHub {
|
||||
if os.windows().focused_window().is_some() {
|
||||
if hw.pad().start().down {
|
||||
action = Some(SystemProfileAction::CloseShell);
|
||||
} else {
|
||||
} else if action != Some(SystemProfileAction::CloseShell) {
|
||||
crash = os.vm().tick(vm, signals, hw);
|
||||
}
|
||||
}
|
||||
@ -113,3 +125,77 @@ impl PrometeuHub {
|
||||
SystemProfileUpdate { crash, action }
|
||||
}
|
||||
}
|
||||
|
||||
fn action_for_click(in_shell: bool, x: i32, y: i32, pressed: bool) -> Option<SystemProfileAction> {
|
||||
if !pressed {
|
||||
return None;
|
||||
}
|
||||
|
||||
if in_shell {
|
||||
return rect_contains(CLOSE_BUTTON, x, y).then_some(SystemProfileAction::CloseShell);
|
||||
}
|
||||
|
||||
if rect_contains(SHELL_A_BUTTON, x, y) {
|
||||
Some(SystemProfileAction::LaunchNativeShell(NativeShellApp::ShellA))
|
||||
} else if rect_contains(SHELL_B_BUTTON, x, y) {
|
||||
Some(SystemProfileAction::LaunchNativeShell(NativeShellApp::ShellB))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn shell_a_button_click_emits_launch_action() {
|
||||
assert_eq!(
|
||||
action_for_click(false, SHELL_A_BUTTON.x, SHELL_A_BUTTON.y, true),
|
||||
Some(SystemProfileAction::LaunchNativeShell(NativeShellApp::ShellA))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shell_b_button_click_emits_launch_action() {
|
||||
assert_eq!(
|
||||
action_for_click(
|
||||
false,
|
||||
SHELL_B_BUTTON.x + SHELL_B_BUTTON.w - 1,
|
||||
SHELL_B_BUTTON.y,
|
||||
true
|
||||
),
|
||||
Some(SystemProfileAction::LaunchNativeShell(NativeShellApp::ShellB))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn button_hit_test_excludes_right_and_bottom_edges() {
|
||||
assert_eq!(
|
||||
action_for_click(false, SHELL_A_BUTTON.x + SHELL_A_BUTTON.w, SHELL_A_BUTTON.y, true),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
action_for_click(false, SHELL_A_BUTTON.x, SHELL_A_BUTTON.y + SHELL_A_BUTTON.h, true),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn held_pointer_does_not_emit_click_action() {
|
||||
assert_eq!(action_for_click(false, SHELL_A_BUTTON.x, SHELL_A_BUTTON.y, false), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn close_button_click_emits_close_only_in_shell() {
|
||||
assert_eq!(
|
||||
action_for_click(true, CLOSE_BUTTON.x, CLOSE_BUTTON.y, true),
|
||||
Some(SystemProfileAction::CloseShell)
|
||||
);
|
||||
assert_eq!(action_for_click(false, CLOSE_BUTTON.x, CLOSE_BUTTON.y, true), None);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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-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-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":"open","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":"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":[]}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
id: PLN-0063
|
||||
ticket: prometeu-hub-ui-direction
|
||||
title: Hub Mouse Input and Click Routing
|
||||
status: open
|
||||
status: done
|
||||
created: 2026-05-15
|
||||
ref_decisions: [DEC-0028]
|
||||
tags: [hub, ui, shell, system-apps, lifecycle, design-system]
|
||||
@ -67,12 +67,12 @@ Provide reliable 270p mouse hit testing and click routing for the initial Hub UI
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] Pointer coordinates are interpreted in the internal 270p coordinate space.
|
||||
- [ ] Clicking the `ShellA` button emits the `ShellA` launch action exactly once per click.
|
||||
- [ ] Clicking the `ShellB` button emits the `ShellB` launch action exactly once per click.
|
||||
- [ ] Clicking the close command emits close intent for the active Shell app.
|
||||
- [ ] Holding the mouse button does not repeatedly launch apps.
|
||||
- [ ] Existing controller/game input paths are not regressed.
|
||||
- [x] Pointer coordinates are interpreted in the internal 270p coordinate space.
|
||||
- [x] Clicking the `ShellA` button emits the `ShellA` launch action exactly once per click.
|
||||
- [x] Clicking the `ShellB` button emits the `ShellB` launch action exactly once per click.
|
||||
- [x] Clicking the close command emits close intent for the active Shell app.
|
||||
- [x] Holding the mouse button does not repeatedly launch apps.
|
||||
- [x] Existing controller/game input paths are not regressed.
|
||||
|
||||
## Tests
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user