tessera_ui_basic_components/
text.rs

1//! A component for rendering single-style text.
2//!
3//! ## Usage
4//!
5//! Use to display labels, headings, or other static or dynamic text content.
6use derive_builder::Builder;
7use tessera_ui::{Color, ComputedData, DimensionValue, Dp, Px, accesskit::Role, tessera};
8
9use crate::pipelines::{TextCommand, TextConstraint, TextData};
10
11/// Configuration arguments for the `text` component.
12#[derive(Debug, Default, Builder, Clone)]
13#[builder(pattern = "owned")]
14pub struct TextArgs {
15    /// The text content to be rendered.
16    #[builder(setter(into))]
17    pub text: String,
18
19    /// The color of the text.
20    #[builder(default = "Color::BLACK")]
21    pub color: Color,
22
23    /// The font size in density-independent pixels (dp).
24    #[builder(default = "Dp(25.0)")]
25    pub size: Dp,
26
27    /// Optional override for line height in density-independent pixels (dp).
28    #[builder(default, setter(strip_option))]
29    pub line_height: Option<Dp>,
30
31    /// Optional label announced by assistive technologies. Defaults to the text content.
32    #[builder(default, setter(strip_option, into))]
33    pub accessibility_label: Option<String>,
34
35    /// Optional description announced by assistive technologies.
36    #[builder(default, setter(strip_option, into))]
37    pub accessibility_description: Option<String>,
38}
39
40impl From<String> for TextArgs {
41    fn from(val: String) -> Self {
42        TextArgsBuilder::default().text(val).build().unwrap()
43    }
44}
45
46impl From<&str> for TextArgs {
47    fn from(val: &str) -> Self {
48        TextArgsBuilder::default()
49            .text(val.to_string())
50            .build()
51            .unwrap()
52    }
53}
54
55/// # text
56///
57/// Renders a block of text with a single, uniform style.
58///
59/// ## Usage
60///
61/// Display simple text content. For more complex styling or editing, see other components.
62///
63/// ## Parameters
64///
65/// - `args` — configures the text content and styling; see [`TextArgs`]. Can be converted from a `String` or `&str`.
66///
67/// ## Examples
68///
69/// ```
70/// use tessera_ui_basic_components::text::{text, TextArgsBuilder};
71/// use tessera_ui::{Color, Dp};
72///
73/// // Simple text from a string literal
74/// text("Hello, world!");
75///
76/// // Styled text using the builder
77/// text(
78///     TextArgsBuilder::default()
79///         .text("Styled Text")
80///         .color(Color::new(0.2, 0.5, 0.8, 1.0))
81///         .size(Dp(32.0))
82///         .build()
83///         .unwrap(),
84/// );
85/// ```
86#[tessera]
87pub fn text(args: impl Into<TextArgs>) {
88    let text_args: TextArgs = args.into();
89    let accessibility_label = text_args.accessibility_label.clone();
90    let accessibility_description = text_args.accessibility_description.clone();
91    let text_for_accessibility = text_args.text.clone();
92
93    input_handler(Box::new(move |input| {
94        let mut builder = input.accessibility().role(Role::Label);
95
96        if let Some(label) = accessibility_label.as_ref() {
97            builder = builder.label(label.clone());
98        } else if !text_for_accessibility.is_empty() {
99            builder = builder.label(text_for_accessibility.clone());
100        }
101
102        if let Some(description) = accessibility_description.as_ref() {
103            builder = builder.description(description.clone());
104        }
105
106        builder.commit();
107    }));
108    measure(Box::new(move |input| {
109        let max_width: Option<Px> = match input.parent_constraint.width {
110            DimensionValue::Fixed(w) => Some(w),
111            DimensionValue::Wrap { max, .. } => max, // Use max from Wrap
112            DimensionValue::Fill { max, .. } => max, // Use max from Fill
113        };
114
115        let max_height: Option<Px> = match input.parent_constraint.height {
116            DimensionValue::Fixed(h) => Some(h),
117            DimensionValue::Wrap { max, .. } => max, // Use max from Wrap
118            DimensionValue::Fill { max, .. } => max, // Use max from Fill
119        };
120
121        let line_height = text_args.line_height.unwrap_or(Dp(text_args.size.0 * 1.2));
122
123        let text_data = TextData::new(
124            text_args.text.clone(),
125            text_args.color,
126            text_args.size.to_pixels_f32(),
127            line_height.to_pixels_f32(),
128            TextConstraint {
129                max_width: max_width.map(|px| px.to_f32()),
130                max_height: max_height.map(|px| px.to_f32()),
131            },
132        );
133
134        let size = text_data.size;
135        let drawable = TextCommand { data: text_data };
136
137        // Use the new unified command system to add the text rendering command
138        input.metadata_mut().push_draw_command(drawable);
139
140        Ok(ComputedData {
141            width: size[0].into(),
142            height: size[1].into(),
143        })
144    }));
145}