add window manager system

This commit is contained in:
Nilton Constantino 2026-01-16 18:46:38 +00:00
parent e519408f51
commit a8119d71f0
No known key found for this signature in database
11 changed files with 201 additions and 63 deletions

View File

@ -1,4 +1,4 @@
use crate::firmware::firmware_state::{FirmwareState, LoadGameStep, ResetStep}; use crate::firmware::firmware_state::{FirmwareState, LoadCartridgeStep, ResetStep};
use crate::firmware::prometeu_context::PrometeuContext; use crate::firmware::prometeu_context::PrometeuContext;
use crate::hardware::{HardwareBridge, InputSignals}; use crate::hardware::{HardwareBridge, InputSignals};
use crate::model::Cartridge; use crate::model::Cartridge;
@ -26,6 +26,10 @@ impl Firmware {
} }
pub fn step_frame(&mut self, signals: &InputSignals, hw: &mut dyn HardwareBridge) { pub fn step_frame(&mut self, signals: &InputSignals, hw: &mut dyn HardwareBridge) {
// Atualiza input uma vez por frame de host
hw.pad_mut().begin_frame(signals);
hw.touch_mut().begin_frame(signals);
if !self.state_initialized { if !self.state_initialized {
self.on_enter(signals, hw); self.on_enter(signals, hw);
self.state_initialized = true; self.state_initialized = true;
@ -59,7 +63,7 @@ impl Firmware {
FirmwareState::SplashScreen(s) => s.on_enter(&mut req), FirmwareState::SplashScreen(s) => s.on_enter(&mut req),
FirmwareState::LaunchHub(s) => s.on_enter(&mut req), FirmwareState::LaunchHub(s) => s.on_enter(&mut req),
FirmwareState::HubHome(s) => s.on_enter(&mut req), FirmwareState::HubHome(s) => s.on_enter(&mut req),
FirmwareState::LoadGame(s) => s.on_enter(&mut req), FirmwareState::LoadCartridge(s) => s.on_enter(&mut req),
FirmwareState::GameRunning(s) => s.on_enter(&mut req), FirmwareState::GameRunning(s) => s.on_enter(&mut req),
FirmwareState::AppCrashes(s) => s.on_enter(&mut req), FirmwareState::AppCrashes(s) => s.on_enter(&mut req),
} }
@ -78,7 +82,7 @@ impl Firmware {
FirmwareState::SplashScreen(s) => s.on_update(&mut req), FirmwareState::SplashScreen(s) => s.on_update(&mut req),
FirmwareState::LaunchHub(s) => s.on_update(&mut req), FirmwareState::LaunchHub(s) => s.on_update(&mut req),
FirmwareState::HubHome(s) => s.on_update(&mut req), FirmwareState::HubHome(s) => s.on_update(&mut req),
FirmwareState::LoadGame(s) => s.on_update(&mut req), FirmwareState::LoadCartridge(s) => s.on_update(&mut req),
FirmwareState::GameRunning(s) => s.on_update(&mut req), FirmwareState::GameRunning(s) => s.on_update(&mut req),
FirmwareState::AppCrashes(s) => s.on_update(&mut req), FirmwareState::AppCrashes(s) => s.on_update(&mut req),
} }
@ -97,14 +101,14 @@ impl Firmware {
FirmwareState::SplashScreen(s) => s.on_exit(&mut req), FirmwareState::SplashScreen(s) => s.on_exit(&mut req),
FirmwareState::LaunchHub(s) => s.on_exit(&mut req), FirmwareState::LaunchHub(s) => s.on_exit(&mut req),
FirmwareState::HubHome(s) => s.on_exit(&mut req), FirmwareState::HubHome(s) => s.on_exit(&mut req),
FirmwareState::LoadGame(s) => s.on_exit(&mut req), FirmwareState::LoadCartridge(s) => s.on_exit(&mut req),
FirmwareState::GameRunning(s) => s.on_exit(&mut req), FirmwareState::GameRunning(s) => s.on_exit(&mut req),
FirmwareState::AppCrashes(s) => s.on_exit(&mut req), FirmwareState::AppCrashes(s) => s.on_exit(&mut req),
} }
} }
pub fn load_cartridge(&mut self, cartridge: Cartridge) { pub fn load_cartridge(&mut self, cartridge: Cartridge) {
self.state = FirmwareState::LoadGame(LoadGameStep { cartridge }); self.state = FirmwareState::LoadCartridge(LoadCartridgeStep { cartridge });
self.state_initialized = false; self.state_initialized = false;
} }
} }

View File

