ratatui_garnish/
border.rs

1//! Border Garnishes
2//!
3//! A border is composed of a set of characters used to draw the border,
4//! a `BorderSet` and a bitflags struct [`Borders`] (just like in `ratatui`) to
5//! configure which borders to render. Borders don't have their own [`Style`](ratatui::style::Style);
6//! set the style of your border with the `Style` garnish instead.
7//!
8//! # Example
9//! ```rust
10//!
11//! use ratatui::{text::Text, style::{Color, Style}};
12//! use ratatui_garnish::{ GarnishableWidget };
13//! use ratatui_garnish::border::PlainBorder;
14//! let widget = Text::raw("Hello, world")
15//!     .garnish(Style::default().fg(Color::Blue))
16//!     .garnish(PlainBorder::default()); // Blue plain border
17//! ```
18//!
19//! # Standard Borders
20//!
21//! Standard borders are simple wrappers around [`Borders`]. The
22//! `default` implementation returns a border with all borders enabled. Use
23//! `new` to select specific borders.
24//!
25//! ## Example
26//!
27//! ```rust
28//! # use ratatui::{text::Text, style::{Color, Style}};
29//! # use ratatui_garnish::{ GarnishableWidget };
30//! # use ratatui_garnish::border::PlainBorder;
31//! use ratatui_garnish::border::Borders;
32//!
33//! let widget = Text::raw("Hello, world")
34//!     .garnish(PlainBorder::new(Borders::TOP | Borders::BOTTOM));
35//! ```
36//!
37//! Standard borders implement `Deref` and `DerefMut`, allowing
38//! direct calls to `Borders` methods:
39//!
40//! ```rust
41//! # use ratatui_garnish::border::{PlainBorder, Borders};
42//! let mut border = PlainBorder::new(Borders::TOP);
43//! border.insert(Borders::LEFT);
44//! border.remove(Borders::TOP);
45//! ```
46//!
47//! There is a standard border for every constructor of `BorderSet`
48//! that doesn't need arguments: `PlainBorder`, `DashedBorder`,
49//! `DoubleBorder`, `FatInsideBorder`, `FatOutsideBorder`, `QuadrantInsideBorder`,
50//! `QuadrantOutsideBorder`, `RoundedBorder`, `RoundedDashedBorder`,
51//! `ThickBorder` and `ThickDashedBorder`.
52//!
53//! # `CharBorder`
54//!
55//! For creating a border composed of a single character, use
56//! `CharBorder`:
57//!
58//! ```rust
59//! # use ratatui_garnish::border::{CharBorder, Borders};
60//! let border = CharBorder::new('*')
61//!     .borders(Borders::RIGHT);
62//! ```
63//!
64//! # `CustomBorder`
65//!
66//! With `CustomBorder` you can use your own `BorderSet` to define
67//! a border. You can start with one of the standard border constructors
68//! and adapt it as needed.
69//!
70//! ```rust
71//! use ratatui_garnish::border::{BorderSet, CustomBorder};
72//!
73//! let border = CustomBorder::new(BorderSet::plain().corners('*'));
74//! ```
75
76use derive_more::{Deref, DerefMut};
77use ratatui::{buffer::Buffer, layout::Rect};
78
79/// Trait for rendering borders around ratatui widgets.
80///
81/// This trait defines methods for retrieving the border configuration
82///  ([`Borders`]) and character set ([`BorderSet`]), which are used
83///  to render a border. The other methods are used byas well as rendering individual border segments
84/// and corners. Implementors can customize rendering behavior if needed,
85/// but the default implementations should suffice for most cases.
86pub trait Border {
87    /// Returns the character set used for rendering.
88    fn get_border_set(&self) -> BorderSet;
89
90    /// Returns the border configuration.
91    fn get_borders(&self) -> Borders;
92
93    /// Renders the left border.
94    fn render_left(&self, area: Rect, buffer: &mut Buffer, symbol: char) {
95        for y in area.top()..area.bottom() {
96            buffer[(area.left(), y)].set_char(symbol);
97        }
98    }
99
100    /// Renders the top border.
101    fn render_top(&self, area: Rect, buffer: &mut Buffer, symbol: char) {
102        for x in area.left()..area.right() {
103            buffer[(x, area.top())].set_char(symbol);
104        }
105    }
106
107    /// Renders the right border.
108    fn render_right(&self, area: Rect, buffer: &mut Buffer, symbol: char) {
109        let x = area.right().saturating_sub(1);
110        for y in area.top()..area.bottom() {
111            buffer[(x, y)].set_char(symbol);
112        }
113    }
114
115    /// Renders the bottom border.
116    fn render_bottom(&self, area: Rect, buffer: &mut Buffer, symbol: char) {
117        let y = area.bottom().saturating_sub(1);
118        for x in area.left()..area.right() {
119            buffer[(x, y)].set_char(symbol);
120        }
121    }
122
123    /// Renders the corner characters if adjacent sides are present.
124    fn render_corners(&self, area: Rect, buffer: &mut Buffer, charset: &BorderSet) {
125        let borders = self.get_borders();
126        let (right, bottom) = (
127            area.right().saturating_sub(1),
128            area.bottom().saturating_sub(1),
129        );
130
131        if borders.contains(Borders::RIGHT | Borders::BOTTOM) {
132            buffer[(right, bottom)].set_char(charset.bottom_right);
133        }
134        if borders.contains(Borders::RIGHT | Borders::TOP) {
135            buffer[(right, area.top())].set_char(charset.top_right);
136        }
137        if borders.contains(Borders::LEFT | Borders::BOTTOM) {
138            buffer[(area.left(), bottom)].set_char(charset.bottom_left);
139        }
140        if borders.contains(Borders::LEFT | Borders::TOP) {
141            buffer[(area.left(), area.top())].set_char(charset.top_left);
142        }
143    }
144}
145
146impl<T: Border> crate::RenderModifier for T {
147    fn before_render(&self, area: Rect, buffer: &mut Buffer) {
148        let borders = self.get_borders();
149        let border_set = self.get_border_set();
150
151        if borders.contains(Borders::LEFT) {
152            self.render_left(area, buffer, border_set.left);
153        }
154        if borders.contains(Borders::TOP) {
155            self.render_top(area, buffer, border_set.top);
156        }
157        if borders.contains(Borders::RIGHT) {
158            self.render_right(area, buffer, border_set.right);
159        }
160        if borders.contains(Borders::BOTTOM) {
161            self.render_bottom(area, buffer, border_set.bottom);
162        }
163
164        self.render_corners(area, buffer, &border_set);
165    }
166
167    fn modify_area(&self, area: Rect) -> Rect {
168        let mut inner = area;
169        let borders = self.get_borders();
170
171        if borders.contains(Borders::LEFT) {
172            inner.x = inner.x.saturating_add(1).min(inner.right());
173            inner.width = inner.width.saturating_sub(1);
174        }
175        if borders.contains(Borders::TOP) {
176            inner.y = inner.y.saturating_add(1).min(inner.bottom());
177            inner.height = inner.height.saturating_sub(1);
178        }
179        if borders.contains(Borders::RIGHT) {
180            inner.width = inner.width.saturating_sub(1);
181        }
182        if borders.contains(Borders::BOTTOM) {
183            inner.height = inner.height.saturating_sub(1);
184        }
185        inner
186    }
187}
188
189// ===== Character Sets =====
190
191/// Character set for rendering borders with different visual styles.
192///
193/// Provides various predefined styles and methods to customize individual characters.
194///
195/// # Examples
196///
197/// ```rust
198/// use ratatui_garnish::border::BorderSet;
199///
200/// // Use a predefined set
201/// let rounded = BorderSet::rounded();
202///
203/// // Customize a set
204/// let custom = BorderSet::plain()
205///     .corners('*')
206///     .horizontals('=');
207///
208/// // Create from a single character
209/// let simple = BorderSet::new('#');
210/// ```
211#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
212#[cfg_attr(feature = "serde", serde(default))]
213#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
214pub struct BorderSet {
215    pub top_left: char,
216    pub top_right: char,
217    pub bottom_left: char,
218    pub bottom_right: char,
219    pub left: char,
220    pub right: char,
221    pub top: char,
222    pub bottom: char,
223}
224
225impl BorderSet {
226    /// Creates a new `BorderSet` with all characters set to the same symbol.
227    ///
228    /// # Examples
229    ///
230    /// ```rust
231    /// use ratatui_garnish::border::BorderSet;
232    ///
233    /// let charset = BorderSet::new('*');
234    /// assert_eq!(charset.top_left, '*');
235    /// assert_eq!(charset.bottom, '*');
236    /// ```
237    #[must_use = "constructor returns a new instance"]
238    pub const fn new(symbol: char) -> Self {
239        Self {
240            top: symbol,
241            bottom: symbol,
242            left: symbol,
243            right: symbol,
244            top_left: symbol,
245            top_right: symbol,
246            bottom_left: symbol,
247            bottom_right: symbol,
248        }
249    }
250
251    /// Creates a plain border set.
252    ///
253    /// ```text
254    /// ┌───────┐
255    /// │       │
256    /// └───────┘
257    /// ```
258    #[must_use = "constructor returns a new instance"]
259    pub const fn plain() -> Self {
260        Self {
261            top_left: '┌',
262            top_right: '┐',
263            bottom_left: '└',
264            bottom_right: '┘',
265            left: '│',
266            right: '│',
267            top: '─',
268            bottom: '─',
269        }
270    }
271
272    /// Creates a dashed border set.
273    ///
274    /// ```text
275    /// ┌╌╌╌╌╌╌╌┐
276    /// ┆       ┆
277    /// └╌╌╌╌╌╌╌┘
278    /// ```
279    #[must_use = "constructor returns a new instance"]
280    pub const fn dashed() -> Self {
281        Self::plain().horizontals('╌').verticals('┊')
282    }
283
284    /// Creates a plain border set with rounded corners.
285    ///
286    /// ```text
287    /// ╭───────╮
288    /// │       │
289    /// ╰───────╯
290    /// ```
291    #[must_use = "constructor returns a new instance"]
292    pub const fn rounded() -> Self {
293        Self {
294            top_left: '╭',
295            top_right: '╮',
296            bottom_left: '╰',
297            bottom_right: '╯',
298            left: '│',
299            right: '│',
300            top: '─',
301            bottom: '─',
302        }
303    }
304
305    /// Creates a rounded dashed border set.
306    ///
307    /// ```text
308    /// ╭╌╌╌╌╌╌╌╮
309    /// ┆       ┆
310    /// ╰╌╌╌╌╌╌╌╯
311    /// ```
312    #[must_use = "constructor returns a new instance"]
313    pub const fn rounded_dashed() -> Self {
314        Self::rounded().horizontals('╌').verticals('┊')
315    }
316
317    /// Creates a double border set.
318    ///
319    /// ```text
320    /// ╔═══════╗
321    /// ║       ║
322    /// ╚═══════╝
323    /// ```
324    #[must_use = "constructor returns a new instance"]
325    pub const fn double() -> Self {
326        Self {
327            top_left: '╔',
328            top_right: '╗',
329            bottom_left: '╚',
330            bottom_right: '╝',
331            left: '║',
332            right: '║',
333            top: '═',
334            bottom: '═',
335        }
336    }
337
338    /// Creates a thick border set.
339    ///
340    /// ```text
341    /// ┏━━━━━━━┓
342    /// ┃       ┃
343    /// ┗━━━━━━━┛
344    /// ```
345    #[must_use = "constructor returns a new instance"]
346    pub const fn thick() -> Self {
347        Self {
348            top_left: '┏',
349            top_right: '┓',
350            bottom_left: '┗',
351            bottom_right: '┛',
352            left: '┃',
353            right: '┃',
354            top: '━',
355            bottom: '━',
356        }
357    }
358
359    /// Creates a thick dashed border set.
360    ///
361    /// ```text
362    /// ┏╍╍╍╍╍╍╍┓
363    /// ┇       ┇
364    /// ┗╍╍╍╍╍╍╍┛
365    /// ```
366    #[must_use = "constructor returns a new instance"]
367    pub const fn thick_dashed() -> Self {
368        Self::rounded().horizontals('╍').verticals('┋')
369    }
370
371    /// Create a quadrant inside border set.
372    ///
373    /// ```text
374    /// ▗▄▄▄▄▄▄▄▖
375    /// ▐       ▌
376    /// ▐       ▌
377    /// ▝▀▀▀▀▀▀▀▘
378    /// ```
379    #[must_use = "constructor returns a new instance"]
380    pub const fn quadrant_inside() -> Self {
381        Self {
382            top_left: '▗',
383            top_right: '▖',
384            bottom_left: '▝',
385            bottom_right: '▘',
386            left: '▐',
387            right: '▌',
388            top: '▄',
389            bottom: '▀',
390        }
391    }
392
393    /// Create a quadrant outside border set.
394    ///
395    /// ```text
396    /// ▛▀▀▀▀▀▀▀▜
397    /// ▌       ▐
398    /// ▌       ▐
399    /// ▙▄▄▄▄▄▄▄▟
400    /// ```
401    #[must_use = "constructor returns a new instance"]
402    pub const fn quadrant_outside() -> Self {
403        Self {
404            top_left: '▛',
405            top_right: '▜',
406            bottom_left: '▙',
407            bottom_right: '▟',
408            left: '▌',
409            right: '▐',
410            top: '▀',
411            bottom: '▄',
412        }
413    }
414
415    /// Create a fat inside border set.
416    ///
417    /// ```text
418    /// ▄▄▄▄▄▄▄▄▄
419    /// █       █
420    /// █       █
421    /// ▀▀▀▀▀▀▀▀▀
422    /// ```
423    #[must_use = "constructor returns a new instance"]
424    pub const fn fat_inside() -> Self {
425        Self {
426            top_left: '▄',
427            top_right: '▄',
428            bottom_left: '▀',
429            bottom_right: '▀',
430            left: '█',
431            right: '█',
432            top: '▄',
433            bottom: '▀',
434        }
435    }
436
437    /// Create a fat outside border set.
438    ///
439    /// ```text
440    /// █▀▀▀▀▀▀▀█
441    /// █       █
442    /// █       █
443    /// █▄▄▄▄▄▄▄█
444    /// ```
445    #[must_use = "constructor returns a new instance"]
446    pub const fn fat_outside() -> Self {
447        Self {
448            top_left: '█',
449            top_right: '█',
450            bottom_left: '█',
451            bottom_right: '█',
452            left: '█',
453            right: '█',
454            top: '▀',
455            bottom: '▄',
456        }
457    }
458
459    /// Sets all corner characters to the specified symbol.
460    ///
461    /// # Examples
462    ///
463    /// ```rust
464    /// # use ratatui_garnish::border::BorderSet;
465    /// let border = BorderSet::plain().corners('*');
466    /// ```
467    #[must_use = "method returns a new instance and does not mutate the original"]
468    pub const fn corners(mut self, symbol: char) -> Self {
469        self.top_left = symbol;
470        self.top_right = symbol;
471        self.bottom_left = symbol;
472        self.bottom_right = symbol;
473        self
474    }
475
476    /// Sets vertical border characters (left and right) to the specified symbol.
477    ///
478    /// # Examples
479    ///
480    /// ```rust
481    /// # use ratatui_garnish::border::BorderSet;
482    /// let border = BorderSet::plain().verticals('|');
483    /// ```
484    #[must_use = "method returns a new instance and does not mutate the original"]
485    pub const fn verticals(mut self, symbol: char) -> Self {
486        self.left = symbol;
487        self.right = symbol;
488        self
489    }
490
491    /// Sets horizontal border characters (top and bottom) to the specified symbol.
492    ///
493    /// # Examples
494    ///
495    /// ```rust
496    /// # use ratatui_garnish::border::BorderSet;
497    /// let border = BorderSet::plain().horizontals('-');
498    /// ```
499    #[must_use = "method returns a new instance and does not mutate the original"]
500    pub const fn horizontals(mut self, symbol: char) -> Self {
501        self.top = symbol;
502        self.bottom = symbol;
503        self
504    }
505
506    /// Sets all side characters (non-corner) to the specified symbol.
507    ///
508    /// # Examples
509    ///
510    /// ```rust
511    /// # use ratatui_garnish::border::BorderSet;
512    /// let border = BorderSet::plain().sides('=');
513    /// ```
514    #[must_use = "method returns a new instance and does not mutate the original"]
515    pub const fn sides(mut self, symbol: char) -> Self {
516        self.top = symbol;
517        self.bottom = symbol;
518        self.left = symbol;
519        self.right = symbol;
520        self
521    }
522}
523
524// ===== Border Implementations =====
525
526/// Macro to generate standard border types with predefined character sets.
527macro_rules! standard_border {
528    ($name:ident, $border_set_fn:ident, $doc:literal) => {
529        #[doc = $doc]
530        ///
531        /// This is a wrapper around `Borders` with `Deref` and `DerefMut` implementations
532        /// for convenient access to the underlying `Borders` configuration.
533        ///
534        /// # Examples
535        ///
536        /// ```rust
537        #[doc = concat!("use ratatui_garnish::border::{Borders, ", stringify!($name), "};")]
538        ///
539        #[doc = concat!("let border = ", stringify!($name), "::default();")]
540        #[doc = concat!("let custom = ", stringify!($name), "::new(Borders::TOP | Borders::BOTTOM);")]
541        /// ```
542        #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
543        #[cfg_attr(feature = "serde", serde(transparent))]
544        #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Deref, DerefMut)]
545        pub struct $name(
546            pub Borders
547        );
548
549        impl $name {
550            /// Creates a new border with the specified configuration.
551            #[must_use = "constructor returns a new instance"]
552            pub const fn new(borders: Borders) -> Self {
553                Self(borders)
554            }
555        }
556
557        impl Default for $name {
558            /// Creates a border with all sides enabled.
559            fn default() -> Self {
560                Self(Borders::ALL)
561            }
562        }
563
564        impl Border for $name {
565            fn get_borders(&self) -> Borders {
566                self.0
567            }
568
569            fn get_border_set(&self) -> BorderSet {
570                BorderSet::$border_set_fn()
571            }
572        }
573    };
574}
575
576standard_border!(
577    PlainBorder,
578    plain,
579    "A plain border with standard box-drawing characters."
580);
581standard_border!(DashedBorder, dashed, "A dashed border.");
582standard_border!(
583    RoundedDashedBorder,
584    rounded_dashed,
585    "A dashed border with rounded corners."
586);
587standard_border!(ThickDashedBorder, thick_dashed, "A thick dashed border.");
588standard_border!(RoundedBorder, rounded, "A border with rounded corners.");
589standard_border!(DoubleBorder, double, "A double-line border.");
590standard_border!(ThickBorder, thick, "A thick border.");
591standard_border!(
592    QuadrantInsideBorder,
593    quadrant_inside,
594    "A quadrant-style inside border."
595);
596standard_border!(
597    QuadrantOutsideBorder,
598    quadrant_outside,
599    "A quadrant-style outside border."
600);
601standard_border!(FatInsideBorder, fat_inside, "A fat inside border.");
602standard_border!(FatOutsideBorder, fat_outside, "A fat outside border.");
603
604/// A border rendered with a single character for all sides.
605///
606/// # Examples
607///
608/// ```rust
609/// use ratatui_garnish::border::{Borders, CharBorder};
610///
611/// let star_border = CharBorder::new('*'); // All sides with '*'
612/// let hash_border = CharBorder::new('#').borders(Borders::TOP | Borders::BOTTOM);
613/// ```
614#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
615#[cfg_attr(feature = "serde", serde(default))]
616#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
617pub struct CharBorder {
618    pub symbol: char,
619    pub borders: Borders,
620}
621
622impl Default for CharBorder {
623    /// Creates a border with all sides enabled (`Borders::ALL`) and space as the symbol.
624    fn default() -> Self {
625        Self {
626            symbol: ' ',
627            borders: Borders::ALL,
628        }
629    }
630}
631
632impl CharBorder {
633    /// Creates a new border using the specified symbol for all sides.
634    ///
635    /// By default, uses `Borders::ALL`.
636    ///
637    /// # Example
638    ///
639    /// ```
640    /// use ratatui_garnish::border::CharBorder;
641    ///
642    /// let border = CharBorder::new('*');
643    /// ```
644    #[must_use = "constructor returns a new instance"]
645    pub fn new(symbol: char) -> Self {
646        Self {
647            symbol,
648            ..Default::default()
649        }
650    }
651
652    /// Sets which borders to render.
653    ///
654    /// # Example
655    ///
656    /// ```
657    /// use ratatui_garnish::border::{CharBorder, Border, Borders};
658    ///
659    /// let border = CharBorder::new('*').borders(Borders::TOP);
660    /// assert_eq!(border.get_borders(), Borders::TOP);
661    /// ```
662    #[must_use = "method returns a new instance and does not mutate the original"]
663    pub const fn borders(mut self, borders: Borders) -> Self {
664        self.borders = borders;
665        self
666    }
667}
668
669impl Border for CharBorder {
670    fn get_borders(&self) -> Borders {
671        self.borders
672    }
673
674    fn get_border_set(&self) -> BorderSet {
675        BorderSet::new(self.symbol)
676    }
677}
678
679/// A border with a fully customizable character set.
680///
681/// Provides maximum flexibility for creating unique border styles by allowing
682/// specification of individual characters for each border element.
683///
684/// # Example
685///
686/// ```rust
687/// use ratatui_garnish::border::{Borders, BorderSet, CustomBorder};
688///
689/// let custom_set = BorderSet::plain().corners('*').horizontals('=');
690/// let border = CustomBorder::new(custom_set).borders(Borders::ALL);
691/// ```
692#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
693#[cfg_attr(feature = "serde", serde(default))]
694#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
695pub struct CustomBorder {
696    /// The custom character set used to draw the border.
697    pub char_set: BorderSet,
698    /// Which borders to draw, defaults to `Borders::ALL`.
699    pub borders: Borders,
700}
701
702impl Default for CustomBorder {
703    /// Creates a plain border with all sides enabled (`Borders::ALL`).
704    fn default() -> Self {
705        Self {
706            char_set: BorderSet::plain(),
707            borders: Borders::ALL,
708        }
709    }
710}
711
712impl CustomBorder {
713    /// Creates a new custom border with the specified character set.
714    ///
715    /// By default, uses `Borders::ALL`.
716    #[must_use = "constructor returns a new instance"]
717    pub fn new(char_set: BorderSet) -> Self {
718        Self {
719            char_set,
720            ..Default::default()
721        }
722    }
723
724    /// Sets which borders to render.
725    #[must_use = "method returns a new instance and does not mutate the original"]
726    pub const fn borders(mut self, borders: Borders) -> Self {
727        self.borders = borders;
728        self
729    }
730}
731
732impl Border for CustomBorder {
733    fn get_borders(&self) -> Borders {
734        self.borders
735    }
736
737    fn get_border_set(&self) -> BorderSet {
738        self.char_set
739    }
740}
741
742bitflags::bitflags! {
743    /// Bitflags that can be composed to set the visible borders essentially on any border garnish.
744    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
745    #[cfg_attr(feature = "serde", serde(transparent))]
746    #[cfg_attr(feature = "serde", serde(default))]
747    #[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Hash)]
748    pub struct Borders: u8 {
749        const NONE   = 0b0000;
750        const TOP    = 0b0001;
751        const RIGHT  = 0b0010;
752        const BOTTOM = 0b0100;
753        const LEFT   = 0b1000;
754        const ALL = Self::TOP.bits() | Self::RIGHT.bits() | Self::BOTTOM.bits() | Self::LEFT.bits();
755    }
756}
757
758#[cfg(test)]
759mod tests {
760    use super::*;
761    use crate::RenderModifier;
762
763    fn create_test_buffer(width: u16, height: u16) -> Buffer {
764        Buffer::empty(Rect::new(0, 0, width, height))
765    }
766
767    #[test]
768    fn border_set_new_creates_uniform_set() {
769        let border_set = BorderSet::new('*');
770
771        assert_eq!(border_set.top_left, '*');
772        assert_eq!(border_set.top_right, '*');
773        assert_eq!(border_set.bottom_left, '*');
774        assert_eq!(border_set.bottom_right, '*');
775        assert_eq!(border_set.left, '*');
776        assert_eq!(border_set.right, '*');
777        assert_eq!(border_set.top, '*');
778        assert_eq!(border_set.bottom, '*');
779    }
780
781    #[test]
782    fn border_set_modifiers_work_correctly() {
783        let border_set = BorderSet::plain()
784            .corners('*')
785            .verticals('|')
786            .horizontals('-');
787
788        assert_eq!(border_set.top_left, '*');
789        assert_eq!(border_set.top_right, '*');
790        assert_eq!(border_set.bottom_left, '*');
791        assert_eq!(border_set.bottom_right, '*');
792        assert_eq!(border_set.left, '|');
793        assert_eq!(border_set.right, '|');
794        assert_eq!(border_set.top, '-');
795        assert_eq!(border_set.bottom, '-');
796    }
797
798    #[test]
799    fn char_border_creates_correct_border() {
800        let border = CharBorder::new('*');
801        assert_eq!(border.symbol, '*');
802        assert_eq!(border.borders, Borders::ALL);
803
804        let partial_border = CharBorder::new('#').borders(Borders::TOP | Borders::BOTTOM);
805        assert_eq!(partial_border.symbol, '#');
806        assert_eq!(partial_border.borders, Borders::TOP | Borders::BOTTOM);
807    }
808
809    #[test]
810    fn standard_borders_have_correct_defaults() {
811        assert_eq!(PlainBorder::default().get_border_set(), BorderSet::plain());
812        assert_eq!(PlainBorder::default().get_borders(), Borders::ALL);
813        assert_eq!(
814            RoundedBorder::default().get_border_set(),
815            BorderSet::rounded()
816        );
817        assert_eq!(RoundedBorder::default().get_borders(), Borders::ALL);
818        assert_eq!(
819            DoubleBorder::default().get_border_set(),
820            BorderSet::double()
821        );
822        assert_eq!(DoubleBorder::default().get_borders(), Borders::ALL);
823        assert_eq!(ThickBorder::default().get_border_set(), BorderSet::thick());
824        assert_eq!(ThickBorder::default().get_borders(), Borders::ALL);
825    }
826
827    #[test]
828    fn complete_border_renders_correctly() {
829        let mut buffer = create_test_buffer(5, 5);
830        let area = Rect::new(0, 0, 5, 5);
831        let border = PlainBorder::default();
832
833        border.before_render(area, &mut buffer);
834
835        // Verify corners
836        assert_eq!(buffer[(0, 0)].symbol(), "┌");
837        assert_eq!(buffer[(4, 0)].symbol(), "┐");
838        assert_eq!(buffer[(0, 4)].symbol(), "└");
839        assert_eq!(buffer[(4, 4)].symbol(), "┘");
840
841        // Verify sides
842        assert_eq!(buffer[(0, 1)].symbol(), "│"); // left
843        assert_eq!(buffer[(4, 1)].symbol(), "│"); // right
844        assert_eq!(buffer[(1, 0)].symbol(), "─"); // top
845        assert_eq!(buffer[(1, 4)].symbol(), "─"); // bottom
846    }
847
848    #[test]
849    fn partial_border_renders_only_specified_sides() {
850        let mut buffer = create_test_buffer(5, 5);
851        let area = Rect::new(0, 0, 5, 5);
852        let border = PlainBorder::new(Borders::TOP | Borders::LEFT);
853
854        border.before_render(area, &mut buffer);
855
856        // Should render top-left corner and associated sides
857        assert_eq!(buffer[(0, 0)].symbol(), "┌");
858        assert_eq!(buffer[(0, 1)].symbol(), "│");
859
860        // Should not render other borders
861        assert_eq!(buffer[(0, 4)].symbol(), "│");
862        assert_eq!(buffer[(4, 0)].symbol(), "─");
863        assert_eq!(buffer[(4, 1)].symbol(), " ");
864        assert_eq!(buffer[(1, 4)].symbol(), " ");
865    }
866
867    #[test]
868    fn area_modification_accounts_for_all_borders() {
869        let area = Rect::new(0, 0, 10, 10);
870        let border = PlainBorder::default();
871
872        let inner_area = border.modify_area(area);
873
874        assert_eq!(inner_area.x, 1);
875        assert_eq!(inner_area.y, 1);
876        assert_eq!(inner_area.width, 8);
877        assert_eq!(inner_area.height, 8);
878    }
879
880    #[test]
881    fn area_modification_accounts_for_partial_borders() {
882        let area = Rect::new(0, 0, 10, 10);
883        let border = PlainBorder::new(Borders::TOP | Borders::LEFT);
884
885        let inner_area = border.modify_area(area);
886
887        assert_eq!(inner_area.x, 1); // Left border reduces x
888        assert_eq!(inner_area.y, 1); // Top border reduces y
889        assert_eq!(inner_area.width, 9); // Only left border reduces width
890        assert_eq!(inner_area.height, 9); // Only top border reduces height
891    }
892
893    #[test]
894    fn border_deref_provides_border_access() {
895        let mut border = PlainBorder::new(Borders::TOP);
896        assert_eq!(*border, Borders::TOP);
897
898        // Test DerefMut
899        *border |= Borders::LEFT;
900        assert_eq!(border.get_borders(), Borders::TOP | Borders::LEFT);
901    }
902
903    #[test]
904    fn custom_border_works_with_modified_border_set() {
905        let custom_border_set = BorderSet::plain().corners('*').horizontals('=');
906        let custom_border =
907            CustomBorder::new(custom_border_set).borders(Borders::TOP | Borders::LEFT);
908
909        assert_eq!(custom_border.get_borders(), Borders::TOP | Borders::LEFT);
910
911        let border_set = custom_border.get_border_set();
912        assert_eq!(border_set.top_left, '*');
913        assert_eq!(border_set.top, '=');
914        assert_eq!(border_set.left, '│'); // Unchanged from plain()
915    }
916
917    #[test]
918    fn edge_case_single_cell_area() {
919        let mut buffer = create_test_buffer(1, 1);
920        let area = Rect::new(0, 0, 1, 1);
921        let border = PlainBorder::default();
922
923        border.before_render(area, &mut buffer);
924
925        // In a 1x1 area, only the top-left corner should be rendered
926        // since all borders converge to the same cell
927        assert_eq!(buffer[(0, 0)].symbol(), "┌");
928    }
929
930    #[test]
931    fn zero_area_handling() {
932        let area = Rect::new(0, 0, 0, 0);
933        let border = PlainBorder::default();
934
935        let inner_area = border.modify_area(area);
936
937        // Should handle zero-sized areas gracefully
938        assert_eq!(inner_area.width, 0);
939        assert_eq!(inner_area.height, 0);
940    }
941
942    #[cfg(feature = "serde")]
943    #[test]
944    fn plain_border_serialization() {
945        let border = PlainBorder::default();
946        let json = serde_json::to_string_pretty(&border).unwrap();
947
948        let restored: PlainBorder = serde_json::from_str(&json).unwrap();
949        assert_eq!(restored, border);
950    }
951
952    #[cfg(feature = "serde")]
953    #[test]
954    fn char_border_serialization() {
955        let border = CharBorder::new('*');
956        let json = serde_json::to_string_pretty(&border).unwrap();
957
958        let restored: CharBorder = serde_json::from_str(&json).unwrap();
959        assert_eq!(restored, border);
960    }
961
962    #[cfg(feature = "serde")]
963    #[test]
964    fn custom_border_serialization() {
965        let custom_border_set = BorderSet::plain().corners('*').horizontals('=');
966        let border = CustomBorder::new(custom_border_set).borders(Borders::TOP | Borders::LEFT);
967        let json = serde_json::to_string_pretty(&border).unwrap();
968
969        let restored: CustomBorder = serde_json::from_str(&json).unwrap();
970        assert_eq!(restored, border);
971    }
972}