lv-tui 0.2.0

A reactive TUI framework for Rust, inspired by Textual and React
Documentation
use crate::dirty::Dirty;
use crate::event::{Command, Event, EventPhase};
use crate::geom::{Rect, Size};
use crate::layout::Constraint;
use crate::node::Node;
use crate::render::RenderCx;
use crate::style::Style;

/// 组件 trait——应用的基本组织单元
pub trait Component {
    /// 渲染组件内容
    fn render(&self, cx: &mut RenderCx);

    /// 处理事件(可选实现)
    fn event(&mut self, _event: &Event, _cx: &mut EventCx) {}

    /// 返回组件样式(可选实现)
    fn style(&self) -> Style {
        Style::default()
    }

    /// 测量组件在给定约束下的自适应尺寸
    ///
    /// 默认实现:单行高度,宽度填满可用空间。
    fn measure(&self, constraint: Constraint, _cx: &mut MeasureCx) -> Size {
        Size {
            width: constraint.max.width,
            height: 1,
        }
    }

    /// 布局回调——组件在此计算子节点 rect 并调用 child.layout()
    ///
    /// 默认实现:无子节点,不执行任何操作。
    fn layout(&mut self, _rect: Rect, _cx: &mut LayoutCx) {}

    /// 首次挂载(初始化后调用)
    fn mount(&mut self, _cx: &mut EventCx) {}
    /// 从树卸载(退出前调用)
    fn unmount(&mut self, _cx: &mut EventCx) {}
    /// 每次事件分发前调用
    fn update(&mut self, _cx: &mut EventCx) {}

    /// 组件类型名(用于样式表类型选择器匹配)
    fn type_name(&self) -> &str {
        std::any::type_name::<Self>()
            .split("::")
            .last()
            .unwrap_or("Unknown")
    }

    /// 组件 ID(用于样式表 #id 选择器匹配)
    fn id(&self) -> Option<&str> {
        None
    }

    /// 组件 class(用于样式表 .class 选择器匹配)
    fn class(&self) -> Option<&str> {
        None
    }

    /// 是否可聚焦(Tab 导航会跳过不可聚焦的组件)
    fn focusable(&self) -> bool {
        true
    }

    /// 遍历子节点(为焦点系统等提供统一的树遍历接口)
    fn for_each_child(&self, _f: &mut dyn FnMut(&Node)) {}

    /// 遍历子节点(可变版本)
    fn for_each_child_mut(&mut self, _f: &mut dyn FnMut(&mut Node)) {}
}

/// 事件上下文——组件通过它标记 dirty、退出应用等
///
/// 每个 Node 拥有独立的 EventCx,dirty 标记精确到当前节点,
/// 同时通过 global_dirty 确保 Runtime 感知到变更。
pub struct EventCx<'a> {
    pub(crate) node_dirty: &'a mut Dirty,
    pub(crate) global_dirty: &'a mut Dirty,
    pub(crate) quit: &'a mut bool,
    pub(crate) phase: EventPhase,
    pub(crate) propagation_stopped: &'a mut bool,
    pub(crate) task_sender: Option<std::sync::mpsc::Sender<String>>,
}

impl<'a> EventCx<'a> {
    pub(crate) fn new(
        node_dirty: &'a mut Dirty,
        global_dirty: &'a mut Dirty,
        quit: &'a mut bool,
        phase: EventPhase,
        propagation_stopped: &'a mut bool,
    ) -> Self {
        Self {
            node_dirty,
            global_dirty,
            quit,
            phase,
            propagation_stopped,
            task_sender: None,
        }
    }

    pub(crate) fn with_task_sender(
        node_dirty: &'a mut Dirty,
        global_dirty: &'a mut Dirty,
        quit: &'a mut bool,
        phase: EventPhase,
        propagation_stopped: &'a mut bool,
        task_sender: Option<std::sync::mpsc::Sender<String>>,
    ) -> Self {
        Self {
            node_dirty,
            global_dirty,
            quit,
            phase,
            propagation_stopped,
            task_sender,
        }
    }

