Skip to content

Commit

Permalink
feat(painter): 🎸 SVG support switch default color
Browse files Browse the repository at this point in the history
  • Loading branch information
M-Adoo committed Nov 20, 2024
1 parent dd1c4cd commit e35922f
Show file tree
Hide file tree
Showing 22 changed files with 228 additions and 47 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Please only add new entries below the [Unreleased](#unreleased---releasedate) he
- **core**: Added `QueryId` as a replacement for `TypeId` to facilitate querying types by Provider across different binaries. (#656 @M-Adoo)
- **core**: Added `OverrideClass` to override a single class within a subtree. (#657 @M-Adoo)
- **widgets**: Added `LinearProgress` and `SpinnerProgress` widgets along with their respective material themes. (#630 @wjian23 @M-Adoo)
- **painter**: SVG now supports switching the default color, allowing for icon color changes. (#661 @M-Adoo)

### Changed

Expand Down
8 changes: 4 additions & 4 deletions core/src/builtin_widgets/svg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::prelude::*;

impl Render for Svg {
#[inline]
fn perform_layout(&self, clamp: BoxClamp, _: &mut LayoutCtx) -> Size { clamp.clamp(self.size) }
fn perform_layout(&self, clamp: BoxClamp, _: &mut LayoutCtx) -> Size { clamp.clamp(self.size()) }

fn paint(&self, ctx: &mut PaintingCtx) {
let painter = ctx.painter();
Expand All @@ -17,7 +17,7 @@ pub mod named_svgs {

const DEFAULT_SVG_KEY: &str = "__RIRBIR_DEFAULT_SVG__";
static SVGS: LazyLock<Mutex<ahash::AHashMap<&'static str, Svg>>> = LazyLock::new(|| {
let svg = include_crate_svg!("src/builtin_widgets/default_named.svg");
let svg = include_crate_svg!("src/builtin_widgets/default_named.svg", true, false);
let mut set = ahash::AHashMap::new();
set.insert(DEFAULT_SVG_KEY, svg);
Mutex::new(set)
Expand Down Expand Up @@ -51,8 +51,8 @@ mod tests {
fn svgs_smoke() -> Painter {
named_svgs::register(
"test::add",
Svg::parse_from_bytes(
r#"<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M22.5 38V25.5H10v-3h12.5V10h3v12.5H38v3H25.5V38Z"/></svg>"#.as_bytes(),
Svg::parse_from_bytes(r#"<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M22.5 38V25.5H10v-3h12.5V10h3v12.5H38v3H25.5V38Z"/></svg>"#.as_bytes(),
true, false
).unwrap(),
);
let mut painter = Painter::new(Rect::from_size(Size::new(128., 64.)));
Expand Down
4 changes: 2 additions & 2 deletions core/src/builtin_widgets/theme/icon_theme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ macro_rules! define_named_svg {
macro_rules! fill_svgs {
($theme: expr, $($name: path: $path: literal),+) => {
$(
let icon = Resource::new(include_crate_svg!($path));
let icon = Resource::new(include_crate_svg!($path, true, false));
$theme.set_svg($name, icon);
)+
};
Expand All @@ -70,7 +70,7 @@ impl Compose for NamedSvg {

impl IconTheme {
pub fn new(icon_size: IconSize) -> Self {
let svg = include_crate_svg!("src/builtin_widgets/default_named.svg");
let svg = include_crate_svg!("src/builtin_widgets/default_named.svg", true, false);
let miss_icon = Resource::new(svg);
let mut icons = HashMap::<_, _, ahash::RandomState>::default();
icons.insert(MISS_ICON, miss_icon);
Expand Down
11 changes: 7 additions & 4 deletions gpu/src/gpu_backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -759,8 +759,12 @@ mod tests {
painter_backend_eq_image_test!(draw_svg_gradient, comparison = 0.0025);
fn draw_svg_gradient() -> Painter {
let mut painter = painter(Size::new(64., 64.));
let svg =
Svg::parse_from_bytes(include_bytes!("../../tests/assets/fill_with_gradient.svg")).unwrap();
let svg = Svg::parse_from_bytes(
include_bytes!("../../tests/assets/fill_with_gradient.svg"),
true,
false,
)
.unwrap();

painter.draw_svg(&svg);
painter
Expand Down Expand Up @@ -805,8 +809,7 @@ mod tests {
})
.collect();

let svg = Svg { size: Size::new(512., 512.), commands: Resource::new(commands) };
painter.draw_svg(&svg);
painter.draw_bundle_commands(Rect::from_size(Size::new(512., 512.)), Resource::new(commands));
painter
}
painter_backend_eq_image_test!(draw_bundle_svg, comparison = 0.001);
Expand Down
40 changes: 31 additions & 9 deletions macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,10 +248,11 @@ pub fn part_writer(input: TokenStream) -> TokenStream {
/// This macro returns an expression of type `Svg`.
#[proc_macro]
pub fn include_crate_svg(input: TokenStream) -> TokenStream {
let file = parse_macro_input! { input as syn::LitStr };
let IncludeSvgArgs { path, inherit_fill, inherit_stroke } =
parse_macro_input! { input as IncludeSvgArgs };
let dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
let path = std::path::Path::new(&dir).join(file.value());
include_svg_from_path(path)
let path = std::path::Path::new(&dir).join(path);
include_svg_from_path(path, inherit_fill, inherit_stroke)
}

/// Includes an SVG file as an `Svg`.
Expand All @@ -265,22 +266,25 @@ pub fn include_crate_svg(input: TokenStream) -> TokenStream {
#[cfg(feature = "nightly")]
#[proc_macro]
pub fn include_svg(input: TokenStream) -> TokenStream {
let rf = parse_macro_input! { input as syn::LitStr };
let IncludeSvgArgs { path, inherit_fill, inherit_stroke } =
parse_macro_input! { input as IncludeSvgArgs };

let mut span = proc_macro::Span::call_site();
while let Some(p) = span.parent() {
span = p;
}
let mut file = span.source_file().path();
file.pop();
file.push(rf.value());
file.push(path);

include_svg_from_path(file)
include_svg_from_path(file, inherit_fill, inherit_stroke)
}

fn include_svg_from_path(path: std::path::PathBuf) -> TokenStream {
let encoded_bytes =
ribir_painter::Svg::open(path.as_path()).and_then(|reader| reader.serialize());
fn include_svg_from_path(
path: std::path::PathBuf, inherit_fill: bool, inherit_stroke: bool,
) -> TokenStream {
let encoded_bytes = ribir_painter::Svg::open(path.as_path(), inherit_fill, inherit_stroke)
.and_then(|reader| reader.serialize());
match encoded_bytes {
Ok(data) => quote! {
Svg::deserialize(#data).unwrap()
Expand All @@ -292,3 +296,21 @@ fn include_svg_from_path(path: std::path::PathBuf) -> TokenStream {
}
}
}

struct IncludeSvgArgs {
path: String,
inherit_fill: bool,
inherit_stroke: bool,
}

impl syn::parse::Parse for IncludeSvgArgs {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let path = input.parse::<syn::LitStr>()?.value();
input.parse::<syn::Token![,]>()?;
let inherit_fill = input.parse::<syn::LitBool>()?.value;
input.parse::<syn::Token![,]>()?;
let inherit_stroke = input.parse::<syn::LitBool>()?.value;

Ok(IncludeSvgArgs { path, inherit_fill, inherit_stroke })
}
}
4 changes: 2 additions & 2 deletions painter/src/color.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,13 @@ impl Color {
}

#[inline]
pub fn from_u32(rgba: u32) -> Self {
pub const fn from_u32(rgba: u32) -> Self {
let bytes = rgba.to_be_bytes();
Self { red: bytes[0], green: bytes[1], blue: bytes[2], alpha: bytes[3] }
}

#[inline]
pub fn into_u32(self) -> u32 {
pub const fn into_u32(self) -> u32 {
let Self { red, green, blue, alpha } = self;
u32::from_be_bytes([red, green, blue, alpha])
}
Expand Down
22 changes: 14 additions & 8 deletions painter/src/painter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -569,18 +569,19 @@ impl Painter {

pub fn draw_svg(&mut self, svg: &Svg) -> &mut Self {
invisible_return!(self);
let commands = svg.commands(self.fill_brush(), self.stroke_brush());

// For a large number of path commands (more than 16), bundle them
// together as a single resource. This allows the backend to cache
// them collectively.
// For a small number of path commands (less than 16), store them
// individually as multiple resources. This means the backend doesn't
// need to perform a single draw operation for an SVG.
if svg.commands.len() <= 16 {
if commands.len() <= 16 {
let transform = *self.transform();
let alpha = self.alpha();

for cmd in svg.commands.iter() {
for cmd in commands.iter() {
let cmd = match cmd.clone() {
PaintCommand::Path(mut path) => {
path.transform(&transform);
Expand All @@ -600,8 +601,8 @@ impl Painter {
self.commands.push(cmd);
}
} else {
let rect = Rect::from_size(svg.size);
self.draw_bundle_commands(rect, svg.commands.clone());
let rect = Rect::from_size(svg.size());
self.draw_bundle_commands(rect, commands.clone());
}

self
Expand Down Expand Up @@ -662,7 +663,7 @@ impl Painter {
.map(|h| h as f32 / face.units_per_em() as f32)
.unwrap_or(1.)
.max(1.);
let size = svg.size;
let size = svg.size();
let bound_size = bounds.size;
let scale = (bound_size.width / size.width).min(bound_size.height / size.height) / grid_scale;
self
Expand Down Expand Up @@ -984,7 +985,8 @@ mod test {
fn filter_invalid_commands() {
let mut painter = painter();

let svg = Svg::parse_from_bytes(include_bytes!("../../tests/assets/test1.svg")).unwrap();
let svg =
Svg::parse_from_bytes(include_bytes!("../../tests/assets/test1.svg"), true, false).unwrap();
painter
.save()
.set_transform(Transform::translation(f32::NAN, f32::INFINITY))
Expand All @@ -995,8 +997,12 @@ mod test {
#[test]
fn draw_svg_gradient() {
let mut painter = Painter::new(Rect::from_size(Size::new(64., 64.)));
let svg =
Svg::parse_from_bytes(include_bytes!("../../tests/assets/fill_with_gradient.svg")).unwrap();
let svg = Svg::parse_from_bytes(
include_bytes!("../../tests/assets/fill_with_gradient.svg"),
true,
false,
)
.unwrap();

painter.draw_svg(&svg);
}
Expand Down
Loading

0 comments on commit e35922f

Please sign in to comment.