lv-tui 0.3.0

A reactive TUI framework for Rust, inspired by Textual and React
Documentation
use crate::backend::{CrosstermBackend, TerminalBackend};
use crate::buffer::Buffer;
use crate::component::Component;
use crate::dirty::Dirty;
use std::cell::RefCell;

use crate::event::{Command, Event, EventPhase, Key, KeyEvent};
use crate::geom::Rect;
use crate::geom::Pos;
use crate::node::{Node, NodeId};
use crate::Result;

thread_local! {
    /// Global command queue. Components push [`Command`] values here; the
    /// runtime drains and executes them at the top of each event-loop tick.
    pub static COMMAND_QUEUE: RefCell<Vec<Command>> = RefCell::new(Vec::new());
    /// Global debug-mode flag. Toggled by the `d` key. When `true`, the
    /// runtime renders component borders and labels on top of normal output.
    pub static DEBUG_MODE: RefCell<bool> = RefCell::new(false);
    /// Set to `true` by `set_theme()` to force a full repaint (not diff)
    /// on the next paint cycle, so color changes take effect immediately.
    pub static FORCE_FULL_PAINT: RefCell<bool> = RefCell::new(false);
}

/// The application runtime.
///
/// Owns the component tree, terminal backend, double-buffer, and event loop.
/// Constructed by [`App::run`](crate::app::App::run) — most users don't
/// need to interact with `Runtime` directly.
pub struct Runtime {
    root: Node,
    backend: CrosstermBackend,
    front: Buffer,
    back: Buffer,
    root_rect: Rect,
    quit: bool,
    full_paint: bool,
    dirty: Dirty,
    focused_id: Option<NodeId>,
    tick_rate: std::time::Duration,
    task_rx: std::sync::mpsc::Receiver<String>,
    task_tx: std::sync::mpsc::Sender<String>,
}

const ZERO_RECT: Rect = Rect {
    x: 0,
    y: 0,
    width: 0,
    height: 0,
};

impl Runtime {
    /// Creates a new runtime wrapping the given root component.
    pub fn new(root: impl Component + 'static) -> Result<Self> {
        let (tx, rx) = std::sync::mpsc::channel::<String>();
        Ok(Self {
            root: Node::root(root),
            backend: CrosstermBackend::new()?,
            front: Buffer::new(crate::geom::Size { width: 1, height: 1 }),
            back: Buffer::new(crate::geom::Size { width: 1, height: 1 }),
            root_rect: ZERO_RECT,
            quit: false,
            full_paint: false,
            dirty: Dirty::NONE,
            focused_id: None,
            tick_rate: std::time::Duration::from_millis(250),
            task_rx: rx,
            task_tx: tx,
        })
    }

    /// Builder: sets the tick interval (default 250ms).
    pub fn tick_rate(mut self, rate: std::time::Duration) -> Self {
        self.tick_rate = rate;
        self
    }

    /// Starts the event loop. Blocks until the application exits.
    pub fn run(&mut self) -> Result<()> {
        self.backend.enter()?;
        self.initial_layout_and_paint()?;

        while !self.quit {
            // 处理全局命令队列
            let commands: Vec<Command> = COMMAND_QUEUE.with(|q| q.borrow_mut().drain(..).collect());
            for cmd in commands {
                match cmd {
                    Command::Quit => self.quit = true,
                    Command::FocusNext => self.focus_next(false),
                    Command::FocusPrev => self.focus_next(true),
                    Command::Custom(_) => {}
                }
            }

            // 检查任务完成
            while let Ok(result) = self.task_rx.try_recv() {
                self.dispatch_event(Event::TaskComplete(result))?;
                // 任务完成后立即 paint
                if self.dirty.contains(Dirty::PAINT) {
                    self.paint()?;
                    self.dirty = Dirty::NONE;
                }
            }

            let event = match self.backend.poll_event(
                if self.task_rx.try_recv().is_ok() {
                    std::time::Duration::from_millis(0)
                } else {
                    self.tick_rate
                },
            )? {
                Some(e) => e,
                None => Event::Tick,
            };
            self.dispatch_event(event)?;

            if self.dirty.intersects(Dirty::LAYOUT | Dirty::TREE) {
                self.layout()?;
            }

            if self.dirty.contains(Dirty::PAINT) {
                self.paint()?;
                self.dirty = Dirty::NONE;
            }
        }

        // 卸载生命周期
        self.root.unmount(&mut self.dirty, &mut self.quit, Some(self.task_tx.clone()));
        self.backend.leave()?;
        Ok(())
    }

    fn initial_layout_and_paint(&mut self) -> Result<()> {
        let size = self.backend.size()?;

        self.root_rect = Rect {
            x: 0,
            y: 0,
            width: size.width,
            height: size.height,
        };

        self.front = Buffer::new(size);
        self.back = Buffer::new(size);

        self.full_paint = true;
        self.dirty = Dirty::LAYOUT | Dirty::PAINT;

        self.layout()?;

        // 挂载生命周期
        self.root.mount(&mut self.dirty, &mut self.quit, Some(self.task_tx.clone()));

        // 启动时自动聚焦第一个可聚焦组件
        let mut ids = Vec::new();
        self.root.collect_focusable(&mut ids);
        if !ids.is_empty() {
            self.focused_id = Some(ids[0]);
        }

        self.paint()?;
        self.dirty = Dirty::NONE;

        Ok(())
    }

