From c153d11aa655119188358a206fb23a718f3d1beb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 4 Nov 2020 03:55:33 +0100 Subject: [PATCH 01/10] Draft strategy to reuse `view` result in event loop --- glutin/src/application.rs | 3 +- winit/src/application.rs | 463 ++++++++++++++++++++++++-------------- 2 files changed, 296 insertions(+), 170 deletions(-) diff --git a/glutin/src/application.rs b/glutin/src/application.rs index fe6ad99d..8718b81f 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -239,11 +239,12 @@ where event: window_event, .. } => { + application::handle_control_flow(&window_event, control_flow); + application::handle_window_event( &window_event, context.window(), scale_factor, - control_flow, &mut cursor_position, &mut modifiers, &mut viewport, diff --git a/winit/src/application.rs b/winit/src/application.rs index 12f92053..fb01bf7a 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -7,7 +7,11 @@ use crate::{ }; 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 iced_futures::futures; +use iced_futures::futures::channel::mpsc; /// An interactive, native cross-platform application. /// @@ -116,24 +120,86 @@ where E: Executor + 'static, C: window::Compositor + 'static, { - use winit::{ - event, - event_loop::{ControlFlow, EventLoop}, - }; + use futures::{Future, FutureExt}; + use winit::event_loop::EventLoop; let mut debug = Debug::new(); debug.startup_started(); - let event_loop = EventLoop::with_user_event(); + let event_loop: EventLoop = EventLoop::with_user_event(); + + let window = settings + .window + .into_builder("", Mode::Windowed, event_loop.primary_monitor()) + .build(&event_loop) + .map_err(Error::WindowCreationFailed)?; + + let (mut sender, receiver) = mpsc::unbounded(); + let proxy = Proxy::new(event_loop.create_proxy()); + let flags = settings.flags; + + let mut event_logic = Box::pin( + process_events::( + window, + proxy, + debug, + flags, + compositor_settings, + receiver, + ) + .map(|_| ()), + ); + + let waker = futures::task::noop_waker(); + + event_loop.run(move |event, _, control_flow| { + use winit::event_loop::ControlFlow; + + match event { + winit::event::Event::WindowEvent { ref event, .. } => { + handle_control_flow(event, control_flow); + } + _ => { + *control_flow = ControlFlow::Wait; + } + } + + if let Some(event) = event.to_static() { + sender.start_send(event).expect("Send event"); + + let mut context = futures::task::Context::from_waker(&waker); + let _ = event_logic.as_mut().poll(&mut context); + } + }); +} + +/// Runs an [`Application`] with an executor, compositor, and the provided +/// settings. +/// +/// [`Application`]: trait.Application.html +pub async fn process_events( + window: winit::window::Window, + proxy: Proxy, + mut debug: Debug, + flags: A::Flags, + compositor_settings: C::Settings, + mut receiver: mpsc::UnboundedReceiver>, +) -> Result<(), Error> +where + A: Application + 'static, + E: Executor + 'static, + C: window::Compositor + 'static, +{ + use iced_futures::futures::stream::StreamExt; + use winit::event; + let mut runtime = { let executor = E::new().map_err(Error::ExecutorCreationFailed)?; - let proxy = Proxy::new(event_loop.create_proxy()); Runtime::new(executor, proxy) }; - let flags = settings.flags; - let (application, init_command) = runtime.enter(|| A::new(flags)); + let (mut application, init_command) = runtime.enter(|| A::new(flags)); runtime.spawn(init_command); let subscription = application.subscription(); @@ -144,12 +210,6 @@ where let mut background_color = application.background_color(); let mut scale_factor = application.scale_factor(); - let window = settings - .window - .into_builder(&title, 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); @@ -173,186 +233,248 @@ where physical_size.height, ); - let mut state = program::State::new( - application, - viewport.logical_size(), - conversion::cursor_position(cursor_position, viewport.scale_factor()), + let mut user_interface = std::mem::ManuallyDrop::new(build_user_interface( + &mut application, + Cache::default(), &mut renderer, + viewport.logical_size(), &mut debug, + )); + + let mut primitive = user_interface.draw( + &mut renderer, + conversion::cursor_position(cursor_position, viewport.scale_factor()), ); + let mut events = Vec::new(); + let mut external_messages = Vec::new(); + debug.startup_finished(); - event_loop.run(move |event, _, control_flow| match event { - event::Event::MainEventsCleared => { - if state.is_queue_empty() { - return; - } + while let Some(event) = receiver.next().await { + match event { + event::Event::MainEventsCleared => { + if events.is_empty() && external_messages.is_empty() { + continue; + } - let command = runtime.enter(|| { - state.update( - viewport.logical_size(), + debug.event_processing_started(); + let mut messages = user_interface.update( + &events, conversion::cursor_position( cursor_position, viewport.scale_factor(), ), clipboard.as_ref().map(|c| c as _), &mut renderer, - &mut debug, - ) - }); + ); - // If the application was updated - if let Some(command) = command { - runtime.spawn(command); + messages.extend(external_messages.drain(..)); + events.clear(); + debug.event_processing_finished(); - let program = state.program(); - - // Update subscriptions - let subscription = program.subscription(); - runtime.track(subscription); - - // Update window title - let new_title = program.title(); - - if title != new_title { - window.set_title(&new_title); - - title = new_title; - } - - // Update window mode - let new_mode = program.mode(); - - if mode != new_mode { - window.set_fullscreen(conversion::fullscreen( - window.current_monitor(), - new_mode, - )); - - mode = new_mode; - } - - // Update background color - background_color = program.background_color(); - - // Update scale factor - let new_scale_factor = program.scale_factor(); - - if scale_factor != new_scale_factor { - let size = window.inner_size(); - - viewport = Viewport::with_physical_size( - Size::new(size.width, size.height), - window.scale_factor() * new_scale_factor, - ); - - // 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(), + if messages.is_empty() { + debug.draw_started(); + primitive = user_interface.draw( + &mut renderer, conversion::cursor_position( cursor_position, viewport.scale_factor(), ), - clipboard.as_ref().map(|c| c as _), + ); + debug.draw_finished(); + } else { + let cache = + std::mem::ManuallyDrop::into_inner(user_interface) + .into_cache(); + + for message in messages.drain(..) { + debug.log_message(&message); + + debug.update_started(); + let command = + runtime.enter(|| application.update(message)); + debug.update_finished(); + + runtime.spawn(command); + } + + // Update subscriptions + let subscription = application.subscription(); + runtime.track(subscription); + + // Update window title + let new_title = application.title(); + + if title != new_title { + window.set_title(&new_title); + + title = new_title; + } + + // Update window mode + let new_mode = application.mode(); + + if mode != new_mode { + window.set_fullscreen(conversion::fullscreen( + window.current_monitor(), + new_mode, + )); + + mode = new_mode; + } + + // Update background color + background_color = application.background_color(); + + // Update scale factor + let new_scale_factor = application.scale_factor(); + + if scale_factor != new_scale_factor { + let size = window.inner_size(); + + viewport = Viewport::with_physical_size( + Size::new(size.width, size.height), + window.scale_factor() * new_scale_factor, + ); + + scale_factor = new_scale_factor; + } + + user_interface = + std::mem::ManuallyDrop::new(build_user_interface( + &mut application, + cache, + &mut renderer, + viewport.logical_size(), + &mut debug, + )); + + debug.draw_started(); + primitive = user_interface.draw( &mut renderer, - &mut debug, + conversion::cursor_position( + cursor_position, + viewport.scale_factor(), + ), + ); + debug.draw_finished(); + } + + window.request_redraw(); + } + event::Event::UserEvent(message) => { + external_messages.push(message); + } + event::Event::RedrawRequested(_) => { + debug.render_started(); + + if resized { + let physical_size = viewport.physical_size(); + + swap_chain = compositor.create_swap_chain( + &surface, + physical_size.width, + physical_size.height, ); - scale_factor = new_scale_factor; + resized = false; } - } - window.request_redraw(); - } - event::Event::UserEvent(message) => { - state.queue_message(message); - } - event::Event::RedrawRequested(_) => { - debug.render_started(); - - if resized { - let physical_size = viewport.physical_size(); - - swap_chain = compositor.create_swap_chain( - &surface, - physical_size.width, - physical_size.height, + let new_mouse_interaction = compositor.draw( + &mut renderer, + &mut swap_chain, + &viewport, + background_color, + &primitive, + &debug.overlay(), ); - resized = false; + debug.render_finished(); + + if new_mouse_interaction != mouse_interaction { + window.set_cursor_icon(conversion::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, + .. + } => { + handle_window_event( + &window_event, + &window, + scale_factor, + &mut cursor_position, + &mut modifiers, + &mut viewport, + &mut resized, + &mut debug, + ); - let new_mouse_interaction = compositor.draw( - &mut renderer, - &mut swap_chain, - &viewport, - background_color, - state.primitive(), - &debug.overlay(), - ); - - debug.render_finished(); - - if new_mouse_interaction != mouse_interaction { - window.set_cursor_icon(conversion::mouse_interaction( - new_mouse_interaction, - )); - - mouse_interaction = new_mouse_interaction; + if let Some(event) = conversion::window_event( + &window_event, + viewport.scale_factor(), + modifiers, + ) { + events.push(event.clone()); + runtime.broadcast(event); + } } + _ => {} + } + } - // 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); - } - } - _ => { - *control_flow = ControlFlow::Wait; - } - }) + Ok(()) } -/// Handles a `WindowEvent` and mutates the provided control flow, keyboard -/// modifiers, viewport, and resized flag accordingly. +/// Handles a `WindowEvent` and mutates the provided control flow to exit +/// if necessary. +pub fn handle_control_flow( + event: &winit::event::WindowEvent<'_>, + control_flow: &mut winit::event_loop::ControlFlow, +) { + use winit::event::WindowEvent; + use winit::event_loop::ControlFlow; + + match event { + WindowEvent::CloseRequested => { + *control_flow = ControlFlow::Exit; + } + #[cfg(target_os = "macos")] + WindowEvent::KeyboardInput { + input: + winit::event::KeyboardInput { + virtual_keycode: Some(winit::event::VirtualKeyCode::Q), + state: winit::event::ElementState::Pressed, + .. + }, + .. + } if modifiers.logo() => { + *control_flow = ControlFlow::Exit; + } + _ => {} + } +} + +/// Handles a `WindowEvent` and mutates the keyboard modifiers, viewport, and +/// resized flag accordingly. pub fn handle_window_event( event: &winit::event::WindowEvent<'_>, window: &winit::window::Window, scale_factor: f64, - control_flow: &mut winit::event_loop::ControlFlow, cursor_position: &mut winit::dpi::PhysicalPosition, modifiers: &mut winit::event::ModifiersState, viewport: &mut Viewport, resized: &mut bool, _debug: &mut Debug, ) { - use winit::{event::WindowEvent, event_loop::ControlFlow}; + use winit::event::WindowEvent; match event { WindowEvent::Resized(new_size) => { @@ -376,9 +498,6 @@ pub fn handle_window_event( ); *resized = true; } - WindowEvent::CloseRequested => { - *control_flow = ControlFlow::Exit; - } WindowEvent::CursorMoved { position, .. } => { *cursor_position = *position; } @@ -389,18 +508,6 @@ pub fn handle_window_event( WindowEvent::ModifiersChanged(new_modifiers) => { *modifiers = *new_modifiers; } - #[cfg(target_os = "macos")] - WindowEvent::KeyboardInput { - input: - winit::event::KeyboardInput { - virtual_keycode: Some(winit::event::VirtualKeyCode::Q), - state: winit::event::ElementState::Pressed, - .. - }, - .. - } if modifiers.logo() => { - *control_flow = ControlFlow::Exit; - } #[cfg(feature = "debug")] WindowEvent::KeyboardInput { input: @@ -414,3 +521,21 @@ pub fn handle_window_event( _ => {} } } + +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 +} From ee38b04d8b08827c9ec9c970c7c0ab9fc3235c1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 4 Nov 2020 21:00:47 +0100 Subject: [PATCH 02/10] Use static noop `Waker` in `application::run` --- winit/src/application.rs | 11 +++++++---- winit/src/compositor.rs | 7 +++++++ 2 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 winit/src/compositor.rs diff --git a/winit/src/application.rs b/winit/src/application.rs index fb01bf7a..b34345d7 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -120,6 +120,7 @@ where E: Executor + 'static, C: window::Compositor + 'static, { + use futures::task::Poll; use futures::{Future, FutureExt}; use winit::event_loop::EventLoop; @@ -150,7 +151,8 @@ where .map(|_| ()), ); - let waker = futures::task::noop_waker(); + let mut context = + futures::task::Context::from_waker(futures::task::noop_waker_ref()); event_loop.run(move |event, _, control_flow| { use winit::event_loop::ControlFlow; @@ -167,8 +169,9 @@ where if let Some(event) = event.to_static() { sender.start_send(event).expect("Send event"); - let mut context = futures::task::Context::from_waker(&waker); - let _ = event_logic.as_mut().poll(&mut context); + if let Poll::Ready(_) = event_logic.as_mut().poll(&mut context) { + panic!("Event logic has stopped running!"); + } } }); } @@ -177,7 +180,7 @@ where /// settings. /// /// [`Application`]: trait.Application.html -pub async fn process_events( +async fn process_events( window: winit::window::Window, proxy: Proxy, mut debug: Debug, diff --git a/winit/src/compositor.rs b/winit/src/compositor.rs new file mode 100644 index 00000000..f6ce3075 --- /dev/null +++ b/winit/src/compositor.rs @@ -0,0 +1,7 @@ +use crate::Size; + +pub trait Compositor { + fn window(&self) -> &winit::window::Window; + + fn resize(&self, new_size: Size); +} From ed2b9a91b44a392439828bd0ac7f979c0df61c45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 4 Nov 2020 21:33:18 +0100 Subject: [PATCH 03/10] Initialize runtime values in `application::run` --- winit/src/application.rs | 90 ++++++++++++++++++++-------------------- 1 file changed, 46 insertions(+), 44 deletions(-) diff --git a/winit/src/application.rs b/winit/src/application.rs index b34345d7..50f135b8 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -121,35 +121,54 @@ where C: window::Compositor + 'static, { use futures::task::Poll; - use futures::{Future, FutureExt}; + use futures::Future; use winit::event_loop::EventLoop; let mut debug = Debug::new(); debug.startup_started(); - let event_loop: EventLoop = EventLoop::with_user_event(); + let (compositor, renderer) = C::new(compositor_settings)?; + + let event_loop = EventLoop::with_user_event(); + + let mut runtime = { + let proxy = Proxy::new(event_loop.create_proxy()); + let executor = E::new().map_err(Error::ExecutorCreationFailed)?; + + Runtime::new(executor, proxy) + }; + + let (application, init_command) = { + let flags = settings.flags; + runtime.enter(|| A::new(flags)) + }; + + let subscription = application.subscription(); + + runtime.spawn(init_command); + runtime.track(subscription); let window = settings .window - .into_builder("", Mode::Windowed, event_loop.primary_monitor()) + .into_builder( + &application.title(), + application.mode(), + event_loop.primary_monitor(), + ) .build(&event_loop) .map_err(Error::WindowCreationFailed)?; let (mut sender, receiver) = mpsc::unbounded(); - let proxy = Proxy::new(event_loop.create_proxy()); - let flags = settings.flags; - let mut event_logic = Box::pin( - process_events::( - window, - proxy, - debug, - flags, - compositor_settings, - receiver, - ) - .map(|_| ()), - ); + let mut event_logic = Box::pin(process_events::( + application, + compositor, + renderer, + window, + runtime, + debug, + receiver, + )); let mut context = futures::task::Context::from_waker(futures::task::noop_waker_ref()); @@ -181,14 +200,14 @@ where /// /// [`Application`]: trait.Application.html async fn process_events( + mut application: A, + mut compositor: C, + mut renderer: A::Renderer, window: winit::window::Window, - proxy: Proxy, + mut runtime: Runtime, A::Message>, mut debug: Debug, - flags: A::Flags, - compositor_settings: C::Settings, mut receiver: mpsc::UnboundedReceiver>, -) -> Result<(), Error> -where +) where A: Application + 'static, E: Executor + 'static, C: window::Compositor + 'static, @@ -196,29 +215,11 @@ where use iced_futures::futures::stream::StreamExt; use winit::event; - let mut runtime = { - let executor = E::new().map_err(Error::ExecutorCreationFailed)?; - - Runtime::new(executor, proxy) - }; - - let (mut application, init_command) = runtime.enter(|| A::new(flags)); - runtime.spawn(init_command); - - 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(); - 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 physical_size = window.inner_size(); let mut viewport = Viewport::with_physical_size( Size::new(physical_size.width, physical_size.height), @@ -226,16 +227,19 @@ where ); let mut resized = false; - let (mut compositor, mut renderer) = C::new(compositor_settings)?; - let surface = compositor.create_surface(&window); - let mut swap_chain = compositor.create_swap_chain( &surface, physical_size.width, physical_size.height, ); + 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 user_interface = std::mem::ManuallyDrop::new(build_user_interface( &mut application, Cache::default(), @@ -432,8 +436,6 @@ where _ => {} } } - - Ok(()) } /// Handles a `WindowEvent` and mutates the provided control flow to exit From fee46fd653c716328d0afaee8e5558e5b408ad24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 4 Nov 2020 22:46:32 +0100 Subject: [PATCH 04/10] Introduce `application::State` in `iced_winit` --- graphics/src/viewport.rs | 2 +- winit/src/application.rs | 180 +++++---------------------------- winit/src/application/state.rs | 171 +++++++++++++++++++++++++++++++ winit/src/compositor.rs | 7 -- winit/src/lib.rs | 2 +- 5 files changed, 201 insertions(+), 161 deletions(-) create mode 100644 winit/src/application/state.rs delete mode 100644 winit/src/compositor.rs diff --git a/graphics/src/viewport.rs b/graphics/src/viewport.rs index 66122e6d..bc7c5807 100644 --- a/graphics/src/viewport.rs +++ b/graphics/src/viewport.rs @@ -1,7 +1,7 @@ use crate::{Size, Transformation}; /// A viewing region for displaying computer graphics. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Viewport { physical_size: Size, logical_size: Size, diff --git a/winit/src/application.rs b/winit/src/application.rs index 50f135b8..e868a099 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -1,4 +1,8 @@ //! Create interactive, native cross-platform applications. +mod state; + +pub use state::State; + use crate::conversion; use crate::mouse; use crate::{ @@ -6,7 +10,6 @@ use crate::{ Settings, Size, Subscription, }; use iced_graphics::window; -use iced_graphics::Viewport; use iced_native::program::Program; use iced_native::{Cache, UserInterface}; @@ -215,19 +218,11 @@ async fn process_events( use iced_futures::futures::stream::StreamExt; use winit::event; - let mut title = application.title(); - let mut mode = application.mode(); - let mut background_color = application.background_color(); - let mut scale_factor = application.scale_factor(); - - 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 state = State::new(&application, &window); let surface = compositor.create_surface(&window); + let mut physical_size = state.physical_size(); + let mut swap_chain = compositor.create_swap_chain( &surface, physical_size.width, @@ -235,23 +230,19 @@ async fn process_events( ); 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 user_interface = std::mem::ManuallyDrop::new(build_user_interface( &mut application, Cache::default(), &mut renderer, - viewport.logical_size(), + state.logical_size(), &mut debug, )); - let mut primitive = user_interface.draw( - &mut renderer, - conversion::cursor_position(cursor_position, viewport.scale_factor()), - ); + let mut primitive = + user_interface.draw(&mut renderer, state.cursor_position()); + let mut mouse_interaction = mouse::Interaction::default(); + let mut events = Vec::new(); let mut external_messages = Vec::new(); @@ -267,10 +258,7 @@ async fn process_events( debug.event_processing_started(); let mut messages = user_interface.update( &events, - conversion::cursor_position( - cursor_position, - viewport.scale_factor(), - ), + state.cursor_position(), clipboard.as_ref().map(|c| c as _), &mut renderer, ); @@ -281,13 +269,8 @@ async fn process_events( if messages.is_empty() { debug.draw_started(); - primitive = user_interface.draw( - &mut renderer, - conversion::cursor_position( - cursor_position, - viewport.scale_factor(), - ), - ); + primitive = user_interface + .draw(&mut renderer, state.cursor_position()); debug.draw_finished(); } else { let cache = @@ -309,61 +292,21 @@ async fn process_events( let subscription = application.subscription(); runtime.track(subscription); - // Update window title - let new_title = application.title(); - - if title != new_title { - window.set_title(&new_title); - - title = new_title; - } - - // Update window mode - let new_mode = application.mode(); - - if mode != new_mode { - window.set_fullscreen(conversion::fullscreen( - window.current_monitor(), - new_mode, - )); - - mode = new_mode; - } - - // Update background color - background_color = application.background_color(); - - // Update scale factor - let new_scale_factor = application.scale_factor(); - - if scale_factor != new_scale_factor { - let size = window.inner_size(); - - viewport = Viewport::with_physical_size( - Size::new(size.width, size.height), - window.scale_factor() * new_scale_factor, - ); - - scale_factor = new_scale_factor; - } + // Update window + state.synchronize(&application, &window); user_interface = std::mem::ManuallyDrop::new(build_user_interface( &mut application, cache, &mut renderer, - viewport.logical_size(), + state.logical_size(), &mut debug, )); debug.draw_started(); - primitive = user_interface.draw( - &mut renderer, - conversion::cursor_position( - cursor_position, - viewport.scale_factor(), - ), - ); + primitive = user_interface + .draw(&mut renderer, state.cursor_position()); debug.draw_finished(); } @@ -375,23 +318,23 @@ async fn process_events( event::Event::RedrawRequested(_) => { debug.render_started(); - if resized { - let physical_size = viewport.physical_size(); + let current_physical_size = state.physical_size(); + if physical_size != current_physical_size { swap_chain = compositor.create_swap_chain( &surface, physical_size.width, physical_size.height, ); - resized = false; + physical_size = current_physical_size; } let new_mouse_interaction = compositor.draw( &mut renderer, &mut swap_chain, - &viewport, - background_color, + state.viewport(), + state.background_color(), &primitive, &debug.overlay(), ); @@ -413,21 +356,12 @@ async fn process_events( event: window_event, .. } => { - handle_window_event( - &window_event, - &window, - scale_factor, - &mut cursor_position, - &mut modifiers, - &mut viewport, - &mut resized, - &mut debug, - ); + state.update(&window, &window_event, &mut debug); if let Some(event) = conversion::window_event( &window_event, - viewport.scale_factor(), - modifiers, + state.scale_factor(), + state.modifiers(), ) { events.push(event.clone()); runtime.broadcast(event); @@ -469,64 +403,6 @@ pub fn handle_control_flow( /// Handles a `WindowEvent` and mutates the keyboard modifiers, viewport, and /// resized flag accordingly. -pub fn handle_window_event( - event: &winit::event::WindowEvent<'_>, - window: &winit::window::Window, - scale_factor: f64, - cursor_position: &mut winit::dpi::PhysicalPosition, - modifiers: &mut winit::event::ModifiersState, - viewport: &mut Viewport, - resized: &mut bool, - _debug: &mut Debug, -) { - 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::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; - } - #[cfg(feature = "debug")] - WindowEvent::KeyboardInput { - input: - winit::event::KeyboardInput { - virtual_keycode: Some(winit::event::VirtualKeyCode::F12), - state: winit::event::ElementState::Pressed, - .. - }, - .. - } => _debug.toggle(), - _ => {} - } -} - fn build_user_interface<'a, A: Application>( application: &'a mut A, cache: Cache, diff --git a/winit/src/application/state.rs b/winit/src/application/state.rs new file mode 100644 index 00000000..7de3ecef --- /dev/null +++ b/winit/src/application/state.rs @@ -0,0 +1,171 @@ +use crate::conversion; +use crate::{Application, Color, Debug, Mode, Point, Size, Viewport}; + +use std::marker::PhantomData; +use winit::event::WindowEvent; +use winit::window::Window; + +#[derive(Debug, Clone)] +pub struct State { + title: String, + mode: Mode, + background_color: Color, + scale_factor: f64, + viewport: Viewport, + cursor_position: winit::dpi::PhysicalPosition, + modifiers: winit::event::ModifiersState, + application: PhantomData, +} + +impl State { + 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, + // 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, + } + } + + pub fn background_color(&self) -> Color { + self.background_color + } + + pub fn viewport(&self) -> &Viewport { + &self.viewport + } + + pub fn physical_size(&self) -> Size { + self.viewport.physical_size() + } + + pub fn logical_size(&self) -> Size { + self.viewport.logical_size() + } + + pub fn scale_factor(&self) -> f64 { + self.viewport.scale_factor() + } + + pub fn cursor_position(&self) -> Point { + conversion::cursor_position( + self.cursor_position, + self.viewport.scale_factor(), + ) + } + + pub fn modifiers(&self) -> winit::event::ModifiersState { + self.modifiers + } + + 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, + ); + } + 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, + ); + } + 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(), + _ => {} + } + } + + 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; + } + } +} diff --git a/winit/src/compositor.rs b/winit/src/compositor.rs deleted file mode 100644 index f6ce3075..00000000 --- a/winit/src/compositor.rs +++ /dev/null @@ -1,7 +0,0 @@ -use crate::Size; - -pub trait Compositor { - fn window(&self) -> &winit::window::Window; - - fn resize(&self, new_size: Size); -} diff --git a/winit/src/lib.rs b/winit/src/lib.rs index 8ca8eec1..91e7774c 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -15,7 +15,7 @@ //! [`winit`]: https://github.com/rust-windowing/winit //! [`Application`]: trait.Application.html //! [`conversion`]: conversion -#![deny(missing_docs)] +//#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![forbid(unsafe_code)] From 86b26f65d6e28698d9e6b2d8356105847b0d64c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 5 Nov 2020 01:48:47 +0100 Subject: [PATCH 05/10] Handle event loop `ControlFlow` in `iced_winit` --- winit/src/application.rs | 56 +++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 32 deletions(-) diff --git a/winit/src/application.rs b/winit/src/application.rs index e868a099..dcc5a282 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -123,7 +123,7 @@ where E: Executor + 'static, C: window::Compositor + 'static, { - use futures::task::Poll; + use futures::task; use futures::Future; use winit::event_loop::EventLoop; @@ -163,7 +163,7 @@ where let (mut sender, receiver) = mpsc::unbounded(); - let mut event_logic = Box::pin(process_events::( + let mut instance = Box::pin(run_instance::( application, compositor, renderer, @@ -173,36 +173,29 @@ where receiver, )); - let mut context = - futures::task::Context::from_waker(futures::task::noop_waker_ref()); + let mut context = task::Context::from_waker(task::noop_waker_ref()); event_loop.run(move |event, _, control_flow| { use winit::event_loop::ControlFlow; - match event { - winit::event::Event::WindowEvent { ref event, .. } => { - handle_control_flow(event, control_flow); - } - _ => { - *control_flow = ControlFlow::Wait; - } + if let ControlFlow::Exit = control_flow { + return; } if let Some(event) = event.to_static() { sender.start_send(event).expect("Send event"); - if let Poll::Ready(_) = event_logic.as_mut().poll(&mut context) { - panic!("Event logic has stopped running!"); - } + let poll = instance.as_mut().poll(&mut context); + + *control_flow = match poll { + task::Poll::Pending => ControlFlow::Wait, + task::Poll::Ready(_) => ControlFlow::Exit, + }; } }); } -/// Runs an [`Application`] with an executor, compositor, and the provided -/// settings. -/// -/// [`Application`]: trait.Application.html -async fn process_events( +async fn run_instance( mut application: A, mut compositor: C, mut renderer: A::Renderer, @@ -356,6 +349,10 @@ async fn process_events( event: window_event, .. } => { + if requests_exit(&window_event, state.modifiers()) { + break; + } + state.update(&window, &window_event, &mut debug); if let Some(event) = conversion::window_event( @@ -372,19 +369,16 @@ async fn process_events( } } -/// Handles a `WindowEvent` and mutates the provided control flow to exit -/// if necessary. -pub fn handle_control_flow( +/// Returns true if the provided event should cause the [`Application`] to +/// exit. +pub fn requests_exit( event: &winit::event::WindowEvent<'_>, - control_flow: &mut winit::event_loop::ControlFlow, -) { + _modifiers: winit::event::ModifiersState, +) -> bool { use winit::event::WindowEvent; - use winit::event_loop::ControlFlow; match event { - WindowEvent::CloseRequested => { - *control_flow = ControlFlow::Exit; - } + WindowEvent::CloseRequested => true, #[cfg(target_os = "macos")] WindowEvent::KeyboardInput { input: @@ -394,10 +388,8 @@ pub fn handle_control_flow( .. }, .. - } if modifiers.logo() => { - *control_flow = ControlFlow::Exit; - } - _ => {} + } if _modifiers.logo() => true, + _ => false, } } From 88993fb092fb0391ea42ffd725f680d1c98c95d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 5 Nov 2020 02:11:11 +0100 Subject: [PATCH 06/10] Relayout `UserInterface` on resize in `iced_winit` --- native/src/user_interface.rs | 17 ++++++++++++ winit/src/application.rs | 49 ++++++++++++++++++++++------------ winit/src/application/state.rs | 10 +++++++ 3 files changed, 59 insertions(+), 17 deletions(-) diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 59d91f42..504dbe0f 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -388,6 +388,23 @@ where } } + /// Relayouts and returns a new [`UserInterface`] using the provided + /// bounds. + /// + /// [`UserInterface`]: struct.UserInterface.html + pub fn relayout(self, bounds: Size, renderer: &mut Renderer) -> Self { + Self::build( + self.root, + bounds, + Cache { + base: self.base, + overlay: self.overlay, + bounds: self.bounds, + }, + renderer, + ) + } + /// Extract the [`Cache`] of the [`UserInterface`], consuming it in the /// process. /// diff --git a/winit/src/application.rs b/winit/src/application.rs index dcc5a282..96438d73 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -9,12 +9,14 @@ 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_native::program::Program; use iced_native::{Cache, UserInterface}; -use iced_futures::futures; -use iced_futures::futures::channel::mpsc; +use std::mem::ManuallyDrop; /// An interactive, native cross-platform application. /// @@ -214,8 +216,9 @@ async fn run_instance( let mut state = State::new(&application, &window); let surface = compositor.create_surface(&window); - let mut physical_size = state.physical_size(); + let physical_size = state.physical_size(); + let mut viewport_version = state.viewport_version(); let mut swap_chain = compositor.create_swap_chain( &surface, physical_size.width, @@ -224,7 +227,7 @@ async fn run_instance( let clipboard = Clipboard::new(&window); - let mut user_interface = std::mem::ManuallyDrop::new(build_user_interface( + let mut user_interface = ManuallyDrop::new(build_user_interface( &mut application, Cache::default(), &mut renderer, @@ -267,8 +270,7 @@ async fn run_instance( debug.draw_finished(); } else { let cache = - std::mem::ManuallyDrop::into_inner(user_interface) - .into_cache(); + ManuallyDrop::into_inner(user_interface).into_cache(); for message in messages.drain(..) { debug.log_message(&message); @@ -288,14 +290,13 @@ async fn run_instance( // Update window state.synchronize(&application, &window); - user_interface = - std::mem::ManuallyDrop::new(build_user_interface( - &mut application, - cache, - &mut renderer, - state.logical_size(), - &mut debug, - )); + user_interface = ManuallyDrop::new(build_user_interface( + &mut application, + cache, + &mut renderer, + state.logical_size(), + &mut debug, + )); debug.draw_started(); primitive = user_interface @@ -310,17 +311,31 @@ async fn run_instance( } event::Event::RedrawRequested(_) => { debug.render_started(); + let current_viewport_version = state.viewport_version(); - let current_physical_size = state.physical_size(); + 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(); + + debug.draw_started(); + primitive = user_interface + .draw(&mut renderer, state.cursor_position()); + debug.draw_finished(); - if physical_size != current_physical_size { swap_chain = compositor.create_swap_chain( &surface, physical_size.width, physical_size.height, ); - physical_size = current_physical_size; + viewport_version = current_viewport_version; } let new_mouse_interaction = compositor.draw( diff --git a/winit/src/application/state.rs b/winit/src/application/state.rs index 7de3ecef..1f3c77a0 100644 --- a/winit/src/application/state.rs +++ b/winit/src/application/state.rs @@ -12,6 +12,7 @@ pub struct State { background_color: Color, scale_factor: f64, viewport: Viewport, + viewport_version: usize, cursor_position: winit::dpi::PhysicalPosition, modifiers: winit::event::ModifiersState, application: PhantomData, @@ -39,6 +40,7 @@ impl State { 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(), @@ -54,6 +56,10 @@ impl State { &self.viewport } + pub fn viewport_version(&self) -> usize { + self.viewport_version + } + pub fn physical_size(&self) -> Size { self.viewport.physical_size() } @@ -91,6 +97,8 @@ impl State { size, window.scale_factor() * self.scale_factor, ); + + self.viewport_version = self.viewport_version.wrapping_add(1); } WindowEvent::ScaleFactorChanged { scale_factor: new_scale_factor, @@ -103,6 +111,8 @@ impl State { size, new_scale_factor * self.scale_factor, ); + + self.viewport_version = self.viewport_version.wrapping_add(1); } WindowEvent::CursorMoved { position, .. } => { self.cursor_position = *position; From 6619fab0441ac0770bd95f5c69e80289bc323d0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 5 Nov 2020 04:08:10 +0100 Subject: [PATCH 07/10] Update `iced_glutin` with new event loop logic --- glutin/src/application.rs | 431 +++++++++++++++++++++----------------- winit/src/application.rs | 3 +- 2 files changed, 243 insertions(+), 191 deletions(-) diff --git a/glutin/src/application.rs b/glutin/src/application.rs index 8718b81f..b8e80ddf 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -1,13 +1,16 @@ //! Create interactive, native cross-platform applications. use crate::{mouse, Error, Executor, Runtime, Size}; -use iced_graphics::window; -use iced_graphics::Viewport; -use iced_winit::application; -use iced_winit::conversion; -use iced_winit::{Clipboard, Debug, Proxy, Settings}; -pub use iced_winit::Application; -pub use iced_winit::{program, Program}; +pub use iced_winit::application::{self, Application}; + +use iced_graphics::window; +use iced_winit::conversion; +use iced_winit::futures; +use iced_winit::futures::channel::mpsc; +use iced_winit::{Cache, Clipboard, Debug, Proxy, Settings, UserInterface}; + +use glutin::window::Window; +use std::mem::ManuallyDrop; /// Runs an [`Application`] with an executor, compositor, and the provided /// settings. @@ -22,11 +25,10 @@ where E: Executor + 'static, C: window::GLCompositor + 'static, { - use glutin::{ - event, - event_loop::{ControlFlow, EventLoop}, - ContextBuilder, - }; + use futures::task; + use futures::Future; + use glutin::event_loop::EventLoop; + use glutin::ContextBuilder; let mut debug = Debug::new(); debug.startup_started(); @@ -39,22 +41,21 @@ where 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 context = { let builder = settings.window.into_builder( - &title, - mode, + &application.title(), + application.mode(), event_loop.primary_monitor(), ); @@ -79,190 +80,242 @@ where } }; - let clipboard = Clipboard::new(&context.window()); - let mut cursor_position = glutin::dpi::PhysicalPosition::new(-1.0, -1.0); - let mut mouse_interaction = mouse::Interaction::default(); - let mut modifiers = glutin::event::ModifiersState::default(); - - let physical_size = context.window().inner_size(); - let mut viewport = Viewport::with_physical_size( - Size::new(physical_size.width, physical_size.height), - context.window().scale_factor() * scale_factor, - ); - let mut resized = false; - #[allow(unsafe_code)] - let (mut compositor, mut renderer) = unsafe { + let (compositor, renderer) = unsafe { C::new(compositor_settings, |address| { context.get_proc_address(address) })? }; - let mut state = program::State::new( + let (mut sender, receiver) = mpsc::unbounded(); + + let mut instance = Box::pin(run_instance::( application, - viewport.logical_size(), - conversion::cursor_position(cursor_position, viewport.scale_factor()), + compositor, + renderer, + context, + runtime, + debug, + receiver, + )); + + let mut context = task::Context::from_waker(task::noop_waker_ref()); + + event_loop.run(move |event, _, control_flow| { + use glutin::event_loop::ControlFlow; + + if let ControlFlow::Exit = control_flow { + return; + } + + if let Some(event) = event.to_static() { + sender.start_send(event).expect("Send event"); + + let poll = instance.as_mut().poll(&mut context); + + *control_flow = match poll { + task::Poll::Pending => ControlFlow::Wait, + task::Poll::Ready(_) => ControlFlow::Exit, + }; + } + }); +} + +async fn run_instance( + mut application: A, + mut compositor: C, + mut renderer: A::Renderer, + context: glutin::ContextWrapper, + mut runtime: Runtime, A::Message>, + mut debug: Debug, + mut receiver: mpsc::UnboundedReceiver>, +) where + A: Application + 'static, + E: Executor + 'static, + C: window::GLCompositor + 'static, +{ + use glutin::event; + use iced_winit::futures::stream::StreamExt; + + let mut state = application::State::new(&application, context.window()); + let mut viewport_version = state.viewport_version(); + + let clipboard = Clipboard::new(context.window()); + + let mut user_interface = ManuallyDrop::new(build_user_interface( + &mut application, + Cache::default(), &mut renderer, + state.logical_size(), &mut debug, - ); + )); + + let mut primitive = + user_interface.draw(&mut renderer, state.cursor_position()); + let mut mouse_interaction = mouse::Interaction::default(); + + let mut events = Vec::new(); + let mut external_messages = Vec::new(); + debug.startup_finished(); - event_loop.run(move |event, _, control_flow| match event { - event::Event::MainEventsCleared => { - if state.is_queue_empty() { - return; - } + while let Some(event) = receiver.next().await { + match event { + event::Event::MainEventsCleared => { + if events.is_empty() && external_messages.is_empty() { + continue; + } - let command = runtime.enter(|| { - state.update( - viewport.logical_size(), - conversion::cursor_position( - cursor_position, - viewport.scale_factor(), - ), + debug.event_processing_started(); + let mut messages = user_interface.update( + &events, + state.cursor_position(), clipboard.as_ref().map(|c| c as _), &mut renderer, - &mut debug, - ) - }); - - // If the application was updated - if let Some(command) = command { - runtime.spawn(command); - - let program = state.program(); - - // Update subscriptions - let subscription = program.subscription(); - runtime.track(subscription); - - // Update window title - let new_title = program.title(); - - if title != new_title { - context.window().set_title(&new_title); - - title = new_title; - } - - // Update window mode - let new_mode = program.mode(); - - if mode != new_mode { - context.window().set_fullscreen(conversion::fullscreen( - context.window().current_monitor(), - new_mode, - )); - - mode = new_mode; - } - - // Update background color - background_color = program.background_color(); - - // Update scale factor - let new_scale_factor = program.scale_factor(); - - if scale_factor != new_scale_factor { - let size = context.window().inner_size(); - - viewport = Viewport::with_physical_size( - Size::new(size.width, size.height), - context.window().scale_factor() * new_scale_factor, - ); - - // 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 _), - &mut renderer, - &mut debug, - ); - - scale_factor = new_scale_factor; - } - } - - context.window().request_redraw(); - } - event::Event::UserEvent(message) => { - state.queue_message(message); - } - event::Event::RedrawRequested(_) => { - debug.render_started(); - - if resized { - let physical_size = viewport.physical_size(); - - context.resize(glutin::dpi::PhysicalSize::new( - physical_size.width, - physical_size.height, - )); - - compositor.resize_viewport(physical_size); - - resized = false; - } - - let new_mouse_interaction = compositor.draw( - &mut renderer, - &viewport, - background_color, - state.primitive(), - &debug.overlay(), - ); - - context.swap_buffers().expect("Swap buffers"); - - debug.render_finished(); - - if new_mouse_interaction != mouse_interaction { - context.window().set_cursor_icon( - conversion::mouse_interaction(new_mouse_interaction), ); - mouse_interaction = new_mouse_interaction; + messages.extend(external_messages.drain(..)); + events.clear(); + debug.event_processing_finished(); + + if messages.is_empty() { + debug.draw_started(); + primitive = user_interface + .draw(&mut renderer, state.cursor_position()); + debug.draw_finished(); + } else { + let cache = + ManuallyDrop::into_inner(user_interface).into_cache(); + + for message in messages.drain(..) { + debug.log_message(&message); + + debug.update_started(); + let command = + runtime.enter(|| application.update(message)); + debug.update_finished(); + + runtime.spawn(command); + } + + // Update subscriptions + let subscription = application.subscription(); + runtime.track(subscription); + + // Update window + state.synchronize(&application, context.window()); + + user_interface = ManuallyDrop::new(build_user_interface( + &mut application, + cache, + &mut renderer, + state.logical_size(), + &mut debug, + )); + + debug.draw_started(); + primitive = user_interface + .draw(&mut renderer, state.cursor_position()); + debug.draw_finished(); + } + + context.window().request_redraw(); } - - // TODO: Handle animations! - // Maybe we can use `ControlFlow::WaitUntil` for this. - } - event::Event::WindowEvent { - event: window_event, - .. - } => { - application::handle_control_flow(&window_event, control_flow); - - application::handle_window_event( - &window_event, - context.window(), - scale_factor, - &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); + 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(); + + debug.draw_started(); + primitive = user_interface + .draw(&mut renderer, state.cursor_position()); + debug.draw_finished(); + + context.resize(glutin::dpi::PhysicalSize::new( + physical_size.width, + physical_size.height, + )); + + compositor.resize_viewport(physical_size); + + viewport_version = current_viewport_version; + } + + let new_mouse_interaction = compositor.draw( + &mut renderer, + state.viewport(), + state.background_color(), + &primitive, + &debug.overlay(), + ); + + context.swap_buffers().expect("Swap buffers"); + + debug.render_finished(); + + if new_mouse_interaction != mouse_interaction { + context.window().set_cursor_icon( + conversion::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 application::requests_exit(&window_event, state.modifiers()) + { + break; + } + + state.update(context.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; - } - }) + } +} + +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 } diff --git a/winit/src/application.rs b/winit/src/application.rs index 96438d73..07108249 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -145,6 +145,7 @@ where let (application, init_command) = { let flags = settings.flags; + runtime.enter(|| A::new(flags)) }; @@ -408,8 +409,6 @@ pub fn requests_exit( } } -/// Handles a `WindowEvent` and mutates the keyboard modifiers, viewport, and -/// resized flag accordingly. fn build_user_interface<'a, A: Application>( application: &'a mut A, cache: Cache, From d5a15419e98cf31c173fff5a10f97e36958d994f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 5 Nov 2020 04:11:49 +0100 Subject: [PATCH 08/10] Drop `UserInterface` manually after exit request --- glutin/src/application.rs | 3 +++ winit/src/application.rs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/glutin/src/application.rs b/glutin/src/application.rs index b8e80ddf..0f748ecb 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -300,6 +300,9 @@ async fn run_instance( _ => {} } } + + // Manually drop the user interface + drop(ManuallyDrop::into_inner(user_interface)); } fn build_user_interface<'a, A: Application>( diff --git a/winit/src/application.rs b/winit/src/application.rs index 07108249..3a41a0e4 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -383,6 +383,9 @@ async fn run_instance( _ => {} } } + + // Manually drop the user interface + drop(ManuallyDrop::into_inner(user_interface)); } /// Returns true if the provided event should cause the [`Application`] to From e966cd5b591d8014e53f414014cb49deffef535d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 5 Nov 2020 04:50:57 +0100 Subject: [PATCH 09/10] Remove a bit of code duplication in both shells --- glutin/src/application.rs | 95 ++++++++++++++------------------------- winit/src/application.rs | 83 +++++++++++++++++++--------------- 2 files changed, 79 insertions(+), 99 deletions(-) diff --git a/glutin/src/application.rs b/glutin/src/application.rs index 0f748ecb..26e7b852 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -1,5 +1,5 @@ //! Create interactive, native cross-platform applications. -use crate::{mouse, Error, Executor, Runtime, Size}; +use crate::{mouse, Error, Executor, Runtime}; pub use iced_winit::application::{self, Application}; @@ -7,7 +7,7 @@ use iced_graphics::window; use iced_winit::conversion; use iced_winit::futures; use iced_winit::futures::channel::mpsc; -use iced_winit::{Cache, Clipboard, Debug, Proxy, Settings, UserInterface}; +use iced_winit::{Cache, Clipboard, Debug, Proxy, Settings}; use glutin::window::Window; use std::mem::ManuallyDrop; @@ -137,18 +137,18 @@ async fn run_instance( use glutin::event; use iced_winit::futures::stream::StreamExt; - let mut state = application::State::new(&application, context.window()); - let mut viewport_version = state.viewport_version(); - let clipboard = Clipboard::new(context.window()); - let mut user_interface = ManuallyDrop::new(build_user_interface( - &mut application, - Cache::default(), - &mut renderer, - state.logical_size(), - &mut debug, - )); + let mut state = application::State::new(&application, context.window()); + let mut viewport_version = state.viewport_version(); + let mut user_interface = + ManuallyDrop::new(application::build_user_interface( + &mut application, + Cache::default(), + &mut renderer, + state.logical_size(), + &mut debug, + )); let mut primitive = user_interface.draw(&mut renderer, state.cursor_position()); @@ -178,47 +178,36 @@ async fn run_instance( events.clear(); debug.event_processing_finished(); - if messages.is_empty() { - debug.draw_started(); - primitive = user_interface - .draw(&mut renderer, state.cursor_position()); - debug.draw_finished(); - } else { + if !messages.is_empty() { let cache = ManuallyDrop::into_inner(user_interface).into_cache(); - for message in messages.drain(..) { - debug.log_message(&message); - - debug.update_started(); - let command = - runtime.enter(|| application.update(message)); - debug.update_finished(); - - runtime.spawn(command); - } - - // Update subscriptions - let subscription = application.subscription(); - runtime.track(subscription); + // Update application + application::update( + &mut application, + &mut runtime, + &mut debug, + messages, + ); // Update window state.synchronize(&application, context.window()); - user_interface = ManuallyDrop::new(build_user_interface( - &mut application, - cache, - &mut renderer, - state.logical_size(), - &mut debug, - )); - - debug.draw_started(); - primitive = user_interface - .draw(&mut renderer, state.cursor_position()); - debug.draw_finished(); + user_interface = + ManuallyDrop::new(application::build_user_interface( + &mut application, + cache, + &mut renderer, + state.logical_size(), + &mut debug, + )); } + debug.draw_started(); + primitive = + user_interface.draw(&mut renderer, state.cursor_position()); + debug.draw_finished(); + context.window().request_redraw(); } event::Event::UserEvent(message) => { @@ -304,21 +293,3 @@ async fn run_instance( // Manually drop the user interface drop(ManuallyDrop::into_inner(user_interface)); } - -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 -} diff --git a/winit/src/application.rs b/winit/src/application.rs index 3a41a0e4..8e97706f 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -214,20 +214,21 @@ async fn run_instance( use iced_futures::futures::stream::StreamExt; use winit::event; - let mut state = State::new(&application, &window); - let surface = compositor.create_surface(&window); - let physical_size = state.physical_size(); - - let mut viewport_version = state.viewport_version(); - let mut swap_chain = compositor.create_swap_chain( - &surface, - physical_size.width, - physical_size.height, - ); - let clipboard = Clipboard::new(&window); + let mut state = State::new(&application, &window); + let mut viewport_version = state.viewport_version(); + let mut swap_chain = { + let physical_size = state.physical_size(); + + compositor.create_swap_chain( + &surface, + physical_size.width, + physical_size.height, + ) + }; + let mut user_interface = ManuallyDrop::new(build_user_interface( &mut application, Cache::default(), @@ -264,29 +265,17 @@ async fn run_instance( events.clear(); debug.event_processing_finished(); - if messages.is_empty() { - debug.draw_started(); - primitive = user_interface - .draw(&mut renderer, state.cursor_position()); - debug.draw_finished(); - } else { + if !messages.is_empty() { let cache = ManuallyDrop::into_inner(user_interface).into_cache(); - for message in messages.drain(..) { - debug.log_message(&message); - - debug.update_started(); - let command = - runtime.enter(|| application.update(message)); - debug.update_finished(); - - runtime.spawn(command); - } - - // Update subscriptions - let subscription = application.subscription(); - runtime.track(subscription); + // Update application + update( + &mut application, + &mut runtime, + &mut debug, + messages, + ); // Update window state.synchronize(&application, &window); @@ -298,13 +287,13 @@ async fn run_instance( state.logical_size(), &mut debug, )); - - debug.draw_started(); - primitive = user_interface - .draw(&mut renderer, state.cursor_position()); - debug.draw_finished(); } + debug.draw_started(); + primitive = + user_interface.draw(&mut renderer, state.cursor_position()); + debug.draw_finished(); + window.request_redraw(); } event::Event::UserEvent(message) => { @@ -412,7 +401,7 @@ pub fn requests_exit( } } -fn build_user_interface<'a, A: Application>( +pub fn build_user_interface<'a, A: Application>( application: &'a mut A, cache: Cache, renderer: &mut A::Renderer, @@ -429,3 +418,23 @@ fn build_user_interface<'a, A: Application>( user_interface } + +pub fn update( + application: &mut A, + runtime: &mut Runtime, A::Message>, + debug: &mut Debug, + messages: Vec, +) { + 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); +} From 631c9e4a215d8f044d76ea926117f9a9bdd24d5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 6 Nov 2020 02:25:56 +0100 Subject: [PATCH 10/10] Write missing documentation in `iced_winit` --- glutin/src/application.rs | 3 +- winit/src/application.rs | 16 +++++++++- winit/src/application/state.rs | 54 ++++++++++++++++++++++++++++++++++ winit/src/lib.rs | 2 +- 4 files changed, 72 insertions(+), 3 deletions(-) diff --git a/glutin/src/application.rs b/glutin/src/application.rs index 26e7b852..68612978 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -1,9 +1,10 @@ //! Create interactive, native cross-platform applications. use crate::{mouse, Error, Executor, Runtime}; -pub use iced_winit::application::{self, Application}; +pub use iced_winit::Application; use iced_graphics::window; +use iced_winit::application; use iced_winit::conversion; use iced_winit::futures; use iced_winit::futures::channel::mpsc; diff --git a/winit/src/application.rs b/winit/src/application.rs index 8e97706f..c1d86471 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -377,8 +377,10 @@ async fn run_instance( drop(ManuallyDrop::into_inner(user_interface)); } -/// Returns true if the provided event should cause the [`Application`] to +/// Returns true if the provided event should cause an [`Application`] to /// exit. +/// +/// [`Application`]: trait.Application.html pub fn requests_exit( event: &winit::event::WindowEvent<'_>, _modifiers: winit::event::ModifiersState, @@ -401,6 +403,12 @@ pub fn requests_exit( } } +/// 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, @@ -419,6 +427,12 @@ pub fn build_user_interface<'a, A: Application>( 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( application: &mut A, runtime: &mut Runtime, A::Message>, diff --git a/winit/src/application/state.rs b/winit/src/application/state.rs index 1f3c77a0..4c0bfd34 100644 --- a/winit/src/application/state.rs +++ b/winit/src/application/state.rs @@ -5,6 +5,9 @@ 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 { title: String, @@ -19,6 +22,10 @@ pub struct State { } impl State { + /// 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(); @@ -48,30 +55,61 @@ impl State { } } + /// 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 { 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 { 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, @@ -79,10 +117,17 @@ impl State { ) } + /// 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, @@ -139,6 +184,15 @@ impl State { } } + /// 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(); diff --git a/winit/src/lib.rs b/winit/src/lib.rs index 91e7774c..8ca8eec1 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -15,7 +15,7 @@ //! [`winit`]: https://github.com/rust-windowing/winit //! [`Application`]: trait.Application.html //! [`conversion`]: conversion -//#![deny(missing_docs)] +#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![forbid(unsafe_code)]