From 0249640213120e039462f5fc12c677f15ecbc7cc Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 16 Nov 2022 16:35:56 +0100 Subject: feat: Add Color::into_rgb8 --- core/src/color.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/core/src/color.rs b/core/src/color.rs index 212c1214..e578eda4 100644 --- a/core/src/color.rs +++ b/core/src/color.rs @@ -89,6 +89,19 @@ impl Color { } } + /// Converts the [`Color`] into its RGBA8 equivalent. + #[must_use] + #[allow(clippy::cast_sign_loss)] + #[allow(clippy::cast_possible_truncation)] + pub fn into_rgba8(self) -> [u8; 4] { + [ + (self.r * 255.0).round() as u8, + (self.g * 255.0).round() as u8, + (self.b * 255.0).round() as u8, + (self.a * 255.0).round() as u8, + ] + } + /// Converts the [`Color`] into its linear values. pub fn into_linear(self) -> [f32; 4] { // As described in: -- cgit From 75ae0de9bdd3376b6e537bf59030059c926114ee Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 16 Nov 2022 17:42:41 +0100 Subject: feat: SVG styling with icon fill color --- graphics/src/image/vector.rs | 38 ++++++++++++++++++++++--------- native/src/svg.rs | 14 ++++++++++++ native/src/widget/helpers.rs | 8 ++++++- native/src/widget/svg.rs | 53 +++++++++++++++++++++++++++++++++++--------- style/src/lib.rs | 1 + style/src/svg.rs | 21 ++++++++++++++++++ style/src/theme.rs | 24 ++++++++++++++++++++ 7 files changed, 137 insertions(+), 22 deletions(-) create mode 100644 style/src/svg.rs diff --git a/graphics/src/image/vector.rs b/graphics/src/image/vector.rs index 42f4b500..5be5d3c7 100644 --- a/graphics/src/image/vector.rs +++ b/graphics/src/image/vector.rs @@ -7,6 +7,8 @@ use iced_native::Size; use std::collections::{HashMap, HashSet}; use std::fs; +type Fill = Option<[u8; 4]>; + /// Entry in cache corresponding to an svg handle pub enum Svg { /// Parsed svg @@ -33,9 +35,9 @@ impl Svg { #[derive(Debug)] pub struct Cache { svgs: HashMap, - rasterized: HashMap<(u64, u32, u32), T::Entry>, + rasterized: HashMap<(u64, u32, u32, Fill), T::Entry>, svg_hits: HashSet, - rasterized_hits: HashSet<(u64, u32, u32)>, + rasterized_hits: HashSet<(u64, u32, u32, Fill)>, } impl Cache { @@ -88,15 +90,18 @@ impl Cache { (scale * height).ceil() as u32, ); + let appearance = handle.appearance(); + let fill = appearance.fill.map(crate::Color::into_rgba8); + // TODO: Optimize! // We currently rerasterize the SVG when its size changes. This is slow // as heck. A GPU rasterizer like `pathfinder` may perform better. // It would be cool to be able to smooth resize the `svg` example. - if self.rasterized.contains_key(&(id, width, height)) { + if self.rasterized.contains_key(&(id, width, height, fill)) { let _ = self.svg_hits.insert(id); - let _ = self.rasterized_hits.insert((id, width, height)); + let _ = self.rasterized_hits.insert((id, width, height, fill)); - return self.rasterized.get(&(id, width, height)); + return self.rasterized.get(&(id, width, height, fill)); } match self.load(handle) { @@ -121,15 +126,28 @@ impl Cache { img.as_mut(), )?; - let allocation = - storage.upload(width, height, img.data(), state)?; + let mut rgba = img.take(); + + if let Some(color) = fill { + rgba.chunks_exact_mut(4).for_each(|rgba| { + if rgba[3] > 0 { + rgba[0] = color[0]; + rgba[1] = color[1]; + rgba[2] = color[2]; + } + }); + } + + let allocation = storage.upload(width, height, &rgba, state)?; log::debug!("allocating {} {}x{}", id, width, height); let _ = self.svg_hits.insert(id); - let _ = self.rasterized_hits.insert((id, width, height)); - let _ = self.rasterized.insert((id, width, height), allocation); + let _ = self.rasterized_hits.insert((id, width, height, fill)); + let _ = self + .rasterized + .insert((id, width, height, fill), allocation); - self.rasterized.get(&(id, width, height)) + self.rasterized.get(&(id, width, height, fill)) } Svg::NotFound => None, } diff --git a/native/src/svg.rs b/native/src/svg.rs index a8e481d2..08b0984a 100644 --- a/native/src/svg.rs +++ b/native/src/svg.rs @@ -6,11 +6,14 @@ use std::hash::{Hash, Hasher as _}; use std::path::PathBuf; use std::sync::Arc; +pub use iced_style::svg::{Appearance, StyleSheet}; + /// A handle of Svg data. #[derive(Debug, Clone)] pub struct Handle { id: u64, data: Arc, + appearance: Appearance, } impl Handle { @@ -36,6 +39,7 @@ impl Handle { Handle { id: hasher.finish(), data: Arc::new(data), + appearance: Appearance::default(), } } @@ -48,6 +52,16 @@ impl Handle { pub fn data(&self) -> &Data { &self.data } + + /// Returns the styling [`Appearance`] for the SVG. + pub fn appearance(&self) -> Appearance { + self.appearance + } + + /// Set the [`Appearance`] for the SVG. + pub fn set_appearance(&mut self, appearance: Appearance) { + self.appearance = appearance; + } } impl Hash for Handle { diff --git a/native/src/widget/helpers.rs b/native/src/widget/helpers.rs index 3bce9e60..e802f629 100644 --- a/native/src/widget/helpers.rs +++ b/native/src/widget/helpers.rs @@ -285,6 +285,12 @@ where /// /// [`Svg`]: widget::Svg /// [`Handle`]: widget::svg::Handle -pub fn svg(handle: impl Into) -> widget::Svg { +pub fn svg( + handle: impl Into, +) -> widget::Svg +where + Renderer: crate::svg::Renderer, + Renderer::Theme: crate::svg::StyleSheet, +{ widget::Svg::new(handle) } diff --git a/native/src/widget/svg.rs b/native/src/widget/svg.rs index 1015ed0a..c7eb4f6d 100644 --- a/native/src/widget/svg.rs +++ b/native/src/widget/svg.rs @@ -9,7 +9,7 @@ use crate::{ use std::path::PathBuf; -pub use svg::Handle; +pub use svg::{Handle, StyleSheet}; /// A vector graphics image. /// @@ -17,15 +17,25 @@ pub use svg::Handle; /// /// [`Svg`] images can have a considerable rendering cost when resized, /// specially when they are complex. -#[derive(Debug, Clone)] -pub struct Svg { +#[derive(Clone)] +#[allow(missing_debug_implementations)] +pub struct Svg +where + Renderer: svg::Renderer, + Renderer::Theme: StyleSheet, +{ handle: Handle, width: Length, height: Length, content_fit: ContentFit, + style: ::Style, } -impl Svg { +impl Svg +where + Renderer: svg::Renderer, + Renderer::Theme: StyleSheet, +{ /// Creates a new [`Svg`] from the given [`Handle`]. pub fn new(handle: impl Into) -> Self { Svg { @@ -33,22 +43,26 @@ impl Svg { width: Length::Fill, height: Length::Shrink, content_fit: ContentFit::Contain, + style: Default::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 { Self::new(Handle::from_path(path)) } /// Sets the width of the [`Svg`]. + #[must_use] pub fn width(mut self, width: Length) -> Self { self.width = width; self } /// Sets the height of the [`Svg`]. + #[must_use] pub fn height(mut self, height: Length) -> Self { self.height = height; self @@ -57,17 +71,29 @@ impl Svg { /// Sets the [`ContentFit`] of the [`Svg`]. /// /// Defaults to [`ContentFit::Contain`] + #[must_use] pub fn content_fit(self, content_fit: ContentFit) -> Self { Self { content_fit, ..self } } + + /// Sets the style variant of this [`Svg`]. + #[must_use] + pub fn style( + mut self, + style: ::Style, + ) -> Self { + self.style = style; + self + } } -impl Widget for Svg +impl Widget for Svg where Renderer: svg::Renderer, + Renderer::Theme: iced_style::svg::StyleSheet, { fn width(&self) -> Length { self.width @@ -114,12 +140,15 @@ where &self, _state: &Tree, renderer: &mut Renderer, - _theme: &Renderer::Theme, + theme: &Renderer::Theme, _style: &renderer::Style, layout: Layout<'_>, _cursor_position: Point, _viewport: &Rectangle, ) { + let mut handle = self.handle.clone(); + handle.set_appearance(theme.appearance(self.style)); + let Size { width, height } = renderer.dimensions(&self.handle); let image_size = Size::new(width as f32, height as f32); @@ -138,7 +167,7 @@ where ..bounds }; - renderer.draw(self.handle.clone(), drawing_bounds + offset) + renderer.draw(handle, drawing_bounds + offset); }; if adjusted_fit.width > bounds.width @@ -146,16 +175,18 @@ where { renderer.with_layer(bounds, render); } else { - render(renderer) + render(renderer); } } } -impl<'a, Message, Renderer> From for Element<'a, Message, Renderer> +impl<'a, Message, Renderer> From> + for Element<'a, Message, Renderer> where - Renderer: svg::Renderer, + Renderer: svg::Renderer + 'a, + Renderer::Theme: iced_style::svg::StyleSheet, { - fn from(icon: Svg) -> Element<'a, Message, Renderer> { + fn from(icon: Svg) -> Element<'a, Message, Renderer> { Element::new(icon) } } diff --git a/style/src/lib.rs b/style/src/lib.rs index 3242602c..59eb1eb8 100644 --- a/style/src/lib.rs +++ b/style/src/lib.rs @@ -32,6 +32,7 @@ pub mod radio; pub mod rule; pub mod scrollable; pub mod slider; +pub mod svg; pub mod text; pub mod text_input; pub mod theme; diff --git a/style/src/svg.rs b/style/src/svg.rs new file mode 100644 index 00000000..66791d04 --- /dev/null +++ b/style/src/svg.rs @@ -0,0 +1,21 @@ +//! Change the appearance of a svg. + +use iced_core::Color; + +/// The appearance of a svg. +#[derive(Debug, Default, Clone, Copy)] +pub struct Appearance { + /// Changes the fill color + /// + /// Useful for coloring a symbolic icon. + pub fill: Option, +} + +/// The stylesheet of a svg. +pub trait StyleSheet { + /// The supported style of the [`StyleSheet`]. + type Style: Default + Copy; + + /// Produces the [`Appearance`] of the svg. + fn appearance(&self, style: Self::Style) -> Appearance; +} diff --git a/style/src/theme.rs b/style/src/theme.rs index dde0df5d..d825b086 100644 --- a/style/src/theme.rs +++ b/style/src/theme.rs @@ -16,6 +16,7 @@ use crate::radio; use crate::rule; use crate::scrollable; use crate::slider; +use crate::svg; use crate::text; use crate::text_input; use crate::toggler; @@ -797,6 +798,29 @@ impl From rule::Appearance> for Rule { } } +/** + * SVG + */ +#[derive(Default, Clone, Copy)] +pub enum Svg { + /// No filtering to the rendered SVG. + #[default] + Default, + /// Apply custom filtering to the SVG. + Custom(fn(&Theme) -> svg::Appearance), +} + +impl svg::StyleSheet for Theme { + type Style = Svg; + + fn appearance(&self, style: Self::Style) -> svg::Appearance { + match style { + Svg::Default => Default::default(), + Svg::Custom(appearance) => appearance(self), + } + } +} + impl rule::StyleSheet for Theme { type Style = Rule; -- cgit From 314b0f7dc52c844669c224060a7b03e842762370 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 16 Nov 2022 17:43:16 +0100 Subject: chore(examples): Add svg-style example --- examples/svg_style/Cargo.toml | 10 +++ examples/svg_style/resources/go-next-symbolic.svg | 1 + examples/svg_style/src/main.rs | 78 +++++++++++++++++++++++ 3 files changed, 89 insertions(+) create mode 100644 examples/svg_style/Cargo.toml create mode 100644 examples/svg_style/resources/go-next-symbolic.svg create mode 100644 examples/svg_style/src/main.rs diff --git a/examples/svg_style/Cargo.toml b/examples/svg_style/Cargo.toml new file mode 100644 index 00000000..9ecda7c4 --- /dev/null +++ b/examples/svg_style/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "svg_style" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +iced = { path = "../..", features = ["svg"] } +iced_style = { path = "../../style" } \ No newline at end of file diff --git a/examples/svg_style/resources/go-next-symbolic.svg b/examples/svg_style/resources/go-next-symbolic.svg new file mode 100644 index 00000000..79c456b7 --- /dev/null +++ b/examples/svg_style/resources/go-next-symbolic.svg @@ -0,0 +1 @@ + diff --git a/examples/svg_style/src/main.rs b/examples/svg_style/src/main.rs new file mode 100644 index 00000000..905e1d86 --- /dev/null +++ b/examples/svg_style/src/main.rs @@ -0,0 +1,78 @@ +use iced::widget::{container, svg}; +use iced::{Color, Element, Length, Sandbox, Settings}; +use iced_style::svg::Appearance; +use iced_style::theme::{self, Theme}; + +pub fn main() -> iced::Result { + SvgStyleExample::run(Settings::default()) +} + +struct SvgStyleExample; + +impl Sandbox for SvgStyleExample { + type Message = (); + + fn new() -> Self { + SvgStyleExample + } + + fn theme(&self) -> Theme { + Theme::Light + } + + fn title(&self) -> String { + String::from("SVG - Iced") + } + + fn update(&mut self, _message: ()) {} + + fn view(&self) -> Element<()> { + let svg1: Element<_> = svg(svg::Handle::from_path(format!( + "{}/resources/go-next-symbolic.svg", + env!("CARGO_MANIFEST_DIR") + ))) + .width(Length::Fill) + .height(Length::Fill) + .into(); + + let svg2: Element<_> = svg(svg::Handle::from_path(format!( + "{}/resources/go-next-symbolic.svg", + env!("CARGO_MANIFEST_DIR") + ))) + .style(theme::Svg::Custom(|_theme| Appearance { + fill: Some(Color { + r: 0.0, + g: 0.28627452, + b: 0.42745098, + a: 1.0, + }), + })) + .width(Length::Fill) + .height(Length::Fill) + .into(); + + let svg3: Element<_> = svg(svg::Handle::from_path(format!( + "{}/resources/go-next-symbolic.svg", + env!("CARGO_MANIFEST_DIR") + ))) + .style(theme::Svg::Custom(|_theme| Appearance { + fill: Some(Color { + r: 0.5803922, + g: 0.92156863, + b: 0.92156863, + a: 1.0, + }), + })) + .width(Length::Fill) + .height(Length::Fill) + .into(); + + container(iced::widget::row!(svg1, svg2, svg3)) + .width(Length::Fill) + .height(Length::Fill) + .padding(20) + .center_x() + .center_y() + .into() + } +} -- cgit From b205a663471a8170d7b30cc59894425c09bea563 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 6 Dec 2022 04:34:00 +0100 Subject: Remove `appearance` from `Handle` ... and pass it directly to `Renderer::draw` instead. --- examples/svg_style/src/main.rs | 8 +++--- glow/src/image.rs | 7 ++++- graphics/src/image/vector.rs | 22 +++++++-------- graphics/src/layer.rs | 7 ++++- graphics/src/layer/image.rs | 6 ++++- graphics/src/primitive.rs | 3 +++ graphics/src/renderer.rs | 15 ++++++++--- native/src/svg.rs | 20 +++----------- native/src/widget/helpers.rs | 2 +- native/src/widget/svg.rs | 15 ++++++----- src/widget.rs | 2 +- style/src/svg.rs | 12 +++++---- style/src/theme.rs | 61 ++++++++++++++++++++++++++---------------- wgpu/src/image.rs | 7 ++++- 14 files changed, 112 insertions(+), 75 deletions(-) diff --git a/examples/svg_style/src/main.rs b/examples/svg_style/src/main.rs index 905e1d86..0a1fa039 100644 --- a/examples/svg_style/src/main.rs +++ b/examples/svg_style/src/main.rs @@ -39,8 +39,8 @@ impl Sandbox for SvgStyleExample { "{}/resources/go-next-symbolic.svg", env!("CARGO_MANIFEST_DIR") ))) - .style(theme::Svg::Custom(|_theme| Appearance { - fill: Some(Color { + .style(theme::Svg::custom_fn(|_theme| Appearance { + color: Some(Color { r: 0.0, g: 0.28627452, b: 0.42745098, @@ -55,8 +55,8 @@ impl Sandbox for SvgStyleExample { "{}/resources/go-next-symbolic.svg", env!("CARGO_MANIFEST_DIR") ))) - .style(theme::Svg::Custom(|_theme| Appearance { - fill: Some(Color { + .style(theme::Svg::custom_fn(|_theme| Appearance { + color: Some(Color { r: 0.5803922, g: 0.92156863, b: 0.92156863, diff --git a/glow/src/image.rs b/glow/src/image.rs index 955fd1ab..c32b2162 100644 --- a/glow/src/image.rs +++ b/glow/src/image.rs @@ -172,11 +172,16 @@ impl Pipeline { layer::Image::Raster { handle: _, bounds } => (None, bounds), #[cfg(feature = "svg")] - layer::Image::Vector { handle, bounds } => { + layer::Image::Vector { + handle, + color, + bounds, + } => { let size = [bounds.width, bounds.height]; ( vector_cache.upload( handle, + *color, size, _scale_factor, &mut gl, diff --git a/graphics/src/image/vector.rs b/graphics/src/image/vector.rs index 5be5d3c7..0af5be01 100644 --- a/graphics/src/image/vector.rs +++ b/graphics/src/image/vector.rs @@ -1,5 +1,6 @@ //! Vector image loading and caching use crate::image::Storage; +use crate::Color; use iced_native::svg; use iced_native::Size; @@ -78,6 +79,7 @@ impl Cache { pub fn upload( &mut self, handle: &svg::Handle, + color: Option, [width, height]: [f32; 2], scale: f32, state: &mut T::State<'_>, @@ -90,18 +92,18 @@ impl Cache { (scale * height).ceil() as u32, ); - let appearance = handle.appearance(); - let fill = appearance.fill.map(crate::Color::into_rgba8); + let color = color.map(Color::into_rgba8); + let key = (id, width, height, color); // TODO: Optimize! // We currently rerasterize the SVG when its size changes. This is slow // as heck. A GPU rasterizer like `pathfinder` may perform better. // It would be cool to be able to smooth resize the `svg` example. - if self.rasterized.contains_key(&(id, width, height, fill)) { + if self.rasterized.contains_key(&key) { let _ = self.svg_hits.insert(id); - let _ = self.rasterized_hits.insert((id, width, height, fill)); + let _ = self.rasterized_hits.insert(key); - return self.rasterized.get(&(id, width, height, fill)); + return self.rasterized.get(&key); } match self.load(handle) { @@ -128,7 +130,7 @@ impl Cache { let mut rgba = img.take(); - if let Some(color) = fill { + if let Some(color) = color { rgba.chunks_exact_mut(4).for_each(|rgba| { if rgba[3] > 0 { rgba[0] = color[0]; @@ -142,12 +144,10 @@ impl Cache { log::debug!("allocating {} {}x{}", id, width, height); let _ = self.svg_hits.insert(id); - let _ = self.rasterized_hits.insert((id, width, height, fill)); - let _ = self - .rasterized - .insert((id, width, height, fill), allocation); + let _ = self.rasterized_hits.insert(key); + let _ = self.rasterized.insert(key, allocation); - self.rasterized.get(&(id, width, height, fill)) + self.rasterized.get(&key) } Svg::NotFound => None, } diff --git a/graphics/src/layer.rs b/graphics/src/layer.rs index fd670f48..1d453caa 100644 --- a/graphics/src/layer.rs +++ b/graphics/src/layer.rs @@ -251,11 +251,16 @@ impl<'a> Layer<'a> { bounds: *bounds + translation, }); } - Primitive::Svg { handle, bounds } => { + Primitive::Svg { + handle, + color, + bounds, + } => { let layer = &mut layers[current_layer]; layer.images.push(Image::Vector { handle: handle.clone(), + color: *color, bounds: *bounds + translation, }); } diff --git a/graphics/src/layer/image.rs b/graphics/src/layer/image.rs index 045ec665..3eff2397 100644 --- a/graphics/src/layer/image.rs +++ b/graphics/src/layer/image.rs @@ -1,4 +1,5 @@ -use crate::Rectangle; +use crate::{Color, Rectangle}; + use iced_native::{image, svg}; /// A raster or vector image. @@ -17,6 +18,9 @@ pub enum Image { /// The handle of a vector image. handle: svg::Handle, + /// The [`Color`] filter + color: Option, + /// The bounds of the image. bounds: Rectangle, }, diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs index 6f1b6f26..5a163a2f 100644 --- a/graphics/src/primitive.rs +++ b/graphics/src/primitive.rs @@ -60,6 +60,9 @@ pub enum Primitive { /// The path of the SVG file handle: svg::Handle, + /// The [`Color`] filter + color: Option, + /// The bounds of the viewport bounds: Rectangle, }, diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index 65350037..aabdf7fc 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -6,7 +6,7 @@ use iced_native::layout; use iced_native::renderer; use iced_native::svg; use iced_native::text::{self, Text}; -use iced_native::{Background, Element, Font, Point, Rectangle, Size}; +use iced_native::{Background, Color, Element, Font, Point, Rectangle, Size}; pub use iced_native::renderer::Style; @@ -200,7 +200,16 @@ where self.backend().viewport_dimensions(handle) } - fn draw(&mut self, handle: svg::Handle, bounds: Rectangle) { - self.draw_primitive(Primitive::Svg { handle, bounds }) + fn draw( + &mut self, + handle: svg::Handle, + color: Option, + bounds: Rectangle, + ) { + self.draw_primitive(Primitive::Svg { + handle, + color, + bounds, + }) } } diff --git a/native/src/svg.rs b/native/src/svg.rs index 08b0984a..2168e409 100644 --- a/native/src/svg.rs +++ b/native/src/svg.rs @@ -1,19 +1,16 @@ //! Load and draw vector graphics. -use crate::{Hasher, Rectangle, Size}; +use crate::{Color, Hasher, Rectangle, Size}; use std::borrow::Cow; use std::hash::{Hash, Hasher as _}; use std::path::PathBuf; use std::sync::Arc; -pub use iced_style::svg::{Appearance, StyleSheet}; - /// A handle of Svg data. #[derive(Debug, Clone)] pub struct Handle { id: u64, data: Arc, - appearance: Appearance, } impl Handle { @@ -39,7 +36,6 @@ impl Handle { Handle { id: hasher.finish(), data: Arc::new(data), - appearance: Appearance::default(), } } @@ -52,16 +48,6 @@ impl Handle { pub fn data(&self) -> &Data { &self.data } - - /// Returns the styling [`Appearance`] for the SVG. - pub fn appearance(&self) -> Appearance { - self.appearance - } - - /// Set the [`Appearance`] for the SVG. - pub fn set_appearance(&mut self, appearance: Appearance) { - self.appearance = appearance; - } } impl Hash for Handle { @@ -98,6 +84,6 @@ pub trait Renderer: crate::Renderer { /// Returns the default dimensions of an SVG for the given [`Handle`]. fn dimensions(&self, handle: &Handle) -> Size; - /// Draws an SVG with the given [`Handle`] and inside the provided `bounds`. - fn draw(&mut self, handle: Handle, bounds: Rectangle); + /// Draws an SVG with the given [`Handle`], an optional [`Color`] filter, and inside the provided `bounds`. + fn draw(&mut self, handle: Handle, color: Option, bounds: Rectangle); } diff --git a/native/src/widget/helpers.rs b/native/src/widget/helpers.rs index e802f629..0bde288f 100644 --- a/native/src/widget/helpers.rs +++ b/native/src/widget/helpers.rs @@ -290,7 +290,7 @@ pub fn svg( ) -> widget::Svg where Renderer: crate::svg::Renderer, - Renderer::Theme: crate::svg::StyleSheet, + Renderer::Theme: widget::svg::StyleSheet, { widget::Svg::new(handle) } diff --git a/native/src/widget/svg.rs b/native/src/widget/svg.rs index c7eb4f6d..f83f5acf 100644 --- a/native/src/widget/svg.rs +++ b/native/src/widget/svg.rs @@ -9,7 +9,8 @@ use crate::{ use std::path::PathBuf; -pub use svg::{Handle, StyleSheet}; +pub use iced_style::svg::{Appearance, StyleSheet}; +pub use svg::Handle; /// A vector graphics image. /// @@ -17,7 +18,6 @@ pub use svg::{Handle, StyleSheet}; /// /// [`Svg`] images can have a considerable rendering cost when resized, /// specially when they are complex. -#[derive(Clone)] #[allow(missing_debug_implementations)] pub struct Svg where @@ -146,9 +146,6 @@ where _cursor_position: Point, _viewport: &Rectangle, ) { - let mut handle = self.handle.clone(); - handle.set_appearance(theme.appearance(self.style)); - let Size { width, height } = renderer.dimensions(&self.handle); let image_size = Size::new(width as f32, height as f32); @@ -167,7 +164,13 @@ where ..bounds }; - renderer.draw(handle, drawing_bounds + offset); + let appearance = theme.appearance(&self.style); + + renderer.draw( + self.handle.clone(), + appearance.color, + drawing_bounds + offset, + ); }; if adjusted_fit.width > bounds.width diff --git a/src/widget.rs b/src/widget.rs index 7c67a599..e7df6e4e 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -194,7 +194,7 @@ pub use iced_graphics::widget::qr_code; pub mod svg { //! Display vector graphics in your application. pub use iced_native::svg::Handle; - pub use iced_native::widget::Svg; + pub use iced_native::widget::svg::{Appearance, StyleSheet, Svg}; } #[cfg(feature = "canvas")] diff --git a/style/src/svg.rs b/style/src/svg.rs index 66791d04..9378c1a7 100644 --- a/style/src/svg.rs +++ b/style/src/svg.rs @@ -2,20 +2,22 @@ use iced_core::Color; -/// The appearance of a svg. +/// The appearance of an SVG. #[derive(Debug, Default, Clone, Copy)] pub struct Appearance { - /// Changes the fill color + /// The [`Color`] filter of an SVG. /// /// Useful for coloring a symbolic icon. - pub fill: Option, + /// + /// `None` keeps the original color. + pub color: Option, } /// The stylesheet of a svg. pub trait StyleSheet { /// The supported style of the [`StyleSheet`]. - type Style: Default + Copy; + type Style: Default; /// Produces the [`Appearance`] of the svg. - fn appearance(&self, style: Self::Style) -> Appearance; + fn appearance(&self, style: &Self::Style) -> Appearance; } diff --git a/style/src/theme.rs b/style/src/theme.rs index d825b086..d2b583ed 100644 --- a/style/src/theme.rs +++ b/style/src/theme.rs @@ -798,29 +798,6 @@ impl From rule::Appearance> for Rule { } } -/** - * SVG - */ -#[derive(Default, Clone, Copy)] -pub enum Svg { - /// No filtering to the rendered SVG. - #[default] - Default, - /// Apply custom filtering to the SVG. - Custom(fn(&Theme) -> svg::Appearance), -} - -impl svg::StyleSheet for Theme { - type Style = Svg; - - fn appearance(&self, style: Self::Style) -> svg::Appearance { - match style { - Svg::Default => Default::default(), - Svg::Custom(appearance) => appearance(self), - } - } -} - impl rule::StyleSheet for Theme { type Style = Rule; @@ -847,6 +824,44 @@ impl rule::StyleSheet for fn(&Theme) -> rule::Appearance { } } +/** + * SVG + */ +#[derive(Default)] +pub enum Svg { + /// No filtering to the rendered SVG. + #[default] + Default, + /// A custom style. + Custom(Box>), +} + +impl Svg { + /// Creates a custom [`Svg`] style. + pub fn custom_fn(f: fn(&Theme) -> svg::Appearance) -> Self { + Self::Custom(Box::new(f)) + } +} + +impl svg::StyleSheet for Theme { + type Style = Svg; + + fn appearance(&self, style: &Self::Style) -> svg::Appearance { + match style { + Svg::Default => Default::default(), + Svg::Custom(custom) => custom.appearance(self), + } + } +} + +impl svg::StyleSheet for fn(&Theme) -> svg::Appearance { + type Style = Theme; + + fn appearance(&self, style: &Self::Style) -> svg::Appearance { + (self)(style) + } +} + /// The style of a scrollable. #[derive(Default)] pub enum Scrollable { diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index d06815bb..390bad90 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -318,11 +318,16 @@ impl Pipeline { layer::Image::Raster { .. } => {} #[cfg(feature = "svg")] - layer::Image::Vector { handle, bounds } => { + layer::Image::Vector { + handle, + color, + bounds, + } => { let size = [bounds.width, bounds.height]; if let Some(atlas_entry) = vector_cache.upload( handle, + *color, size, _scale, &mut (device, encoder), -- cgit From c0ca1807d42b0ec58887df9926ff53a587104723 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 6 Dec 2022 04:34:42 +0100 Subject: Fix macro hygiene of `color!` --- core/src/color.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/core/src/color.rs b/core/src/color.rs index e578eda4..2a4d43f4 100644 --- a/core/src/color.rs +++ b/core/src/color.rs @@ -161,24 +161,26 @@ impl From<[f32; 4]> for Color { #[macro_export] macro_rules! color { ($r:expr, $g:expr, $b:expr) => { - Color::from_rgb8($r, $g, $b) + $crate::Color::from_rgb8($r, $g, $b) }; ($r:expr, $g:expr, $b:expr, $a:expr) => { - Color::from_rgba8($r, $g, $b, $a) + $crate::Color::from_rgba8($r, $g, $b, $a) }; ($hex:expr) => {{ let hex = $hex as u32; let r = (hex & 0xff0000) >> 16; let g = (hex & 0xff00) >> 8; let b = (hex & 0xff); - Color::from_rgb8(r as u8, g as u8, b as u8) + + $crate::Color::from_rgb8(r as u8, g as u8, b as u8) }}; ($hex:expr, $a:expr) => {{ let hex = $hex as u32; let r = (hex & 0xff0000) >> 16; let g = (hex & 0xff00) >> 8; let b = (hex & 0xff); - Color::from_rgba8(r as u8, g as u8, b as u8, $a) + + $crate::Color::from_rgba8(r as u8, g as u8, b as u8, $a) }}; } -- cgit From 1220ce55bc882cc4fef891c8b2d0fdc22e18a015 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 6 Dec 2022 04:35:08 +0100 Subject: Showcase color filtering in existing `svg` example ... and remove the `svg_style` example --- examples/svg/src/main.rs | 71 ++++++++++++++++----- examples/svg_style/Cargo.toml | 10 --- examples/svg_style/resources/go-next-symbolic.svg | 1 - examples/svg_style/src/main.rs | 78 ----------------------- 4 files changed, 54 insertions(+), 106 deletions(-) delete mode 100644 examples/svg_style/Cargo.toml delete mode 100644 examples/svg_style/resources/go-next-symbolic.svg delete mode 100644 examples/svg_style/src/main.rs diff --git a/examples/svg/src/main.rs b/examples/svg/src/main.rs index 27d175da..4dc92416 100644 --- a/examples/svg/src/main.rs +++ b/examples/svg/src/main.rs @@ -1,39 +1,76 @@ -use iced::widget::{container, svg}; -use iced::{Element, Length, Sandbox, Settings}; +use iced::theme; +use iced::widget::{checkbox, column, container, svg}; +use iced::{color, Element, Length, Sandbox, Settings}; pub fn main() -> iced::Result { Tiger::run(Settings::default()) } -struct Tiger; +#[derive(Debug, Default)] +struct Tiger { + apply_color_filter: bool, +} + +#[derive(Debug, Clone, Copy)] +pub enum Message { + ToggleColorFilter(bool), +} impl Sandbox for Tiger { - type Message = (); + type Message = Message; fn new() -> Self { - Tiger + Tiger::default() } fn title(&self) -> String { String::from("SVG - Iced") } - fn update(&mut self, _message: ()) {} + fn update(&mut self, message: Self::Message) { + match message { + Message::ToggleColorFilter(apply_color_filter) => { + self.apply_color_filter = apply_color_filter; + } + } + } - fn view(&self) -> Element<()> { - let svg = svg(svg::Handle::from_path(format!( + fn view(&self) -> Element { + let handle = svg::Handle::from_path(format!( "{}/resources/tiger.svg", env!("CARGO_MANIFEST_DIR") - ))) - .width(Length::Fill) - .height(Length::Fill); + )); + + let svg = svg(handle).width(Length::Fill).height(Length::Fill).style( + if self.apply_color_filter { + theme::Svg::custom_fn(|_theme| svg::Appearance { + color: Some(color!(0x0000ff)), + }) + } else { + theme::Svg::Default + }, + ); - container(svg) + let apply_color_filter = checkbox( + "Apply a color filter", + self.apply_color_filter, + Message::ToggleColorFilter, + ); + + container( + column![ + svg, + container(apply_color_filter).width(Length::Fill).center_x() + ] + .spacing(20) .width(Length::Fill) - .height(Length::Fill) - .padding(20) - .center_x() - .center_y() - .into() + .height(Length::Fill), + ) + .width(Length::Fill) + .height(Length::Fill) + .padding(20) + .center_x() + .center_y() + .into() } } diff --git a/examples/svg_style/Cargo.toml b/examples/svg_style/Cargo.toml deleted file mode 100644 index 9ecda7c4..00000000 --- a/examples/svg_style/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "svg_style" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -iced = { path = "../..", features = ["svg"] } -iced_style = { path = "../../style" } \ No newline at end of file diff --git a/examples/svg_style/resources/go-next-symbolic.svg b/examples/svg_style/resources/go-next-symbolic.svg deleted file mode 100644 index 79c456b7..00000000 --- a/examples/svg_style/resources/go-next-symbolic.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/examples/svg_style/src/main.rs b/examples/svg_style/src/main.rs deleted file mode 100644 index 0a1fa039..00000000 --- a/examples/svg_style/src/main.rs +++ /dev/null @@ -1,78 +0,0 @@ -use iced::widget::{container, svg}; -use iced::{Color, Element, Length, Sandbox, Settings}; -use iced_style::svg::Appearance; -use iced_style::theme::{self, Theme}; - -pub fn main() -> iced::Result { - SvgStyleExample::run(Settings::default()) -} - -struct SvgStyleExample; - -impl Sandbox for SvgStyleExample { - type Message = (); - - fn new() -> Self { - SvgStyleExample - } - - fn theme(&self) -> Theme { - Theme::Light - } - - fn title(&self) -> String { - String::from("SVG - Iced") - } - - fn update(&mut self, _message: ()) {} - - fn view(&self) -> Element<()> { - let svg1: Element<_> = svg(svg::Handle::from_path(format!( - "{}/resources/go-next-symbolic.svg", - env!("CARGO_MANIFEST_DIR") - ))) - .width(Length::Fill) - .height(Length::Fill) - .into(); - - let svg2: Element<_> = svg(svg::Handle::from_path(format!( - "{}/resources/go-next-symbolic.svg", - env!("CARGO_MANIFEST_DIR") - ))) - .style(theme::Svg::custom_fn(|_theme| Appearance { - color: Some(Color { - r: 0.0, - g: 0.28627452, - b: 0.42745098, - a: 1.0, - }), - })) - .width(Length::Fill) - .height(Length::Fill) - .into(); - - let svg3: Element<_> = svg(svg::Handle::from_path(format!( - "{}/resources/go-next-symbolic.svg", - env!("CARGO_MANIFEST_DIR") - ))) - .style(theme::Svg::custom_fn(|_theme| Appearance { - color: Some(Color { - r: 0.5803922, - g: 0.92156863, - b: 0.92156863, - a: 1.0, - }), - })) - .width(Length::Fill) - .height(Length::Fill) - .into(); - - container(iced::widget::row!(svg1, svg2, svg3)) - .width(Length::Fill) - .height(Length::Fill) - .padding(20) - .center_x() - .center_y() - .into() - } -} -- cgit From 2d58a2c03325a77893461194bd601b312a0097ca Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 6 Dec 2022 04:40:32 +0100 Subject: Remove unnecessary `clippy` directives in `Color` --- core/src/color.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/src/color.rs b/core/src/color.rs index 2a4d43f4..fe0a1856 100644 --- a/core/src/color.rs +++ b/core/src/color.rs @@ -91,8 +91,6 @@ impl Color { /// Converts the [`Color`] into its RGBA8 equivalent. #[must_use] - #[allow(clippy::cast_sign_loss)] - #[allow(clippy::cast_possible_truncation)] pub fn into_rgba8(self) -> [u8; 4] { [ (self.r * 255.0).round() as u8, -- cgit From a2f71f42ba300cec5490050a471efeebf627d534 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 6 Dec 2022 04:42:25 +0100 Subject: Rename `Fill` to `ColorFilter` in `graphics::image::vector` --- graphics/src/image/vector.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/graphics/src/image/vector.rs b/graphics/src/image/vector.rs index 0af5be01..82d77aff 100644 --- a/graphics/src/image/vector.rs +++ b/graphics/src/image/vector.rs @@ -8,8 +8,6 @@ use iced_native::Size; use std::collections::{HashMap, HashSet}; use std::fs; -type Fill = Option<[u8; 4]>; - /// Entry in cache corresponding to an svg handle pub enum Svg { /// Parsed svg @@ -36,11 +34,13 @@ impl Svg { #[derive(Debug)] pub struct Cache { svgs: HashMap, - rasterized: HashMap<(u64, u32, u32, Fill), T::Entry>, + rasterized: HashMap<(u64, u32, u32, ColorFilter), T::Entry>, svg_hits: HashSet, - rasterized_hits: HashSet<(u64, u32, u32, Fill)>, + rasterized_hits: HashSet<(u64, u32, u32, ColorFilter)>, } +type ColorFilter = Option<[u8; 4]>; + impl Cache { /// Load svg pub fn load(&mut self, handle: &svg::Handle) -> &Svg { -- cgit From f99d24e0850b63194b7976ec66d547ea2ff6bfc8 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 6 Dec 2022 04:44:37 +0100 Subject: Fix casing in `theme` --- style/src/theme.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/style/src/theme.rs b/style/src/theme.rs index d2b583ed..271d9a29 100644 --- a/style/src/theme.rs +++ b/style/src/theme.rs @@ -825,7 +825,7 @@ impl rule::StyleSheet for fn(&Theme) -> rule::Appearance { } /** - * SVG + * Svg */ #[derive(Default)] pub enum Svg { -- cgit