use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; use winit::application::ApplicationHandler; use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize}; use winit::event::{ElementState, KeyEvent, MouseButton, MouseScrollDelta, WindowEvent}; use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop}; use winit::keyboard::{Key, ModifiersState, NamedKey}; use winit::window::{Window, WindowAttributes, WindowId}; use crate::desktop_capture::DesktopCapture; use crate::viewport::ViewportHandle; const DEFAULT_LOGICAL: (u32, u32) = (1100, 700); const MIN_LOGICAL: (u32, u32) = (520, 380); /// winit application driver owning the desktop window and forwarding events into the viewport. pub struct ShellApp { window: Option>, handle: Option, modifiers: ModifiersState, last_cursor: PhysicalPosition, capture: Option, } impl Default for ShellApp { fn default() -> Self { Self { window: None, handle: None, modifiers: ModifiersState::empty(), last_cursor: PhysicalPosition::new(0.0, 0.0), capture: None, } } } /// boots the winit event loop and runs the desktop shell until the window closes. pub fn run() { let event_loop = EventLoop::new().expect("winit: create event loop"); event_loop.set_control_flow(ControlFlow::Wait); let mut app = ShellApp::default(); if let Err(e) = event_loop.run_app(&mut app) { eprintln!("yr_crystals shell exited with error: {e}"); std::process::exit(1); } } impl ApplicationHandler for ShellApp { fn resumed(&mut self, event_loop: &ActiveEventLoop) { if self.window.is_some() { return; } let attrs = WindowAttributes::default() .with_title("Yr Xtals") .with_inner_size(LogicalSize::new(DEFAULT_LOGICAL.0, DEFAULT_LOGICAL.1)) .with_min_inner_size(LogicalSize::new(MIN_LOGICAL.0, MIN_LOGICAL.1)); let window = event_loop .create_window(attrs) .expect("winit: create window"); let inner = window.inner_size(); let scale = window.scale_factor() as f32; let window = Box::new(window); let raw_window = window .window_handle() .expect("winit: window handle") .as_raw(); let raw_display = window .display_handle() .expect("winit: display handle") .as_raw(); let handle = ViewportHandle::new_from_raw( raw_window, raw_display, (inner.width as f32 / scale).max(1.0), (inner.height as f32 / scale).max(1.0), scale, ) .expect("yr_crystals: failed to build wgpu/iced viewport"); self.window = Some(window); self.handle = Some(handle); } fn window_event( &mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent, ) { let Some(window) = self.window.as_ref() else { return; }; let Some(handle) = self.handle.as_mut() else { return; }; let scale = window.scale_factor() as f32; match event { WindowEvent::CloseRequested => event_loop.exit(), WindowEvent::Resized(PhysicalSize { width, height }) => { let w = (width as f32 / scale).max(1.0); let h = (height as f32 / scale).max(1.0); handle.resize_px(w, h, scale); window.request_redraw(); } WindowEvent::ScaleFactorChanged { scale_factor, .. } => { let size = window.inner_size(); let s = scale_factor as f32; let w = (size.width as f32 / s).max(1.0); let h = (size.height as f32 / s).max(1.0); handle.resize_px(w, h, s); window.request_redraw(); } WindowEvent::CursorMoved { position, .. } => { self.last_cursor = position; handle.push_mouse_move(position.x as f32 / scale, position.y as f32 / scale); window.request_redraw(); } WindowEvent::CursorLeft { .. } => { handle.push_mouse_left(); window.request_redraw(); } WindowEvent::MouseInput { state, button, .. } => { let code = match button { MouseButton::Left => 0, MouseButton::Right => 1, MouseButton::Middle => 2, _ => return, }; let pressed = matches!(state, ElementState::Pressed); handle.push_mouse_button( self.last_cursor.x as f32 / scale, self.last_cursor.y as f32 / scale, code, pressed, ); window.request_redraw(); } WindowEvent::MouseWheel { delta, .. } => { let (dx, dy) = match delta { MouseScrollDelta::LineDelta(x, y) => (x * 20.0, y * 20.0), MouseScrollDelta::PixelDelta(p) => (p.x as f32, p.y as f32), }; handle.push_mouse_scroll( self.last_cursor.x as f32 / scale, self.last_cursor.y as f32 / scale, dx, dy, ); window.request_redraw(); } WindowEvent::ModifiersChanged(mods) => { self.modifiers = mods.state(); } WindowEvent::KeyboardInput { event, .. } => { let KeyEvent { logical_key, state, text, .. } = event; let pressed = matches!(state, ElementState::Pressed); let named = map_named_key(&logical_key); let utf8 = match (named, &logical_key, &text) { (0, Key::Character(s), _) => Some(s.to_string()), (0, _, Some(s)) => Some(s.to_string()), _ => None, }; let mods = encode_modifiers(self.modifiers); handle.push_key_event(named, utf8, mods, pressed); window.request_redraw(); } WindowEvent::RedrawRequested => handle.render_frame(), _ => {} } } /// switches between Poll and Wait control flow depending on active playback or loading. fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { let Some(window) = self.window.as_ref() else { return; }; let Some(handle) = self.handle.as_mut() else { return; }; match handle.take_pending_capture_action() { 1 => { if self.capture.is_none() { let pusher = handle.pcm_sender(); let device = handle.selected_input_device().map(|s| s.to_string()); match DesktopCapture::start_with_device(pusher, device.as_deref()) { Ok(cap) => self.capture = Some(cap), Err(e) => eprintln!("yr_crystals: capture start failed: {e}"), } } } 2 => self.capture = None, _ => {} } if handle.take_pending_rebuild_capture() && self.capture.is_some() { self.capture = None; let pusher = handle.pcm_sender(); let device = handle.selected_input_device().map(|s| s.to_string()); match DesktopCapture::start_with_device(pusher, device.as_deref()) { Ok(cap) => self.capture = Some(cap), Err(e) => eprintln!("yr_crystals: capture rebuild failed: {e}"), } } let playing = handle .state .engine .as_ref() .map(|e| e.is_playing()) .unwrap_or(false); let in_capture = handle.state.playback_mode == crate::ui::app::PlaybackMode::Capture; let animating = playing || handle.state.track_loading || handle.state.library_progress.is_some() || in_capture; if animating { window.request_redraw(); event_loop.set_control_flow(ControlFlow::Poll); } else { event_loop.set_control_flow(ControlFlow::Wait); } } } /// maps a winit named key into the viewport's small-integer key code. fn map_named_key(k: &Key) -> u32 { match k { Key::Named(NamedKey::Enter) => 1, Key::Named(NamedKey::Escape) => 2, Key::Named(NamedKey::Backspace) => 3, Key::Named(NamedKey::Tab) => 4, Key::Named(NamedKey::ArrowLeft) => 5, Key::Named(NamedKey::ArrowRight) => 6, Key::Named(NamedKey::ArrowUp) => 7, Key::Named(NamedKey::ArrowDown) => 8, Key::Named(NamedKey::Delete) => 9, Key::Named(NamedKey::Home) => 10, Key::Named(NamedKey::End) => 11, _ => 0, } } /// packs winit modifier state into the viewport's modifier bitfield. fn encode_modifiers(mods: ModifiersState) -> u32 { let mut out = 0; if mods.shift_key() { out |= 1; } if mods.control_key() { out |= 2; } if mods.alt_key() { out |= 4; } if mods.super_key() { out |= 8; } out }