1use super::layout::{stack_height, stack_measure, stack_place};
2use crate::{
3 component::{
4 Cached, Component, EventCtx, Flow, HitTag, IntoChildren, PaintCtx, Slot, next_slot,
5 },
6 context::UiContext,
7 frame::Rect,
8 input::{Key, Mouse, UiEvent},
9 markup::Border,
10 props::{Prop, PropValue, Props},
11};
12
13pub struct Boxed {
15 props: Props,
16 slot: Slot,
17 children: Vec<Cached>,
18}
19
20impl Boxed {
21 pub fn new() -> Self {
23 Self {
24 props: Props::new().with(Prop::Border, Border::default()),
25 slot: next_slot(),
26 children: Vec::new(),
27 }
28 }
29
30 pub fn with(mut self, prop: Prop, value: impl Into<PropValue>) -> Self {
32 self.props.set(prop, value);
33 self
34 }
35
36 pub fn with_str(mut self, prop: Prop, value: &str) -> Self {
38 self.props.set(prop, value);
39 self
40 }
41
42 pub fn child(mut self, children: impl IntoChildren) -> Self {
44 children.extend_children(&mut self.children);
45 self
46 }
47}
48
49impl Default for Boxed {
50 fn default() -> Self {
51 Self::new()
52 }
53}
54
55impl Component for Boxed {
56 fn props(&self) -> &Props {
57 &self.props
58 }
59
60 fn props_mut(&mut self) -> &mut Props {
61 &mut self.props
62 }
63
64 fn slot(&self) -> Slot {
65 self.slot
66 }
67
68 fn children(&self) -> &[Cached] {
69 &self.children
70 }
71
72 fn children_mut(&mut self) -> &mut [Cached] {
73 &mut self.children
74 }
75
76 fn measure(&mut self, ctx: &UiContext) -> (u16, u16) {
77 stack_measure(ctx, &mut self.children)
78 }
79
80 fn height(&mut self, ctx: &UiContext, width: u16) -> u16 {
81 stack_height(ctx, &mut self.children, width, self.props.gap())
82 }
83
84 fn place(&mut self, ctx: &UiContext, content: Rect) {
85 stack_place(
86 ctx,
87 &mut self.children,
88 content,
89 self.props.gap(),
90 self.props.valign(),
91 self.props.align(),
92 );
93 }
94
95 fn paint(&mut self, pc: &mut PaintCtx<'_>, _rect: Rect) {
96 for child in self.children.iter_mut().filter(|child| child.visible) {
97 child.paint(pc);
98 }
99 }
100
101 fn key(&mut self, _ec: &mut EventCtx<'_>, key: Key) -> Flow {
104 if key == Key::Enter
105 && self.props.flag(Prop::Focus)
106 && let Some(id) = self.props.id()
107 {
108 return Flow::Event(UiEvent::Pressed(id.clone()));
109 }
110 Flow::Skip
111 }
112
113 fn mouse(
115 &mut self,
116 _ec: &mut EventCtx<'_>,
117 tag: HitTag,
118 _at: (u16, u16),
119 _rect: Rect,
120 mouse: Mouse,
121 ) -> Flow {
122 if tag == HitTag::Zone
123 && mouse == Mouse::Click
124 && self.props.flag(Prop::Focus)
125 && let Some(id) = self.props.id()
126 {
127 return Flow::Event(UiEvent::Pressed(id.clone()));
128 }
129 Flow::Skip
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::Boxed;
136 use crate::{
137 component::{Cached, PaintCtx},
138 components::TextLeaf,
139 context::UiContext,
140 frame::{Frame, Rect, Size},
141 markup::Border,
142 props::Prop,
143 test_support::frame_row_text,
144 };
145
146 #[test]
147 fn cached_paints_box_border_and_title() {
148 let ctx = UiContext::default();
149 let mut root = Cached::new(Box::new(
150 Boxed::new()
151 .with(Prop::Border, Border::Round)
152 .with(Prop::Title, "Panel")
153 .child(TextLeaf::new().text("body")),
154 ));
155 let height = root.height(&ctx, 14);
156 root.place(&ctx, Rect::new(0, 0, 14, height));
157 let mut frame = Frame::new(Size::new(14, height));
158 let mut hits = Vec::new();
159 root.paint(&mut PaintCtx::new(&mut frame, &ctx, &mut hits, &mut Vec::new()));
160 assert!(frame_row_text(&frame, 0).starts_with("╭─ Panel "));
161 assert_eq!(frame_row_text(&frame, 1), "│body │");
162 assert_eq!(frame_row_text(&frame, height - 1), "╰────────────╯");
163 }
164}