diff options
author | 2022-01-12 11:15:05 +0700 | |
---|---|---|
committer | 2022-01-12 11:15:05 +0700 | |
commit | 870d651f35c4dad12c805951fca70213816983de (patch) | |
tree | dcf50e912ca3e7823e07aee635c7e5f464b5bd66 /lazy | |
parent | 5a03cac7e75ccb8ca87a97def723694be0471742 (diff) | |
download | iced-870d651f35c4dad12c805951fca70213816983de.tar.gz iced-870d651f35c4dad12c805951fca70213816983de.tar.bz2 iced-870d651f35c4dad12c805951fca70213816983de.zip |
Implement `Widget::overlay` for `Responsive` widget
Diffstat (limited to 'lazy')
-rw-r--r-- | lazy/src/cache.rs | 13 | ||||
-rw-r--r-- | lazy/src/component.rs | 11 | ||||
-rw-r--r-- | lazy/src/lib.rs | 6 | ||||
-rw-r--r-- | lazy/src/responsive.rs | 413 |
4 files changed, 434 insertions, 9 deletions
diff --git a/lazy/src/cache.rs b/lazy/src/cache.rs new file mode 100644 index 00000000..229b7912 --- /dev/null +++ b/lazy/src/cache.rs @@ -0,0 +1,13 @@ +use iced_native::overlay; +use iced_native::Element; + +use ouroboros::self_referencing; + +#[self_referencing(pub_extras)] +pub struct Cache<'a, Message: 'a, Renderer: 'a> { + pub element: Element<'a, Message, Renderer>, + + #[borrows(mut element)] + #[covariant] + pub overlay: Option<overlay::Element<'this, Message, Renderer>>, +} diff --git a/lazy/src/component.rs b/lazy/src/component.rs index ca0d115c..ae8d6bbe 100644 --- a/lazy/src/component.rs +++ b/lazy/src/component.rs @@ -1,3 +1,5 @@ +use crate::{Cache, CacheBuilder}; + use iced_native::event; use iced_native::layout::{self, Layout}; use iced_native::mouse; @@ -62,15 +64,6 @@ struct State<'a, Message: 'a, Renderer: 'a, Event: 'a> { cache: Option<Cache<'this, Event, Renderer>>, } -#[self_referencing] -struct Cache<'a, Message: 'a, Renderer: 'a> { - element: Element<'a, Message, Renderer>, - - #[borrows(mut element)] - #[covariant] - overlay: Option<overlay::Element<'this, Message, Renderer>>, -} - impl<'a, Message, Renderer, Event> Instance<'a, Message, Renderer, Event> { fn with_element<T>( &self, diff --git a/lazy/src/lib.rs b/lazy/src/lib.rs index 42e5f587..05fce765 100644 --- a/lazy/src/lib.rs +++ b/lazy/src/lib.rs @@ -1,3 +1,9 @@ pub mod component; +pub mod responsive; pub use component::Component; +pub use responsive::Responsive; + +mod cache; + +use cache::{Cache, CacheBuilder}; diff --git a/lazy/src/responsive.rs b/lazy/src/responsive.rs new file mode 100644 index 00000000..2afad2e5 --- /dev/null +++ b/lazy/src/responsive.rs @@ -0,0 +1,413 @@ +use crate::{Cache, CacheBuilder}; + +use iced_native::event::{self, Event}; +use iced_native::layout::{self, Layout}; +use iced_native::mouse; +use iced_native::overlay; +use iced_native::renderer; +use iced_native::{ + Clipboard, Element, Hasher, Length, Point, Rectangle, Shell, Size, Widget, +}; + +use std::cell::RefCell; +use std::hash::{Hash, Hasher as _}; +use std::ops::Deref; + +#[derive(Debug, Clone, Default)] +pub struct State { + last_size: Option<Size>, + last_layout: layout::Node, + last_layout_hash: u64, +} + +impl State { + pub fn new() -> State { + State::default() + } + + pub fn layout(&self, parent: Layout<'_>) -> Layout<'_> { + Layout::with_offset( + parent.position() - Point::ORIGIN, + &self.last_layout, + ) + } +} + +#[allow(missing_debug_implementations)] +pub struct Responsive<'a, Message, Renderer>( + RefCell<Internal<'a, Message, Renderer>>, +); + +impl<'a, Message, Renderer> Responsive<'a, Message, Renderer> { + pub fn new( + state: &'a mut State, + view: impl FnOnce(Size) -> Element<'a, Message, Renderer> + 'a, + ) -> Self { + Self(RefCell::new(Internal { + state, + content: Content::Pending(Some(Box::new(view))), + })) + } +} + +impl<'a, Message, Renderer> Widget<Message, Renderer> + for Responsive<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, +{ + fn width(&self) -> Length { + Length::Fill + } + + fn height(&self) -> Length { + Length::Fill + } + + fn hash_layout(&self, _hasher: &mut Hasher) {} + + fn layout( + &self, + _renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let size = limits.max(); + + self.0.borrow_mut().state.last_size = Some(size); + + layout::Node::new(size) + } + + fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + let mut internal = self.0.borrow_mut(); + + if internal.state.last_size != Some(internal.state.last_layout.size()) { + shell.invalidate_widgets(); + } + + internal.resolve(renderer, |state, renderer, content| { + content.on_event( + event, + state.layout(layout), + cursor_position, + renderer, + clipboard, + shell, + ) + }) + } + + fn draw( + &self, + renderer: &mut Renderer, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) { + let mut internal = self.0.borrow_mut(); + + internal.resolve(renderer, |state, renderer, content| { + content.draw( + renderer, + style, + state.layout(layout), + cursor_position, + viewport, + ) + }) + } + + fn mouse_interaction( + &self, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + let mut internal = self.0.borrow_mut(); + + internal.resolve(renderer, |state, renderer, content| { + content.mouse_interaction( + state.layout(layout), + cursor_position, + viewport, + renderer, + ) + }) + } + + fn overlay( + &mut self, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option<overlay::Element<'_, Message, Renderer>> { + let has_overlay = { + use std::ops::DerefMut; + + let mut internal = self.0.borrow_mut(); + + let _ = + internal.resolve(renderer, |_state, _renderer, _content| {}); + + let Internal { content, state } = internal.deref_mut(); + + let content_layout = state.layout(layout); + + match content { + Content::Pending(_) => false, + Content::Ready(cache) => { + *cache = Some( + CacheBuilder { + element: cache.take().unwrap().into_heads().element, + overlay_builder: |element| { + element.overlay(content_layout, renderer) + }, + } + .build(), + ); + + cache.as_ref().unwrap().borrow_overlay().is_some() + } + } + }; + + has_overlay.then(|| { + overlay::Element::new( + layout.position(), + Box::new(Overlay { instance: self }), + ) + }) + } +} + +struct Internal<'a, Message, Renderer> { + state: &'a mut State, + content: Content<'a, Message, Renderer>, +} + +impl<'a, Message, Renderer> Internal<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, +{ + fn resolve<R, T>( + &mut self, + renderer: R, + f: impl FnOnce(&State, R, &mut Element<'a, Message, Renderer>) -> T, + ) -> T + where + R: Deref<Target = Renderer>, + { + self.content.resolve(&mut self.state, renderer, f) + } +} + +enum Content<'a, Message, Renderer> { + Pending( + Option<Box<dyn FnOnce(Size) -> Element<'a, Message, Renderer> + 'a>>, + ), + Ready(Option<Cache<'a, Message, Renderer>>), +} + +impl<'a, Message, Renderer> Content<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, +{ + fn resolve<R, T>( + &mut self, + state: &mut State, + renderer: R, + f: impl FnOnce(&State, R, &mut Element<'a, Message, Renderer>) -> T, + ) -> T + where + R: Deref<Target = Renderer>, + { + match self { + Content::Ready(cache) => { + let mut heads = cache.take().unwrap().into_heads(); + + let result = f(state, renderer, &mut heads.element); + + *cache = Some( + CacheBuilder { + element: heads.element, + overlay_builder: |_| None, + } + .build(), + ); + + result + } + Content::Pending(view) => { + let element = + view.take().unwrap()(state.last_size.unwrap_or(Size::ZERO)); + + let new_layout_hash = { + let mut hasher = Hasher::default(); + element.hash_layout(&mut hasher); + + hasher.finish() + }; + + if new_layout_hash != state.last_layout_hash { + state.last_layout = element.layout( + renderer.deref(), + &layout::Limits::new( + Size::ZERO, + state.last_size.unwrap_or(Size::ZERO), + ), + ); + + state.last_layout_hash = new_layout_hash; + } + + *self = Content::Ready(Some( + CacheBuilder { + element, + overlay_builder: |_| None, + } + .build(), + )); + + self.resolve(state, renderer, f) + } + } + } +} + +impl<'a, Message, Renderer> From<Responsive<'a, Message, Renderer>> + for Element<'a, Message, Renderer> +where + Renderer: iced_native::Renderer + 'a, + Message: 'a, +{ + fn from(responsive: Responsive<'a, Message, Renderer>) -> Self { + Self::new(responsive) + } +} + +struct Overlay<'a, 'b, Message, Renderer> { + instance: &'b mut Responsive<'a, Message, Renderer>, +} + +impl<'a, 'b, Message, Renderer> Overlay<'a, 'b, Message, Renderer> { + fn with_overlay_maybe<T>( + &self, + f: impl FnOnce(&overlay::Element<'_, Message, Renderer>) -> T, + ) -> Option<T> { + let internal = self.instance.0.borrow(); + + match &internal.content { + Content::Pending(_) => None, + Content::Ready(cache) => { + cache.as_ref().unwrap().borrow_overlay().as_ref().map(f) + } + } + } + + fn with_overlay_mut_maybe<T>( + &self, + f: impl FnOnce(&mut overlay::Element<'_, Message, Renderer>) -> T, + ) -> Option<T> { + let mut internal = self.instance.0.borrow_mut(); + + match &mut internal.content { + Content::Pending(_) => None, + Content::Ready(cache) => cache + .as_mut() + .unwrap() + .with_overlay_mut(|overlay| overlay.as_mut().map(f)), + } + } +} + +impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, Renderer> + for Overlay<'a, 'b, Message, Renderer> +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, + renderer: &Renderer, + ) -> mouse::Interaction { + self.with_overlay_maybe(|overlay| { + overlay.mouse_interaction( + layout, + cursor_position, + viewport, + renderer, + ) + }) + .unwrap_or_default() + } + + fn hash_layout(&self, state: &mut Hasher, position: Point) { + struct Marker; + std::any::TypeId::of::<Marker>().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, + event: iced_native::Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> iced_native::event::Status { + self.with_overlay_mut_maybe(|overlay| { + overlay.on_event( + event, + layout, + cursor_position, + renderer, + clipboard, + shell, + ) + }) + .unwrap_or_else(|| iced_native::event::Status::Ignored) + } +} |