1use crate::{
2 component::{Component, PaintCtx, Slot, next_slot},
3 context::UiContext,
4 frame::Rect,
5 markup::Border,
6 props::{Prop, PropValue, Props},
7};
8
9pub struct Hr {
11 props: Props,
12 slot: Slot,
13 bar: String,
14}
15
16impl Hr {
17 pub fn new() -> Self {
19 Self { props: Props::new(), slot: next_slot(), bar: String::new() }
20 }
21
22 pub fn with(mut self, prop: Prop, value: impl Into<PropValue>) -> Self {
24 self.props.set(prop, value);
25 self
26 }
27
28 pub fn with_str(mut self, prop: Prop, value: &str) -> Self {
30 self.props.set(prop, value);
31 self
32 }
33}
34
35impl Default for Hr {
36 fn default() -> Self {
37 Self::new()
38 }
39}
40
41impl Component for Hr {
42 fn props(&self) -> &Props {
43 &self.props
44 }
45
46 fn props_mut(&mut self) -> &mut Props {
47 &mut self.props
48 }
49
50 fn slot(&self) -> Slot {
51 self.slot
52 }
53
54 fn paints_border(&self) -> bool {
55 false
56 }
57
58 fn stretch_in_row(&self) -> bool {
59 true
60 }
61
62 fn measure(&mut self, _ctx: &UiContext) -> (u16, u16) {
63 if self.props.flag(Prop::Vertical) {
64 (1, 1)
65 } else {
66 (1, 4)
67 }
68 }
69
70 fn height(&mut self, _ctx: &UiContext, _width: u16) -> u16 {
71 1
72 }
73
74 fn paint(&mut self, pc: &mut PaintCtx<'_>, rect: Rect) {
75 let border = self.props.border().unwrap_or(Border::Square);
76 let (.., horizontal, vertical) = pc.ctx.charset.border(border);
77 let style = self.props.style(&pc.ctx.theme);
78 let line = self.props.edge(&pc.ctx.theme).map_or_else(
80 || {
81 if self.props.get(Prop::Fg).is_none() {
82 style.fg(pc.ctx.theme.border)
83 } else {
84 style.dim()
85 }
86 },
87 |color| style.fg(color),
88 );
89 if self.props.flag(Prop::Vertical) {
90 self.bar.clear();
91 repeated_char(&mut self.bar, vertical, 1);
92 for row in 0..rect.height {
93 let y = rect.y.saturating_add(row);
94 if y >= pc.clip {
95 break;
96 }
97 pc.frame.put(rect.x, y, &self.bar, line);
98 }
99 return;
100 }
101 if rect.y >= pc.clip {
102 return;
103 }
104 self.bar.clear();
105 repeated_char(&mut self.bar, horizontal, usize::from(rect.width));
106 pc.frame.put(rect.x, rect.y, &self.bar, line);
107 if let Some(title) = self.props.title() {
108 pc.frame.put(rect.x.saturating_add(2), rect.y, " ", style);
109 let end = pc
110 .frame
111 .put(rect.x.saturating_add(3), rect.y, title, style.bold());
112 pc.frame.put(end, rect.y, " ", style);
113 }
114 }
115}
116
117pub struct Spacer {
119 props: Props,
120 slot: Slot,
121}
122
123impl Spacer {
124 pub fn new() -> Self {
126 Self { props: Props::new(), slot: next_slot() }
127 }
128
129 pub fn with(mut self, prop: Prop, value: impl Into<PropValue>) -> Self {
131 self.props.set(prop, value);
132 self
133 }
134
135 pub fn with_str(mut self, prop: Prop, value: &str) -> Self {
137 self.props.set(prop, value);
138 self
139 }
140}
141
142impl Default for Spacer {
143 fn default() -> Self {
144 Self::new()
145 }
146}
147
148impl Component for Spacer {
149 fn props(&self) -> &Props {
150 &self.props
151 }
152
153 fn props_mut(&mut self) -> &mut Props {
154 &mut self.props
155 }
156
157 fn slot(&self) -> Slot {
158 self.slot
159 }
160
161 fn measure(&mut self, _ctx: &UiContext) -> (u16, u16) {
162 (1, 4)
163 }
164
165 fn height(&mut self, _ctx: &UiContext, _width: u16) -> u16 {
166 1
167 }
168
169 fn paint(&mut self, _pc: &mut PaintCtx<'_>, _rect: Rect) {}
170}
171
172fn repeated_char(output: &mut String, character: char, count: usize) {
173 output.reserve(count.saturating_mul(character.len_utf8()));
174 output.extend(std::iter::repeat_n(character, count));
175}
176
177#[cfg(test)]
178mod tests {
179 use super::Hr;
180 use crate::{
181 component::{Cached, Component, PaintCtx},
182 context::UiContext,
183 frame::{Frame, Rect, Size},
184 props::Prop,
185 test_support::frame_row_text,
186 };
187
188 #[test]
189 fn fills_horizontal_rule_with_charset_glyph() {
190 let ctx = UiContext::default();
191 let mut hr = Cached::new(Box::new(Hr::new()));
192 assert_eq!(hr.measure(&ctx), (1, 4));
193 hr.place(&ctx, Rect::new(0, 0, 6, 1));
194 let mut frame = Frame::new(Size::new(6, 1));
195 let mut hits = Vec::new();
196 hr.paint(&mut PaintCtx::new(&mut frame, &ctx, &mut hits, &mut Vec::new()));
197 assert_eq!(frame_row_text(&frame, 0), "──────");
198 }
199
200 #[test]
201 fn vertical_rule_uses_one_column_and_fills_height() {
202 let ctx = UiContext::default();
203 let mut hr = Hr::new().with(Prop::Vertical, true);
204 assert_eq!(hr.measure(&ctx), (1, 1));
205 let mut frame = Frame::new(Size::new(1, 3));
206 let mut hits = Vec::new();
207 hr.paint(
208 &mut PaintCtx::new(&mut frame, &ctx, &mut hits, &mut Vec::new()),
209 Rect::new(0, 0, 1, 3),
210 );
211 assert_eq!(frame_row_text(&frame, 0), "│");
212 assert_eq!(frame_row_text(&frame, 1), "│");
213 assert_eq!(frame_row_text(&frame, 2), "│");
214 }
215}