272 lines
9.4 KiB
Rust
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
|
|
}
|