use std::collections::HashMap;
use crate::input;
use crate::window::DesktopWindow;
use blinc_platform::{
ControlFlow, Event, EventLoop, LifecycleEvent, PlatformError, Window, WindowConfig,
WindowEvent, WindowId,
};
use winit::application::ApplicationHandler;
use winit::event::{StartCause, WindowEvent as WinitWindowEvent};
use winit::event_loop::{ActiveEventLoop, EventLoop as WinitEventLoop, EventLoopProxy};
use winit::keyboard::ModifiersState;
use winit::window::WindowId as WinitWindowId;
#[derive(Debug)]
pub enum AppCommand {
Wake,
CreateWindow(WindowConfig),
CloseWindow(WindowId),
}
#[derive(Clone)]
pub struct WakeProxy {
proxy: EventLoopProxy<AppCommand>,
}
impl WakeProxy {
pub fn wake(&self) {
let _ = self.proxy.send_event(AppCommand::Wake);
}
pub fn create_window(&self, config: WindowConfig) {
let _ = self.proxy.send_event(AppCommand::CreateWindow(config));
}
pub fn close_window(&self, id: WindowId) {
let _ = self.proxy.send_event(AppCommand::CloseWindow(id));
}
}
pub struct DesktopEventLoop {
event_loop: WinitEventLoop<AppCommand>,
window_config: WindowConfig,
wake_proxy: WakeProxy,
}
impl DesktopEventLoop {
pub fn new(config: WindowConfig) -> Result<Self, PlatformError> {
let event_loop = WinitEventLoop::with_user_event()
.build()
.map_err(|e| PlatformError::EventLoop(e.to_string()))?;
let wake_proxy = WakeProxy {
proxy: event_loop.create_proxy(),
};
Ok(Self {
event_loop,
window_config: config,
wake_proxy,
})
}
pub fn wake_proxy(&self) -> WakeProxy {
self.wake_proxy.clone()
}
}
impl EventLoop for DesktopEventLoop {
type Window = DesktopWindow;
fn run<F>(self, handler: F) -> Result<(), PlatformError>
where
F: FnMut(Event, &Self::Window) -> ControlFlow + 'static,
{
let mut app = DesktopApp::new(self.window_config, handler);
self.event_loop
.run_app(&mut app)
.map_err(|e| PlatformError::EventLoop(e.to_string()))
}
}
fn to_window_id(winit_id: WinitWindowId) -> WindowId {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
winit_id.hash(&mut hasher);
WindowId(hasher.finish())
}
struct DesktopApp<F>
where
F: FnMut(Event, &DesktopWindow) -> ControlFlow,
{
window_config: WindowConfig,
windows: HashMap<WinitWindowId, DesktopWindow>,
primary_winit_id: Option<WinitWindowId>,
handler: F,
modifiers: ModifiersState,
mouse_position: (f32, f32),
should_exit: bool,
modal_window: Option<WinitWindowId>,
}
impl<F> DesktopApp<F>
where
F: FnMut(Event, &DesktopWindow) -> ControlFlow,
{
fn new(window_config: WindowConfig, handler: F) -> Self {
Self {
window_config,
windows: HashMap::new(),
primary_winit_id: None,
handler,
modifiers: ModifiersState::empty(),
mouse_position: (0.0, 0.0),
should_exit: false,
modal_window: None,
}
}
fn handle_event_for(&mut self, winit_id: WinitWindowId, event: Event) {
if let Some(window) = self.windows.get(&winit_id) {
let flow = (self.handler)(event, window);
if flow == ControlFlow::Exit {
self.should_exit = true;
}
}
}
fn handle_event(&mut self, event: Event) {
if let Some(primary_id) = self.primary_winit_id {
self.handle_event_for(primary_id, event);
}
}
fn create_window(
&mut self,
event_loop: &ActiveEventLoop,
config: &WindowConfig,
) -> Option<WinitWindowId> {
match DesktopWindow::new(event_loop, config) {
Ok(window) => {
let winit_id = window.winit_window().id();
self.windows.insert(winit_id, window);
Some(winit_id)
}
Err(e) => {
tracing::error!("Failed to create window: {}", e);
None
}
}
}
}
impl<F> ApplicationHandler<AppCommand> for DesktopApp<F>
where
F: FnMut(Event, &DesktopWindow) -> ControlFlow,
{
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
if self.primary_winit_id.is_none() {
let config = self.window_config.clone();
if let Some(winit_id) = self.create_window(event_loop, &config) {
self.primary_winit_id = Some(winit_id);
self.handle_event_for(winit_id, Event::Lifecycle(LifecycleEvent::Resumed));
} else {
event_loop.exit();
}
}
}
fn suspended(&mut self, _event_loop: &ActiveEventLoop) {
self.handle_event(Event::Lifecycle(LifecycleEvent::Suspended));
}
fn new_events(&mut self, _event_loop: &ActiveEventLoop, cause: StartCause) {
if matches!(cause, StartCause::WaitCancelled { .. } | StartCause::Poll) {
for window in self.windows.values() {
window.request_redraw();
}
}
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
winit_id: WinitWindowId,
event: WinitWindowEvent,
) {
if let Some(modal_id) = self.modal_window {
if winit_id != modal_id {
match &event {
WinitWindowEvent::KeyboardInput { .. }
| WinitWindowEvent::MouseInput { .. }
| WinitWindowEvent::CursorMoved { .. }
| WinitWindowEvent::MouseWheel { .. }
| WinitWindowEvent::Touch(_) => return,
_ => {}
}
}
}
let wid = to_window_id(winit_id);
match event {
WinitWindowEvent::CloseRequested => {
self.handle_event_for(winit_id, Event::Window(wid, WindowEvent::CloseRequested));
self.windows.remove(&winit_id);
if self.modal_window == Some(winit_id) {
self.modal_window = None;
}
if self.windows.is_empty() {
self.should_exit = true;
event_loop.exit();
}
}
WinitWindowEvent::Resized(size) => {
self.handle_event_for(
winit_id,
Event::Window(
wid,
WindowEvent::Resized {
width: size.width,
height: size.height,
},
),
);
}
WinitWindowEvent::Moved(pos) => {
self.handle_event_for(
winit_id,
Event::Window(wid, WindowEvent::Moved { x: pos.x, y: pos.y }),
);
}
WinitWindowEvent::Focused(focused) => {
if let Some(window) = self.windows.get(&winit_id) {
window.set_focused(focused);
}
self.handle_event_for(winit_id, Event::Window(wid, WindowEvent::Focused(focused)));
}
WinitWindowEvent::ScaleFactorChanged { scale_factor, .. } => {
self.handle_event_for(
winit_id,
Event::Window(wid, WindowEvent::ScaleFactorChanged { scale_factor }),
);
}
WinitWindowEvent::RedrawRequested => {
self.handle_event_for(winit_id, Event::Frame(wid));
if self.should_exit {
event_loop.exit();
}
}
WinitWindowEvent::ModifiersChanged(mods) => {
self.modifiers = mods.state();
}
WinitWindowEvent::KeyboardInput { event, .. } => {
let input_event =
input::convert_keyboard_event(&event.logical_key, event.state, self.modifiers);
self.handle_event_for(winit_id, Event::Input(wid, input_event));
if let Some(window) = self.windows.get(&winit_id) {
window.request_redraw();
}
}
WinitWindowEvent::CursorMoved { position, .. } => {
self.mouse_position = (position.x as f32, position.y as f32);
let input_event = input::mouse_moved(self.mouse_position.0, self.mouse_position.1);
self.handle_event_for(winit_id, Event::Input(wid, input_event));
}
WinitWindowEvent::MouseInput { state, button, .. } => {
let (x, y) = self.mouse_position;
let input_event = match state {
winit::event::ElementState::Pressed => input::mouse_pressed(button, x, y),
winit::event::ElementState::Released => input::mouse_released(button, x, y),
};
self.handle_event_for(winit_id, Event::Input(wid, input_event));
}
WinitWindowEvent::MouseWheel { delta, phase, .. } => {
let (dx, dy) = match delta {
winit::event::MouseScrollDelta::LineDelta(x, y) => (x, y),
winit::event::MouseScrollDelta::PixelDelta(pos) => {
(pos.x as f32 / 10.0, pos.y as f32 / 10.0)
}
};
let input_event = input::scroll_event(dx, dy, phase);
self.handle_event_for(winit_id, Event::Input(wid, input_event));
if matches!(
phase,
winit::event::TouchPhase::Ended | winit::event::TouchPhase::Cancelled
) {
self.handle_event_for(winit_id, Event::Input(wid, input::scroll_end_event()));
}
}
WinitWindowEvent::Touch(touch) => {
let input_event = input::convert_touch_event(&touch);
self.handle_event_for(winit_id, Event::Input(wid, input_event));
}
WinitWindowEvent::CursorEntered { .. } => {
self.handle_event_for(
winit_id,
Event::Input(
wid,
blinc_platform::InputEvent::Mouse(blinc_platform::MouseEvent::Entered),
),
);
}
WinitWindowEvent::CursorLeft { .. } => {
self.handle_event_for(
winit_id,
Event::Input(
wid,
blinc_platform::InputEvent::Mouse(blinc_platform::MouseEvent::Left),
),
);
}
WinitWindowEvent::DroppedFile(path) => {
self.handle_event_for(
winit_id,
Event::Window(wid, WindowEvent::DroppedFile { paths: vec![path] }),
);
}
WinitWindowEvent::HoveredFile(path) => {
self.handle_event_for(
winit_id,
Event::Window(wid, WindowEvent::DroppedFileHovered { paths: vec![path] }),
);
}
WinitWindowEvent::HoveredFileCancelled => {
self.handle_event_for(
winit_id,
Event::Window(wid, WindowEvent::DroppedFileCancelled),
);
}
WinitWindowEvent::Ime(ime_event) => {
match ime_event {
winit::event::Ime::Commit(text) => {
for c in text.chars() {
let input_event = blinc_platform::InputEvent::Keyboard(
blinc_platform::KeyboardEvent {
key: blinc_platform::Key::Char(c),
state: blinc_platform::KeyState::Pressed,
modifiers: blinc_platform::Modifiers::default(),
},
);
self.handle_event_for(winit_id, Event::Input(wid, input_event));
}
if let Some(window) = self.windows.get(&winit_id) {
window.request_redraw();
}
}
winit::event::Ime::Preedit(text, cursor) => {
let _ = (text, cursor);
}
winit::event::Ime::Enabled => {
tracing::debug!("IME enabled for window {:?}", winit_id);
}
winit::event::Ime::Disabled => {
tracing::debug!("IME disabled for window {:?}", winit_id);
}
}
}
WinitWindowEvent::PinchGesture { delta, phase, .. } => {
let scroll_phase = match phase {
winit::event::TouchPhase::Started => blinc_platform::ScrollPhase::Started,
winit::event::TouchPhase::Moved => blinc_platform::ScrollPhase::Moved,
winit::event::TouchPhase::Ended => blinc_platform::ScrollPhase::Ended,
winit::event::TouchPhase::Cancelled => blinc_platform::ScrollPhase::Ended,
};
self.handle_event_for(
winit_id,
Event::Input(
wid,
blinc_platform::InputEvent::Pinch {
scale: 1.0 + delta as f32,
phase: scroll_phase,
},
),
);
}
WinitWindowEvent::RotationGesture { delta, phase, .. } => {
let scroll_phase = match phase {
winit::event::TouchPhase::Started => blinc_platform::ScrollPhase::Started,
winit::event::TouchPhase::Moved => blinc_platform::ScrollPhase::Moved,
winit::event::TouchPhase::Ended => blinc_platform::ScrollPhase::Ended,
winit::event::TouchPhase::Cancelled => blinc_platform::ScrollPhase::Ended,
};
self.handle_event_for(
winit_id,
Event::Input(
wid,
blinc_platform::InputEvent::Rotation {
angle: delta.to_radians(),
phase: scroll_phase,
},
),
);
}
_ => {}
}
if self.should_exit {
event_loop.exit();
}
}
fn memory_warning(&mut self, _event_loop: &ActiveEventLoop) {
self.handle_event(Event::Lifecycle(LifecycleEvent::LowMemory));
}
fn user_event(&mut self, event_loop: &ActiveEventLoop, command: AppCommand) {
match command {
AppCommand::Wake => {
for window in self.windows.values() {
window.request_redraw();
}
}
AppCommand::CreateWindow(config) => {
let is_modal = config.modal;
if let Some(winit_id) = self.create_window(event_loop, &config) {
if is_modal {
self.modal_window = Some(winit_id);
}
let wid = to_window_id(winit_id);
self.handle_event_for(winit_id, Event::Lifecycle(LifecycleEvent::Resumed));
tracing::info!(
"Created new window {:?} (wid={:?}, modal={})",
winit_id,
wid,
is_modal
);
}
}
AppCommand::CloseWindow(wid) => {
let winit_id = self
.windows
.iter()
.find(|(_, w)| w.id() == wid)
.map(|(id, _)| *id);
if let Some(winit_id) = winit_id {
self.handle_event_for(
winit_id,
Event::Window(wid, WindowEvent::CloseRequested),
);
self.windows.remove(&winit_id);
if self.modal_window == Some(winit_id) {
self.modal_window = None;
}
if self.windows.is_empty() {
self.should_exit = true;
event_loop.exit();
}
}
}
}
}
}