lv-tui 0.1.1

A reactive TUI framework for Rust, inspired by Textual and React
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::Style;

/// 可滚动容器——内容可大于可视区域
pub struct Scroll {
    child: Option<Node>,
    scroll_y: u16,
    content_height: u16,
}

impl Scroll {
    pub fn new(child: impl Component + 'static) -> Self {
        Self {
            child: Some(Node::new(child)),
            scroll_y: 0,
            content_height: 0,
        }
    }
}

impl Component for Scroll {
    fn render(&self, cx: &mut RenderCx) {
        if let Some(child) = &self.child {
            let viewport = cx.rect;
            let max_scroll = self.content_height.saturating_sub(viewport.height);
            let scroll = self.scroll_y.min(max_scroll);

            // 递归偏移所有后代 rect(wrapping,避免饱和导致不对称)
            fn offset_all(node: &Node, delta: i16) {
                let mut r = node.rect();
                if delta >= 0 {
                    r.y = r.y.wrapping_add(delta as u16);
                } else {
                    r.y = r.y.wrapping_sub((-delta) as u16);
                }
                node.set_rect(r);
                node.component.for_each_child(&mut |c: &Node| offset_all(c, delta));
            }

            let delta = -(scroll as i16);
            offset_all(child, delta);
            child.render_with_clip(cx.buffer, cx.focused_id, Some(viewport));
            offset_all(child, -delta); // wrapping 可以精确恢复
        }
    }

    fn measure(&self, constraint: Constraint, _cx: &mut MeasureCx) -> Size {
        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) {
            return;
        }

        if let Event::Key(key_event) = event {
            match &key_event.key {
                crate::event::Key::Up => {
                    self.scroll_y = self.scroll_y.saturating_sub(1);
                    cx.invalidate_paint();
                    return;
                }
                crate::event::Key::Down => {
                    self.scroll_y = self.scroll_y.saturating_add(1);
                    cx.invalidate_paint();
                    return;
                }
                _ => {}
            }
        }

        if let Event::Mouse(mouse_event) = event {
            match mouse_event.kind {
                crate::event::MouseKind::ScrollUp => {
                    self.scroll_y = self.scroll_y.saturating_sub(1);
                    cx.invalidate_paint();
                    return;
                }
                crate::event::MouseKind::ScrollDown => {
                    self.scroll_y = self.scroll_y.saturating_add(1);
                    cx.invalidate_paint();
                    return;
                }
                _ => {}
            }
        }

        // 转发事件给子组件
        if let Some(child) = &mut self.child {
            let mut child_cx = EventCx::new(
                &mut child.dirty,
                cx.global_dirty,
                cx.quit,
                cx.phase,
                cx.propagation_stopped,
            );
            child.component.event(event, &mut child_cx);
        }
    }

    fn layout(&mut self, rect: Rect, _cx: &mut LayoutCx) {
        if let Some(child) = &mut self.child {
            let child_size = child.measure(Constraint::loose(rect.width, 65535));
            self.content_height = child_size.height;
            let child_rect = Rect {
                x: rect.x,
                y: rect.y,
                width: rect.width,
                height: child_size.height,
            };
            child.layout(child_rect);
        }
    }

    fn for_each_child(&self, f: &mut dyn FnMut(&Node)) {
        if let Some(child) = &self.child {
            f(child);
        }
    }

    fn for_each_child_mut(&mut self, f: &mut dyn FnMut(&mut Node)) {
        if let Some(child) = &mut self.child {
            f(child);
        }
    }

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