diff options
author | 2024-05-23 13:29:45 +0200 | |
---|---|---|
committer | 2024-05-23 13:29:45 +0200 | |
commit | d8ba6b0673a33724a177f3a1ba59705527280142 (patch) | |
tree | 89482c8d1e3a03e00b3a8151abbb81e30ae5898c /winit/src | |
parent | 72ed8bcc8def9956e25f3720a3095fc96bb2eef0 (diff) | |
parent | 468794d918eb06c1dbebb33c32b10017ad335f05 (diff) | |
download | iced-d8ba6b0673a33724a177f3a1ba59705527280142.tar.gz iced-d8ba6b0673a33724a177f3a1ba59705527280142.tar.bz2 iced-d8ba6b0673a33724a177f3a1ba59705527280142.zip |
Merge branch 'master' into feat/text-macro
Diffstat (limited to 'winit/src')
-rw-r--r-- | winit/src/application.rs | 484 | ||||
-rw-r--r-- | winit/src/conversion.rs | 91 | ||||
-rw-r--r-- | winit/src/lib.rs | 8 | ||||
-rw-r--r-- | winit/src/multi_window.rs | 546 | ||||
-rw-r--r-- | winit/src/multi_window/window_manager.rs | 5 | ||||
-rw-r--r-- | winit/src/proxy.rs | 101 |
6 files changed, 794 insertions, 441 deletions
diff --git a/winit/src/application.rs b/winit/src/application.rs index 13d9282d..f7508b4c 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -13,6 +13,7 @@ use crate::core::window; use crate::core::{Color, Event, Point, Size, Theme}; use crate::futures::futures; use crate::futures::{Executor, Runtime, Subscription}; +use crate::graphics; use crate::graphics::compositor::{self, Compositor}; use crate::runtime::clipboard; use crate::runtime::program::Program; @@ -21,7 +22,9 @@ use crate::runtime::{Command, Debug}; use crate::{Clipboard, Error, Proxy, Settings}; use futures::channel::mpsc; +use futures::channel::oneshot; +use std::borrow::Cow; use std::mem::ManuallyDrop; use std::sync::Arc; @@ -128,9 +131,9 @@ pub fn default(theme: &Theme) -> Appearance { /// Runs an [`Application`] with an executor, compositor, and the provided /// settings. -pub async fn run<A, E, C>( +pub fn run<A, E, C>( settings: Settings<A::Flags>, - compositor_settings: C::Settings, + graphics_settings: graphics::Settings, ) -> Result<(), Error> where A: Application + 'static, @@ -140,21 +143,22 @@ where { use futures::task; use futures::Future; - use winit::event_loop::EventLoopBuilder; + use winit::event_loop::EventLoop; let mut debug = Debug::new(); debug.startup_started(); - let event_loop = EventLoopBuilder::with_user_event() + let event_loop = EventLoop::with_user_event() .build() .expect("Create event loop"); - let proxy = event_loop.create_proxy(); + + let (proxy, worker) = Proxy::new(event_loop.create_proxy()); let runtime = { - let proxy = Proxy::new(event_loop.create_proxy()); let executor = E::new().map_err(Error::ExecutorCreationFailed)?; + executor.spawn(worker); - Runtime::new(executor, proxy) + Runtime::new(executor, proxy.clone()) }; let (application, init_command) = { @@ -163,104 +167,282 @@ where runtime.enter(|| A::new(flags)) }; - #[cfg(target_arch = "wasm32")] - let target = settings.window.platform_specific.target.clone(); + let id = settings.id; + let title = application.title(); - let should_be_visible = settings.window.visible; - let exit_on_close_request = settings.window.exit_on_close_request; + let (boot_sender, boot_receiver) = oneshot::channel(); + let (event_sender, event_receiver) = mpsc::unbounded(); + let (control_sender, control_receiver) = mpsc::unbounded(); - let builder = conversion::window_settings( - settings.window, - &application.title(), - event_loop.primary_monitor(), - settings.id, - ) - .with_visible(false); + let instance = Box::pin(run_instance::<A, E, C>( + application, + runtime, + proxy, + debug, + boot_receiver, + event_receiver, + control_sender, + init_command, + settings.fonts, + )); - log::debug!("Window builder: {builder:#?}"); + let context = task::Context::from_waker(task::noop_waker_ref()); + + struct Runner<Message: 'static, F, C> { + instance: std::pin::Pin<Box<F>>, + context: task::Context<'static>, + boot: Option<BootConfig<C>>, + sender: mpsc::UnboundedSender<winit::event::Event<Message>>, + receiver: mpsc::UnboundedReceiver<winit::event_loop::ControlFlow>, + error: Option<Error>, + #[cfg(target_arch = "wasm32")] + is_booted: std::rc::Rc<std::cell::RefCell<bool>>, + #[cfg(target_arch = "wasm32")] + queued_events: Vec<winit::event::Event<Message>>, + } - let window = Arc::new( - builder - .build(&event_loop) - .map_err(Error::WindowCreationFailed)?, - ); + struct BootConfig<C> { + sender: oneshot::Sender<Boot<C>>, + id: Option<String>, + title: String, + window_settings: window::Settings, + graphics_settings: graphics::Settings, + } - #[cfg(target_arch = "wasm32")] + let runner = Runner { + instance, + context, + boot: Some(BootConfig { + sender: boot_sender, + id, + title, + window_settings: settings.window, + graphics_settings, + }), + sender: event_sender, + receiver: control_receiver, + error: None, + #[cfg(target_arch = "wasm32")] + is_booted: std::rc::Rc::new(std::cell::RefCell::new(false)), + #[cfg(target_arch = "wasm32")] + queued_events: Vec::new(), + }; + + impl<Message, F, C> winit::application::ApplicationHandler<Message> + for Runner<Message, F, C> + where + F: Future<Output = ()>, + C: Compositor + 'static, { - use winit::platform::web::WindowExtWebSys; + fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { + let Some(BootConfig { + sender, + id, + title, + window_settings, + graphics_settings, + }) = self.boot.take() + else { + return; + }; - let canvas = window.canvas().expect("Get window canvas"); - let _ = canvas.set_attribute( - "style", - "display: block; width: 100%; height: 100%", - ); + let should_be_visible = window_settings.visible; + let exit_on_close_request = window_settings.exit_on_close_request; + + #[cfg(target_arch = "wasm32")] + let target = window_settings.platform_specific.target.clone(); + + let window_attributes = conversion::window_attributes( + window_settings, + &title, + event_loop.primary_monitor(), + id, + ) + .with_visible(false); + + log::debug!("Window attributes: {window_attributes:#?}"); + + let window = match event_loop.create_window(window_attributes) { + Ok(window) => Arc::new(window), + Err(error) => { + self.error = Some(Error::WindowCreationFailed(error)); + event_loop.exit(); + return; + } + }; + + let finish_boot = { + let window = window.clone(); + + async move { + let compositor = + C::new(graphics_settings, window.clone()).await?; - let window = web_sys::window().unwrap(); - let document = window.document().unwrap(); - let body = document.body().unwrap(); - - let target = target.and_then(|target| { - body.query_selector(&format!("#{target}")) - .ok() - .unwrap_or(None) - }); - - match target { - Some(node) => { - let _ = node - .replace_with_with_node_1(&canvas) - .expect(&format!("Could not replace #{}", node.id())); + sender + .send(Boot { + window, + compositor, + should_be_visible, + exit_on_close_request, + }) + .ok() + .expect("Send boot event"); + + Ok::<_, graphics::Error>(()) + } + }; + + #[cfg(not(target_arch = "wasm32"))] + if let Err(error) = futures::executor::block_on(finish_boot) { + self.error = Some(Error::GraphicsCreationFailed(error)); + event_loop.exit(); } - None => { - let _ = body - .append_child(&canvas) - .expect("Append canvas to HTML body"); + + #[cfg(target_arch = "wasm32")] + { + use winit::platform::web::WindowExtWebSys; + + let canvas = window.canvas().expect("Get window canvas"); + let _ = canvas.set_attribute( + "style", + "display: block; width: 100%; height: 100%", + ); + + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + let body = document.body().unwrap(); + + let target = target.and_then(|target| { + body.query_selector(&format!("#{target}")) + .ok() + .unwrap_or(None) + }); + + match target { + Some(node) => { + let _ = node.replace_with_with_node_1(&canvas).expect( + &format!("Could not replace #{}", node.id()), + ); + } + None => { + let _ = body + .append_child(&canvas) + .expect("Append canvas to HTML body"); + } + }; + + let is_booted = self.is_booted.clone(); + + wasm_bindgen_futures::spawn_local(async move { + finish_boot.await.expect("Finish boot!"); + + *is_booted.borrow_mut() = true; + }); } - }; - } + } - let compositor = C::new(compositor_settings, window.clone()).await?; - let mut renderer = compositor.create_renderer(); + fn new_events( + &mut self, + event_loop: &winit::event_loop::ActiveEventLoop, + cause: winit::event::StartCause, + ) { + if self.boot.is_some() { + return; + } - for font in settings.fonts { - use crate::core::text::Renderer; + self.process_event( + event_loop, + winit::event::Event::NewEvents(cause), + ); + } - renderer.load_font(font); - } + fn window_event( + &mut self, + event_loop: &winit::event_loop::ActiveEventLoop, + window_id: winit::window::WindowId, + event: winit::event::WindowEvent, + ) { + #[cfg(target_os = "windows")] + let is_move_or_resize = matches!( + event, + winit::event::WindowEvent::Resized(_) + | winit::event::WindowEvent::Moved(_) + ); + + self.process_event( + event_loop, + winit::event::Event::WindowEvent { window_id, event }, + ); + + // TODO: Remove when unnecessary + // On Windows, we emulate an `AboutToWait` event after every `Resized` event + // since the event loop does not resume during resize interaction. + // More details: https://github.com/rust-windowing/winit/issues/3272 + #[cfg(target_os = "windows")] + { + if is_move_or_resize { + self.process_event( + event_loop, + winit::event::Event::AboutToWait, + ); + } + } + } - let (mut event_sender, event_receiver) = mpsc::unbounded(); - let (control_sender, mut control_receiver) = mpsc::unbounded(); + fn user_event( + &mut self, + event_loop: &winit::event_loop::ActiveEventLoop, + message: Message, + ) { + self.process_event( + event_loop, + winit::event::Event::UserEvent(message), + ); + } - let mut instance = Box::pin(run_instance::<A, E, C>( - application, - compositor, - renderer, - runtime, - proxy, - debug, - event_receiver, - control_sender, - init_command, - window, - should_be_visible, - exit_on_close_request, - )); + fn about_to_wait( + &mut self, + event_loop: &winit::event_loop::ActiveEventLoop, + ) { + self.process_event(event_loop, winit::event::Event::AboutToWait); + } + } - let mut context = task::Context::from_waker(task::noop_waker_ref()); + impl<Message, F, C> Runner<Message, F, C> + where + F: Future<Output = ()>, + { + fn process_event( + &mut self, + event_loop: &winit::event_loop::ActiveEventLoop, + event: winit::event::Event<Message>, + ) { + // On Wasm, events may start being processed before the compositor + // boots up. We simply queue them and process them once ready. + #[cfg(target_arch = "wasm32")] + if !*self.is_booted.borrow() { + self.queued_events.push(event); + return; + } else if !self.queued_events.is_empty() { + let queued_events = std::mem::take(&mut self.queued_events); + + // This won't infinitely recurse, since we `mem::take` + for event in queued_events { + self.process_event(event_loop, event); + } + } - let process_event = - move |event, event_loop: &winit::event_loop::EventLoopWindowTarget<_>| { if event_loop.exiting() { return; } - event_sender.start_send(event).expect("Send event"); + self.sender.start_send(event).expect("Send event"); - let poll = instance.as_mut().poll(&mut context); + let poll = self.instance.as_mut().poll(&mut self.context); match poll { task::Poll::Pending => { - if let Ok(Some(flow)) = control_receiver.try_next() { + if let Ok(Some(flow)) = self.receiver.try_next() { event_loop.set_control_flow(flow); } } @@ -268,54 +450,45 @@ where event_loop.exit(); } } - }; + } + } - #[cfg(not(target_os = "windows"))] - let _ = event_loop.run(process_event); + #[cfg(not(target_arch = "wasm32"))] + { + let mut runner = runner; + let _ = event_loop.run_app(&mut runner); - // TODO: Remove when unnecessary - // On Windows, we emulate an `AboutToWait` event after every `Resized` event - // since the event loop does not resume during resize interaction. - // More details: https://github.com/rust-windowing/winit/issues/3272 - #[cfg(target_os = "windows")] + runner.error.map(Err).unwrap_or(Ok(())) + } + + #[cfg(target_arch = "wasm32")] { - let mut process_event = process_event; + use winit::platform::web::EventLoopExtWebSys; + let _ = event_loop.spawn_app(runner); - let _ = event_loop.run(move |event, event_loop| { - if matches!( - event, - winit::event::Event::WindowEvent { - event: winit::event::WindowEvent::Resized(_) - | winit::event::WindowEvent::Moved(_), - .. - } - ) { - process_event(event, event_loop); - process_event(winit::event::Event::AboutToWait, event_loop); - } else { - process_event(event, event_loop); - } - }); + Ok(()) } +} - Ok(()) +struct Boot<C> { + window: Arc<winit::window::Window>, + compositor: C, + should_be_visible: bool, + exit_on_close_request: bool, } async fn run_instance<A, E, C>( mut application: A, - mut compositor: C, - mut renderer: A::Renderer, mut runtime: Runtime<E, Proxy<A::Message>, A::Message>, - mut proxy: winit::event_loop::EventLoopProxy<A::Message>, + mut proxy: Proxy<A::Message>, mut debug: Debug, + mut boot: oneshot::Receiver<Boot<C>>, mut event_receiver: mpsc::UnboundedReceiver< winit::event::Event<A::Message>, >, mut control_sender: mpsc::UnboundedSender<winit::event_loop::ControlFlow>, init_command: Command<A::Message>, - window: Arc<winit::window::Window>, - should_be_visible: bool, - exit_on_close_request: bool, + fonts: Vec<Cow<'static, [u8]>>, ) where A: Application + 'static, E: Executor + 'static, @@ -326,6 +499,19 @@ async fn run_instance<A, E, C>( use winit::event; use winit::event_loop::ControlFlow; + let Boot { + window, + mut compositor, + should_be_visible, + exit_on_close_request, + } = boot.try_recv().ok().flatten().expect("Receive boot"); + + let mut renderer = compositor.create_renderer(); + + for font in fonts { + compositor.load_font(font); + } + let mut state = State::new(&application, &window); let mut viewport_version = state.viewport_version(); let physical_size = state.physical_size(); @@ -371,6 +557,7 @@ async fn run_instance<A, E, C>( let mut mouse_interaction = mouse::Interaction::default(); let mut events = Vec::new(); let mut messages = Vec::new(); + let mut user_events = 0; let mut redraw_pending = false; debug.startup_finished(); @@ -397,6 +584,7 @@ async fn run_instance<A, E, C>( } event::Event::UserEvent(message) => { messages.push(message); + user_events += 1; } event::Event::WindowEvent { event: event::WindowEvent::RedrawRequested { .. }, @@ -478,7 +666,7 @@ async fn run_instance<A, E, C>( debug.draw_finished(); if new_mouse_interaction != mouse_interaction { - window.set_cursor_icon(conversion::mouse_interaction( + window.set_cursor(conversion::mouse_interaction( new_mouse_interaction, )); @@ -594,6 +782,11 @@ async fn run_instance<A, E, C>( if should_exit { break; } + + if user_events > 0 { + proxy.free_slots(user_events); + user_events = 0; + } } if !redraw_pending { @@ -668,7 +861,7 @@ pub fn update<A: Application, C, E: Executor>( runtime: &mut Runtime<E, Proxy<A::Message>, A::Message>, clipboard: &mut Clipboard, should_exit: &mut bool, - proxy: &mut winit::event_loop::EventLoopProxy<A::Message>, + proxy: &mut Proxy<A::Message>, debug: &mut Debug, messages: &mut Vec<A::Message>, window: &winit::window::Window, @@ -718,7 +911,7 @@ pub fn run_command<A, C, E>( runtime: &mut Runtime<E, Proxy<A::Message>, A::Message>, clipboard: &mut Clipboard, should_exit: &mut bool, - proxy: &mut winit::event_loop::EventLoopProxy<A::Message>, + proxy: &mut Proxy<A::Message>, debug: &mut Debug, window: &winit::window::Window, ) where @@ -743,9 +936,7 @@ pub fn run_command<A, C, E>( clipboard::Action::Read(tag, kind) => { let message = tag(clipboard.read(kind)); - proxy - .send_event(message) - .expect("Send message to event loop"); + proxy.send(message); } clipboard::Action::Write(contents, kind) => { clipboard.write(kind, contents); @@ -775,25 +966,16 @@ pub fn run_command<A, C, E>( let size = window.inner_size().to_logical(window.scale_factor()); - proxy - .send_event(callback(Size::new( - size.width, - size.height, - ))) - .expect("Send message to event loop"); + proxy.send(callback(Size::new(size.width, size.height))); } window::Action::FetchMaximized(_id, callback) => { - proxy - .send_event(callback(window.is_maximized())) - .expect("Send message to event loop"); + proxy.send(callback(window.is_maximized())); } window::Action::Maximize(_id, maximized) => { window.set_maximized(maximized); } window::Action::FetchMinimized(_id, callback) => { - proxy - .send_event(callback(window.is_minimized())) - .expect("Send message to event loop"); + proxy.send(callback(window.is_minimized())); } window::Action::Minimize(_id, minimized) => { window.set_minimized(minimized); @@ -809,9 +991,7 @@ pub fn run_command<A, C, E>( }) .ok(); - proxy - .send_event(callback(position)) - .expect("Send message to event loop"); + proxy.send(callback(position)); } window::Action::Move(_id, position) => { window.set_outer_position(winit::dpi::LogicalPosition { @@ -836,9 +1016,7 @@ pub fn run_command<A, C, E>( core::window::Mode::Hidden }; - proxy - .send_event(tag(mode)) - .expect("Send message to event loop"); + proxy.send(tag(mode)); } window::Action::ToggleMaximize(_id) => { window.set_maximized(!window.is_maximized()); @@ -866,17 +1044,13 @@ pub fn run_command<A, C, E>( } } window::Action::FetchId(_id, tag) => { - proxy - .send_event(tag(window.id().into())) - .expect("Send message to event loop"); + proxy.send(tag(window.id().into())); } window::Action::RunWithHandle(_id, tag) => { use window::raw_window_handle::HasWindowHandle; if let Ok(handle) = window.window_handle() { - proxy - .send_event(tag(&handle)) - .expect("Send message to event loop"); + proxy.send(tag(handle)); } } @@ -889,12 +1063,10 @@ pub fn run_command<A, C, E>( &debug.overlay(), ); - proxy - .send_event(tag(window::Screenshot::new( - bytes, - state.physical_size(), - ))) - .expect("Send message to event loop."); + proxy.send(tag(window::Screenshot::new( + bytes, + state.physical_size(), + ))); } }, command::Action::System(action) => match action { @@ -902,7 +1074,7 @@ pub fn run_command<A, C, E>( #[cfg(feature = "system")] { let graphics_info = compositor.fetch_information(); - let proxy = proxy.clone(); + let mut proxy = proxy.clone(); let _ = std::thread::spawn(move || { let information = @@ -910,9 +1082,7 @@ pub fn run_command<A, C, E>( let message = _tag(information); - proxy - .send_event(message) - .expect("Send message to event loop"); + proxy.send(message); }); } } @@ -935,9 +1105,7 @@ pub fn run_command<A, C, E>( match operation.finish() { operation::Outcome::None => {} operation::Outcome::Some(message) => { - proxy - .send_event(message) - .expect("Send message to event loop"); + proxy.send(message); } operation::Outcome::Chain(next) => { current_operation = Some(next); @@ -949,14 +1117,10 @@ pub fn run_command<A, C, E>( *cache = current_cache; } command::Action::LoadFont { bytes, tagger } => { - use crate::core::text::Renderer; - // TODO: Error handling (?) - renderer.load_font(bytes); + compositor.load_font(bytes); - proxy - .send_event(tagger(Ok(()))) - .expect("Send message to event loop"); + proxy.send(tagger(Ok(()))); } command::Action::Custom(_) => { log::warn!("Unsupported custom action in `iced_winit` shell"); diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index fc3d1c08..ea33e610 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -8,16 +8,16 @@ use crate::core::touch; use crate::core::window; use crate::core::{Event, Point, Size}; -/// Converts some [`window::Settings`] into a `WindowBuilder` from `winit`. -pub fn window_settings( +/// Converts some [`window::Settings`] into some `WindowAttributes` from `winit`. +pub fn window_attributes( settings: window::Settings, title: &str, primary_monitor: Option<winit::monitor::MonitorHandle>, _id: Option<String>, -) -> winit::window::WindowBuilder { - let mut window_builder = winit::window::WindowBuilder::new(); +) -> winit::window::WindowAttributes { + let mut attributes = winit::window::WindowAttributes::default(); - window_builder = window_builder + attributes = attributes .with_title(title) .with_inner_size(winit::dpi::LogicalSize { width: settings.size.width, @@ -39,23 +39,21 @@ pub fn window_settings( if let Some(position) = position(primary_monitor.as_ref(), settings.size, settings.position) { - window_builder = window_builder.with_position(position); + attributes = attributes.with_position(position); } if let Some(min_size) = settings.min_size { - window_builder = - window_builder.with_min_inner_size(winit::dpi::LogicalSize { - width: min_size.width, - height: min_size.height, - }); + attributes = attributes.with_min_inner_size(winit::dpi::LogicalSize { + width: min_size.width, + height: min_size.height, + }); } if let Some(max_size) = settings.max_size { - window_builder = - window_builder.with_max_inner_size(winit::dpi::LogicalSize { - width: max_size.width, - height: max_size.height, - }); + attributes = attributes.with_max_inner_size(winit::dpi::LogicalSize { + width: max_size.width, + height: max_size.height, + }); } #[cfg(any( @@ -65,35 +63,33 @@ pub fn window_settings( target_os = "openbsd" ))] { - // `with_name` is available on both `WindowBuilderExtWayland` and `WindowBuilderExtX11` and they do - // exactly the same thing. We arbitrarily choose `WindowBuilderExtWayland` here. - use ::winit::platform::wayland::WindowBuilderExtWayland; + use ::winit::platform::wayland::WindowAttributesExtWayland; if let Some(id) = _id { - window_builder = window_builder.with_name(id.clone(), id); + attributes = attributes.with_name(id.clone(), id); } } #[cfg(target_os = "windows")] { - use winit::platform::windows::WindowBuilderExtWindows; + use winit::platform::windows::WindowAttributesExtWindows; #[allow(unsafe_code)] unsafe { - window_builder = window_builder + attributes = attributes .with_parent_window(settings.platform_specific.parent); } - window_builder = window_builder + attributes = attributes .with_drag_and_drop(settings.platform_specific.drag_and_drop); - window_builder = window_builder + attributes = attributes .with_skip_taskbar(settings.platform_specific.skip_taskbar); } #[cfg(target_os = "macos")] { - use winit::platform::macos::WindowBuilderExtMacOS; + use winit::platform::macos::WindowAttributesExtMacOS; - window_builder = window_builder + attributes = attributes .with_title_hidden(settings.platform_specific.title_hidden) .with_titlebar_transparent( settings.platform_specific.titlebar_transparent, @@ -107,25 +103,25 @@ pub fn window_settings( { #[cfg(feature = "x11")] { - use winit::platform::x11::WindowBuilderExtX11; + use winit::platform::x11::WindowAttributesExtX11; - window_builder = window_builder.with_name( + attributes = attributes.with_name( &settings.platform_specific.application_id, &settings.platform_specific.application_id, ); } #[cfg(feature = "wayland")] { - use winit::platform::wayland::WindowBuilderExtWayland; + use winit::platform::wayland::WindowAttributesExtWayland; - window_builder = window_builder.with_name( + attributes = attributes.with_name( &settings.platform_specific.application_id, &settings.platform_specific.application_id, ); } } - window_builder + attributes } /// Converts a winit window event into an iced event. @@ -327,6 +323,35 @@ pub fn position( y: f64::from(position.y), })) } + window::Position::SpecificWith(to_position) => { + if let Some(monitor) = monitor { + let start = monitor.position(); + + let resolution: winit::dpi::LogicalSize<f32> = + monitor.size().to_logical(monitor.scale_factor()); + + let position = to_position( + size, + Size::new(resolution.width, resolution.height), + ); + + let centered: winit::dpi::PhysicalPosition<i32> = + winit::dpi::LogicalPosition { + x: position.x, + y: position.y, + } + .to_physical(monitor.scale_factor()); + + Some(winit::dpi::Position::Physical( + winit::dpi::PhysicalPosition { + x: start.x + centered.x, + y: start.y + centered.y, + }, + )) + } else { + None + } + } window::Position::Centered => { if let Some(monitor) = monitor { let start = monitor.position(); @@ -396,7 +421,9 @@ pub fn mouse_interaction( use mouse::Interaction; match interaction { - Interaction::Idle => winit::window::CursorIcon::Default, + Interaction::None | Interaction::Idle => { + winit::window::CursorIcon::Default + } Interaction::Pointer => winit::window::CursorIcon::Pointer, Interaction::Working => winit::window::CursorIcon::Progress, Interaction::Grab => winit::window::CursorIcon::Grab, diff --git a/winit/src/lib.rs b/winit/src/lib.rs index 64912b3f..3619cde8 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -17,14 +17,6 @@ #![doc( html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] -#![forbid(rust_2018_idioms)] -#![deny( - missing_debug_implementations, - missing_docs, - unused_results, - unsafe_code, - rustdoc::broken_intra_doc_links -)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] pub use iced_graphics as graphics; pub use iced_runtime as runtime; diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 18db1fb5..95d78b83 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -12,10 +12,12 @@ use crate::core::widget::operation; use crate::core::window; use crate::core::{Point, Size}; use crate::futures::futures::channel::mpsc; +use crate::futures::futures::channel::oneshot; use crate::futures::futures::executor; use crate::futures::futures::task; use crate::futures::futures::{Future, StreamExt}; use crate::futures::{Executor, Runtime, Subscription}; +use crate::graphics; use crate::graphics::{compositor, Compositor}; use crate::multi_window::window_manager::WindowManager; use crate::runtime::command::{self, Command}; @@ -26,7 +28,7 @@ use crate::{Clipboard, Error, Proxy, Settings}; pub use crate::application::{default, Appearance, DefaultStyle}; -use std::collections::HashMap; +use rustc_hash::FxHashMap; use std::mem::ManuallyDrop; use std::sync::Arc; use std::time::Instant; @@ -105,7 +107,7 @@ where /// settings. pub fn run<A, E, C>( settings: Settings<A::Flags>, - compositor_settings: C::Settings, + graphics_settings: graphics::Settings, ) -> Result<(), Error> where A: Application + 'static, @@ -113,22 +115,22 @@ where C: Compositor<Renderer = A::Renderer> + 'static, A::Theme: DefaultStyle, { - use winit::event_loop::EventLoopBuilder; + use winit::event_loop::EventLoop; let mut debug = Debug::new(); debug.startup_started(); - let event_loop = EventLoopBuilder::with_user_event() + let event_loop = EventLoop::with_user_event() .build() .expect("Create event loop"); - let proxy = event_loop.create_proxy(); + let (proxy, worker) = Proxy::new(event_loop.create_proxy()); let runtime = { - let proxy = Proxy::new(event_loop.create_proxy()); let executor = E::new().map_err(Error::ExecutorCreationFailed)?; + executor.spawn(worker); - Runtime::new(executor, proxy) + Runtime::new(executor, proxy.clone()) }; let (application, init_command) = { @@ -137,187 +139,292 @@ where runtime.enter(|| A::new(flags)) }; - let should_main_be_visible = settings.window.visible; - let exit_on_close_request = settings.window.exit_on_close_request; + let id = settings.id; + let title = application.title(window::Id::MAIN); - let builder = conversion::window_settings( - settings.window, - &application.title(window::Id::MAIN), - event_loop.primary_monitor(), - settings.id, - ) - .with_visible(false); + let (boot_sender, boot_receiver) = oneshot::channel(); + let (event_sender, event_receiver) = mpsc::unbounded(); + let (control_sender, control_receiver) = mpsc::unbounded(); - log::info!("Window builder: {:#?}", builder); + let instance = Box::pin(run_instance::<A, E, C>( + application, + runtime, + proxy, + debug, + boot_receiver, + event_receiver, + control_sender, + init_command, + )); - let main_window = Arc::new( - builder - .build(&event_loop) - .map_err(Error::WindowCreationFailed)?, - ); + let context = task::Context::from_waker(task::noop_waker_ref()); + + struct Runner<Message: 'static, F, C> { + instance: std::pin::Pin<Box<F>>, + context: task::Context<'static>, + boot: Option<BootConfig<C>>, + sender: mpsc::UnboundedSender<Event<Message>>, + receiver: mpsc::UnboundedReceiver<Control>, + error: Option<Error>, + } + + struct BootConfig<C> { + sender: oneshot::Sender<Boot<C>>, + id: Option<String>, + title: String, + window_settings: window::Settings, + graphics_settings: graphics::Settings, + } - #[cfg(target_arch = "wasm32")] + let mut runner = Runner { + instance, + context, + boot: Some(BootConfig { + sender: boot_sender, + id, + title, + window_settings: settings.window, + graphics_settings, + }), + sender: event_sender, + receiver: control_receiver, + error: None, + }; + + impl<Message, F, C> winit::application::ApplicationHandler<Message> + for Runner<Message, F, C> + where + F: Future<Output = ()>, + C: Compositor, { - use winit::platform::web::WindowExtWebSys; + fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { + let Some(BootConfig { + sender, + id, + title, + window_settings, + graphics_settings, + }) = self.boot.take() + else { + return; + }; - let canvas = main_window.canvas(); + let should_be_visible = window_settings.visible; + let exit_on_close_request = window_settings.exit_on_close_request; - let window = web_sys::window().unwrap(); - let document = window.document().unwrap(); - let body = document.body().unwrap(); + let window_attributes = conversion::window_attributes( + window_settings, + &title, + event_loop.primary_monitor(), + id, + ) + .with_visible(false); - let target = target.and_then(|target| { - body.query_selector(&format!("#{}", target)) - .ok() - .unwrap_or(None) - }); + log::debug!("Window attributes: {window_attributes:#?}"); - match target { - Some(node) => { - let _ = node - .replace_with_with_node_1(&canvas) - .expect(&format!("Could not replace #{}", node.id())); - } - None => { - let _ = body - .append_child(&canvas) - .expect("Append canvas to HTML body"); - } - }; - } + let window = match event_loop.create_window(window_attributes) { + Ok(window) => Arc::new(window), + Err(error) => { + self.error = Some(Error::WindowCreationFailed(error)); + event_loop.exit(); + return; + } + }; - let mut compositor = - executor::block_on(C::new(compositor_settings, main_window.clone()))?; + let finish_boot = async move { + let compositor = + C::new(graphics_settings, window.clone()).await?; + + sender + .send(Boot { + window, + compositor, + should_be_visible, + exit_on_close_request, + }) + .ok() + .expect("Send boot event"); + + Ok::<_, graphics::Error>(()) + }; - let mut window_manager = WindowManager::new(); - let _ = window_manager.insert( - window::Id::MAIN, - main_window, - &application, - &mut compositor, - exit_on_close_request, - ); + if let Err(error) = executor::block_on(finish_boot) { + self.error = Some(Error::GraphicsCreationFailed(error)); + event_loop.exit(); + } + } - let (mut event_sender, event_receiver) = mpsc::unbounded(); - let (control_sender, mut control_receiver) = mpsc::unbounded(); + fn new_events( + &mut self, + event_loop: &winit::event_loop::ActiveEventLoop, + cause: winit::event::StartCause, + ) { + if self.boot.is_some() { + return; + } - let mut instance = Box::pin(run_instance::<A, E, C>( - application, - compositor, - runtime, - proxy, - debug, - event_receiver, - control_sender, - init_command, - window_manager, - should_main_be_visible, - )); + self.process_event( + event_loop, + Event::EventLoopAwakened(winit::event::Event::NewEvents(cause)), + ); + } + + fn window_event( + &mut self, + event_loop: &winit::event_loop::ActiveEventLoop, + window_id: winit::window::WindowId, + event: winit::event::WindowEvent, + ) { + #[cfg(target_os = "windows")] + let is_move_or_resize = matches!( + event, + winit::event::WindowEvent::Resized(_) + | winit::event::WindowEvent::Moved(_) + ); + + self.process_event( + event_loop, + Event::EventLoopAwakened(winit::event::Event::WindowEvent { + window_id, + event, + }), + ); + + // TODO: Remove when unnecessary + // On Windows, we emulate an `AboutToWait` event after every `Resized` event + // since the event loop does not resume during resize interaction. + // More details: https://github.com/rust-windowing/winit/issues/3272 + #[cfg(target_os = "windows")] + { + if is_move_or_resize { + self.process_event( + event_loop, + Event::EventLoopAwakened( + winit::event::Event::AboutToWait, + ), + ); + } + } + } - let mut context = task::Context::from_waker(task::noop_waker_ref()); + fn user_event( + &mut self, + event_loop: &winit::event_loop::ActiveEventLoop, + message: Message, + ) { + self.process_event( + event_loop, + Event::EventLoopAwakened(winit::event::Event::UserEvent( + message, + )), + ); + } - let process_event = move |event, event_loop: &winit::event_loop::EventLoopWindowTarget<_>| { - if event_loop.exiting() { - return; + fn about_to_wait( + &mut self, + event_loop: &winit::event_loop::ActiveEventLoop, + ) { + self.process_event( + event_loop, + Event::EventLoopAwakened(winit::event::Event::AboutToWait), + ); } + } - event_sender - .start_send(Event::EventLoopAwakened(event)) - .expect("Send event"); - - loop { - let poll = instance.as_mut().poll(&mut context); - - match poll { - task::Poll::Pending => match control_receiver.try_next() { - Ok(Some(control)) => match control { - Control::ChangeFlow(flow) => { - use winit::event_loop::ControlFlow; - - match (event_loop.control_flow(), flow) { - ( - ControlFlow::WaitUntil(current), - ControlFlow::WaitUntil(new), - ) if new < current => {} - ( - ControlFlow::WaitUntil(target), - ControlFlow::Wait, - ) if target > Instant::now() => {} - _ => { - event_loop.set_control_flow(flow); + impl<Message, F, C> Runner<Message, F, C> + where + F: Future<Output = ()>, + C: Compositor, + { + fn process_event( + &mut self, + event_loop: &winit::event_loop::ActiveEventLoop, + event: Event<Message>, + ) { + if event_loop.exiting() { + return; + } + + self.sender.start_send(event).expect("Send event"); + + loop { + let poll = self.instance.as_mut().poll(&mut self.context); + + match poll { + task::Poll::Pending => match self.receiver.try_next() { + Ok(Some(control)) => match control { + Control::ChangeFlow(flow) => { + use winit::event_loop::ControlFlow; + + match (event_loop.control_flow(), flow) { + ( + ControlFlow::WaitUntil(current), + ControlFlow::WaitUntil(new), + ) if new < current => {} + ( + ControlFlow::WaitUntil(target), + ControlFlow::Wait, + ) if target > Instant::now() => {} + _ => { + event_loop.set_control_flow(flow); + } } } - } - Control::CreateWindow { - id, - settings, - title, - monitor, - } => { - let exit_on_close_request = - settings.exit_on_close_request; - - let window = conversion::window_settings( - settings, &title, monitor, None, - ) - .build(event_loop) - .expect("Failed to build window"); - - event_sender - .start_send(Event::WindowCreated { - id, - window, - exit_on_close_request, - }) - .expect("Send event"); - } - Control::Exit => { - event_loop.exit(); + Control::CreateWindow { + id, + settings, + title, + monitor, + } => { + let exit_on_close_request = + settings.exit_on_close_request; + + let window = event_loop + .create_window( + conversion::window_attributes( + settings, &title, monitor, None, + ), + ) + .expect("Create window"); + + self.process_event( + event_loop, + Event::WindowCreated { + id, + window, + exit_on_close_request, + }, + ); + } + Control::Exit => { + event_loop.exit(); + } + }, + _ => { + break; } }, - _ => { + task::Poll::Ready(_) => { + event_loop.exit(); break; } - }, - task::Poll::Ready(_) => { - event_loop.exit(); - break; - } - }; - } - }; - - #[cfg(not(target_os = "windows"))] - let _ = event_loop.run(process_event); - - // TODO: Remove when unnecessary - // On Windows, we emulate an `AboutToWait` event after every `Resized` event - // since the event loop does not resume during resize interaction. - // More details: https://github.com/rust-windowing/winit/issues/3272 - #[cfg(target_os = "windows")] - { - let mut process_event = process_event; - - let _ = event_loop.run(move |event, event_loop| { - if matches!( - event, - winit::event::Event::WindowEvent { - event: winit::event::WindowEvent::Resized(_) - | winit::event::WindowEvent::Moved(_), - .. - } - ) { - process_event(event, event_loop); - process_event(winit::event::Event::AboutToWait, event_loop); - } else { - process_event(event, event_loop); + }; } - }); + } } + let _ = event_loop.run_app(&mut runner); + Ok(()) } +struct Boot<C> { + window: Arc<winit::window::Window>, + compositor: C, + should_be_visible: bool, + exit_on_close_request: bool, +} + enum Event<Message: 'static> { WindowCreated { id: window::Id, @@ -340,15 +447,13 @@ enum Control { async fn run_instance<A, E, C>( mut application: A, - mut compositor: C, mut runtime: Runtime<E, Proxy<A::Message>, A::Message>, - mut proxy: winit::event_loop::EventLoopProxy<A::Message>, + mut proxy: Proxy<A::Message>, mut debug: Debug, + mut boot: oneshot::Receiver<Boot<C>>, mut event_receiver: mpsc::UnboundedReceiver<Event<A::Message>>, mut control_sender: mpsc::UnboundedSender<Control>, init_command: Command<A::Message>, - mut window_manager: WindowManager<A, C>, - should_main_window_be_visible: bool, ) where A: Application + 'static, E: Executor + 'static, @@ -358,11 +463,28 @@ async fn run_instance<A, E, C>( use winit::event; use winit::event_loop::ControlFlow; + let Boot { + window: main_window, + mut compositor, + should_be_visible, + exit_on_close_request, + } = boot.try_recv().ok().flatten().expect("Receive boot"); + + let mut window_manager = WindowManager::new(); + + let _ = window_manager.insert( + window::Id::MAIN, + main_window, + &application, + &mut compositor, + exit_on_close_request, + ); + let main_window = window_manager .get_mut(window::Id::MAIN) .expect("Get main window"); - if should_main_window_be_visible { + if should_be_visible { main_window.raw.set_visible(true); } @@ -380,12 +502,12 @@ async fn run_instance<A, E, C>( )] }; - let mut ui_caches = HashMap::new(); + let mut ui_caches = FxHashMap::default(); let mut user_interfaces = ManuallyDrop::new(build_user_interfaces( &application, &mut debug, &mut window_manager, - HashMap::from_iter([( + FxHashMap::from_iter([( window::Id::MAIN, user_interface::Cache::default(), )]), @@ -407,6 +529,7 @@ async fn run_instance<A, E, C>( runtime.track(application.subscription().into_recipes()); let mut messages = Vec::new(); + let mut user_events = 0; debug.startup_finished(); @@ -481,6 +604,7 @@ async fn run_instance<A, E, C>( } event::Event::UserEvent(message) => { messages.push(message); + user_events += 1; } event::Event::WindowEvent { window_id: id, @@ -529,7 +653,7 @@ async fn run_instance<A, E, C>( debug.draw_finished(); if new_mouse_interaction != window.mouse_interaction { - window.raw.set_cursor_icon( + window.raw.set_cursor( conversion::mouse_interaction( new_mouse_interaction, ), @@ -600,7 +724,7 @@ async fn run_instance<A, E, C>( if new_mouse_interaction != window.mouse_interaction { - window.raw.set_cursor_icon( + window.raw.set_cursor( conversion::mouse_interaction( new_mouse_interaction, ), @@ -758,7 +882,7 @@ async fn run_instance<A, E, C>( // TODO mw application update returns which window IDs to update if !messages.is_empty() || uis_stale { - let mut cached_interfaces: HashMap< + let mut cached_interfaces: FxHashMap< window::Id, user_interface::Cache, > = ManuallyDrop::into_inner(user_interfaces) @@ -802,6 +926,11 @@ async fn run_instance<A, E, C>( &mut window_manager, cached_interfaces, )); + + if user_events > 0 { + proxy.free_slots(user_events); + user_events = 0; + } } } _ => {} @@ -844,11 +973,11 @@ fn update<A: Application, C, E: Executor>( runtime: &mut Runtime<E, Proxy<A::Message>, A::Message>, clipboard: &mut Clipboard, control_sender: &mut mpsc::UnboundedSender<Control>, - proxy: &mut winit::event_loop::EventLoopProxy<A::Message>, + proxy: &mut Proxy<A::Message>, debug: &mut Debug, messages: &mut Vec<A::Message>, window_manager: &mut WindowManager<A, C>, - ui_caches: &mut HashMap<window::Id, user_interface::Cache>, + ui_caches: &mut FxHashMap<window::Id, user_interface::Cache>, ) where C: Compositor<Renderer = A::Renderer> + 'static, A::Theme: DefaultStyle, @@ -886,10 +1015,10 @@ fn run_command<A, C, E>( runtime: &mut Runtime<E, Proxy<A::Message>, A::Message>, clipboard: &mut Clipboard, control_sender: &mut mpsc::UnboundedSender<Control>, - proxy: &mut winit::event_loop::EventLoopProxy<A::Message>, + proxy: &mut Proxy<A::Message>, debug: &mut Debug, window_manager: &mut WindowManager<A, C>, - ui_caches: &mut HashMap<window::Id, user_interface::Cache>, + ui_caches: &mut FxHashMap<window::Id, user_interface::Cache>, ) where A: Application, E: Executor, @@ -912,9 +1041,7 @@ fn run_command<A, C, E>( clipboard::Action::Read(tag, kind) => { let message = tag(clipboard.read(kind)); - proxy - .send_event(message) - .expect("Send message to event loop"); + proxy.send(message); } clipboard::Action::Write(contents, kind) => { clipboard.write(kind, contents); @@ -966,18 +1093,12 @@ fn run_command<A, C, E>( .to_logical(window.raw.scale_factor()); proxy - .send_event(callback(Size::new( - size.width, - size.height, - ))) - .expect("Send message to event loop"); + .send(callback(Size::new(size.width, size.height))); } } window::Action::FetchMaximized(id, callback) => { if let Some(window) = window_manager.get_mut(id) { - proxy - .send_event(callback(window.raw.is_maximized())) - .expect("Send message to event loop"); + proxy.send(callback(window.raw.is_maximized())); } } window::Action::Maximize(id, maximized) => { @@ -987,9 +1108,7 @@ fn run_command<A, C, E>( } window::Action::FetchMinimized(id, callback) => { if let Some(window) = window_manager.get_mut(id) { - proxy - .send_event(callback(window.raw.is_minimized())) - .expect("Send message to event loop"); + proxy.send(callback(window.raw.is_minimized())); } } window::Action::Minimize(id, minimized) => { @@ -1011,9 +1130,7 @@ fn run_command<A, C, E>( }) .ok(); - proxy - .send_event(callback(position)) - .expect("Send message to event loop"); + proxy.send(callback(position)); } } window::Action::Move(id, position) => { @@ -1048,9 +1165,7 @@ fn run_command<A, C, E>( core::window::Mode::Hidden }; - proxy - .send_event(tag(mode)) - .expect("Send message to event loop"); + proxy.send(tag(mode)); } } window::Action::ToggleMaximize(id) => { @@ -1098,9 +1213,7 @@ fn run_command<A, C, E>( } window::Action::FetchId(id, tag) => { if let Some(window) = window_manager.get_mut(id) { - proxy - .send_event(tag(window.raw.id().into())) - .expect("Send message to event loop"); + proxy.send(tag(window.raw.id().into())); } } window::Action::RunWithHandle(id, tag) => { @@ -1110,9 +1223,7 @@ fn run_command<A, C, E>( .get_mut(id) .and_then(|window| window.raw.window_handle().ok()) { - proxy - .send_event(tag(&handle)) - .expect("Send message to event loop"); + proxy.send(tag(handle)); } } window::Action::Screenshot(id, tag) => { @@ -1125,12 +1236,10 @@ fn run_command<A, C, E>( &debug.overlay(), ); - proxy - .send_event(tag(window::Screenshot::new( - bytes, - window.state.physical_size(), - ))) - .expect("Event loop doesn't exist."); + proxy.send(tag(window::Screenshot::new( + bytes, + window.state.physical_size(), + ))); } } }, @@ -1139,7 +1248,7 @@ fn run_command<A, C, E>( #[cfg(feature = "system")] { let graphics_info = compositor.fetch_information(); - let proxy = proxy.clone(); + let mut proxy = proxy.clone(); let _ = std::thread::spawn(move || { let information = @@ -1147,9 +1256,7 @@ fn run_command<A, C, E>( let message = _tag(information); - proxy - .send_event(message) - .expect("Event loop doesn't exist."); + proxy.send(message); }); } } @@ -1174,9 +1281,7 @@ fn run_command<A, C, E>( match operation.finish() { operation::Outcome::None => {} operation::Outcome::Some(message) => { - proxy - .send_event(message) - .expect("Event loop doesn't exist."); + proxy.send(message); // operation completed, don't need to try to operate on rest of UIs break 'operate; @@ -1193,17 +1298,10 @@ fn run_command<A, C, E>( uis.drain().map(|(id, ui)| (id, ui.into_cache())).collect(); } command::Action::LoadFont { bytes, tagger } => { - use crate::core::text::Renderer; - - // TODO change this once we change each renderer to having a single backend reference.. :pain: // TODO: Error handling (?) - for (_, window) in window_manager.iter_mut() { - window.renderer.load_font(bytes.clone()); - } + compositor.load_font(bytes.clone()); - proxy - .send_event(tagger(Ok(()))) - .expect("Send message to event loop"); + proxy.send(tagger(Ok(()))); } command::Action::Custom(_) => { log::warn!("Unsupported custom action in `iced_winit` shell"); @@ -1213,12 +1311,12 @@ fn run_command<A, C, E>( } /// Build the user interface for every window. -pub fn build_user_interfaces<'a, A: Application, C: Compositor>( +pub fn build_user_interfaces<'a, A: Application, C>( application: &'a A, debug: &mut Debug, window_manager: &mut WindowManager<A, C>, - mut cached_user_interfaces: HashMap<window::Id, user_interface::Cache>, -) -> HashMap<window::Id, UserInterface<'a, A::Message, A::Theme, A::Renderer>> + mut cached_user_interfaces: FxHashMap<window::Id, user_interface::Cache>, +) -> FxHashMap<window::Id, UserInterface<'a, A::Message, A::Theme, A::Renderer>> where C: Compositor<Renderer = A::Renderer>, A::Theme: DefaultStyle, diff --git a/winit/src/multi_window/window_manager.rs b/winit/src/multi_window/window_manager.rs index 71c1688b..57a7dc7e 100644 --- a/winit/src/multi_window/window_manager.rs +++ b/winit/src/multi_window/window_manager.rs @@ -9,8 +9,9 @@ use std::sync::Arc; use winit::monitor::MonitorHandle; #[allow(missing_debug_implementations)] -pub struct WindowManager<A: Application, C: Compositor> +pub struct WindowManager<A, C> where + A: Application, C: Compositor<Renderer = A::Renderer>, A::Theme: DefaultStyle, { @@ -60,7 +61,7 @@ where exit_on_close_request, surface, renderer, - mouse_interaction: mouse::Interaction::Idle, + mouse_interaction: mouse::Interaction::None, }, ); diff --git a/winit/src/proxy.rs b/winit/src/proxy.rs index 1d6c48bb..3edc30ad 100644 --- a/winit/src/proxy.rs +++ b/winit/src/proxy.rs @@ -1,28 +1,94 @@ use crate::futures::futures::{ channel::mpsc, + select, task::{Context, Poll}, - Sink, + Future, Sink, StreamExt, }; use std::pin::Pin; -/// An event loop proxy that implements `Sink`. +/// An event loop proxy with backpressure that implements `Sink`. #[derive(Debug)] pub struct Proxy<Message: 'static> { raw: winit::event_loop::EventLoopProxy<Message>, + sender: mpsc::Sender<Message>, + notifier: mpsc::Sender<usize>, } impl<Message: 'static> Clone for Proxy<Message> { fn clone(&self) -> Self { Self { raw: self.raw.clone(), + sender: self.sender.clone(), + notifier: self.notifier.clone(), } } } impl<Message: 'static> Proxy<Message> { + const MAX_SIZE: usize = 100; + /// Creates a new [`Proxy`] from an `EventLoopProxy`. - pub fn new(raw: winit::event_loop::EventLoopProxy<Message>) -> Self { - Self { raw } + pub fn new( + raw: winit::event_loop::EventLoopProxy<Message>, + ) -> (Self, impl Future<Output = ()>) { + let (notifier, mut processed) = mpsc::channel(Self::MAX_SIZE); + let (sender, mut receiver) = mpsc::channel(Self::MAX_SIZE); + let proxy = raw.clone(); + + let worker = async move { + let mut count = 0; + + loop { + if count < Self::MAX_SIZE { + select! { + message = receiver.select_next_some() => { + let _ = proxy.send_event(message); + count += 1; + + } + amount = processed.select_next_some() => { + count = count.saturating_sub(amount); + } + complete => break, + } + } else { + select! { + amount = processed.select_next_some() => { + count = count.saturating_sub(amount); + } + complete => break, + } + } + } + }; + + ( + Self { + raw, + sender, + notifier, + }, + worker, + ) + } + + /// Sends a `Message` to the event loop. + /// + /// Note: This skips the backpressure mechanism with an unbounded + /// channel. Use sparingly! + pub fn send(&mut self, message: Message) + where + Message: std::fmt::Debug, + { + self.raw + .send_event(message) + .expect("Send message to event loop"); + } + + /// Frees an amount of slots for additional messages to be queued in + /// this [`Proxy`]. + pub fn free_slots(&mut self, amount: usize) { + let _ = self.notifier.start_send(amount); } } @@ -30,32 +96,37 @@ impl<Message: 'static> Sink<Message> for Proxy<Message> { type Error = mpsc::SendError; fn poll_ready( - self: Pin<&mut Self>, - _cx: &mut Context<'_>, + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, ) -> Poll<Result<(), Self::Error>> { - Poll::Ready(Ok(())) + self.sender.poll_ready(cx) } fn start_send( - self: Pin<&mut Self>, + mut self: Pin<&mut Self>, message: Message, ) -> Result<(), Self::Error> { - let _ = self.raw.send_event(message); - - Ok(()) + self.sender.start_send(message) } fn poll_flush( - self: Pin<&mut Self>, - _cx: &mut Context<'_>, + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, ) -> Poll<Result<(), Self::Error>> { - Poll::Ready(Ok(())) + match self.sender.poll_ready(cx) { + Poll::Ready(Err(ref e)) if e.is_disconnected() => { + // If the receiver disconnected, we consider the sink to be flushed. + Poll::Ready(Ok(())) + } + x => x, + } } fn poll_close( - self: Pin<&mut Self>, + mut self: Pin<&mut Self>, _cx: &mut Context<'_>, ) -> Poll<Result<(), Self::Error>> { + self.sender.disconnect(); Poll::Ready(Ok(())) } } |