1use std::cell::Cell;
2use std::sync::atomic::{AtomicU64, Ordering};
3
4use crate::buffer::Buffer;
5use crate::component::{Component, EventCx, LayoutCx, MeasureCx};
6use crate::dirty::Dirty;
7use crate::event::{Event, EventPhase};
8use crate::geom::{Pos, Rect, Size};
9use crate::layout::Constraint;
10use crate::render::RenderCx;
11use crate::style::{Style, TextAlign, TextTruncate, TextWrap};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18pub struct NodeId(u64);
19
20impl NodeId {
21 pub fn new() -> Self {
23 static NEXT: AtomicU64 = AtomicU64::new(1);
24 Self(NEXT.fetch_add(1, Ordering::Relaxed))
25 }
26
27 pub const ROOT: NodeId = NodeId(0);
29}
30
31pub struct Node {
38 pub id: NodeId,
40 pub rect: Cell<Rect>,
43 pub dirty: Dirty,
45 pub parent: Option<NodeId>,
47 pub component: Box<dyn Component>,
49 pub children: Vec<Node>,
51}
52
53impl Node {
54 pub fn rect(&self) -> Rect {
56 self.rect.get()
57 }
58
59 pub fn set_rect(&self, r: Rect) {
61 self.rect.set(r);
62 }
63
64 pub fn new(component: impl Component + 'static) -> Self {
68 Self {
69 id: NodeId::new(),
70 rect: Cell::new(Rect::default()),
71 dirty: Dirty::NONE,
72 parent: None,
73 component: Box::new(component),
74 children: Vec::new(),
75 }
76 }
77
78 pub fn root(component: impl Component + 'static) -> Self {
80 Self {
81 id: NodeId::ROOT,
82 rect: Cell::new(Rect::default()),
83 dirty: Dirty::NONE,
84 parent: None,
85 component: Box::new(component),
86 children: Vec::new(),
87 }
88 }
89
90 pub fn add_child(&mut self, child: Node) {
92 self.children.push(child);
93 }
94
95 pub fn render(&self, buffer: &mut Buffer, focused_id: Option<NodeId>) {
97 self.render_with_clip(buffer, focused_id, None);
98 }
99
100 pub fn render_with_clip(
102 &self,
103 buffer: &mut Buffer,
104 focused_id: Option<NodeId>,
105 clip_rect: Option<Rect>,
106 ) {
107 self.render_with_clip_and_wrap(
108 buffer,
109 focused_id,
110 clip_rect,
111 TextWrap::None,
112 TextTruncate::None,
113 TextAlign::Left,
114 );
115 }
116
117 pub fn render_with_clip_and_wrap(
119 &self,
120 buffer: &mut Buffer,
121 focused_id: Option<NodeId>,
122 clip_rect: Option<Rect>,
123 wrap: crate::style::TextWrap,
124 truncate: crate::style::TextTruncate,
125 align: crate::style::TextAlign,
126 ) {
127 self.render_inner(buffer, focused_id, clip_rect, wrap, truncate, align, None, None);
128 }
129
130 pub fn render_with_parent(
132 &self,
133 buffer: &mut Buffer,
134 focused_id: Option<NodeId>,
135 clip_rect: Option<Rect>,
136 wrap: crate::style::TextWrap,
137 truncate: crate::style::TextTruncate,
138 align: crate::style::TextAlign,
139 parent_style: Option<&crate::style::Style>,
140 ) {
141 self.render_inner(
142 buffer,
143 focused_id,
144 clip_rect,
145 wrap,
146 truncate,
147 align,
148 None,
149 parent_style,
150 );
151 }
152
153 pub fn render_inner(
158 &self,
159 buffer: &mut Buffer,
160 focused_id: Option<NodeId>,
161 clip_rect: Option<Rect>,
162 wrap: crate::style::TextWrap,
163 truncate: crate::style::TextTruncate,
164 align: crate::style::TextAlign,
165 sheet: Option<&crate::style_parser::StyleSheet>,
166 parent_style: Option<&Style>,
167 ) {
168 let mut style = self.component.style();
169 if let Some(p) = parent_style {
170 style = crate::style_parser::inherit_style(p, &style);
171 }
172 if let Some(s) = sheet {
173 let r = s.resolve(
174 self.component.type_name(),
175 self.component.id(),
176 self.component.class(),
177 );
178 style = crate::style_parser::merge_styles(r, &style);
179 }
180 let mut cx = RenderCx::new(self.rect(), buffer, style);
181 cx.focused_id = focused_id;
182 cx.clip_rect = clip_rect;
183 cx.wrap = wrap;
184 cx.truncate = truncate;
185 cx.align = align;
186 self.component.render(&mut cx);
187 }
188
189 pub fn event(
193 &mut self,
194 event: &Event,
195 global_dirty: &mut Dirty,
196 quit: &mut bool,
197 task_tx: Option<std::sync::mpsc::Sender<String>>,
198 ) {
199 let mut stopped = false;
200 let mut cx = EventCx::with_task_sender(
201 &mut self.dirty,
202 global_dirty,
203 quit,
204 EventPhase::Target,
205 &mut stopped,
206 task_tx.clone(),
207 );
208 self.component.update(&mut cx);
209 self.component.event(event, &mut cx);
210 if !stopped {
211 for child in &mut self.children {
212 child.event(event, global_dirty, quit, task_tx.clone());
213 }
214 }
215 }
216
217 pub fn measure(&self, constraint: Constraint) -> Size {
219 let mut cx = MeasureCx { constraint };
220 self.component.measure(constraint, &mut cx)
221 }
222
223 pub fn mount(
225 &mut self,
226 global_dirty: &mut Dirty,
227 quit: &mut bool,
228 task_tx: Option<std::sync::mpsc::Sender<String>>,
229 ) {
230 let mut stopped = false;
231 let mut cx = EventCx::with_task_sender(
232 &mut self.dirty,
233 global_dirty,
234 quit,
235 EventPhase::Target,
236 &mut stopped,
237 task_tx.clone(),
238 );
239 self.component.mount(&mut cx);
240 for child in &mut self.children {
241 child.mount(global_dirty, quit, task_tx.clone());
242 }
243 }
244
245 pub fn debug_render(&self, buffer: &mut Buffer, focused_id: Option<NodeId>) {
250 let type_name = self.component.type_name();
251 let short = type_name.rsplit("::").next().unwrap_or(type_name);
252
253 let border = if focused_id == Some(self.id) {
254 crate::style::Border::Double
255 } else {
256 crate::style::Border::Rounded
257 };
258 let style = crate::style::Style::default().fg(crate::style::Color::Yellow);
259 buffer.draw_border(self.rect(), border, &style);
260
261 buffer.write_text(
262 Pos {
263 x: self.rect().x.saturating_add(2),
264 y: self.rect().y,
265 },
266 self.rect(),
267 short,
268 &style,
269 );
270
271 self.component.for_each_child(&mut |child: &Node| {
272 child.debug_render(buffer, focused_id);
273 });
274 }
275
276 pub fn unmount(
279 &mut self,
280 global_dirty: &mut Dirty,
281 quit: &mut bool,
282 task_tx: Option<std::sync::mpsc::Sender<String>>,
283 ) {
284 for child in &mut self.children {
285 child.unmount(global_dirty, quit, task_tx.clone());
286 }
287 let mut stopped = false;
288 let mut cx = EventCx::with_task_sender(
289 &mut self.dirty,
290 global_dirty,
291 quit,
292 EventPhase::Target,
293 &mut stopped,
294 task_tx,
295 );
296 self.component.unmount(&mut cx);
297 }
298
299 pub fn layout(&mut self, rect: Rect) {
305 self.set_rect(rect);
306 for child in &mut self.children {
307 child.parent = Some(self.id);
308 }
309 let mut cx = LayoutCx::new(&mut self.children);
310 self.component.layout(rect, &mut cx);
311 }
312
313 pub fn find_path_to(&self, target_id: NodeId) -> Option<Vec<NodeId>> {
317 if self.id == target_id {
318 return Some(vec![self.id]);
319 }
320 let mut result: Option<Vec<NodeId>> = None;
321 self.component.for_each_child(&mut |child: &Node| {
322 if result.is_none() {
323 if let Some(child_path) = child.find_path_to(target_id) {
324 let mut full = vec![self.id];
325 full.extend(child_path);
326 result = Some(full);
327 }
328 }
329 });
330 result
331 }
332
333 pub fn any_needs_paint(&self) -> bool {
335 self.dirty.contains(Dirty::PAINT)
336 || self.children.iter().any(|c| c.any_needs_paint())
337 }
338
339 pub fn any_needs_layout(&self) -> bool {
341 self.dirty.contains(Dirty::LAYOUT)
342 || self.children.iter().any(|c| c.any_needs_layout())
343 }
344
345 pub fn clear_dirty(&mut self) {
350 self.dirty = Dirty::NONE;
351 }
352
353 pub fn collect_focusable(&self, ids: &mut Vec<NodeId>) {
355 if self.component.focusable() {
356 ids.push(self.id);
357 }
358 self.component
359 .for_each_child(&mut |child: &Node| child.collect_focusable(ids));
360 }
361
362 pub fn hit_test(&self, pos: Pos) -> Option<NodeId> {
367 let mut hit: Option<NodeId> = None;
368 self.component.for_each_child(&mut |child: &Node| {
369 if let Some(id) = child.hit_test(pos) {
370 hit = Some(id);
371 }
372 });
373 if hit.is_none() && self.rect().contains(pos) {
374 hit = Some(self.id);
375 }
376 hit
377 }
378
379 pub fn send_event(
384 &mut self,
385 target_id: NodeId,
386 event: &Event,
387 global_dirty: &mut Dirty,
388 quit: &mut bool,
389 phase: EventPhase,
390 stopped: &mut bool,
391 task_tx: Option<std::sync::mpsc::Sender<String>>,
392 ) -> bool {
393 if *stopped {
394 return true;
395 }
396 if self.id == target_id {
397 let mut cx = EventCx::with_task_sender(
398 &mut self.dirty,
399 global_dirty,
400 quit,
401 phase,
402 stopped,
403 task_tx.clone(),
404 );
405 self.component.event(event, &mut cx);
406 return true;
407 }
408 let mut found = false;
409 self.component.for_each_child_mut(&mut |child: &mut Node| {
410 if !found && !*stopped {
411 found = child.send_event(
412 target_id, event, global_dirty, quit, phase, stopped, task_tx.clone(),
413 );
414 }
415 });
416 found
417 }
418}
419
420#[cfg(test)]
421mod tests {
422 use super::*;
423 use crate::widgets::Label;
424
425 #[test]
426 fn test_collect_focusable_simple() {
427 let label = Node::new(Label::new("test"));
428 let mut ids = Vec::new();
429 label.collect_focusable(&mut ids);
430 assert!(ids.is_empty());
432 }
433
434 #[test]
435 fn test_collect_focusable_tree() {
436 use crate::widgets::{Checkbox, Column};
438 let column = Node::new(
439 Column::new()
440 .child(Label::new("not focusable"))
441 .child(Checkbox::new("focusable"))
442 );
443 let mut ids = Vec::new();
444 column.collect_focusable(&mut ids);
445 assert_eq!(ids.len(), 1);
446 }
447
448 #[test]
449 fn test_collect_focusable_block() {
450 use crate::widgets::{Block, Checkbox, Column};
452 let column = Node::new(
453 Column::new()
454 .child(Block::new(Checkbox::new("inner")))
455 );
456 let mut ids = Vec::new();
457 column.collect_focusable(&mut ids);
458 assert_eq!(ids.len(), 1, "focusable inside Block should be found");
459 }
460}