aura-gui 0.2.2

UI nativa acelerada por GPU para escritorio, Android e iOS, en un solo crate: estilo tipo Tailwind, componentes, sombras, animaciones y agentes de IA. Reposo a cero. Licencia AGPL-3.0 o comercial.
//! Texto con ajuste de lĂ­nea y texto enriquecido (varios estilos en una cadena).

use crate::gpu::TextRun;
use crate::view::accessibility::AccessNode;
use crate::view::context::ElementContext;
use crate::view::element::Element;
use crate::view::style::Style;
use crate::view::styled::Styled;
use crate::view::text_layout::TextLeaf;
use std::rc::Rc;
use taffy::NodeId;

pub struct Text {
    content: Rc<str>,
    runs: Rc<[TextRun]>,
    style: Style,
    monospace: bool,
    node: Option<NodeId>,
    leaf: Option<TextLeaf>,
}

pub fn text(content: impl Into<String>) -> Text {
    Text::new(content)
}

/// Texto con tramos de estilo propios: `styled_text("fn main", vec![TextRun { length: 2, bold: true, ..}])`.
pub fn styled_text(content: impl Into<String>, runs: Vec<TextRun>) -> Text {
    let mut text = Text::new(content);
    text.runs = Rc::from(runs);
    text
}

impl Text {
    pub fn new(content: impl Into<String>) -> Self {
        let content: String = content.into();
        Self {
            content: Rc::from(content),
            runs: Rc::from([]),
            style: Style::default(),
            monospace: false,
            node: None,
            leaf: None,
        }
    }

    pub fn monospace(mut self) -> Self {
        self.monospace = true;
        self
    }
}

impl Styled for Text {
    fn style(&mut self) -> &mut Style {
        &mut self.style
    }
}

impl Element for Text {
    fn request_layout(&mut self, cx: &mut ElementContext) -> NodeId {
        let own_style = self.style.clone();
        let leaf = cx.with_text_style(&own_style, |cx| {
            let inherited = cx.text_style();
            let decorated = inherited.bold || inherited.italic || inherited.underline || inherited.strike;
            let runs = if self.runs.is_empty() && decorated {
                Rc::from([TextRun {
                    length: self.content.len(),
                    color: None,
                    bold: inherited.bold,
                    italic: inherited.italic,
                    underline: inherited.underline,
                    strikethrough: inherited.strike,
                    ..TextRun::default()
                }])
            } else {
                self.runs.clone()
            };
            TextLeaf {
                content: self.content.clone(),
                runs,
                font_size: inherited.size.0,
                line_height: inherited.line_height(),
                monospace: self.monospace || inherited.mono,
                family: None,
                wrap: inherited.wrap,
                traits: inherited.traits,
            }
        });
        let mut layout_style = self.style.clone();
        layout_style.flex_shrink = layout_style.flex_shrink.or(Some(if leaf.wrap { 1.0 } else { 0.0 }));
        let node = cx.request_text_node(&layout_style, leaf.clone());
        self.node = Some(node);
        self.leaf = Some(leaf);
        node
    }

    fn prepaint(&mut self, cx: &mut ElementContext) {
        if let (true, Some(node)) = (cx.accessibility_enabled(), self.node) {
            let bounds = cx.node_bounds(node);
            cx.register_access(AccessNode {
                role: accesskit::Role::Label,
                label: Some(self.content.clone()),
                value: None,
                bounds,
                focus: None,
                clickable: false,
            });
        }
    }

    fn paint(&mut self, cx: &mut ElementContext) {
        let (Some(node), Some(leaf)) = (self.node, self.leaf.as_ref()) else { return };
        let bounds = cx.node_bounds(node);
        let shaped = cx.shape_leaf(leaf, Some(bounds.size.width.0));
        let spec = self.style.text_color.unwrap_or(cx.text_style().color);
        let color = cx.theme.resolve(spec);
        cx.paint_text(&shaped, bounds.origin, color);
    }
}