1use std::ops::{Deref, DerefMut};
2
3use crate::prelude::*;
4
5pub struct Border {
9 pub size: Vec2,
10 pub horizontal: &'static str,
11 pub vertical: &'static str,
12 pub top_left: &'static str,
13 pub top_right: &'static str,
14 pub bottom_left: &'static str,
15 pub bottom_right: &'static str,
16
17 pub title: Option<Buffer>,
18
19 pub style: ContentStyle,
20}
21
22impl Deref for Border {
23 type Target = ContentStyle;
24 fn deref(&self) -> &Self::Target {
25 &self.style
26 }
27}
28
29impl DerefMut for Border {
30 fn deref_mut(&mut self) -> &mut Self::Target {
31 &mut self.style
32 }
33}
34
35impl Border {
36 pub const fn square(width: u16, height: u16) -> Border {
37 Border {
38 size: vec2(width, height),
39 horizontal: "─",
40 vertical: "│",
41 top_right: "┐",
42 top_left: "┌",
43 bottom_left: "└",
44 bottom_right: "┘",
45
46 title: None,
47
48 style: ContentStyle {
49 foreground_color: None,
50 background_color: None,
51 underline_color: None,
52 attributes: Attributes::none(),
53 },
54 }
55 }
56
57 pub const fn rounded(width: u16, height: u16) -> Border {
58 Border {
59 size: vec2(width, height),
60 horizontal: "─",
61 vertical: "│",
62 top_right: "╮",
63 top_left: "╭",
64 bottom_left: "╰",
65 bottom_right: "╯",
66
67 title: None,
68
69 style: ContentStyle {
70 foreground_color: None,
71 background_color: None,
72 underline_color: None,
73 attributes: Attributes::none(),
74 },
75 }
76 }
77
78 pub const fn thick(width: u16, height: u16) -> Border {
79 Border {
80 size: vec2(width, height),
81 horizontal: "━",
82 vertical: "┃",
83 top_right: "┓",
84 top_left: "┏",
85 bottom_left: "┗",
86 bottom_right: "┛",
87
88 title: None,
89
90 style: ContentStyle {
91 foreground_color: None,
92 background_color: None,
93 underline_color: None,
94 attributes: Attributes::none(),
95 },
96 }
97 }
98
99 pub const fn double(width: u16, height: u16) -> Border {
100 Border {
101 size: vec2(width, height),
102 horizontal: "═",
103 vertical: "║",
104 top_right: "╗",
105 top_left: "╔",
106 bottom_left: "╚",
107 bottom_right: "╝",
108
109 title: None,
110
111 style: ContentStyle {
112 foreground_color: None,
113 background_color: None,
114 underline_color: None,
115 attributes: Attributes::none(),
116 },
117 }
118 }
119
120 pub fn with_title(mut self, title: impl Render) -> Border {
121 let title_buf = Buffer::sized_element(title);
122 self.title = Some(title_buf);
123
124 self
125 }
126}
127
128impl Render for Border {
129 fn render(&self, loc: Vec2, buffer: &mut Buffer) -> Vec2 {
130 if self.size.x < 3 || self.size.y < 3 {
131 return loc;
132 }
133
134 for y in (loc.y + 1)..(loc.y + self.size.y.saturating_sub(1)) {
136 for x in (loc.x + 1)..(loc.x + self.size.x.saturating_sub(1)) {
137 buffer.set(vec2(x, y), " ");
138 }
139 }
140
141 for y in (loc.y + 1)..(loc.y + self.size.y.saturating_sub(1)) {
143 buffer.set(
144 vec2(loc.x, y),
145 StyledContent::new(self.style, self.vertical),
146 );
147 buffer.set(
148 vec2(loc.x + self.size.x.saturating_sub(1), y),
149 StyledContent::new(self.style, self.vertical),
150 );
151 }
152
153 let horizontal_repeat = self
155 .horizontal
156 .repeat(self.size.x.saturating_sub(2) as usize);
157 render!(buffer,
158 loc => [
159 StyledContent::new(self.style, self.top_left),
160 StyledContent::new(self.style, horizontal_repeat.as_str()),
161 StyledContent::new(self.style, self.top_right)
162 ],
163 vec2(loc.x, loc.y + self.size.y.saturating_sub(1)) => [
164 StyledContent::new(self.style, self.bottom_left),
165 StyledContent::new(self.style, self.horizontal.repeat(self.size.x.saturating_sub(2) as usize).as_str()),
166 StyledContent::new(self.style, self.bottom_right)
167 ]
168 );
169
170 if let Some(title) = &self.title {
172 let max_title_width = self.size.x.saturating_sub(2); title.render_clipped(loc + vec2(1, 0), vec2(max_title_width, 1), buffer);
174 }
175
176 vec2(loc.x + 1, loc.y + 1)
177 }
178
179 fn size(&self) -> Vec2 {
180 self.size
181 }
182}
183
184#[cfg(test)]
185mod test {
186 use crate::{
187 math::Vec2,
188 render,
189 widgets::border::Border,
190 window::{Buffer, Render},
191 };
192
193 #[test]
194 fn render_small() {
195 let border = Border::square(0, 0);
196 let _ = Buffer::sized_element(border);
198 }
199
200 #[test]
201 fn check_size() {
202 let border = Border::square(16, 16);
203 let mut buf = Buffer::new((80, 80));
204 render!(buf, (0, 0) => [ border ]);
205 buf.shrink();
206 assert_eq!(buf.size(), border.size())
207 }
208}