Skip to main content

lv_tui/widgets/
block.rs

1use crate::component::{Component, EventCx, LayoutCx, MeasureCx};
2use crate::event::Event;
3use crate::geom::{Insets, Pos, Rect, Size};
4use crate::layout::Constraint;
5use crate::node::Node;
6use crate::render::RenderCx;
7use crate::style::{Border, Style};
8
9/// 边框 + 内边距包装容器
10pub struct Block {
11    child: Option<Node>,
12    style: Style,
13    title: Option<String>,
14}
15
16impl Block {
17    pub fn new(child: impl Component + 'static) -> Self {
18        Self {
19            child: Some(Node::new(child)),
20            style: Style::default(),
21            title: None,
22        }
23    }
24
25    pub fn border(mut self, border: Border) -> Self {
26        self.style = self.style.border(border);
27        self
28    }
29
30    pub fn padding(mut self, value: u16) -> Self {
31        self.style = self.style.padding(value);
32        self
33    }
34
35    pub fn title(mut self, title: impl Into<String>) -> Self {
36        self.title = Some(title.into());
37        self
38    }
39
40    pub fn style(mut self, style: Style) -> Self {
41        self.style = style;
42        self
43    }
44}
45
46impl Component for Block {
47    fn render(&self, cx: &mut RenderCx) {
48        cx.draw_border(self.style.border);
49
50        // 边框标题(top-left corner 右侧)
51        if let Some(title) = &self.title {
52            let pos = Pos { x: cx.rect.x.saturating_add(2), y: cx.rect.y };
53            cx.buffer.write_text(pos, cx.rect, title, &cx.style);
54        }
55
56        if let Some(child) = &self.child {
57            child.render_with_parent(cx.buffer, cx.focused_id, cx.clip_rect, cx.wrap, cx.truncate, cx.align, Some(&cx.style));
58        }
59    }
60
61    fn for_each_child(&self, f: &mut dyn FnMut(&Node)) {
62        if let Some(child) = &self.child {
63            f(child);
64        }
65    }
66
67    fn for_each_child_mut(&mut self, f: &mut dyn FnMut(&mut Node)) {
68        if let Some(child) = &mut self.child {
69            f(child);
70        }
71    }
72
73    fn measure(&self, constraint: Constraint, _cx: &mut MeasureCx) -> Size {
74        let pad = self.effective_padding();
75        let child_constraint = Constraint {
76            min: Size::default(),
77            max: Size {
78                width: constraint.max.width.saturating_sub(pad.left + pad.right),
79                height: constraint.max.height.saturating_sub(pad.top + pad.bottom),
80            },
81        };
82
83        let child_size = self
84            .child
85            .as_ref()
86            .map(|c| c.measure(child_constraint))
87            .unwrap_or_default();
88
89        Size {
90            width: child_size.width.saturating_add(pad.left + pad.right),
91            height: child_size.height.saturating_add(pad.top + pad.bottom),
92        }
93    }
94
95    fn focusable(&self) -> bool {
96        false
97    }
98
99    fn event(&mut self, event: &Event, cx: &mut EventCx) {
100        if matches!(event, Event::Focus | Event::Blur | Event::Tick) {
101            return;
102        }
103
104        if let Some(child) = &mut self.child {
105            let mut child_cx = EventCx::with_task_sender(&mut child.dirty, cx.global_dirty, cx.quit, cx.phase, cx.propagation_stopped, cx.task_sender.clone());
106            child.component.event(event, &mut child_cx);
107        }
108    }
109
110    fn layout(&mut self, rect: Rect, _cx: &mut LayoutCx) {
111        let inner = rect.inner(self.effective_padding());
112        if let Some(child) = &mut self.child {
113            child.layout(inner);
114        }
115    }
116
117    fn style(&self) -> Style {
118        self.style.clone()
119    }
120}
121
122impl Block {
123    fn effective_padding(&self) -> Insets {
124        let border_width: u16 = match self.style.border {
125            Border::None => 0,
126            _ => 1,
127        };
128        Insets {
129            top: self.style.padding.top + border_width,
130            right: self.style.padding.right + border_width,
131            bottom: self.style.padding.bottom + border_width,
132            left: self.style.padding.left + border_width,
133        }
134    }
135}