1use crate::prelude::{
2 string_to_cp437, to_cp437, CharacterTranslationMode, ColoredTextSpans, Console, FontCharType,
3 TextAlign, XpLayer,
4};
5use bracket_color::prelude::RGBA;
6use bracket_geometry::prelude::{PointF, Rect};
7use bracket_rex::prelude::XpColor;
8use ultraviolet::Vec2;
9
10use std::any::Any;
11
12pub struct FlexiTile {
14 pub position: PointF,
15 pub z_order: i32,
16 pub glyph: FontCharType,
17 pub fg: RGBA,
18 pub bg: RGBA,
19 pub rotation: f32,
20 pub scale: PointF,
21}
22
23pub struct FlexiConsole {
26 pub width: u32,
27 pub height: u32,
28
29 pub tiles: Vec<FlexiTile>,
30 pub is_dirty: bool,
31
32 pub offset_x: f32,
34 pub offset_y: f32,
35
36 pub scale: f32,
37 pub scale_center: (i32, i32),
38
39 pub extra_clipping: Option<Rect>,
40 pub translation: CharacterTranslationMode,
41 pub(crate) needs_resize_internal: bool,
42}
43
44impl FlexiConsole {
45 pub fn init(width: u32, height: u32) -> Box<FlexiConsole> {
47 let new_console = FlexiConsole {
49 width,
50 height,
51 tiles: Vec::with_capacity((width * height) as usize),
52 is_dirty: true,
53 offset_x: 0.0,
54 offset_y: 0.0,
55 scale: 1.0,
56 scale_center: (width as i32 / 2, height as i32 / 2),
57 extra_clipping: None,
58 translation: CharacterTranslationMode::Codepage437,
59 needs_resize_internal: false,
60 };
61
62 Box::new(new_console)
63 }
64
65 #[allow(clippy::too_many_arguments)]
67 pub fn set_fancy(
68 &mut self,
69 position: PointF,
70 z_order: i32,
71 rotation: f32,
72 scale: PointF,
73 fg: RGBA,
74 bg: RGBA,
75 glyph: FontCharType,
76 ) {
77 self.is_dirty = true;
78 let invert_pos = PointF {
79 x: position.x,
80 y: self.height as f32 - position.y,
81 };
82 self.tiles.push(FlexiTile {
83 position: invert_pos,
84 z_order,
85 glyph,
86 fg,
87 bg,
88 rotation,
89 scale,
90 });
91 }
92}
93
94impl Console for FlexiConsole {
95 fn get_char_size(&self) -> (u32, u32) {
96 (self.width, self.height)
97 }
98
99 fn resize_pixels(&mut self, _width: u32, _height: u32) {
100 self.is_dirty = true;
101 }
102
103 fn at(&self, x: i32, y: i32) -> usize {
105 (((self.height - 1 - y as u32) * self.width) + x as u32) as usize
106 }
107
108 fn cls(&mut self) {
110 self.is_dirty = true;
111 self.tiles.clear();
112
113 self.tiles.push(FlexiTile {
114 glyph: 32,
115 fg: RGBA::from_u8(255, 255, 255, 255),
116 bg: RGBA::from_u8(0, 0, 0, 255),
117 rotation: 0.,
118 scale: Vec2::new(0., 0.),
119 z_order: 0,
120 position: Vec2::new(0., 0.),
121 });
122 }
123
124 fn cls_bg(&mut self, _background: RGBA) {
126 self.is_dirty = true;
127 for tile in &mut self.tiles {
128 tile.glyph = 32;
129 tile.fg = RGBA::from_u8(255, 255, 255, 255);
130 tile.bg = RGBA::from_u8(0, 0, 0, 255);
131 }
132 }
133
134 fn print(&mut self, x: i32, y: i32, output: &str) {
136 self.is_dirty = true;
137
138 let bytes = match self.translation {
139 CharacterTranslationMode::Codepage437 => string_to_cp437(output),
140 CharacterTranslationMode::Unicode => {
141 output.chars().map(|c| c as FontCharType).collect()
142 }
143 };
144
145 let h = (self.height - 1) as f32;
146 self.tiles
147 .extend(bytes.into_iter().enumerate().map(|(i, glyph)| FlexiTile {
148 position: PointF {
149 x: i as f32 + x as f32,
150 y: h - y as f32,
151 },
152 z_order: 0,
153 glyph,
154 fg: RGBA::from_f32(1.0, 1.0, 1.0, 1.0),
155 bg: RGBA::from_f32(0.0, 0.0, 0.0, 1.0),
156 rotation: 0.0,
157 scale: PointF { x: 1.0, y: 1.0 },
158 }));
159 }
160
161 fn print_color(&mut self, x: i32, y: i32, fg: RGBA, bg: RGBA, output: &str) {
163 self.is_dirty = true;
164
165 let bytes = match self.translation {
166 CharacterTranslationMode::Codepage437 => string_to_cp437(output),
167 CharacterTranslationMode::Unicode => {
168 output.chars().map(|c| c as FontCharType).collect()
169 }
170 };
171 let h = (self.height - 1) as f32;
172 self.tiles
173 .extend(bytes.into_iter().enumerate().map(|(i, glyph)| FlexiTile {
174 z_order: 0,
175 position: PointF {
176 x: i as f32 + x as f32,
177 y: h - y as f32,
178 },
179 glyph,
180 fg,
181 bg,
182 rotation: 0.0,
183 scale: PointF { x: 1.0, y: 1.0 },
184 }));
185 }
186
187 fn set(&mut self, x: i32, y: i32, fg: RGBA, bg: RGBA, glyph: FontCharType) {
189 self.is_dirty = true;
190 if self.try_at(x, y).is_some() {
191 let h = (self.height - 1) as f32;
192 self.tiles.push(FlexiTile {
193 position: PointF {
194 x: x as f32,
195 y: h - y as f32,
196 },
197 z_order: 0,
198 glyph,
199 fg,
200 bg,
201 rotation: 0.0,
202 scale: PointF { x: 1.0, y: 1.0 },
203 });
204 }
205 }
206
207 fn set_bg(&mut self, _x: i32, _y: i32, _bg: RGBA) {
209 }
211
212 fn draw_box(&mut self, sx: i32, sy: i32, width: i32, height: i32, fg: RGBA, bg: RGBA) {
214 crate::prelude::draw_box(self, sx, sy, width, height, fg, bg);
215 }
216
217 fn draw_box_double(&mut self, sx: i32, sy: i32, width: i32, height: i32, fg: RGBA, bg: RGBA) {
219 crate::prelude::draw_box_double(self, sx, sy, width, height, fg, bg);
220 }
221
222 fn draw_hollow_box(&mut self, sx: i32, sy: i32, width: i32, height: i32, fg: RGBA, bg: RGBA) {
224 crate::prelude::draw_hollow_box(self, sx, sy, width, height, fg, bg);
225 }
226
227 fn draw_hollow_box_double(
229 &mut self,
230 sx: i32,
231 sy: i32,
232 width: i32,
233 height: i32,
234 fg: RGBA,
235 bg: RGBA,
236 ) {
237 crate::prelude::draw_hollow_box_double(self, sx, sy, width, height, fg, bg);
238 }
239
240 fn fill_region(&mut self, target: Rect, glyph: FontCharType, fg: RGBA, bg: RGBA) {
242 target.for_each(|point| {
243 self.set(point.x, point.y, fg, bg, glyph);
244 });
245 }
246
247 fn draw_bar_horizontal(
249 &mut self,
250 sx: i32,
251 sy: i32,
252 width: i32,
253 n: i32,
254 max: i32,
255 fg: RGBA,
256 bg: RGBA,
257 ) {
258 crate::prelude::draw_bar_horizontal(self, sx, sy, width, n, max, fg, bg);
259 }
260
261 fn draw_bar_vertical(
263 &mut self,
264 sx: i32,
265 sy: i32,
266 height: i32,
267 n: i32,
268 max: i32,
269 fg: RGBA,
270 bg: RGBA,
271 ) {
272 crate::prelude::draw_bar_vertical(self, sx, sy, height, n, max, fg, bg);
273 }
274
275 fn print_centered(&mut self, y: i32, text: &str) {
277 self.is_dirty = true;
278 self.print(
279 (self.width as i32 / 2) - (text.to_string().len() as i32 / 2),
280 y,
281 text,
282 );
283 }
284
285 fn print_color_centered(&mut self, y: i32, fg: RGBA, bg: RGBA, text: &str) {
287 self.is_dirty = true;
288 self.print_color(
289 (self.width as i32 / 2) - (text.to_string().len() as i32 / 2),
290 y,
291 fg,
292 bg,
293 text,
294 );
295 }
296
297 fn print_centered_at(&mut self, x: i32, y: i32, text: &str) {
299 self.is_dirty = true;
300 self.print(x - (text.to_string().len() as i32 / 2), y, text);
301 }
302
303 fn print_color_centered_at(&mut self, x: i32, y: i32, fg: RGBA, bg: RGBA, text: &str) {
305 self.is_dirty = true;
306 self.print_color(x - (text.to_string().len() as i32 / 2), y, fg, bg, text);
307 }
308
309 fn print_right(&mut self, x: i32, y: i32, text: &str) {
311 let len = text.len() as i32;
312 let actual_x = x - len;
313 self.print(actual_x, y, text);
314 }
315
316 fn print_color_right(&mut self, x: i32, y: i32, fg: RGBA, bg: RGBA, text: &str) {
318 let len = text.len() as i32;
319 let actual_x = x - len;
320 self.print_color(actual_x, y, fg, bg, text);
321 }
322
323 fn printer(
328 &mut self,
329 x: i32,
330 y: i32,
331 output: &str,
332 align: TextAlign,
333 background: Option<RGBA>,
334 ) {
335 let bg = if let Some(bg) = background {
336 bg
337 } else {
338 RGBA::from_u8(0, 0, 0, 255)
339 };
340
341 let split_text = ColoredTextSpans::new(output);
342
343 let mut tx = match align {
344 TextAlign::Left => x,
345 TextAlign::Center => x - (split_text.length as i32 / 2),
346 TextAlign::Right => x - split_text.length as i32,
347 };
348 for span in split_text.spans.iter() {
349 let fg = span.0;
350 for ch in span.1.chars() {
351 self.set(
352 tx,
353 y,
354 fg,
355 bg,
356 match self.translation {
357 CharacterTranslationMode::Codepage437 => to_cp437(ch),
358 CharacterTranslationMode::Unicode => ch as FontCharType,
359 },
360 );
361 tx += 1;
362 }
363 }
364 }
365
366 fn to_xp_layer(&self) -> XpLayer {
368 let mut layer = XpLayer::new(self.width as usize, self.height as usize);
369
370 for y in 0..self.height {
372 for x in 0..self.width {
373 let cell = layer.get_mut(x as usize, y as usize).unwrap();
374 cell.bg = XpColor::TRANSPARENT;
375 }
376 }
377
378 for c in &self.tiles {
379 let x = c.position.x as usize;
380 let y = c.position.y as usize;
381 let cell = layer.get_mut(x as usize, y as usize).unwrap();
382 cell.ch = u32::from(c.glyph);
383 cell.fg = XpColor::from(c.fg);
384 cell.bg = XpColor::from(c.bg);
385 }
386
387 layer
388 }
389
390 fn set_offset(&mut self, x: f32, y: f32) {
394 self.is_dirty = true;
395 self.offset_x = x * (2.0 / self.width as f32);
396 self.offset_y = y * (2.0 / self.height as f32);
397 }
398
399 fn set_scale(&mut self, scale: f32, center_x: i32, center_y: i32) {
400 self.is_dirty = true;
401 self.scale = scale;
402 self.scale_center = (center_x, center_y);
403 }
404
405 fn get_scale(&self) -> (f32, i32, i32) {
406 (self.scale, self.scale_center.0, self.scale_center.1)
407 }
408
409 fn as_any(&self) -> &dyn Any {
410 self
411 }
412
413 fn as_any_mut(&mut self) -> &mut dyn Any {
414 self
415 }
416
417 fn set_clipping(&mut self, clipping: Option<Rect>) {
420 self.extra_clipping = clipping;
421 }
422
423 fn get_clipping(&self) -> Option<Rect> {
425 self.extra_clipping
426 }
427
428 fn set_all_fg_alpha(&mut self, alpha: f32) {
430 self.tiles.iter_mut().for_each(|t| t.fg.a = alpha);
431 }
432
433 fn set_all_bg_alpha(&mut self, alpha: f32) {
435 self.tiles.iter_mut().for_each(|t| t.bg.a = alpha);
436 }
437
438 fn set_all_alpha(&mut self, fg: f32, bg: f32) {
440 self.tiles.iter_mut().for_each(|t| {
441 t.fg.a = fg;
442 t.bg.a = bg;
443 });
444 }
445
446 fn set_translation_mode(&mut self, mode: CharacterTranslationMode) {
448 self.translation = mode;
449 }
450
451 fn set_char_size(&mut self, width: u32, height: u32) {
453 self.width = width;
454 self.height = height;
455 self.needs_resize_internal = true;
456 }
457
458 fn clear_dirty(&mut self) {
460 self.is_dirty = false;
461 }
462}