diff options
Diffstat (limited to 'winit/src/application.rs')
-rw-r--r-- | winit/src/application.rs | 565 |
1 files changed, 279 insertions, 286 deletions
diff --git a/winit/src/application.rs b/winit/src/application.rs index f6bc8fcc..d1a94864 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -1,39 +1,36 @@ +//! Create interactive, native cross-platform applications. +mod state; + +pub use state::State; + +use crate::conversion; +use crate::mouse; use crate::{ - conversion, mouse, size::Size, window, Cache, Clipboard, Command, Debug, - Element, Executor, Mode, Proxy, Runtime, Settings, Subscription, - UserInterface, + 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 std::mem::ManuallyDrop; + /// An interactive, native cross-platform application. /// /// This trait is the main entrypoint of Iced. Once implemented, you can run /// your GUI application by simply calling [`run`](#method.run). It will run in /// its own window. /// -/// An [`Application`](trait.Application.html) can execute asynchronous actions -/// by returning a [`Command`](struct.Command.html) in some of its methods. +/// An [`Application`] can execute asynchronous actions by returning a +/// [`Command`] in some of its methods. /// /// When using an [`Application`] with the `debug` feature enabled, a debug view /// can be toggled by pressing `F12`. -pub trait Application: Sized { - /// The graphics backend to use to draw the [`Application`]. - /// - /// [`Application`]: trait.Application.html - type Backend: window::Backend; - - /// The [`Executor`] that will run commands and subscriptions. - /// - /// [`Executor`]: trait.Executor.html - type Executor: Executor; - - /// The type of __messages__ your [`Application`] will produce. - /// - /// [`Application`]: trait.Application.html - type Message: std::fmt::Debug + Send; - +pub trait Application: Program { /// The data needed to initialize your [`Application`]. - /// - /// [`Application`]: trait.Application.html type Flags; /// Initializes the [`Application`] with the flags provided to @@ -41,36 +38,17 @@ pub trait Application: Sized { /// /// Here is where you should return the initial state of your app. /// - /// Additionally, you can return a [`Command`](struct.Command.html) if you - /// need to perform some async action in the background on startup. This is - /// useful if you want to load state from a file, perform an initial HTTP - /// request, etc. - /// - /// [`Application`]: trait.Application.html - /// [`run`]: #method.run.html - /// [`Settings`]: struct.Settings.html + /// Additionally, you can return a [`Command`] if you need to perform some + /// async action in the background on startup. This is useful if you want to + /// load state from a file, perform an initial HTTP request, etc. fn new(flags: Self::Flags) -> (Self, Command<Self::Message>); /// Returns the current title of the [`Application`]. /// /// This title can be dynamic! The runtime will automatically update the /// title of your application when necessary. - /// - /// [`Application`]: trait.Application.html fn title(&self) -> String; - /// Handles a __message__ and updates the state of the [`Application`]. - /// - /// This is where you define your __update logic__. All the __messages__, - /// produced by either user interactions or commands, will be handled by - /// this method. - /// - /// Any [`Command`] returned will be executed immediately in the background. - /// - /// [`Application`]: trait.Application.html - /// [`Command`]: struct.Command.html - fn update(&mut self, message: Self::Message) -> Command<Self::Message>; - /// Returns the event `Subscription` for the current state of the /// application. /// @@ -78,16 +56,11 @@ pub trait Application: Sized { /// [`update`](#tymethod.update). /// /// A `Subscription` will be kept alive as long as you keep returning it! - fn subscription(&self) -> Subscription<Self::Message>; - - /// Returns the widgets to display in the [`Application`]. /// - /// These widgets can produce __messages__ based on user interaction. - /// - /// [`Application`]: trait.Application.html - fn view( - &mut self, - ) -> Element<'_, Self::Message, <Self::Backend as window::Backend>::Renderer>; + /// By default, it returns an empty subscription. + fn subscription(&self) -> Subscription<Self::Message> { + Subscription::none() + } /// Returns the current [`Application`] mode. /// @@ -95,244 +68,255 @@ pub trait Application: Sized { /// is returned. /// /// By default, an application will run in windowed mode. - /// - /// [`Application`]: trait.Application.html fn mode(&self) -> Mode { Mode::Windowed } - /// Runs the [`Application`] with the provided [`Settings`]. + /// Returns the background [`Color`] of the [`Application`]. /// - /// On native platforms, this method will take control of the current thread - /// and __will NOT return__. + /// By default, it returns [`Color::WHITE`]. + fn background_color(&self) -> Color { + Color::WHITE + } + + /// Returns the scale factor of the [`Application`]. /// - /// It should probably be that last thing you call in your `main` function. + /// It can be used to dynamically control the size of the UI at runtime + /// (i.e. zooming). /// - /// [`Application`]: trait.Application.html - /// [`Settings`]: struct.Settings.html - fn run( - settings: Settings<Self::Flags>, - backend_settings: <Self::Backend as window::Backend>::Settings, - ) where - Self: 'static, - { - use window::Backend as _; - use winit::{ - event::{self, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - window::WindowBuilder, - }; - - let mut debug = Debug::new(); - - debug.startup_started(); - let event_loop = EventLoop::with_user_event(); - let mut external_messages = Vec::new(); - - let mut runtime = { - let executor = Self::Executor::new().expect("Create executor"); - - Runtime::new(executor, Proxy::new(event_loop.create_proxy())) - }; + /// For instance, a scale factor of `2.0` will make widgets twice as big, + /// while a scale factor of `0.5` will shrink them to half their size. + /// + /// By default, it returns `1.0`. + fn scale_factor(&self) -> f64 { + 1.0 + } +} - let flags = settings.flags; - let (mut application, init_command) = - runtime.enter(|| Self::new(flags)); - runtime.spawn(init_command); +/// Runs an [`Application`] with an executor, compositor, and the provided +/// settings. +pub fn run<A, E, C>( + settings: Settings<A::Flags>, + compositor_settings: C::Settings, +) -> Result<(), Error> +where + A: Application + 'static, + E: Executor + 'static, + C: window::Compositor<Renderer = A::Renderer> + 'static, +{ + use futures::task; + use futures::Future; + use winit::event_loop::EventLoop; - let subscription = application.subscription(); - runtime.track(subscription); + let mut debug = Debug::new(); + debug.startup_started(); - let mut title = application.title(); - let mut mode = application.mode(); + let (compositor, renderer) = C::new(compositor_settings)?; - let window = { - let mut window_builder = WindowBuilder::new(); + let event_loop = EventLoop::with_user_event(); - let (width, height) = settings.window.size; + let mut runtime = { + let proxy = Proxy::new(event_loop.create_proxy()); + let executor = E::new().map_err(Error::ExecutorCreationFailed)?; - window_builder = window_builder - .with_title(&title) - .with_inner_size(winit::dpi::LogicalSize { width, height }) - .with_resizable(settings.window.resizable) - .with_decorations(settings.window.decorations) - .with_fullscreen(conversion::fullscreen( - event_loop.primary_monitor(), - mode, - )); + Runtime::new(executor, proxy) + }; - #[cfg(target_os = "windows")] - { - use winit::platform::windows::WindowBuilderExtWindows; + let (application, init_command) = { + let flags = settings.flags; - if let Some(parent) = settings.window.platform_specific.parent { - window_builder = window_builder.with_parent_window(parent); - } - } + runtime.enter(|| A::new(flags)) + }; - window_builder.build(&event_loop).expect("Open window") - }; + let subscription = application.subscription(); - let mut size = Size::new(window.inner_size(), window.scale_factor()); - let mut resized = false; + runtime.spawn(init_command); + runtime.track(subscription); - let clipboard = Clipboard::new(&window); - let (mut backend, mut renderer) = Self::Backend::new(backend_settings); + let window = settings + .window + .into_builder( + &application.title(), + application.mode(), + event_loop.primary_monitor(), + ) + .build(&event_loop) + .map_err(Error::WindowCreationFailed)?; - let surface = backend.create_surface(&window); + let (mut sender, receiver) = mpsc::unbounded(); - let mut swap_chain = { - let physical_size = size.physical(); + let mut instance = Box::pin(run_instance::<A, E, C>( + application, + compositor, + renderer, + window, + runtime, + debug, + receiver, + )); + + let mut context = task::Context::from_waker(task::noop_waker_ref()); - backend.create_swap_chain( - &surface, - physical_size.width, - physical_size.height, - ) - }; + event_loop.run(move |event, _, control_flow| { + use winit::event_loop::ControlFlow; - let user_interface = build_user_interface( - &mut application, - Cache::default(), - &mut renderer, - size.logical(), - &mut debug, - ); + if let ControlFlow::Exit = control_flow { + return; + } - debug.draw_started(); - let mut primitive = user_interface.draw(&mut renderer); - debug.draw_finished(); + if let Some(event) = event.to_static() { + sender.start_send(event).expect("Send event"); - let mut cache = Some(user_interface.into_cache()); - let mut events = Vec::new(); - let mut mouse_interaction = mouse::Interaction::default(); - let mut modifiers = winit::event::ModifiersState::default(); - debug.startup_finished(); + let poll = instance.as_mut().poll(&mut context); - window.request_redraw(); + *control_flow = match poll { + task::Poll::Pending => ControlFlow::Wait, + task::Poll::Ready(_) => ControlFlow::Exit, + }; + } + }); +} - event_loop.run(move |event, _, control_flow| match event { +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 surface = compositor.create_surface(&window); + 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(), + &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 messages = Vec::new(); + + debug.startup_finished(); + + while let Some(event) = receiver.next().await { + match event { event::Event::MainEventsCleared => { - if events.is_empty() && external_messages.is_empty() { - return; + if events.is_empty() && messages.is_empty() { + continue; } - // TODO: We should be able to keep a user interface alive - // between events once we remove state references. - // - // This will allow us to rebuild it only when a message is - // handled. - let mut user_interface = build_user_interface( - &mut application, - cache.take().unwrap(), + debug.event_processing_started(); + + let statuses = user_interface.update( + &events, + state.cursor_position(), + clipboard.as_ref().map(|c| c as _), &mut renderer, - size.logical(), - &mut debug, + &mut messages, ); - debug.event_processing_started(); - events - .iter() - .cloned() - .for_each(|event| runtime.broadcast(event)); - - let mut messages = user_interface.update( - events.drain(..), - clipboard - .as_ref() - .map(|c| c as &dyn iced_native::Clipboard), - &renderer, - ); - messages.extend(external_messages.drain(..)); debug.event_processing_finished(); - if messages.is_empty() { - debug.draw_started(); - primitive = user_interface.draw(&mut renderer); - debug.draw_finished(); - - cache = Some(user_interface.into_cache()); - } else { - // When there are messages, we are forced to rebuild twice - // for now :^) - let temp_cache = user_interface.into_cache(); - - for message in messages { - log::debug!("Updating"); - - debug.log_message(&message); - - debug.update_started(); - let command = - runtime.enter(|| application.update(message)); - runtime.spawn(command); - debug.update_finished(); - } - - 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, - )); + for event in events.drain(..).zip(statuses.into_iter()) { + runtime.broadcast(event); + } - mode = new_mode; - } + if !messages.is_empty() { + let cache = + ManuallyDrop::into_inner(user_interface).into_cache(); - let user_interface = build_user_interface( + // Update application + update( &mut application, - temp_cache, - &mut renderer, - size.logical(), + &mut runtime, &mut debug, + &mut messages, ); - debug.draw_started(); - primitive = user_interface.draw(&mut renderer); - debug.draw_finished(); + // Update window + state.synchronize(&application, &window); - cache = Some(user_interface.into_cache()); + 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(); + window.request_redraw(); } event::Event::UserEvent(message) => { - external_messages.push(message); + messages.push(message); } event::Event::RedrawRequested(_) => { debug.render_started(); + let current_viewport_version = state.viewport_version(); - if resized { - let physical_size = size.physical(); + if viewport_version != current_viewport_version { + let physical_size = state.physical_size(); + let logical_size = state.logical_size(); - swap_chain = backend.create_swap_chain( + 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(); + + swap_chain = compositor.create_swap_chain( &surface, physical_size.width, physical_size.height, ); - resized = false; + viewport_version = current_viewport_version; } - let new_mouse_interaction = backend.draw( + let new_mouse_interaction = compositor.draw( &mut renderer, &mut swap_chain, + state.viewport(), + state.background_color(), &primitive, - size.scale_factor(), &debug.overlay(), ); @@ -353,81 +337,90 @@ pub trait Application: Sized { event: window_event, .. } => { - match window_event { - WindowEvent::Resized(new_size) => { - size = Size::new(new_size, window.scale_factor()); - resized = true; - } - WindowEvent::CloseRequested => { - *control_flow = ControlFlow::Exit; - } - 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: - winit::event::KeyboardInput { - virtual_keycode: - Some(winit::event::VirtualKeyCode::F12), - state: winit::event::ElementState::Pressed, - .. - }, - .. - } => debug.toggle(), - _ => {} + if requests_exit(&window_event, state.modifiers()) { + break; } + state.update(&window, &window_event, &mut debug); + if let Some(event) = conversion::window_event( &window_event, - size.scale_factor(), - modifiers, + state.scale_factor(), + state.modifiers(), ) { events.push(event); } } - _ => { - *control_flow = ControlFlow::Wait; - } - }) + _ => {} + } } + + // Manually drop the user interface + drop(ManuallyDrop::into_inner(user_interface)); } -fn build_user_interface<'a, A: Application>( +/// Returns true if the provided event should cause an [`Application`] to +/// exit. +pub fn requests_exit( + event: &winit::event::WindowEvent<'_>, + _modifiers: winit::event::ModifiersState, +) -> bool { + use winit::event::WindowEvent; + + match event { + WindowEvent::CloseRequested => true, + #[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() => true, + _ => false, + } +} + +/// Builds a [`UserInterface`] for the provided [`Application`], logging +/// [`struct@Debug`] information accordingly. +pub fn build_user_interface<'a, A: Application>( application: &'a mut A, cache: Cache, - renderer: &mut <A::Backend as window::Backend>::Renderer, - size: winit::dpi::LogicalSize<f64>, + renderer: &mut A::Renderer, + size: Size, debug: &mut Debug, -) -> UserInterface<'a, A::Message, <A::Backend as window::Backend>::Renderer> { +) -> 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, - iced_native::Size::new( - size.width.round() as f32, - size.height.round() as f32, - ), - cache, - renderer, - ); + 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`]. +pub fn update<A: Application, E: Executor>( + application: &mut A, + runtime: &mut Runtime<E, Proxy<A::Message>, A::Message>, + debug: &mut Debug, + messages: &mut Vec<A::Message>, +) { + 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); + } + + let subscription = application.subscription(); + runtime.track(subscription); +} |