From b2670e8752eb96a4018f93b9cb8945da81a7ebff Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 12 Feb 2022 15:17:44 +0700 Subject: Implement `Scrollable` in `iced_pure` --- pure/src/widget/scrollable.rs | 268 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 268 insertions(+) create mode 100644 pure/src/widget/scrollable.rs (limited to 'pure/src/widget/scrollable.rs') diff --git a/pure/src/widget/scrollable.rs b/pure/src/widget/scrollable.rs new file mode 100644 index 00000000..69acabb7 --- /dev/null +++ b/pure/src/widget/scrollable.rs @@ -0,0 +1,268 @@ +use crate::widget::{Column, Tree}; +use crate::{Element, Widget}; + +use iced_native::event::{self, Event}; +use iced_native::layout::{self, Layout}; +use iced_native::mouse; +use iced_native::renderer; +use iced_native::widget::scrollable; +use iced_native::{ + Alignment, Clipboard, Hasher, Length, Padding, Point, Rectangle, Shell, +}; + +pub use iced_style::scrollable::StyleSheet; + +use std::any::{self, Any}; + +/// A widget that can vertically display an infinite amount of content with a +/// scrollbar. +#[allow(missing_debug_implementations)] +pub struct Scrollable<'a, Message, Renderer> { + height: Length, + scrollbar_width: u16, + scrollbar_margin: u16, + scroller_width: u16, + content: Column<'a, Message, Renderer>, + on_scroll: Option Message>>, + style_sheet: Box, +} + +impl<'a, Message, Renderer: iced_native::Renderer> + Scrollable<'a, Message, Renderer> +{ + /// Creates a new [`Scrollable`] with the given [`State`]. + pub fn new() -> Self { + Scrollable { + height: Length::Shrink, + scrollbar_width: 10, + scrollbar_margin: 0, + scroller_width: 10, + content: Column::new(), + on_scroll: None, + style_sheet: Default::default(), + } + } + + /// Sets the vertical spacing _between_ elements. + /// + /// Custom margins per element do not exist in Iced. You should use this + /// method instead! While less flexible, it helps you keep spacing between + /// elements consistent. + pub fn spacing(mut self, units: u16) -> Self { + self.content = self.content.spacing(units); + self + } + + /// Sets the [`Padding`] of the [`Scrollable`]. + pub fn padding>(mut self, padding: P) -> Self { + self.content = self.content.padding(padding); + self + } + + /// Sets the width of the [`Scrollable`]. + pub fn width(mut self, width: Length) -> Self { + self.content = self.content.width(width); + self + } + + /// Sets the height of the [`Scrollable`]. + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } + + /// Sets the horizontal alignment of the contents of the [`Scrollable`] . + pub fn align_items(mut self, align_items: Alignment) -> Self { + self.content = self.content.align_items(align_items); + self + } + + /// Sets the scrollbar width of the [`Scrollable`] . + /// Silently enforces a minimum value of 1. + pub fn scrollbar_width(mut self, scrollbar_width: u16) -> Self { + self.scrollbar_width = scrollbar_width.max(1); + self + } + + /// Sets the scrollbar margin of the [`Scrollable`] . + pub fn scrollbar_margin(mut self, scrollbar_margin: u16) -> Self { + self.scrollbar_margin = scrollbar_margin; + self + } + + /// Sets the scroller width of the [`Scrollable`] . + /// + /// It silently enforces a minimum value of 1. + pub fn scroller_width(mut self, scroller_width: u16) -> Self { + self.scroller_width = scroller_width.max(1); + self + } + + /// Sets a function to call when the [`Scrollable`] is scrolled. + /// + /// The function takes the new relative offset of the [`Scrollable`] + /// (e.g. `0` means top, while `1` means bottom). + pub fn on_scroll(mut self, f: impl Fn(f32) -> Message + 'static) -> Self { + self.on_scroll = Some(Box::new(f)); + self + } + + /// Sets the style of the [`Scrollable`] . + pub fn style( + mut self, + style_sheet: impl Into>, + ) -> Self { + self.style_sheet = style_sheet.into(); + self + } + + /// Adds an element to the [`Scrollable`]. + pub fn push(mut self, child: E) -> Self + where + E: Into>, + { + self.content = self.content.push(child); + self + } +} + +impl<'a, Message, Renderer> Widget + for Scrollable<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, +{ + fn tag(&self) -> any::TypeId { + any::TypeId::of::() + } + + fn state(&self) -> Box { + Box::new(scrollable::State::new()) + } + + fn children(&self) -> &[Element] { + self.content.children() + } + + fn width(&self) -> Length { + Widget::::width(&self.content) + } + + fn height(&self) -> Length { + self.height + } + + fn hash_layout(&self, state: &mut Hasher) { + use std::hash::Hash; + + self.tag().hash(state); + self.height.hash(state); + self.content.hash_layout(state) + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + scrollable::layout( + renderer, + limits, + Widget::::width(self), + self.height, + |renderer, limits| self.content.layout(renderer, limits), + ) + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + scrollable::update( + tree.state.downcast_mut::(), + event, + layout, + cursor_position, + clipboard, + shell, + self.scrollbar_width, + self.scrollbar_margin, + self.scroller_width, + &self.on_scroll, + |event, layout, cursor_position, clipboard, shell| { + self.content.on_event( + &mut tree.children[0], + event, + layout, + cursor_position, + renderer, + clipboard, + shell, + ) + }, + ) + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + _viewport: &Rectangle, + ) { + scrollable::draw( + tree.state.downcast_ref::(), + renderer, + layout, + cursor_position, + self.scrollbar_width, + self.scrollbar_margin, + self.scroller_width, + self.style_sheet.as_ref(), + |renderer, layout, cursor_position, viewport| { + self.content.draw( + &tree.children[0], + renderer, + style, + layout, + cursor_position, + viewport, + ) + }, + ) + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor_position: Point, + _viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + scrollable::mouse_interaction( + tree.state.downcast_ref::(), + layout, + cursor_position, + self.scrollbar_width, + self.scrollbar_margin, + self.scroller_width, + |layout, cursor_position, viewport| { + self.content.mouse_interaction( + &tree.children[0], + layout, + cursor_position, + viewport, + renderer, + ) + }, + ) + } +} -- cgit From bd22cc0bc0f7551d29cf2acd22520f4a906f253c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 12 Feb 2022 17:21:28 +0700 Subject: Implement pure version of `todos` example :tada: The `Widget` trait in `iced_pure` needed to change a bit to make the implementation of `Element::map` possible. Specifically, the `children` method has been split into `diff` and `children_state`. --- pure/src/widget/scrollable.rs | 86 +++++++++++++++++-------------------------- 1 file changed, 33 insertions(+), 53 deletions(-) (limited to 'pure/src/widget/scrollable.rs') diff --git a/pure/src/widget/scrollable.rs b/pure/src/widget/scrollable.rs index 69acabb7..badc9fc2 100644 --- a/pure/src/widget/scrollable.rs +++ b/pure/src/widget/scrollable.rs @@ -1,4 +1,4 @@ -use crate::widget::{Column, Tree}; +use crate::widget::Tree; use crate::{Element, Widget}; use iced_native::event::{self, Event}; @@ -6,9 +6,7 @@ use iced_native::layout::{self, Layout}; use iced_native::mouse; use iced_native::renderer; use iced_native::widget::scrollable; -use iced_native::{ - Alignment, Clipboard, Hasher, Length, Padding, Point, Rectangle, Shell, -}; +use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell}; pub use iced_style::scrollable::StyleSheet; @@ -22,61 +20,33 @@ pub struct Scrollable<'a, Message, Renderer> { scrollbar_width: u16, scrollbar_margin: u16, scroller_width: u16, - content: Column<'a, Message, Renderer>, on_scroll: Option Message>>, style_sheet: Box, + content: Element<'a, Message, Renderer>, } impl<'a, Message, Renderer: iced_native::Renderer> Scrollable<'a, Message, Renderer> { - /// Creates a new [`Scrollable`] with the given [`State`]. - pub fn new() -> Self { + /// Creates a new [`Scrollable`]. + pub fn new(content: impl Into>) -> Self { Scrollable { height: Length::Shrink, scrollbar_width: 10, scrollbar_margin: 0, scroller_width: 10, - content: Column::new(), on_scroll: None, style_sheet: Default::default(), + content: content.into(), } } - /// Sets the vertical spacing _between_ elements. - /// - /// Custom margins per element do not exist in Iced. You should use this - /// method instead! While less flexible, it helps you keep spacing between - /// elements consistent. - pub fn spacing(mut self, units: u16) -> Self { - self.content = self.content.spacing(units); - self - } - - /// Sets the [`Padding`] of the [`Scrollable`]. - pub fn padding>(mut self, padding: P) -> Self { - self.content = self.content.padding(padding); - self - } - - /// Sets the width of the [`Scrollable`]. - pub fn width(mut self, width: Length) -> Self { - self.content = self.content.width(width); - self - } - /// Sets the height of the [`Scrollable`]. pub fn height(mut self, height: Length) -> Self { self.height = height; self } - /// Sets the horizontal alignment of the contents of the [`Scrollable`] . - pub fn align_items(mut self, align_items: Alignment) -> Self { - self.content = self.content.align_items(align_items); - self - } - /// Sets the scrollbar width of the [`Scrollable`] . /// Silently enforces a minimum value of 1. pub fn scrollbar_width(mut self, scrollbar_width: u16) -> Self { @@ -115,15 +85,6 @@ impl<'a, Message, Renderer: iced_native::Renderer> self.style_sheet = style_sheet.into(); self } - - /// Adds an element to the [`Scrollable`]. - pub fn push(mut self, child: E) -> Self - where - E: Into>, - { - self.content = self.content.push(child); - self - } } impl<'a, Message, Renderer> Widget @@ -139,12 +100,16 @@ where Box::new(scrollable::State::new()) } - fn children(&self) -> &[Element] { - self.content.children() + fn diff(&self, tree: &mut Tree) { + tree.diff_children(std::slice::from_ref(&self.content)) + } + + fn children_state(&self) -> Vec { + vec![Tree::new(&self.content)] } fn width(&self) -> Length { - Widget::::width(&self.content) + self.content.as_widget().width() } fn height(&self) -> Length { @@ -156,7 +121,7 @@ where self.tag().hash(state); self.height.hash(state); - self.content.hash_layout(state) + self.content.as_widget().hash_layout(state) } fn layout( @@ -169,7 +134,9 @@ where limits, Widget::::width(self), self.height, - |renderer, limits| self.content.layout(renderer, limits), + |renderer, limits| { + self.content.as_widget().layout(renderer, limits) + }, ) } @@ -195,7 +162,7 @@ where self.scroller_width, &self.on_scroll, |event, layout, cursor_position, clipboard, shell| { - self.content.on_event( + self.content.as_widget_mut().on_event( &mut tree.children[0], event, layout, @@ -227,7 +194,7 @@ where self.scroller_width, self.style_sheet.as_ref(), |renderer, layout, cursor_position, viewport| { - self.content.draw( + self.content.as_widget().draw( &tree.children[0], renderer, style, @@ -255,7 +222,7 @@ where self.scrollbar_margin, self.scroller_width, |layout, cursor_position, viewport| { - self.content.mouse_interaction( + self.content.as_widget().mouse_interaction( &tree.children[0], layout, cursor_position, @@ -266,3 +233,16 @@ where ) } } + +impl<'a, Message, Renderer> From> + for Element<'a, Message, Renderer> +where + Message: 'a + Clone, + Renderer: 'a + iced_native::Renderer, +{ + fn from( + text_input: Scrollable<'a, Message, Renderer>, + ) -> Element<'a, Message, Renderer> { + Element::new(text_input) + } +} -- cgit From 35e9b75e415ef3b9124051696b60628ef56afe47 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 16 Feb 2022 15:44:50 +0700 Subject: Introduce `Tag` and `State` opaque types in `iced_pure::widget::tree` --- pure/src/widget/scrollable.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) (limited to 'pure/src/widget/scrollable.rs') diff --git a/pure/src/widget/scrollable.rs b/pure/src/widget/scrollable.rs index badc9fc2..c3289f9e 100644 --- a/pure/src/widget/scrollable.rs +++ b/pure/src/widget/scrollable.rs @@ -1,4 +1,4 @@ -use crate::widget::Tree; +use crate::widget::tree::{self, Tree}; use crate::{Element, Widget}; use iced_native::event::{self, Event}; @@ -10,8 +10,6 @@ use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell}; pub use iced_style::scrollable::StyleSheet; -use std::any::{self, Any}; - /// A widget that can vertically display an infinite amount of content with a /// scrollbar. #[allow(missing_debug_implementations)] @@ -92,20 +90,20 @@ impl<'a, Message, Renderer> Widget where Renderer: iced_native::Renderer, { - fn tag(&self) -> any::TypeId { - any::TypeId::of::() + fn tag(&self) -> tree::Tag { + tree::Tag::of::() } - fn state(&self) -> Box { - Box::new(scrollable::State::new()) + fn state(&self) -> tree::State { + tree::State::new(scrollable::State::new()) } - fn diff(&self, tree: &mut Tree) { - tree.diff_children(std::slice::from_ref(&self.content)) + fn children(&self) -> Vec { + vec![Tree::new(&self.content)] } - fn children_state(&self) -> Vec { - vec![Tree::new(&self.content)] + fn diff(&self, tree: &mut Tree) { + tree.diff_children(std::slice::from_ref(&self.content)) } fn width(&self) -> Length { -- cgit From 019af8ddbf96680ffcee2b3407819e90575760cb Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 16 Feb 2022 17:07:25 +0700 Subject: Add `overlay` support in `iced_pure` and port `PickList` :tada: --- pure/src/widget/scrollable.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'pure/src/widget/scrollable.rs') diff --git a/pure/src/widget/scrollable.rs b/pure/src/widget/scrollable.rs index c3289f9e..6653125e 100644 --- a/pure/src/widget/scrollable.rs +++ b/pure/src/widget/scrollable.rs @@ -1,3 +1,4 @@ +use crate::overlay; use crate::widget::tree::{self, Tree}; use crate::{Element, Widget}; @@ -230,6 +231,19 @@ where }, ) } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option> { + self.content.as_widget_mut().overlay( + &mut tree.children[0], + layout.children().next().unwrap(), + renderer, + ) + } } impl<'a, Message, Renderer> From> -- cgit From 0ca066277a296469fff95bef48e8c23e1d2b375e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 16 Feb 2022 17:15:56 +0700 Subject: Fix `overlay` translation for `Scrollable` in `iced_pure` --- pure/src/widget/scrollable.rs | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) (limited to 'pure/src/widget/scrollable.rs') diff --git a/pure/src/widget/scrollable.rs b/pure/src/widget/scrollable.rs index 6653125e..8a206a6c 100644 --- a/pure/src/widget/scrollable.rs +++ b/pure/src/widget/scrollable.rs @@ -7,7 +7,7 @@ use iced_native::layout::{self, Layout}; use iced_native::mouse; use iced_native::renderer; use iced_native::widget::scrollable; -use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell}; +use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell, Vector}; pub use iced_style::scrollable::StyleSheet; @@ -238,11 +238,24 @@ where layout: Layout<'_>, renderer: &Renderer, ) -> Option> { - self.content.as_widget_mut().overlay( - &mut tree.children[0], - layout.children().next().unwrap(), - renderer, - ) + self.content + .as_widget_mut() + .overlay( + &mut tree.children[0], + layout.children().next().unwrap(), + renderer, + ) + .map(|overlay| { + let bounds = layout.bounds(); + let content_layout = layout.children().next().unwrap(); + let content_bounds = content_layout.bounds(); + let offset = tree + .state + .downcast_ref::() + .offset(bounds, content_bounds); + + overlay.translate(Vector::new(0.0, -(offset as f32))) + }) } } -- cgit From da45b6c1627935bff5334d213096c4e78972af46 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 17 Feb 2022 19:08:54 +0700 Subject: Implement `pure::Component` in `iced_lazy` --- pure/src/widget/scrollable.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'pure/src/widget/scrollable.rs') diff --git a/pure/src/widget/scrollable.rs b/pure/src/widget/scrollable.rs index 8a206a6c..bbda50e5 100644 --- a/pure/src/widget/scrollable.rs +++ b/pure/src/widget/scrollable.rs @@ -233,13 +233,13 @@ where } fn overlay<'b>( - &'b mut self, + &'b self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option> { self.content - .as_widget_mut() + .as_widget() .overlay( &mut tree.children[0], layout.children().next().unwrap(), -- cgit From d7100fd2597da82d97eaf196d50573ea64f3f8ff Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 16 Mar 2022 17:37:19 +0700 Subject: Export widget modules in `iced_pure` ... and fix collisions with the new `helpers` --- pure/src/widget/scrollable.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pure/src/widget/scrollable.rs') diff --git a/pure/src/widget/scrollable.rs b/pure/src/widget/scrollable.rs index 1548fa9d..f9a51200 100644 --- a/pure/src/widget/scrollable.rs +++ b/pure/src/widget/scrollable.rs @@ -9,7 +9,7 @@ use iced_native::renderer; use iced_native::widget::scrollable; use iced_native::{Clipboard, Length, Point, Rectangle, Shell, Vector}; -pub use iced_style::scrollable::StyleSheet; +pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet}; /// A widget that can vertically display an infinite amount of content with a /// scrollbar. -- cgit