@ -2,7 +2,7 @@ pub use crate::firmware::firmware_step_reset::ResetStep;
pub use crate::firmware::firmware_step_splash_screen::SplashScreenStep; pub use crate::firmware::firmware_step_splash_screen::SplashScreenStep;
pub use crate::firmware::firmware_step_launch_hub::LaunchHubStep; pub use crate::firmware::firmware_step_launch_hub::LaunchHubStep;
pub use crate::firmware::firmware_step_hub_home::HubHomeStep; pub use crate::firmware::firmware_step_hub_home::HubHomeStep;
pub use crate::firmware::firmware_step_load_game::LoadGameStep; pub use crate::firmware::firmware_step_load_cartridge::LoadCartridgeStep;
pub use crate::firmware::firmware_step_game_running::GameRunningStep; pub use crate::firmware::firmware_step_game_running::GameRunningStep;
pub use crate::firmware::firmware_step_crash_screen::AppCrashesStep; pub use crate::firmware::firmware_step_crash_screen::AppCrashesStep;
@ -12,7 +12,7 @@ pub enum FirmwareState {
SplashScreen(SplashScreenStep), SplashScreen(SplashScreenStep),
LaunchHub(LaunchHubStep), LaunchHub(LaunchHubStep),
HubHome(HubHomeStep), HubHome(HubHomeStep),
LoadGame(LoadGameStep), LoadCartridge(LoadCartridgeStep),
GameRunning(GameRunningStep), GameRunning(GameRunningStep),
AppCrashes(AppCrashesStep), AppCrashes(AppCrashesStep),
} }

View File

@ -8,7 +8,13 @@ impl GameRunningStep {
pub fn on_enter(&mut self, _ctx: &mut PrometeuContext) {} pub fn on_enter(&mut self, _ctx: &mut PrometeuContext) {}
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> { pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
ctx.os.step_frame(ctx.vm, ctx.signals, ctx.hw).map(|err| FirmwareState::AppCrashes(AppCrashesStep { error: err })) let result = ctx.os.step_frame(ctx.vm, ctx.signals, ctx.hw);
if !ctx.os.logical_frame_active {
ctx.hw.gfx_mut().present();
}
result.map(|err| FirmwareState::AppCrashes(AppCrashesStep { error: err }))
} }
pub fn on_exit(&mut self, _ctx: &mut PrometeuContext) {} pub fn on_exit(&mut self, _ctx: &mut PrometeuContext) {}

View File

@ -1,4 +1,4 @@
use crate::firmware::firmware_state::FirmwareState; use crate::firmware::firmware_state::{AppCrashesStep, FirmwareState};
use crate::firmware::prometeu_context::PrometeuContext; use crate::firmware::prometeu_context::PrometeuContext;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -8,11 +8,29 @@ impl HubHomeStep {
pub fn on_enter(&mut self, _ctx: &mut PrometeuContext) {} pub fn on_enter(&mut self, _ctx: &mut PrometeuContext) {}
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> { pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
// req.hw.gfx_mut().clear(Color::BLACK); let mut error = None;
ctx.hw.pad_mut().begin_frame(ctx.signals);
// Sempre atualiza a GUI do Hub (limpa a tela e processa input se não houver foco)
ctx.hub.gui_update(ctx.os, ctx.hw); ctx.hub.gui_update(ctx.os, ctx.hw);
if let Some(focused_id) = ctx.hub.window_manager.focused {
if ctx.hw.pad().start.down {
ctx.hub.window_manager.remove_window(focused_id);
} else {
// System App roda aqui, desenhando sobre o fundo do Hub
error = ctx.os.step_frame(ctx.vm, ctx.signals, ctx.hw);
}
}
// Renderiza as bordas das janelas do System App
ctx.hub.render(ctx.os, ctx.hw);
ctx.hw.gfx_mut().present(); ctx.hw.gfx_mut().present();
if let Some(err) = error {
return Some(FirmwareState::AppCrashes(AppCrashesStep { error: err }));
}
None None
} }

View File

@ -0,0 +1,30 @@
use crate::firmware::firmware_state::{FirmwareState, GameRunningStep, HubHomeStep};
use crate::firmware::prometeu_context::PrometeuContext;
use crate::model::{AppMode, Cartridge, Color, Rect};
#[derive(Debug, Clone)]
pub struct LoadCartridgeStep {
pub cartridge: Cartridge,
}
impl LoadCartridgeStep {
pub fn on_enter(&mut self, ctx: &mut PrometeuContext) {
ctx.os.initialize_vm(ctx.vm, &self.cartridge);
}
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
if self.cartridge.header.mode == AppMode::System {
let id = ctx.hub.window_manager.add_window(
self.cartridge.header.title.clone(),
Rect { x: 40, y: 20, w: 240, h: 140 },
Color::WHITE
);
ctx.hub.window_manager.set_focus(id);
return Some(FirmwareState::HubHome(HubHomeStep));
}
Some(FirmwareState::GameRunning(GameRunningStep))
}
pub fn on_exit(&mut self, _ctx: &mut PrometeuContext) {}
}

