lv-tui 0.4.0

A reactive TUI framework for Rust
Documentation
use crate::component::{Component, EventCx, LayoutCx, MeasureCx};
use crate::event::Event;
use crate::geom::{Rect, Size};
use crate::layout::Constraint;
use crate::node::Node;
use crate::render::RenderCx;
use crate::style::{Color, Style};

/// 模态弹窗容器
pub struct Overlay {
    background: Option<Node>,
    foreground: Option<Node>,
    active: bool,
}

impl Overlay {
    pub fn new(
        background: impl Component + 'static,
        foreground: impl Component + 'static,
    ) -> Self {
        Self {
            background: Some(Node::new(background)),
            foreground: Some(Node::new(foreground)),
            active: false,
        }
    }

    pub fn show(&mut self) { self.active = true; }
    pub fn hide(&mut self) { self.active = false; }
    pub fn is_active(&self) -> bool { self.active }
}

impl Component for Overlay {
    fn render(&self, cx: &mut RenderCx) {
        // 渲染背景
        if let Some(bg) = &self.background {
            bg.render_with_clip(cx.buffer, cx.focused_id, None);
        }

        if self.active {
            // 半透明遮罩
            let viewport = cx.rect;
            for y in viewport.y..viewport.y.saturating_add(viewport.height) {
                for x in viewport.x..viewport.x.saturating_add(viewport.width) {
                    if let Some(cell) = cx.buffer.get_mut(x, y) {
                        cell.style.bg = Some(Color::Black);
                        if cell.style.fg.is_none() {
                            cell.style.fg = Some(Color::Gray);
                        }
                    }
                }
            }

            // 渲染弹出框(居中)
            if let Some(fg) = &self.foreground {
                let fg_size = fg.measure(Constraint::loose(viewport.width.saturating_sub(4), viewport.height.saturating_sub(4)));
                let fg_w = fg_size.width.min(viewport.width.saturating_sub(4));
                let fg_h = fg_size.height.min(viewport.height.saturating_sub(4));
                let fg_x = viewport.x.saturating_add(viewport.width.saturating_sub(fg_w) / 2);
                let fg_y = viewport.y.saturating_add(viewport.height.saturating_sub(fg_h) / 2);
                let fg_rect = Rect { x: fg_x, y: fg_y, width: fg_w + 4, height: fg_h + 4 };

                // 白色背景
                for y in fg_rect.y..fg_rect.y.saturating_add(fg_rect.height) {
                    for x in fg_rect.x..fg_rect.x.saturating_add(fg_rect.width) {
                        if let Some(cell) = cx.buffer.get_mut(x, y) {
                            cell.style.bg = Some(Color::White);
                            cell.style.fg = Some(Color::Black);
                        }
                    }
                }

                // 渲染前景内容
                let inner = fg_rect.inner(crate::geom::Insets::all(2));
                let saved = fg.rect();
                fg.set_rect(inner);
                fg.render(cx.buffer, cx.focused_id);
                fg.set_rect(saved);
            }
        }
    }

    fn measure(&self, constraint: Constraint, _cx: &mut MeasureCx) -> Size {
        if let Some(bg) = &self.background {
            bg.measure(constraint)
        } else {
            Size { width: constraint.max.width, height: constraint.max.height }
        }
    }

    fn focusable(&self) -> bool { false }

    fn event(&mut self, event: &Event, cx: &mut EventCx) {
        if matches!(event, Event::Focus | Event::Blur | Event::Tick) { return; }

        if self.active {
            // Esc 关闭
            if let Event::Key(key) = event {
                if key.key == crate::event::Key::Esc {
                    self.hide();
                    cx.invalidate_paint();
                    return;
                }
            }
            // 前景处理事件
            if let Some(fg) = &mut self.foreground {
                let mut child_cx = EventCx::new(
                    &mut fg.dirty, cx.global_dirty, cx.quit, cx.phase, cx.propagation_stopped,
                );
                child_cx.task_sender = cx.task_sender.clone();
                fg.component.event(event, &mut child_cx);
            }
        } else {
            if let Some(bg) = &mut self.background {
                let mut child_cx = EventCx::new(
                    &mut bg.dirty, cx.global_dirty, cx.quit, cx.phase, cx.propagation_stopped,
                );
                child_cx.task_sender = cx.task_sender.clone();
                bg.component.event(event, &mut child_cx);
            }
        }
    }

    fn layout(&mut self, rect: Rect, _cx: &mut LayoutCx) {
        if let Some(bg) = &mut self.background {
            bg.layout(rect);
        }
        if let Some(fg) = &mut self.foreground {
            fg.layout(rect); // 前景测量后决定居中位置
        }
    }

    fn for_each_child(&self, f: &mut dyn FnMut(&Node)) {
        if self.active {
            if let Some(fg) = &self.foreground { f(fg); }
        } else {
            if let Some(bg) = &self.background { f(bg); }
        }
    }

    fn for_each_child_mut(&mut self, f: &mut dyn FnMut(&mut Node)) {
        if self.active {
            if let Some(fg) = &mut self.foreground { f(fg); }
        } else {
            if let Some(bg) = &mut self.background { f(bg); }
        }
    }

    fn style(&self) -> Style { Style::default() }
}