YrXtals/src/shell.rs

272 lines
9.4 KiB
Rust

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<Box<Window>>,
handle: Option<ViewportHandle>,
modifiers: ModifiersState,
last_cursor: PhysicalPosition<f64>,
capture: Option<DesktopCapture>,
}
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
}