Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-core/src/elements/icon.rs
1use super::{Element, Point};
2use crate::{
3 assets::asset_cache::{AssetCache, AssetSource, AssetState},
4 event::DispatchedEvent,
5 image_cache::{AnimatedImageBehavior, CacheOption, FitType, Image, ImageCache},
6 AfterLayoutContext, AppContext, EventContext, LayoutContext, PaintContext, SingletonEntity,
7 SizeConstraint,
8};
9use pathfinder_color::ColorU;
10use pathfinder_geometry::rect::RectF;
11use pathfinder_geometry::vector::Vector2F;
12 
13/// An element that renders a monochrome icon. This differs from `Svg` in that it sets the color dynamically
14/// instead of statically from the SVG itself.
15#[derive(Clone, Copy)]
16pub struct Icon {
17 path: &'static str,
18 opacity: f32,
19 size: Option<Vector2F>,
20 origin: Option<Point>,
21 color: ColorU,
22 #[cfg(debug_assertions)]
23 /// Captures the location of the constructor call site. This is used for debugging purposes.
24 constructor_location: Option<&'static std::panic::Location<'static>>,
25}
26 
27impl Icon {
28 #[cfg_attr(debug_assertions, track_caller)]
29 pub fn new(path: &'static str, color: impl Into<ColorU>) -> Self {
30 Self {
31 path,
32 opacity: 1.,
33 size: None,
34 color: color.into(),
35 origin: None,
36 #[cfg(debug_assertions)]
37 constructor_location: Some(std::panic::Location::caller()),
38 }
39 }
40 pub fn with_opacity(mut self, opacity: f32) -> Self {
41 self.opacity = opacity;
42 self
43 }
44 pub fn with_color(mut self, color: impl Into<ColorU>) -> Self {
45 self.color = color.into();
46 self
47 }
48}
49 
50impl Element for Icon {
51 fn layout(
52 &mut self,
53 constraint: SizeConstraint,
54 _: &mut LayoutContext,
55 _: &AppContext,
56 ) -> Vector2F {
57 let size = constraint.max;
58 self.size = Some(size);
59 size
60 }
61 
62 fn after_layout(&mut self, _: &mut AfterLayoutContext, _: &AppContext) {}
63 
64 fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, app: &AppContext) {
65 let bounds = (self.size.unwrap() * ctx.scene.scale_factor()).to_i32();
66 
67 // If the x or y bounds are less than or equal to 0, don't attempt to paint the icon.
68 if bounds.x() <= 0 || bounds.y() <= 0 {
69 return;
70 }
71 
72 let asset_cache = AssetCache::as_ref(app);
73 match ImageCache::as_ref(app).image(
74 // Right now, the location of SVG files is hard-coded to be the app bundle. In the future,
75 // to make icons a fetch-able asset, we should modify the API of Icon to accept an AssetSource,
76 // exactly how Image does.
77 AssetSource::Bundled { path: self.path },
78 bounds,
79 FitType::Contain,
80 AnimatedImageBehavior::FullAnimation,
81 CacheOption::BySize,
82 ctx.max_texture_dimension_2d,
83 asset_cache,
84 ) {
85 AssetState::Loaded { data } => match data.as_ref() {
86 Image::Static(image) => {
87 let logical_image_size = image.size().to_f32() / ctx.scene.scale_factor();
88 let origin = origin + ((self.size().unwrap() - logical_image_size) / 2.0);
89 self.origin = Some(Point::from_vec2f(origin, ctx.scene.z_index()));
90 
91 #[cfg(debug_assertions)]
92 ctx.scene
93 .set_location_for_panic_logging(self.constructor_location);
94 
95 ctx.scene.draw_icon(
96 RectF::new(origin, logical_image_size),
97 image.clone(),
98 self.opacity,
99 self.color,
100 );
101 }
102 Image::Animated(_image) => {
103 log::info!("Animated icons are currently not supported");
104 }
105 },
106 AssetState::Loading { handle } => {
107 ctx.repaint_after_load(handle);
108 }
109 AssetState::Evicted => {
110 log::warn!("Unable to render svg because it was evicted");
111 }
112 AssetState::FailedToLoad(err) => {
113 log::warn!("Unable to render svg: {err:#}");
114 }
115 }
116 }
117 
118 fn size(&self) -> Option<Vector2F> {
119 self.size
120 }
121 
122 fn dispatch_event(
123 &mut self,
124 _: &DispatchedEvent,
125 _: &mut EventContext,
126 _: &AppContext,
127 ) -> bool {
128 false
129 }
130 
131 fn origin(&self) -> Option<Point> {
132 self.origin
133 }
134}
135