diff options
-rw-r--r-- | examples/pane_grid/Cargo.toml | 1 | ||||
-rw-r--r-- | examples/pane_grid/src/main.rs | 2 | ||||
-rw-r--r-- | glow/src/widget.rs | 3 | ||||
-rw-r--r-- | glow/src/widget/responsive.rs | 6 | ||||
-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 | ||||
-rw-r--r-- | native/src/widget.rs | 3 | ||||
-rw-r--r-- | native/src/widget/responsive.rs | 212 | ||||
-rw-r--r-- | src/widget.rs | 9 | ||||
-rw-r--r-- | wgpu/src/widget.rs | 3 | ||||
-rw-r--r-- | wgpu/src/widget/responsive.rs | 6 |
13 files changed, 440 insertions, 248 deletions
diff --git a/examples/pane_grid/Cargo.toml b/examples/pane_grid/Cargo.toml index e489f210..03e6cd4a 100644 --- a/examples/pane_grid/Cargo.toml +++ b/examples/pane_grid/Cargo.toml @@ -8,3 +8,4 @@ publish = false [dependencies] iced = { path = "../..", features = ["debug"] } iced_native = { path = "../../native" } +iced_lazy = { path = "../../lazy" } diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index 59e5f3f9..9330ddb4 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -3,12 +3,12 @@ use iced::button::{self, Button}; use iced::executor; use iced::keyboard; use iced::pane_grid::{self, PaneGrid}; -use iced::responsive::{self, Responsive}; use iced::scrollable::{self, Scrollable}; use iced::{ Application, Color, Column, Command, Container, Element, Length, Row, Settings, Size, Subscription, Text, }; +use iced_lazy::responsive::{self, Responsive}; use iced_native::{event, subscription, Event}; pub fn main() -> iced::Result { diff --git a/glow/src/widget.rs b/glow/src/widget.rs index 85ef2f02..ee2810f9 100644 --- a/glow/src/widget.rs +++ b/glow/src/widget.rs @@ -16,7 +16,6 @@ pub mod pane_grid; pub mod pick_list; pub mod progress_bar; pub mod radio; -pub mod responsive; pub mod rule; pub mod scrollable; pub mod slider; @@ -39,8 +38,6 @@ pub use progress_bar::ProgressBar; #[doc(no_inline)] pub use radio::Radio; #[doc(no_inline)] -pub use responsive::Responsive; -#[doc(no_inline)] pub use rule::Rule; #[doc(no_inline)] pub use scrollable::Scrollable; diff --git a/glow/src/widget/responsive.rs b/glow/src/widget/responsive.rs deleted file mode 100644 index 1a64ac62..00000000 --- a/glow/src/widget/responsive.rs +++ /dev/null @@ -1,6 +0,0 @@ -use crate::Renderer; - -pub use iced_native::widget::responsive::State; - -pub type Responsive<'a, Message> = - iced_native::widget::Responsive<'a, Message, Renderer>; 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) + } +} diff --git a/native/src/widget.rs b/native/src/widget.rs index 30405346..acd2e580 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -20,7 +20,6 @@ pub mod pane_grid; pub mod pick_list; pub mod progress_bar; pub mod radio; -pub mod responsive; pub mod row; pub mod rule; pub mod scrollable; @@ -51,8 +50,6 @@ pub use progress_bar::ProgressBar; #[doc(no_inline)] pub use radio::Radio; #[doc(no_inline)] -pub use responsive::Responsive; -#[doc(no_inline)] pub use row::Row; #[doc(no_inline)] pub use rule::Rule; diff --git a/native/src/widget/responsive.rs b/native/src/widget/responsive.rs deleted file mode 100644 index fd9e490a..00000000 --- a/native/src/widget/responsive.rs +++ /dev/null @@ -1,212 +0,0 @@ -use crate::event::{self, Event}; -use crate::layout::{self, Layout}; -use crate::mouse; -use crate::renderer; -use crate::{ - Clipboard, Element, Hasher, Length, Point, Rectangle, Shell, Size, Widget, -}; - -use std::cell::RefCell; -use std::hash::Hasher as _; - -#[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() - } -} - -#[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: crate::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(); - } - - let (content, content_layout) = internal.content(layout, renderer); - - content.on_event( - event, - content_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(); - let (content, content_layout) = internal.content(layout, renderer); - - content.draw(renderer, style, content_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(); - let (content, content_layout) = internal.content(layout, renderer); - - content.mouse_interaction( - content_layout, - cursor_position, - viewport, - renderer, - ) - } -} - -struct Internal<'a, Message, Renderer> { - state: &'a mut State, - content: Content<'a, Message, Renderer>, -} - -impl<'a, Message, Renderer> Internal<'a, Message, Renderer> -where - Renderer: crate::Renderer, -{ - fn content( - &mut self, - layout: Layout<'_>, - renderer: &Renderer, - ) -> (&mut Element<'a, Message, Renderer>, Layout<'_>) { - let content = self.content.resolve(&mut self.state, renderer); - - let content_layout = Layout::with_offset( - layout.position() - Point::ORIGIN, - &self.state.last_layout, - ); - - (content, content_layout) - } -} - -enum Content<'a, Message, Renderer> { - Pending( - Option<Box<dyn FnOnce(Size) -> Element<'a, Message, Renderer> + 'a>>, - ), - Ready(Element<'a, Message, Renderer>), -} - -impl<'a, Message, Renderer> Content<'a, Message, Renderer> -where - Renderer: crate::Renderer, -{ - fn resolve( - &mut self, - state: &mut State, - renderer: &Renderer, - ) -> &mut Element<'a, Message, Renderer> { - match self { - Content::Ready(element) => element, - 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, - &layout::Limits::new( - Size::ZERO, - state.last_size.unwrap_or(Size::ZERO), - ), - ); - - state.last_layout_hash = new_layout_hash; - } - - *self = Content::Ready(element); - - self.resolve(state, renderer) - } - } - } -} - -impl<'a, Message, Renderer> From<Responsive<'a, Message, Renderer>> - for Element<'a, Message, Renderer> -where - Renderer: crate::Renderer + 'a, - Message: 'a, -{ - fn from(responsive: Responsive<'a, Message, Renderer>) -> Self { - Self::new(responsive) - } -} diff --git a/src/widget.rs b/src/widget.rs index c2790e4a..0f0b0325 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -17,8 +17,8 @@ mod platform { pub use crate::renderer::widget::{ button, checkbox, container, pane_grid, pick_list, progress_bar, radio, - responsive, rule, scrollable, slider, text_input, toggler, tooltip, - Column, Row, Space, Text, + rule, scrollable, slider, text_input, toggler, tooltip, Column, Row, + Space, Text, }; #[cfg(any(feature = "canvas", feature = "glow_canvas"))] @@ -54,9 +54,8 @@ mod platform { pub use { button::Button, checkbox::Checkbox, container::Container, image::Image, pane_grid::PaneGrid, pick_list::PickList, progress_bar::ProgressBar, - radio::Radio, responsive::Responsive, rule::Rule, - scrollable::Scrollable, slider::Slider, svg::Svg, - text_input::TextInput, toggler::Toggler, tooltip::Tooltip, + radio::Radio, rule::Rule, scrollable::Scrollable, slider::Slider, + svg::Svg, text_input::TextInput, toggler::Toggler, tooltip::Tooltip, }; #[cfg(any(feature = "canvas", feature = "glow_canvas"))] diff --git a/wgpu/src/widget.rs b/wgpu/src/widget.rs index ac8653d4..99ae0ac2 100644 --- a/wgpu/src/widget.rs +++ b/wgpu/src/widget.rs @@ -16,7 +16,6 @@ pub mod pane_grid; pub mod pick_list; pub mod progress_bar; pub mod radio; -pub mod responsive; pub mod rule; pub mod scrollable; pub mod slider; @@ -39,8 +38,6 @@ pub use progress_bar::ProgressBar; #[doc(no_inline)] pub use radio::Radio; #[doc(no_inline)] -pub use responsive::Responsive; -#[doc(no_inline)] pub use rule::Rule; #[doc(no_inline)] pub use scrollable::Scrollable; diff --git a/wgpu/src/widget/responsive.rs b/wgpu/src/widget/responsive.rs deleted file mode 100644 index 1a64ac62..00000000 --- a/wgpu/src/widget/responsive.rs +++ /dev/null @@ -1,6 +0,0 @@ -use crate::Renderer; - -pub use iced_native::widget::responsive::State; - -pub type Responsive<'a, Message> = - iced_native::widget::Responsive<'a, Message, Renderer>; |