1use super::{Area, Shapes, unit};
4use crate::colour::Style;
5use crate::surface::{Cells, Surface};
6
7const EIGHTHS: [char; 7] = ['▏', '▎', '▍', '▌', '▋', '▊', '▉'];
9
10fn put(surface: &mut Surface<Cells>, x: i64, y: i64, character: char, style: Style) {
11 if (0..i64::from(u32::MAX)).contains(&x) && (0..i64::from(u32::MAX)).contains(&y) {
12 surface.put(x as u32, y as u32, character, style);
13 }
14}
15
16fn span(from: i64, to: i64, limit: u32) -> core::ops::Range<i64> {
18 from.max(0)..to.min(i64::from(limit))
19}
20
21fn frame(surface: &mut Surface<Cells>, area: Area, corners: [char; 4], style: Style) {
22 if area.w == 0 || area.h == 0 {
23 return;
24 }
25 let (cols, rows) = surface.size();
26 let (left, top) = (area.left(), area.top());
27 let (right, bottom) = (area.right() - 1, area.bottom() - 1);
28 if area.h == 1 {
29 for x in span(left, right + 1, cols) {
30 put(surface, x, top, '─', style);
31 }
32 return;
33 }
34 if area.w == 1 {
35 for y in span(top, bottom + 1, rows) {
36 put(surface, left, y, '│', style);
37 }
38 return;
39 }
40 for x in span(left + 1, right, cols) {
41 put(surface, x, top, '─', style);
42 put(surface, x, bottom, '─', style);
43 }
44 for y in span(top + 1, bottom, rows) {
45 put(surface, left, y, '│', style);
46 put(surface, right, y, '│', style);
47 }
48 put(surface, left, top, corners[0], style);
49 put(surface, right, top, corners[1], style);
50 put(surface, left, bottom, corners[2], style);
51 put(surface, right, bottom, corners[3], style);
52}
53
54fn inside(dx: i64, dy: i64, rx: i64, ry: i64) -> bool {
57 let (dx, dy, rx, ry) = (dx * 2, dy * 2, rx * 2 + 1, ry * 2 + 1);
59 dx * dx * ry * ry + dy * dy * rx * rx <= rx * rx * ry * ry
60}
61
62fn ellipse(
63 surface: &mut Surface<Cells>,
64 centre: (i32, i32),
65 radius: u32,
66 fill: bool,
67 style: Style,
68) {
69 let (cols, rows) = surface.size();
70 let (cx, cy) = (i64::from(centre.0), i64::from(centre.1));
71 let (rx, ry) = (i64::from(radius) * 2, i64::from(radius));
72 for y in span(cy - ry, cy + ry + 1, rows) {
73 for x in span(cx - rx, cx + rx + 1, cols) {
74 let (dx, dy) = (x - cx, y - cy);
75 if !inside(dx, dy, rx, ry) {
76 continue;
77 }
78 let edge = !inside(dx - 1, dy, rx, ry)
79 || !inside(dx + 1, dy, rx, ry)
80 || !inside(dx, dy - 1, rx, ry)
81 || !inside(dx, dy + 1, rx, ry);
82 if fill {
83 put(surface, x, y, '█', style);
84 } else if edge {
85 put(surface, x, y, '•', style);
86 }
87 }
88 }
89}
90
91impl Shapes for Surface<Cells> {
92 type Ink = Style;
93
94 fn stroke_rect(&mut self, area: Area, ink: Style) {
95 frame(self, area, ['┌', '┐', '└', '┘'], ink);
96 }
97
98 fn fill_rect(&mut self, area: Area, ink: Style) {
101 let (cols, rows) = self.size();
102 for y in span(area.top(), area.bottom(), rows) {
103 for x in span(area.left(), area.right(), cols) {
104 put(self, x, y, '█', ink);
105 }
106 }
107 }
108
109 fn line(&mut self, from: (i32, i32), to: (i32, i32), ink: Style) {
110 let (cols, rows) = self.size();
111 let (fx, fy) = (i64::from(from.0), i64::from(from.1));
112 let (tx, ty) = (i64::from(to.0), i64::from(to.1));
113 let glyph = if fy == ty {
114 '─'
115 } else if fx == tx {
116 '│'
117 } else if (tx > fx) == (ty > fy) {
118 '╲'
119 } else {
120 '╱'
121 };
122 let Some(((mut x, mut y), (x1, y1))) = super::clip_segment((fx, fy), (tx, ty), cols, rows)
123 else {
124 return;
125 };
126 let (dx, dy) = ((x1 - x).abs(), -(y1 - y).abs());
127 let (sx, sy) = (if x < x1 { 1 } else { -1 }, if y < y1 { 1 } else { -1 });
128 let mut error = dx + dy;
129 loop {
130 put(self, x, y, glyph, ink);
131 if x == x1 && y == y1 {
132 break;
133 }
134 let doubled = 2 * error;
135 if doubled >= dy {
136 error += dy;
137 x += sx;
138 }
139 if doubled <= dx {
140 error += dx;
141 y += sy;
142 }
143 }
144 }
145
146 fn bar(&mut self, area: Area, fraction: f32, fill: Style, track: Style) {
149 let (cols, rows) = self.size();
150 let eighths = (unit(fraction) * area.w as f32 * 8.0 + 0.5) as i64;
151 for y in span(area.top(), area.bottom(), rows) {
152 for x in span(area.left(), area.right(), cols) {
153 let here = (eighths - (x - area.left()) * 8).clamp(0, 8);
154 match here {
155 8 => put(self, x, y, '█', fill),
156 0 => put(self, x, y, '░', track),
157 partial => put(
158 self,
159 x,
160 y,
161 EIGHTHS[partial as usize - 1],
162 fill.with_bg(track.bg),
163 ),
164 }
165 }
166 }
167 }
168
169 fn rounded_rect(&mut self, area: Area, radius: u32, ink: Style) {
170 let corners = if radius == 0 {
171 ['┌', '┐', '└', '┘']
172 } else {
173 ['╭', '╮', '╰', '╯']
174 };
175 frame(self, area, corners, ink);
176 }
177
178 fn fill_rounded_rect(&mut self, area: Area, radius: u32, ink: Style) {
180 self.fill_rect(area, ink);
181 if radius == 0 || area.w < 2 || area.h < 2 {
182 return;
183 }
184 let (right, bottom) = (area.right() - 1, area.bottom() - 1);
185 put(self, area.left(), area.top(), '▗', ink);
186 put(self, right, area.top(), '▖', ink);
187 put(self, area.left(), bottom, '▝', ink);
188 put(self, right, bottom, '▘', ink);
189 }
190
191 fn circle(&mut self, centre: (f32, f32), radius: f32, ink: Style) {
192 ellipse(self, cell_centre(centre), cell_radius(radius), false, ink);
193 }
194
195 fn fill_circle(&mut self, centre: (f32, f32), radius: f32, ink: Style) {
196 ellipse(self, cell_centre(centre), cell_radius(radius), true, ink);
197 }
198
199 fn label(&mut self, x: i32, y: i32, text: &str, ink: Style) -> u32 {
200 crate::surface::write_text_clipped(self, i64::from(x), i64::from(y), text, ink)
201 }
202}
203
204fn cell_centre((x, y): (f32, f32)) -> (i32, i32) {
206 let whole = |v: f32| {
207 if v.is_nan() {
208 0
209 } else {
210 libm::floorf(v).clamp(i32::MIN as f32, i32::MAX as f32) as i32
211 }
212 };
213 (whole(x), whole(y))
214}
215
216fn cell_radius(radius: f32) -> u32 {
217 if radius.is_nan() {
218 0
219 } else {
220 libm::roundf(radius).clamp(0.0, u32::MAX as f32) as u32
221 }
222}