1mod ground;
21
22use crate::prelude::*;
23use alloc::rc::Rc;
24
25use shuyi_assets::image::{ImageRect, Picture};
26use shuyi_assets::tileset::TerrainTiles;
27
28use super::glyph::{self, GlyphSkin};
29use super::layout::{Grid, px_rect};
30use super::path;
31use super::scene::{CursorStyle, MapScene};
32use super::skin::{GridSkin, Looks, UnitPainter, WalkSpeeds};
33use super::threat;
34use crate::frame::glyph_buffer::{GlyphBuffer, Layer, Overlay, PxRect, Rect, Sprite};
35
36#[derive(Debug, Clone, PartialEq)]
38pub struct SpriteSkin {
39 terrain: Option<TerrainTiles>,
41 zoom: i32,
44 zooms: Vec<i32>,
47 glyph: GlyphSkin,
49 walk: WalkSpeeds,
51}
52
53impl SpriteSkin {
54 pub const fn new(terrain: Option<TerrainTiles>, glyph: GlyphSkin, walk: WalkSpeeds) -> Self {
57 Self {
58 terrain,
59 zoom: 1,
60 zooms: Vec::new(),
61 glyph,
62 walk,
63 }
64 }
65
66 #[must_use]
70 pub fn with_zooms(mut self, levels: &[i32]) -> Self {
71 let mut zooms: Vec<i32> = levels.iter().copied().filter(|&z| z >= 1).collect();
72 zooms.sort_unstable();
73 zooms.dedup();
74 self.zooms = zooms;
75 self
76 }
77
78 fn levels(&self) -> &[i32] {
81 match self.terrain {
82 Some(_) => &self.zooms,
83 None => &[],
84 }
85 }
86
87 #[must_use]
89 pub fn zoomed(mut self, zoom: i32) -> Self {
90 self.zoom = zoom.max(1);
91 self
92 }
93
94 pub const fn terrain(&self) -> Option<&TerrainTiles> {
96 self.terrain.as_ref()
97 }
98
99 pub const fn zoom(&self) -> i32 {
102 self.zoom
103 }
104
105 fn scale(&self) -> i32 {
108 match self.terrain {
109 Some(_) => self.zoom,
110 None => 1,
111 }
112 }
113
114 pub fn tile_size(&self) -> (i32, i32) {
118 let zoomed = |px: u32| side(px).saturating_mul(self.scale());
119 match &self.terrain {
120 Some(terrain) => (zoomed(terrain.tile_px.0), zoomed(terrain.tile_px.1)),
121 None => glyph::tile_px(),
122 }
123 }
124
125 pub fn view_tiles(&self, area: Rect) -> (i32, i32) {
128 if self.terrain.is_none() {
129 return glyph::view_tiles(area);
130 }
131 let px = px_rect(area);
132 let (w, h) = self.tile_size();
133 (px.w.max(0) / w, px.h.max(0) / h)
134 }
135
136 pub fn grid<U>(&self, scene: &MapScene<U>, area: Rect) -> Grid {
140 if self.terrain.is_none() {
141 return glyph::Layout::new(scene, area).grid();
142 }
143 let px = px_rect(area);
144 let tile = self.tile_size();
145 let fit = self.view_tiles(area);
146 let margin = |len: i32, n: i32, side: i32| (len.max(0) - n * side) / 2;
147 Grid::top_down(
148 scene.origin,
149 (
150 px.x + margin(px.w, fit.0, tile.0),
151 px.y + margin(px.h, fit.1, tile.1),
152 ),
153 tile,
154 (scene.size.0.clamp(0, fit.0), scene.size.1.clamp(0, fit.1)),
155 self.scale(),
156 )
157 }
158
159 fn paint_own_cursor<U>(
163 &self,
164 looks: &Looks,
165 scene: &MapScene<U>,
166 grid: &Grid,
167 buf: &mut GlyphBuffer,
168 ) {
169 if let Some(c) = &scene.cursor
170 && let Some(tile) = grid.rect(c.pos)
171 {
172 let edge = looks.dark();
173 let color = looks.cursor(&self.glyph.colors).scale(c.brightness);
174 let marks = CursorMarks::of(c.style, grid.zoom());
175 let art = grid.art_tile();
176 let edges = corner_marks(art, 0, marks.arm + 2, marks.thick + 2);
177 let arms = corner_marks(art, 1, marks.arm, marks.thick);
178 for (rects, color) in [(edges, edge), (arms, color)] {
179 for r in rects {
180 let r = grid.zoomed((tile.x, tile.y), r);
181 buf.add_overlay(Overlay::new(r, color, Layer::Over));
182 }
183 }
184 }
185 }
186}
187
188#[derive(Debug, Clone, Copy, PartialEq, Eq)]
191struct CursorMarks {
192 arm: i32,
194 thick: i32,
196}
197
198impl CursorMarks {
199 const fn of(style: CursorStyle, zoom: i32) -> Self {
204 let (arm, thick) = if zoom == 1 { (4, 2) } else { (3, 1) };
205 let longer = match style {
206 CursorStyle::Corners | CursorStyle::TileGlow => 0,
207 CursorStyle::LargeCorners => 1,
208 };
209 Self {
210 arm: arm + longer,
211 thick,
212 }
213 }
214}
215
216impl<U> GridSkin<U> for SpriteSkin {
217 fn name(&self) -> &'static str {
220 match self.terrain {
221 Some(_) => "sprite",
222 None => "sprite_units",
223 }
224 }
225
226 fn tile_size(&self) -> (i32, i32) {
227 SpriteSkin::tile_size(self)
228 }
229
230 fn view_tiles(&self, area: Rect) -> (i32, i32) {
231 SpriteSkin::view_tiles(self, area)
232 }
233
234 fn grid(&self, scene: &MapScene<U>, area: Rect) -> Grid {
235 SpriteSkin::grid(self, scene, area)
236 }
237
238 fn walk(&self) -> WalkSpeeds {
239 self.walk
240 }
241
242 fn zoom_levels(&self) -> &[i32] {
245 self.levels()
246 }
247
248 fn zoom(&self) -> Option<i32> {
249 (!self.levels().is_empty()).then_some(self.zoom)
250 }
251
252 fn at_zoom(&self, zoom: i32) -> Option<Rc<dyn GridSkin<U>>> {
253 if !self.levels().contains(&zoom) {
254 return None;
255 }
256 Some(Rc::new(self.clone().zoomed(zoom)))
257 }
258
259 fn paint(
265 &self,
266 looks: &Looks,
267 scene: &MapScene<U>,
268 area: Rect,
269 units: &dyn UnitPainter<U>,
270 buf: &mut GlyphBuffer,
271 ) {
272 let grid = SpriteSkin::grid(self, scene, area);
273 let layout = glyph::Layout::new(scene, area);
274 match &self.terrain {
275 Some(terrain) => ground::paint(looks, terrain, scene, &grid, buf),
276 None => glyph::draw_tiles(looks, buf, scene, &layout),
277 }
278 units.paint_under(scene, &grid, buf);
279 let color = looks.path(&self.glyph.colors);
280 let (line, arrowhead): (Vec<Overlay>, Vec<Overlay>) =
281 path::path_overlays(&scene.path, &grid, color)
282 .into_iter()
283 .partition(|o| o.layer == Layer::Under);
284 for overlay in line {
285 buf.add_overlay(overlay);
286 }
287 units.paint(scene, &grid, buf);
288 for overlay in arrowhead {
289 buf.add_overlay(overlay);
290 }
291 units.paint_over(scene, &grid, buf);
292 if self.terrain.is_some() {
293 self.paint_own_cursor(looks, scene, &grid, buf);
294 } else {
295 self.glyph.draw_cursor(looks, buf, scene, &layout);
296 }
297 let colors = looks.threat(&self.glyph.colors);
298 threat::paint(scene, &grid, threat::Start::Middle, colors, buf);
299 }
300}
301
302pub fn side(px: u32) -> i32 {
304 i32::try_from(px).unwrap_or(i32::MAX)
305}
306
307pub fn src_rect(rect: ImageRect) -> PxRect {
309 Rect::new(side(rect.x), side(rect.y), side(rect.w), side(rect.h))
310}
311
312pub fn tile_sprite(picture: Picture, dest: PxRect) -> Sprite {
314 Sprite::new(picture.image, src_rect(picture.rect), dest, Layer::Under)
315}
316
317fn corner_marks(tile: PxRect, inset: i32, arm: i32, thick: i32) -> [Rect; 8] {
320 let (left, top) = (tile.x + inset, tile.y + inset);
321 let (right, bottom) = (tile.x + tile.w - inset, tile.y + tile.h - inset);
322 let across = |x0, y0| Rect::new(x0, y0, arm, thick);
323 let down = |x0, y0| Rect::new(x0, y0, thick, arm);
324 [
325 across(left, top),
326 down(left, top),
327 across(right - arm, top),
328 down(right - thick, top),
329 across(left, bottom - thick),
330 down(left, bottom - arm),
331 across(right - arm, bottom - thick),
332 down(right - thick, bottom - arm),
333 ]
334}
335
336#[cfg(test)]
337pub(crate) mod tests;