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    /// 按单词边界换行(英文空格分词,CJK 按字符)
103    Word,
104}
105
106/// 文本对齐
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
108pub enum TextAlign {
109    #[default]
110    Left,
111    Center,
112    Right,
113}
114
115/// 文本截断模式
116#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
117pub enum TextTruncate {
118    /// 不截断
119    #[default]
120    None,
121    /// 末尾追加省略号 …
122    Ellipsis,
123}
124
125/// 边框样式
126#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
127pub enum Border {
128    #[default]
129    None,
130    Plain,
131    Rounded,
132    Double,
133}
134
135/// 组件样式
136#[derive(Debug, Clone, PartialEq, Eq)]
137pub struct Style {
138    // 视觉效果
139    pub fg: Option<Color>,
140    pub bg: Option<Color>,
141    pub bold: bool,
142    pub italic: bool,
143    pub underline: bool,
144
145    // 布局
146    pub width: Length,
147    pub height: Length,
148    pub padding: Insets,
149    pub margin: Insets,
150    pub layout: Layout,
151    pub gap: u16,
152    pub flex_grow: u16,
153    pub flex_shrink: bool,
154    pub border: Border,
155}
156
157impl Default for Style {
158    fn default() -> Self {
159        Self {
160            fg: None,
161            bg: None,
162            bold: false,
163            italic: false,
164            underline: false,
165            width: Length::Auto,
166            height: Length::Auto,
167            padding: Insets::ZERO,
168            margin: Insets::ZERO,
169            layout: Layout::None,
170            gap: 0,
171            flex_grow: 0,
172            flex_shrink: true,
173            border: Border::None,
174        }
175    }
176}
177
178impl Style {
179    /// Builder: sets the foreground color.
180    pub fn fg(mut self, color: Color) -> Self {
181        self.fg = Some(color);
182        self
183    }
184
185    /// Builder: sets the background color.
186    pub fn bg(mut self, color: Color) -> Self {
187        self.bg = Some(color);
188        self
189    }
190
191    /// Builder: enables bold.
192    pub fn bold(mut self) -> Self {
193        self.bold = true;
194        self
195    }
196
197    /// Builder: enables italic.
198    pub fn italic(mut self) -> Self {
199        self.italic = true;
200        self
201    }
202
203    /// Builder: enables underline.
204    pub fn underline(mut self) -> Self {
205        self.underline = true;
206        self
207    }
208
209    /// Builder: sets the width constraint.
210    pub fn width(mut self, width: Length) -> Self {
211        self.width = width;
212        self
213    }
214
215    /// Builder: sets the height constraint.
216    pub fn height(mut self, height: Length) -> Self {
217        self.height = height;
218        self
219    }
220
221    /// Builder: sets uniform padding on all four sides.
222    pub fn padding(mut self, value: u16) -> Self {
223        self.padding = Insets::all(value);
224        self
225    }
226
227    /// Builder: sets padding on each side independently.
228    pub fn padding_each(mut self, top: u16, right: u16, bottom: u16, left: u16) -> Self {
229        self.padding = Insets { top, right, bottom, left };
230        self
231    }
232
233    /// Builder: sets uniform margin on all four sides.
234    pub fn margin(mut self, value: u16) -> Self {
235        self.margin = Insets::all(value);
236        self
237    }
238
239    /// Builder: sets the layout direction.
240    pub fn layout(mut self, layout: Layout) -> Self {
241        self.layout = layout;
242        self
243    }
244
245    /// Builder: sets the gap between children.
246    pub fn gap(mut self, gap: u16) -> Self {
247        self.gap = gap;
248        self
249    }
250
251    /// Builder: sets flex grow factor (0 = don't grow).
252    pub fn flex_grow(mut self, grow: u16) -> Self {
253        self.flex_grow = grow;
254        self
255    }
256
257    /// Builder: sets whether the item can shrink below intrinsic size.
258    pub fn flex_shrink(mut self, shrink: bool) -> Self {
259        self.flex_shrink = shrink;
260        self
261    }
262
263    /// Builder: sets the border style.
264    pub fn border(mut self, border: Border) -> Self {
265        self.border = border;
266        self
267    }
268
269    /// 转换为 CellStyle
270    pub fn into_cell_style(&self) -> CellStyle {
271        CellStyle {
272            fg: self.fg,
273            bg: self.bg,
274            bold: self.bold,
275            italic: self.italic,
276            underline: self.underline,
277        }
278    }
279}