duat_term/print/
frame.rs

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