    fn dispatch_event(&mut self, event: Event) -> Result<()> {
        match event {
            Event::Resize(size) => {
                let new_rect = Rect {
                    x: 0,
                    y: 0,
                    width: size.width,
                    height: size.height,
                };

                if new_rect != self.root_rect {
                    self.root_rect = new_rect;
                    self.front.resize(size);
                    self.back.resize(size);
                    self.full_paint = true;
                    self.dirty = Dirty::LAYOUT | Dirty::PAINT;
                }
            }
            Event::Key(KeyEvent { key: Key::Tab, modifiers }) => {
                self.focus_next(modifiers.shift);
            }
            Event::Mouse(mouse_event) => {
                let pos = Pos {
                    x: mouse_event.x,
                    y: mouse_event.y,
                };
                if let Some(id) = self.root.hit_test(pos) {
                    self.dispatch_to_target(id, &Event::Mouse(mouse_event));
                }
            }
            Event::Focus | Event::Blur => {
                // 焦点事件由 set_focus 内发送
            }
            other => {
                // 键盘等事件发给焦点组件(有焦点时)或 root
                let target = self.focused_id.unwrap_or(NodeId::ROOT);
                self.dispatch_to_target(target, &other);
            }
        }

        Ok(())
    }

    fn focus_next(&mut self, backwards: bool) {
        let mut ids = Vec::new();
        self.root.collect_focusable(&mut ids);

        if ids.is_empty() {
            return;
        }

        let old_id = self.focused_id;

        let new_idx = match old_id {
            None => {
                if backwards {
                    ids.len() - 1
                } else {
                    0
                }
            }
            Some(old) => {
                if let Some(pos) = ids.iter().position(|id| *id == old) {
                    if backwards {
                        if pos == 0 {
                            ids.len() - 1
                        } else {
                            pos - 1
                        }
                    } else if pos + 1 >= ids.len() {
                        0
                    } else {
                        pos + 1
                    }
                } else {
                    0
                }
            }
        };

        let new_id = ids[new_idx];
        self.set_focus(new_id);
    }

    /// 三阶段事件分发:capture → target → bubble
    fn dispatch_to_target(&mut self, target_id: NodeId, event: &Event) {
        // 找到从 root 到 target 的完整路径
        let (path, effective_target) = match self.root.find_path_to(target_id) {
                Some(p) => (p, target_id),
                None => (vec![NodeId::ROOT], NodeId::ROOT),
            };

        let mut stopped = false;

        // Capture: root → target 的祖先(正序,不含 target 自身)
        for &node_id in &path[..path.len().saturating_sub(1)] {
            if stopped {
                break;
            }
            self.root.send_event(
                node_id,
                event,
                &mut self.dirty,
                &mut self.quit,
                EventPhase::Capture,
                &mut stopped,
                Some(self.task_tx.clone()),
            );
        }

        // Target: 目标自身
        if !stopped {
            self.root.send_event(
                effective_target,
                event,
                &mut self.dirty,
                &mut self.quit,
                EventPhase::Target,
                &mut stopped,
                Some(self.task_tx.clone()),
            );
        }

        // Bubble: target 的祖先 → root(逆序,不含 target)
        for &node_id in path[..path.len().saturating_sub(1)].iter().rev() {
            if stopped {
                break;
            }
            self.root.send_event(
                node_id,
                event,
                &mut self.dirty,
                &mut self.quit,
                EventPhase::Bubble,
                &mut stopped,
                Some(self.task_tx.clone()),
            );
        }
    }

    fn set_focus(&mut self, new_id: NodeId) {
        let old_id = self.focused_id;

        // 发送 Blur 给旧焦点(直接目标,不冒泡)
        if let Some(old) = old_id {
            if old != new_id {
                let mut stopped = false;
                self.root.send_event(
                    old,
                    &Event::Blur,
                    &mut self.dirty,
                    &mut self.quit,
                    EventPhase::Target,
                    &mut stopped,
                Some(self.task_tx.clone()),
                );
            }
        }

        self.focused_id = Some(new_id);

        // 发送 Focus 给新焦点
        if old_id != Some(new_id) {
            let mut stopped = false;
            self.root.send_event(
                new_id,
                &Event::Focus,
                &mut self.dirty,
                &mut self.quit,
                EventPhase::Target,
                &mut stopped,
                Some(self.task_tx.clone()),
            );
        }

        self.dirty |= Dirty::PAINT;
    }

    fn layout(&mut self) -> Result<()> {
        self.root.layout(self.root_rect);
        Ok(())
    }

    fn paint(&mut self) -> Result<()> {
        self.back.clear();
        self.root.render_with_clip(&mut self.back, self.focused_id, None);

        // 全局 debug 渲染
        if DEBUG_MODE.with(|d| *d.borrow()) {
            self.root.debug_render(&mut self.back, self.focused_id);
        }

        if self.full_paint || FORCE_FULL_PAINT.with(|f| f.replace(false)) {
            let ops = self.back.all_ops();
            self.backend.flush(&ops)?;
            self.full_paint = false;
        } else {
            let ops = self.front.diff(&self.back);
            self.backend.flush(&ops)?;
        }

        std::mem::swap(&mut self.front, &mut self.back);

        Ok(())
    }
}