Skip to main content

lv_tui/
style.rs

1use crate::buffer::CellStyle;
2use crate::geom::Insets;
3
4/// Theme mode for terminal colors.
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum Theme {
7    Dark,
8    Light,
9}
10
11thread_local! {
12    /// Current global theme. Set via [`set_theme`].
13    pub static CURRENT_THEME: std::cell::RefCell<Theme> = std::cell::RefCell::new(Theme::Dark);
14}
15
16/// Sets the global theme (Dark or Light) and forces a full repaint.
17pub fn set_theme(theme: Theme) {
18    CURRENT_THEME.with(|t| *t.borrow_mut() = theme);
19    // Force a full repaint so color changes take effect immediately.
20    crate::runtime::FORCE_FULL_PAINT.with(|f| *f.borrow_mut() = true);
21}
22
23/// Terminal color palette.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum Color {
26    Black,
27    Red,
28    Green,
29    Yellow,
30    Blue,
31    Magenta,
32    Cyan,
33    White,
34    Gray,
35}
36
37impl From<Color> for crossterm::style::Color {
38    fn from(c: Color) -> Self {
39        let dark = CURRENT_THEME.with(|t| *t.borrow() == Theme::Dark);
40        match (c, dark) {
41            (Color::Black, _) => crossterm::style::Color::Black,
42            (Color::Red, true) => crossterm::style::Color::DarkRed,
43            (Color::Red, false) => crossterm::style::Color::Red,
44            (Color::Green, true) => crossterm::style::Color::DarkGreen,
45            (Color::Green, false) => crossterm::style::Color::Green,
46            (Color::Yellow, true) => crossterm::style::Color::DarkYellow,
47            (Color::Yellow, false) => crossterm::style::Color::Yellow,
48            (Color::Blue, true) => crossterm::style::Color::DarkBlue,
49            (Color::Blue, false) => crossterm::style::Color::Blue,
50            (Color::Magenta, true) => crossterm::style::Color::DarkMagenta,
51            (Color::Magenta, false) => crossterm::style::Color::Magenta,
52            (Color::Cyan, true) => crossterm::style::Color::DarkCyan,
53            (Color::Cyan, false) => crossterm::style::Color::Cyan,
54            (Color::White, _) => crossterm::style::Color::Grey,
55            (Color::Gray, true) => crossterm::style::Color::DarkGrey,
56            (Color::Gray, false) => crossterm::style::Color::Grey,
57        }
58    }
59}
60
61/// 长度规格
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub enum Length {
64    /// 内容自适应
65    Auto,
66    /// 固定字符格数
67    Fixed(u16),
68    /// 父容器可用空间的百分比
69    Percent(u16),
70    /// 参与剩余空间按比例分配
71    Fraction(u16),
72}
73
74impl Default for Length {
75    fn default() -> Self {
76        Length::Auto
77    }
78}
79
80/// 布局方向
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub enum Layout {
83    None,
84    Vertical,
85    Horizontal,
86}
87
88impl Default for Layout {
89    fn default() -> Self {
90        Layout::None
91    }
92}
93
94/// 文本换行模式
95#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
96pub enum TextWrap {
97    /// 不换行,超出 clip 截断
98    #[default]
99    None,
100    /// 按字符边界换行
101    Char,
102}
103
104/// 文本对齐
105#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
106pub enum TextAlign {
107    #[default]
108    Left,
109    Center,
110    Right,
111}
112
113/// 文本截断模式
114#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
115pub enum TextTruncate {
116    /// 不截断
117    #[default]
118    None,
119    /// 末尾追加省略号 …
120    Ellipsis,
121}
122
123/// 边框样式
124#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
125pub enum Border {
126    #[default]
127    None,
128    Plain,
129    Rounded,
130    Double,
131}
132
133/// 组件样式
134#[derive(Debug, Clone, PartialEq, Eq)]
135pub struct Style {
136    // 视觉效果
137    pub fg: Option<Color>,
138    pub bg: Option<Color>,
139    pub bold: bool,
140    pub italic: bool,
141    pub underline: bool,
142
143    // 布局
144    pub width: Length,
145    pub height: Length,
146    pub padding: Insets,
147    pub margin: Insets,
148    pub layout: Layout,
149    pub gap: u16,
150    pub flex_grow: u16,
151    pub flex_shrink: bool,
152    pub border: Border,
153}
154
155impl Default for Style {
156    fn default() -> Self {
157        Self {
158            fg: None,
159            bg: None,
160            bold: false,
161            italic: false,
162            underline: false,
163            width: Length::Auto,
164            height: Length::Auto,
165            padding: Insets::ZERO,
166            margin: Insets::ZERO,
167            layout: Layout::None,
168            gap: 0,
169            flex_grow: 0,
170            flex_shrink: true,
171            border: Border::None,
172        }
173    }
174}
175
176impl Style {
177    /// Builder: sets the foreground color.
178    pub fn fg(mut self, color: Color) -> Self {
179        self.fg = Some(color);
180        self
181    }
182
183    /// Builder: sets the background color.
184    pub fn bg(mut self, color: Color) -> Self {
185        self.bg = Some(color);
186        self
187    }
188
189    /// Builder: enables bold.
190    pub fn bold(mut self) -> Self {
191        self.bold = true;
192        self
193    }
194
195    /// Builder: enables italic.
196    pub fn italic(mut self) -> Self {
197        self.italic = true;
198        self
199    }
200
201    /// Builder: enables underline.
202    pub fn underline(mut self) -> Self {
203        self.underline = true;
204        self
205    }
206
207    /// Builder: sets the width constraint.
208    pub fn width(mut self, width: Length) -> Self {
209        self.width = width;
210        self
211    }
212
213    /// Builder: sets the height constraint.
214    pub fn height(mut self, height: Length) -> Self {
215        self.height = height;
216        self
217    }
218
219    /// Builder: sets uniform padding on all four sides.
220    pub fn padding(mut self, value: u16) -> Self {
221        self.padding = Insets::all(value);
222        self
223    }
224
225    /// Builder: sets uniform margin on all four sides.
226    pub fn margin(mut self, value: u16) -> Self {
227        self.margin = Insets::all(value);
228        self
229    }
230
231    /// Builder: sets the layout direction.
232    pub fn layout(mut self, layout: Layout) -> Self {
233        self.layout = layout;
234        self
235    }
236
237    /// Builder: sets the gap between children.
238    pub fn gap(mut self, gap: u16) -> Self {
239        self.gap = gap;
240        self
241    }
242
243    /// Builder: sets flex grow factor (0 = don't grow).
244    pub fn flex_grow(mut self, grow: u16) -> Self {
245        self.flex_grow = grow;
246        self
247    }
248
249    /// Builder: sets whether the item can shrink below intrinsic size.
250    pub fn flex_shrink(mut self, shrink: bool) -> Self {
251        self.flex_shrink = shrink;
252        self
253    }
254
255    /// Builder: sets the border style.
256    pub fn border(mut self, border: Border) -> Self {
257        self.border = border;
258        self
259    }
260
261    /// 转换为 CellStyle
262    pub fn into_cell_style(&self) -> CellStyle {
263        CellStyle {
264            fg: self.fg,
265            bg: self.bg,
266            bold: self.bold,
267            italic: self.italic,
268            underline: self.underline,
269        }
270    }
271}