lv-tui 0.4.0

A reactive TUI framework for Rust
Documentation
use crate::buffer::CellStyle;
use crate::geom::Insets;

/// Theme mode for terminal colors.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Theme {
    Dark,
    Light,
}

thread_local! {
    /// Current global theme. Set via [`set_theme`].
    pub static CURRENT_THEME: std::cell::RefCell<Theme> = std::cell::RefCell::new(Theme::Dark);
}

/// Sets the global theme (Dark or Light) and forces a full repaint.
pub fn set_theme(theme: Theme) {
    CURRENT_THEME.with(|t| *t.borrow_mut() = theme);
    // Force a full repaint so color changes take effect immediately.
    crate::runtime::FORCE_FULL_PAINT.with(|f| *f.borrow_mut() = true);
}

/// Terminal color palette.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Color {
    // Standard ANSI (16)
    Black,
    Red,
    Green,
    Yellow,
    Blue,
    Magenta,
    Cyan,
    White,
    Gray,
    LightRed,
    LightGreen,
    LightYellow,
    LightBlue,
    LightMagenta,
    LightCyan,
    DarkGray,
    /// 256-color palette index (0–255)
    Indexed(u8),
    /// 24-bit true color
    Rgb(u8, u8, u8),
}

impl Color {
    /// Constructs from hex string. Accepts `#rgb`, `#rrggbb`, `rgb`, `rrggbb`.
    pub fn hex(hex: &str) -> Option<Color> {
        let s = hex.trim().trim_start_matches('#');
        if s.is_empty() {
            return None;
        }
        match s.len() {
            3 => {
                let chars: Vec<char> = s.chars().collect();
                let r = u8::from_str_radix(&chars[0].to_string().repeat(2), 16).ok()?;
                let g = u8::from_str_radix(&chars[1].to_string().repeat(2), 16).ok()?;
                let b = u8::from_str_radix(&chars[2].to_string().repeat(2), 16).ok()?;
                Some(Color::Rgb(r, g, b))
            }
            6 => {
                let r = u8::from_str_radix(&s[0..2], 16).ok()?;
                let g = u8::from_str_radix(&s[2..4], 16).ok()?;
                let b = u8::from_str_radix(&s[4..6], 16).ok()?;
                Some(Color::Rgb(r, g, b))
            }
            _ => None,
        }
    }

    pub fn rgb(r: u8, g: u8, b: u8) -> Color {
        Color::Rgb(r, g, b)
    }

    pub fn indexed(i: u8) -> Color {
        Color::Indexed(i)
    }
}

impl From<Color> for crossterm::style::Color {
    fn from(c: Color) -> Self {
        let dark = CURRENT_THEME.with(|t| *t.borrow() == Theme::Dark);
        match c {
            Color::Rgb(r, g, b) => crossterm::style::Color::Rgb { r, g, b },
            Color::Indexed(i) => crossterm::style::Color::AnsiValue(i),
            Color::Black => crossterm::style::Color::Black,
            Color::Red if dark => crossterm::style::Color::DarkRed,
            Color::Red => crossterm::style::Color::Red,
            Color::Green if dark => crossterm::style::Color::DarkGreen,
            Color::Green => crossterm::style::Color::Green,
            Color::Yellow if dark => crossterm::style::Color::DarkYellow,
            Color::Yellow => crossterm::style::Color::Yellow,
            Color::Blue if dark => crossterm::style::Color::DarkBlue,
            Color::Blue => crossterm::style::Color::Blue,
            Color::Magenta if dark => crossterm::style::Color::DarkMagenta,
            Color::Magenta => crossterm::style::Color::Magenta,
            Color::Cyan if dark => crossterm::style::Color::DarkCyan,
            Color::Cyan => crossterm::style::Color::Cyan,
            Color::White => crossterm::style::Color::Grey,
            Color::Gray => crossterm::style::Color::DarkGrey,
            Color::LightRed => crossterm::style::Color::Red,
            Color::LightGreen => crossterm::style::Color::Green,
            Color::LightYellow => crossterm::style::Color::Yellow,
            Color::LightBlue => crossterm::style::Color::Blue,
            Color::LightMagenta => crossterm::style::Color::Magenta,
            Color::LightCyan => crossterm::style::Color::Cyan,
            Color::DarkGray => crossterm::style::Color::DarkGrey,
        }
    }
}