    /// 当前事件传播阶段
    pub fn phase(&self) -> EventPhase {
        self.phase
    }

    /// 停止事件传播(后续阶段和祖先不再收到该事件)
    pub fn stop_propagation(&mut self) {
        *self.propagation_stopped = true;
    }

    /// 标记当前节点需要重绘
    pub fn invalidate_paint(&mut self) {
        *self.node_dirty |= Dirty::PAINT;
        *self.global_dirty |= Dirty::PAINT;
    }

    /// 标记当前节点需要重新布局并重绘
    pub fn invalidate_layout(&mut self) {
        *self.node_dirty |= Dirty::LAYOUT | Dirty::PAINT;
        *self.global_dirty |= Dirty::LAYOUT | Dirty::PAINT;
    }

    /// 标记需要重新布局(等同于 invalidate_layout)
    pub fn invalidate(&mut self) {
        self.invalidate_layout();
    }

    /// 标记组件树结构变化,触发完整 rebuild + relayout + repaint
    pub fn invalidate_tree(&mut self) {
        *self.node_dirty |= Dirty::TREE | Dirty::LAYOUT | Dirty::PAINT;
        *self.global_dirty |= Dirty::TREE | Dirty::LAYOUT | Dirty::PAINT;
    }

    /// Copy text to the system clipboard via OSC 52.
    ///
    /// Most modern terminals support this. The escape sequence is written
    /// directly to stdout.
    pub fn copy_to_clipboard(&self, text: &str) {
        use std::io::Write;
        let seq = crate::clipboard::copy_to_clipboard_sequence(text);
        let _ = std::io::stdout().write_all(seq.as_bytes());
        let _ = std::io::stdout().flush();
    }

    /// 退出应用
    pub fn quit(&mut self) {
        *self.quit = true;
    }

    /// 发送命令给 Runtime 统一处理(通过全局队列)
    pub fn dispatch(&mut self, cmd: Command) {
        crate::runtime::COMMAND_QUEUE.with(|q| {
            q.borrow_mut().push(cmd);
        });
    }

    /// 切换 debug 视图
    pub fn toggle_debug() {
        crate::runtime::DEBUG_MODE.with(|d| {
            let current = *d.borrow();
            *d.borrow_mut() = !current;
        });
    }

    /// 启动后台任务
    pub fn spawn<F>(&mut self, task: F)
    where
        F: FnOnce() -> String + Send + 'static,
    {
        if let Some(tx) = self.task_sender.clone() {
            std::thread::spawn(move || {
                let result = task();
                let _ = tx.send(result);
            });
        }
    }
}

/// 测量上下文——组件通过它测量自身和子组件的自适应尺寸
pub struct MeasureCx {
    pub constraint: Constraint,
}

/// 布局上下文——容器组件通过它为子节点分配 Rect
pub struct LayoutCx<'a> {
    children: &'a mut Vec<Node>,
}

impl<'a> LayoutCx<'a> {
    pub(crate) fn new(children: &'a mut Vec<Node>) -> Self {
        Self { children }
    }

    /// 获取子节点数量
    pub fn child_count(&self) -> usize {
        self.children.len()
    }

    /// 获取第 i 个子节点的 style
    pub fn child_style(&self, index: usize) -> Option<Style> {
        self.children.get(index).map(|n| n.component.style())
    }

    /// 为第 i 个子节点设置布局 rect(递归触发子节点 layout)
    pub fn layout_child(&mut self, index: usize, rect: Rect) {
        if let Some(child) = self.children.get_mut(index) {
            child.layout(rect);
        }
    }

    /// 遍历所有子节点
    pub fn for_each_child(&mut self, mut f: impl FnMut(usize, &mut Node)) {
        for (i, child) in self.children.iter_mut().enumerate() {
            f(i, child);
        }
    }
}