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
9pub 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 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}