tessera_ui_basic_components/text.rs
1use derive_builder::Builder;
2use tessera_ui::{Color, ComputedData, DimensionValue, Dp, Px};
3use tessera_ui_macros::tessera;
4
5use crate::pipelines::{TextCommand, TextConstraint, TextData};
6
7/// Configuration arguments for the `text` component.
8///
9/// `TextArgs` defines the visual properties and content for rendering text in the Tessera UI framework.
10/// It uses the builder pattern for convenient construction and provides sensible defaults for all styling properties.
11///
12/// # Fields
13///
14/// - `text`: The string content to be displayed
15/// - `color`: Text color (defaults to black)
16/// - `size`: Font size in density-independent pixels (defaults to 25.0 dp)
17/// - `line_height`: Optional line height override (defaults to 1.2 × font size)
18///
19/// # Builder Pattern
20///
21/// This struct uses the `derive_builder` crate to provide a fluent builder API. All fields except `text`
22/// have sensible defaults, making it easy to create text with minimal configuration.
23///
24/// # Examples
25///
26/// ## Basic text with defaults
27/// ```
28/// use tessera_ui_basic_components::text::{TextArgs, TextArgsBuilder};
29///
30/// let args = TextArgsBuilder::default()
31/// .text("Hello, World!".to_string())
32/// .build()
33/// .unwrap();
34/// // Uses: black color, 25.0 dp size, 30.0 dp line height (1.2 × size)
35/// ```
36///
37/// ## Customized text styling
38/// ```
39/// use tessera_ui_basic_components::text::{TextArgs, TextArgsBuilder};
40/// use tessera_ui::{Color, Dp};
41///
42/// let args = TextArgsBuilder::default()
43/// .text("Styled Text".to_string())
44/// .color(Color::from_rgb(0.2, 0.4, 0.8)) // Blue color
45/// .size(Dp(32.0)) // Larger font
46/// .line_height(Dp(40.0)) // Custom line height
47/// .build()
48/// .unwrap();
49/// ```
50///
51/// ## Using automatic line height calculation
52/// ```
53/// use tessera_ui_basic_components::text::{TextArgs, TextArgsBuilder};
54/// use tessera_ui::Dp;
55///
56/// let args = TextArgsBuilder::default()
57/// .text("Auto Line Height".to_string())
58/// .size(Dp(50.0))
59/// // line_height will automatically be Dp(60.0) (1.2 × 50.0)
60/// .build()
61/// .unwrap();
62/// ```
63///
64/// ## Converting from string types
65/// ```
66/// use tessera_ui_basic_components::text::TextArgs;
67///
68/// // From String
69/// let args1: TextArgs = "Hello".to_string().into();
70///
71/// // From &str
72/// let args2: TextArgs = "World".into();
73/// ```
74#[derive(Debug, Default, Builder, Clone)]
75#[builder(pattern = "owned")]
76pub struct TextArgs {
77 /// The text content to be rendered.
78 ///
79 /// This is the actual string that will be displayed on screen. It can contain
80 /// Unicode characters and will be rendered using the specified font properties.
81 pub text: String,
82
83 /// The color of the text.
84 ///
85 /// Defaults to `Color::BLACK` if not specified. The color is applied uniformly
86 /// to all characters in the text string.
87 #[builder(default = "Color::BLACK")]
88 pub color: Color,
89
90 /// The font size in density-independent pixels (dp).
91 ///
92 /// Defaults to `Dp(25.0)` if not specified. This size is automatically scaled
93 /// based on the device's pixel density to ensure consistent visual appearance
94 /// across different screen densities.
95 #[builder(default = "Dp(25.0)")]
96 pub size: Dp,
97
98 /// Optional override for line height in density-independent pixels (dp).
99 ///
100 /// If not specified (None), the line height will automatically be calculated as
101 /// 1.2 times the font size, which provides good readability for most text.
102 ///
103 /// Set this to a specific value if you need precise control over line spacing,
104 /// such as for dense layouts or specific design requirements.
105 ///
106 /// # Example
107 /// ```
108 /// use tessera_ui_basic_components::text::TextArgsBuilder;
109 /// use tessera_ui::Dp;
110 ///
111 /// // Automatic line height (1.2 × size)
112 /// let auto = TextArgsBuilder::default()
113 /// .text("Auto spacing".to_string())
114 /// .size(Dp(20.0)) // line_height will be Dp(24.0)
115 /// .build()
116 /// .unwrap();
117 ///
118 /// // Custom line height
119 /// let custom = TextArgsBuilder::default()
120 /// .text("Custom spacing".to_string())
121 /// .size(Dp(20.0))
122 /// .line_height(Dp(30.0)) // Explicit line height
123 /// .build()
124 /// .unwrap();
125 /// ```
126 #[builder(default, setter(strip_option))]
127 pub line_height: Option<Dp>,
128}
129
130impl From<String> for TextArgs {
131 fn from(val: String) -> Self {
132 TextArgsBuilder::default().text(val).build().unwrap()
133 }
134}
135
136impl From<&str> for TextArgs {
137 fn from(val: &str) -> Self {
138 TextArgsBuilder::default()
139 .text(val.to_string())
140 .build()
141 .unwrap()
142 }
143}
144
145/// Basic text component.
146///
147/// # Example
148/// ```
149/// use tessera_ui_basic_components::text::{text, TextArgs, TextArgsBuilder};
150/// use tessera_ui::Dp;
151/// // a simple hello world text, in black
152/// let args = TextArgsBuilder::default()
153/// .text("Hello, World!".to_string())
154/// .size(Dp(50.0)) // Example using Dp
155/// // line_height will be Dp(60.0) (1.2 * size) by default
156/// .build()
157/// .unwrap();
158/// text(args);
159/// ```
160#[tessera]
161pub fn text(args: impl Into<TextArgs>) {
162 let text_args: TextArgs = args.into();
163 measure(Box::new(move |input| {
164 let max_width: Option<Px> = match input.parent_constraint.width {
165 DimensionValue::Fixed(w) => Some(w),
166 DimensionValue::Wrap { max, .. } => max, // Use max from Wrap
167 DimensionValue::Fill { max, .. } => max, // Use max from Fill
168 };
169
170 let max_height: Option<Px> = match input.parent_constraint.height {
171 DimensionValue::Fixed(h) => Some(h),
172 DimensionValue::Wrap { max, .. } => max, // Use max from Wrap
173 DimensionValue::Fill { max, .. } => max, // Use max from Fill
174 };
175
176 let line_height = text_args.line_height.unwrap_or(Dp(text_args.size.0 * 1.2));
177
178 let text_data = TextData::new(
179 text_args.text.clone(),
180 text_args.color,
181 text_args.size.to_pixels_f32(),
182 line_height.to_pixels_f32(),
183 TextConstraint {
184 max_width: max_width.map(|px| px.to_f32()),
185 max_height: max_height.map(|px| px.to_f32()),
186 },
187 );
188
189 let size = text_data.size;
190 let drawable = TextCommand { data: text_data };
191
192 // Use the new unified command system to add the text rendering command
193 if let Some(mut metadata) = input.metadatas.get_mut(&input.current_node_id) {
194 metadata.push_draw_command(drawable);
195 }
196
197 Ok(ComputedData {
198 width: size[0].into(),
199 height: size[1].into(),
200 })
201 }));
202}