lv-tui 0.2.0

A reactive TUI framework for Rust, inspired by Textual and React
Documentation
use crate::component::{Component, EventCx, LayoutCx, MeasureCx};
use crate::event::Event;
use crate::geom::{Rect, Size};
use crate::layout::Constraint;
use crate::render::RenderCx;
use crate::style::{Style, TextWrap};

/// 文本标签 widget
pub struct Label {
    text: String,
    style: Style,
    id: Option<String>,
    class: Option<String>,
}

impl Label {
    pub fn new(text: impl Into<String>) -> Self {
        Self {
            text: text.into(),
            style: Style::default(),
            id: None,
            class: None,
        }
    }

    pub fn style(mut self, style: Style) -> Self {
        self.style = style;
        self
    }

    pub fn id(mut self, id: impl Into<String>) -> Self {
        self.id = Some(id.into());
        self
    }

    pub fn class(mut self, class: impl Into<String>) -> Self {
        self.class = Some(class.into());
        self
    }
}

impl Component for Label {
    fn render(&self, cx: &mut RenderCx) {
        // Split by newlines and render each segment as a separate line
        let segments: Vec<&str> = self.text.split('\n').collect();
        for (i, seg) in segments.iter().enumerate() {
            if i > 0 {
                cx.cursor.y = cx.cursor.y.saturating_add(1);
                cx.cursor.x = cx.rect.x;
            }
            match cx.wrap {
                TextWrap::None => {
                    let tw: u16 = seg.chars().map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0) as u16).sum();
                    cx.cursor.x = cx.cursor.x.saturating_add(cx.align_offset(tw));
                    cx.text(seg);
                }
                TextWrap::Char => cx.line(seg),
            }
        }
    }

    fn measure(&self, constraint: Constraint, _cx: &mut MeasureCx) -> Size {
        let max_w = constraint.max.width.max(1);
        let mut line_width: u16 = 0;
        let mut lines: u16 = 1;
        let mut total_w: u16 = 0;
        for c in self.text.chars() {
            if c == '\n' {
                lines += 1;
                total_w = total_w.max(line_width);
                line_width = 0;
                continue;
            }
            let w = unicode_width::UnicodeWidthChar::width(c).unwrap_or(0) as u16;
            if line_width + w > max_w {
                lines += 1;
                total_w = total_w.max(line_width);
                line_width = w;
            } else {
                line_width += w;
            }
        }
        total_w = total_w.max(line_width);
        Size {
            width: max_w.min(total_w),
            height: lines,
        }
    }

    fn layout(&mut self, _rect: Rect, _cx: &mut LayoutCx) {
        // Label 无子节点,无需操作
    }

    fn style(&self) -> Style {
        self.style.clone()
    }

    fn id(&self) -> Option<&str> {
        self.id.as_deref()
    }

    fn class(&self) -> Option<&str> {
        self.class.as_deref()
    }

    fn focusable(&self) -> bool {
        false
    }

    fn event(&mut self, _event: &Event, _cx: &mut EventCx) {}
}