From 71381418116afc1f9c40c8faf2cb1e53072a1cfa Mon Sep 17 00:00:00 2001 From: Nick Senger Date: Thu, 4 Aug 2022 21:25:36 -0700 Subject: [PATCH 01/14] feat: implement `Cached` widget --- lazy/src/cached.rs | 344 +++++++++++++++++++++++++++++++++++++++++++++ lazy/src/lib.rs | 2 + 2 files changed, 346 insertions(+) create mode 100644 lazy/src/cached.rs diff --git a/lazy/src/cached.rs b/lazy/src/cached.rs new file mode 100644 index 0000000000..c046179fe3 --- /dev/null +++ b/lazy/src/cached.rs @@ -0,0 +1,344 @@ +use iced_native::event; +use iced_native::layout::{self, Layout}; +use iced_native::mouse; +use iced_native::overlay; +use iced_native::renderer; +use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell, Size}; +use iced_native::widget::tree::{self, Tree}; +use iced_native::{Element, Widget}; + +use ouroboros::self_referencing; +use std::cell::{Ref, RefCell, RefMut}; +use std::hash::{Hash, Hasher as H}; +use std::marker::PhantomData; +use std::rc::Rc; + +#[allow(missing_debug_implementations)] +pub struct Cached<'a, Message, Renderer, Dependency, View> { + dependency: Dependency, + view: Box View + 'a>, + element: RefCell>>>>, +} + +impl<'a, Message, Renderer, Dependency, View> + Cached<'a, Message, Renderer, Dependency, View> +where + Dependency: Hash + 'a, + View: Into>, +{ + pub fn new(dependency: Dependency, view: impl Fn() -> View + 'a) -> Self { + Self { + dependency, + view: Box::new(view), + element: RefCell::new(None), + } + } + + fn with_element( + &self, + f: impl FnOnce(Ref>) -> T, + ) -> T { + f(self.element.borrow().as_ref().unwrap().borrow()) + } + + fn with_element_mut( + &self, + f: impl FnOnce(RefMut>) -> T, + ) -> T { + f(self.element.borrow().as_ref().unwrap().borrow_mut()) + } +} + +struct Internal { + element: Rc>>, + hash: u64, +} + +impl<'a, Message, Renderer, Dependency, View> Widget + for Cached<'a, Message, Renderer, Dependency, View> +where + View: Into> + 'static, + Dependency: Hash + 'a, + Message: 'static, + Renderer: iced_native::Renderer + 'static, +{ + fn tag(&self) -> tree::Tag { + struct Tag(T); + tree::Tag::of::>() + } + + fn state(&self) -> tree::State { + let mut hasher = Hasher::default(); + self.dependency.hash(&mut hasher); + let hash = hasher.finish(); + + let element = Rc::new(RefCell::new((self.view)().into())); + + (*self.element.borrow_mut()) = Some(element.clone()); + + tree::State::new(Internal { element, hash }) + } + + fn children(&self) -> Vec { + vec![Tree::new( + self.element.borrow().as_ref().unwrap().borrow().as_widget(), + )] + } + + fn diff(&self, tree: &mut Tree) { + let current = tree.state.downcast_mut::>(); + + let mut hasher = Hasher::default(); + self.dependency.hash(&mut hasher); + let new_hash = hasher.finish(); + + if current.hash != new_hash { + current.hash = new_hash; + + let element = (self.view)().into(); + current.element = Rc::new(RefCell::new(element)); + } + + (*self.element.borrow_mut()) = Some(current.element.clone()); + tree.diff_children(std::slice::from_ref( + &self.element.borrow().as_ref().unwrap().borrow().as_widget(), + )); + } + + fn width(&self) -> Length { + self.with_element(|element| element.as_widget().width()) + } + + fn height(&self) -> Length { + self.with_element(|element| element.as_widget().height()) + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + self.with_element(|element| { + element.as_widget().layout(renderer, limits) + }) + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: iced_native::Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + self.with_element_mut(|mut element| { + element.as_widget_mut().on_event( + &mut tree.children[0], + event, + layout, + cursor_position, + renderer, + clipboard, + shell, + ) + }) + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.with_element(|element| { + element.as_widget().mouse_interaction( + &tree.children[0], + layout, + cursor_position, + viewport, + renderer, + ) + }) + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &Renderer::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) { + self.with_element(|element| { + element.as_widget().draw( + &tree.children[0], + renderer, + theme, + style, + layout, + cursor_position, + viewport, + ) + }) + } + + fn overlay<'b>( + &'b self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option> { + let overlay = OverlayBuilder { + cached: self, + tree: &mut tree.children[0], + types: PhantomData, + element_ref_builder: |cached| cached.element.borrow(), + element_builder: |element_ref| { + element_ref.as_ref().unwrap().borrow() + }, + overlay_builder: |element, tree| { + element.as_widget().overlay(tree, layout, renderer) + }, + } + .build(); + + let has_overlay = overlay.with_overlay(|overlay| { + overlay.as_ref().map(overlay::Element::position) + }); + + has_overlay + .map(|position| overlay::Element::new(position, Box::new(overlay))) + } +} + +#[self_referencing] +struct Overlay<'a, 'b, Message, Renderer, Dependency, View> { + cached: &'a Cached<'b, Message, Renderer, Dependency, View>, + tree: &'a mut Tree, + types: PhantomData<(Message, Dependency, View)>, + + #[borrows(cached)] + #[covariant] + element_ref: + Ref<'this, Option>>>>, + + #[borrows(element_ref)] + #[covariant] + element: Ref<'this, Element<'static, Message, Renderer>>, + + #[borrows(element, mut tree)] + #[covariant] + overlay: Option>, +} + +impl<'a, 'b, Message, Renderer, Dependency, View> + Overlay<'a, 'b, Message, Renderer, Dependency, View> +{ + fn with_overlay_maybe( + &self, + f: impl FnOnce(&overlay::Element<'_, Message, Renderer>) -> T, + ) -> Option { + self.borrow_overlay().as_ref().map(f) + } + + fn with_overlay_mut_maybe( + &mut self, + f: impl FnOnce(&mut overlay::Element<'_, Message, Renderer>) -> T, + ) -> Option { + self.with_overlay_mut(|overlay| overlay.as_mut().map(f)) + } +} + +impl<'a, 'b, Message, Renderer, Dependency, View> + overlay::Overlay + for Overlay<'a, 'b, Message, Renderer, Dependency, View> +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, + theme: &Renderer::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + ) { + let _ = self.with_overlay_maybe(|overlay| { + overlay.draw(renderer, theme, 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 on_event( + &mut self, + event: iced_native::Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> 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) + } +} + +impl<'a, Message, Renderer, Dependency, View> + From> + for Element<'a, Message, Renderer> +where + View: Into> + 'static, + Renderer: iced_native::Renderer + 'static, + Message: 'static, + Dependency: Hash + 'a, +{ + fn from(cached: Cached<'a, Message, Renderer, Dependency, View>) -> Self { + Self::new(cached) + } +} diff --git a/lazy/src/lib.rs b/lazy/src/lib.rs index 3827746c05..c01b439b75 100644 --- a/lazy/src/lib.rs +++ b/lazy/src/lib.rs @@ -17,9 +17,11 @@ clippy::type_complexity )] #![cfg_attr(docsrs, feature(doc_cfg))] +pub mod cached; pub mod component; pub mod responsive; +pub use cached::Cached; pub use component::Component; pub use responsive::Responsive; From 50eb9e34b8ea939c263c1f548ef3f228400d4bda Mon Sep 17 00:00:00 2001 From: Nick Senger Date: Fri, 5 Aug 2022 15:40:55 -0700 Subject: [PATCH 02/14] add example --- Cargo.toml | 1 + examples/cached/Cargo.toml | 11 +++ examples/cached/src/main.rs | 141 ++++++++++++++++++++++++++++++++++++ 3 files changed, 153 insertions(+) create mode 100644 examples/cached/Cargo.toml create mode 100644 examples/cached/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 9c6a435a99..d855e82ac2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,7 @@ members = [ "winit", "examples/arc", "examples/bezier_tool", + "examples/cached", "examples/clock", "examples/color_palette", "examples/component", diff --git a/examples/cached/Cargo.toml b/examples/cached/Cargo.toml new file mode 100644 index 0000000000..21f5988644 --- /dev/null +++ b/examples/cached/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "pure_cached" +version = "0.1.0" +authors = ["Nick Senger "] +edition = "2021" +publish = false + +[dependencies] +iced = { path = "../..", features = ["debug"] } +iced_native = { path = "../../native" } +iced_lazy = { path = "../../lazy" } diff --git a/examples/cached/src/main.rs b/examples/cached/src/main.rs new file mode 100644 index 0000000000..d7787979da --- /dev/null +++ b/examples/cached/src/main.rs @@ -0,0 +1,141 @@ +use iced::widget::{ + button, column, horizontal_rule, horizontal_space, row, scrollable, text, + text_input, +}; +use iced::{Element, Sandbox}; +use iced::{Length, Settings}; +use iced_lazy::Cached; + +use std::collections::HashSet; + +pub fn main() -> iced::Result { + App::run(Settings::default()) +} + +#[derive(Hash)] +enum SortOrder { + Ascending, + Descending, +} + +impl std::fmt::Display for SortOrder { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::Ascending => "Ascending", + Self::Descending => "Descending", + } + ) + } +} + +struct App { + options: HashSet, + input: String, + sort_order: SortOrder, +} + +impl Default for App { + fn default() -> Self { + Self { + options: ["Foo", "Bar", "Baz", "Qux", "Corge", "Waldo", "Fred"] + .into_iter() + .map(ToString::to_string) + .collect(), + input: Default::default(), + sort_order: SortOrder::Ascending, + } + } +} + +#[derive(Debug, Clone)] +enum Message { + InputChanged(String), + ToggleSortOrder, + DeleteOption(String), + AddOption(String), +} + +impl Sandbox for App { + type Message = Message; + + fn new() -> Self { + Self::default() + } + + fn title(&self) -> String { + String::from("Cached - Iced") + } + + fn update(&mut self, message: Message) { + match message { + Message::InputChanged(input) => { + self.input = input; + } + Message::ToggleSortOrder => { + self.sort_order = match self.sort_order { + SortOrder::Ascending => SortOrder::Descending, + SortOrder::Descending => SortOrder::Ascending, + } + } + Message::AddOption(option) => { + self.options.insert(option); + self.input.clear(); + } + Message::DeleteOption(option) => { + self.options.remove(&option); + } + } + } + + fn view(&self) -> Element { + let options = + Cached::new((&self.sort_order, self.options.len()), || { + let mut options = self.options.iter().collect::>(); + options.sort_by(|a, b| match self.sort_order { + SortOrder::Ascending => { + a.to_lowercase().cmp(&b.to_lowercase()) + } + SortOrder::Descending => { + b.to_lowercase().cmp(&a.to_lowercase()) + } + }); + + options.into_iter().fold( + column![horizontal_rule(1)], + |column, option| { + column + .push(row![ + text(option), + horizontal_space(Length::Fill), + button("Delete").on_press( + Message::DeleteOption(option.to_string(),), + ) + ]) + .push(horizontal_rule(1)) + }, + ) + }); + + scrollable( + column![ + button(text(format!( + "Toggle Sort Order ({})", + self.sort_order + ))) + .on_press(Message::ToggleSortOrder), + options, + text_input( + "Add a new option", + &self.input, + Message::InputChanged, + ) + .on_submit(Message::AddOption(self.input.clone())), + ] + .spacing(20), + ) + .into() + } +} From 459d32b98476b05da5e7548c67c28c147b107736 Mon Sep 17 00:00:00 2001 From: Nick Senger Date: Fri, 5 Aug 2022 16:15:19 -0700 Subject: [PATCH 03/14] lint --- lazy/src/cached.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lazy/src/cached.rs b/lazy/src/cached.rs index c046179fe3..184ec9e7de 100644 --- a/lazy/src/cached.rs +++ b/lazy/src/cached.rs @@ -3,8 +3,8 @@ use iced_native::layout::{self, Layout}; use iced_native::mouse; use iced_native::overlay; use iced_native::renderer; -use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell, Size}; use iced_native::widget::tree::{self, Tree}; +use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell, Size}; use iced_native::{Element, Widget}; use ouroboros::self_referencing; @@ -325,7 +325,7 @@ where shell, ) }) - .unwrap_or_else(|| iced_native::event::Status::Ignored) + .unwrap_or(iced_native::event::Status::Ignored) } } From b5d33b0370ba8430bb8dbede7fef377ac2a67667 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 3 Nov 2022 02:28:54 +0100 Subject: [PATCH 04/14] Diff children only when hash differs in `lazy::Cached` --- lazy/src/cached.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lazy/src/cached.rs b/lazy/src/cached.rs index 184ec9e7de..a2a519c0ce 100644 --- a/lazy/src/cached.rs +++ b/lazy/src/cached.rs @@ -97,12 +97,14 @@ where let element = (self.view)().into(); current.element = Rc::new(RefCell::new(element)); - } - (*self.element.borrow_mut()) = Some(current.element.clone()); - tree.diff_children(std::slice::from_ref( - &self.element.borrow().as_ref().unwrap().borrow().as_widget(), - )); + (*self.element.borrow_mut()) = Some(current.element.clone()); + tree.diff_children(std::slice::from_ref( + &self.element.borrow().as_ref().unwrap().borrow().as_widget(), + )); + } else { + (*self.element.borrow_mut()) = Some(current.element.clone()); + } } fn width(&self) -> Length { From 54f9ab7d5f0680bab95a8bdf95b46fb7d06b6ede Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 3 Nov 2022 02:30:41 +0100 Subject: [PATCH 05/14] Implement `Widget::operate` for `lazy::Cached` --- lazy/src/cached.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/lazy/src/cached.rs b/lazy/src/cached.rs index a2a519c0ce..931184b554 100644 --- a/lazy/src/cached.rs +++ b/lazy/src/cached.rs @@ -4,8 +4,9 @@ use iced_native::mouse; use iced_native::overlay; use iced_native::renderer; use iced_native::widget::tree::{self, Tree}; +use iced_native::widget::{self, Widget}; +use iced_native::Element; use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell, Size}; -use iced_native::{Element, Widget}; use ouroboros::self_referencing; use std::cell::{Ref, RefCell, RefMut}; @@ -125,6 +126,21 @@ where }) } + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + operation: &mut dyn widget::Operation, + ) { + self.with_element(|element| { + element.as_widget().operate( + &mut tree.children[0], + layout, + operation, + ); + }); + } + fn on_event( &mut self, tree: &mut Tree, From 4f83500bb8f522504a7ec0875a6f134eac733175 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 3 Nov 2022 02:31:04 +0100 Subject: [PATCH 06/14] Rename `pure_cached` example to `cached` --- examples/cached/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/cached/Cargo.toml b/examples/cached/Cargo.toml index 21f5988644..4d4013e659 100644 --- a/examples/cached/Cargo.toml +++ b/examples/cached/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "pure_cached" +name = "cached" version = "0.1.0" authors = ["Nick Senger "] edition = "2021" From 1fb84ae5d3732ed51b35fb5419a5ad014e22ca5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 3 Nov 2022 02:32:23 +0100 Subject: [PATCH 07/14] Remove `iced_native` dependency from `cached` example --- examples/cached/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/cached/Cargo.toml b/examples/cached/Cargo.toml index 4d4013e659..2c7edde268 100644 --- a/examples/cached/Cargo.toml +++ b/examples/cached/Cargo.toml @@ -7,5 +7,4 @@ publish = false [dependencies] iced = { path = "../..", features = ["debug"] } -iced_native = { path = "../../native" } iced_lazy = { path = "../../lazy" } From 0e295be8917a543e3641f8c4657db87fed0ce91b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 3 Nov 2022 02:32:38 +0100 Subject: [PATCH 08/14] Move declaration of `SortOrder` in `cached` example --- examples/cached/src/main.rs | 38 ++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/examples/cached/src/main.rs b/examples/cached/src/main.rs index d7787979da..b900ff36ff 100644 --- a/examples/cached/src/main.rs +++ b/examples/cached/src/main.rs @@ -12,25 +12,6 @@ pub fn main() -> iced::Result { App::run(Settings::default()) } -#[derive(Hash)] -enum SortOrder { - Ascending, - Descending, -} - -impl std::fmt::Display for SortOrder { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - match self { - Self::Ascending => "Ascending", - Self::Descending => "Descending", - } - ) - } -} - struct App { options: HashSet, input: String, @@ -139,3 +120,22 @@ impl Sandbox for App { .into() } } + +#[derive(Debug, Hash)] +enum SortOrder { + Ascending, + Descending, +} + +impl std::fmt::Display for SortOrder { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::Ascending => "Ascending", + Self::Descending => "Descending", + } + ) + } +} From 0478df9fd61c67255b0ea213aa83f56f5698ae7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 3 Nov 2022 02:35:05 +0100 Subject: [PATCH 09/14] Add `padding` to main `column` in `cached` example --- examples/cached/src/main.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/cached/src/main.rs b/examples/cached/src/main.rs index b900ff36ff..85a4a4f0f2 100644 --- a/examples/cached/src/main.rs +++ b/examples/cached/src/main.rs @@ -115,7 +115,8 @@ impl Sandbox for App { ) .on_submit(Message::AddOption(self.input.clone())), ] - .spacing(20), + .spacing(20) + .padding(20), ) .into() } From 1687d11389fa8ddfb8d2d7cda64cc6b5c4aa7f9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 3 Nov 2022 02:35:17 +0100 Subject: [PATCH 10/14] Increase default `padding` of `TextInput` --- native/src/widget/text_input.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index e5213cbee6..54a6aaf805 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -92,7 +92,7 @@ where is_secure: false, font: Default::default(), width: Length::Fill, - padding: Padding::ZERO, + padding: Padding::new(5), size: None, on_change: Box::new(on_change), on_paste: None, From adf541d4325df22d577342f870f0f95fa357797a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 3 Nov 2022 02:40:51 +0100 Subject: [PATCH 11/14] Improve layout of `cached` example --- examples/cached/src/main.rs | 59 ++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/examples/cached/src/main.rs b/examples/cached/src/main.rs index 85a4a4f0f2..39364dc9e1 100644 --- a/examples/cached/src/main.rs +++ b/examples/cached/src/main.rs @@ -1,9 +1,8 @@ +use iced::theme; use iced::widget::{ - button, column, horizontal_rule, horizontal_space, row, scrollable, text, - text_input, + button, column, horizontal_space, row, scrollable, text, text_input, }; -use iced::{Element, Sandbox}; -use iced::{Length, Settings}; +use iced::{Element, Length, Sandbox, Settings}; use iced_lazy::Cached; use std::collections::HashSet; @@ -74,7 +73,8 @@ impl Sandbox for App { fn view(&self) -> Element { let options = Cached::new((&self.sort_order, self.options.len()), || { - let mut options = self.options.iter().collect::>(); + let mut options: Vec<_> = self.options.iter().collect(); + options.sort_by(|a, b| match self.sort_order { SortOrder::Ascending => { a.to_lowercase().cmp(&b.to_lowercase()) @@ -84,40 +84,45 @@ impl Sandbox for App { } }); - options.into_iter().fold( - column![horizontal_rule(1)], - |column, option| { - column - .push(row![ + column( + options + .into_iter() + .map(|option| { + row![ text(option), horizontal_space(Length::Fill), - button("Delete").on_press( - Message::DeleteOption(option.to_string(),), - ) - ]) - .push(horizontal_rule(1)) - }, + button("Delete") + .on_press(Message::DeleteOption( + option.to_string(), + ),) + .style(theme::Button::Destructive) + ] + .into() + }) + .collect(), ) + .spacing(10) }); - scrollable( - column![ - button(text(format!( - "Toggle Sort Order ({})", - self.sort_order - ))) - .on_press(Message::ToggleSortOrder), - options, + column![ + scrollable(options).height(Length::Fill), + row![ text_input( "Add a new option", &self.input, Message::InputChanged, ) .on_submit(Message::AddOption(self.input.clone())), + button(text(format!( + "Toggle Sort Order ({})", + self.sort_order + ))) + .on_press(Message::ToggleSortOrder) ] - .spacing(20) - .padding(20), - ) + .spacing(10) + ] + .spacing(20) + .padding(20) .into() } } From 1cdc1fcd0669bfea096237c07b32742c1a3f2158 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 3 Nov 2022 02:46:31 +0100 Subject: [PATCH 12/14] Rename `iced_lazy::Cached` to `Lazy` :tada: --- examples/cached/src/main.rs | 59 ++++++++++++++++----------------- lazy/src/{cached.rs => lazy.rs} | 14 ++++---- lazy/src/lib.rs | 17 ++++++++-- 3 files changed, 50 insertions(+), 40 deletions(-) rename lazy/src/{cached.rs => lazy.rs} (96%) diff --git a/examples/cached/src/main.rs b/examples/cached/src/main.rs index 39364dc9e1..7c8b06f005 100644 --- a/examples/cached/src/main.rs +++ b/examples/cached/src/main.rs @@ -3,7 +3,7 @@ use iced::widget::{ button, column, horizontal_space, row, scrollable, text, text_input, }; use iced::{Element, Length, Sandbox, Settings}; -use iced_lazy::Cached; +use iced_lazy::lazy; use std::collections::HashSet; @@ -71,39 +71,36 @@ impl Sandbox for App { } fn view(&self) -> Element { - let options = - Cached::new((&self.sort_order, self.options.len()), || { - let mut options: Vec<_> = self.options.iter().collect(); + let options = lazy((&self.sort_order, self.options.len()), || { + let mut options: Vec<_> = self.options.iter().collect(); - options.sort_by(|a, b| match self.sort_order { - SortOrder::Ascending => { - a.to_lowercase().cmp(&b.to_lowercase()) - } - SortOrder::Descending => { - b.to_lowercase().cmp(&a.to_lowercase()) - } - }); - - column( - options - .into_iter() - .map(|option| { - row![ - text(option), - horizontal_space(Length::Fill), - button("Delete") - .on_press(Message::DeleteOption( - option.to_string(), - ),) - .style(theme::Button::Destructive) - ] - .into() - }) - .collect(), - ) - .spacing(10) + options.sort_by(|a, b| match self.sort_order { + SortOrder::Ascending => a.to_lowercase().cmp(&b.to_lowercase()), + SortOrder::Descending => { + b.to_lowercase().cmp(&a.to_lowercase()) + } }); + column( + options + .into_iter() + .map(|option| { + row![ + text(option), + horizontal_space(Length::Fill), + button("Delete") + .on_press(Message::DeleteOption( + option.to_string(), + ),) + .style(theme::Button::Destructive) + ] + .into() + }) + .collect(), + ) + .spacing(10) + }); + column![ scrollable(options).height(Length::Fill), row![ diff --git a/lazy/src/cached.rs b/lazy/src/lazy.rs similarity index 96% rename from lazy/src/cached.rs rename to lazy/src/lazy.rs index 931184b554..d61cc77e46 100644 --- a/lazy/src/cached.rs +++ b/lazy/src/lazy.rs @@ -15,14 +15,14 @@ use std::marker::PhantomData; use std::rc::Rc; #[allow(missing_debug_implementations)] -pub struct Cached<'a, Message, Renderer, Dependency, View> { +pub struct Lazy<'a, Message, Renderer, Dependency, View> { dependency: Dependency, view: Box View + 'a>, element: RefCell>>>>, } impl<'a, Message, Renderer, Dependency, View> - Cached<'a, Message, Renderer, Dependency, View> + Lazy<'a, Message, Renderer, Dependency, View> where Dependency: Hash + 'a, View: Into>, @@ -56,7 +56,7 @@ struct Internal { } impl<'a, Message, Renderer, Dependency, View> Widget - for Cached<'a, Message, Renderer, Dependency, View> + for Lazy<'a, Message, Renderer, Dependency, View> where View: Into> + 'static, Dependency: Hash + 'a, @@ -237,7 +237,7 @@ where #[self_referencing] struct Overlay<'a, 'b, Message, Renderer, Dependency, View> { - cached: &'a Cached<'b, Message, Renderer, Dependency, View>, + cached: &'a Lazy<'b, Message, Renderer, Dependency, View>, tree: &'a mut Tree, types: PhantomData<(Message, Dependency, View)>, @@ -348,7 +348,7 @@ where } impl<'a, Message, Renderer, Dependency, View> - From> + From> for Element<'a, Message, Renderer> where View: Into> + 'static, @@ -356,7 +356,7 @@ where Message: 'static, Dependency: Hash + 'a, { - fn from(cached: Cached<'a, Message, Renderer, Dependency, View>) -> Self { - Self::new(cached) + fn from(lazy: Lazy<'a, Message, Renderer, Dependency, View>) -> Self { + Self::new(lazy) } } diff --git a/lazy/src/lib.rs b/lazy/src/lib.rs index c01b439b75..f49fe4b6e6 100644 --- a/lazy/src/lib.rs +++ b/lazy/src/lib.rs @@ -17,17 +17,30 @@ clippy::type_complexity )] #![cfg_attr(docsrs, feature(doc_cfg))] -pub mod cached; +mod lazy; + pub mod component; pub mod responsive; -pub use cached::Cached; pub use component::Component; +pub use lazy::Lazy; pub use responsive::Responsive; mod cache; use iced_native::{Element, Size}; +use std::hash::Hash; + +pub fn lazy<'a, Message, Renderer, Dependency, View>( + dependency: Dependency, + view: impl Fn() -> View + 'a, +) -> Lazy<'a, Message, Renderer, Dependency, View> +where + Dependency: Hash + 'a, + View: Into>, +{ + Lazy::new(dependency, view) +} /// Turns an implementor of [`Component`] into an [`Element`] that can be /// embedded in any application. From 6efda2457e7b80e9d3d145ceb9910bfbb5af9994 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 3 Nov 2022 02:47:43 +0100 Subject: [PATCH 13/14] Rename `SortOrder` to `Order` in `cached` example --- examples/cached/src/main.rs | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/examples/cached/src/main.rs b/examples/cached/src/main.rs index 7c8b06f005..8845b87407 100644 --- a/examples/cached/src/main.rs +++ b/examples/cached/src/main.rs @@ -14,7 +14,7 @@ pub fn main() -> iced::Result { struct App { options: HashSet, input: String, - sort_order: SortOrder, + order: Order, } impl Default for App { @@ -25,7 +25,7 @@ impl Default for App { .map(ToString::to_string) .collect(), input: Default::default(), - sort_order: SortOrder::Ascending, + order: Order::Ascending, } } } @@ -33,7 +33,7 @@ impl Default for App { #[derive(Debug, Clone)] enum Message { InputChanged(String), - ToggleSortOrder, + ToggleOrder, DeleteOption(String), AddOption(String), } @@ -54,10 +54,10 @@ impl Sandbox for App { Message::InputChanged(input) => { self.input = input; } - Message::ToggleSortOrder => { - self.sort_order = match self.sort_order { - SortOrder::Ascending => SortOrder::Descending, - SortOrder::Descending => SortOrder::Ascending, + Message::ToggleOrder => { + self.order = match self.order { + Order::Ascending => Order::Descending, + Order::Descending => Order::Ascending, } } Message::AddOption(option) => { @@ -71,14 +71,12 @@ impl Sandbox for App { } fn view(&self) -> Element { - let options = lazy((&self.sort_order, self.options.len()), || { + let options = lazy((&self.order, self.options.len()), || { let mut options: Vec<_> = self.options.iter().collect(); - options.sort_by(|a, b| match self.sort_order { - SortOrder::Ascending => a.to_lowercase().cmp(&b.to_lowercase()), - SortOrder::Descending => { - b.to_lowercase().cmp(&a.to_lowercase()) - } + options.sort_by(|a, b| match self.order { + Order::Ascending => a.to_lowercase().cmp(&b.to_lowercase()), + Order::Descending => b.to_lowercase().cmp(&a.to_lowercase()), }); column( @@ -110,11 +108,8 @@ impl Sandbox for App { Message::InputChanged, ) .on_submit(Message::AddOption(self.input.clone())), - button(text(format!( - "Toggle Sort Order ({})", - self.sort_order - ))) - .on_press(Message::ToggleSortOrder) + button(text(format!("Toggle Order ({})", self.order))) + .on_press(Message::ToggleOrder) ] .spacing(10) ] @@ -125,12 +120,12 @@ impl Sandbox for App { } #[derive(Debug, Hash)] -enum SortOrder { +enum Order { Ascending, Descending, } -impl std::fmt::Display for SortOrder { +impl std::fmt::Display for Order { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, From 415978b80771fdd065b65e115d1dcc6aaa9d792c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 3 Nov 2022 02:55:22 +0100 Subject: [PATCH 14/14] Rename `cached` example to `lazy` --- Cargo.toml | 2 +- examples/lazy/Cargo.toml | 10 +++ examples/lazy/src/main.rs | 139 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 examples/lazy/Cargo.toml create mode 100644 examples/lazy/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index d855e82ac2..f9b441a530 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,7 +60,6 @@ members = [ "winit", "examples/arc", "examples/bezier_tool", - "examples/cached", "examples/clock", "examples/color_palette", "examples/component", @@ -73,6 +72,7 @@ members = [ "examples/geometry", "examples/integration_opengl", "examples/integration_wgpu", + "examples/lazy", "examples/multitouch", "examples/pane_grid", "examples/pick_list", diff --git a/examples/lazy/Cargo.toml b/examples/lazy/Cargo.toml new file mode 100644 index 0000000000..79255c2563 --- /dev/null +++ b/examples/lazy/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "lazy" +version = "0.1.0" +authors = ["Nick Senger "] +edition = "2021" +publish = false + +[dependencies] +iced = { path = "../..", features = ["debug"] } +iced_lazy = { path = "../../lazy" } diff --git a/examples/lazy/src/main.rs b/examples/lazy/src/main.rs new file mode 100644 index 0000000000..8845b87407 --- /dev/null +++ b/examples/lazy/src/main.rs @@ -0,0 +1,139 @@ +use iced::theme; +use iced::widget::{ + button, column, horizontal_space, row, scrollable, text, text_input, +}; +use iced::{Element, Length, Sandbox, Settings}; +use iced_lazy::lazy; + +use std::collections::HashSet; + +pub fn main() -> iced::Result { + App::run(Settings::default()) +} + +struct App { + options: HashSet, + input: String, + order: Order, +} + +impl Default for App { + fn default() -> Self { + Self { + options: ["Foo", "Bar", "Baz", "Qux", "Corge", "Waldo", "Fred"] + .into_iter() + .map(ToString::to_string) + .collect(), + input: Default::default(), + order: Order::Ascending, + } + } +} + +#[derive(Debug, Clone)] +enum Message { + InputChanged(String), + ToggleOrder, + DeleteOption(String), + AddOption(String), +} + +impl Sandbox for App { + type Message = Message; + + fn new() -> Self { + Self::default() + } + + fn title(&self) -> String { + String::from("Cached - Iced") + } + + fn update(&mut self, message: Message) { + match message { + Message::InputChanged(input) => { + self.input = input; + } + Message::ToggleOrder => { + self.order = match self.order { + Order::Ascending => Order::Descending, + Order::Descending => Order::Ascending, + } + } + Message::AddOption(option) => { + self.options.insert(option); + self.input.clear(); + } + Message::DeleteOption(option) => { + self.options.remove(&option); + } + } + } + + fn view(&self) -> Element { + let options = lazy((&self.order, self.options.len()), || { + let mut options: Vec<_> = self.options.iter().collect(); + + options.sort_by(|a, b| match self.order { + Order::Ascending => a.to_lowercase().cmp(&b.to_lowercase()), + Order::Descending => b.to_lowercase().cmp(&a.to_lowercase()), + }); + + column( + options + .into_iter() + .map(|option| { + row![ + text(option), + horizontal_space(Length::Fill), + button("Delete") + .on_press(Message::DeleteOption( + option.to_string(), + ),) + .style(theme::Button::Destructive) + ] + .into() + }) + .collect(), + ) + .spacing(10) + }); + + column![ + scrollable(options).height(Length::Fill), + row![ + text_input( + "Add a new option", + &self.input, + Message::InputChanged, + ) + .on_submit(Message::AddOption(self.input.clone())), + button(text(format!("Toggle Order ({})", self.order))) + .on_press(Message::ToggleOrder) + ] + .spacing(10) + ] + .spacing(20) + .padding(20) + .into() + } +} + +#[derive(Debug, Hash)] +enum Order { + Ascending, + Descending, +} + +impl std::fmt::Display for Order { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::Ascending => "Ascending", + Self::Descending => "Descending", + } + ) + } +}