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