diff options
Diffstat (limited to 'runtime')
-rw-r--r-- | runtime/src/lib.rs | 1 | ||||
-rw-r--r-- | runtime/src/overlay.rs | 4 | ||||
-rw-r--r-- | runtime/src/overlay/nested.rs | 353 | ||||
-rw-r--r-- | runtime/src/program/state.rs | 10 | ||||
-rw-r--r-- | runtime/src/user_interface.rs | 145 |
5 files changed, 453 insertions, 60 deletions
diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 50abf7b2..4bbf9687 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -47,6 +47,7 @@ pub mod clipboard; pub mod command; pub mod font; pub mod keyboard; +pub mod overlay; pub mod program; pub mod system; pub mod user_interface; diff --git a/runtime/src/overlay.rs b/runtime/src/overlay.rs new file mode 100644 index 00000000..03390980 --- /dev/null +++ b/runtime/src/overlay.rs @@ -0,0 +1,4 @@ +//! Overlays for user interfaces. +mod nested; + +pub use nested::Nested; diff --git a/runtime/src/overlay/nested.rs b/runtime/src/overlay/nested.rs new file mode 100644 index 00000000..b729f769 --- /dev/null +++ b/runtime/src/overlay/nested.rs @@ -0,0 +1,353 @@ +use crate::core::event; +use crate::core::layout; +use crate::core::mouse; +use crate::core::overlay; +use crate::core::renderer; +use crate::core::widget; +use crate::core::{Clipboard, Event, Layout, Point, Rectangle, Shell, Size}; + +/// An [`Overlay`] container that displays nested overlays +#[allow(missing_debug_implementations)] +pub struct Nested<'a, Message, Renderer> { + overlay: overlay::Element<'a, Message, Renderer>, +} + +impl<'a, Message, Renderer> Nested<'a, Message, Renderer> +where + Renderer: renderer::Renderer, +{ + /// Creates a nested overlay from the provided [`overlay::Element`] + pub fn new(element: overlay::Element<'a, Message, Renderer>) -> Self { + Self { overlay: element } + } + + /// Returns the position of the [`Nested`] overlay. + pub fn position(&self) -> Point { + self.overlay.position() + } + + /// Returns the layout [`Node`] of the [`Nested`] overlay. + pub fn layout( + &mut self, + renderer: &Renderer, + bounds: Size, + position: Point, + ) -> layout::Node { + fn recurse<Message, Renderer>( + element: &mut overlay::Element<'_, Message, Renderer>, + renderer: &Renderer, + bounds: Size, + position: Point, + ) -> layout::Node + where + Renderer: renderer::Renderer, + { + let translation = position - Point::ORIGIN; + + let node = element.layout(renderer, bounds, translation); + + if let Some(mut nested) = + element.overlay(Layout::new(&node), renderer) + { + layout::Node::with_children( + node.size(), + vec![ + node, + recurse(&mut nested, renderer, bounds, position), + ], + ) + } else { + layout::Node::with_children(node.size(), vec![node]) + } + } + + recurse(&mut self.overlay, renderer, bounds, position) + } + + /// Draws the [`Nested`] overlay using the associated `Renderer`. + pub fn draw( + &mut self, + renderer: &mut Renderer, + theme: &<Renderer as renderer::Renderer>::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor: mouse::Cursor, + ) { + fn recurse<Message, Renderer>( + element: &mut overlay::Element<'_, Message, Renderer>, + layout: Layout<'_>, + renderer: &mut Renderer, + theme: &<Renderer as renderer::Renderer>::Theme, + style: &renderer::Style, + cursor: mouse::Cursor, + ) where + Renderer: renderer::Renderer, + { + let mut layouts = layout.children(); + + if let Some(layout) = layouts.next() { + let nested_layout = layouts.next(); + + let is_over = cursor + .position() + .zip(nested_layout) + .and_then(|(cursor_position, nested_layout)| { + element.overlay(layout, renderer).map(|nested| { + nested.is_over( + nested_layout.children().next().unwrap(), + renderer, + cursor_position, + ) + }) + }) + .unwrap_or_default(); + + renderer.with_layer(layout.bounds(), |renderer| { + element.draw( + renderer, + theme, + style, + layout, + if is_over { + mouse::Cursor::Unavailable + } else { + cursor + }, + ); + }); + + if let Some((mut nested, nested_layout)) = + element.overlay(layout, renderer).zip(nested_layout) + { + recurse( + &mut nested, + nested_layout, + renderer, + theme, + style, + cursor, + ); + } + } + } + + recurse(&mut self.overlay, layout, renderer, theme, style, cursor); + } + + /// Applies a [`widget::Operation`] to the [`Nested`] overlay. + pub fn operate( + &mut self, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn widget::Operation<Message>, + ) { + fn recurse<Message, Renderer>( + element: &mut overlay::Element<'_, Message, Renderer>, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn widget::Operation<Message>, + ) where + Renderer: renderer::Renderer, + { + let mut layouts = layout.children(); + + if let Some(layout) = layouts.next() { + element.operate(layout, renderer, operation); + + if let Some((mut nested, nested_layout)) = + element.overlay(layout, renderer).zip(layouts.next()) + { + recurse(&mut nested, nested_layout, renderer, operation); + } + } + } + + recurse(&mut self.overlay, layout, renderer, operation) + } + + /// Processes a runtime [`Event`]. + pub fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + fn recurse<Message, Renderer>( + element: &mut overlay::Element<'_, Message, Renderer>, + layout: Layout<'_>, + event: Event, + cursor: mouse::Cursor, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> (event::Status, bool) + where + Renderer: renderer::Renderer, + { + let mut layouts = layout.children(); + + if let Some(layout) = layouts.next() { + let (nested_status, nested_is_over) = + if let Some((mut nested, nested_layout)) = + element.overlay(layout, renderer).zip(layouts.next()) + { + recurse( + &mut nested, + nested_layout, + event.clone(), + cursor, + renderer, + clipboard, + shell, + ) + } else { + (event::Status::Ignored, false) + }; + + if matches!(nested_status, event::Status::Ignored) { + let is_over = nested_is_over + || cursor + .position() + .map(|cursor_position| { + element.is_over( + layout, + renderer, + cursor_position, + ) + }) + .unwrap_or_default(); + + ( + element.on_event( + event, + layout, + if nested_is_over { + mouse::Cursor::Unavailable + } else { + cursor + }, + renderer, + clipboard, + shell, + ), + is_over, + ) + } else { + (nested_status, nested_is_over) + } + } else { + (event::Status::Ignored, false) + } + } + + let (status, _) = recurse( + &mut self.overlay, + layout, + event, + cursor, + renderer, + clipboard, + shell, + ); + + status + } + + /// Returns the current [`mouse::Interaction`] of the [`Nested`] overlay. + pub fn mouse_interaction( + &mut self, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + fn recurse<Message, Renderer>( + element: &mut overlay::Element<'_, Message, Renderer>, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + renderer: &Renderer, + ) -> Option<mouse::Interaction> + where + Renderer: renderer::Renderer, + { + let mut layouts = layout.children(); + + let layout = layouts.next()?; + let cursor_position = cursor.position()?; + + if !element.is_over(layout, renderer, cursor_position) { + return None; + } + + Some( + element + .overlay(layout, renderer) + .zip(layouts.next()) + .and_then(|(mut overlay, layout)| { + recurse( + &mut overlay, + layout, + cursor, + viewport, + renderer, + ) + }) + .unwrap_or_else(|| { + element.mouse_interaction( + layout, cursor, viewport, renderer, + ) + }), + ) + } + + recurse(&mut self.overlay, layout, cursor, viewport, renderer) + .unwrap_or_default() + } + + /// Returns true if the cursor is over the [`Nested`] overlay. + pub fn is_over( + &mut self, + layout: Layout<'_>, + renderer: &Renderer, + cursor_position: Point, + ) -> bool { + fn recurse<Message, Renderer>( + element: &mut overlay::Element<'_, Message, Renderer>, + layout: Layout<'_>, + renderer: &Renderer, + cursor_position: Point, + ) -> bool + where + Renderer: renderer::Renderer, + { + let mut layouts = layout.children(); + + if let Some(layout) = layouts.next() { + if element.is_over(layout, renderer, cursor_position) { + return true; + } + + if let Some((mut nested, nested_layout)) = + element.overlay(layout, renderer).zip(layouts.next()) + { + recurse( + &mut nested, + nested_layout, + renderer, + cursor_position, + ) + } else { + false + } + } else { + false + } + } + + recurse(&mut self.overlay, layout, renderer, cursor_position) + } +} diff --git a/runtime/src/program/state.rs b/runtime/src/program/state.rs index 2fa9934d..d83e3f54 100644 --- a/runtime/src/program/state.rs +++ b/runtime/src/program/state.rs @@ -1,7 +1,7 @@ use crate::core::event::{self, Event}; use crate::core::mouse; use crate::core::renderer; -use crate::core::{Clipboard, Point, Size}; +use crate::core::{Clipboard, Size}; use crate::user_interface::{self, UserInterface}; use crate::{Command, Debug, Program}; @@ -88,7 +88,7 @@ where pub fn update( &mut self, bounds: Size, - cursor_position: Point, + cursor: mouse::Cursor, renderer: &mut P::Renderer, theme: &<P::Renderer as iced_core::Renderer>::Theme, style: &renderer::Style, @@ -108,7 +108,7 @@ where let (_, event_statuses) = user_interface.update( &self.queued_events, - cursor_position, + cursor, renderer, clipboard, &mut messages, @@ -131,7 +131,7 @@ where let command = if messages.is_empty() { debug.draw_started(); self.mouse_interaction = - user_interface.draw(renderer, theme, style, cursor_position); + user_interface.draw(renderer, theme, style, cursor); debug.draw_finished(); self.cache = Some(user_interface.into_cache()); @@ -163,7 +163,7 @@ where debug.draw_started(); self.mouse_interaction = - user_interface.draw(renderer, theme, style, cursor_position); + user_interface.draw(renderer, theme, style, cursor); debug.draw_finished(); self.cache = Some(user_interface.into_cache()); diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index d9206134..619423fd 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -5,8 +5,8 @@ use crate::core::mouse; use crate::core::renderer; use crate::core::widget; use crate::core::window; -use crate::core::{Clipboard, Point, Rectangle, Size, Vector}; -use crate::core::{Element, Layout, Shell}; +use crate::core::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size}; +use crate::overlay; /// A set of interactive graphical elements with a specific [`Layout`]. /// @@ -128,7 +128,9 @@ where /// # pub fn view(&self) -> iced_core::Element<(), Renderer> { unimplemented!() } /// # pub fn update(&mut self, _: ()) {} /// # } - /// use iced_runtime::core::{clipboard, Size, Point}; + /// 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; /// @@ -136,7 +138,7 @@ where /// let mut cache = user_interface::Cache::new(); /// let mut renderer = Renderer::new(); /// let mut window_size = Size::new(1024.0, 768.0); - /// let mut cursor_position = Point::default(); + /// let mut cursor = mouse::Cursor::default(); /// let mut clipboard = clipboard::Null; /// /// // Initialize our event storage @@ -156,7 +158,7 @@ where /// // Update the user interface /// let (state, event_statuses) = user_interface.update( /// &events, - /// cursor_position, + /// cursor, /// &mut renderer, /// &mut clipboard, /// &mut messages @@ -173,7 +175,7 @@ where pub fn update( &mut self, events: &[Event], - cursor_position: Point, + cursor: mouse::Cursor, renderer: &mut Renderer, clipboard: &mut dyn Clipboard, messages: &mut Vec<Message>, @@ -183,18 +185,18 @@ where let mut outdated = false; let mut redraw_request = None; - let mut manual_overlay = - ManuallyDrop::new(self.root.as_widget_mut().overlay( - &mut self.state, - Layout::new(&self.base), - renderer, - )); + let mut manual_overlay = ManuallyDrop::new( + self.root + .as_widget_mut() + .overlay(&mut self.state, Layout::new(&self.base), renderer) + .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, Vector::ZERO); + let mut layout = overlay.layout(renderer, bounds, Point::ORIGIN); let mut event_statuses = Vec::new(); for event in events.iter().cloned() { @@ -203,7 +205,7 @@ where let event_status = overlay.on_event( event, Layout::new(&layout), - cursor_position, + cursor, renderer, clipboard, &mut shell, @@ -229,12 +231,16 @@ where &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, - )); + manual_overlay = ManuallyDrop::new( + self.root + .as_widget_mut() + .overlay( + &mut self.state, + Layout::new(&self.base), + renderer, + ) + .map(overlay::Nested::new), + ); if manual_overlay.is_none() { break; @@ -243,7 +249,8 @@ where overlay = manual_overlay.as_mut().unwrap(); shell.revalidate_layout(|| { - layout = overlay.layout(renderer, bounds, Vector::ZERO); + layout = + overlay.layout(renderer, bounds, Point::ORIGIN); }); } @@ -252,22 +259,29 @@ where } } - let base_cursor = manual_overlay - .as_ref() - .filter(|overlay| { - overlay.is_over(Layout::new(&layout), cursor_position) - }) - .map(|_| { - // TODO: Type-safe cursor availability - Point::new(-1.0, -1.0) + 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(cursor_position); + .unwrap_or_default() + { + mouse::Cursor::Unavailable + } else { + cursor + }; self.overlay = Some(layout); (base_cursor, event_statuses) } else { - (cursor_position, vec![event::Status::Ignored; events.len()]) + (cursor, vec![event::Status::Ignored; events.len()]) }; let _ = ManuallyDrop::into_inner(manual_overlay); @@ -359,8 +373,9 @@ where /// # 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, Point}; + /// use iced_runtime::core::{Element, Size}; /// use iced_runtime::user_interface::{self, UserInterface}; /// use iced_wgpu::{Renderer, Theme}; /// @@ -368,7 +383,7 @@ where /// let mut cache = user_interface::Cache::new(); /// let mut renderer = Renderer::new(); /// let mut window_size = Size::new(1024.0, 768.0); - /// let mut cursor_position = Point::default(); + /// let mut cursor = mouse::Cursor::default(); /// let mut clipboard = clipboard::Null; /// let mut events = Vec::new(); /// let mut messages = Vec::new(); @@ -387,14 +402,14 @@ where /// // Update the user interface /// let event_statuses = user_interface.update( /// &events, - /// cursor_position, + /// cursor, /// &mut renderer, /// &mut clipboard, /// &mut messages /// ); /// /// // Draw the user interface - /// let mouse_cursor = user_interface.draw(&mut renderer, &theme, &renderer::Style::default(), cursor_position); + /// let mouse_interaction = user_interface.draw(&mut renderer, &theme, &renderer::Style::default(), cursor); /// /// cache = user_interface.into_cache(); /// @@ -411,35 +426,44 @@ where renderer: &mut Renderer, theme: &Renderer::Theme, style: &renderer::Style, - cursor_position: Point, + 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(overlay) = self + let base_cursor = if let Some(mut overlay) = self .root .as_widget_mut() .overlay(&mut self.state, Layout::new(&self.base), renderer) + .map(overlay::Nested::new) { let overlay_layout = self.overlay.take().unwrap_or_else(|| { - overlay.layout(renderer, self.bounds, Vector::ZERO) + overlay.layout(renderer, self.bounds, Point::ORIGIN) }); - let new_cursor_position = if overlay - .is_over(Layout::new(&overlay_layout), cursor_position) + let cursor = if cursor + .position() + .map(|cursor_position| { + overlay.is_over( + Layout::new(&overlay_layout), + renderer, + cursor_position, + ) + }) + .unwrap_or_default() { - Point::new(-1.0, -1.0) + mouse::Cursor::Unavailable } else { - cursor_position + cursor }; self.overlay = Some(overlay_layout); - new_cursor_position + cursor } else { - cursor_position + cursor }; self.root.as_widget().draw( @@ -455,7 +479,7 @@ where let base_interaction = self.root.as_widget().mouse_interaction( &self.state, Layout::new(&self.base), - cursor_position, + base_cursor, &viewport, renderer, ); @@ -477,10 +501,11 @@ where .and_then(|layout| { root.as_widget_mut() .overlay(&mut self.state, Layout::new(base), renderer) - .map(|overlay| { + .map(overlay::Nested::new) + .map(|mut overlay| { let overlay_interaction = overlay.mouse_interaction( Layout::new(layout), - cursor_position, + cursor, &viewport, renderer, ); @@ -493,11 +518,20 @@ where theme, style, Layout::new(layout), - cursor_position, + cursor, ); }); - if overlay.is_over(Layout::new(layout), cursor_position) + if cursor + .position() + .map(|cursor_position| { + overlay.is_over( + Layout::new(layout), + renderer, + cursor_position, + ) + }) + .unwrap_or_default() { overlay_interaction } else { @@ -521,14 +555,15 @@ where operation, ); - if let Some(mut overlay) = self.root.as_widget_mut().overlay( - &mut self.state, - Layout::new(&self.base), - renderer, - ) { + if let Some(mut overlay) = self + .root + .as_widget_mut() + .overlay(&mut self.state, Layout::new(&self.base), renderer) + .map(overlay::Nested::new) + { if self.overlay.is_none() { self.overlay = - Some(overlay.layout(renderer, self.bounds, Vector::ZERO)); + Some(overlay.layout(renderer, self.bounds, Point::ORIGIN)); } overlay.operate( |