duat_term/print/
frame.rs

1use std::sync::{
2    Arc,
3    atomic::{AtomicU32, Ordering},
4};
5
6use duat_core::ui::Axis;
7
8use crate::{area::Coord, print::VarPoint};
9
10/// What type of line should separate widgets
11#[derive(Default, Clone, Copy, Debug)]
12pub enum Brush {
13    /// Uses `─`, `│`, `┐`
14    #[default]
15    Regular,
16    /// Uses `━`, `┃`, `┓`
17    Thick,
18    /// Uses `╌`, `╎`, `┐`
19    Dashed,
20    /// Uses `╍`, `╏`, `┓`
21    ThickDashed,
22    /// Uses `═`, `║`, `╗`
23    Double,
24    /// Uses `─`, `│`, `╮`
25    Rounded,
26    /// Uses `-`, `|`, `+`
27    Ascii,
28    /// Uses `char` for all positions
29    Custom(char),
30}
31
32/// Details of the right/bottom edge of a widget
33#[derive(Debug)]
34pub struct Edge {
35    pub width: Arc<AtomicU32>,
36    lhs: VarPoint,
37    rhs: VarPoint,
38    axis: Axis,
39    fr: Frame,
40}
41
42impl Edge {
43    /// Returns a new instance of [`Edge`].
44    pub fn new(
45        width: &Arc<AtomicU32>,
46        lhs: &VarPoint,
47        rhs: &VarPoint,
48        axis: Axis,
49        fr: Frame,
50    ) -> Self {
51        Self {
52            width: width.clone(),
53            lhs: lhs.clone(),
54            rhs: rhs.clone(),
55            axis,
56            fr,
57        }
58    }
59
60    /// The [`Coords`] that will be used to draw the line.
61    pub fn edge_coords(&self) -> Option<EdgeCoords> {
62        if self.width.load(Ordering::Acquire) == 0 {
63            return None;
64        }
65
66        let start = match self.axis {
67            Axis::Horizontal => Coord::new(self.rhs.x().value(), self.lhs.y().value()),
68            Axis::Vertical => Coord::new(self.lhs.x().value(), self.rhs.y().value()),
69        };
70        let end = match self.axis {
71            Axis::Horizontal => Coord::new(self.lhs.x().value(), self.rhs.y().value()),
72            Axis::Vertical => Coord::new(self.rhs.x().value(), self.lhs.y().value()),
73        };
74
75        Some(EdgeCoords::new(start, end, self.axis, self.fr.brush()))
76    }
77}
78
79#[derive(Clone, Copy)]
80pub struct EdgeCoords {
81    pub tl: Coord,
82    pub br: Coord,
83    pub axis: Axis,
84    pub line: Option<Brush>,
85}
86
87impl EdgeCoords {
88    fn new(tl: Coord, br: Coord, axis: Axis, line: Option<Brush>) -> Self {
89        Self { tl, br, axis, line }
90    }
91
92    #[allow(clippy::type_complexity)]
93    pub fn crossing(&self, other: EdgeCoords) -> Option<(Coord, [Option<Brush>; 4])> {
94        if let Axis::Vertical = self.axis {
95            if let Axis::Vertical = other.axis
96                && self.br.x == other.tl.x
97                && self.br.y + 2 == other.tl.y
98            {
99                let coord = Coord::new(self.br.x, self.br.y + 1);
100                return Some((coord, [None, self.line, None, other.line]));
101            } else {
102                return other.crossing(*self);
103            }
104        }
105
106        if let Axis::Horizontal = other.axis {
107            if self.br.y == other.br.y && self.br.x + 2 == other.tl.x {
108                let coord = Coord::new(self.br.x + 1, self.br.y);
109                Some((coord, [other.line, None, self.line, None]))
110            } else {
111                None
112            }
113        } else if self.tl.x <= other.tl.x + 1 && other.tl.x <= self.br.x + 1 {
114            let right_height = (other.tl.y..=other.br.y + 2).contains(&(self.br.y + 1));
115            let right = match right_height && other.br.x <= self.br.x {
116                true => self.line,
117                false => None,
118            };
119            let up = match other.tl.y <= self.tl.y && self.br.y <= other.br.y + 1 {
120                true => other.line,
121                false => None,
122            };
123            let left = match right_height && self.tl.x <= other.tl.x {
124                true => self.line,
125                false => None,
126            };
127            let down = match self.br.y <= other.br.y && other.tl.y <= self.tl.y + 1 {
128                true => other.line,
129                false => None,
130            };
131
132            if up.is_some() || down.is_some() {
133                let coord = Coord { x: other.tl.x, y: self.tl.y };
134
135                Some((coord, [right, up, left, down]))
136            } else {
137                None
138            }
139        } else {
140            None
141        }
142    }
143}
144
145/// Where to apply a [`Brush`] around widgets
146///
147/// This type serves to determine whether frames will be applied only
148/// between widgets, around the whole application, or not at all:
149///
150/// - [`Empty`]: Do not frame at all.
151/// - [`Surround`]: Frame on all sides.
152/// - [`Border`]: Frame only around files.
153/// - [`Vertical`]: Like [`Surround`], but only on vertical lines.
154/// - [`VerBorder`]: Like [`Border`], but only on vertical lines.
155/// - [`Horizontal`]: Like [`Surround`], but only on horizontal lines.
156/// - [`HorBorder`]: Like [`Border`], but only on horizontal lines.
157///
158/// [`Empty`]: Frame::Empty
159/// [`Surround`]: Frame::Surround
160/// [`Border`]: Frame::Border
161/// [`Vertical`]: Frame::Vertical
162/// [`VerBorder`]: Frame::VerBorder
163/// [`Horizontal`]: Frame::Horizontal
164/// [`HorBorder`]: Frame::HorBorder
165#[derive(Clone, Copy, Debug)]
166pub enum Frame {
167    /// No frame
168    Empty,
169    /// Frame the window's edges and borders between widgets
170    Surround(Brush),
171    /// Frame borders between widgets
172    Border(Brush),
173    /// Frame vertical window edges and borders between widgets
174    Vertical(Brush),
175    /// Frame vertical borders between widgets
176    VerBorder(Brush),
177    /// Frame horizontal window edges and borders between widgets
178    Horizontal(Brush),
179    /// Frame horizontal borders between widgets
180    HorBorder(Brush),
181}
182
183impl Default for Frame {
184    fn default() -> Self {
185        Self::Border(Brush::Regular)
186    }
187}
188
189impl Frame {
190    /// Same as [`files_edges`], but on only one [`Axis`]
191    ///
192    /// [`files_edges`]: Self::files_edges
193    pub(crate) fn files_edge_on(&self, axis: Axis) -> f64 {
194        let (hor_fr, ver_fr) = self.files_edges();
195        match axis {
196            Axis::Horizontal => hor_fr,
197            Axis::Vertical => ver_fr,
198        }
199    }
200
201    /// Same as [`border_edges`], but on only one [`Axis`]
202    ///
203    /// [`border_edges`]: Self::border_edges
204    pub(crate) fn border_edge_on(&self, axis: Axis) -> f64 {
205        let (hor_fr, ver_fr) = self.border_edges();
206        match axis {
207            Axis::Horizontal => hor_fr,
208            Axis::Vertical => ver_fr,
209        }
210    }
211
212    /// The [`Brush`] in use, [`None`] for [`Frame::Empty`]
213    fn brush(&self) -> Option<Brush> {
214        match self {
215            Self::Empty => None,
216            Self::Surround(brush)
217            | Self::Border(brush)
218            | Self::Vertical(brush)
219            | Self::VerBorder(brush)
220            | Self::Horizontal(brush)
221            | Self::HorBorder(brush) => Some(*brush),
222        }
223    }
224
225    /// The edges below and to the right of [`File`] regions
226    ///
227    /// [`File`]: duat_core::widgets::File
228    fn files_edges(&self) -> (f64, f64) {
229        match self {
230            Self::Surround(_) => (1.0, 1.0),
231            Self::Vertical(_) => (1.0, 0.0),
232            Self::Horizontal(_) => (0.0, 1.0),
233            _ => (0.0, 0.0),
234        }
235    }
236
237    /// The edges below and to the right of the [`File`]s region
238    ///
239    /// [`File`]: duat_core::widgets::File
240    fn border_edges(&self) -> (f64, f64) {
241        match self {
242            Self::Surround(_) | Self::Border(_) => (1.0, 1.0),
243            Self::Vertical(_) | Self::VerBorder(_) => (1.0, 0.0),
244            Self::Horizontal(_) | Self::HorBorder(_) => (0.0, 1.0),
245            Self::Empty => (0.0, 0.0),
246        }
247    }
248}