Skip to main content

lv_tui/
component.rs

1use crate::dirty::Dirty;
2use crate::event::{Command, Event, EventPhase};
3use crate::geom::{Rect, Size};
4use crate::layout::Constraint;
5use crate::node::Node;
6use crate::render::RenderCx;
7use crate::style::Style;
8
9/// 组件 trait——应用的基本组织单元
10pub trait Component {
11    /// 渲染组件内容
12    fn render(&self, cx: &mut RenderCx);
13
14    /// 处理事件(可选实现)
15    fn event(&mut self, _event: &Event, _cx: &mut EventCx) {}
16
17    /// 返回组件样式(可选实现)
18    fn style(&self) -> Style {
19        Style::default()
20    }
21
22    /// 测量组件在给定约束下的自适应尺寸
23    ///
24    /// 默认实现:单行高度,宽度填满可用空间。
25    fn measure(&self, constraint: Constraint, _cx: &mut MeasureCx) -> Size {
26        Size {
27            width: constraint.max.width,
28            height: 1,
29        }
30    }
31
32    /// 布局回调——组件在此计算子节点 rect 并调用 child.layout()
33    ///
34    /// 默认实现:无子节点,不执行任何操作。
35    fn layout(&mut self, _rect: Rect, _cx: &mut LayoutCx) {}
36
37    /// 首次挂载(初始化后调用)
38    fn mount(&mut self, _cx: &mut EventCx) {}
39    /// 从树卸载(退出前调用)
40    fn unmount(&mut self, _cx: &mut EventCx) {}
41    /// 每次事件分发前调用
42    fn update(&mut self, _cx: &mut EventCx) {}
43
44    /// 组件类型名(用于样式表类型选择器匹配)
45    fn type_name(&self) -> &str {
46        std::any::type_name::<Self>()
47            .split("::")
48            .last()
49            .unwrap_or("Unknown")
50    }
51
52    /// 组件 ID(用于样式表 #id 选择器匹配)
53    fn id(&self) -> Option<&str> {
54        None
55    }
56
57    /// 组件 class(用于样式表 .class 选择器匹配)
58    fn class(&self) -> Option<&str> {
59        None
60    }
61
62    /// 是否可聚焦(Tab 导航会跳过不可聚焦的组件)
63    fn focusable(&self) -> bool {
64        true
65    }
66
67    /// 遍历子节点(为焦点系统等提供统一的树遍历接口)
68    fn for_each_child(&self, _f: &mut dyn FnMut(&Node)) {}
69
70    /// 遍历子节点(可变版本)
71    fn for_each_child_mut(&mut self, _f: &mut dyn FnMut(&mut Node)) {}
72}
73
74/// 事件上下文——组件通过它标记 dirty、退出应用等
75///
76/// 每个 Node 拥有独立的 EventCx,dirty 标记精确到当前节点,
77/// 同时通过 global_dirty 确保 Runtime 感知到变更。
78pub struct EventCx<'a> {
79    pub(crate) node_dirty: &'a mut Dirty,
80    pub(crate) global_dirty: &'a mut Dirty,
81    pub(crate) quit: &'a mut bool,
82    pub(crate) phase: EventPhase,
83    pub(crate) propagation_stopped: &'a mut bool,
84    pub(crate) task_sender: Option<std::sync::mpsc::Sender<String>>,
85}
86
87impl<'a> EventCx<'a> {
88    pub(crate) fn new(
89        node_dirty: &'a mut Dirty,
90        global_dirty: &'a mut Dirty,
91        quit: &'a mut bool,
92        phase: EventPhase,
93        propagation_stopped: &'a mut bool,
94    ) -> Self {
95        Self {
96            node_dirty,
97            global_dirty,
98            quit,
99            phase,
100            propagation_stopped,
101            task_sender: None,
102        }
103    }
104
105    pub(crate) fn with_task_sender(
106        node_dirty: &'a mut Dirty,
107        global_dirty: &'a mut Dirty,
108        quit: &'a mut bool,
109        phase: EventPhase,
110        propagation_stopped: &'a mut bool,
111        task_sender: Option<std::sync::mpsc::Sender<String>>,
112    ) -> Self {
113        Self {
114            node_dirty,
115            global_dirty,
116            quit,
117            phase,
118            propagation_stopped,
119            task_sender,
120        }
121    }
122
123    /// 当前事件传播阶段
124    pub fn phase(&self) -> EventPhase {
125        self.phase
126    }
127
128    /// 停止事件传播(后续阶段和祖先不再收到该事件)
129    pub fn stop_propagation(&mut self) {
130        *self.propagation_stopped = true;
131    }
132
133    /// 标记当前节点需要重绘
134    pub fn invalidate_paint(&mut self) {
135        *self.node_dirty |= Dirty::PAINT;
136        *self.global_dirty |= Dirty::PAINT;
137    }
138
139    /// 标记当前节点需要重新布局并重绘
140    pub fn invalidate_layout(&mut self) {
141        *self.node_dirty |= Dirty::LAYOUT | Dirty::PAINT;
142        *self.global_dirty |= Dirty::LAYOUT | Dirty::PAINT;
143    }
144
145    /// 标记需要重新布局(等同于 invalidate_layout)
146    pub fn invalidate(&mut self) {
147        self.invalidate_layout();
148    }
149
150    /// 标记组件树结构变化,触发完整 rebuild + relayout + repaint
151    pub fn invalidate_tree(&mut self) {
152        *self.node_dirty |= Dirty::TREE | Dirty::LAYOUT | Dirty::PAINT;
153        *self.global_dirty |= Dirty::TREE | Dirty::LAYOUT | Dirty::PAINT;
154    }
155
156    /// Copy text to the system clipboard via OSC 52.
157    ///
158    /// Most modern terminals support this. The escape sequence is written
159    /// directly to stdout.
160    pub fn copy_to_clipboard(&self, text: &str) {
161        use std::io::Write;
162        let seq = crate::clipboard::copy_to_clipboard_sequence(text);
163        let _ = std::io::stdout().write_all(seq.as_bytes());
164        let _ = std::io::stdout().flush();
165    }
166
167    /// 退出应用
168    pub fn quit(&mut self) {
169        *self.quit = true;
170    }
171
172    /// 发送命令给 Runtime 统一处理(通过全局队列)
173    pub fn dispatch(&mut self, cmd: Command) {
174        crate::runtime::COMMAND_QUEUE.with(|q| {
175            q.borrow_mut().push(cmd);
176        });
177    }
178
179    /// 切换 debug 视图
180    pub fn toggle_debug() {
181        crate::runtime::DEBUG_MODE.with(|d| {
182            let current = *d.borrow();
183            *d.borrow_mut() = !current;
184        });
185    }
186
187    /// 启动后台任务
188    pub fn spawn<F>(&mut self, task: F)
189    where
190        F: FnOnce() -> String + Send + 'static,
191    {
192        if let Some(tx) = self.task_sender.clone() {
193            std::thread::spawn(move || {
194                let result = task();
195                let _ = tx.send(result);
196            });
197        }
198    }
199}
200
201/// 测量上下文——组件通过它测量自身和子组件的自适应尺寸
202pub struct MeasureCx {
203    pub constraint: Constraint,
204}
205
206/// 布局上下文——容器组件通过它为子节点分配 Rect
207pub struct LayoutCx<'a> {
208    children: &'a mut Vec<Node>,
209}
210
211impl<'a> LayoutCx<'a> {
212    pub(crate) fn new(children: &'a mut Vec<Node>) -> Self {
213        Self { children }
214    }
215
216    /// 获取子节点数量
217    pub fn child_count(&self) -> usize {
218        self.children.len()
219    }
220
221    /// 获取第 i 个子节点的 style
222    pub fn child_style(&self, index: usize) -> Option<Style> {
223        self.children.get(index).map(|n| n.component.style())
224    }
225
226    /// 为第 i 个子节点设置布局 rect(递归触发子节点 layout)
227    pub fn layout_child(&mut self, index: usize, rect: Rect) {
228        if let Some(child) = self.children.get_mut(index) {
229            child.layout(rect);
230        }
231    }
232
233    /// 遍历所有子节点
234    pub fn for_each_child(&mut self, mut f: impl FnMut(usize, &mut Node)) {
235        for (i, child) in self.children.iter_mut().enumerate() {
236            f(i, child);
237        }
238    }
239}