diff options
Diffstat (limited to 'native/src/user_interface.rs')
-rw-r--r-- | native/src/user_interface.rs | 213 |
1 files changed, 147 insertions, 66 deletions
diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 6fc6a479..344ba4d6 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -1,8 +1,10 @@ //! Implement your own event loop to drive a user interface. +use crate::application; use crate::event::{self, Event}; use crate::layout; use crate::mouse; use crate::renderer; +use crate::widget; use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size}; /// A set of interactive graphical elements with a specific [`Layout`]. @@ -13,14 +15,16 @@ use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size}; /// 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. +/// The [`integration_opengl`] & [`integration_wgpu`] examples use a +/// [`UserInterface`] to integrate Iced in an existing graphical application. /// -/// [`integration` example]: https://github.com/iced-rs/iced/tree/0.3/examples/integration +/// [`integration_opengl`]: https://github.com/iced-rs/iced/tree/0.4/examples/integration_opengl +/// [`integration_wgpu`]: https://github.com/iced-rs/iced/tree/0.4/examples/integration_wgpu #[allow(missing_debug_implementations)] pub struct UserInterface<'a, Message, Renderer> { root: Element<'a, Message, Renderer>, base: layout::Node, + state: widget::Tree, overlay: Option<layout::Node>, bounds: Size, } @@ -28,6 +32,7 @@ pub struct UserInterface<'a, Message, Renderer> { impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer> where Renderer: crate::Renderer, + Renderer::Theme: application::StyleSheet, { /// Builds a user interface for an [`Element`]. /// @@ -86,17 +91,21 @@ where pub fn build<E: Into<Element<'a, Message, Renderer>>>( root: E, bounds: Size, - _cache: Cache, + cache: Cache, renderer: &mut Renderer, ) -> Self { let root = root.into(); + let Cache { mut state } = cache; + state.diff(root.as_widget()); + let base = renderer.layout(&root, &layout::Limits::new(Size::ZERO, bounds)); UserInterface { root, base, + state, overlay: None, bounds, } @@ -177,40 +186,67 @@ where clipboard: &mut dyn Clipboard, messages: &mut Vec<Message>, ) -> (State, Vec<event::Status>) { - let mut state = State::Updated; + use std::mem::ManuallyDrop; - let (base_cursor, overlay_statuses) = if let Some(mut overlay) = - self.root.overlay(Layout::new(&self.base), renderer) - { + let mut state = State::Updated; + let mut manual_overlay = + ManuallyDrop::new(self.root.as_widget().overlay( + &mut self.state, + Layout::new(&self.base), + renderer, + )); + + 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.iter().cloned() { + let mut shell = Shell::new(messages); + + let event_status = overlay.on_event( + event, + Layout::new(&layout), + cursor_position, + renderer, + clipboard, + &mut shell, + ); + + event_statuses.push(event_status); - let event_statuses = events - .iter() - .cloned() - .map(|event| { - let mut shell = Shell::new(messages); - - let event_status = overlay.on_event( - event, - Layout::new(&layout), - cursor_position, - renderer, - clipboard, - &mut shell, + if shell.is_layout_invalid() { + let _ = ManuallyDrop::into_inner(manual_overlay); + + self.base = renderer.layout( + &self.root, + &layout::Limits::new(Size::ZERO, self.bounds), ); + manual_overlay = + ManuallyDrop::new(self.root.as_widget().overlay( + &mut self.state, + Layout::new(&self.base), + renderer, + )); + + 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() { - state = State::Outdated; - } - - event_status - }) - .collect(); + if shell.are_widgets_invalid() { + state = State::Outdated; + } + } let base_cursor = if layout.bounds().contains(cursor_position) { // TODO: Type-safe cursor availability @@ -226,14 +262,21 @@ where (cursor_position, vec![event::Status::Ignored; events.len()]) }; + let _ = ManuallyDrop::into_inner(manual_overlay); + let event_statuses = events .iter() .cloned() .zip(overlay_statuses.into_iter()) .map(|(event, overlay_status)| { + if matches!(overlay_status, event::Status::Captured) { + return overlay_status; + } + let mut shell = Shell::new(messages); - let event_status = self.root.widget.on_event( + let event_status = self.root.as_widget_mut().on_event( + &mut self.state, event, Layout::new(&self.base), base_cursor, @@ -264,19 +307,20 @@ where /// Draws the [`UserInterface`] with the provided [`Renderer`]. /// - /// It returns the some [`Renderer::Output`]. You should update the icon of - /// the mouse cursor accordingly in your system. + /// It returns the current [`mouse::Interaction`]. You should update the + /// icon of the mouse cursor accordingly in your system. /// /// [`Renderer`]: crate::Renderer - /// [`Renderer::Output`]: crate::Renderer::Output /// /// # Example /// We can finally draw our [counter](index.html#usage) by /// [completing the last example](#example-1): /// /// ```no_run - /// use iced_native::{clipboard, Size, Point}; + /// use iced_native::clipboard; + /// use iced_native::renderer; /// use iced_native::user_interface::{self, UserInterface}; + /// use iced_native::{Size, Point, Theme}; /// use iced_wgpu::Renderer; /// /// # mod iced_wgpu { @@ -323,7 +367,7 @@ where /// ); /// /// // Draw the user interface - /// let mouse_cursor = user_interface.draw(&mut renderer, cursor_position); + /// let mouse_cursor = user_interface.draw(&mut renderer, &Theme::default(), &renderer::Style::default(), cursor_position); /// /// cache = user_interface.into_cache(); /// @@ -338,6 +382,8 @@ where pub fn draw( &mut self, renderer: &mut Renderer, + theme: &Renderer::Theme, + style: &renderer::Style, cursor_position: Point, ) -> mouse::Interaction { // TODO: Move to shell level (?) @@ -345,9 +391,11 @@ where let viewport = Rectangle::with_size(self.bounds); - let base_cursor = if let Some(overlay) = - self.root.overlay(Layout::new(&self.base), renderer) - { + let base_cursor = if let Some(overlay) = self.root.as_widget().overlay( + &mut self.state, + Layout::new(&self.base), + renderer, + ) { let overlay_layout = self .overlay .take() @@ -367,15 +415,18 @@ where cursor_position }; - self.root.widget.draw( + self.root.as_widget().draw( + &self.state, renderer, - &renderer::Style::default(), + theme, + style, Layout::new(&self.base), base_cursor, &viewport, ); - let base_interaction = self.root.widget.mouse_interaction( + let base_interaction = self.root.as_widget().mouse_interaction( + &self.state, Layout::new(&self.base), cursor_position, &viewport, @@ -397,51 +448,79 @@ where overlay .as_ref() .and_then(|layout| { - root.overlay(Layout::new(&base), renderer).map(|overlay| { - let overlay_interaction = overlay.mouse_interaction( - Layout::new(layout), - cursor_position, - &viewport, - renderer, - ); - - let overlay_bounds = layout.bounds(); - - renderer.with_layer(overlay_bounds, |renderer| { - overlay.draw( - renderer, - &renderer::Style::default(), + root.as_widget() + .overlay(&mut self.state, Layout::new(base), renderer) + .map(|overlay| { + let overlay_interaction = overlay.mouse_interaction( Layout::new(layout), cursor_position, + &viewport, + renderer, ); - }); - if overlay_bounds.contains(cursor_position) { - overlay_interaction - } else { - base_interaction - } - }) + let overlay_bounds = layout.bounds(); + + renderer.with_layer(overlay_bounds, |renderer| { + overlay.draw( + renderer, + theme, + style, + Layout::new(layout), + cursor_position, + ); + }); + + if overlay_bounds.contains(cursor_position) { + 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<Message>, + ) { + self.root.as_widget().operate( + &mut self.state, + Layout::new(&self.base), + operation, + ); + + if let Some(layout) = self.overlay.as_ref() { + if let Some(overlay) = self.root.as_widget().overlay( + &mut self.state, + Layout::new(&self.base), + renderer, + ) { + overlay.operate(Layout::new(layout), 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, renderer) + 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 + Cache { state: self.state } } } /// Reusable data of a specific [`UserInterface`]. -#[derive(Debug, Clone)] -pub struct Cache; +#[derive(Debug)] +pub struct Cache { + state: widget::Tree, +} impl Cache { /// Creates an empty [`Cache`]. @@ -449,7 +528,9 @@ impl Cache { /// You should use this to initialize a [`Cache`] before building your first /// [`UserInterface`]. pub fn new() -> Cache { - Cache + Cache { + state: widget::Tree::empty(), + } } } |