1use alloc::rc::Rc;
6
7use super::scene::MapScene;
8use super::skin::{GridSkin, Looks, UnitPainter};
9use crate::frame::console::ConsoleSpec;
10use crate::frame::glyph_buffer::{Cell, GlyphBuffer, Rect};
11
12pub const ROOMY: Rect = Rect::new(0, 0, 64, 64);
14
15#[derive(Debug, Clone, PartialEq)]
17pub struct MapPicture {
18 pub scene: Rc<GlyphBuffer>,
20 pub tile_px: (i32, i32),
22}
23
24impl MapPicture {
25 pub fn paint<U>(
30 looks: &Looks,
31 skin: &dyn GridSkin<U>,
32 units: &dyn UnitPainter<U>,
33 scene: &MapScene<U>,
34 console: ConsoleSpec,
35 blank: Cell,
36 ) -> Option<Self> {
37 let tile = skin.tile_px(scene, ROOMY, scene.origin)?;
38 let paint = |buf: &mut GlyphBuffer| skin.paint(looks, scene, buf.bounds(), units, buf);
39 Some(Self::painted(
40 (tile.w, tile.h),
41 scene.size,
42 console,
43 blank,
44 paint,
45 ))
46 }
47
48 pub fn painted(
53 tile_px: (i32, i32),
54 tiles: (i32, i32),
55 console: ConsoleSpec,
56 blank: Cell,
57 paint: impl FnOnce(&mut GlyphBuffer),
58 ) -> Self {
59 let cells = |tiles: i32, tile_px: i32, cell_px: u16| {
60 let cell_px = i32::from(cell_px);
61 u16::try_from((tiles * tile_px + cell_px - 1) / cell_px).unwrap_or(0)
62 };
63 let (w, h) = (
64 cells(tiles.0, tile_px.0, console.cell_w),
65 cells(tiles.1, tile_px.1, console.cell_h),
66 );
67 let mut buf = GlyphBuffer::with_spec(console.sized(w, h), blank);
68 paint(&mut buf);
69 Self {
70 scene: Rc::new(buf),
71 tile_px,
72 }
73 }
74
75 pub fn size_px(&self) -> (u32, u32) {
77 pixel_size(&self.scene)
78 }
79
80 #[allow(clippy::cast_precision_loss)] pub fn point_px(&self, tiles: (f32, f32)) -> (f32, f32) {
84 (
85 (tiles.0 + 0.5) * self.tile_px.0 as f32,
86 (tiles.1 + 0.5) * self.tile_px.1 as f32,
87 )
88 }
89}
90
91pub fn pixel_size(buf: &GlyphBuffer) -> (u32, u32) {
93 let px = buf.pixel_bounds();
94 (
95 u32::try_from(px.w).unwrap_or(0),
96 u32::try_from(px.h).unwrap_or(0),
97 )
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103 use crate::frame::color::{EngineColor, Rgb};
104 use crate::grid::NoUnits;
105 use crate::toy::grid::{Cards, Field, GLYPH, Kit, Letters, Piece, sprite, tiles};
106 use shuyi_core::Pos;
107
108 const CONSOLE: ConsoleSpec = ConsoleSpec::text(40, 20);
109
110 fn blank(kit: &Kit) -> Cell {
111 let c = |e| kit.palette.engine(e);
112 Cell::new(' ', c(EngineColor::Text), c(EngineColor::Background))
113 }
114
115 fn picture(
117 kit: &Kit,
118 skin: &dyn GridSkin<Piece>,
119 units: &dyn UnitPainter<Piece>,
120 scene: &MapScene<Piece>,
121 ) -> Option<MapPicture> {
122 MapPicture::paint(&kit.looks(), skin, units, scene, CONSOLE, blank(kit))
123 }
124
125 #[test]
126 fn a_glyph_picture_is_the_whole_field_with_its_pieces() {
127 let kit = Kit::new();
128 let scene = Field::glyphs().whole();
129 let shown = picture(&kit, &GLYPH, &Letters, &scene).unwrap();
131 assert_eq!((shown.scene.width(), shown.scene.height()), (20, 8));
132 assert_eq!(shown.tile_px, (16, 16));
133 assert_eq!(shown.size_px(), (160, 128));
134 assert_eq!(shown.point_px((0.0, 0.0)), (8.0, 8.0));
136 assert_eq!(shown.point_px((5.75, 3.5)), (100.0, 64.0));
137 assert_eq!(shown.point_px((-0.5, 7.5)), (0.0, 128.0));
138 let bare = picture(&kit, &GLYPH, &NoUnits, &scene).unwrap();
140 assert_eq!(shown.scene.get(4, 3).map(|c| c.glyph), Some('y'));
141 assert_ne!(shown.scene.get(4, 3), bare.scene.get(4, 3));
142 assert_eq!(shown.scene.get(0, 0), bare.scene.get(0, 0));
143 assert_ne!(shown, bare);
144 assert!(shown.scene.backdrop().is_none());
145 let found = shown.scene.sprites();
146 assert!(found.is_empty(), "{found:?}");
147 }
148
149 #[test]
150 fn a_sprite_picture_is_in_whole_cells_of_the_tiles_pixels() {
151 let kit = Kit::new();
152 let skin = sprite(Some(tiles(kit.tiles_image())));
153 let cards = Cards { card: kit.card() };
154 let scene = Field::glyphs().whole();
155 let shown = picture(&kit, &skin, &cards, &scene).unwrap();
157 assert_eq!((shown.scene.width(), shown.scene.height()), (30, 12));
158 assert_eq!(shown.tile_px, (24, 24));
159 assert_eq!(shown.size_px(), (240, 192));
160 assert_eq!(shown.point_px((1.0, 2.0)), (36.0, 60.0));
161 assert_eq!(shown.scene.sprites().len(), 10 * 8 + 2);
163 let odd = MapScene::<Piece>::new(Pos::new(0, 0), (1, 1));
165 let shown = picture(&kit, &skin, &cards, &odd).unwrap();
166 assert_eq!((shown.scene.width(), shown.scene.height()), (3, 2));
167 assert_eq!(shown.size_px(), (24, 32));
168 let none = MapScene::<Piece>::new(Pos::new(0, 0), (0, 0));
170 assert!(picture(&kit, &skin, &cards, &none).is_none());
171 assert!(picture(&kit, &GLYPH, &Letters, &none).is_none());
172 }
173
174 #[test]
175 fn a_picture_painted_by_hand_is_cleared_first_and_sized_in_cells() {
176 let (ink, paper) = (Rgb::new(1, 2, 3), Rgb::new(4, 5, 6));
177 let blank = Cell::new('.', ink, paper);
178 let mut seen = None;
179 let shown = MapPicture::painted((24, 24), (3, 2), CONSOLE, blank, |buf| {
180 seen = buf.get(8, 2).copied();
181 buf.set(0, 0, Cell::new('#', paper, ink));
182 });
183 assert_eq!(seen, Some(blank));
184 assert_eq!((shown.scene.width(), shown.scene.height()), (9, 3));
186 assert_eq!(shown.scene.get(0, 0), Some(&Cell::new('#', paper, ink)));
187 assert_eq!(pixel_size(&shown.scene), (72, 48));
188 }
189}