View File

@ -1,20 +0,0 @@
use crate::firmware::firmware_state::{FirmwareState, GameRunningStep};
use crate::firmware::prometeu_context::PrometeuContext;
use crate::model::Cartridge;
#[derive(Debug, Clone)]
pub struct LoadGameStep {
pub cartridge: Cartridge,
}
impl LoadGameStep {
pub fn on_enter(&mut self, ctx: &mut PrometeuContext) {
ctx.os.initialize_vm(ctx.vm, &self.cartridge);
}
pub fn on_update(&mut self, _ctx: &mut PrometeuContext) -> Option<FirmwareState> {
Some(FirmwareState::GameRunning(GameRunningStep))
}
pub fn on_exit(&mut self, _ctx: &mut PrometeuContext) {}
}

View File

@ -5,7 +5,7 @@ pub(crate) mod firmware_step_reset;
pub(crate) mod firmware_step_splash_screen; pub(crate) mod firmware_step_splash_screen;
pub(crate) mod firmware_step_launch_hub; pub(crate) mod firmware_step_launch_hub;
pub(crate) mod firmware_step_hub_home; pub(crate) mod firmware_step_hub_home;
pub(crate) mod firmware_step_load_game; pub(crate) mod firmware_step_load_cartridge;
pub(crate) mod firmware_step_game_running; pub(crate) mod firmware_step_game_running;
pub(crate) mod firmware_step_crash_screen; pub(crate) mod firmware_step_crash_screen;
mod prometeu_context; mod prometeu_context;

View File

@ -6,6 +6,7 @@ mod tile_bank;
mod sprite; mod sprite;
mod sample; mod sample;
mod cartridge; mod cartridge;
mod window;
pub use button::Button; pub use button::Button;
pub use cartridge::{AppHeader, AppMode, Cartridge}; pub use cartridge::{AppHeader, AppMode, Cartridge};
@ -15,3 +16,4 @@ pub use sprite::Sprite;
pub use tile::Tile; pub use tile::Tile;
pub use tile_bank::{TileBank, TileSize}; pub use tile_bank::{TileBank, TileSize};
pub use tile_layer::{HudTileLayer, ScrollableTileLayer, TileMap}; pub use tile_layer::{HudTileLayer, ScrollableTileLayer, TileMap};
pub use window::{Rect, Window, WindowId};

View File

