//! Implement your own event loop to drive a user interface. use crate::core::event::{self, Event}; use crate::core::layout; use crate::core::mouse; use crate::core::renderer; use crate::core::widget; use crate::core::window; use crate::core::{ Clipboard, Element, InputMethod, Layout, Rectangle, Shell, Size, Vector, }; use crate::overlay; /// A set of interactive graphical elements with a specific [`Layout`]. /// /// It can be updated and drawn. /// /// Iced tries to avoid dictating how to write your event loop. You are in /// charge of using this type in your system in any way you want. /// /// # Example /// The [`integration`] example uses a [`UserInterface`] to integrate Iced in an /// existing graphical application. /// /// [`integration`]: https://github.com/iced-rs/iced/tree/0.13/examples/integration #[allow(missing_debug_implementations)] pub struct UserInterface<'a, Message, Theme, Renderer> { root: Element<'a, Message, Theme, Renderer>, base: layout::Node, state: widget::Tree, overlay: Option, bounds: Size, } impl<'a, Message, Theme, Renderer> UserInterface<'a, Message, Theme, Renderer> where Renderer: crate::core::Renderer, { /// Builds a user interface for an [`Element`]. /// /// It is able to avoid expensive computations when using a [`Cache`] /// obtained from a previous instance of a [`UserInterface`]. /// /// # Example /// Imagine we want to build a [`UserInterface`] for /// [the counter example that we previously wrote](index.html#usage). Here /// is naive way to set up our application loop: /// /// ```no_run /// # mod iced_wgpu { /// # pub type Renderer = (); /// # } /// # /// # pub struct Counter; /// # /// # impl Counter { /// # pub fn new() -> Self { Counter } /// # pub fn view(&self) -> iced_core::Element<(), (), Renderer> { unimplemented!() } /// # pub fn update(&mut self, _: ()) {} /// # } /// use iced_runtime::core::Size; /// use iced_runtime::user_interface::{self, UserInterface}; /// use iced_wgpu::Renderer; /// /// // Initialization /// let mut counter = Counter::new(); /// let mut cache = user_interface::Cache::new(); /// let mut renderer = Renderer::default(); /// let mut window_size = Size::new(1024.0, 768.0); /// /// // Application loop /// loop { /// // Process system events here... /// /// // Build the user interface /// let user_interface = UserInterface::build( /// counter.view(), /// window_size, /// cache, /// &mut renderer, /// ); /// /// // Update and draw the user interface here... /// // ... /// /// // Obtain the cache for the next iteration /// cache = user_interface.into_cache(); /// } /// ``` pub fn build>>( root: E, bounds: Size, cache: Cache, renderer: &mut Renderer, ) -> Self { let root = root.into(); let Cache { mut state } = cache; state.diff(root.as_widget()); let base = root.as_widget().layout( &mut state, renderer, &layout::Limits::new(Size::ZERO, bounds), ); UserInterface { root, base, state, overlay: None, bounds, } } /// Updates the [`UserInterface`] by processing each provided [`Event`]. /// /// It returns __messages__ that may have been produced as a result of user /// interactions. You should feed these to your __update logic__. /// /// # Example /// Let's allow our [counter](index.html#usage) to change state by /// completing [the previous example](#example): /// /// ```no_run /// # mod iced_wgpu { /// # pub type Renderer = (); /// # } /// # /// # pub struct Counter; /// # /// # impl Counter { /// # pub fn new() -> Self { Counter } /// # pub fn view(&self) -> iced_core::Element<(), (), Renderer> { unimplemented!() } /// # pub fn update(&mut self, _: ()) {} /// # } /// use iced_runtime::core::clipboard; /// use iced_runtime::core::mouse; /// use iced_runtime::core::Size; /// use iced_runtime::user_interface::{self, UserInterface}; /// use iced_wgpu::Renderer; /// /// let mut counter = Counter::new(); /// let mut cache = user_interface::Cache::new(); /// let mut renderer = Renderer::default(); /// let mut window_size = Size::new(1024.0, 768.0); /// let mut cursor = mouse::Cursor::default(); /// let mut clipboard = clipboard::Null; /// /// // Initialize our event storage /// let mut events = Vec::new(); /// let mut messages = Vec::new(); /// /// loop { /// // Obtain system events... /// /// let mut user_interface = UserInterface::build( /// counter.view(), /// window_size, /// cache, /// &mut renderer, /// ); /// /// // Update the user interface /// let (state, event_statuses) = user_interface.update( /// &events, /// cursor, /// &mut renderer, /// &mut clipboard, /// &mut messages /// ); /// /// cache = user_interface.into_cache(); /// /// // Process the produced messages /// for message in messages.drain(..) { /// counter.update(message); /// } /// } /// ``` pub fn update( &mut self, events: &[Event], cursor: mouse::Cursor, renderer: &mut Renderer, clipboard: &mut dyn Clipboard, messages: &mut Vec, ) -> (State, Vec) { use std::mem::ManuallyDrop; let mut outdated = false; let mut redraw_request = window::RedrawRequest::Wait; let mut input_method = InputMethod::Disabled; let mut manual_overlay = ManuallyDrop::new( self.root .as_widget_mut() .overlay( &mut self.state, Layout::new(&self.base), renderer, Vector::ZERO, ) .map(overlay::Nested::new), ); let (base_cursor, overlay_statuses) = if manual_overlay.is_some() { let bounds = self.bounds; let mut overlay = manual_overlay.as_mut().unwrap(); let mut layout = overlay.layout(renderer, bounds); let mut event_statuses = Vec::new(); for event in events { let mut shell = Shell::new(messages); overlay.update( event, Layout::new(&layout), cursor, renderer, clipboard, &mut shell, ); event_statuses.push(shell.event_status()); redraw_request = redraw_request.min(shell.redraw_request()); input_method.merge(shell.input_method()); if shell.is_layout_invalid() { let _ = ManuallyDrop::into_inner(manual_overlay); self.base = self.root.as_widget().layout( &mut self.state, renderer, &layout::Limits::new(Size::ZERO, self.bounds), ); manual_overlay = ManuallyDrop::new( self.root .as_widget_mut() .overlay( &mut self.state, Layout::new(&self.base), renderer, Vector::ZERO, ) .map(overlay::Nested::new), ); if manual_overlay.is_none() { break; } overlay = manual_overlay.as_mut().unwrap(); shell.revalidate_layout(|| { layout = overlay.layout(renderer, bounds); }); } if shell.are_widgets_invalid() { outdated = true; } } let base_cursor = if manual_overlay .as_mut() .and_then(|overlay| { cursor.position().map(|cursor_position| { overlay.is_over( Layout::new(&layout), renderer, cursor_position, ) }) }) .unwrap_or_default() { mouse::Cursor::Unavailable } else { cursor }; self.overlay = Some(layout); (base_cursor, event_statuses) } else { (cursor, vec![event::Status::Ignored; events.len()]) }; let viewport = Rectangle::with_size(self.bounds); let _ = ManuallyDrop::into_inner(manual_overlay); let event_statuses = events .iter() .zip(overlay_statuses) .map(|(event, overlay_status)| { if matches!(overlay_status, event::Status::Captured) { return overlay_status; } let mut shell = Shell::new(messages); self.root.as_widget_mut().update( &mut self.state, event, Layout::new(&self.base), base_cursor, renderer, clipboard, &mut shell, &viewport, ); if shell.event_status() == event::Status::Captured { self.overlay = None; } redraw_request = redraw_request.min(shell.redraw_request()); input_method.merge(shell.input_method()); shell.revalidate_layout(|| { self.base = self.root.as_widget().layout( &mut self.state, renderer, &layout::Limits::new(Size::ZERO, self.bounds), ); self.overlay = None; }); if shell.are_widgets_invalid() { outdated = true; } shell.event_status().merge(overlay_status) }) .collect(); ( if outdated { State::Outdated } else { State::Updated { redraw_request, input_method, } }, event_statuses, ) } /// Draws the [`UserInterface`] with the provided [`Renderer`]. /// /// It returns the current [`mouse::Interaction`]. You should update the /// icon of the mouse cursor accordingly in your system. /// /// [`Renderer`]: crate::core::Renderer /// /// # Example /// We can finally draw our [counter](index.html#usage) by /// [completing the last example](#example-1): /// /// ```no_run /// # mod iced_wgpu { /// # pub type Renderer = (); /// # pub type Theme = (); /// # } /// # /// # pub struct Counter; /// # /// # impl Counter { /// # pub fn new() -> Self { Counter } /// # pub fn view(&self) -> Element<(), (), Renderer> { unimplemented!() } /// # pub fn update(&mut self, _: ()) {} /// # } /// use iced_runtime::core::clipboard; /// use iced_runtime::core::mouse; /// use iced_runtime::core::renderer; /// use iced_runtime::core::{Element, Size}; /// use iced_runtime::user_interface::{self, UserInterface}; /// use iced_wgpu::{Renderer, Theme}; /// /// let mut counter = Counter::new(); /// let mut cache = user_interface::Cache::new(); /// let mut renderer = Renderer::default(); /// let mut window_size = Size::new(1024.0, 768.0); /// let mut cursor = mouse::Cursor::default(); /// let mut clipboard = clipboard::Null; /// let mut events = Vec::new(); /// let mut messages = Vec::new(); /// let mut theme = Theme::default(); /// /// loop { /// // Obtain system events... /// /// let mut user_interface = UserInterface::build( /// counter.view(), /// window_size, /// cache, /// &mut renderer, /// ); /// /// // Update the user interface /// let event_statuses = user_interface.update( /// &events, /// cursor, /// &mut renderer, /// &mut clipboard, /// &mut messages /// ); /// /// // Draw the user interface /// let mouse_interaction = user_interface.draw(&mut renderer, &theme, &renderer::Style::default(), cursor); /// /// cache = user_interface.into_cache(); /// /// for message in messages.drain(..) { /// counter.update(message); /// } /// /// // Update mouse cursor icon... /// // Flush rendering operations... /// } /// ``` pub fn draw( &mut self, renderer: &mut Renderer, theme: &Theme, style: &renderer::Style, cursor: mouse::Cursor, ) -> mouse::Interaction { // TODO: Move to shell level (?) renderer.clear(); let viewport = Rectangle::with_size(self.bounds); let base_cursor = if let Some(mut overlay) = self .root .as_widget_mut() .overlay( &mut self.state, Layout::new(&self.base), renderer, Vector::ZERO, ) .map(overlay::Nested::new) { let overlay_layout = self .overlay .take() .unwrap_or_else(|| overlay.layout(renderer, self.bounds)); let cursor = if cursor .position() .map(|cursor_position| { overlay.is_over( Layout::new(&overlay_layout), renderer, cursor_position, ) }) .unwrap_or_default() { mouse::Cursor::Unavailable } else { cursor }; self.overlay = Some(overlay_layout); cursor } else { cursor }; self.root.as_widget().draw( &self.state, renderer, theme, style, Layout::new(&self.base), base_cursor, &viewport, ); let base_interaction = self.root.as_widget().mouse_interaction( &self.state, Layout::new(&self.base), base_cursor, &viewport, renderer, ); let Self { overlay, root, base, .. } = self; // TODO: Currently, we need to call Widget::overlay twice to // implement the painter's algorithm properly. // // Once we have a proper persistent widget tree, we should be able to // avoid this additional call. overlay .as_ref() .and_then(|layout| { root.as_widget_mut() .overlay( &mut self.state, Layout::new(base), renderer, Vector::ZERO, ) .map(overlay::Nested::new) .map(|mut overlay| { let overlay_interaction = overlay.mouse_interaction( Layout::new(layout), cursor, &viewport, renderer, ); overlay.draw( renderer, theme, style, Layout::new(layout), cursor, ); if cursor .position() .map(|cursor_position| { overlay.is_over( Layout::new(layout), renderer, cursor_position, ) }) .unwrap_or_default() { overlay_interaction } else { base_interaction } }) }) .unwrap_or(base_interaction) } /// Applies a [`widget::Operation`] to the [`UserInterface`]. pub fn operate( &mut self, renderer: &Renderer, operation: &mut dyn widget::Operation, ) { self.root.as_widget().operate( &mut self.state, Layout::new(&self.base), renderer, operation, ); if let Some(mut overlay) = self .root .as_widget_mut() .overlay( &mut self.state, Layout::new(&self.base), renderer, Vector::ZERO, ) .map(overlay::Nested::new) { if self.overlay.is_none() { self.overlay = Some(overlay.layout(renderer, self.bounds)); } overlay.operate( Layout::new(self.overlay.as_ref().unwrap()), renderer, operation, ); } } /// Relayouts and returns a new [`UserInterface`] using the provided /// bounds. pub fn relayout(self, bounds: Size, renderer: &mut Renderer) -> Self { Self::build(self.root, bounds, Cache { state: self.state }, renderer) } /// Extract the [`Cache`] of the [`UserInterface`], consuming it in the /// process. pub fn into_cache(self) -> Cache { Cache { state: self.state } } } /// Reusable data of a specific [`UserInterface`]. #[derive(Debug)] pub struct Cache { state: widget::Tree, } impl Cache { /// Creates an empty [`Cache`]. /// /// You should use this to initialize a [`Cache`] before building your first /// [`UserInterface`]. pub fn new() -> Cache { Cache { state: widget::Tree::empty(), } } } impl Default for Cache { fn default() -> Cache { Cache::new() } } /// The resulting state after updating a [`UserInterface`]. #[derive(Debug, Clone)] pub enum State { /// The [`UserInterface`] is outdated and needs to be rebuilt. Outdated, /// The [`UserInterface`] is up-to-date and can be reused without /// rebuilding. Updated { /// The [`window::RedrawRequest`] describing when a redraw should be performed. redraw_request: window::RedrawRequest, /// The current [`InputMethod`] strategy of the user interface. input_method: InputMethod, }, }