use std::cell::Cell;
use std::sync::atomic::{AtomicU64, Ordering};
use crate::buffer::Buffer;
use crate::component::{Component, EventCx, LayoutCx, MeasureCx};
use crate::dirty::Dirty;
use crate::event::{Event, EventPhase};
use crate::geom::{Pos, Rect, Size};
use crate::layout::Constraint;
use crate::render::RenderCx;
use crate::style::{Style, TextAlign, TextTruncate, TextWrap};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct NodeId(u64);
impl NodeId {
pub fn new() -> Self {
static NEXT: AtomicU64 = AtomicU64::new(1);
Self(NEXT.fetch_add(1, Ordering::Relaxed))
}
pub const ROOT: NodeId = NodeId(0);
}
pub struct Node {
pub id: NodeId,
pub rect: Cell<Rect>,
pub dirty: Dirty,
pub parent: Option<NodeId>,
pub component: Box<dyn Component>,
pub children: Vec<Node>,
}
impl Node {
pub fn rect(&self) -> Rect { self.rect.get() }
pub fn set_rect(&self, r: Rect) { self.rect.set(r); }
pub fn new(component: impl Component + 'static) -> Self {
Self {
id: NodeId::new(),
rect: Cell::new(Rect::default()),
dirty: Dirty::NONE,
parent: None,
component: Box::new(component),
children: Vec::new(),
}
}
pub fn root(component: impl Component + 'static) -> Self {
Self {
id: NodeId::ROOT,
rect: Cell::new(Rect::default()),
dirty: Dirty::NONE,
parent: None,
component: Box::new(component),
children: Vec::new(),
}
}
pub fn add_child(&mut self, child: Node) { self.children.push(child); }
pub fn render(&self, buffer: &mut Buffer, focused_id: Option<NodeId>) {
self.render_with_clip(buffer, focused_id, None);
}
pub fn render_with_clip(&self, buffer: &mut Buffer, focused_id: Option<NodeId>, clip_rect: Option<Rect>) {
self.render_with_clip_and_wrap(buffer, focused_id, clip_rect, TextWrap::None, TextTruncate::None, TextAlign::Left);
}
pub fn render_with_clip_and_wrap(
&self,
buffer: &mut Buffer,
focused_id: Option<NodeId>,
clip_rect: Option<Rect>,
wrap: crate::style::TextWrap,
truncate: crate::style::TextTruncate,
align: crate::style::TextAlign,
) {
self.render_inner(buffer, focused_id, clip_rect, wrap, truncate, align, None, None);
}
pub fn render_with_parent(
&self,
buffer: &mut Buffer,
focused_id: Option<NodeId>,
clip_rect: Option<Rect>,
wrap: crate::style::TextWrap,
truncate: crate::style::TextTruncate,
align: crate::style::TextAlign,
parent_style: Option<&crate::style::Style>,
) {
self.render_inner(buffer, focused_id, clip_rect, wrap, truncate, align, None, parent_style);
}
pub fn render_inner(
&self,
buffer: &mut Buffer,
focused_id: Option<NodeId>,
clip_rect: Option<Rect>,
wrap: crate::style::TextWrap,
truncate: crate::style::TextTruncate,
align: crate::style::TextAlign,
sheet: Option<&crate::style_parser::StyleSheet>,
parent_style: Option<&Style>,
) {
let mut style = self.component.style();
if let Some(p) = parent_style {
style = crate::style_parser::inherit_style(p, &style);
}
if let Some(s) = sheet {
let r = s.resolve(self.component.type_name(), self.component.id(), self.component.class());
style = crate::style_parser::merge_styles(r, &style);
}
let mut cx = RenderCx::new(self.rect(), buffer, style);
cx.focused_id = focused_id;
cx.clip_rect = clip_rect;
cx.wrap = wrap;
cx.truncate = truncate;
cx.align = align;
self.component.render(&mut cx);
}
pub fn event(&mut self, event: &Event, global_dirty: &mut Dirty, quit: &mut bool, task_tx: Option<std::sync::mpsc::Sender<String>>) {
let mut stopped = false;
let mut cx = EventCx::with_task_sender(&mut self.dirty, global_dirty, quit, EventPhase::Target, &mut stopped, task_tx.clone());
self.component.update(&mut cx);
self.component.event(event, &mut cx);
if !stopped {
for child in &mut self.children { child.event(event, global_dirty, quit, task_tx.clone()); }
}
}
pub fn measure(&self, constraint: Constraint) -> Size {
let mut cx = MeasureCx { constraint };
self.component.measure(constraint, &mut cx)
}
pub fn mount(&mut self, global_dirty: &mut Dirty, quit: &mut bool, task_tx: Option<std::sync::mpsc::Sender<String>>) {
let mut stopped = false;
let mut cx = EventCx::with_task_sender(&mut self.dirty, global_dirty, quit, EventPhase::Target, &mut stopped, task_tx.clone());
self.component.mount(&mut cx);
for child in &mut self.children { child.mount(global_dirty, quit, task_tx.clone()); }
}
pub fn debug_render(&self, buffer: &mut Buffer, focused_id: Option<NodeId>) {
let type_name = self.component.type_name();
let short = type_name.rsplit("::").next().unwrap_or(type_name);
let border = if focused_id == Some(self.id) {
crate::style::Border::Double
} else {
crate::style::Border::Rounded
};
let style = crate::style::Style::default().fg(crate::style::Color::Yellow);
buffer.draw_border(self.rect(), border, &style);
buffer.write_text(
Pos { x: self.rect().x.saturating_add(2), y: self.rect().y },
self.rect(),
short,
&style,
);
self.component.for_each_child(&mut |child: &Node| {
child.debug_render(buffer, focused_id);
});
}
pub fn unmount(&mut self, global_dirty: &mut Dirty, quit: &mut bool, task_tx: Option<std::sync::mpsc::Sender<String>>) {
for child in &mut self.children { child.unmount(global_dirty, quit, task_tx.clone()); }
let mut stopped = false;
let mut cx = EventCx::with_task_sender(&mut self.dirty, global_dirty, quit, EventPhase::Target, &mut stopped, task_tx);
self.component.unmount(&mut cx);
}
pub fn layout(&mut self, rect: Rect) {
self.set_rect(rect);
for child in &mut self.children { child.parent = Some(self.id); }
let mut cx = LayoutCx::new(&mut self.children);
self.component.layout(rect, &mut cx);
}
pub fn find_path_to(&self, target_id: NodeId) -> Option<Vec<NodeId>> {
if self.id == target_id { return Some(vec![self.id]); }
let mut result: Option<Vec<NodeId>> = None;
self.component.for_each_child(&mut |child: &Node| {
if result.is_none() {
if let Some(child_path) = child.find_path_to(target_id) {
let mut full = vec![self.id];
full.extend(child_path);
result = Some(full);
}
}
});
result
}
pub fn any_needs_paint(&self) -> bool {
self.dirty.contains(Dirty::PAINT) || self.children.iter().any(|c| c.any_needs_paint())
}
pub fn any_needs_layout(&self) -> bool {
self.dirty.contains(Dirty::LAYOUT) || self.children.iter().any(|c| c.any_needs_layout())
}
pub fn clear_dirty(&mut self) {
self.dirty = Dirty::NONE;
}
pub fn collect_focusable(&self, ids: &mut Vec<NodeId>) {
if self.component.focusable() { ids.push(self.id); }
self.component.for_each_child(&mut |child: &Node| child.collect_focusable(ids));
}
pub fn hit_test(&self, pos: Pos) -> Option<NodeId> {
let mut hit: Option<NodeId> = None;
self.component.for_each_child(&mut |child: &Node| {
if let Some(id) = child.hit_test(pos) { hit = Some(id); }
});
if hit.is_none() && self.rect().contains(pos) { hit = Some(self.id); }
hit
}
pub fn send_event(&mut self, target_id: NodeId, event: &Event, global_dirty: &mut Dirty, quit: &mut bool, phase: EventPhase, stopped: &mut bool, task_tx: Option<std::sync::mpsc::Sender<String>>) -> bool {
if *stopped { return true; }
if self.id == target_id {
let mut cx = EventCx::with_task_sender(&mut self.dirty, global_dirty, quit, phase, stopped, task_tx.clone());
self.component.event(event, &mut cx);
return true;
}
let mut found = false;
self.component.for_each_child_mut(&mut |child: &mut Node| {
if !found && !*stopped {
found = child.send_event(target_id, event, global_dirty, quit, phase, stopped, task_tx.clone());
}
});
found
}
}