@ -0,0 +1,21 @@
use crate::model::Color;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Rect {
pub x: i32,
pub y: i32,
pub w: i32,
pub h: i32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct WindowId(pub u32);
#[derive(Debug, Clone)]
pub struct Window {
pub id: WindowId,
pub viewport: Rect,
pub has_focus: bool,
pub title: String,
pub color: Color,
}

View File

@ -1,32 +1,63 @@
use crate::hardware::HardwareBridge; use crate::hardware::HardwareBridge;
use crate::model::Color; use crate::model::{Color, Rect, Window, WindowId};
use crate::prometeu_os::PrometeuOS; use crate::prometeu_os::PrometeuOS;
// TODO: Mover para arquivo próprio se crescer. /// Gerenciador de janelas do PROMETEU.
/// Sistema de janelas do PROMETEU. pub struct WindowManager {
pub struct WindowSystem { pub windows: Vec<Window>,
pub theme_color: Color, pub focused: Option<WindowId>,
} }
impl WindowSystem { impl WindowManager {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
theme_color: Color::INDIGO, windows: Vec::new(),
focused: None,
}
}
pub fn add_window(&mut self, title: String, viewport: Rect, color: Color) -> WindowId {
let id = WindowId(self.windows.len() as u32);
let window = Window {
id,
viewport,
has_focus: false,
title,
color,
};
self.windows.push(window);
id
}
pub fn remove_window(&mut self, id: WindowId) {
self.windows.retain(|w| w.id != id);
if self.focused == Some(id) {
self.focused = None;
}
}
pub fn remove_all_windows(&mut self) {
self.windows.clear();
self.focused = None;
}
pub fn set_focus(&mut self, id: WindowId) {
self.focused = Some(id);
for window in &mut self.windows {
window.has_focus = window.id == id;
} }
} }
} }
/// PrometeuHub: Launcher e ambiente de UI do sistema. /// PrometeuHub: Launcher e ambiente de UI do sistema.
pub struct PrometeuHub { pub struct PrometeuHub {
pub window_system: WindowSystem, pub window_manager: WindowManager,
pub color: Color,
} }
impl PrometeuHub { impl PrometeuHub {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
window_system: WindowSystem::new(), window_manager: WindowManager::new(),
color: Color::BLACK,
} }
} }
@ -35,22 +66,74 @@ impl PrometeuHub {
} }
pub fn gui_update(&mut self, _os: &mut PrometeuOS, hw: &mut dyn HardwareBridge) { pub fn gui_update(&mut self, _os: &mut PrometeuOS, hw: &mut dyn HardwareBridge) {
if hw.pad().a.down { hw.gfx_mut().clear(Color::BLACK);
self.color = Color::GREEN;
} let mut next_window = None;
if hw.pad().b.down {
self.color = Color::INDIGO; if hw.pad().a.pressed {
} next_window = Some(("Green Window".to_string(), Rect { x: 0, y: 0, w: 160, h: 90 }, Color::GREEN));
if hw.pad().x.down { } else if hw.pad().b.pressed {
self.color = Color::YELLOW; next_window = Some(("Indigo Window".to_string(), Rect { x: 160, y: 0, w: 160, h: 90 }, Color::INDIGO));
} } else if hw.pad().x.pressed {
if hw.pad().y.down { next_window = Some(("Yellow Window".to_string(), Rect { x: 0, y: 90, w: 160, h: 90 }, Color::YELLOW));
self.color = Color::RED; } else if hw.pad().y.pressed {
next_window = Some(("Red Window".to_string(), Rect { x: 160, y: 90, w: 160, h: 90 }, Color::RED));
} }
hw.gfx_mut().clear(self.color); if let Some((title, rect, color)) = next_window {
self.window_manager.remove_all_windows();
let id = self.window_manager.add_window(title, rect, color);
self.window_manager.set_focus(id);
}
} }
pub fn render(&mut self, _os: &mut PrometeuOS, _hw: &mut dyn HardwareBridge) { pub fn render(&mut self, _os: &mut PrometeuOS, hw: &mut dyn HardwareBridge) {
for window in &self.window_manager.windows {
hw.gfx_mut().fill_rect(
window.viewport.x,
window.viewport.y,
window.viewport.w,
window.viewport.h,
window.color,
);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::Rect;
#[test]
fn test_window_manager_focus() {
let mut wm = WindowManager::new();
let id1 = wm.add_window("Window 1".to_string(), Rect { x: 0, y: 0, w: 10, h: 10 }, Color::WHITE);
let id2 = wm.add_window("Window 2".to_string(), Rect { x: 10, y: 10, w: 10, h: 10 }, Color::WHITE);
assert_eq!(wm.windows.len(), 2);
assert_eq!(wm.focused, None);
wm.set_focus(id1);
assert_eq!(wm.focused, Some(id1));
assert!(wm.windows[0].has_focus);
assert!(!wm.windows[1].has_focus);
wm.set_focus(id2);
assert_eq!(wm.focused, Some(id2));
assert!(!wm.windows[0].has_focus);
assert!(wm.windows[1].has_focus);
}
#[test]
fn test_window_manager_remove_window() {
let mut wm = WindowManager::new();
let id = wm.add_window("Window".to_string(), Rect { x: 0, y: 0, w: 10, h: 10 }, Color::WHITE);
wm.set_focus(id);
assert_eq!(wm.focused, Some(id));
wm.remove_window(id);
assert_eq!(wm.windows.len(), 0);
assert_eq!(wm.focused, None);
} }
} }

View File

@ -79,7 +79,6 @@ impl PrometeuOS {
if run.reason == crate::virtual_machine::LogicalFrameEndingReason::FrameSync { if run.reason == crate::virtual_machine::LogicalFrameEndingReason::FrameSync {
hw.gfx_mut().render_all(); hw.gfx_mut().render_all();
self.end_logical_frame(hw);
self.logical_frame_index += 1; self.logical_frame_index += 1;
self.logical_frame_active = false; self.logical_frame_active = false;
self.logical_frame_remaining_cycles = 0; self.logical_frame_remaining_cycles = 0;
@ -95,15 +94,10 @@ impl PrometeuOS {
None None
} }
fn begin_logical_frame(&mut self, signals: &InputSignals, hw: &mut dyn HardwareBridge) { fn begin_logical_frame(&mut self, _signals: &InputSignals, hw: &mut dyn HardwareBridge) {
hw.audio_mut().clear_commands(); hw.audio_mut().clear_commands();
hw.touch_mut().begin_frame(signals);
hw.pad_mut().begin_frame(signals);
} }
fn end_logical_frame(&mut self, hw: &mut dyn HardwareBridge) {
hw.gfx_mut().present();
}
// Helper para syscalls // Helper para syscalls
pub fn get_color(&self, index: usize, hw: &mut dyn HardwareBridge) -> Color { pub fn get_color(&self, index: usize, hw: &mut dyn HardwareBridge) -> Color {