bracket_terminal/consoles/
virtual_console.rs

1//! A virtual console exists to store large amounts of arbitrary text,
2//! which can then be "windowed" into actual consoles.
3
4use crate::prelude::{
5    string_to_cp437, to_cp437, BTerm, CharacterTranslationMode, ColoredTextSpans, Console,
6    DrawBatch, FontCharType, TextAlign, Tile, XpLayer,
7};
8use bracket_color::prelude::*;
9use bracket_geometry::prelude::{Point, Rect};
10use std::any::Any;
11
12pub struct VirtualConsole {
13    pub width: u32,
14    pub height: u32,
15
16    pub tiles: Vec<Tile>,
17
18    pub extra_clipping: Option<Rect>,
19    pub translation: CharacterTranslationMode,
20}
21
22impl VirtualConsole {
23    /// Creates a new virtual console of arbitrary dimensions.
24    pub fn new(dimensions: Point) -> Self {
25        let num_tiles: usize = (dimensions.x * dimensions.y) as usize;
26        let mut console = VirtualConsole {
27            width: dimensions.x as u32,
28            height: dimensions.y as u32,
29            tiles: Vec::with_capacity(num_tiles),
30            extra_clipping: None,
31            translation: CharacterTranslationMode::Codepage437,
32        };
33        for _ in 0..num_tiles {
34            console.tiles.push(Tile {
35                glyph: 0,
36                fg: RGBA::from_f32(1.0, 1.0, 1.0, 1.0),
37                bg: RGBA::from_f32(0.0, 0.0, 0.0, 1.0),
38            });
39        }
40        console
41    }
42
43    /// Creates a new virtual console from a blob of text.
44    /// Useful if you want to scroll through manuals!
45    pub fn from_text(text: &str, width: usize) -> Self {
46        let raw_lines = text.split('\n');
47        let mut lines: Vec<String> = Vec::new();
48        for line in raw_lines {
49            let mut newline: String = String::from("");
50
51            line.chars().for_each(|c| {
52                newline.push(c);
53                if newline.len() > width {
54                    lines.push(newline.clone());
55                    newline.clear();
56                }
57            });
58            lines.push(newline.clone());
59        }
60
61        let num_tiles: usize = width * lines.len();
62        let mut console = VirtualConsole {
63            width: width as u32,
64            height: lines.len() as u32,
65            tiles: Vec::with_capacity(num_tiles),
66            extra_clipping: None,
67            translation: CharacterTranslationMode::Codepage437,
68        };
69        //println!("{}x{}", console.width, console.height);
70
71        for _ in 0..num_tiles {
72            console.tiles.push(Tile {
73                glyph: 0,
74                fg: RGBA::from_f32(1.0, 1.0, 1.0, 1.0),
75                bg: RGBA::from_f32(0.0, 0.0, 0.0, 1.0),
76            });
77        }
78
79        for (i, line) in lines.iter().enumerate() {
80            console.print(0, i as i32, line);
81        }
82
83        console
84    }
85
86    /// Send a portion of the Virtual Console to a physical console, specifying both source and destination
87    /// rectangles and the target console.
88    pub fn print_sub_rect(&self, source: Rect, dest: Rect, target: &mut BTerm) {
89        target.set_clipping(Some(dest));
90        for y in dest.y1..dest.y2 {
91            let source_y = y + source.y1 - dest.y1;
92            for x in dest.x1..dest.x2 {
93                let source_x = x + source.x1 - dest.x1;
94                if let Some(idx) = self.try_at(source_x, source_y) {
95                    let t = self.tiles[idx];
96                    if t.glyph > 0 {
97                        target.set(x, y, t.fg, t.bg, t.glyph);
98                    }
99                }
100            }
101        }
102        target.set_clipping(None);
103    }
104
105    /// Send a portion of the Virtual Console to a render batch, specifying both source and destination
106    /// rectangles and the target batch.
107    pub fn batch_sub_rect(&self, source: Rect, dest: Rect, target: &mut DrawBatch) {
108        target.set_clipping(Some(dest));
109        for y in dest.y1..dest.y2 {
110            let source_y = y + source.y1 - dest.y1;
111            for x in dest.x1..dest.x2 {
112                let source_x = x + source.x1 - dest.x1;
113                if let Some(idx) = self.try_at(source_x, source_y) {
114                    let t = self.tiles[idx];
115                    if t.glyph > 0 {
116                        target.set(Point::new(x, y), ColorPair::new(t.fg, t.bg), t.glyph);
117                    }
118                }
119            }
120        }
121        target.set_clipping(None);
122    }
123}
124
125impl Console for VirtualConsole {
126    fn get_char_size(&self) -> (u32, u32) {
127        (self.width, self.height)
128    }
129
130    fn resize_pixels(&mut self, _width: u32, _height: u32) {
131        // Ignored
132    }
133
134    /// Translate an x/y into an array index.
135    fn at(&self, x: i32, y: i32) -> usize {
136        (((self.height - 1 - y as u32) * self.width) + x as u32) as usize
137    }
138
139    /// Clears the screen.
140    fn cls(&mut self) {
141        for tile in &mut self.tiles {
142            tile.glyph = 32;
143            tile.fg = RGBA::from_f32(1.0, 1.0, 1.0, 1.0);
144            tile.bg = RGBA::from_f32(0.0, 0.0, 0.0, 1.0);
145        }
146    }
147
148    /// Clears the screen with a background color.
149    fn cls_bg(&mut self, background: RGBA) {
150        for tile in &mut self.tiles {
151            tile.glyph = 32;
152            tile.fg = RGBA::from_f32(1.0, 1.0, 1.0, 1.0);
153            tile.bg = background;
154        }
155    }
156
157    /// Prints a string at x/y.
158    fn print(&mut self, mut x: i32, y: i32, output: &str) {
159        let bytes = match self.translation {
160            CharacterTranslationMode::Codepage437 => string_to_cp437(output),
161            CharacterTranslationMode::Unicode => {
162                output.chars().map(|c| c as FontCharType).collect()
163            }
164        };
165        for glyph in bytes {
166            if let Some(idx) = self.try_at(x, y) {
167                self.tiles[idx].glyph = glyph;
168            }
169            x += 1;
170        }
171    }
172
173    /// Prints a string at x/y, with foreground and background colors.
174    fn print_color(&mut self, mut x: i32, y: i32, fg: RGBA, bg: RGBA, output: &str) {
175        let bytes = match self.translation {
176            CharacterTranslationMode::Codepage437 => string_to_cp437(output),
177            CharacterTranslationMode::Unicode => {
178                output.chars().map(|c| c as FontCharType).collect()
179            }
180        };
181        for glyph in bytes {
182            if let Some(idx) = self.try_at(x, y) {
183                self.tiles[idx].glyph = glyph;
184                self.tiles[idx].bg = bg;
185                self.tiles[idx].fg = fg;
186            }
187            x += 1;
188        }
189    }
190
191    /// Sets a single cell in the console
192    fn set(&mut self, x: i32, y: i32, fg: RGBA, bg: RGBA, glyph: FontCharType) {
193        if let Some(idx) = self.try_at(x, y) {
194            self.tiles[idx].glyph = glyph;
195            self.tiles[idx].fg = fg;
196            self.tiles[idx].bg = bg;
197        }
198    }
199
200    /// Sets a single cell in the console's background
201    fn set_bg(&mut self, x: i32, y: i32, bg: RGBA) {
202        if let Some(idx) = self.try_at(x, y) {
203            self.tiles[idx].bg = bg;
204        }
205    }
206
207    /// Draws a box, starting at x/y with the extents width/height using CP437 line characters
208    fn draw_box(&mut self, sx: i32, sy: i32, width: i32, height: i32, fg: RGBA, bg: RGBA) {
209        crate::prelude::draw_box(self, sx, sy, width, height, fg, bg);
210    }
211
212    /// Draws a box, starting at x/y with the extents width/height using CP437 line characters
213    fn draw_hollow_box(&mut self, sx: i32, sy: i32, width: i32, height: i32, fg: RGBA, bg: RGBA) {
214        crate::prelude::draw_hollow_box(self, sx, sy, width, height, fg, bg);
215    }
216
217    /// Draws a box, starting at x/y with the extents width/height using CP437 double line characters
218    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    /// Draws a box, starting at x/y with the extents width/height using CP437 double line characters
223    fn draw_hollow_box_double(
224        &mut self,
225        sx: i32,
226        sy: i32,
227        width: i32,
228        height: i32,
229        fg: RGBA,
230        bg: RGBA,
231    ) {
232        crate::prelude::draw_hollow_box_double(self, sx, sy, width, height, fg, bg);
233    }
234
235    /// Fills a rectangle with the specified rendering information
236    fn fill_region(&mut self, target: Rect, glyph: FontCharType, fg: RGBA, bg: RGBA) {
237        target.for_each(|point| {
238            self.set(point.x, point.y, fg, bg, glyph);
239        });
240    }
241
242    /// Draws a horizontal progress bar
243    fn draw_bar_horizontal(
244        &mut self,
245        sx: i32,
246        sy: i32,
247        width: i32,
248        n: i32,
249        max: i32,
250        fg: RGBA,
251        bg: RGBA,
252    ) {
253        crate::prelude::draw_bar_horizontal(self, sx, sy, width, n, max, fg, bg);
254    }
255
256    /// Draws a vertical progress bar
257    fn draw_bar_vertical(
258        &mut self,
259        sx: i32,
260        sy: i32,
261        height: i32,
262        n: i32,
263        max: i32,
264        fg: RGBA,
265        bg: RGBA,
266    ) {
267        crate::prelude::draw_bar_vertical(self, sx, sy, height, n, max, fg, bg);
268    }
269
270    /// Prints text, centered to the whole console width, at vertical location y.
271    fn print_centered(&mut self, y: i32, text: &str) {
272        self.print(
273            (self.width as i32 / 2) - (text.to_string().len() as i32 / 2),
274            y,
275            text,
276        );
277    }
278
279    /// Prints text in color, centered to the whole console width, at vertical location y.
280    fn print_color_centered(&mut self, y: i32, fg: RGBA, bg: RGBA, text: &str) {
281        self.print_color(
282            (self.width as i32 / 2) - (text.to_string().len() as i32 / 2),
283            y,
284            fg,
285            bg,
286            text,
287        );
288    }
289
290    /// Prints text, centered to the whole console width, at vertical location y.
291    fn print_centered_at(&mut self, x: i32, y: i32, text: &str) {
292        self.print(x - (text.to_string().len() as i32 / 2), y, text);
293    }
294
295    /// Prints text in color, centered to the whole console width, at vertical location y.
296    fn print_color_centered_at(&mut self, x: i32, y: i32, fg: RGBA, bg: RGBA, text: &str) {
297        self.print_color(x - (text.to_string().len() as i32 / 2), y, fg, bg, text);
298    }
299
300    /// Prints text right-aligned
301    fn print_right(&mut self, x: i32, y: i32, text: &str) {
302        let len = text.len() as i32;
303        let actual_x = x - len;
304        self.print(actual_x, y, text);
305    }
306
307    /// Prints colored text right-aligned
308    fn print_color_right(&mut self, x: i32, y: i32, fg: RGBA, bg: RGBA, text: &str) {
309        let len = text.len() as i32;
310        let actual_x = x - len;
311        self.print_color(actual_x, y, fg, bg, text);
312    }
313
314    /// Print a colorized string with the color encoding defined inline.
315    /// For example: printer(1, 1, "#[blue]This blue text contains a #[pink]pink#[] word")
316    /// You can get the same effect with a TextBlock, but this can be easier.
317    /// Thanks to doryen_rs for the idea.
318    fn printer(
319        &mut self,
320        x: i32,
321        y: i32,
322        output: &str,
323        align: TextAlign,
324        background: Option<RGBA>,
325    ) {
326        let bg = if let Some(bg) = background {
327            bg
328        } else {
329            RGBA::from_f32(0.0, 0.0, 0.0, 1.0)
330        };
331
332        let split_text = ColoredTextSpans::new(output);
333
334        let mut tx = match align {
335            TextAlign::Left => x,
336            TextAlign::Center => x - (split_text.length as i32 / 2),
337            TextAlign::Right => x - split_text.length as i32,
338        };
339        for span in split_text.spans.iter() {
340            let fg = span.0;
341            for ch in span.1.chars() {
342                self.set(
343                    tx,
344                    y,
345                    fg,
346                    bg,
347                    match self.translation {
348                        CharacterTranslationMode::Codepage437 => to_cp437(ch),
349                        CharacterTranslationMode::Unicode => ch as FontCharType,
350                    },
351                );
352                tx += 1;
353            }
354        }
355    }
356
357    /// Saves the layer to an XpFile structure
358    fn to_xp_layer(&self) -> XpLayer {
359        let mut layer = XpLayer::new(self.width as usize, self.height as usize);
360
361        for y in 0..self.height {
362            for x in 0..self.width {
363                let cell = layer.get_mut(x as usize, y as usize).unwrap();
364                let idx = self.at(x as i32, y as i32);
365                cell.ch = u32::from(self.tiles[idx].glyph);
366                cell.fg = self.tiles[idx].fg.into();
367                cell.bg = self.tiles[idx].bg.into();
368            }
369        }
370
371        layer
372    }
373
374    /// Sets an offset to total console rendering, useful for layers that
375    /// draw between tiles. Offsets are specified as a percentage of total
376    /// character size; so -0.5 will offset half a character to the left/top.
377    fn set_offset(&mut self, _x: f32, _y: f32) {
378        panic!("Unsupported on virtual consoles.");
379    }
380
381    fn set_scale(&mut self, _scale: f32, _center_x: i32, _center_y: i32) {
382        panic!("Unsupported on virtual consoles.");
383    }
384
385    fn get_scale(&self) -> (f32, i32, i32) {
386        (1.0, 0, 0)
387    }
388
389    fn as_any(&self) -> &dyn Any {
390        self
391    }
392
393    fn as_any_mut(&mut self) -> &mut dyn Any {
394        self
395    }
396
397    /// Permits the creation of an arbitrary clipping rectangle. It's a really good idea
398    /// to make sure that this rectangle is entirely valid.
399    fn set_clipping(&mut self, clipping: Option<Rect>) {
400        self.extra_clipping = clipping;
401    }
402
403    /// Returns the current arbitrary clipping rectangle, None if there isn't one.
404    fn get_clipping(&self) -> Option<Rect> {
405        self.extra_clipping
406    }
407
408    /// Sets ALL tiles foreground alpha (only tiles that exist, in sparse consoles).
409    fn set_all_fg_alpha(&mut self, alpha: f32) {
410        self.tiles.iter_mut().for_each(|t| t.fg.a = alpha);
411    }
412
413    /// Sets ALL tiles background alpha (only tiles that exist, in sparse consoles).
414    fn set_all_bg_alpha(&mut self, alpha: f32) {
415        self.tiles.iter_mut().for_each(|t| t.bg.a = alpha);
416    }
417
418    /// Sets ALL tiles foreground alpha (only tiles that exist, in sparse consoles).
419    fn set_all_alpha(&mut self, fg: f32, bg: f32) {
420        self.tiles.iter_mut().for_each(|t| {
421            t.fg.a = fg;
422            t.bg.a = bg;
423        });
424    }
425
426    /// Sets the character translation mode
427    fn set_translation_mode(&mut self, mode: CharacterTranslationMode) {
428        self.translation = mode;
429    }
430
431    /// Sets the character size of the terminal
432    fn set_char_size(&mut self, _width: u32, _height: u32) {
433        panic!("Not implemented.");
434    }
435
436    // Clears the dirty bit
437    fn clear_dirty(&mut self) {}
438}