diff options
Diffstat (limited to '')
-rw-r--r-- | winit/src/application.rs | 488 | ||||
-rw-r--r-- | winit/src/application/state.rs | 235 |
2 files changed, 498 insertions, 225 deletions
diff --git a/winit/src/application.rs b/winit/src/application.rs index 12f92053..c1d86471 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -1,13 +1,22 @@ //! Create interactive, native cross-platform applications. +mod state; + +pub use state::State; + use crate::conversion; use crate::mouse; use crate::{ Clipboard, Color, Command, Debug, Error, Executor, Mode, Proxy, Runtime, Settings, Size, Subscription, }; + +use iced_futures::futures; +use iced_futures::futures::channel::mpsc; use iced_graphics::window; -use iced_graphics::Viewport; -use iced_native::program::{self, Program}; +use iced_native::program::Program; +use iced_native::{Cache, UserInterface}; + +use std::mem::ManuallyDrop; /// An interactive, native cross-platform application. /// @@ -116,279 +125,270 @@ where E: Executor + 'static, C: window::Compositor<Renderer = A::Renderer> + 'static, { - use winit::{ - event, - event_loop::{ControlFlow, EventLoop}, - }; + use futures::task; + use futures::Future; + use winit::event_loop::EventLoop; let mut debug = Debug::new(); debug.startup_started(); + let (compositor, renderer) = C::new(compositor_settings)?; + let event_loop = EventLoop::with_user_event(); + let mut runtime = { - let executor = E::new().map_err(Error::ExecutorCreationFailed)?; let proxy = Proxy::new(event_loop.create_proxy()); + let executor = E::new().map_err(Error::ExecutorCreationFailed)?; Runtime::new(executor, proxy) }; - let flags = settings.flags; - let (application, init_command) = runtime.enter(|| A::new(flags)); - runtime.spawn(init_command); + let (application, init_command) = { + let flags = settings.flags; + + runtime.enter(|| A::new(flags)) + }; let subscription = application.subscription(); - runtime.track(subscription); - let mut title = application.title(); - let mut mode = application.mode(); - let mut background_color = application.background_color(); - let mut scale_factor = application.scale_factor(); + runtime.spawn(init_command); + runtime.track(subscription); let window = settings .window - .into_builder(&title, mode, event_loop.primary_monitor()) + .into_builder( + &application.title(), + application.mode(), + event_loop.primary_monitor(), + ) .build(&event_loop) .map_err(Error::WindowCreationFailed)?; - let clipboard = Clipboard::new(&window); - // TODO: Encode cursor availability in the type-system - let mut cursor_position = winit::dpi::PhysicalPosition::new(-1.0, -1.0); - let mut mouse_interaction = mouse::Interaction::default(); - let mut modifiers = winit::event::ModifiersState::default(); + let (mut sender, receiver) = mpsc::unbounded(); - let physical_size = window.inner_size(); - let mut viewport = Viewport::with_physical_size( - Size::new(physical_size.width, physical_size.height), - window.scale_factor() * scale_factor, - ); - let mut resized = false; + let mut instance = Box::pin(run_instance::<A, E, C>( + application, + compositor, + renderer, + window, + runtime, + debug, + receiver, + )); - let (mut compositor, mut renderer) = C::new(compositor_settings)?; + let mut context = task::Context::from_waker(task::noop_waker_ref()); - let surface = compositor.create_surface(&window); + event_loop.run(move |event, _, control_flow| { + use winit::event_loop::ControlFlow; - let mut swap_chain = compositor.create_swap_chain( - &surface, - physical_size.width, - physical_size.height, - ); + if let ControlFlow::Exit = control_flow { + return; + } - let mut state = program::State::new( - application, - viewport.logical_size(), - conversion::cursor_position(cursor_position, viewport.scale_factor()), - &mut renderer, - &mut debug, - ); - debug.startup_finished(); + if let Some(event) = event.to_static() { + sender.start_send(event).expect("Send event"); - event_loop.run(move |event, _, control_flow| match event { - event::Event::MainEventsCleared => { - if state.is_queue_empty() { - return; - } + let poll = instance.as_mut().poll(&mut context); - let command = runtime.enter(|| { - state.update( - viewport.logical_size(), - conversion::cursor_position( - cursor_position, - viewport.scale_factor(), - ), - clipboard.as_ref().map(|c| c as _), - &mut renderer, - &mut debug, - ) - }); + *control_flow = match poll { + task::Poll::Pending => ControlFlow::Wait, + task::Poll::Ready(_) => ControlFlow::Exit, + }; + } + }); +} - // If the application was updated - if let Some(command) = command { - runtime.spawn(command); +async fn run_instance<A, E, C>( + mut application: A, + mut compositor: C, + mut renderer: A::Renderer, + window: winit::window::Window, + mut runtime: Runtime<E, Proxy<A::Message>, A::Message>, + mut debug: Debug, + mut receiver: mpsc::UnboundedReceiver<winit::event::Event<'_, A::Message>>, +) where + A: Application + 'static, + E: Executor + 'static, + C: window::Compositor<Renderer = A::Renderer> + 'static, +{ + use iced_futures::futures::stream::StreamExt; + use winit::event; - let program = state.program(); + let surface = compositor.create_surface(&window); + let clipboard = Clipboard::new(&window); - // Update subscriptions - let subscription = program.subscription(); - runtime.track(subscription); + let mut state = State::new(&application, &window); + let mut viewport_version = state.viewport_version(); + let mut swap_chain = { + let physical_size = state.physical_size(); - // Update window title - let new_title = program.title(); + compositor.create_swap_chain( + &surface, + physical_size.width, + physical_size.height, + ) + }; - if title != new_title { - window.set_title(&new_title); + let mut user_interface = ManuallyDrop::new(build_user_interface( + &mut application, + Cache::default(), + &mut renderer, + state.logical_size(), + &mut debug, + )); - title = new_title; - } + let mut primitive = + user_interface.draw(&mut renderer, state.cursor_position()); + let mut mouse_interaction = mouse::Interaction::default(); - // Update window mode - let new_mode = program.mode(); + let mut events = Vec::new(); + let mut external_messages = Vec::new(); - if mode != new_mode { - window.set_fullscreen(conversion::fullscreen( - window.current_monitor(), - new_mode, - )); + debug.startup_finished(); - mode = new_mode; + while let Some(event) = receiver.next().await { + match event { + event::Event::MainEventsCleared => { + if events.is_empty() && external_messages.is_empty() { + continue; } - // Update background color - background_color = program.background_color(); + debug.event_processing_started(); + let mut messages = user_interface.update( + &events, + state.cursor_position(), + clipboard.as_ref().map(|c| c as _), + &mut renderer, + ); - // Update scale factor - let new_scale_factor = program.scale_factor(); + messages.extend(external_messages.drain(..)); + events.clear(); + debug.event_processing_finished(); - if scale_factor != new_scale_factor { - let size = window.inner_size(); + if !messages.is_empty() { + let cache = + ManuallyDrop::into_inner(user_interface).into_cache(); - viewport = Viewport::with_physical_size( - Size::new(size.width, size.height), - window.scale_factor() * new_scale_factor, + // Update application + update( + &mut application, + &mut runtime, + &mut debug, + messages, ); - // We relayout the UI with the new logical size. - // The queue is empty, therefore this will never produce - // a `Command`. - // - // TODO: Properly queue `WindowResized` - let _ = state.update( - viewport.logical_size(), - conversion::cursor_position( - cursor_position, - viewport.scale_factor(), - ), - clipboard.as_ref().map(|c| c as _), + // Update window + state.synchronize(&application, &window); + + user_interface = ManuallyDrop::new(build_user_interface( + &mut application, + cache, &mut renderer, + state.logical_size(), &mut debug, - ); - - scale_factor = new_scale_factor; + )); } + + debug.draw_started(); + primitive = + user_interface.draw(&mut renderer, state.cursor_position()); + debug.draw_finished(); + + window.request_redraw(); } + event::Event::UserEvent(message) => { + external_messages.push(message); + } + event::Event::RedrawRequested(_) => { + debug.render_started(); + let current_viewport_version = state.viewport_version(); + + if viewport_version != current_viewport_version { + let physical_size = state.physical_size(); + let logical_size = state.logical_size(); + + debug.layout_started(); + user_interface = ManuallyDrop::new( + ManuallyDrop::into_inner(user_interface) + .relayout(logical_size, &mut renderer), + ); + debug.layout_finished(); - window.request_redraw(); - } - event::Event::UserEvent(message) => { - state.queue_message(message); - } - event::Event::RedrawRequested(_) => { - debug.render_started(); + debug.draw_started(); + primitive = user_interface + .draw(&mut renderer, state.cursor_position()); + debug.draw_finished(); - if resized { - let physical_size = viewport.physical_size(); + swap_chain = compositor.create_swap_chain( + &surface, + physical_size.width, + physical_size.height, + ); - swap_chain = compositor.create_swap_chain( - &surface, - physical_size.width, - physical_size.height, - ); + viewport_version = current_viewport_version; + } - resized = false; - } + let new_mouse_interaction = compositor.draw( + &mut renderer, + &mut swap_chain, + state.viewport(), + state.background_color(), + &primitive, + &debug.overlay(), + ); - let new_mouse_interaction = compositor.draw( - &mut renderer, - &mut swap_chain, - &viewport, - background_color, - state.primitive(), - &debug.overlay(), - ); + debug.render_finished(); - debug.render_finished(); + if new_mouse_interaction != mouse_interaction { + window.set_cursor_icon(conversion::mouse_interaction( + new_mouse_interaction, + )); - if new_mouse_interaction != mouse_interaction { - window.set_cursor_icon(conversion::mouse_interaction( - new_mouse_interaction, - )); + mouse_interaction = new_mouse_interaction; + } - mouse_interaction = new_mouse_interaction; + // TODO: Handle animations! + // Maybe we can use `ControlFlow::WaitUntil` for this. } + event::Event::WindowEvent { + event: window_event, + .. + } => { + if requests_exit(&window_event, state.modifiers()) { + break; + } - // TODO: Handle animations! - // Maybe we can use `ControlFlow::WaitUntil` for this. - } - event::Event::WindowEvent { - event: window_event, - .. - } => { - handle_window_event( - &window_event, - &window, - scale_factor, - control_flow, - &mut cursor_position, - &mut modifiers, - &mut viewport, - &mut resized, - &mut debug, - ); - - if let Some(event) = conversion::window_event( - &window_event, - viewport.scale_factor(), - modifiers, - ) { - state.queue_event(event.clone()); - runtime.broadcast(event); + state.update(&window, &window_event, &mut debug); + + if let Some(event) = conversion::window_event( + &window_event, + state.scale_factor(), + state.modifiers(), + ) { + events.push(event.clone()); + runtime.broadcast(event); + } } + _ => {} } - _ => { - *control_flow = ControlFlow::Wait; - } - }) + } + + // Manually drop the user interface + drop(ManuallyDrop::into_inner(user_interface)); } -/// Handles a `WindowEvent` and mutates the provided control flow, keyboard -/// modifiers, viewport, and resized flag accordingly. -pub fn handle_window_event( +/// Returns true if the provided event should cause an [`Application`] to +/// exit. +/// +/// [`Application`]: trait.Application.html +pub fn requests_exit( event: &winit::event::WindowEvent<'_>, - window: &winit::window::Window, - scale_factor: f64, - control_flow: &mut winit::event_loop::ControlFlow, - cursor_position: &mut winit::dpi::PhysicalPosition<f64>, - modifiers: &mut winit::event::ModifiersState, - viewport: &mut Viewport, - resized: &mut bool, - _debug: &mut Debug, -) { - use winit::{event::WindowEvent, event_loop::ControlFlow}; + _modifiers: winit::event::ModifiersState, +) -> bool { + use winit::event::WindowEvent; match event { - WindowEvent::Resized(new_size) => { - let size = Size::new(new_size.width, new_size.height); - - *viewport = Viewport::with_physical_size( - size, - window.scale_factor() * scale_factor, - ); - *resized = true; - } - WindowEvent::ScaleFactorChanged { - scale_factor: new_scale_factor, - new_inner_size, - } => { - let size = Size::new(new_inner_size.width, new_inner_size.height); - - *viewport = Viewport::with_physical_size( - size, - new_scale_factor * scale_factor, - ); - *resized = true; - } - WindowEvent::CloseRequested => { - *control_flow = ControlFlow::Exit; - } - WindowEvent::CursorMoved { position, .. } => { - *cursor_position = *position; - } - WindowEvent::CursorLeft { .. } => { - // TODO: Encode cursor availability in the type-system - *cursor_position = winit::dpi::PhysicalPosition::new(-1.0, -1.0); - } - WindowEvent::ModifiersChanged(new_modifiers) => { - *modifiers = *new_modifiers; - } + WindowEvent::CloseRequested => true, #[cfg(target_os = "macos")] WindowEvent::KeyboardInput { input: @@ -398,19 +398,57 @@ pub fn handle_window_event( .. }, .. - } if modifiers.logo() => { - *control_flow = ControlFlow::Exit; - } - #[cfg(feature = "debug")] - WindowEvent::KeyboardInput { - input: - winit::event::KeyboardInput { - virtual_keycode: Some(winit::event::VirtualKeyCode::F12), - state: winit::event::ElementState::Pressed, - .. - }, - .. - } => _debug.toggle(), - _ => {} + } if _modifiers.logo() => true, + _ => false, + } +} + +/// Builds a [`UserInterface`] for the provided [`Application`], logging +/// [`Debug`] information accordingly. +/// +/// [`UserInterface`]: struct.UserInterface.html +/// [`Application`]: trait.Application.html +/// [`Debug`]: struct.Debug.html +pub fn build_user_interface<'a, A: Application>( + application: &'a mut A, + cache: Cache, + renderer: &mut A::Renderer, + size: Size, + debug: &mut Debug, +) -> UserInterface<'a, A::Message, A::Renderer> { + debug.view_started(); + let view = application.view(); + debug.view_finished(); + + debug.layout_started(); + let user_interface = UserInterface::build(view, size, cache, renderer); + debug.layout_finished(); + + user_interface +} + +/// Updates an [`Application`] by feeding it the provided messages, spawning any +/// resulting [`Command`], and tracking its [`Subscription`]. +/// +/// [`Application`]: trait.Application.html +/// [`Command`]: struct.Command.html +/// [`Subscription`]: struct.Subscription.html +pub fn update<A: Application, E: Executor>( + application: &mut A, + runtime: &mut Runtime<E, Proxy<A::Message>, A::Message>, + debug: &mut Debug, + messages: Vec<A::Message>, +) { + for message in messages { + debug.log_message(&message); + + debug.update_started(); + let command = runtime.enter(|| application.update(message)); + debug.update_finished(); + + runtime.spawn(command); } + + let subscription = application.subscription(); + runtime.track(subscription); } diff --git a/winit/src/application/state.rs b/winit/src/application/state.rs new file mode 100644 index 00000000..4c0bfd34 --- /dev/null +++ b/winit/src/application/state.rs @@ -0,0 +1,235 @@ +use crate::conversion; +use crate::{Application, Color, Debug, Mode, Point, Size, Viewport}; + +use std::marker::PhantomData; +use winit::event::WindowEvent; +use winit::window::Window; + +/// The state of a windowed [`Application`]. +/// +/// [`Application`]: ../trait.Application.html +#[derive(Debug, Clone)] +pub struct State<A: Application> { + title: String, + mode: Mode, + background_color: Color, + scale_factor: f64, + viewport: Viewport, + viewport_version: usize, + cursor_position: winit::dpi::PhysicalPosition<f64>, + modifiers: winit::event::ModifiersState, + application: PhantomData<A>, +} + +impl<A: Application> State<A> { + /// Creates a new [`State`] for the provided [`Application`] and window. + /// + /// [`State`]: struct.State.html + /// [`Application`]: ../trait.Application.html + pub fn new(application: &A, window: &Window) -> Self { + let title = application.title(); + let mode = application.mode(); + let background_color = application.background_color(); + let scale_factor = application.scale_factor(); + + let viewport = { + let physical_size = window.inner_size(); + + Viewport::with_physical_size( + Size::new(physical_size.width, physical_size.height), + window.scale_factor() * scale_factor, + ) + }; + + Self { + title, + mode, + background_color, + scale_factor, + viewport, + viewport_version: 0, + // TODO: Encode cursor availability in the type-system + cursor_position: winit::dpi::PhysicalPosition::new(-1.0, -1.0), + modifiers: winit::event::ModifiersState::default(), + application: PhantomData, + } + } + + /// Returns the current background [`Color`] of the [`State`]. + /// + /// [`Color`]: ../struct.Color.html + /// [`State`]: struct.State.html + pub fn background_color(&self) -> Color { + self.background_color + } + + /// Returns the current [`Viewport`] of the [`State`]. + /// + /// [`Viewport`]: ../struct.Viewport.html + /// [`State`]: struct.State.html + pub fn viewport(&self) -> &Viewport { + &self.viewport + } + + /// Returns the version of the [`Viewport`] of the [`State`]. + /// + /// The version is incremented every time the [`Viewport`] changes. + /// + /// [`Viewport`]: ../struct.Viewport.html + /// [`State`]: struct.State.html + pub fn viewport_version(&self) -> usize { + self.viewport_version + } + + /// Returns the physical [`Size`] of the [`Viewport`] of the [`State`]. + /// + /// [`Size`]: ../struct.Size.html + /// [`Viewport`]: ../struct.Viewport.html + /// [`State`]: struct.State.html + pub fn physical_size(&self) -> Size<u32> { + self.viewport.physical_size() + } + + /// Returns the logical [`Size`] of the [`Viewport`] of the [`State`]. + /// + /// [`Size`]: ../struct.Size.html + /// [`Viewport`]: ../struct.Viewport.html + /// [`State`]: struct.State.html + pub fn logical_size(&self) -> Size<f32> { + self.viewport.logical_size() + } + + /// Returns the current scale factor of the [`Viewport`] of the [`State`]. + /// + /// [`Viewport`]: ../struct.Viewport.html + /// [`State`]: struct.State.html + pub fn scale_factor(&self) -> f64 { + self.viewport.scale_factor() + } + + /// Returns the current cursor position of the [`State`]. + /// + /// [`State`]: struct.State.html + pub fn cursor_position(&self) -> Point { + conversion::cursor_position( + self.cursor_position, + self.viewport.scale_factor(), + ) + } + + /// Returns the current keyboard modifiers of the [`State`]. + /// + /// [`State`]: struct.State.html + pub fn modifiers(&self) -> winit::event::ModifiersState { + self.modifiers + } + + /// Processes the provided window event and updates the [`State`] + /// accordingly. + /// + /// [`State`]: struct.State.html + pub fn update( + &mut self, + window: &Window, + event: &WindowEvent<'_>, + _debug: &mut Debug, + ) { + match event { + WindowEvent::Resized(new_size) => { + let size = Size::new(new_size.width, new_size.height); + + self.viewport = Viewport::with_physical_size( + size, + window.scale_factor() * self.scale_factor, + ); + + self.viewport_version = self.viewport_version.wrapping_add(1); + } + WindowEvent::ScaleFactorChanged { + scale_factor: new_scale_factor, + new_inner_size, + } => { + let size = + Size::new(new_inner_size.width, new_inner_size.height); + + self.viewport = Viewport::with_physical_size( + size, + new_scale_factor * self.scale_factor, + ); + + self.viewport_version = self.viewport_version.wrapping_add(1); + } + WindowEvent::CursorMoved { position, .. } => { + self.cursor_position = *position; + } + WindowEvent::CursorLeft { .. } => { + // TODO: Encode cursor availability in the type-system + self.cursor_position = + winit::dpi::PhysicalPosition::new(-1.0, -1.0); + } + WindowEvent::ModifiersChanged(new_modifiers) => { + self.modifiers = *new_modifiers; + } + #[cfg(feature = "debug")] + WindowEvent::KeyboardInput { + input: + winit::event::KeyboardInput { + virtual_keycode: Some(winit::event::VirtualKeyCode::F12), + state: winit::event::ElementState::Pressed, + .. + }, + .. + } => _debug.toggle(), + _ => {} + } + } + + /// Synchronizes the [`State`] with its [`Application`] and its respective + /// window. + /// + /// Normally an [`Application`] should be synchronized with its [`State`] + /// and window after calling [`Application::update`]. + /// + /// [`State`]: struct.State.html + /// [`Application`]: ../trait.Application.html + /// [`Application::update`]: ../trait.Application.html#tymethod.update + pub fn synchronize(&mut self, application: &A, window: &Window) { + // Update window title + let new_title = application.title(); + + if self.title != new_title { + window.set_title(&new_title); + + self.title = new_title; + } + + // Update window mode + let new_mode = application.mode(); + + if self.mode != new_mode { + window.set_fullscreen(conversion::fullscreen( + window.current_monitor(), + new_mode, + )); + + self.mode = new_mode; + } + + // Update background color + self.background_color = application.background_color(); + + // Update scale factor + let new_scale_factor = application.scale_factor(); + + if self.scale_factor != new_scale_factor { + let size = window.inner_size(); + + self.viewport = Viewport::with_physical_size( + Size::new(size.width, size.height), + window.scale_factor() * new_scale_factor, + ); + + self.scale_factor = new_scale_factor; + } + } +} |