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    /// The NodeId of the component currently handling this event.
86    pub(crate) current_node_id: crate::node::NodeId,
87}
88
89impl<'a> EventCx<'a> {
90    pub(crate) fn new(
91        node_dirty: &'a mut Dirty,
92        global_dirty: &'a mut Dirty,
93        quit: &'a mut bool,
94        phase: EventPhase,
95        propagation_stopped: &'a mut bool,
96    ) -> Self {
97        Self {
98            node_dirty,
99            global_dirty,
100            quit,
101            phase,
102            propagation_stopped,
103            task_sender: None,
104            current_node_id: crate::node::NodeId::ROOT,
105        }
106    }
107
108    pub(crate) fn with_task_sender(
109        node_dirty: &'a mut Dirty,
110        global_dirty: &'a mut Dirty,
111        quit: &'a mut bool,
112        phase: EventPhase,
113        propagation_stopped: &'a mut bool,
114        task_sender: Option<std::sync::mpsc::Sender<String>>,
115    ) -> Self {
116        Self {
117            node_dirty,
118            global_dirty,
119            quit,
120            phase,
121            propagation_stopped,
122            task_sender,
123            current_node_id: crate::node::NodeId::ROOT,
124        }
125    }
126
127    /// 当前事件传播阶段
128    pub fn phase(&self) -> EventPhase {
129        self.phase
130    }
131
132    /// 停止事件传播(后续阶段和祖先不再收到该事件)
133    pub fn stop_propagation(&mut self) {
134        *self.propagation_stopped = true;
135    }
136
137    /// 标记当前节点需要重绘
138    pub fn invalidate_paint(&mut self) {
139        *self.node_dirty |= Dirty::PAINT;
140        *self.global_dirty |= Dirty::PAINT;
141    }
142
143    /// 标记当前节点需要重新布局并重绘
144    pub fn invalidate_layout(&mut self) {
145        *self.node_dirty |= Dirty::LAYOUT | Dirty::PAINT;
146        *self.global_dirty |= Dirty::LAYOUT | Dirty::PAINT;
147    }
148
149    /// 标记需要重新布局(等同于 invalidate_layout)
150    pub fn invalidate(&mut self) {
151        self.invalidate_layout();
152    }
153
154    /// 标记组件树结构变化,触发完整 rebuild + relayout + repaint
155    pub fn invalidate_tree(&mut self) {
156        *self.node_dirty |= Dirty::TREE | Dirty::LAYOUT | Dirty::PAINT;
157        *self.global_dirty |= Dirty::TREE | Dirty::LAYOUT | Dirty::PAINT;
158    }
159
160    /// Copy text to the system clipboard via OSC 52.
161    ///
162    /// Most modern terminals support this. The escape sequence is written
163    /// directly to stdout.
164    pub fn copy_to_clipboard(&self, text: &str) {
165        use std::io::Write;
166        let seq = crate::clipboard::copy_to_clipboard_sequence(text);
167        let _ = std::io::stdout().write_all(seq.as_bytes());
168        let _ = std::io::stdout().flush();
169    }
170
171    /// 退出应用
172    pub fn quit(&mut self) {
173        *self.quit = true;
174    }
175
176    /// 发送命令给 Runtime 统一处理(通过全局队列)
177    pub fn dispatch(&mut self, cmd: Command) {
178        crate::runtime::COMMAND_QUEUE.with(|q| {
179            q.borrow_mut().push(cmd);
180        });
181    }
182
183    /// 切换 debug 视图
184    pub fn toggle_debug() {
185        crate::runtime::DEBUG_MODE.with(|d| {
186            let current = *d.borrow();
187            *d.borrow_mut() = !current;
188        });
189    }
190
191    /// 启动后台任务
192    pub fn spawn<F>(&mut self, task: F)
193    where
194        F: FnOnce() -> String + Send + 'static,
195    {
196        if let Some(tx) = self.task_sender.clone() {
197            std::thread::spawn(move || {
198                let result = task();
199                let _ = tx.send(result);
200            });
201        }
202    }
203
204    /// 启动可追踪的后台任务。返回 WorkerId 用于取消。
205    pub fn spawn_worker<F>(&mut self, task: F) -> crate::event::WorkerId
206    where
207        F: FnOnce() -> String + Send + 'static,
208    {
209        use crate::event::WorkerId;
210        use crate::runtime::WORKER_REGISTRY;
211        use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
212
213        let id = WORKER_REGISTRY.with(|r| {
214            let mut r = r.borrow_mut();
215            let id = WorkerId(r.next_id);
216            r.next_id += 1;
217            id
218        });
219
220        let cancel_flag = Arc::new(AtomicBool::new(false));
221        WORKER_REGISTRY.with(|r| {
222            r.borrow_mut().flags.insert(id, cancel_flag.clone());
223        });
224
225        if let Some(tx) = self.task_sender.clone() {
226            let flag = cancel_flag.clone();
227            std::thread::spawn(move || {
228                if flag.load(Ordering::Relaxed) {
229                    let _ = tx.send(format!("__worker_cancelled__{}", id.0));
230                    return;
231                }
232                let result = task();
233                if flag.load(Ordering::Relaxed) {
234                    let _ = tx.send(format!("__worker_cancelled__{}", id.0));
235                    return;
236                }
237                let _ = tx.send(format!("__worker_done__{}__{}", id.0, result));
238            });
239        }
240
241        id
242    }
243
244    /// 取消指定 Worker。
245    pub fn cancel_worker(&mut self, id: crate::event::WorkerId) {
246        use std::sync::atomic::Ordering;
247        crate::runtime::WORKER_REGISTRY.with(|r| {
248            let r = r.borrow();
249            if let Some(flag) = r.flags.get(&id) {
250                flag.store(true, Ordering::Relaxed);
251            }
252        });
253    }
254
255    /// Registers a one-shot timer. After `duration_ms` milliseconds,
256    /// an `Event::Timer(id)` is dispatched to this component and the timer
257    /// is automatically removed. Returns the timer id.
258    pub fn set_timer(&mut self, duration_ms: u64) -> u64 {
259        crate::runtime::TIMER_QUEUE.with(|q| {
260            let mut q = q.borrow_mut();
261            let id = q.next_id;
262            q.next_id += 1;
263            q.requests.push(crate::runtime::TimerRequest {
264                action: crate::runtime::TimerAction::SetOneShot { id, duration_ms },
265                owner: self.current_node_id,
266            });
267            id
268        })
269    }
270
271    /// Registers a periodic timer. Every `interval_ms` milliseconds,
272    /// an `Event::Timer(id)` is dispatched to this component. The timer
273    /// continues until `cancel_timer(id)` is called or the component is
274    /// unmounted. Returns the timer id.
275    pub fn set_interval(&mut self, interval_ms: u64) -> u64 {
276        crate::runtime::TIMER_QUEUE.with(|q| {
277            let mut q = q.borrow_mut();
278            let id = q.next_id;
279            q.next_id += 1;
280            q.requests.push(crate::runtime::TimerRequest {
281                action: crate::runtime::TimerAction::SetInterval { id, interval_ms },
282                owner: self.current_node_id,
283            });
284            id
285        })
286    }
287
288    /// Cancels a previously registered timer. No further `Event::Timer`
289    /// events will be dispatched for this id.
290    pub fn cancel_timer(&mut self, timer_id: u64) {
291        crate::runtime::TIMER_QUEUE.with(|q| {
292            q.borrow_mut().requests.push(crate::runtime::TimerRequest {
293                action: crate::runtime::TimerAction::Cancel { id: timer_id },
294                owner: self.current_node_id,
295            });
296        })
297    }
298}
299
300/// 测量上下文——组件通过它测量自身和子组件的自适应尺寸
301pub struct MeasureCx {
302    pub constraint: Constraint,
303}
304
305/// 布局上下文——容器组件通过它为子节点分配 Rect
306pub struct LayoutCx<'a> {
307    children: &'a mut Vec<Node>,
308}
309
310impl<'a> LayoutCx<'a> {
311    pub(crate) fn new(children: &'a mut Vec<Node>) -> Self {
312        Self { children }
313    }
314
315    /// 获取子节点数量
316    pub fn child_count(&self) -> usize {
317        self.children.len()
318    }
319
320    /// 获取第 i 个子节点的 style
321    pub fn child_style(&self, index: usize) -> Option<Style> {
322        self.children.get(index).map(|n| n.component.style())
323    }
324
325    /// 为第 i 个子节点设置布局 rect(递归触发子节点 layout)
326    pub fn layout_child(&mut self, index: usize, rect: Rect) {
327        if let Some(child) = self.children.get_mut(index) {
328            child.layout(rect);
329        }
330    }
331
332    /// 遍历所有子节点
333    pub fn for_each_child(&mut self, mut f: impl FnMut(usize, &mut Node)) {
334        for (i, child) in self.children.iter_mut().enumerate() {
335            f(i, child);
336        }
337    }
338}