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}