/// 长度规格
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Length {
    /// 内容自适应
    Auto,
    /// 固定字符格数
    Fixed(u16),
    /// 父容器可用空间的百分比
    Percent(u16),
    /// 参与剩余空间按比例分配
    Fraction(u16),
}

impl Default for Length {
    fn default() -> Self {
        Length::Auto
    }
}

/// 布局方向
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Layout {
    None,
    Vertical,
    Horizontal,
}

impl Default for Layout {
    fn default() -> Self {
        Layout::None
    }
}

/// 文本换行模式
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum TextWrap {
    /// 不换行,超出 clip 截断
    #[default]
    None,
    /// 按字符边界换行
    Char,
    /// 按单词边界换行(英文空格分词,CJK 按字符)
    Word,
}

/// 文本对齐
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum TextAlign {
    #[default]
    Left,
    Center,
    Right,
}

/// 文本截断模式
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum TextTruncate {
    /// 不截断
    #[default]
    None,
    /// 末尾追加省略号 …
    Ellipsis,
}

/// 边框样式
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Border {
    #[default]
    None,
    Plain,
    Rounded,
    Double,
}

/// 组件样式
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Style {
    // 视觉效果
    pub fg: Option<Color>,
    pub bg: Option<Color>,
    pub bold: bool,
    pub italic: bool,
    pub underline: bool,

    // 布局
    pub width: Length,
    pub height: Length,
    pub padding: Insets,
    pub margin: Insets,
    pub layout: Layout,
    pub gap: u16,
    pub flex_grow: u16,
    pub flex_shrink: bool,
    pub border: Border,
}

impl Default for Style {
    fn default() -> Self {
        Self {
            fg: None,
            bg: None,
            bold: false,
            italic: false,
            underline: false,
            width: Length::Auto,
            height: Length::Auto,
            padding: Insets::ZERO,
            margin: Insets::ZERO,
            layout: Layout::None,
            gap: 0,
            flex_grow: 0,
            flex_shrink: true,
            border: Border::None,
        }
    }
}

impl Style {
    /// Builder: sets the foreground color.
    pub fn fg(mut self, color: Color) -> Self {
        self.fg = Some(color);
        self
    }

    /// Builder: sets the background color.
    pub fn bg(mut self, color: Color) -> Self {
        self.bg = Some(color);
        self
    }

    /// Builder: enables bold.
    pub fn bold(mut self) -> Self {
        self.bold = true;
        self
    }

    /// Builder: enables italic.
    pub fn italic(mut self) -> Self {
        self.italic = true;
        self
    }

    /// Builder: enables underline.
    pub fn underline(mut self) -> Self {
        self.underline = true;
        self
    }

    /// Builder: sets the width constraint.
    pub fn width(mut self, width: Length) -> Self {
        self.width = width;
        self
    }

    /// Builder: sets the height constraint.
    pub fn height(mut self, height: Length) -> Self {
        self.height = height;
        self
    }

    /// Builder: sets uniform padding on all four sides.
    pub fn padding(mut self, value: u16) -> Self {
        self.padding = Insets::all(value);
        self
    }

    /// Builder: sets padding on each side independently.
    pub fn padding_each(mut self, top: u16, right: u16, bottom: u16, left: u16) -> Self {
        self.padding = Insets { top, right, bottom, left };
        self
    }

    /// Builder: sets uniform margin on all four sides.
    pub fn margin(mut self, value: u16) -> Self {
        self.margin = Insets::all(value);
        self
    }

    /// Builder: sets the layout direction.
    pub fn layout(mut self, layout: Layout) -> Self {
        self.layout = layout;
        self
    }

    /// Builder: sets the gap between children.
    pub fn gap(mut self, gap: u16) -> Self {
        self.gap = gap;
        self
    }

    /// Builder: sets flex grow factor (0 = don't grow).
    pub fn flex_grow(mut self, grow: u16) -> Self {
        self.flex_grow = grow;
        self
    }

    /// Builder: sets whether the item can shrink below intrinsic size.
    pub fn flex_shrink(mut self, shrink: bool) -> Self {
        self.flex_shrink = shrink;
        self
    }

    /// Builder: sets the border style.
    pub fn border(mut self, border: Border) -> Self {
        self.border = border;
        self
    }

    /// 转换为 CellStyle
    pub fn into_cell_style(&self) -> CellStyle {
        CellStyle {
            fg: self.fg,
            bg: self.bg,
            bold: self.bold,
            italic: self.italic,
            underline: self.underline,
        }
    }
}