prototty_common/
border.rs

1use decorated::Decorated;
2use defaults::*;
3use prototty_render::*;
4
5/// The characters comprising a border. By default, borders are made of unicode
6/// box-drawing characters, but they can be changed to arbitrary characters via
7/// this struct.
8#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
9#[derive(Debug, Clone)]
10pub struct BorderChars {
11    pub top: char,
12    pub bottom: char,
13    pub left: char,
14    pub right: char,
15    pub top_left: char,
16    pub top_right: char,
17    pub bottom_left: char,
18    pub bottom_right: char,
19    pub before_title: char,
20    pub after_title: char,
21}
22
23impl Default for BorderChars {
24    fn default() -> Self {
25        Self {
26            top: '─',
27            bottom: '─',
28            left: '│',
29            right: '│',
30            top_left: '┌',
31            top_right: '┐',
32            bottom_left: '└',
33            bottom_right: '┘',
34            before_title: '┤',
35            after_title: '├',
36        }
37    }
38}
39
40/// The space in cells between the edge of the bordered area
41/// and the element inside.
42#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
43#[derive(Default, Debug, Clone)]
44pub struct BorderPadding {
45    pub top: u32,
46    pub bottom: u32,
47    pub left: u32,
48    pub right: u32,
49}
50
51/// Decorate another element with a border.
52/// It's possible to give the border a title, in which case
53/// the text appears in the top-left corner.
54#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
55#[derive(Debug, Clone)]
56pub struct Border {
57    pub title: Option<String>,
58    pub padding: BorderPadding,
59    pub chars: BorderChars,
60    pub foreground_colour: Rgb24,
61    pub background_colour: Rgb24,
62    pub title_colour: Rgb24,
63    pub bold_title: bool,
64    pub underline_title: bool,
65    pub bold_border: bool,
66}
67
68impl<T: ?Sized, V: View<T> + ViewSize<T>> View<T> for Decorated<V, Border> {
69    fn view<G: ViewGrid>(&mut self, data: &T, offset: Coord, depth: i32, grid: &mut G) {
70        self.view
71            .view(data, offset + self.decorator.child_offset(), depth, grid);
72
73        let span = self.decorator.span_offset() + self.view.size(data);
74
75        grid.set_cell(
76            offset,
77            depth,
78            self.decorator.view_cell_info(self.decorator.chars.top_left),
79        );
80        grid.set_cell(
81            offset + Coord::new(span.x, 0),
82            depth,
83            self.decorator
84                .view_cell_info(self.decorator.chars.top_right),
85        );
86        grid.set_cell(
87            offset + Coord::new(0, span.y),
88            depth,
89            self.decorator
90                .view_cell_info(self.decorator.chars.bottom_left),
91        );
92        grid.set_cell(
93            offset + Coord::new(span.x, span.y),
94            depth,
95            self.decorator
96                .view_cell_info(self.decorator.chars.bottom_right),
97        );
98        let title_offset = if let Some(title) = self.decorator.title.as_ref() {
99            let before = offset + Coord::new(1, 0);
100            let after = offset + Coord::new(title.len() as i32 + 2, 0);
101            grid.set_cell(
102                before,
103                depth,
104                self.decorator
105                    .view_cell_info(self.decorator.chars.before_title),
106            );
107            grid.set_cell(
108                after,
109                depth,
110                self.decorator
111                    .view_cell_info(self.decorator.chars.after_title),
112            );
113            for (index, ch) in title.chars().enumerate() {
114                let coord = offset + Coord::new(index as i32 + 2, 0);
115                grid.set_cell(
116                    coord,
117                    depth,
118                    ViewCell {
119                        character: Some(ch),
120                        bold: Some(self.decorator.bold_title),
121                        underline: Some(self.decorator.underline_title),
122                        foreground: Some(self.decorator.title_colour),
123                        background: Some(self.decorator.background_colour),
124                    },
125                );
126            }
127
128            title.len() as i32 + 2
129        } else {
130            0
131        };
132
133        for i in (1 + title_offset)..span.x {
134            grid.set_cell(
135                offset + Coord::new(i, 0),
136                depth,
137                self.decorator.view_cell_info(self.decorator.chars.top),
138            );
139        }
140        for i in 1..span.x {
141            grid.set_cell(
142                offset + Coord::new(i, span.y),
143                depth,
144                self.decorator.view_cell_info(self.decorator.chars.bottom),
145            );
146        }
147
148        for i in 1..span.y {
149            grid.set_cell(
150                offset + Coord::new(0, i),
151                depth,
152                self.decorator.view_cell_info(self.decorator.chars.left),
153            );
154            grid.set_cell(
155                offset + Coord::new(span.x, i),
156                depth,
157                self.decorator.view_cell_info(self.decorator.chars.right),
158            );
159        }
160    }
161}
162
163impl<T: ?Sized, V: View<T> + ViewSize<T>> ViewSize<T> for Decorated<V, Border> {
164    fn size(&mut self, data: &T) -> Size {
165        self.view.size(data) + Size::new(2, 2)
166    }
167}
168
169impl Border {
170    pub fn new() -> Self {
171        Self {
172            title: None,
173            padding: Default::default(),
174            chars: Default::default(),
175            foreground_colour: DEFAULT_FG,
176            background_colour: DEFAULT_BG,
177            title_colour: DEFAULT_FG,
178            bold_title: false,
179            underline_title: false,
180            bold_border: false,
181        }
182    }
183    pub fn with_title<S: Into<String>>(title: S) -> Self {
184        Self {
185            title: Some(title.into()),
186            padding: Default::default(),
187            chars: Default::default(),
188            foreground_colour: DEFAULT_FG,
189            background_colour: DEFAULT_BG,
190            title_colour: DEFAULT_FG,
191            bold_title: false,
192            underline_title: false,
193            bold_border: false,
194        }
195    }
196    fn child_offset(&self) -> Coord {
197        Coord {
198            x: (self.padding.left + 1) as i32,
199            y: (self.padding.top + 1) as i32,
200        }
201    }
202    fn span_offset(&self) -> Coord {
203        Coord {
204            x: (self.padding.left + self.padding.right + 1) as i32,
205            y: (self.padding.top + self.padding.bottom + 1) as i32,
206        }
207    }
208    fn view_cell_info(&self, character: char) -> ViewCell {
209        ViewCell {
210            character: Some(character),
211            foreground: Some(self.foreground_colour),
212            background: Some(self.background_colour),
213            bold: Some(self.bold_border),
214            underline: Some(false),
215        }
216    }
217}