1use crate::prelude::*;
8use shuyi_core::Pos;
9
10use super::layout::Grid;
11use crate::frame::color::Rgb;
12use crate::frame::glyph_buffer::{Layer, Overlay, PxRect, Rect};
13
14pub const LINE_W: i32 = 3;
16
17const LINE_HALF: i32 = 1;
19
20const ARROW_LEN: i32 = 6;
23
24const fn line_mid(len: i32) -> i32 {
27 len / 2
28}
29
30const fn line_offset(len: i32) -> i32 {
32 line_mid(len) - LINE_HALF
33}
34
35const fn arrow_base(len: i32) -> i32 {
38 (len - ARROW_LEN) / 2
39}
40
41fn segment(a: Pos, b: Pos, grid: &Grid, from_edge: bool) -> PxRect {
44 let ((ax, ay), (bx, by)) = (grid.tile_px(a), grid.tile_px(b));
45 let (w, h) = grid.tile;
46 let (ox, oy) = (line_offset(w), line_offset(h));
47 let (mut x0, mut y0) = (ax.min(bx) + ox, ay.min(by) + oy);
48 let (mut x1, mut y1) = (ax.max(bx) + ox + LINE_W, ay.max(by) + oy + LINE_W);
49 if from_edge {
50 match (b.x - a.x, b.y - a.y) {
51 (1, _) => x0 = ax + w,
52 (-1, _) => x1 = ax,
53 (_, 1) => y0 = ay + h,
54 _ => y1 = ay,
55 }
56 }
57 Rect::new(x0, y0, x1 - x0, y1 - y0)
58}
59
60fn arrowhead(from: Pos, to: Pos, grid: &Grid) -> Vec<PxRect> {
64 let (px, py) = grid.tile_px(to);
65 let (w, h) = grid.tile;
66 let (mid_x, mid_y) = (line_mid(w), line_mid(h));
67 (0..ARROW_LEN)
68 .map(|i| {
69 let half = ARROW_LEN - 1 - i;
70 let across = 2 * half + 1;
71 let near = |len| arrow_base(len) + i;
72 let far = |len| len - 1 - arrow_base(len) - i;
73 match (to.x - from.x, to.y - from.y) {
74 (1, _) => Rect::new(px + near(w), py + mid_y - half, 1, across),
75 (-1, _) => Rect::new(px + far(w), py + mid_y - half, 1, across),
76 (_, 1) => Rect::new(px + mid_x - half, py + near(h), across, 1),
77 _ => Rect::new(px + mid_x - half, py + far(h), across, 1),
78 }
79 })
80 .collect()
81}
82
83pub fn path_overlays(path: &[Pos], grid: &Grid, color: Rgb) -> Vec<Overlay> {
89 let view = grid.bounds();
90 let art = grid.at_art_size();
91 let mut out = Vec::new();
92 let mut add = |rect: PxRect, layer| {
93 if let Some(rect) = grid.zoomed(grid.corner, rect).intersect(&view) {
94 out.push(Overlay::new(rect, color, layer));
95 }
96 };
97 for (i, pair) in path.windows(2).enumerate() {
98 add(segment(pair[0], pair[1], &art, i == 0), Layer::Under);
99 }
100 if let [.., from, to] = path {
101 for rect in arrowhead(*from, *to, &art) {
102 add(rect, Layer::Over);
103 }
104 }
105 out
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 const RED: Rgb = Rgb::new(255, 0, 0);
113
114 fn p(x: i32, y: i32) -> Pos {
115 Pos::new(x, y)
116 }
117
118 fn view() -> Grid {
121 Grid::top_down(p(0, 0), (0, 0), (16, 16), (35, 30), 1)
122 }
123
124 fn rects(path: &[Pos]) -> Vec<(Layer, i32, i32, i32, i32)> {
127 path_overlays(path, &view(), RED)
128 .iter()
129 .map(|o| (o.layer, o.rect.x, o.rect.y, o.rect.w, o.rect.h))
130 .collect()
131 }
132
133 #[test]
134 fn a_one_tile_path_draws_nothing() {
135 let found = rects(&[p(2, 2)]);
136 assert!(found.is_empty(), "{found:?}");
137 }
138
139 #[test]
140 fn the_line_starts_at_the_unit_tile_edge_and_ends_in_one_arrowhead() {
141 let r = rects(&[p(1, 1), p(2, 1), p(2, 2)]);
143 let under: Vec<_> = r.iter().filter(|o| o.0 == Layer::Under).collect();
144 assert_eq!(
145 under,
146 [
147 &(Layer::Under, 32, 23, 10, 3),
149 &(Layer::Under, 39, 23, 3, 19),
151 ]
152 );
153 let over: Vec<_> = r.iter().filter(|o| o.0 == Layer::Over).copied().collect();
156 let expect: Vec<_> = (0..6)
157 .map(|i| (Layer::Over, 40 - (5 - i), 37 + i, 2 * (5 - i) + 1, 1))
158 .collect();
159 assert_eq!(over, expect);
160 }
161
162 #[test]
163 fn arrowheads_point_the_way_of_the_last_step() {
164 let tips = |path: &[Pos]| {
165 let over: Vec<_> = rects(path)
166 .into_iter()
167 .filter(|o| o.0 == Layer::Over)
168 .collect();
169 let (_, x, y, w, h) = over[over.len() - 1];
171 assert_eq!((w, h), (1, 1));
172 (x, y)
173 };
174 assert_eq!(tips(&[p(0, 1), p(1, 1)]), (16 + 10, 24));
176 assert_eq!(tips(&[p(2, 1), p(1, 1)]), (16 + 5, 24));
177 assert_eq!(tips(&[p(1, 0), p(1, 1)]), (24, 16 + 10));
178 assert_eq!(tips(&[p(1, 2), p(1, 1)]), (24, 16 + 5));
179 let first = |path: &[Pos]| rects(path)[0];
181 assert_eq!(first(&[p(1, 1), p(0, 1)]), (Layer::Under, 7, 23, 9, 3));
182 assert_eq!(first(&[p(1, 1), p(1, 0)]), (Layer::Under, 23, 7, 3, 9));
183 assert_eq!(first(&[p(1, 1), p(1, 2)]), (Layer::Under, 23, 32, 3, 10));
184 let base = |path: &[Pos]| {
187 let over: Vec<_> = rects(path)
188 .into_iter()
189 .filter(|o| o.0 == Layer::Over)
190 .collect();
191 over[0]
192 };
193 assert_eq!(base(&[p(0, 1), p(1, 1)]), (Layer::Over, 21, 19, 1, 11));
194 assert_eq!(base(&[p(2, 1), p(1, 1)]), (Layer::Over, 26, 19, 1, 11));
195 assert_eq!(base(&[p(1, 0), p(1, 1)]), (Layer::Over, 19, 21, 11, 1));
196 assert_eq!(base(&[p(1, 2), p(1, 1)]), (Layer::Over, 19, 26, 11, 1));
197 assert_eq!(2 * LINE_HALF + 1, LINE_W);
198 assert_eq!(line_offset(16) + LINE_HALF, line_mid(16));
199 assert_eq!((line_offset(16), line_mid(16), arrow_base(16)), (7, 8, 5));
200 }
201
202 #[test]
203 fn the_geometry_follows_the_tile_size() {
204 let grid = Grid::top_down(p(0, 0), (0, 0), (24, 20), (10, 10), 1);
206 let r: Vec<_> = path_overlays(&[p(1, 1), p(2, 1), p(2, 2)], &grid, RED)
207 .iter()
208 .map(|o| (o.layer, o.rect.x, o.rect.y, o.rect.w, o.rect.h))
209 .collect();
210 assert_eq!(r[0], (Layer::Under, 48, 29, 14, 3));
214 assert_eq!(r[1], (Layer::Under, 59, 29, 3, 23));
215 assert_eq!(r[2], (Layer::Over, 55, 47, 11, 1));
218 assert_eq!(r[7], (Layer::Over, 60, 52, 1, 1));
219 assert_eq!(r.len(), 8);
220 let left = path_overlays(&[p(3, 1), p(2, 1)], &grid, RED);
222 let xs: Vec<i32> = left
223 .iter()
224 .filter(|o| o.layer == Layer::Over)
225 .map(|o| o.rect.x)
226 .collect();
227 assert_eq!(xs, [62, 61, 60, 59, 58, 57]);
228 assert_eq!((line_offset(24), line_mid(24), arrow_base(24)), (11, 12, 9));
229 assert_eq!(arrow_base(20), 7);
230 let path = [p(1, 1), p(2, 1), p(2, 2)];
233 let zoomed = Grid {
234 corner: (8, 16),
235 tile: (48, 40),
236 scale: 2,
237 ..grid
238 };
239 let twice: Vec<_> = path_overlays(&path, &zoomed, RED)
240 .iter()
241 .map(|o| (o.layer, o.rect.x, o.rect.y, o.rect.w, o.rect.h))
242 .collect();
243 let doubled: Vec<_> = r
244 .iter()
245 .map(|&(layer, x, y, w, h)| (layer, 8 + 2 * x, 16 + 2 * y, 2 * w, 2 * h))
246 .collect();
247 assert_eq!(twice, doubled);
248 assert_eq!(twice[0], (Layer::Under, 104, 74, 28, 6));
249 let two = Grid {
251 tiles: (2, 1),
252 ..zoomed
253 };
254 let cut = path_overlays(&[p(0, 0), p(1, 0), p(2, 0)], &two, RED);
255 let right = two.bounds().x + two.bounds().w;
256 assert!(cut.iter().all(|o| o.rect.x + o.rect.w <= right), "{cut:?}");
257 assert_eq!(cut.len(), 2);
258 }
259
260 #[test]
261 fn overlays_are_clipped_to_the_map_view() {
262 let r = rects(&[p(33, 0), p(34, 0), p(35, 0)]);
264 let right = view().bounds().w;
265 assert_eq!(right, 560);
266 assert!(r.iter().all(|o| o.1 + o.3 <= right), "{r:?}");
267 assert_eq!(r.len(), 2, "no arrowhead off the view: {r:?}");
268 }
269}