summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--core/src/color.rs21
-rw-r--r--examples/svg/src/main.rs71
-rw-r--r--glow/src/image.rs7
-rw-r--r--graphics/src/image/vector.rs38
-rw-r--r--graphics/src/layer.rs7
-rw-r--r--graphics/src/layer/image.rs6
-rw-r--r--graphics/src/primitive.rs3
-rw-r--r--graphics/src/renderer.rs15
-rw-r--r--native/src/svg.rs6
-rw-r--r--native/src/widget/helpers.rs8
-rw-r--r--native/src/widget/svg.rs54
-rw-r--r--src/widget.rs2
-rw-r--r--style/src/lib.rs1
-rw-r--r--style/src/svg.rs23
-rw-r--r--style/src/theme.rs39
-rw-r--r--wgpu/src/image.rs7
16 files changed, 255 insertions, 53 deletions
diff --git a/core/src/color.rs b/core/src/color.rs
index 212c1214..fe0a1856 100644
--- a/core/src/color.rs
+++ b/core/src/color.rs
@@ -89,6 +89,17 @@ impl Color {
}
}
+ /// Converts the [`Color`] into its RGBA8 equivalent.
+ #[must_use]
+ 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:
@@ -148,24 +159,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)
}};
}
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<Self::Message> {
+ 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/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 42f4b500..82d77aff 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;
@@ -33,11 +34,13 @@ impl Svg {
#[derive(Debug)]
pub struct Cache<T: Storage> {
svgs: HashMap<u64, Svg>,
- rasterized: HashMap<(u64, u32, u32), T::Entry>,
+ rasterized: HashMap<(u64, u32, u32, ColorFilter), T::Entry>,
svg_hits: HashSet<u64>,
- rasterized_hits: HashSet<(u64, u32, u32)>,
+ rasterized_hits: HashSet<(u64, u32, u32, ColorFilter)>,
}
+type ColorFilter = Option<[u8; 4]>;
+
impl<T: Storage> Cache<T> {
/// Load svg
pub fn load(&mut self, handle: &svg::Handle) -> &Svg {
@@ -76,6 +79,7 @@ impl<T: Storage> Cache<T> {
pub fn upload(
&mut self,
handle: &svg::Handle,
+ color: Option<Color>,
[width, height]: [f32; 2],
scale: f32,
state: &mut T::State<'_>,
@@ -88,15 +92,18 @@ impl<T: Storage> Cache<T> {
(scale * height).ceil() as u32,
);
+ 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)) {
+ if self.rasterized.contains_key(&key) {
let _ = self.svg_hits.insert(id);
- let _ = self.rasterized_hits.insert((id, width, height));
+ let _ = self.rasterized_hits.insert(key);
- return self.rasterized.get(&(id, width, height));
+ return self.rasterized.get(&key);
}
match self.load(handle) {
@@ -121,15 +128,26 @@ impl<T: Storage> Cache<T> {
img.as_mut(),
)?;
- let allocation =
- storage.upload(width, height, img.data(), state)?;
+ let mut rgba = img.take();
+
+ if let Some(color) = color {
+ 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(key);
+ let _ = self.rasterized.insert(key, allocation);
- self.rasterized.get(&(id, width, height))
+ 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<Color>,
+
/// 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<Color>,
+
/// 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<Color>,
+ bounds: Rectangle,
+ ) {
+ self.draw_primitive(Primitive::Svg {
+ handle,
+ color,
+ bounds,
+ })
}
}
diff --git a/native/src/svg.rs b/native/src/svg.rs
index a8e481d2..2168e409 100644
--- a/native/src/svg.rs
+++ b/native/src/svg.rs
@@ -1,5 +1,5 @@
//! 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 _};
@@ -84,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<u32>;
- /// 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<Color>, bounds: Rectangle);
}
diff --git a/native/src/widget/helpers.rs b/native/src/widget/helpers.rs
index 3bce9e60..0bde288f 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::Handle>) -> widget::Svg {
+pub fn svg<Renderer>(
+ handle: impl Into<widget::svg::Handle>,
+) -> widget::Svg<Renderer>
+where
+ Renderer: crate::svg::Renderer,
+ Renderer::Theme: widget::svg::StyleSheet,
+{
widget::Svg::new(handle)
}
diff --git a/native/src/widget/svg.rs b/native/src/widget/svg.rs
index 1015ed0a..f83f5acf 100644
--- a/native/src/widget/svg.rs
+++ b/native/src/widget/svg.rs
@@ -9,6 +9,7 @@ use crate::{
use std::path::PathBuf;
+pub use iced_style::svg::{Appearance, StyleSheet};
pub use svg::Handle;
/// A vector graphics image.
@@ -17,15 +18,24 @@ 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 {
+#[allow(missing_debug_implementations)]
+pub struct Svg<Renderer>
+where
+ Renderer: svg::Renderer,
+ Renderer::Theme: StyleSheet,
+{
handle: Handle,
width: Length,
height: Length,
content_fit: ContentFit,
+ style: <Renderer::Theme as StyleSheet>::Style,
}
-impl Svg {
+impl<Renderer> Svg<Renderer>
+where
+ Renderer: svg::Renderer,
+ Renderer::Theme: StyleSheet,
+{
/// Creates a new [`Svg`] from the given [`Handle`].
pub fn new(handle: impl Into<Handle>) -> 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<PathBuf>) -> 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: <Renderer::Theme as StyleSheet>::Style,
+ ) -> Self {
+ self.style = style;
+ self
+ }
}
-impl<Message, Renderer> Widget<Message, Renderer> for Svg
+impl<Message, Renderer> Widget<Message, Renderer> for Svg<Renderer>
where
Renderer: svg::Renderer,
+ Renderer::Theme: iced_style::svg::StyleSheet,
{
fn width(&self) -> Length {
self.width
@@ -114,7 +140,7 @@ where
&self,
_state: &Tree,
renderer: &mut Renderer,
- _theme: &Renderer::Theme,
+ theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: Point,
@@ -138,7 +164,13 @@ where
..bounds
};
- renderer.draw(self.handle.clone(), 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
@@ -146,16 +178,18 @@ where
{
renderer.with_layer(bounds, render);
} else {
- render(renderer)
+ render(renderer);
}
}
}
-impl<'a, Message, Renderer> From<Svg> for Element<'a, Message, Renderer>
+impl<'a, Message, Renderer> From<Svg<Renderer>>
+ 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<Renderer>) -> Element<'a, Message, Renderer> {
Element::new(icon)
}
}
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/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..9378c1a7
--- /dev/null
+++ b/style/src/svg.rs
@@ -0,0 +1,23 @@
+//! Change the appearance of a svg.
+
+use iced_core::Color;
+
+/// The appearance of an SVG.
+#[derive(Debug, Default, Clone, Copy)]
+pub struct Appearance {
+ /// The [`Color`] filter of an SVG.
+ ///
+ /// Useful for coloring a symbolic icon.
+ ///
+ /// `None` keeps the original color.
+ pub color: Option<Color>,
+}
+
+/// The stylesheet of a svg.
+pub trait StyleSheet {
+ /// The supported style of the [`StyleSheet`].
+ type Style: Default;
+
+ /// 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..271d9a29 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;
@@ -823,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<dyn svg::StyleSheet<Style = Theme>>),
+}
+
+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),