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 use crate::style_parser::WidgetState;
174 let focus_within = focused_id.map_or(false, |fid| {
176 if fid == self.id { true }
177 else {
178 let mut ids = Vec::new();
179 self.collect_focusable(&mut ids);
180 ids.contains(&fid)
181 }
182 });
183 let state = WidgetState {
184 focused: focused_id == Some(self.id),
185 focus_within,
186 ..WidgetState::default()
187 };
188 let r = s.resolve(
189 self.component.type_name(),
190 self.component.id(),
191 self.component.class(),
192 &state,
193 );
194 style = crate::style_parser::merge_styles(r, &style);
195 }
196 let mut cx = RenderCx::new(self.rect(), buffer, style);
197 cx.focused_id = focused_id;
198 cx.clip_rect = clip_rect;
199 cx.wrap = wrap;
200 cx.truncate = truncate;
201 cx.align = align;
202 self.component.render(&mut cx);
203 }
204
205 pub fn event(
209 &mut self,
210 event: &Event,
211 global_dirty: &mut Dirty,
212 quit: &mut bool,
213 task_tx: Option<std::sync::mpsc::Sender<String>>,
214 ) {
215 let mut stopped = false;
216 let mut cx = EventCx::with_task_sender(
217 &mut self.dirty,
218 global_dirty,
219 quit,
220 EventPhase::Target,
221 &mut stopped,
222 task_tx.clone(),
223 );
224 cx.current_node_id = self.id;
225 self.component.update(&mut cx);
226 self.component.event(event, &mut cx);
227 if !stopped {
228 for child in &mut self.children {
229 child.event(event, global_dirty, quit, task_tx.clone());
230 }
231 }
232 }
233
234 pub fn measure(&self, constraint: Constraint) -> Size {
236 let mut cx = MeasureCx { constraint };
237 self.component.measure(constraint, &mut cx)
238 }
239
240 pub fn mount(
242 &mut self,
243 global_dirty: &mut Dirty,
244 quit: &mut bool,
245 task_tx: Option<std::sync::mpsc::Sender<String>>,
246 ) {
247 let mut stopped = false;
248 let mut cx = EventCx::with_task_sender(
249 &mut self.dirty,
250 global_dirty,
251 quit,
252 EventPhase::Target,
253 &mut stopped,
254 task_tx.clone(),
255 );
256 cx.current_node_id = self.id;
257 self.component.mount(&mut cx);
258 for child in &mut self.children {
259 child.mount(global_dirty, quit, task_tx.clone());
260 }
261 }
262
263 pub fn debug_render(&self, buffer: &mut Buffer, focused_id: Option<NodeId>) {
268 let type_name = self.component.type_name();
269 let short = type_name.rsplit("::").next().unwrap_or(type_name);
270
271 let border = if focused_id == Some(self.id) {
272 crate::style::Border::Double
273 } else {
274 crate::style::Border::Rounded
275 };
276 let style = crate::style::Style::default().fg(crate::style::Color::Yellow);
277 buffer.draw_border(self.rect(), border, &style);
278
279 buffer.write_text(
280 Pos {
281 x: self.rect().x.saturating_add(2),
282 y: self.rect().y,
283 },
284 self.rect(),
285 short,
286 &style,
287 );
288
289 self.component.for_each_child(&mut |child: &Node| {
290 child.debug_render(buffer, focused_id);
291 });
292 }
293
294 pub fn unmount(
297 &mut self,
298 global_dirty: &mut Dirty,
299 quit: &mut bool,
300 task_tx: Option<std::sync::mpsc::Sender<String>>,
301 ) {
302 for child in &mut self.children {
303 child.unmount(global_dirty, quit, task_tx.clone());
304 }
305 let mut stopped = false;
306 let mut cx = EventCx::with_task_sender(
307 &mut self.dirty,
308 global_dirty,
309 quit,
310 EventPhase::Target,
311 &mut stopped,
312 task_tx,
313 );
314 self.component.unmount(&mut cx);
315 }
316
317 pub fn layout(&mut self, rect: Rect) {
323 self.set_rect(rect);
324 for child in &mut self.children {
325 child.parent = Some(self.id);
326 }
327 let mut cx = LayoutCx::new(&mut self.children);
328 self.component.layout(rect, &mut cx);
329 }
330
331 pub fn find_path_to(&self, target_id: NodeId) -> Option<Vec<NodeId>> {
335 if self.id == target_id {
336 return Some(vec![self.id]);
337 }
338 let mut result: Option<Vec<NodeId>> = None;
339 self.component.for_each_child(&mut |child: &Node| {
340 if result.is_none() {
341 if let Some(child_path) = child.find_path_to(target_id) {
342 let mut full = vec![self.id];
343 full.extend(child_path);
344 result = Some(full);
345 }
346 }
347 });
348 result
349 }
350
351 pub fn any_needs_paint(&self) -> bool {
353 self.dirty.contains(Dirty::PAINT)
354 || self.children.iter().any(|c| c.any_needs_paint())
355 }
356
357 pub fn any_needs_layout(&self) -> bool {
359 self.dirty.contains(Dirty::LAYOUT)
360 || self.children.iter().any(|c| c.any_needs_layout())
361 }
362
363 pub fn clear_dirty(&mut self) {
368 self.dirty = Dirty::NONE;
369 }
370
371 pub fn collect_focusable(&self, ids: &mut Vec<NodeId>) {
373 if self.component.focusable() {
374 ids.push(self.id);
375 }
376 self.component
377 .for_each_child(&mut |child: &Node| child.collect_focusable(ids));
378 }
379
380 pub fn hit_test(&self, pos: Pos) -> Option<NodeId> {
385 let mut hit: Option<NodeId> = None;
386 self.component.for_each_child(&mut |child: &Node| {
387 if let Some(id) = child.hit_test(pos) {
388 hit = Some(id);
389 }
390 });
391 if hit.is_none() && self.rect().contains(pos) {
392 hit = Some(self.id);
393 }
394 hit
395 }
396
397 pub fn send_event(
402 &mut self,
403 target_id: NodeId,
404 event: &Event,
405 global_dirty: &mut Dirty,
406 quit: &mut bool,
407 phase: EventPhase,
408 stopped: &mut bool,
409 task_tx: Option<std::sync::mpsc::Sender<String>>,
410 ) -> bool {
411 if *stopped {
412 return true;
413 }
414 if self.id == target_id {
415 let mut cx = EventCx::with_task_sender(
416 &mut self.dirty,
417 global_dirty,
418 quit,
419 phase,
420 stopped,
421 task_tx.clone(),
422 );
423 cx.current_node_id = self.id;
424 self.component.event(event, &mut cx);
425 return true;
426 }
427 let mut found = false;
428 self.component.for_each_child_mut(&mut |child: &mut Node| {
429 if !found && !*stopped {
430 found = child.send_event(
431 target_id, event, global_dirty, quit, phase, stopped, task_tx.clone(),
432 );
433 }
434 });
435 found
436 }
437}
438
439#[cfg(test)]
440mod tests {
441 use super::*;
442 use crate::widgets::Label;
443
444 #[test]
445 fn test_collect_focusable_simple() {
446 let label = Node::new(Label::new("test"));
447 let mut ids = Vec::new();
448 label.collect_focusable(&mut ids);
449 assert!(ids.is_empty());
451 }
452
453 #[test]
454 fn test_collect_focusable_tree() {
455 use crate::widgets::{Checkbox, Column};
457 let column = Node::new(
458 Column::new()
459 .child(Label::new("not focusable"))
460 .child(Checkbox::new("focusable"))
461 );
462 let mut ids = Vec::new();
463 column.collect_focusable(&mut ids);
464 assert_eq!(ids.len(), 1);
465 }
466
467 #[test]
468 fn test_collect_focusable_block() {
469 use crate::widgets::{Block, Checkbox, Column};
471 let column = Node::new(
472 Column::new()
473 .child(Block::new(Checkbox::new("inner")))
474 );
475 let mut ids = Vec::new();
476 column.collect_focusable(&mut ids);
477 assert_eq!(ids.len(), 1, "focusable inside Block should be found");
478 }
479}