use std::time::{Duration, Instant}; use pixels::{Pixels, SurfaceTexture}; use prometeu_core::peripherals::InputSignals; use prometeu_core::Machine; use winit::{ application::ApplicationHandler, dpi::LogicalSize, event::{ElementState, MouseButton, WindowEvent}, event_loop::{ActiveEventLoop, ControlFlow, EventLoop}, keyboard::{KeyCode, PhysicalKey}, window::{Window, WindowAttributes, WindowId}, }; fn main() -> Result<(), Box> { let event_loop = EventLoop::new()?; let mut app = PrometeuApp::new(); event_loop.run_app(&mut app)?; Ok(()) } struct PrometeuApp { window: Option<&'static Window>, pixels: Option>, machine: Machine, input_signals: InputSignals, frame_target_dt: Duration, next_frame: Instant, } impl PrometeuApp { fn new() -> Self { Self { window: None, pixels: None, machine: Machine::new(), input_signals: InputSignals::default(), frame_target_dt: Duration::from_nanos(1_000_000_000 / 60), next_frame: Instant::now(), } } fn window(&self) -> &'static Window { self.window.expect("window not created yet") } // fn pixels_mut(&mut self) -> &mut Pixels<'static> { // self.pixels.as_mut().expect("pixels not created yet") // } fn resize_surface(&mut self, width: u32, height: u32) { if let Some(p) = self.pixels.as_mut() { let _ = p.resize_surface(width, height); } } fn request_redraw(&self) { if let Some(w) = self.window.as_ref() { w.request_redraw(); } } } impl ApplicationHandler for PrometeuApp { fn resumed(&mut self, event_loop: &ActiveEventLoop) { let attrs = WindowAttributes::default() .with_title(format!("PROMETEU - host_desktop | GFX Mem: {:.2} KB | Frame: {}", 0.0, 0)) .with_inner_size(LogicalSize::new(960.0, 540.0)) .with_min_inner_size(LogicalSize::new(320.0, 180.0)); let window = event_loop.create_window(attrs).expect("failed to create window"); // 🔥 Leak: Window vira &'static Window (bootstrap) let window: &'static Window = Box::leak(Box::new(window)); self.window = Some(window); let size = window.inner_size(); let surface_texture = SurfaceTexture::new(size.width, size.height, window); let mut pixels = Pixels::new(Machine::W as u32, Machine::H as u32, surface_texture) .expect("failed to create Pixels"); pixels.frame_mut().fill(0); self.pixels = Some(pixels); self.next_frame = Instant::now(); event_loop.set_control_flow(ControlFlow::Poll); } fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) { match event { WindowEvent::CloseRequested => event_loop.exit(), WindowEvent::Resized(size) => { self.resize_surface(size.width, size.height); } WindowEvent::ScaleFactorChanged { .. } => { let size = self.window().inner_size(); self.resize_surface(size.width, size.height); } WindowEvent::RedrawRequested => { // Pegue o Pixels diretamente do campo (não via helper que pega &mut self inteiro) let pixels = self.pixels.as_mut().expect("pixels not initialized"); { // Borrow mutável do frame (dura só dentro deste bloco) let frame = pixels.frame_mut(); // Borrow imutável do core (campo diferente, ok) let src = self.machine.gfx.front_buffer(); draw_rgb565_to_rgba8(src, frame); } // <- frame borrow termina aqui if pixels.render().is_err() { event_loop.exit(); } } WindowEvent::KeyboardInput { event, .. } => { if let PhysicalKey::Code(code) = event.physical_key { let is_down = event.state == ElementState::Pressed; match code { KeyCode::ArrowUp => self.input_signals.up_signal = is_down, KeyCode::ArrowDown => self.input_signals.down_signal = is_down, KeyCode::ArrowLeft => self.input_signals.left_signal = is_down, KeyCode::ArrowRight => self.input_signals.right_signal = is_down, // A/B (troque depois como quiser) KeyCode::KeyA => self.input_signals.a_signal = is_down, KeyCode::KeyD => self.input_signals.d_signal = is_down, KeyCode::KeyW => self.input_signals.w_signal = is_down, KeyCode::KeyS => self.input_signals.s_signal = is_down, KeyCode::KeyQ => self.input_signals.q_signal = is_down, KeyCode::KeyE => self.input_signals.e_signal = is_down, KeyCode::KeyZ => self.input_signals.start_signal = is_down, KeyCode::ShiftLeft | KeyCode::ShiftRight => self.input_signals.select_signal = is_down, _ => {} } } } WindowEvent::CursorMoved { position, .. } => { let v = window_to_fb(position.x as f32, position.y as f32, self.window()); self.input_signals.x_pos = v.0; self.input_signals.y_pos = v.1; } WindowEvent::MouseInput { state, button, .. } => { if button == MouseButton::Left { match state { ElementState::Pressed => { self.input_signals.t_signal = true; } ElementState::Released => { self.input_signals.t_signal = false; } } } } _ => {} } } fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) { // Pacing simples a ~60Hz let now = Instant::now(); if now >= self.next_frame { self.next_frame += self.frame_target_dt; // executa 1 frame do PROMETEU self.machine.step_frame(&self.input_signals); // temp: atualiza o título da janela a cada 1 segundo (60 frames) if self.machine.frame_index % 60 == 0 { if let Some(window) = self.window { let usage_bytes = self.machine.gfx.memory_usage_bytes(); let kb = usage_bytes as f64 / 1024.0; let title = format!( "PROMETEU - host_desktop | GFX Mem: {:.2} KB | Frame: {}", kb, self.machine.frame_index); window.set_title(&title); } } // pede redraw self.request_redraw(); } else { // dorme pouco para não fritar CPU let sleep_for = self.next_frame - now; std::thread::sleep(sleep_for.min(Duration::from_millis(2))); } } } /// Mapeamento simples: window coords -> framebuffer coords (stretch). /// Depois podemos fazer letterbox/aspect-ratio correto. fn window_to_fb(wx: f32, wy: f32, window: &Window) -> (i32, i32) { let size = window.inner_size(); let fb_w = Machine::W as f32; let fb_h = Machine::H as f32; let x = (wx * fb_w / size.width as f32).floor() as i32; let y = (wy * fb_h / size.height as f32).floor() as i32; (x.clamp(0, Machine::W as i32 - 1), y.clamp(0, Machine::H as i32 - 1)) } /// Copia RGB565 (u16) -> RGBA8888 (u8[4]) para o frame do pixels. /// Formato do pixels: RGBA8. fn draw_rgb565_to_rgba8(src: &[u16], dst_rgba: &mut [u8]) { for (i, &px) in src.iter().enumerate() { let (r8, g8, b8) = rgb565_to_rgb888(px); let o = i * 4; dst_rgba[o] = r8; dst_rgba[o + 1] = g8; dst_rgba[o + 2] = b8; dst_rgba[o + 3] = 0xFF; } } /// Expande RGB565 para RGB888 (replicação de bits altos). #[inline(always)] fn rgb565_to_rgb888(px: u16) -> (u8, u8, u8) { let r5 = ((px >> 11) & 0x1F) as u8; let g6 = ((px >> 5) & 0x3F) as u8; let b5 = (px & 0x1F) as u8; let r8 = (r5 << 3) | (r5 >> 2); let g8 = (g6 << 2) | (g6 >> 4); let b8 = (b5 << 3) | (b5 >> 2); (r8, g8, b8) }