diff --git a/lazy/src/component.rs b/lazy/src/component.rs index 698f179639..787adc109c 100644 --- a/lazy/src/component.rs +++ b/lazy/src/component.rs @@ -4,10 +4,12 @@ use iced_native::mouse; use iced_native::overlay; use iced_native::renderer; use iced_native::{ - Clipboard, Element, Hasher, Length, Point, Rectangle, Shell, Widget, + Clipboard, Element, Hasher, Length, Point, Rectangle, Shell, Size, Widget, }; use ouroboros::self_referencing; +use std::cell::RefCell; +use std::hash::Hash; use std::marker::PhantomData; pub fn view<'a, C, Message, Renderer>( @@ -19,16 +21,22 @@ where Renderer: iced_native::Renderer + 'a, { Element::new(Instance { - state: Some( + state: RefCell::new(Some( StateBuilder { component: Box::new(component), - cache_builder: |state| Cache { - element: state.view(), - message: PhantomData, + cache_builder: |state| { + Some( + CacheBuilder { + element: state.view(), + message: PhantomData, + overlay_builder: |_| None, + } + .build(), + ) }, } .build(), - ), + )), }) } @@ -41,7 +49,7 @@ pub trait Component { } struct Instance<'a, Message, Renderer, Event> { - state: Option>, + state: RefCell>>, } #[self_referencing] @@ -50,12 +58,51 @@ struct State<'a, Message: 'a, Renderer: 'a, Event: 'a> { #[borrows(mut component)] #[covariant] - cache: Cache<'this, Message, Renderer, Event>, + cache: Option>, } -struct Cache<'a, Message, Renderer, Event> { +#[self_referencing] +struct Cache<'a, Message, Renderer: 'a, Event: 'a> { element: Element<'a, Event, Renderer>, message: PhantomData, + + #[borrows(mut element)] + #[covariant] + overlay: Option>, +} + +impl<'a, Message, Renderer, Event> Instance<'a, Message, Renderer, Event> { + fn with_element( + &self, + f: impl FnOnce(&Element<'_, Event, Renderer>) -> T, + ) -> T { + self.with_element_mut(|element| f(element)) + } + + fn with_element_mut( + &self, + f: impl FnOnce(&mut Element<'_, Event, Renderer>) -> T, + ) -> T { + self.state + .borrow_mut() + .as_mut() + .unwrap() + .with_cache_mut(|cache| { + let mut element = cache.take().unwrap().into_heads().element; + let result = f(&mut element); + + *cache = Some( + CacheBuilder { + element, + message: PhantomData, + overlay_builder: |_| None, + } + .build(), + ); + + result + }) + } } impl<'a, Message, Renderer, Event> Widget @@ -64,11 +111,11 @@ where Renderer: iced_native::Renderer, { fn width(&self) -> Length { - self.state.as_ref().unwrap().borrow_cache().element.width() + self.with_element(|element| element.width()) } fn height(&self) -> Length { - self.state.as_ref().unwrap().borrow_cache().element.height() + self.with_element(|element| element.height()) } fn layout( @@ -76,12 +123,7 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - self.state - .as_ref() - .unwrap() - .borrow_cache() - .element - .layout(renderer, limits) + self.with_element(|element| element.layout(renderer, limits)) } fn on_event( @@ -96,21 +138,25 @@ where let mut local_messages = Vec::new(); let mut local_shell = Shell::new(&mut local_messages); - let event_status = - self.state.as_mut().unwrap().with_cache_mut(|cache| { - cache.element.on_event( - event, - layout, - cursor_position, - renderer, - clipboard, - &mut local_shell, - ) - }); + let event_status = self.with_element_mut(|element| { + element.on_event( + event, + layout, + cursor_position, + renderer, + clipboard, + &mut local_shell, + ) + }); if !local_messages.is_empty() { - let mut component = - self.state.take().unwrap().into_heads().component; + let mut component = self + .state + .borrow_mut() + .take() + .unwrap() + .into_heads() + .component; for message in local_messages .into_iter() @@ -119,12 +165,18 @@ where shell.publish(message); } - self.state = Some( + *self.state.borrow_mut() = Some( StateBuilder { component, - cache_builder: |state| Cache { - element: state.view(), - message: PhantomData, + cache_builder: |state| { + Some( + CacheBuilder { + element: state.view(), + message: PhantomData, + overlay_builder: |_| None, + } + .build(), + ) }, } .build(), @@ -144,22 +196,15 @@ where cursor_position: Point, viewport: &Rectangle, ) { - self.state.as_ref().unwrap().borrow_cache().element.draw( - renderer, - style, - layout, - cursor_position, - viewport, - ) + self.with_element(|element| { + element.draw(renderer, style, layout, cursor_position, viewport); + }); } fn hash_layout(&self, state: &mut Hasher) { - self.state - .as_ref() - .unwrap() - .borrow_cache() - .element - .hash_layout(state) + self.with_element(|element| { + element.hash_layout(state); + }); } fn mouse_interaction( @@ -168,19 +213,199 @@ where cursor_position: Point, viewport: &Rectangle, ) -> mouse::Interaction { - self.state + self.with_element(|element| { + element.mouse_interaction(layout, cursor_position, viewport) + }) + } + + fn overlay( + &mut self, + layout: Layout<'_>, + ) -> Option> { + let has_overlay = self + .state + .borrow_mut() + .as_mut() + .unwrap() + .with_cache_mut(|cache| { + let element = cache.take().unwrap().into_heads().element; + + *cache = Some( + CacheBuilder { + element, + message: PhantomData, + overlay_builder: |element| element.overlay(layout), + } + .build(), + ); + + cache.as_ref().unwrap().borrow_overlay().is_some() + }); + + has_overlay.then(|| { + overlay::Element::new( + layout.position(), + Box::new(Overlay { instance: self }), + ) + }) + } +} + +struct Overlay<'a, 'b, Message, Renderer, Event> { + instance: &'b mut Instance<'a, Message, Renderer, Event>, +} + +impl<'a, 'b, Message, Renderer, Event> + Overlay<'a, 'b, Message, Renderer, Event> +{ + fn with_overlay_maybe( + &self, + f: impl FnOnce(&overlay::Element<'_, Event, Renderer>) -> T, + ) -> Option { + self.instance + .state + .borrow() .as_ref() .unwrap() .borrow_cache() - .element - .mouse_interaction(layout, cursor_position, viewport) + .as_ref() + .unwrap() + .borrow_overlay() + .as_ref() + .map(f) } - fn overlay( + fn with_overlay_mut_maybe( + &self, + f: impl FnOnce(&mut overlay::Element<'_, Event, Renderer>) -> T, + ) -> Option { + self.instance + .state + .borrow_mut() + .as_mut() + .unwrap() + .with_cache_mut(|cache| { + cache + .as_mut() + .unwrap() + .with_overlay_mut(|overlay| overlay.as_mut().map(f)) + }) + } +} + +impl<'a, 'b, Message, Renderer, Event> overlay::Overlay + for Overlay<'a, 'b, Message, Renderer, Event> +where + Renderer: iced_native::Renderer, +{ + fn layout( + &self, + renderer: &Renderer, + bounds: Size, + position: Point, + ) -> layout::Node { + self.with_overlay_maybe(|overlay| { + let vector = position - overlay.position(); + + overlay.layout(renderer, bounds).translate(vector) + }) + .unwrap_or_default() + } + + fn draw( + &self, + renderer: &mut Renderer, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + ) { + self.with_overlay_maybe(|overlay| { + overlay.draw(renderer, style, layout, cursor_position); + }); + } + + fn mouse_interaction( + &self, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) -> mouse::Interaction { + self.with_overlay_maybe(|overlay| { + overlay.mouse_interaction(layout, cursor_position, viewport) + }) + .unwrap_or_default() + } + + fn hash_layout(&self, state: &mut Hasher, position: Point) { + struct Marker; + std::any::TypeId::of::().hash(state); + + (position.x as u32).hash(state); + (position.y as u32).hash(state); + + self.with_overlay_maybe(|overlay| { + overlay.hash_layout(state); + }); + } + + fn on_event( &mut self, - _layout: Layout<'_>, - ) -> Option> { - // TODO: Rethink overlay composability - None + event: iced_native::Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> iced_native::event::Status { + let mut local_messages = Vec::new(); + let mut local_shell = Shell::new(&mut local_messages); + + let event_status = self + .with_overlay_mut_maybe(|overlay| { + overlay.on_event( + event, + layout, + cursor_position, + renderer, + clipboard, + &mut local_shell, + ) + }) + .unwrap_or_else(|| iced_native::event::Status::Ignored); + + if !local_messages.is_empty() { + let mut component = + self.instance.state.take().unwrap().into_heads().component; + + for message in local_messages + .into_iter() + .filter_map(|message| component.update(message)) + { + shell.publish(message); + } + + self.instance.state = RefCell::new(Some( + StateBuilder { + component, + cache_builder: |state| { + Some( + CacheBuilder { + element: state.view(), + message: PhantomData, + overlay_builder: |element| { + element.overlay(layout) + }, + } + .build(), + ) + }, + } + .build(), + )); + + shell.invalidate_layout(); + } + + event_status } } diff --git a/native/src/layout/node.rs b/native/src/layout/node.rs index e9e6058e39..e0c7dcb2fd 100644 --- a/native/src/layout/node.rs +++ b/native/src/layout/node.rs @@ -1,4 +1,4 @@ -use crate::{Alignment, Point, Rectangle, Size}; +use crate::{Alignment, Point, Rectangle, Size, Vector}; /// The bounds of an element and its children. #[derive(Debug, Clone, Default)] @@ -80,4 +80,12 @@ impl Node { self.bounds.x = position.x; self.bounds.y = position.y; } + + /// Translates the [`Node`] by the given translation. + pub fn translate(self, translation: Vector) -> Self { + Self { + bounds: self.bounds + translation, + ..self + } + } }