duat_term/printer/edges/
mod.rs

1use duat_core::ui::Axis;
2
3use super::{VarPoint, variables::Variables};
4use crate::area::Coord;
5
6mod combinations;
7
8pub use combinations::*;
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    lhs: VarPoint,
36    rhs: VarPoint,
37    axis: Axis,
38    fr: Frame,
39}
40
41impl Edge {
42    /// Returns a new instance of [`Edge`].
43    pub fn new(lhs: VarPoint, rhs: VarPoint, axis: Axis, fr: Frame) -> Self {
44        Self { lhs, rhs, axis, fr }
45    }
46
47    /// The [`Coords`] that will be used to draw the line.
48    pub fn coords(&self, vars: &mut Variables) -> Option<EdgeCoords> {
49        let (lhs, _) = vars.coord(self.lhs, false);
50        let (rhs, _) = vars.coord(self.rhs, false);
51        if lhs.x == rhs.x || lhs.y == rhs.y {
52            return None;
53        }
54
55        let start = match self.axis {
56            Axis::Horizontal => Coord::new(rhs.x, lhs.y),
57            Axis::Vertical => Coord::new(lhs.x, rhs.y),
58        };
59        let end = match self.axis {
60            Axis::Horizontal => Coord::new(lhs.x, rhs.y),
61            Axis::Vertical => Coord::new(rhs.x, lhs.y),
62        };
63
64        Some(EdgeCoords::new(start, end, self.axis, self.fr.brush()))
65    }
66}
67
68#[derive(Clone, Copy)]
69pub struct EdgeCoords {
70    pub tl: Coord,
71    pub br: Coord,
72    pub axis: Axis,
73    pub line: Option<Brush>,
74}
75
76impl EdgeCoords {
77    fn new(tl: Coord, br: Coord, axis: Axis, line: Option<Brush>) -> Self {
78        Self { tl, br, axis, line }
79    }
80
81    pub fn crossing(&self, other: EdgeCoords) -> Option<(Coord, [Option<Brush>; 4])> {
82        if let Axis::Vertical = self.axis {
83            if let Axis::Vertical = other.axis {
84                if self.br.x == other.tl.x && self.br.y + 2 == other.tl.y {
85                    let coord = Coord::new(self.br.x, self.br.y + 1);
86                    return Some((coord, [None, self.line, None, other.line]));
87                } else {
88                    return None;
89                }
90            } else {
91                return other.crossing(*self);
92            }
93        }
94
95        if let Axis::Horizontal = other.axis {
96            if self.br.y == other.br.y && self.br.x + 2 == other.tl.x {
97                let coord = Coord::new(self.br.x + 1, self.br.y);
98                Some((coord, [other.line, None, self.line, None]))
99            } else {
100                None
101            }
102        } else if self.tl.x <= other.tl.x + 1 && other.tl.x <= self.br.x + 1 {
103            let right_height = (other.tl.y..=other.br.y + 2).contains(&(self.br.y + 1));
104            let right = match right_height && other.br.x <= self.br.x {
105                true => self.line,
106                false => None,
107            };
108            let up = match other.tl.y <= self.tl.y && self.br.y <= other.br.y + 1 {
109                true => other.line,
110                false => None,
111            };
112            let left = match right_height && self.tl.x <= other.tl.x {
113                true => self.line,
114                false => None,
115            };
116            let down = match self.br.y <= other.br.y && other.tl.y <= self.tl.y + 1 {
117                true => other.line,
118                false => None,
119            };
120
121            if up.is_some() || down.is_some() {
122                let coord = Coord { x: other.tl.x, y: self.tl.y };
123
124                Some((coord, [right, up, left, down]))
125            } else {
126                None
127            }
128        } else {
129            None
130        }
131    }
132}
133
134/// Where to apply a [`Brush`] around widgets
135///
136/// This type serves to determine whether frames will be applied only
137/// between widgets, around the whole application, or not at all:
138///
139/// - [`Empty`]: Do not frame at all.
140/// - [`Surround`]: Frame on all sides.
141/// - [`Border`]: Frame only around files.
142/// - [`Vertical`]: Like [`Surround`], but only on vertical lines.
143/// - [`VerBorder`]: Like [`Border`], but only on vertical lines.
144/// - [`Horizontal`]: Like [`Surround`], but only on horizontal lines.
145/// - [`HorBorder`]: Like [`Border`], but only on horizontal lines.
146///
147/// [`Empty`]: Frame::Empty
148/// [`Surround`]: Frame::Surround
149/// [`Border`]: Frame::Border
150/// [`Vertical`]: Frame::Vertical
151/// [`VerBorder`]: Frame::VerBorder
152/// [`Horizontal`]: Frame::Horizontal
153/// [`HorBorder`]: Frame::HorBorder
154#[derive(Clone, Copy, Debug)]
155pub enum Frame {
156    /// No frame
157    Empty,
158    /// Frame the window's edges and borders between widgets
159    Surround(Brush),
160    /// Frame borders between widgets
161    Border(Brush),
162    /// Frame vertical window edges and borders between widgets
163    Vertical(Brush),
164    /// Frame vertical borders between widgets
165    VerBorder(Brush),
166    /// Frame horizontal window edges and borders between widgets
167    Horizontal(Brush),
168    /// Frame horizontal borders between widgets
169    HorBorder(Brush),
170}
171
172impl Default for Frame {
173    fn default() -> Self {
174        Self::Border(Brush::Regular)
175    }
176}
177
178impl Frame {
179    /// Same as [`files_edges`], but on only one [`Axis`]
180    ///
181    /// [`files_edges`]: Self::files_edges
182    pub(crate) fn files_edge_on(&self, axis: Axis) -> f64 {
183        let (hor_fr, ver_fr) = self.files_edges();
184        match axis {
185            Axis::Horizontal => hor_fr,
186            Axis::Vertical => ver_fr,
187        }
188    }
189
190    /// Same as [`border_edges`], but on only one [`Axis`]
191    ///
192    /// [`border_edges`]: Self::border_edges
193    pub(crate) fn border_edge_on(&self, axis: Axis) -> f64 {
194        let (hor_fr, ver_fr) = self.border_edges();
195        match axis {
196            Axis::Horizontal => hor_fr,
197            Axis::Vertical => ver_fr,
198        }
199    }
200
201    /// The [`Brush`] in use, [`None`] for [`Frame::Empty`]
202    fn brush(&self) -> Option<Brush> {
203        match self {
204            Self::Empty => None,
205            Self::Surround(brush)
206            | Self::Border(brush)
207            | Self::Vertical(brush)
208            | Self::VerBorder(brush)
209            | Self::Horizontal(brush)
210            | Self::HorBorder(brush) => Some(*brush),
211        }
212    }
213
214    /// The edges below and to the right of [`File`] regions
215    ///
216    /// [`File`]: duat_core::widgets::File
217    fn files_edges(&self) -> (f64, f64) {
218        match self {
219            Self::Surround(_) => (1.0, 1.0),
220            Self::Vertical(_) => (1.0, 0.0),
221            Self::Horizontal(_) => (0.0, 1.0),
222            _ => (0.0, 0.0),
223        }
224    }
225
226    /// The edges below and to the right of the [`File`]s region
227    ///
228    /// [`File`]: duat_core::widgets::File
229    fn border_edges(&self) -> (f64, f64) {
230        match self {
231            Self::Surround(_) | Self::Border(_) => (1.0, 1.0),
232            Self::Vertical(_) | Self::VerBorder(_) => (1.0, 0.0),
233            Self::Horizontal(_) | Self::HorBorder(_) => (0.0, 1.0),
234            Self::Empty => (0.0, 0.0),
235        }
236    }
237}