Skip to main content

lv_tui/widgets/
overlay.rs

1use crate::component::{Component, EventCx, LayoutCx, MeasureCx};
2use crate::event::Event;
3use crate::geom::{Rect, Size};
4use crate::layout::Constraint;
5use crate::node::Node;
6use crate::render::RenderCx;
7use crate::style::{Color, Style};
8
9/// 模态弹窗容器
10pub struct Overlay {
11    background: Option<Node>,
12    foreground: Option<Node>,
13    active: bool,
14}
15
16impl Overlay {
17    pub fn new(
18        background: impl Component + 'static,
19        foreground: impl Component + 'static,
20    ) -> Self {
21        Self {
22            background: Some(Node::new(background)),
23            foreground: Some(Node::new(foreground)),
24            active: false,
25        }
26    }
27
28    pub fn show(&mut self) { self.active = true; }
29    pub fn hide(&mut self) { self.active = false; }
30    pub fn is_active(&self) -> bool { self.active }
31}
32
33impl Component for Overlay {
34    fn render(&self, cx: &mut RenderCx) {
35        // 渲染背景
36        if let Some(bg) = &self.background {
37            bg.render_with_clip(cx.buffer, cx.focused_id, None);
38        }
39
40        if self.active {
41            // 半透明遮罩
42            let viewport = cx.rect;
43            for y in viewport.y..viewport.y.saturating_add(viewport.height) {
44                for x in viewport.x..viewport.x.saturating_add(viewport.width) {
45                    if let Some(cell) = cx.buffer.get_mut(x, y) {
46                        cell.style.bg = Some(Color::Black);
47                        if cell.style.fg.is_none() {
48                            cell.style.fg = Some(Color::Gray);
49                        }
50                    }
51                }
52            }
53
54            // 渲染弹出框(居中)
55            if let Some(fg) = &self.foreground {
56                let fg_size = fg.measure(Constraint::loose(viewport.width.saturating_sub(4), viewport.height.saturating_sub(4)));
57                let fg_w = fg_size.width.min(viewport.width.saturating_sub(4));
58                let fg_h = fg_size.height.min(viewport.height.saturating_sub(4));
59                let fg_x = viewport.x.saturating_add(viewport.width.saturating_sub(fg_w) / 2);
60                let fg_y = viewport.y.saturating_add(viewport.height.saturating_sub(fg_h) / 2);
61                let fg_rect = Rect { x: fg_x, y: fg_y, width: fg_w + 4, height: fg_h + 4 };
62
63                // 白色背景
64                for y in fg_rect.y..fg_rect.y.saturating_add(fg_rect.height) {
65                    for x in fg_rect.x..fg_rect.x.saturating_add(fg_rect.width) {
66                        if let Some(cell) = cx.buffer.get_mut(x, y) {
67                            cell.style.bg = Some(Color::White);
68                            cell.style.fg = Some(Color::Black);
69                        }
70                    }
71                }
72
73                // 渲染前景内容
74                let inner = fg_rect.inner(crate::geom::Insets::all(2));
75                let saved = fg.rect();
76                fg.set_rect(inner);
77                fg.render(cx.buffer, cx.focused_id);
78                fg.set_rect(saved);
79            }
80        }
81    }
82
83    fn measure(&self, constraint: Constraint, _cx: &mut MeasureCx) -> Size {
84        if let Some(bg) = &self.background {
85            bg.measure(constraint)
86        } else {
87            Size { width: constraint.max.width, height: constraint.max.height }
88        }
89    }
90
91    fn focusable(&self) -> bool { false }
92
93    fn event(&mut self, event: &Event, cx: &mut EventCx) {
94        if matches!(event, Event::Focus | Event::Blur | Event::Tick) { return; }
95
96        if self.active {
97            // Esc 关闭
98            if let Event::Key(key) = event {
99                if key.key == crate::event::Key::Esc {
100                    self.hide();
101                    cx.invalidate_paint();
102                    return;
103                }
104            }
105            // 前景处理事件
106            if let Some(fg) = &mut self.foreground {
107                let mut child_cx = EventCx::new(
108                    &mut fg.dirty, cx.global_dirty, cx.quit, cx.phase, cx.propagation_stopped,
109                );
110                child_cx.task_sender = cx.task_sender.clone();
111                fg.component.event(event, &mut child_cx);
112            }
113        } else {
114            if let Some(bg) = &mut self.background {
115                let mut child_cx = EventCx::new(
116                    &mut bg.dirty, cx.global_dirty, cx.quit, cx.phase, cx.propagation_stopped,
117                );
118                child_cx.task_sender = cx.task_sender.clone();
119                bg.component.event(event, &mut child_cx);
120            }
121        }
122    }
123
124    fn layout(&mut self, rect: Rect, _cx: &mut LayoutCx) {
125        if let Some(bg) = &mut self.background {
126            bg.layout(rect);
127        }
128        if let Some(fg) = &mut self.foreground {
129            fg.layout(rect); // 前景测量后决定居中位置
130        }
131    }
132
133    fn for_each_child(&self, f: &mut dyn FnMut(&Node)) {
134        if self.active {
135            if let Some(fg) = &self.foreground { f(fg); }
136        } else {
137            if let Some(bg) = &self.background { f(bg); }
138        }
139    }
140
141    fn for_each_child_mut(&mut self, f: &mut dyn FnMut(&mut Node)) {
142        if self.active {
143            if let Some(fg) = &mut self.foreground { f(fg); }
144        } else {
145            if let Some(bg) = &mut self.background { f(bg); }
146        }
147    }
148
149    fn style(&self) -> Style { Style::default() }
150}