From e812276677ca7c787ffb52a078e7a238227fb522 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 21 Mar 2024 00:34:26 +0100 Subject: Fix layout invalidation for `Responsive` widget --- widget/src/lazy/component.rs | 2 ++ widget/src/lazy/responsive.rs | 45 ++++++++++++++++++++++++++++++++----------- 2 files changed, 36 insertions(+), 11 deletions(-) (limited to 'widget') diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs index a512e0de..7ba71a02 100644 --- a/widget/src/lazy/component.rs +++ b/widget/src/lazy/component.rs @@ -478,12 +478,14 @@ where translation: Vector, ) -> Option> { self.rebuild_element_if_necessary(); + let tree = tree .state .downcast_mut::>>>() .borrow_mut() .take() .unwrap(); + let overlay = Overlay(Some( InnerBuilder { instance: self, diff --git a/widget/src/lazy/responsive.rs b/widget/src/lazy/responsive.rs index 313e1edb..f612102e 100644 --- a/widget/src/lazy/responsive.rs +++ b/widget/src/lazy/responsive.rs @@ -308,10 +308,13 @@ where content_layout_node.as_ref().unwrap(), ); - element - .as_widget_mut() - .overlay(tree, content_layout, renderer, translation) - .map(|overlay| RefCell::new(Nested::new(overlay))) + ( + element + .as_widget_mut() + .overlay(tree, content_layout, renderer, translation) + .map(|overlay| RefCell::new(Nested::new(overlay))), + content_layout_node, + ) }, } .build(); @@ -341,7 +344,10 @@ struct Overlay<'a, 'b, Message, Theme, Renderer> { #[borrows(mut content, mut tree)] #[not_covariant] - overlay: Option>>, + overlay: ( + Option>>, + &'this mut Option, + ), } impl<'a, 'b, Message, Theme, Renderer> @@ -351,7 +357,7 @@ impl<'a, 'b, Message, Theme, Renderer> &self, f: impl FnOnce(&mut Nested<'_, Message, Theme, Renderer>) -> T, ) -> Option { - self.with_overlay(|overlay| { + self.with_overlay(|(overlay, _layout)| { overlay.as_ref().map(|nested| (f)(&mut nested.borrow_mut())) }) } @@ -360,7 +366,7 @@ impl<'a, 'b, Message, Theme, Renderer> &mut self, f: impl FnOnce(&mut Nested<'_, Message, Theme, Renderer>) -> T, ) -> Option { - self.with_overlay_mut(|overlay| { + self.with_overlay_mut(|(overlay, _layout)| { overlay.as_mut().map(|nested| (f)(nested.get_mut())) }) } @@ -412,10 +418,27 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, ) -> event::Status { - self.with_overlay_mut_maybe(|overlay| { - overlay.on_event(event, layout, cursor, renderer, clipboard, shell) - }) - .unwrap_or(event::Status::Ignored) + let mut is_layout_invalid = false; + + let event_status = self + .with_overlay_mut_maybe(|overlay| { + let event_status = overlay.on_event( + event, layout, cursor, renderer, clipboard, shell, + ); + + is_layout_invalid = shell.is_layout_invalid(); + + event_status + }) + .unwrap_or(event::Status::Ignored); + + if is_layout_invalid { + self.with_overlay_mut(|(_overlay, layout)| { + **layout = None; + }); + } + + event_status } fn is_over( -- cgit From 188db4da48954b95a3fe79bcd22689ffc3a661e0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 21 Mar 2024 05:52:48 +0100 Subject: Draft support for dynamic custom renderer injection --- widget/src/image.rs | 10 +++++++--- widget/src/image/viewer.rs | 7 +++---- widget/src/svg.rs | 6 +++--- 3 files changed, 13 insertions(+), 10 deletions(-) (limited to 'widget') diff --git a/widget/src/image.rs b/widget/src/image.rs index ccf1f175..f673c7b3 100644 --- a/widget/src/image.rs +++ b/widget/src/image.rs @@ -93,7 +93,7 @@ where { // The raw w/h of the underlying image let image_size = { - let Size { width, height } = renderer.dimensions(handle); + let Size { width, height } = renderer.measure_image(handle); Size::new(width as f32, height as f32) }; @@ -130,7 +130,7 @@ pub fn draw( Renderer: image::Renderer, Handle: Clone + Hash, { - let Size { width, height } = renderer.dimensions(handle); + let Size { width, height } = renderer.measure_image(handle); let image_size = Size::new(width as f32, height as f32); let bounds = layout.bounds(); @@ -148,7 +148,11 @@ pub fn draw( ..bounds }; - renderer.draw(handle.clone(), filter_method, drawing_bounds + offset); + renderer.draw_image( + handle.clone(), + filter_method, + drawing_bounds + offset, + ); }; if adjusted_fit.width > bounds.width || adjusted_fit.height > bounds.height diff --git a/widget/src/image/viewer.rs b/widget/src/image/viewer.rs index 2e3fd713..5f7bb345 100644 --- a/widget/src/image/viewer.rs +++ b/widget/src/image/viewer.rs @@ -117,7 +117,7 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - let Size { width, height } = renderer.dimensions(&self.handle); + let Size { width, height } = renderer.measure_image(&self.handle); let mut size = limits.resolve( self.width, @@ -335,8 +335,7 @@ where renderer.with_layer(bounds, |renderer| { renderer.with_translation(translation, |renderer| { - image::Renderer::draw( - renderer, + renderer.draw_image( self.handle.clone(), self.filter_method, Rectangle { @@ -421,7 +420,7 @@ pub fn image_size( where Renderer: image::Renderer, { - let Size { width, height } = renderer.dimensions(handle); + let Size { width, height } = renderer.measure_image(handle); let (width, height) = { let dimensions = (width as f32, height as f32); diff --git a/widget/src/svg.rs b/widget/src/svg.rs index 1ac07ade..53c45bcb 100644 --- a/widget/src/svg.rs +++ b/widget/src/svg.rs @@ -108,7 +108,7 @@ where limits: &layout::Limits, ) -> layout::Node { // The raw w/h of the underlying image - let Size { width, height } = renderer.dimensions(&self.handle); + let Size { width, height } = renderer.measure_svg(&self.handle); let image_size = Size::new(width as f32, height as f32); // The size to be available to the widget prior to `Shrink`ing @@ -142,7 +142,7 @@ where cursor: mouse::Cursor, _viewport: &Rectangle, ) { - let Size { width, height } = renderer.dimensions(&self.handle); + let Size { width, height } = renderer.measure_svg(&self.handle); let image_size = Size::new(width as f32, height as f32); let bounds = layout.bounds(); @@ -169,7 +169,7 @@ where let appearance = (self.style)(theme, status); - renderer.draw( + renderer.draw_svg( self.handle.clone(), appearance.color, drawing_bounds + offset, -- cgit From 3645d34d6a1ba1247238e830e9eefd52d9e5b986 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 21 Mar 2024 22:27:17 +0100 Subject: Implement composable, type-safe renderer fallback --- widget/src/canvas.rs | 19 +++++++++++-------- widget/src/canvas/program.rs | 19 ++++++------------- widget/src/qr_code.rs | 9 +++++---- 3 files changed, 22 insertions(+), 25 deletions(-) (limited to 'widget') diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs index 0eda0191..fcd91d17 100644 --- a/widget/src/canvas.rs +++ b/widget/src/canvas.rs @@ -7,7 +7,6 @@ pub use event::Event; pub use program::Program; pub use crate::graphics::geometry::*; -pub use crate::renderer::geometry::*; use crate::core; use crate::core::layout::{self, Layout}; @@ -21,13 +20,19 @@ use crate::graphics::geometry; use std::marker::PhantomData; +/// A simple cache that stores generated [`Geometry`] to avoid recomputation. +/// +/// A [`Cache`] will not redraw its geometry unless the dimensions of its layer +/// change or it is explicitly cleared. +pub type Cache = geometry::Cache; + /// A widget capable of drawing 2D graphics. /// /// ## Drawing a simple circle /// If you want to get a quick overview, here's how we can draw a simple circle: /// /// ```no_run -/// # use iced_widget::canvas::{self, Canvas, Fill, Frame, Geometry, Path, Program}; +/// # use iced_widget::canvas::{self, frame, Canvas, Fill, Frame, Path, Program}; /// # use iced_widget::core::{Color, Rectangle}; /// # use iced_widget::core::mouse; /// # use iced_widget::{Renderer, Theme}; @@ -42,9 +47,9 @@ use std::marker::PhantomData; /// impl Program<()> for Circle { /// type State = (); /// -/// fn draw(&self, _state: &(), renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor) -> Vec{ +/// fn draw(&self, _state: &(), renderer: &mut Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor) { /// // We prepare a new `Frame` -/// let mut frame = Frame::new(renderer, bounds.size()); +/// let mut frame = frame(renderer, bounds.size()); /// /// // We create a `Path` representing a simple circle /// let circle = Path::circle(frame.center(), self.radius); @@ -53,7 +58,7 @@ use std::marker::PhantomData; /// frame.fill(&circle, Color::BLACK); /// /// // Finally, we produce the geometry -/// vec![frame.into_geometry()] +/// renderer.draw_geometry([frame]); /// } /// } /// @@ -210,9 +215,7 @@ where renderer.with_transformation( Transformation::translate(bounds.x, bounds.y), |renderer| { - renderer.draw( - self.program.draw(state, renderer, theme, bounds, cursor), - ); + self.program.draw(state, renderer, theme, bounds, cursor); }, ); } diff --git a/widget/src/canvas/program.rs b/widget/src/canvas/program.rs index 0bff4bda..307686de 100644 --- a/widget/src/canvas/program.rs +++ b/widget/src/canvas/program.rs @@ -37,22 +37,15 @@ where (event::Status::Ignored, None) } - /// Draws the state of the [`Program`], producing a bunch of [`Geometry`]. - /// - /// [`Geometry`] can be easily generated with a [`Frame`] or stored in a - /// [`Cache`]. - /// - /// [`Geometry`]: crate::canvas::Geometry - /// [`Frame`]: crate::canvas::Frame - /// [`Cache`]: crate::canvas::Cache + /// Draws the state of the [`Program`] with the given [`Renderer`]. fn draw( &self, state: &Self::State, - renderer: &Renderer, + renderer: &mut Renderer, theme: &Theme, bounds: Rectangle, cursor: mouse::Cursor, - ) -> Vec; + ); /// Returns the current mouse interaction of the [`Program`]. /// @@ -90,12 +83,12 @@ where fn draw( &self, state: &Self::State, - renderer: &Renderer, + renderer: &mut Renderer, theme: &Theme, bounds: Rectangle, cursor: mouse::Cursor, - ) -> Vec { - T::draw(self, state, renderer, theme, bounds, cursor) + ) { + T::draw(self, state, renderer, theme, bounds, cursor); } fn mouse_interaction( diff --git a/widget/src/qr_code.rs b/widget/src/qr_code.rs index 90c0c970..bc46aaaa 100644 --- a/widget/src/qr_code.rs +++ b/widget/src/qr_code.rs @@ -8,7 +8,6 @@ use crate::core::{ Color, Element, Layout, Length, Point, Rectangle, Size, Theme, Vector, Widget, }; -use crate::graphics::geometry::Renderer as _; use crate::Renderer; use std::cell::RefCell; @@ -92,6 +91,8 @@ impl<'a, Message, Theme> Widget _cursor: mouse::Cursor, _viewport: &Rectangle, ) { + use canvas::Frame; + let state = tree.state.downcast_ref::(); let bounds = layout.bounds(); @@ -142,7 +143,7 @@ impl<'a, Message, Theme> Widget renderer.with_translation( bounds.position() - Point::ORIGIN, |renderer| { - renderer.draw(vec![geometry]); + renderer.draw_geometry(vec![geometry]); }, ); } @@ -161,11 +162,11 @@ where /// The data of a [`QRCode`]. /// /// It stores the contents that will be displayed. -#[derive(Debug)] +#[allow(missing_debug_implementations)] pub struct Data { contents: Vec, width: usize, - cache: canvas::Cache, + cache: canvas::Cache, } impl Data { -- cgit From b972ebca8f8c23d2df1b45bb26038789766a5a65 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 21 Mar 2024 23:30:36 +0100 Subject: Restore `canvas::Program` API --- widget/src/canvas.rs | 17 +++++++++++++---- widget/src/canvas/program.rs | 11 ++++++----- 2 files changed, 19 insertions(+), 9 deletions(-) (limited to 'widget') diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs index fcd91d17..81067491 100644 --- a/widget/src/canvas.rs +++ b/widget/src/canvas.rs @@ -26,13 +26,17 @@ use std::marker::PhantomData; /// change or it is explicitly cleared. pub type Cache = geometry::Cache; +/// The geometry supported by a renderer. +pub type Geometry = + ::Geometry; + /// A widget capable of drawing 2D graphics. /// /// ## Drawing a simple circle /// If you want to get a quick overview, here's how we can draw a simple circle: /// /// ```no_run -/// # use iced_widget::canvas::{self, frame, Canvas, Fill, Frame, Path, Program}; +/// # use iced_widget::canvas::{self, frame, Canvas, Fill, Frame, Geometry, Path, Program}; /// # use iced_widget::core::{Color, Rectangle}; /// # use iced_widget::core::mouse; /// # use iced_widget::{Renderer, Theme}; @@ -47,7 +51,7 @@ pub type Cache = geometry::Cache; /// impl Program<()> for Circle { /// type State = (); /// -/// fn draw(&self, _state: &(), renderer: &mut Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor) { +/// fn draw(&self, _state: &(), renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor) -> Vec { /// // We prepare a new `Frame` /// let mut frame = frame(renderer, bounds.size()); /// @@ -58,7 +62,7 @@ pub type Cache = geometry::Cache; /// frame.fill(&circle, Color::BLACK); /// /// // Finally, we produce the geometry -/// renderer.draw_geometry([frame]); +/// vec![frame.into()] /// } /// } /// @@ -215,7 +219,12 @@ where renderer.with_transformation( Transformation::translate(bounds.x, bounds.y), |renderer| { - self.program.draw(state, renderer, theme, bounds, cursor); + let layers = + self.program.draw(state, renderer, theme, bounds, cursor); + + for layer in layers { + renderer.draw_geometry(layer); + } }, ); } diff --git a/widget/src/canvas/program.rs b/widget/src/canvas/program.rs index 307686de..3ba31474 100644 --- a/widget/src/canvas/program.rs +++ b/widget/src/canvas/program.rs @@ -1,5 +1,6 @@ use crate::canvas::event::{self, Event}; use crate::canvas::mouse; +use crate::canvas::Geometry; use crate::core::Rectangle; use crate::graphics::geometry; @@ -41,11 +42,11 @@ where fn draw( &self, state: &Self::State, - renderer: &mut Renderer, + renderer: &Renderer, theme: &Theme, bounds: Rectangle, cursor: mouse::Cursor, - ); + ) -> Vec>; /// Returns the current mouse interaction of the [`Program`]. /// @@ -83,12 +84,12 @@ where fn draw( &self, state: &Self::State, - renderer: &mut Renderer, + renderer: &Renderer, theme: &Theme, bounds: Rectangle, cursor: mouse::Cursor, - ) { - T::draw(self, state, renderer, theme, bounds, cursor); + ) -> Vec> { + T::draw(self, state, renderer, theme, bounds, cursor) } fn mouse_interaction( -- cgit From 53a183fe0d6aed460fbb8155ac9541757277aab3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 22 Mar 2024 01:35:14 +0100 Subject: Restore `canvas::Frame` API --- widget/src/canvas.rs | 14 ++++++++++---- widget/src/qr_code.rs | 2 -- 2 files changed, 10 insertions(+), 6 deletions(-) (limited to 'widget') diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs index 81067491..7a21895a 100644 --- a/widget/src/canvas.rs +++ b/widget/src/canvas.rs @@ -6,7 +6,10 @@ mod program; pub use event::Event; pub use program::Program; -pub use crate::graphics::geometry::*; +pub use crate::graphics::geometry::{ + fill, gradient, path, stroke, Fill, Gradient, LineCap, LineDash, LineJoin, + Path, Stroke, Style, Text, +}; use crate::core; use crate::core::layout::{self, Layout}; @@ -30,13 +33,16 @@ pub type Cache = geometry::Cache; pub type Geometry = ::Geometry; +/// The frame supported by a renderer. +pub type Frame = geometry::Frame; + /// A widget capable of drawing 2D graphics. /// /// ## Drawing a simple circle /// If you want to get a quick overview, here's how we can draw a simple circle: /// /// ```no_run -/// # use iced_widget::canvas::{self, frame, Canvas, Fill, Frame, Geometry, Path, Program}; +/// # use iced_widget::canvas::{self, Canvas, Fill, Frame, Geometry, Path, Program}; /// # use iced_widget::core::{Color, Rectangle}; /// # use iced_widget::core::mouse; /// # use iced_widget::{Renderer, Theme}; @@ -53,7 +59,7 @@ pub type Geometry = /// /// fn draw(&self, _state: &(), renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor) -> Vec { /// // We prepare a new `Frame` -/// let mut frame = frame(renderer, bounds.size()); +/// let mut frame = Frame::new(renderer, bounds.size()); /// /// // We create a `Path` representing a simple circle /// let circle = Path::circle(frame.center(), self.radius); @@ -62,7 +68,7 @@ pub type Geometry = /// frame.fill(&circle, Color::BLACK); /// /// // Finally, we produce the geometry -/// vec![frame.into()] +/// vec![frame.into_geometry()] /// } /// } /// diff --git a/widget/src/qr_code.rs b/widget/src/qr_code.rs index bc46aaaa..84898dc0 100644 --- a/widget/src/qr_code.rs +++ b/widget/src/qr_code.rs @@ -91,8 +91,6 @@ impl<'a, Message, Theme> Widget _cursor: mouse::Cursor, _viewport: &Rectangle, ) { - use canvas::Frame; - let state = tree.state.downcast_ref::(); let bounds = layout.bounds(); -- cgit From 85800c99ab285efd244c0addfdcf3c732a98de1d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 22 Mar 2024 01:53:48 +0100 Subject: Fix broken links in documentation --- widget/src/canvas/program.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'widget') diff --git a/widget/src/canvas/program.rs b/widget/src/canvas/program.rs index 3ba31474..a7ded0f4 100644 --- a/widget/src/canvas/program.rs +++ b/widget/src/canvas/program.rs @@ -38,7 +38,14 @@ where (event::Status::Ignored, None) } - /// Draws the state of the [`Program`] with the given [`Renderer`]. + /// Draws the state of the [`Program`], producing a bunch of [`Geometry`]. + /// + /// [`Geometry`] can be easily generated with a [`Frame`] or stored in a + /// [`Cache`]. + /// + /// [`Geometry`]: crate::canvas::Geometry + /// [`Frame`]: crate::canvas::Frame + /// [`Cache`]: crate::canvas::Cache fn draw( &self, state: &Self::State, -- cgit From 1f13a91361258a1607c71f4840a26a6437f88612 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 22 Mar 2024 05:27:31 +0100 Subject: Make `iced_tiny_skia` optional with a `tiny-skia` feature --- widget/src/qr_code.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'widget') diff --git a/widget/src/qr_code.rs b/widget/src/qr_code.rs index 84898dc0..601e5808 100644 --- a/widget/src/qr_code.rs +++ b/widget/src/qr_code.rs @@ -141,7 +141,9 @@ impl<'a, Message, Theme> Widget renderer.with_translation( bounds.position() - Point::ORIGIN, |renderer| { - renderer.draw_geometry(vec![geometry]); + use crate::graphics::geometry::Renderer as _; + + renderer.draw_geometry(geometry); }, ); } -- cgit From 999ad2d288a9354f045bb2e1b838014b3d302779 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 23 Mar 2024 19:23:08 +0100 Subject: Try catalog theming approach with `Button` --- widget/src/button.rs | 102 +++++++++++++++++++++++++------------------------- widget/src/helpers.rs | 2 +- 2 files changed, 52 insertions(+), 52 deletions(-) (limited to 'widget') diff --git a/widget/src/button.rs b/widget/src/button.rs index 5790f811..8754622f 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -1,4 +1,5 @@ //! Allow your users to perform actions by pressing a button. +use crate::core::closure; use crate::core::event::{self, Event}; use crate::core::layout; use crate::core::mouse; @@ -49,6 +50,7 @@ use crate::core::{ pub struct Button<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer> where Renderer: crate::core::Renderer, + Theme: Catalog, { content: Element<'a, Message, Theme, Renderer>, on_press: Option, @@ -56,20 +58,18 @@ where height: Length, padding: Padding, clip: bool, - style: Style<'a, Theme>, + style: Theme::Item<'a>, } impl<'a, Message, Theme, Renderer> Button<'a, Message, Theme, Renderer> where Renderer: crate::core::Renderer, + Theme: Catalog, { /// Creates a new [`Button`] with the given content. pub fn new( content: impl Into>, - ) -> Self - where - Theme: DefaultStyle + 'a, - { + ) -> Self { let content = content.into(); let size = content.as_widget().size_hint(); @@ -80,7 +80,7 @@ where height: size.height.fluid(), padding: DEFAULT_PADDING, clip: false, - style: Box::new(Theme::default_style), + style: Theme::default(), } } @@ -120,11 +120,8 @@ where } /// Sets the style variant of this [`Button`]. - pub fn style( - mut self, - style: impl Fn(&Theme, Status) -> Appearance + 'a, - ) -> Self { - self.style = Box::new(style); + pub fn style(mut self, style: impl Into>) -> Self { + self.style = style.into(); self } @@ -146,6 +143,7 @@ impl<'a, Message, Theme, Renderer> Widget where Message: 'a + Clone, Renderer: 'a + crate::core::Renderer, + Theme: Catalog, { fn tag(&self) -> tree::Tag { tree::Tag::of::() @@ -304,7 +302,7 @@ where Status::Active }; - let styling = (self.style)(theme, status); + let styling = theme.style(&self.style, status); if styling.background.is_some() || styling.border.width > 0.0 @@ -378,7 +376,7 @@ impl<'a, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> where Message: Clone + 'a, - Theme: 'a, + Theme: Catalog + 'a, Renderer: crate::core::Renderer + 'a, { fn from(button: Button<'a, Message, Theme, Renderer>) -> Self { @@ -409,7 +407,7 @@ pub enum Status { /// The appearance of a button. #[derive(Debug, Clone, Copy, PartialEq)] -pub struct Appearance { +pub struct Style { /// The [`Background`] of the button. pub background: Option, /// The text [`Color`] of the button. @@ -420,7 +418,7 @@ pub struct Appearance { pub shadow: Shadow, } -impl Appearance { +impl Style { /// Updates the [`Appearance`] with the given [`Background`]. pub fn with_background(self, background: impl Into) -> Self { Self { @@ -430,7 +428,7 @@ impl Appearance { } } -impl std::default::Default for Appearance { +impl std::default::Default for Style { fn default() -> Self { Self { background: None, @@ -441,41 +439,43 @@ impl std::default::Default for Appearance { } } -/// The style of a [`Button`]. -pub type Style<'a, Theme> = Box Appearance + 'a>; +/// The theme catalog of a [`Button`]. +pub trait Catalog: Sized { + /// The item type of this [`Catalog`]. + type Item<'a>; -/// The default style of a [`Button`]. -pub trait DefaultStyle { - /// Returns the default style of a [`Button`]. - fn default_style(&self, status: Status) -> Appearance; -} + /// The default item produced by this [`Catalog`]. + fn default<'a>() -> Self::Item<'a>; -impl DefaultStyle for Theme { - fn default_style(&self, status: Status) -> Appearance { - primary(self, status) - } + /// The [`Style`] of an item with the given status. + fn style(&self, item: &Self::Item<'_>, status: Status) -> Style; } -impl DefaultStyle for Appearance { - fn default_style(&self, _status: Status) -> Appearance { - *self +/// The item of a button [`Catalog`] for the built-in [`Theme`]. +/// +/// This is just a boxed closure: `Fn(&Theme, Status) -> Style`. +pub type Item<'a, Theme> = closure::Binary<'a, Theme, Status, Style>; + +impl Catalog for Theme { + type Item<'a> = Item<'a, Self>; + + fn default<'a>() -> Self::Item<'a> { + closure::Binary::from(primary) } -} -impl DefaultStyle for Color { - fn default_style(&self, _status: Status) -> Appearance { - Appearance::default().with_background(*self) + fn style(&self, item: &Self::Item<'_>, status: Status) -> Style { + (item.0)(self, status) } } /// A primary button; denoting a main action. -pub fn primary(theme: &Theme, status: Status) -> Appearance { +pub fn primary(theme: &Theme, status: Status) -> Style { let palette = theme.extended_palette(); let base = styled(palette.primary.strong); match status { Status::Active | Status::Pressed => base, - Status::Hovered => Appearance { + Status::Hovered => Style { background: Some(Background::Color(palette.primary.base.color)), ..base }, @@ -484,13 +484,13 @@ pub fn primary(theme: &Theme, status: Status) -> Appearance { } /// A secondary button; denoting a complementary action. -pub fn secondary(theme: &Theme, status: Status) -> Appearance { +pub fn secondary(theme: &Theme, status: Status) -> Style { let palette = theme.extended_palette(); let base = styled(palette.secondary.base); match status { Status::Active | Status::Pressed => base, - Status::Hovered => Appearance { + Status::Hovered => Style { background: Some(Background::Color(palette.secondary.strong.color)), ..base }, @@ -499,13 +499,13 @@ pub fn secondary(theme: &Theme, status: Status) -> Appearance { } /// A success button; denoting a good outcome. -pub fn success(theme: &Theme, status: Status) -> Appearance { +pub fn success(theme: &Theme, status: Status) -> Style { let palette = theme.extended_palette(); let base = styled(palette.success.base); match status { Status::Active | Status::Pressed => base, - Status::Hovered => Appearance { + Status::Hovered => Style { background: Some(Background::Color(palette.success.strong.color)), ..base }, @@ -514,13 +514,13 @@ pub fn success(theme: &Theme, status: Status) -> Appearance { } /// A danger button; denoting a destructive action. -pub fn danger(theme: &Theme, status: Status) -> Appearance { +pub fn danger(theme: &Theme, status: Status) -> Style { let palette = theme.extended_palette(); let base = styled(palette.danger.base); match status { Status::Active | Status::Pressed => base, - Status::Hovered => Appearance { + Status::Hovered => Style { background: Some(Background::Color(palette.danger.strong.color)), ..base }, @@ -529,17 +529,17 @@ pub fn danger(theme: &Theme, status: Status) -> Appearance { } /// A text button; useful for links. -pub fn text(theme: &Theme, status: Status) -> Appearance { +pub fn text(theme: &Theme, status: Status) -> Style { let palette = theme.extended_palette(); - let base = Appearance { + let base = Style { text_color: palette.background.base.text, - ..Appearance::default() + ..Style::default() }; match status { Status::Active | Status::Pressed => base, - Status::Hovered => Appearance { + Status::Hovered => Style { text_color: palette.background.base.text.scale_alpha(0.8), ..base }, @@ -547,17 +547,17 @@ pub fn text(theme: &Theme, status: Status) -> Appearance { } } -fn styled(pair: palette::Pair) -> Appearance { - Appearance { +fn styled(pair: palette::Pair) -> Style { + Style { background: Some(Background::Color(pair.color)), text_color: pair.text, border: Border::rounded(2), - ..Appearance::default() + ..Style::default() } } -fn disabled(appearance: Appearance) -> Appearance { - Appearance { +fn disabled(appearance: Style) -> Style { + Style { background: appearance .background .map(|background| background.scale_alpha(0.5)), diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 4863e550..e60096d1 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -117,7 +117,7 @@ pub fn button<'a, Message, Theme, Renderer>( content: impl Into>, ) -> Button<'a, Message, Theme, Renderer> where - Theme: button::DefaultStyle + 'a, + Theme: button::Catalog + 'a, Renderer: core::Renderer, { Button::new(content) -- cgit From e657dc2ecd2ffa72c0abd87f9a59dcc1acbc7083 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 24 Mar 2024 02:08:20 +0100 Subject: Fine-tune `Catalog` approach for `button`, `checkbox`, and `svg` --- widget/Cargo.toml | 1 + widget/src/button.rs | 85 ++++++++++++++++++++++++------------------- widget/src/checkbox.rs | 99 +++++++++++++++++++++++++++++--------------------- widget/src/helpers.rs | 4 +- widget/src/svg.rs | 89 ++++++++++++++++++++++++++++----------------- 5 files changed, 165 insertions(+), 113 deletions(-) (limited to 'widget') diff --git a/widget/Cargo.toml b/widget/Cargo.toml index 3c9ffddb..a45f47ef 100644 --- a/widget/Cargo.toml +++ b/widget/Cargo.toml @@ -21,6 +21,7 @@ svg = ["iced_renderer/svg"] canvas = ["iced_renderer/geometry"] qr_code = ["canvas", "qrcode"] wgpu = ["iced_renderer/wgpu"] +advanced = [] [dependencies] iced_renderer.workspace = true diff --git a/widget/src/button.rs b/widget/src/button.rs index 8754622f..80a18e82 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -1,5 +1,4 @@ //! Allow your users to perform actions by pressing a button. -use crate::core::closure; use crate::core::event::{self, Event}; use crate::core::layout; use crate::core::mouse; @@ -58,7 +57,7 @@ where height: Length, padding: Padding, clip: bool, - style: Theme::Item<'a>, + class: Theme::Class<'a>, } impl<'a, Message, Theme, Renderer> Button<'a, Message, Theme, Renderer> @@ -80,7 +79,7 @@ where height: size.height.fluid(), padding: DEFAULT_PADDING, clip: false, - style: Theme::default(), + class: Theme::default(), } } @@ -119,18 +118,30 @@ where self } - /// Sets the style variant of this [`Button`]. - pub fn style(mut self, style: impl Into>) -> Self { - self.style = style.into(); - self - } - /// Sets whether the contents of the [`Button`] should be clipped on /// overflow. pub fn clip(mut self, clip: bool) -> Self { self.clip = clip; self } + + /// Sets the style of this [`Button`]. + #[must_use] + pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self + where + Theme::Class<'a>: From>, + { + self.class = (Box::new(style) as StyleFn<'a, Theme>).into(); + self + } + + /// Sets the style class of this [`Button`]. + #[cfg(feature = "advanced")] + #[must_use] + pub fn class(mut self, class: impl Into>) -> Self { + self.class = class.into(); + self + } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] @@ -302,19 +313,19 @@ where Status::Active }; - let styling = theme.style(&self.style, status); + let style = theme.style(&self.class, status); - if styling.background.is_some() - || styling.border.width > 0.0 - || styling.shadow.color.a > 0.0 + if style.background.is_some() + || style.border.width > 0.0 + || style.shadow.color.a > 0.0 { renderer.fill_quad( renderer::Quad { bounds, - border: styling.border, - shadow: styling.shadow, + border: style.border, + shadow: style.shadow, }, - styling + style .background .unwrap_or(Background::Color(Color::TRANSPARENT)), ); @@ -331,7 +342,7 @@ where renderer, theme, &renderer::Style { - text_color: styling.text_color, + text_color: style.text_color, }, content_layout, cursor, @@ -405,7 +416,7 @@ pub enum Status { Disabled, } -/// The appearance of a button. +/// The style of a button. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Style { /// The [`Background`] of the button. @@ -419,7 +430,7 @@ pub struct Style { } impl Style { - /// Updates the [`Appearance`] with the given [`Background`]. + /// Updates the [`Style`] with the given [`Background`]. pub fn with_background(self, background: impl Into) -> Self { Self { background: Some(background.into()), @@ -428,7 +439,7 @@ impl Style { } } -impl std::default::Default for Style { +impl Default for Style { fn default() -> Self { Self { background: None, @@ -441,30 +452,30 @@ impl std::default::Default for Style { /// The theme catalog of a [`Button`]. pub trait Catalog: Sized { - /// The item type of this [`Catalog`]. - type Item<'a>; + /// The item class of this [`Catalog`]. + type Class<'a>; - /// The default item produced by this [`Catalog`]. - fn default<'a>() -> Self::Item<'a>; + /// The default class produced by this [`Catalog`]. + fn default<'a>() -> Self::Class<'a>; - /// The [`Style`] of an item with the given status. - fn style(&self, item: &Self::Item<'_>, status: Status) -> Style; + /// The [`Style`] of a class with the given status. + fn style(&self, item: &Self::Class<'_>, status: Status) -> Style; } -/// The item of a button [`Catalog`] for the built-in [`Theme`]. +/// A styling function for a [`Button`]. /// /// This is just a boxed closure: `Fn(&Theme, Status) -> Style`. -pub type Item<'a, Theme> = closure::Binary<'a, Theme, Status, Style>; +pub type StyleFn<'a, Theme> = Box Style + 'a>; impl Catalog for Theme { - type Item<'a> = Item<'a, Self>; + type Class<'a> = StyleFn<'a, Self>; - fn default<'a>() -> Self::Item<'a> { - closure::Binary::from(primary) + fn default<'a>() -> Self::Class<'a> { + Box::new(primary) } - fn style(&self, item: &Self::Item<'_>, status: Status) -> Style { - (item.0)(self, status) + fn style(&self, class: &Self::Class<'_>, status: Status) -> Style { + class(self, status) } } @@ -556,12 +567,12 @@ fn styled(pair: palette::Pair) -> Style { } } -fn disabled(appearance: Style) -> Style { +fn disabled(style: Style) -> Style { Style { - background: appearance + background: style .background .map(|background| background.scale_alpha(0.5)), - text_color: appearance.text_color.scale_alpha(0.5), - ..appearance + text_color: style.text_color.scale_alpha(0.5), + ..style } } diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index 15fb8f58..a90914b8 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -39,6 +39,7 @@ pub struct Checkbox< Renderer = crate::Renderer, > where Renderer: text::Renderer, + Theme: Catalog, { is_checked: bool, on_toggle: Option Message + 'a>>, @@ -51,12 +52,13 @@ pub struct Checkbox< text_shaping: text::Shaping, font: Option, icon: Icon, - style: Style<'a, Theme>, + class: Theme::Class<'a>, } impl<'a, Message, Theme, Renderer> Checkbox<'a, Message, Theme, Renderer> where Renderer: text::Renderer, + Theme: Catalog, { /// The default size of a [`Checkbox`]. const DEFAULT_SIZE: f32 = 16.0; @@ -69,10 +71,7 @@ where /// It expects: /// * the label of the [`Checkbox`] /// * a boolean describing whether the [`Checkbox`] is checked or not - pub fn new(label: impl Into, is_checked: bool) -> Self - where - Theme: DefaultStyle + 'a, - { + pub fn new(label: impl Into, is_checked: bool) -> Self { Checkbox { is_checked, on_toggle: None, @@ -91,7 +90,7 @@ where line_height: text::LineHeight::default(), shaping: text::Shaping::Basic, }, - style: Box::new(Theme::default_style), + class: Theme::default(), } } @@ -173,12 +172,21 @@ where self } - /// Sets the style of the [`Checkbox`]. - pub fn style( - mut self, - style: impl Fn(&Theme, Status) -> Appearance + 'a, - ) -> Self { - self.style = Box::new(style); + /// Sets the style of this [`Checkbox`]. + #[must_use] + pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self + where + Theme::Class<'a>: From>, + { + self.class = (Box::new(style) as StyleFn<'a, Theme>).into(); + self + } + + /// Sets the style class of this [`Checkbox`]. + #[cfg(feature = "advanced")] + #[must_use] + pub fn class(mut self, class: impl Into>) -> Self { + self.class = class.into(); self } } @@ -187,6 +195,7 @@ impl<'a, Message, Theme, Renderer> Widget for Checkbox<'a, Message, Theme, Renderer> where Renderer: text::Renderer, + Theme: Catalog, { fn tag(&self) -> tree::Tag { tree::Tag::of::>() @@ -285,7 +294,7 @@ where tree: &Tree, renderer: &mut Renderer, theme: &Theme, - style: &renderer::Style, + defaults: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, @@ -304,7 +313,7 @@ where Status::Active { is_checked } }; - let appearance = (self.style)(theme, status); + let style = theme.style(&self.class, status); { let layout = children.next().unwrap(); @@ -313,10 +322,10 @@ where renderer.fill_quad( renderer::Quad { bounds, - border: appearance.border, + border: style.border, ..renderer::Quad::default() }, - appearance.background, + style.background, ); let Icon { @@ -341,7 +350,7 @@ where shaping: *shaping, }, bounds.center(), - appearance.icon_color, + style.icon_color, *viewport, ); } @@ -352,11 +361,11 @@ where crate::text::draw( renderer, - style, + defaults, label_layout, tree.state.downcast_ref(), crate::text::Appearance { - color: appearance.text_color, + color: style.text_color, }, viewport, ); @@ -368,7 +377,7 @@ impl<'a, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> where Message: 'a, - Theme: 'a, + Theme: 'a + Catalog, Renderer: 'a + text::Renderer, { fn from( @@ -413,9 +422,9 @@ pub enum Status { }, } -/// The appearance of a checkbox. +/// The style of a checkbox. #[derive(Debug, Clone, Copy)] -pub struct Appearance { +pub struct Style { /// The [`Background`] of the checkbox. pub background: Background, /// The icon [`Color`] of the checkbox. @@ -426,29 +435,37 @@ pub struct Appearance { pub text_color: Option, } -/// The style of a [`Checkbox`]. -pub type Style<'a, Theme> = Box Appearance + 'a>; +/// The theme catalog of a [`Checkbox`]. +pub trait Catalog: Sized { + /// The item class of this [`Catalog`]. + type Class<'a>; -/// The default style of a [`Checkbox`]. -pub trait DefaultStyle { - /// Returns the default style of a [`Checkbox`]. - fn default_style(&self, status: Status) -> Appearance; + /// The default class produced by this [`Catalog`]. + fn default<'a>() -> Self::Class<'a>; + + /// The [`Style`] of a class with the given status. + fn style(&self, item: &Self::Class<'_>, status: Status) -> Style; } -impl DefaultStyle for Theme { - fn default_style(&self, status: Status) -> Appearance { - primary(self, status) +/// A styling function for a [`Checkbox`]. +/// +/// This is just a boxed closure: `Fn(&Theme, Status) -> Style`. +pub type StyleFn<'a, Theme> = Box Style + 'a>; + +impl Catalog for Theme { + type Class<'a> = StyleFn<'a, Self>; + + fn default<'a>() -> Self::Class<'a> { + Box::new(primary) } -} -impl DefaultStyle for Appearance { - fn default_style(&self, _status: Status) -> Appearance { - *self + fn style(&self, class: &Self::Class<'_>, status: Status) -> Style { + class(self, status) } } /// A primary checkbox; denoting a main toggle. -pub fn primary(theme: &Theme, status: Status) -> Appearance { +pub fn primary(theme: &Theme, status: Status) -> Style { let palette = theme.extended_palette(); match status { @@ -474,7 +491,7 @@ pub fn primary(theme: &Theme, status: Status) -> Appearance { } /// A secondary checkbox; denoting a complementary toggle. -pub fn secondary(theme: &Theme, status: Status) -> Appearance { +pub fn secondary(theme: &Theme, status: Status) -> Style { let palette = theme.extended_palette(); match status { @@ -500,7 +517,7 @@ pub fn secondary(theme: &Theme, status: Status) -> Appearance { } /// A success checkbox; denoting a positive toggle. -pub fn success(theme: &Theme, status: Status) -> Appearance { +pub fn success(theme: &Theme, status: Status) -> Style { let palette = theme.extended_palette(); match status { @@ -526,7 +543,7 @@ pub fn success(theme: &Theme, status: Status) -> Appearance { } /// A danger checkbox; denoting a negaive toggle. -pub fn danger(theme: &Theme, status: Status) -> Appearance { +pub fn danger(theme: &Theme, status: Status) -> Style { let palette = theme.extended_palette(); match status { @@ -556,8 +573,8 @@ fn styled( base: palette::Pair, accent: palette::Pair, is_checked: bool, -) -> Appearance { - Appearance { +) -> Style { + Style { background: Background::Color(if is_checked { accent.color } else { diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index e60096d1..4c4d1012 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -161,7 +161,7 @@ pub fn checkbox<'a, Message, Theme, Renderer>( is_checked: bool, ) -> Checkbox<'a, Message, Theme, Renderer> where - Theme: checkbox::DefaultStyle + 'a, + Theme: checkbox::Catalog + 'a, Renderer: core::text::Renderer, { Checkbox::new(label, is_checked) @@ -367,7 +367,7 @@ pub fn svg<'a, Theme>( handle: impl Into, ) -> crate::Svg<'a, Theme> where - Theme: crate::svg::DefaultStyle + 'a, + Theme: crate::svg::Catalog, { crate::Svg::new(handle) } diff --git a/widget/src/svg.rs b/widget/src/svg.rs index 1ac07ade..d2fd0aee 100644 --- a/widget/src/svg.rs +++ b/widget/src/svg.rs @@ -20,36 +20,36 @@ pub use crate::core::svg::Handle; /// [`Svg`] images can have a considerable rendering cost when resized, /// specially when they are complex. #[allow(missing_debug_implementations)] -pub struct Svg<'a, Theme = crate::Theme> { +pub struct Svg<'a, Theme = crate::Theme> +where + Theme: Catalog, +{ handle: Handle, width: Length, height: Length, content_fit: ContentFit, - style: Style<'a, Theme>, + class: Theme::Class<'a>, } -impl<'a, Theme> Svg<'a, Theme> { +impl<'a, Theme> Svg<'a, Theme> +where + Theme: Catalog, +{ /// Creates a new [`Svg`] from the given [`Handle`]. - pub fn new(handle: impl Into) -> Self - where - Theme: DefaultStyle + 'a, - { + pub fn new(handle: impl Into) -> Self { Svg { handle: handle.into(), width: Length::Fill, height: Length::Shrink, content_fit: ContentFit::Contain, - style: Box::new(Theme::default_style), + class: Theme::default(), } } /// Creates a new [`Svg`] that will display the contents of the file at the /// provided path. #[must_use] - pub fn from_path(path: impl Into) -> Self - where - Theme: DefaultStyle + 'a, - { + pub fn from_path(path: impl Into) -> Self { Self::new(Handle::from_path(path)) } @@ -78,13 +78,21 @@ impl<'a, Theme> Svg<'a, Theme> { } } - /// Sets the style variant of this [`Svg`]. + /// Sets the style of this [`Svg`]. + #[must_use] + pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self + where + Theme::Class<'a>: From>, + { + self.class = (Box::new(style) as StyleFn<'a, Theme>).into(); + self + } + + /// Sets the style class of this [`Svg`]. + #[cfg(feature = "advanced")] #[must_use] - pub fn style( - mut self, - style: impl Fn(&Theme, Status) -> Appearance + 'a, - ) -> Self { - self.style = Box::new(style); + pub fn class(mut self, class: impl Into>) -> Self { + self.class = class.into(); self } } @@ -93,6 +101,7 @@ impl<'a, Message, Theme, Renderer> Widget for Svg<'a, Theme> where Renderer: svg::Renderer, + Theme: Catalog, { fn size(&self) -> Size { Size { @@ -167,7 +176,7 @@ where Status::Idle }; - let appearance = (self.style)(theme, status); + let appearance = theme.style(&self.class, status); renderer.draw( self.handle.clone(), @@ -189,7 +198,7 @@ where impl<'a, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> where - Theme: 'a, + Theme: Catalog + 'a, Renderer: svg::Renderer + 'a, { fn from(icon: Svg<'a, Theme>) -> Element<'a, Message, Theme, Renderer> { @@ -208,7 +217,7 @@ pub enum Status { /// The appearance of an [`Svg`]. #[derive(Debug, Clone, Copy, PartialEq, Default)] -pub struct Appearance { +pub struct Style { /// The [`Color`] filter of an [`Svg`]. /// /// Useful for coloring a symbolic icon. @@ -217,23 +226,37 @@ pub struct Appearance { pub color: Option, } -/// The style of an [`Svg`]. -pub type Style<'a, Theme> = Box Appearance + 'a>; +/// The theme catalog of an [`Svg`]. +pub trait Catalog { + /// The item class of this [`Catalog`]. + type Class<'a>; + + /// The default class produced by this [`Catalog`]. + fn default<'a>() -> Self::Class<'a>; -/// The default style of an [`Svg`]. -pub trait DefaultStyle { - /// Returns the default style of an [`Svg`]. - fn default_style(&self, status: Status) -> Appearance; + /// The [`Style`] of a class with the given status. + fn style(&self, class: &Self::Class<'_>, status: Status) -> Style; } -impl DefaultStyle for Theme { - fn default_style(&self, _status: Status) -> Appearance { - Appearance::default() +impl Catalog for Theme { + type Class<'a> = StyleFn<'a, Self>; + + fn default<'a>() -> Self::Class<'a> { + Box::new(|_theme, _status| Style::default()) + } + + fn style(&self, class: &Self::Class<'_>, status: Status) -> Style { + class(self, status) } } -impl DefaultStyle for Appearance { - fn default_style(&self, _status: Status) -> Appearance { - *self +/// A styling function for an [`Svg`]. +/// +/// This is just a boxed closure: `Fn(&Theme, Status) -> Style`. +pub type StyleFn<'a, Theme> = Box Style + 'a>; + +impl<'a, Theme> From