tabled/settings/style/
border.rs

1use core::marker::PhantomData;
2
3use crate::{
4    grid::config::Border as GridBorder,
5    settings::style::{On, Style},
6};
7
8#[cfg(feature = "std")]
9use crate::{
10    grid::config::{ColoredConfig, Entity, Position},
11    grid::records::{ExactRecords, Records},
12    settings::{CellOption, TableOption},
13};
14
15/// Border represents a border of a Cell.
16///
17/// ```text
18///                         top border
19///                             |
20///                             V
21/// corner top left ------> +_______+  <---- corner top left
22///                         |       |
23/// left border ----------> |  cell |  <---- right border
24///                         |       |
25/// corner bottom right --> +_______+  <---- corner bottom right
26///                             ^
27///                             |
28///                        bottom border
29/// ```
30///
31/// ```rust,no_run
32/// # use tabled::{Table, settings::{style::{Style, Border}, object::Rows}};
33/// # let data: Vec<&'static str> = Vec::new();
34/// let mut table = Table::new(&data);
35/// table.with(Style::ascii());
36/// table.modify(Rows::one(0), Border::new().top('x'));
37/// ```
38#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
39pub struct Border<T, B, L, R> {
40    inner: GridBorder<char>,
41    _top: PhantomData<T>,
42    _bottom: PhantomData<B>,
43    _left: PhantomData<L>,
44    _right: PhantomData<R>,
45}
46
47impl<T, B, L, R> Border<T, B, L, R> {
48    pub(crate) const fn from_border(inner: GridBorder<char>) -> Border<T, B, L, R> {
49        Border {
50            inner,
51            _top: PhantomData,
52            _bottom: PhantomData,
53            _left: PhantomData,
54            _right: PhantomData,
55        }
56    }
57}
58
59impl Border<(), (), (), ()> {
60    /// Creates an empty border.
61    pub const fn new() -> Self {
62        Self::from_border(GridBorder::empty())
63    }
64}
65
66impl Border<On, On, On, On> {
67    /// This function constructs a cell borders with all sides set.
68    #[allow(clippy::too_many_arguments)]
69    pub const fn full(
70        top: char,
71        bottom: char,
72        left: char,
73        right: char,
74        top_left: char,
75        top_right: char,
76        bottom_left: char,
77        bottom_right: char,
78    ) -> Self {
79        Border::from_border(GridBorder::full(
80            top,
81            bottom,
82            left,
83            right,
84            top_left,
85            top_right,
86            bottom_left,
87            bottom_right,
88        ))
89    }
90
91    /// This function constructs a cell borders with all sides's char set to a given character.
92    /// It behaves like [`Border::full`] with the same character set to each side.
93    pub const fn filled(c: char) -> Self {
94        Self::full(c, c, c, c, c, c, c, c)
95    }
96
97    /// Using this function you deconstruct the existing borders.
98    pub const fn empty() -> EmptyBorder {
99        EmptyBorder
100    }
101}
102
103impl<T, B, L, R> Border<T, B, L, R> {
104    /// Fetches outer border from a style.
105    pub const fn inherit<H, V, const HSIZE: usize, const VSIZE: usize>(
106        style: Style<T, B, L, R, H, V, HSIZE, VSIZE>,
107    ) -> Self {
108        let borders = style.get_borders();
109        let line = GridBorder::new(
110            borders.top,
111            borders.bottom,
112            borders.left,
113            borders.right,
114            borders.top_left,
115            borders.bottom_left,
116            borders.top_right,
117            borders.bottom_right,
118        );
119
120        Self::from_border(line)
121    }
122}
123
124impl<T, B, L, R> Border<T, B, L, R> {
125    /// Set a top border character.
126    pub const fn top(mut self, c: char) -> Border<On, B, L, R> {
127        self.inner.top = Some(c);
128        Border::from_border(self.inner)
129    }
130
131    /// Set a bottom border character.
132    pub const fn bottom(mut self, c: char) -> Border<T, On, L, R> {
133        self.inner.bottom = Some(c);
134        Border::from_border(self.inner)
135    }
136
137    /// Set a left border character.
138    pub const fn left(mut self, c: char) -> Border<T, B, On, R> {
139        self.inner.left = Some(c);
140        Border::from_border(self.inner)
141    }
142
143    /// Set a right border character.
144    pub const fn right(mut self, c: char) -> Border<T, B, L, On> {
145        self.inner.right = Some(c);
146        Border::from_border(self.inner)
147    }
148
149    /// Converts a border into a general data structure.
150    pub const fn into_inner(self) -> GridBorder<char> {
151        self.inner
152    }
153}
154
155impl<T, B, L> Border<T, B, L, On> {
156    /// Get a right character.
157    pub const fn get_right(&self) -> char {
158        get_char(self.inner.right)
159    }
160}
161
162impl<T, B, R> Border<T, B, On, R> {
163    /// Get a left character.
164    pub const fn get_left(&self) -> char {
165        get_char(self.inner.left)
166    }
167}
168
169impl<B, L, R> Border<On, B, L, R> {
170    /// Get a top character.
171    pub const fn get_top(&self) -> char {
172        get_char(self.inner.top)
173    }
174}
175
176impl<T, L, R> Border<T, On, L, R> {
177    /// Get a bottom character.
178    pub const fn get_bottom(&self) -> char {
179        get_char(self.inner.bottom)
180    }
181}
182
183impl<B, R> Border<On, B, On, R> {
184    /// Set a top left intersection character.
185    pub const fn corner_top_left(mut self, c: char) -> Self {
186        self.inner.left_top_corner = Some(c);
187        self
188    }
189
190    /// Get a top left intersection character.
191    pub const fn get_corner_top_left(&self) -> char {
192        get_char(self.inner.left_top_corner)
193    }
194}
195
196impl<B, L> Border<On, B, L, On> {
197    /// Set a top right intersection character.
198    pub const fn corner_top_right(mut self, c: char) -> Self {
199        self.inner.right_top_corner = Some(c);
200        self
201    }
202
203    /// Get a top right intersection character.
204    pub const fn get_corner_top_right(&self) -> char {
205        get_char(self.inner.right_top_corner)
206    }
207}
208
209impl<T, R> Border<T, On, On, R> {
210    /// Set a bottom left intersection character.
211    pub const fn corner_bottom_left(mut self, c: char) -> Self {
212        self.inner.left_bottom_corner = Some(c);
213        self
214    }
215
216    /// Get a bottom left intersection character.
217    pub const fn get_corner_bottom_left(&self) -> char {
218        get_char(self.inner.left_bottom_corner)
219    }
220}
221
222impl<T, L> Border<T, On, L, On> {
223    /// Set a bottom right intersection character.
224    pub const fn corner_bottom_right(mut self, c: char) -> Self {
225        self.inner.right_bottom_corner = Some(c);
226        self
227    }
228
229    /// Get a bottom left intersection character.
230    pub const fn get_corner_bottom_right(&self) -> char {
231        get_char(self.inner.right_bottom_corner)
232    }
233}
234
235impl<T, B, L, R> From<Border<T, B, L, R>> for GridBorder<char> {
236    fn from(value: Border<T, B, L, R>) -> Self {
237        value.inner
238    }
239}
240
241#[cfg(feature = "std")]
242impl<T, B, L, R, Data> CellOption<Data, ColoredConfig> for Border<T, B, L, R>
243where
244    Data: Records + ExactRecords,
245{
246    fn change(self, records: &mut Data, cfg: &mut ColoredConfig, entity: Entity) {
247        CellOption::change(self.inner, records, cfg, entity)
248    }
249}
250
251#[cfg(feature = "std")]
252impl<T, B, L, R, Data, D> TableOption<Data, ColoredConfig, D> for Border<T, B, L, R>
253where
254    Data: Records + ExactRecords,
255{
256    fn change(self, records: &mut Data, cfg: &mut ColoredConfig, dims: &mut D) {
257        let border = self.into_inner();
258        TableOption::change(border, records, cfg, dims);
259    }
260}
261
262#[cfg(feature = "std")]
263impl<R> CellOption<R, ColoredConfig> for GridBorder<char>
264where
265    R: Records + ExactRecords,
266{
267    fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) {
268        let shape = (records.count_rows(), records.count_columns());
269
270        for pos in entity.iter(shape.0, shape.1) {
271            cfg.set_border(pos, self);
272        }
273    }
274}
275
276#[cfg(feature = "std")]
277impl<R, D> TableOption<R, ColoredConfig, D> for GridBorder<char> {
278    fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
279        let mut borders = *cfg.get_borders();
280        borders.top = self.top;
281        borders.bottom = self.bottom;
282        borders.left = self.left;
283        borders.right = self.right;
284        borders.top_left = self.left_top_corner;
285        borders.top_right = self.right_top_corner;
286        borders.bottom_left = self.left_bottom_corner;
287        borders.bottom_right = self.right_bottom_corner;
288
289        if borders.has_vertical() {
290            borders.top_intersection = self.top;
291            borders.bottom_intersection = self.bottom;
292        }
293
294        if borders.has_horizontal() {
295            borders.left_intersection = self.left;
296            borders.right_intersection = self.right;
297        }
298
299        cfg.set_borders(borders);
300    }
301}
302
303#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
304pub struct EmptyBorder;
305
306#[cfg(feature = "std")]
307impl<R> CellOption<R, ColoredConfig> for EmptyBorder
308where
309    R: Records + ExactRecords,
310{
311    fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) {
312        let shape = (records.count_rows(), records.count_columns());
313
314        for pos in entity.iter(shape.0, shape.1) {
315            cfg.remove_border(pos, shape);
316        }
317    }
318}
319
320#[cfg(feature = "std")]
321impl<R, D> TableOption<R, ColoredConfig, D> for EmptyBorder
322where
323    R: Records + ExactRecords,
324{
325    fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
326        let count_rows = records.count_rows();
327        let count_columns = records.count_columns();
328        let shape = (count_rows, count_columns);
329
330        if count_rows == 0 || count_columns == 0 {
331            return;
332        }
333
334        let mut borders = *cfg.get_borders();
335        borders.top = None;
336        borders.top_intersection = None;
337        borders.top_left = None;
338        borders.top_right = None;
339        borders.bottom = None;
340        borders.bottom_intersection = None;
341        borders.bottom_left = None;
342        borders.bottom_right = None;
343        borders.left = None;
344        borders.left_intersection = None;
345        borders.right = None;
346        borders.right_intersection = None;
347
348        cfg.set_borders(borders);
349
350        for col in 0..count_columns {
351            let pos = Position::new(0, col);
352            let mut border = cfg.get_border(pos, shape);
353            if border.top.is_some() || border.right_top_corner.is_some() {
354                border.top = None;
355                border.right_top_corner = None;
356                cfg.set_border(pos, border);
357            }
358
359            let pos = Position::new(count_rows - 1, col);
360            let mut border = cfg.get_border(pos, shape);
361            if border.bottom.is_some() || border.right_bottom_corner.is_some() {
362                border.bottom = None;
363                border.right_bottom_corner = None;
364                cfg.set_border(pos, border);
365            }
366        }
367
368        for row in 0..count_rows {
369            let pos = Position::new(row, 0);
370            let mut border = cfg.get_border(pos, shape);
371            if border.left.is_some() || border.left_bottom_corner.is_some() {
372                border.left = None;
373                border.left_bottom_corner = None;
374                cfg.set_border(pos, border);
375            }
376
377            let pos = Position::new(row, count_columns - 1);
378            let mut border = cfg.get_border(pos, shape);
379            if border.right.is_some() || border.right_bottom_corner.is_some() {
380                border.right = None;
381                border.right_bottom_corner = None;
382                cfg.set_border(pos, border);
383            }
384        }
385
386        let pos = Position::new(0, 0);
387        let mut b = cfg.get_border(pos, shape);
388        if b.left_top_corner.is_some() {
389            b.left_top_corner = None;
390            cfg.set_border(pos, b);
391        }
392
393        let pos = Position::new(0, count_columns - 1);
394        let mut b = cfg.get_border(pos, shape);
395        if b.right_top_corner.is_some() {
396            b.right_top_corner = None;
397            cfg.set_border(pos, b);
398        }
399
400        let pos = Position::new(count_rows - 1, 0);
401        let mut b = cfg.get_border(pos, shape);
402        if b.left_bottom_corner.is_some() {
403            b.left_bottom_corner = None;
404            cfg.set_border(pos, b);
405        }
406
407        let pos = Position::new(count_rows - 1, count_columns - 1);
408        let mut b = cfg.get_border(pos, shape);
409        if b.right_bottom_corner.is_some() {
410            b.right_bottom_corner = None;
411            cfg.set_border(pos, b);
412        }
413    }
414}
415
416const fn get_char(c: Option<char>) -> char {
417    match c {
418        Some(c) => c,
419        None => unreachable!(),
420    }
421}