1use std::collections::HashMap;
2use std::ops::RangeInclusive;
3
4use crate::TextStyle;
5
6use super::Position;
7use super::cell::validate_cell_glyph;
8use super::cell_primitives::{clip_line, line_points};
9use crate::view::geometry::Size;
10
11#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
12pub(super) struct LineConnections(u8);
13
14impl LineConnections {
15 const UP: Self = Self(1);
16 const RIGHT: Self = Self(2);
17 const DOWN: Self = Self(4);
18 const LEFT: Self = Self(8);
19
20 pub(super) const fn union(self, other: Self) -> Self {
21 Self(self.0 | other.0)
22 }
23}
24
25#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
32pub struct LineContinuations(u8);
33
34impl LineContinuations {
35 pub const NONE: Self = Self(0);
37 pub const START: Self = Self(1);
39 pub const END: Self = Self(2);
41 pub const BOTH: Self = Self(Self::START.0 | Self::END.0);
43
44 const fn includes_start(self) -> bool {
45 self.0 & Self::START.0 != 0
46 }
47
48 const fn includes_end(self) -> bool {
49 self.0 & Self::END.0 != 0
50 }
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub struct LineGlyphs {
59 pub isolated: char,
61 pub end_up: char,
63 pub end_right: char,
65 pub end_down: char,
67 pub end_left: char,
69 pub vertical: char,
71 pub horizontal: char,
73 pub corner_down_right: char,
75 pub corner_down_left: char,
77 pub corner_up_right: char,
79 pub corner_up_left: char,
81 pub tee_right: char,
83 pub tee_down: char,
85 pub tee_left: char,
87 pub tee_up: char,
89 pub cross: char,
91}
92
93impl LineGlyphs {
94 pub const NORMAL: Self = Self {
96 isolated: '•',
97 end_up: '│',
98 end_right: '─',
99 end_down: '│',
100 end_left: '─',
101 vertical: '│',
102 horizontal: '─',
103 corner_down_right: '┌',
104 corner_down_left: '┐',
105 corner_up_right: '└',
106 corner_up_left: '┘',
107 tee_right: '├',
108 tee_down: '┬',
109 tee_left: '┤',
110 tee_up: '┴',
111 cross: '┼',
112 };
113
114 pub const ROUNDED: Self = Self {
116 corner_down_right: '╭',
117 corner_down_left: '╮',
118 corner_up_right: '╰',
119 corner_up_left: '╯',
120 ..Self::NORMAL
121 };
122
123 pub const ASCII: Self = Self {
125 isolated: '*',
126 end_up: '|',
127 end_right: '-',
128 end_down: '|',
129 end_left: '-',
130 vertical: '|',
131 horizontal: '-',
132 corner_down_right: '+',
133 corner_down_left: '+',
134 corner_up_right: '+',
135 corner_up_left: '+',
136 tee_right: '+',
137 tee_down: '+',
138 tee_left: '+',
139 tee_up: '+',
140 cross: '+',
141 };
142
143 pub const THICK: Self = Self {
145 isolated: '•',
146 end_up: '┃',
147 end_right: '━',
148 end_down: '┃',
149 end_left: '━',
150 vertical: '┃',
151 horizontal: '━',
152 corner_down_right: '┏',
153 corner_down_left: '┓',
154 corner_up_right: '┗',
155 corner_up_left: '┛',
156 tee_right: '┣',
157 tee_down: '┳',
158 tee_left: '┫',
159 tee_up: '┻',
160 cross: '╋',
161 };
162
163 pub const DOUBLE: Self = Self {
165 isolated: '•',
166 end_up: '║',
167 end_right: '═',
168 end_down: '║',
169 end_left: '═',
170 vertical: '║',
171 horizontal: '═',
172 corner_down_right: '╔',
173 corner_down_left: '╗',
174 corner_up_right: '╚',
175 corner_up_left: '╝',
176 tee_right: '╠',
177 tee_down: '╦',
178 tee_left: '╣',
179 tee_up: '╩',
180 cross: '╬',
181 };
182
183 pub const HIDDEN: Self = Self {
185 isolated: ' ',
186 end_up: ' ',
187 end_right: ' ',
188 end_down: ' ',
189 end_left: ' ',
190 vertical: ' ',
191 horizontal: ' ',
192 corner_down_right: ' ',
193 corner_down_left: ' ',
194 corner_up_right: ' ',
195 corner_up_left: ' ',
196 tee_right: ' ',
197 tee_down: ' ',
198 tee_left: ' ',
199 tee_up: ' ',
200 cross: ' ',
201 };
202
203 pub(super) fn glyph(self, connections: LineConnections) -> char {
204 match connections.0 {
205 0 => self.isolated,
206 1 => self.end_up,
207 2 => self.end_right,
208 4 => self.end_down,
209 8 => self.end_left,
210 5 => self.vertical,
211 10 => self.horizontal,
212 6 => self.corner_down_right,
213 12 => self.corner_down_left,
214 3 => self.corner_up_right,
215 9 => self.corner_up_left,
216 7 => self.tee_right,
217 14 => self.tee_down,
218 13 => self.tee_left,
219 11 => self.tee_up,
220 _ => self.cross,
221 }
222 }
223
224 fn validate(self) {
225 for glyph in [
226 self.isolated,
227 self.end_up,
228 self.end_right,
229 self.end_down,
230 self.end_left,
231 self.vertical,
232 self.horizontal,
233 self.corner_down_right,
234 self.corner_down_left,
235 self.corner_up_right,
236 self.corner_up_left,
237 self.tee_right,
238 self.tee_down,
239 self.tee_left,
240 self.tee_up,
241 self.cross,
242 ] {
243 let mut encoded = [0; 4];
244 let glyph = glyph.encode_utf8(&mut encoded);
245 validate_cell_glyph(glyph);
246 }
247 }
248}
249
250#[derive(Debug, Clone, Copy, PartialEq, Eq)]
251struct Segment {
252 from: Position,
253 to: Position,
254 axis: SegmentAxis,
255 continuations: LineContinuations,
256}
257
258#[derive(Debug, Clone, Copy, PartialEq, Eq)]
259enum SegmentAxis {
260 Horizontal,
261 Vertical,
262}
263
264#[derive(Debug, Clone, PartialEq, Eq)]
288pub struct LineNetwork {
289 segments: Vec<Segment>,
290 glyphs: LineGlyphs,
291 style: TextStyle,
292}
293
294impl LineNetwork {
295 pub fn new(glyphs: LineGlyphs, style: TextStyle) -> Self {
302 glyphs.validate();
303 Self {
304 segments: Vec::new(),
305 glyphs,
306 style,
307 }
308 }
309
310 pub fn horizontal(&mut self, y: i64, columns: RangeInclusive<i64>) -> &mut Self {
314 self.horizontal_with(y, columns, LineContinuations::NONE)
315 }
316
317 pub fn horizontal_with(
323 &mut self,
324 y: i64,
325 columns: RangeInclusive<i64>,
326 continuations: LineContinuations,
327 ) -> &mut Self {
328 if columns.is_empty() {
329 return self;
330 }
331 self.segments.push(Segment {
332 from: Position::new(*columns.start(), y),
333 to: Position::new(*columns.end(), y),
334 axis: SegmentAxis::Horizontal,
335 continuations,
336 });
337 self
338 }
339
340 pub fn vertical(&mut self, x: i64, rows: RangeInclusive<i64>) -> &mut Self {
344 self.vertical_with(x, rows, LineContinuations::NONE)
345 }
346
347 pub fn vertical_with(
353 &mut self,
354 x: i64,
355 rows: RangeInclusive<i64>,
356 continuations: LineContinuations,
357 ) -> &mut Self {
358 if rows.is_empty() {
359 return self;
360 }
361 self.segments.push(Segment {
362 from: Position::new(x, *rows.start()),
363 to: Position::new(x, *rows.end()),
364 axis: SegmentAxis::Vertical,
365 continuations,
366 });
367 self
368 }
369
370 pub(super) fn rasterize(&self, size: Size) -> Vec<NetworkCell> {
371 let mut cells = Vec::<NetworkCell>::new();
372 let mut indexes = HashMap::<Position, usize>::new();
373 for segment in &self.segments {
374 if let Some((from, to)) = clip_line(segment.from, segment.to, size) {
375 for position in line_points(from, to) {
376 let connections = segment_connections(position, *segment);
377 if let Some(index) = indexes.get(&position).copied() {
378 cells[index].connections = cells[index].connections.union(connections);
379 } else {
380 indexes.insert(position, cells.len());
381 cells.push(NetworkCell {
382 position,
383 connections,
384 });
385 }
386 }
387 }
388 }
389 cells
390 }
391
392 pub(super) const fn glyphs(&self) -> LineGlyphs {
393 self.glyphs
394 }
395
396 pub(super) const fn style(&self) -> &TextStyle {
397 &self.style
398 }
399}
400
401pub(super) struct NetworkCell {
402 pub(super) position: Position,
403 pub(super) connections: LineConnections,
404}
405
406fn segment_connections(position: Position, segment: Segment) -> LineConnections {
407 let (toward_start, toward_end) = match segment.axis {
408 SegmentAxis::Horizontal => (LineConnections::LEFT, LineConnections::RIGHT),
409 SegmentAxis::Vertical => (LineConnections::UP, LineConnections::DOWN),
410 };
411 let mut connections = LineConnections::default();
412 if position != segment.from || segment.continuations.includes_start() {
413 connections = connections.union(toward_start);
414 }
415 if position != segment.to || segment.continuations.includes_end() {
416 connections = connections.union(toward_end);
417 }
418 connections
419}