Skip to main content

omp_tui/components/
boxed.rs

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
13/// A bordered child stack backing the `<box>` markup tag.
14pub struct Boxed {
15	props:    Props,
16	slot:     Slot,
17	children: Vec<Cached>,
18}
19
20impl Boxed {
21	/// Creates an empty box with the default border.
22	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	/// Sets one box property.
31	pub fn with(mut self, prop: Prop, value: impl Into<PropValue>) -> Self {
32		self.props.set(prop, value);
33		self
34	}
35
36	/// Sets one box property from a string.
37	pub fn with_str(mut self, prop: Prop, value: &str) -> Self {
38		self.props.set(prop, value);
39		self
40	}
41
42	/// Appends child components to the box.
43	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	/// A focusable, `id`-carrying box presses like a button: Enter emits
102	/// [`UiEvent::Pressed`] with its id.
103	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	/// Clicking the pointer zone presses the same way.
114	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}