1use sequoia_assets::{AssetRegistry, icon_key};
2use sequoia_core::{Color, Edges, Length, Rect, Size};
3use sequoia_layout::{Indents, LayoutCtx, LayoutStyle};
4
5use crate::view::{PaintCtx, View};
6use crate::views::Unit;
7
8const DEFAULT_SIZE: f32 = 16.0;
9
10pub struct Icon {
11 name: String,
12 size: Option<f32>,
13 color: Option<Color>,
14 style: LayoutStyle,
15}
16
17impl Icon {
18 pub fn new(name: impl Into<String>) -> Self {
19 Self {
20 name: name.into(),
21 size: None,
22 color: None,
23 style: LayoutStyle {
24 width: Length::Fit,
25 height: Length::Fit,
26 ..LayoutStyle::default()
27 },
28 }
29 }
30
31 pub fn size(mut self, px: f32) -> Self {
32 self.size = Some(px);
33 self
34 }
35
36 pub fn color(mut self, color: Color) -> Self {
37 self.color = Some(color);
38 self
39 }
40
41 pub fn width(mut self, width: impl Into<Length>) -> Self {
42 self.style = self.style.width(width.into());
43 self
44 }
45
46 pub fn height(mut self, height: impl Into<Length>) -> Self {
47 self.style = self.style.height(height.into());
48 self
49 }
50
51 pub fn min_width(mut self, v: f32) -> Self {
52 self.style = self.style.min_width(v);
53 self
54 }
55
56 pub fn max_width(mut self, v: f32) -> Self {
57 self.style = self.style.max_width(v);
58 self
59 }
60
61 pub fn min_height(mut self, v: f32) -> Self {
62 self.style = self.style.min_height(v);
63 self
64 }
65
66 pub fn max_height(mut self, v: f32) -> Self {
67 self.style = self.style.max_height(v);
68 self
69 }
70
71 pub fn flex(mut self, weight: f32) -> Self {
72 self.style = self.style.flex(weight);
73 self
74 }
75
76 pub fn expand(self) -> Self {
77 self.flex(1.0)
78 }
79
80 pub fn fill_width(mut self) -> Self {
81 self.style = self.style.width(Length::Fill(1.0));
82 self
83 }
84
85 pub fn fill_height(mut self) -> Self {
86 self.style = self.style.height(Length::Fill(1.0));
87 self
88 }
89
90 pub fn fill_size(self) -> Self {
91 self.fill_width().fill_height()
92 }
93
94 pub fn padding(mut self, padding: impl Into<Edges>) -> Self {
95 self.style = self.style.padding(padding);
96 self
97 }
98
99 pub fn padding_xy(mut self, x: f32, y: f32) -> Self {
100 self.style = self.style.padding_xy(x, y);
101 self
102 }
103
104 pub fn padding_x(mut self, x: f32) -> Self {
105 self.style = self.style.padding_x(x);
106 self
107 }
108
109 pub fn padding_y(mut self, y: f32) -> Self {
110 self.style = self.style.padding_y(y);
111 self
112 }
113
114 pub fn margin(mut self, margin: impl Into<Edges>) -> Self {
115 self.style = self.style.margin(margin);
116 self
117 }
118
119 pub fn margin_xy(mut self, x: f32, y: f32) -> Self {
120 self.style = self.style.margin_xy(x, y);
121 self
122 }
123
124 pub fn margin_x(mut self, x: f32) -> Self {
125 self.style = self.style.margin_x(x);
126 self
127 }
128
129 pub fn margin_y(mut self, y: f32) -> Self {
130 self.style = self.style.margin_y(y);
131 self
132 }
133
134 pub fn margin_left(mut self, v: f32) -> Self {
135 self.style = self.style.margin_left(v);
136 self
137 }
138
139 pub fn margin_right(mut self, v: f32) -> Self {
140 self.style = self.style.margin_right(v);
141 self
142 }
143
144 pub fn margin_top(mut self, v: f32) -> Self {
145 self.style = self.style.margin_top(v);
146 self
147 }
148
149 pub fn margin_bottom(mut self, v: f32) -> Self {
150 self.style = self.style.margin_bottom(v);
151 self
152 }
153
154 pub fn indent_top(mut self, v: f32) -> Self {
155 self.style = self.style.indent_top(v);
156 self
157 }
158
159 pub fn indent_bottom(mut self, v: f32) -> Self {
160 self.style = self.style.indent_bottom(v);
161 self
162 }
163
164 pub fn indent_left(mut self, v: f32) -> Self {
165 self.style = self.style.indent_left(v);
166 self
167 }
168
169 pub fn indent_right(mut self, v: f32) -> Self {
170 self.style = self.style.indent_right(v);
171 self
172 }
173
174 pub fn clear_indents(mut self) -> Self {
175 self.style = self.style.clear_indents();
176 self
177 }
178
179 pub fn indent(mut self, indents: Indents) -> Self {
180 self.style = self.style.indent(indents);
181 self
182 }
183
184 pub fn indent_all(mut self, v: f32) -> Self {
185 self.style = self.style.indent_all(v);
186 self
187 }
188
189 pub fn indent_xy(mut self, x: f32, y: f32) -> Self {
190 self.style = self.style.indent_xy(x, y);
191 self
192 }
193
194 pub fn indent_axes(mut self, horizontal: f32, vertical: f32) -> Self {
195 self.style = self.style.indent_axes(horizontal, vertical);
196 self
197 }
198
199 pub fn hit_testable(mut self, on: bool) -> Self {
200 self.style = self.style.hit_testable(on);
201 self
202 }
203
204 fn px(&self) -> f32 {
205 self.size.unwrap_or(DEFAULT_SIZE)
206 }
207}
208
209impl View for Icon {
210 fn body(&self) -> impl View + 'static {
211 Unit
212 }
213 fn is_leaf(&self) -> bool {
214 true
215 }
216 fn layout_style(&self) -> LayoutStyle {
217 self.style
218 }
219 fn intrinsic_size(&self, _ctx: &mut LayoutCtx) -> Option<Size> {
220 let px = self.px();
221 let pad = self.style.padding;
222 Some(Size::new(
223 px + pad.horizontal_sum(),
224 px + pad.vertical_sum(),
225 ))
226 }
227 fn paint_self(&self, ctx: &mut PaintCtx) {
228 let Some(assets) = ctx.environment.get::<AssetRegistry>() else {
229 return;
230 };
231 let px = self.px();
232
233 let raster_px = ((px * ctx.text.scale_factor()).round() as u32).max(1);
234 let Ok(sprite) = assets.rasterize_icon(&self.name, raster_px) else {
235 return;
236 };
237 let pad = self.style.padding;
238 let r = Rect::from_xywh(
239 ctx.rect.left() + pad.left,
240 ctx.rect.top() + pad.top,
241 (ctx.rect.size.width - pad.horizontal_sum()).max(0.0),
242 (ctx.rect.size.height - pad.vertical_sum()).max(0.0),
243 );
244 let dst = Rect::from_xywh(
245 r.left() + (r.size.width - px) * 0.5,
246 r.top() + (r.size.height - px) * 0.5,
247 px,
248 px,
249 );
250 let color = self.color.unwrap_or_else(|| ctx.theme().text);
251 ctx.draw_sprite(
252 icon_key(&self.name, raster_px),
253 sprite.width,
254 sprite.height,
255 &sprite.alpha,
256 dst,
257 color,
258 );
259 }
260}