tessera_ui_basic_components/
icon.rs1use std::sync::Arc;
7
8use derive_builder::Builder;
9use tessera_ui::{Color, ComputedData, Constraint, DimensionValue, Dp, Px, tessera};
10
11use crate::{
12 image_vector::TintMode,
13 pipelines::{
14 image::command::{ImageCommand, ImageData},
15 image_vector::command::{ImageVectorCommand, ImageVectorData},
16 },
17};
18
19#[derive(Debug, Clone)]
21pub enum IconContent {
22 Vector(Arc<ImageVectorData>),
24 Raster(Arc<ImageData>),
26}
27
28impl From<ImageVectorData> for IconContent {
29 fn from(data: ImageVectorData) -> Self {
30 Self::Vector(Arc::new(data))
31 }
32}
33
34impl From<Arc<ImageVectorData>> for IconContent {
35 fn from(data: Arc<ImageVectorData>) -> Self {
36 Self::Vector(data)
37 }
38}
39
40impl From<ImageData> for IconContent {
41 fn from(data: ImageData) -> Self {
42 Self::Raster(Arc::new(data))
43 }
44}
45
46impl From<Arc<ImageData>> for IconContent {
47 fn from(data: Arc<ImageData>) -> Self {
48 Self::Raster(data)
49 }
50}
51
52#[derive(Debug, Builder, Clone)]
54#[builder(pattern = "owned")]
55pub struct IconArgs {
56 #[builder(setter(into))]
58 pub content: IconContent,
59 #[builder(default = "Dp(24.0)")]
62 pub size: Dp,
63 #[builder(default, setter(strip_option))]
66 pub width: Option<DimensionValue>,
67 #[builder(default, setter(strip_option))]
70 pub height: Option<DimensionValue>,
71 #[builder(default = "Color::WHITE")]
74 pub tint: Color,
75 #[builder(default)]
77 pub tint_mode: TintMode,
78 #[builder(default = "0.0")]
80 pub rotation: f32,
81}
82
83impl From<IconContent> for IconArgs {
84 fn from(content: IconContent) -> Self {
85 IconArgsBuilder::default()
86 .content(content)
87 .build()
88 .expect("IconArgsBuilder failed with required fields set")
89 }
90}
91
92impl From<ImageVectorData> for IconArgs {
93 fn from(data: ImageVectorData) -> Self {
94 IconContent::from(data).into()
95 }
96}
97
98impl From<Arc<ImageVectorData>> for IconArgs {
99 fn from(data: Arc<ImageVectorData>) -> Self {
100 IconContent::from(data).into()
101 }
102}
103
104impl From<ImageData> for IconArgs {
105 fn from(data: ImageData) -> Self {
106 IconContent::from(data).into()
107 }
108}
109
110impl From<Arc<ImageData>> for IconArgs {
111 fn from(data: Arc<ImageData>) -> Self {
112 IconContent::from(data).into()
113 }
114}
115
116#[tessera]
154pub fn icon(args: impl Into<IconArgs>) {
155 let icon_args: IconArgs = args.into();
156
157 measure(Box::new(move |input| {
158 let (intrinsic_width, intrinsic_height) = intrinsic_dimensions(&icon_args.content);
159 let size_px = icon_args.size.to_px();
160
161 let preferred_width = icon_args.width.unwrap_or(DimensionValue::Fixed(size_px));
162 let preferred_height = icon_args.height.unwrap_or(DimensionValue::Fixed(size_px));
163
164 let constraint = Constraint::new(preferred_width, preferred_height);
165 let effective_constraint = constraint.merge(input.parent_constraint);
166
167 let width = match effective_constraint.width {
168 DimensionValue::Fixed(value) => value,
169 DimensionValue::Wrap { min, max } => min
170 .unwrap_or(Px(0))
171 .max(intrinsic_width)
172 .min(max.unwrap_or(Px::MAX)),
173 DimensionValue::Fill { min, max } => {
174 let parent_max = input.parent_constraint.width.get_max().unwrap_or(Px::MAX);
175 max.unwrap_or(parent_max)
176 .max(min.unwrap_or(Px(0)))
177 .max(intrinsic_width)
178 }
179 };
180
181 let height = match effective_constraint.height {
182 DimensionValue::Fixed(value) => value,
183 DimensionValue::Wrap { min, max } => min
184 .unwrap_or(Px(0))
185 .max(intrinsic_height)
186 .min(max.unwrap_or(Px::MAX)),
187 DimensionValue::Fill { min, max } => {
188 let parent_max = input.parent_constraint.height.get_max().unwrap_or(Px::MAX);
189 max.unwrap_or(parent_max)
190 .max(min.unwrap_or(Px(0)))
191 .max(intrinsic_height)
192 }
193 };
194
195 match &icon_args.content {
196 IconContent::Vector(data) => {
197 let command = ImageVectorCommand {
198 data: data.clone(),
199 tint: icon_args.tint,
200 tint_mode: icon_args.tint_mode,
201 rotation: icon_args.rotation,
202 };
203 input
204 .metadatas
205 .entry(input.current_node_id)
206 .or_default()
207 .push_draw_command(command);
208 }
209 IconContent::Raster(data) => {
210 let command = ImageCommand { data: data.clone() };
211 input
212 .metadatas
213 .entry(input.current_node_id)
214 .or_default()
215 .push_draw_command(command);
216 }
217 }
218
219 Ok(ComputedData { width, height })
220 }));
221}
222
223fn intrinsic_dimensions(content: &IconContent) -> (Px, Px) {
224 match content {
225 IconContent::Vector(data) => (
226 px_from_f32(data.viewport_width),
227 px_from_f32(data.viewport_height),
228 ),
229 IconContent::Raster(data) => (clamp_u32_to_px(data.width), clamp_u32_to_px(data.height)),
230 }
231}
232
233fn px_from_f32(value: f32) -> Px {
234 let clamped = value.max(0.0).min(i32::MAX as f32);
235 Px(clamped.round() as i32)
236}
237
238fn clamp_u32_to_px(value: u32) -> Px {
239 Px::new(value.min(i32::MAX as u32) as i32)
240}