tessera_components/
icon.rs1use std::sync::Arc;
7
8use tessera_ui::{
9 AxisConstraint, Color, ComputedData, Dp, LayoutResult, MeasurementError, Px,
10 layout::{MeasureScope, layout},
11 tessera, use_context,
12};
13
14use crate::{
15 image::clamp_f32_to_px,
16 image_vector::TintMode,
17 painter::Painter,
18 pipelines::{
19 image::command::{ImageCommand, ImageData},
20 image_vector::command::ImageVectorCommand,
21 },
22 theme::{ContentColor, MaterialTheme},
23};
24
25#[derive(Clone, Copy, Debug, PartialEq)]
26pub(crate) struct IconSize(pub Dp);
27
28#[derive(Clone, Copy, Debug, PartialEq)]
29pub(crate) struct IconRotation(pub f32);
30
31struct PlaceholderRaster(Arc<ImageData>);
32
33impl Default for PlaceholderRaster {
34 fn default() -> Self {
35 Self(Arc::new(ImageData::from_rgba(vec![0, 0, 0, 0], 1, 1)))
36 }
37}
38
39fn placeholder_raster_data() -> Arc<ImageData> {
40 Arc::clone(&tessera_ui::__private::runtime_resource::<PlaceholderRaster>().0)
41}
42
43impl IconBuilder {
44 pub fn painter(&self, painter: impl Into<Painter>) -> Self {
46 let value = Some(painter.into());
47 self.props
48 .configure("content", move |props| props.content = value);
49 Self { props: self.props }
50 }
51}
52
53#[derive(Clone, PartialEq)]
54struct IconLayout {
55 content: Painter,
56 size: Dp,
57 width: Option<AxisConstraint>,
58 height: Option<AxisConstraint>,
59 tint: Color,
60 tint_mode: TintMode,
61 rotation: f32,
62}
63
64impl tessera_ui::LayoutModifierNode for IconLayout {
65 fn measure_eq(&self, other: &Self) -> bool {
66 self.size == other.size
67 && self.width == other.width
68 && self.height == other.height
69 && (self.width.is_none() && self.height.is_none()
70 || intrinsic_dimensions(&self.content) == intrinsic_dimensions(&other.content))
71 }
72
73 fn placement_eq(&self, other: &Self) -> bool {
74 self.measure_eq(other)
75 }
76
77 fn measure(&self, input: &mut MeasureScope<'_>) -> Result<LayoutResult, MeasurementError> {
78 let (intrinsic_width, intrinsic_height) = intrinsic_dimensions(&self.content);
79 let size_px = self.size.to_px();
80
81 let preferred_width = self.width.unwrap_or(AxisConstraint::exact(size_px));
82 let preferred_height = self.height.unwrap_or(AxisConstraint::exact(size_px));
83 let width = input
84 .parent_constraint()
85 .width()
86 .clamp(preferred_width.clamp(intrinsic_width));
87 let height = input
88 .parent_constraint()
89 .height()
90 .clamp(preferred_height.clamp(intrinsic_height));
91
92 Ok(LayoutResult::new(ComputedData { width, height }))
93 }
94}
95
96impl tessera_ui::DrawModifierNode for IconLayout {
97 fn draw(
98 &self,
99 ctx: &mut tessera_ui::DrawModifierContext<'_, '_>,
100 content: &mut dyn tessera_ui::DrawModifierContent,
101 ) {
102 let input = &mut *ctx.render_input;
103 let mut metadata = input.metadata_mut();
104 match &self.content {
105 Painter::Source(_) => {}
106 Painter::Vector(data) => {
107 let command = ImageVectorCommand {
108 data: data.clone(),
109 tint: self.tint,
110 tint_mode: self.tint_mode,
111 rotation: self.rotation,
112 };
113 metadata.fragment_mut().push_draw_command(command);
114 }
115 Painter::Raster(data) => {
116 let command = ImageCommand {
117 data: data.clone(),
118 opacity: 1.0,
119 };
120 metadata.fragment_mut().push_draw_command(command);
121 }
122 }
123 content.draw(input);
124 }
125}
126
127#[tessera]
161pub fn icon(
162 #[prop(skip_setter)] content: Option<Painter>,
163 size: Option<Dp>,
164 width: Option<AxisConstraint>,
165 height: Option<AxisConstraint>,
166 tint: Option<Color>,
167 tint_mode: Option<TintMode>,
168 rotation: Option<f32>,
169) {
170 let content = content
171 .and_then(Painter::resolve)
172 .unwrap_or_else(|| Painter::Raster((*placeholder_raster_data()).clone()));
173 let default_size = use_context::<IconSize>()
174 .map(|size| size.get().0)
175 .unwrap_or(Dp(24.0));
176 let size = size.unwrap_or(default_size);
177 let tint = if let Some(tint) = tint {
178 tint
179 } else {
180 let theme = use_context::<MaterialTheme>();
181 use_context::<ContentColor>()
182 .map(|c| c.get().current)
183 .or_else(|| theme.map(|t| t.get().color_scheme.on_surface))
184 .unwrap_or_else(|| ContentColor::default().current)
185 };
186 let tint_mode = tint_mode.unwrap_or(TintMode::Solid);
187 let inherited_rotation = use_context::<IconRotation>()
188 .map(|value| value.get().0)
189 .unwrap_or(0.0);
190 let rotation = rotation.unwrap_or(inherited_rotation);
191 let policy = IconLayout {
192 content,
193 size,
194 width,
195 height,
196 tint,
197 tint_mode,
198 rotation,
199 };
200 layout().policy(policy.clone()).draw(policy);
201}
202
203fn intrinsic_dimensions(content: &Painter) -> (Px, Px) {
204 match content {
205 Painter::Source(_) => (Px::ZERO, Px::ZERO),
206 Painter::Vector(data) => (
207 clamp_f32_to_px(data.viewport_width),
208 clamp_f32_to_px(data.viewport_height),
209 ),
210 Painter::Raster(data) => (clamp_u32_to_px(data.width), clamp_u32_to_px(data.height)),
211 }
212}
213
214fn clamp_u32_to_px(value: u32) -> Px {
215 Px::new(value.min(i32::MAX as u32) as i32)
216}