use std::collections::HashMap; use std::ops::Deref; use std::sync::Arc; use ctx::{Renderer, Vertex}; use tobj::LoadOptions; use tracing::{debug, error, info, trace, warn}; use wgpu::rwh::HasWindowHandle; use winit::application::ApplicationHandler; use winit::event::{KeyEvent, WindowEvent}; use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop}; use winit::window::Window; use winit::window::WindowId; pub mod ctx; struct WindowContext<'window> { window: Arc, ctx: Renderer<'window>, main_window: bool, } impl Deref for WindowContext<'_> { type Target = winit::window::Window; fn deref(&self) -> &Self::Target { self.window.as_ref() } } impl WindowContext<'_> { pub fn is_main_window(&self) -> bool { self.main_window } } #[derive(Default)] pub struct App<'window> { windows: HashMap>, } static CUBE_VERTICES: &[Vertex] = &[ Vertex { position: [-0.5, -0.5, 0.5], normal: [0.0, 0.0, 1.0], }, Vertex { position: [0.5, -0.5, 0.5], normal: [0.0, 0.0, 1.0], }, Vertex { position: [0.5, 0.5, 0.5], normal: [0.0, 0.0, 1.0], }, Vertex { position: [0.5, 0.5, 0.5], normal: [0.0, 0.0, 1.0], }, Vertex { position: [-0.5, 0.5, 0.5], normal: [0.0, 0.0, 1.0], }, Vertex { position: [-0.5, -0.5, 0.5], normal: [0.0, 0.0, 1.0], }, Vertex { position: [0.5, -0.5, -0.5], normal: [0.0, 0.0, -1.0], }, Vertex { position: [-0.5, -0.5, -0.5], normal: [0.0, 0.0, -1.0], }, Vertex { position: [-0.5, 0.5, -0.5], normal: [0.0, 0.0, -1.0], }, Vertex { position: [-0.5, 0.5, -0.5], normal: [0.0, 0.0, -1.0], }, Vertex { position: [0.5, 0.5, -0.5], normal: [0.0, 0.0, -1.0], }, Vertex { position: [0.5, -0.5, -0.5], normal: [0.0, 0.0, -1.0], }, Vertex { position: [0.5, -0.5, 0.5], normal: [1.0, 0.0, 0.0], }, Vertex { position: [0.5, -0.5, -0.5], normal: [1.0, 0.0, 0.0], }, Vertex { position: [0.5, 0.5, -0.5], normal: [1.0, 0.0, 0.0], }, Vertex { position: [0.5, 0.5, -0.5], normal: [1.0, 0.0, 0.0], }, Vertex { position: [0.5, 0.5, 0.5], normal: [1.0, 0.0, 0.0], }, Vertex { position: [0.5, -0.5, 0.5], normal: [1.0, 0.0, 0.0], }, Vertex { position: [-0.5, -0.5, -0.5], normal: [-1.0, 0.0, 0.0], }, Vertex { position: [-0.5, -0.5, 0.5], normal: [-1.0, 0.0, 0.0], }, Vertex { position: [-0.5, 0.5, 0.5], normal: [-1.0, 0.0, 0.0], }, Vertex { position: [-0.5, 0.5, 0.5], normal: [-1.0, 0.0, 0.0], }, Vertex { position: [-0.5, 0.5, -0.5], normal: [-1.0, 0.0, 0.0], }, Vertex { position: [-0.5, -0.5, -0.5], normal: [-1.0, 0.0, 0.0], }, Vertex { position: [-0.5, 0.5, 0.5], normal: [0.0, 1.0, 0.0], }, Vertex { position: [0.5, 0.5, 0.5], normal: [0.0, 1.0, 0.0], }, Vertex { position: [0.5, 0.5, -0.5], normal: [0.0, 1.0, 0.0], }, Vertex { position: [0.5, 0.5, -0.5], normal: [0.0, 1.0, 0.0], }, Vertex { position: [-0.5, 0.5, -0.5], normal: [0.0, 1.0, 0.0], }, Vertex { position: [-0.5, 0.5, 0.5], normal: [0.0, 1.0, 0.0], }, Vertex { position: [-0.5, -0.5, -0.5], normal: [0.0, -1.0, 0.0], }, Vertex { position: [0.5, -0.5, -0.5], normal: [0.0, -1.0, 0.0], }, Vertex { position: [0.5, -0.5, 0.5], normal: [0.0, -1.0, 0.0], }, Vertex { position: [0.5, -0.5, 0.5], normal: [0.0, -1.0, 0.0], }, Vertex { position: [-0.5, -0.5, 0.5], normal: [0.0, -1.0, 0.0], }, Vertex { position: [-0.5, -0.5, -0.5], normal: [0.0, -1.0, 0.0], }, ]; const CUBE_INDICES: &[u32] = &[ 0, 1, 2, 2, 3, 0, 4, 5, 6, 6, 7, 4, 8, 9, 10, 10, 11, 8, 12, 13, 14, 14, 15, 12, 16, 17, 18, 18, 19, 16, 20, 21, 22, 22, 23, 20, ]; impl App<'_> { fn create_main_window(&mut self, event_loop: &ActiveEventLoop) { let win_attr = Window::default_attributes().with_title("Zenyx"); match event_loop.create_window(win_attr) { Ok(window) => { let window = Arc::new(window); let window_id = window.id(); match Renderer::new_blocking(window.clone()) { Ok(mut wgpu_ctx) => { let obj = match tobj::load_obj( "test.obj", &LoadOptions { triangulate: true, single_index: true, ..Default::default() }, ) { Ok(obj) => obj, Err(e) => { error!("{e}"); panic!() } }; let mesh = obj.0.get(0).unwrap().mesh.clone(); let vertices: Vec = (0..mesh.positions.len() / 3) .map(|i| Vertex { position: [ mesh.positions[i * 3], mesh.positions[i * 3 + 1], mesh.positions[i * 3 + 2], ], normal: if !mesh.normals.is_empty() { [ mesh.normals[i * 3], mesh.normals[i * 3 + 1], mesh.normals[i * 3 + 2], ] } else { [0.0; 3] }, }) .collect(); wgpu_ctx.add_model(&vertices, &mesh.indices); // wgpu_ctx.add_model(CUBE_VERTICES,CUBE_INDICES); self.windows.insert( window_id, WindowContext { window, ctx: wgpu_ctx, main_window: true, }, ); info!("Main window created: {:?}", window_id); } Err(e) => error!("Failed to create WGPU context: {:?}", e), } } Err(e) => error!("Failed to create main window: {:?}", e), } } fn handle_close_requested(&mut self, window_id: WindowId) { if self.windows.remove(&window_id).is_some() { debug!("Window {:?} closed", window_id); } else { warn!("Tried to close non-existent window {:?}", window_id); } } fn handle_keyboard_input( &mut self, event_loop: &ActiveEventLoop, window_id: WindowId, key_event: KeyEvent, ) { if !key_event.state.is_pressed() { return; } match key_event.physical_key { winit::keyboard::PhysicalKey::Code(code) => match code { winit::keyboard::KeyCode::Space => { self.toggle_background(window_id); } winit::keyboard::KeyCode::Escape => { self.spawn_child_window(event_loop); } other => error!("Unimplemented keycode: {:?}", other), }, _ => debug!("Received a keyboard event with no physical key"), } } fn toggle_background(&mut self, window_id: WindowId) { if let Some(window_context) = self.windows.get_mut(&window_id) { let current_color = window_context.ctx.bg_color(); let new_color = match current_color { wgpu::Color::WHITE => wgpu::Color::BLACK, wgpu::Color::BLACK => wgpu::Color::WHITE, _ => wgpu::Color::WHITE, }; window_context.ctx.set_bg_color(new_color); debug!("Toggled background color for window {:?}", window_id); } else { warn!("No window context for toggling background: {:?}", window_id); } } fn spawn_child_window(&mut self, event_loop: &ActiveEventLoop) { if let Some(main_ctx) = self.windows.values().find(|ctx| ctx.is_main_window()) { let title = format!("Zenyx - New Window {}", self.windows.len()); // TODO: Verify that this is safe instead of matching on it let win_attr = unsafe { let base = Window::default_attributes().with_title(title); match main_ctx.window_handle() { Ok(handle) => base.with_parent_window(Some(handle.as_raw())), Err(_) => base, } }; match event_loop.create_window(win_attr) { Ok(window) => { let window = Arc::new(window); let window_id = window.id(); match Renderer::new_blocking(window.clone()) { Ok(mut wgpu_ctx) => { wgpu_ctx.add_model(CUBE_VERTICES,CUBE_INDICES); self.windows.insert( window_id, WindowContext { window, ctx: wgpu_ctx, main_window: false, }, ); debug!("Spawned new child window: {:?}", window_id); } Err(e) => error!("Failed to create WGPU context for child window: {:?}", e), } } Err(e) => error!("Failed to create child window: {:?}", e), } } else { error!("No main window found. Cannot spawn a child window."); } } fn handle_redraw_requested(&mut self, window_id: WindowId) { if let Some(window_context) = self.windows.get_mut(&window_id) { window_context.ctx.draw(); window_context.request_redraw(); trace!( "Redrew window {:?} with title: {}", window_id, window_context.window.title() ); } else { warn!("Received redraw for unknown window {:?}", window_id); } } fn handle_resize(&mut self, window_id: WindowId, new_size: winit::dpi::PhysicalSize) { if let Some(window_context) = self.windows.get_mut(&window_id) { window_context.ctx.resize(new_size.into()); window_context.window.request_redraw(); debug!( "Resized window {:?} to {}x{}", window_id, new_size.width, new_size.height ); } else { warn!("Received resize for unknown window {:?}", window_id); } } fn handle_destroyed(&mut self, event_loop: &ActiveEventLoop) { if self.windows.is_empty() || !self.windows.iter().any(|(_, ctx)| ctx.is_main_window()) { self.windows.clear(); debug!("All main windows are closed. Exiting event loop."); event_loop.exit(); } } } impl ApplicationHandler for App<'_> { fn resumed(&mut self, event_loop: &ActiveEventLoop) { if self.windows.is_empty() { self.create_main_window(event_loop); } } fn window_event( &mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent, ) { match event { WindowEvent::CloseRequested => { self.handle_close_requested(window_id); } WindowEvent::KeyboardInput { event: key_event, .. } => { self.handle_keyboard_input(event_loop, window_id, key_event); } WindowEvent::RedrawRequested => { self.handle_redraw_requested(window_id); } WindowEvent::Resized(new_size) => { self.handle_resize(window_id, new_size); } WindowEvent::Destroyed => { self.handle_destroyed(event_loop); } _ => trace!("Unhandled window event for window {:?}", window_id), } } } pub fn init_renderer(event_loop: EventLoop<()>) { event_loop.set_control_flow(ControlFlow::Wait); let mut app = App::default(); if let Err(e) = event_loop.run_app(&mut app) { error!("Failed to run application: {:?}", e); } }