bracket_terminal/
bterm.rs

1#![allow(dead_code)]
2#[allow(unused_imports)]
3use crate::{
4    prelude::{
5        init_raw, BEvent, CharacterTranslationMode, Console, FlexiConsole, Font, FontCharType,
6        GameState, InitHints, Radians, RenderSprite, Shader, SimpleConsole, SpriteConsole,
7        SpriteSheet, TextAlign, VirtualKeyCode, XpFile, XpLayer, BACKEND, INPUT,
8    },
9    BResult,
10};
11use bracket_color::prelude::RGBA;
12use bracket_geometry::prelude::{Point, PointF, Rect};
13use parking_lot::Mutex;
14use std::convert::*;
15
16/// A display console, used internally to provide console render support.
17/// Public in case you want to play with it, or access it directly.
18pub struct DisplayConsole {
19    pub console: Box<dyn Console>,
20    pub shader_index: usize,
21    pub font_index: usize,
22}
23
24pub struct BTermInternal {
25    pub fonts: Vec<Font>,
26    pub shaders: Vec<Shader>,
27    pub consoles: Vec<DisplayConsole>,
28    pub sprite_sheets: Vec<SpriteSheet>,
29}
30
31impl BTermInternal {
32    pub fn new() -> Self {
33        Self {
34            fonts: Vec::new(),
35            shaders: Vec::new(),
36            consoles: Vec::new(),
37            sprite_sheets: Vec::new(),
38        }
39    }
40}
41
42impl Default for BTermInternal {
43    fn default() -> Self {
44        Self {
45            fonts: Vec::new(),
46            shaders: Vec::new(),
47            consoles: Vec::new(),
48            sprite_sheets: Vec::new(),
49        }
50    }
51}
52
53unsafe impl Send for BTermInternal {}
54unsafe impl Sync for BTermInternal {}
55
56lazy_static! {
57    pub static ref BACKEND_INTERNAL: Mutex<BTermInternal> = Mutex::new(BTermInternal::new());
58}
59
60/// A BTerm context.
61#[derive(Clone, Debug)]
62pub struct BTerm {
63    pub width_pixels: u32,
64    pub height_pixels: u32,
65    pub original_height_pixels: u32,
66    pub original_width_pixels: u32,
67    pub fps: f32,
68    pub frame_time_ms: f32,
69    pub active_console: usize,
70    pub key: Option<VirtualKeyCode>,
71    pub mouse_pos: (i32, i32),
72    pub left_click: bool,
73    pub shift: bool,
74    pub control: bool,
75    pub alt: bool,
76    pub web_button: Option<String>,
77    pub quitting: bool,
78    pub post_scanlines: bool,
79    pub post_screenburn: bool,
80    pub screen_burn_color: bracket_color::prelude::RGB,
81    pub mouse_visible: bool,
82}
83
84impl BTerm {
85    /// Initializes an OpenGL context and a window, stores the info in the BTerm structure.
86    pub fn init_raw<S: ToString, T>(
87        width_pixels: T,
88        height_pixels: T,
89        window_title: S,
90        platform_hints: InitHints,
91    ) -> BResult<BTerm>
92    where
93        T: TryInto<u32>,
94    {
95        let w = width_pixels.try_into();
96        let h = height_pixels.try_into();
97        let (w, h) = if let (Ok(w), Ok(h)) = (w, h) {
98            (w, h)
99        } else {
100            return Err("Couldn't convert to u32".into());
101        };
102        init_raw(w, h, window_title, platform_hints)
103    }
104
105    /// Quick initialization for when you just want an 8x8 font terminal
106    #[deprecated(
107        since = "0.6.2",
108        note = "Please migrate to the BTermBuilder system instead."
109    )]
110    pub fn init_simple8x8<S: ToString, T>(
111        width_chars: T,
112        height_chars: T,
113        window_title: S,
114        path_to_shaders: S,
115    ) -> BTerm
116    where
117        T: TryInto<u32>,
118    {
119        let w: u32 = width_chars.try_into().ok().unwrap();
120        let h: u32 = height_chars.try_into().ok().unwrap();
121        let font_path = format!("{}/terminal8x8.png", &path_to_shaders.to_string());
122        let mut context = BTerm::init_raw(w * 8, h * 8, window_title, InitHints::new()).unwrap();
123        let font = context.register_font(Font::load(font_path, (8, 8), None));
124        context.register_console(SimpleConsole::init(w, h), font.unwrap());
125        context
126    }
127
128    /// Quick initialization for when you just want an 8x16 VGA font terminal
129    #[deprecated(
130        since = "0.6.2",
131        note = "Please migrate to the BTermBuilder system instead."
132    )]
133    pub fn init_simple8x16<S: ToString, T>(
134        width_chars: T,
135        height_chars: T,
136        window_title: S,
137        path_to_shaders: S,
138    ) -> BTerm
139    where
140        T: TryInto<u32>,
141    {
142        let w: u32 = width_chars.try_into().ok().unwrap();
143        let h: u32 = height_chars.try_into().ok().unwrap();
144        let font_path = format!("{}/vga8x16.png", &path_to_shaders.to_string());
145        let mut context = BTerm::init_raw(w * 8, h * 16, window_title, InitHints::new()).unwrap();
146        let font = context.register_font(Font::load(font_path, (8, 16), None));
147        context.register_console(SimpleConsole::init(w, h), font.unwrap());
148        context
149    }
150
151    /// Registers a font, and returns its handle number. Do not use after initialization!
152    pub(crate) fn register_font(&mut self, font: Font) -> BResult<usize> {
153        let mut bi = BACKEND_INTERNAL.lock();
154        bi.fonts.push(font);
155        Ok(bi.fonts.len() - 1)
156    }
157
158    /// Registers a new console terminal for output, and returns its handle number.
159    pub fn register_console(&mut self, new_console: Box<dyn Console>, font_index: usize) -> usize {
160        let mut bi = BACKEND_INTERNAL.lock();
161        bi.consoles.push(DisplayConsole {
162            console: new_console,
163            font_index,
164            shader_index: 0,
165        });
166        bi.consoles.len() - 1
167    }
168
169    /// Registers a new console terminal for output, and returns its handle number. This variant requests
170    /// that the new console not render background colors, so it can be layered on top of other consoles.
171    pub fn register_console_no_bg(
172        &mut self,
173        new_console: Box<dyn Console>,
174        font_index: usize,
175    ) -> usize {
176        let mut bi = BACKEND_INTERNAL.lock();
177        bi.consoles.push(DisplayConsole {
178            console: new_console,
179            font_index,
180            shader_index: 1,
181        });
182        bi.consoles.len() - 1
183    }
184
185    /// Registers a new console terminal for output, and returns its handle number. This variant requests
186    /// that the new console not render background colors, so it can be layered on top of other consoles.
187    pub fn register_fancy_console(
188        &mut self,
189        new_console: Box<dyn Console>,
190        font_index: usize,
191    ) -> usize {
192        let mut bi = BACKEND_INTERNAL.lock();
193        bi.consoles.push(DisplayConsole {
194            console: new_console,
195            font_index,
196            shader_index: 4,
197        });
198        bi.consoles.len() - 1
199    }
200
201    /// Registers a new Sprite-based console
202    pub fn register_sprite_console(&mut self, new_console: Box<dyn Console>) -> usize {
203        let mut bi = BACKEND_INTERNAL.lock();
204        bi.consoles.push(DisplayConsole {
205            console: new_console,
206            font_index: 0,
207            shader_index: 5,
208        });
209        bi.consoles.len() - 1
210    }
211
212    /// Sets the currently active console number.
213    pub fn set_active_console(&mut self, id: usize) {
214        let length = BACKEND_INTERNAL.lock().consoles.len();
215        if id < length {
216            self.active_console = id;
217        } else {
218            panic!(
219                "Invalid console id: {}. Valid consoles are 0..{}",
220                id, length
221            );
222        }
223    }
224
225    /// Applies the current physical mouse position to the active console, and translates the coordinates into that console's coordinate space.
226    #[cfg(any(feature = "curses", feature = "cross_term"))]
227    pub fn mouse_pos(&self) -> (i32, i32) {
228        (self.mouse_pos.0, self.mouse_pos.1)
229    }
230
231    ///
232    #[cfg(any(feature = "curses", feature = "cross_term"))]
233    fn pixel_to_char_pos(&self, pos: (i32, i32), _console: &Box<dyn Console>) -> (i32, i32) {
234        pos
235    }
236
237    #[cfg(not(any(feature = "curses", feature = "cross_term")))]
238    fn pixel_to_char_pos(&self, pos: (i32, i32), console: &Box<dyn Console>) -> (i32, i32) {
239        let max_sizes = console.get_char_size();
240        let (scale, center_x, center_y) = console.get_scale();
241
242        // Scaling now works by projecting the mouse position to 0..1 in both dimensions and then
243        // multiplying by the console size (with clamping).
244        let font_size = (
245            self.width_pixels as f32 / max_sizes.0 as f32,
246            self.height_pixels as f32 / max_sizes.1 as f32,
247        );
248        let mut offsets = (
249            center_x as f32 * font_size.0 * (scale - 1.0),
250            center_y as f32 * font_size.1 * (scale - 1.0),
251        );
252
253        let w: f32;
254        let h: f32;
255
256        {
257            let be = crate::hal::BACKEND.lock();
258            w = be.screen_scaler.available_width as f32;
259            h = be.screen_scaler.available_height as f32;
260            offsets.0 -= be.screen_scaler.gutter_left as f32;
261            offsets.1 -= be.screen_scaler.gutter_top as f32;
262        }
263
264        let extent_x = (pos.0 as f32 + offsets.0) / w;
265        let extent_y = (pos.1 as f32 + offsets.1) / h;
266        let mouse_x = f32::min(extent_x * max_sizes.0 as f32, max_sizes.0 as f32 - 1.0);
267        let mouse_y = f32::min(extent_y * max_sizes.1 as f32, max_sizes.1 as f32 - 1.0);
268
269        (i32::max(0, mouse_x as i32), i32::max(0, mouse_y as i32))
270    }
271
272    /// Applies the current physical mouse position to the active console, and translates the coordinates into that console's coordinate space.
273    #[cfg(not(any(feature = "curses", feature = "cross_term")))]
274    pub fn mouse_pos(&self) -> (i32, i32) {
275        let bi = BACKEND_INTERNAL.lock();
276        let active_console = &bi.consoles[self.active_console].console;
277
278        self.pixel_to_char_pos(self.mouse_pos, active_console)
279    }
280
281    /// Applies the current physical mouse position to the active console, and translates the coordinates into that console's coordinate space.
282    pub fn mouse_point(&self) -> Point {
283        let bi = BACKEND_INTERNAL.lock();
284        let active_console = &bi.consoles[self.active_console].console;
285        let char_pos = self.pixel_to_char_pos(self.mouse_pos, active_console);
286
287        Point::new(char_pos.0, char_pos.1)
288    }
289
290    /// Tells the game to quit
291    pub fn quit(&mut self) {
292        self.quitting = true;
293    }
294
295    /// Render a REX Paint (https://www.gridsagegames.com/rexpaint/) file as a sprite.
296    /// The sprite will be offset by offset_x and offset_y.
297    /// Transparent cells will not be rendered.
298    pub fn render_xp_sprite(&mut self, xp: &super::rex::XpFile, x: i32, y: i32) {
299        let mut bi = BACKEND_INTERNAL.lock();
300        super::rex::xp_to_console(xp, &mut bi.consoles[self.active_console].console, x, y);
301    }
302
303    /// Saves the entire console stack to a REX Paint XP file. If your consoles are of
304    /// varying sizes, the file format supports it - but REX doesn't. So you may want to
305    /// avoid that. You can also get individual layers with `to_xp_layer`.
306    pub fn to_xp_file(&self, width: usize, height: usize) -> XpFile {
307        let bi = BACKEND_INTERNAL.lock();
308        let mut xp = XpFile::new(width, height);
309
310        xp.layers
311            .push(bi.consoles[self.active_console].console.to_xp_layer());
312
313        if bi.consoles.len() > 1 {
314            for layer in bi.consoles.iter().skip(1) {
315                xp.layers.push(layer.console.to_xp_layer());
316            }
317        }
318
319        xp
320    }
321
322    /// Enable scanlines post-processing effect.
323    pub fn with_post_scanlines(&mut self, with_burn: bool) {
324        self.post_scanlines = true;
325        self.post_screenburn = with_burn;
326    }
327
328    // Change the screen-burn color
329    pub fn screen_burn_color(&mut self, color: bracket_color::prelude::RGB) {
330        self.screen_burn_color = color;
331    }
332
333    // Set the mouse cursor visibility
334    pub fn with_mouse_visibility(&mut self, with_visibility: bool) {
335        self.mouse_visible = with_visibility;
336    }
337
338    /// Internal: mark a key press
339    pub(crate) fn on_key(&mut self, key: VirtualKeyCode, scan_code: u32, pressed: bool) {
340        let mut input = INPUT.lock();
341        if pressed {
342            self.key = Some(key);
343            input.on_key_down(key, scan_code);
344        } else {
345            self.key = None;
346            input.on_key_up(key, scan_code);
347        }
348        input.push_event(BEvent::KeyboardInput {
349            key,
350            scan_code,
351            pressed,
352        });
353    }
354
355    /// Internal: mark a mouse press
356    pub(crate) fn on_mouse_button(&mut self, button_num: usize, pressed: bool) {
357        if button_num == 0 {
358            self.left_click = true;
359        }
360        let mut input = INPUT.lock();
361        if pressed {
362            input.on_mouse_button_down(button_num);
363        } else {
364            input.on_mouse_button_up(button_num);
365        }
366        input.push_event(BEvent::MouseClick {
367            button: button_num,
368            pressed,
369        });
370    }
371
372    /// Internal: mark mouse position changes
373    pub(crate) fn on_mouse_position(&mut self, x: f64, y: f64) {
374        let bi = BACKEND_INTERNAL.lock();
375        self.mouse_pos = (x as i32, y as i32);
376        let mut input = INPUT.lock();
377        input.on_mouse_pixel_position(x, y);
378        // TODO: Console cascade!
379        for (i, cons) in bi.consoles.iter().enumerate() {
380            let char_pos = self.pixel_to_char_pos(self.mouse_pos, &cons.console);
381
382            input.on_mouse_tile_position(i, char_pos.0, char_pos.1);
383        }
384    }
385
386    /// Internal: record an event from the HAL back-end
387    #[allow(dead_code)]
388    pub(crate) fn on_event(&mut self, event: BEvent) {
389        INPUT.lock().push_event(event);
390    }
391}
392
393/// Implements console-like BTerm. Note that this *isn't* a Console trait anymore,
394/// due to the need for helper generics.
395impl BTerm {
396    /// Gets the active console's size, in characters.
397    pub fn get_char_size(&self) -> (u32, u32) {
398        let bi = BACKEND_INTERNAL.lock();
399        bi.consoles[self.active_console].console.get_char_size()
400    }
401
402    /// Internal - do not use.
403    /// Passes a resize message down to all registered consoles.
404    pub(crate) fn resize_pixels<T>(&mut self, width: T, height: T, scaling_enabled: bool)
405    where
406        T: Into<u32>,
407    {
408        self.width_pixels = width.into();
409        self.height_pixels = height.into();
410
411        if scaling_enabled {
412            self.original_width_pixels = self.width_pixels;
413            self.original_height_pixels = self.height_pixels;
414        }
415
416        let mut bi = BACKEND_INTERNAL.lock();
417        for c in bi.consoles.iter_mut() {
418            c.console
419                .resize_pixels(self.width_pixels, self.height_pixels);
420        }
421    }
422
423    /// Request that the active console clear itself to default values.
424    pub fn cls(&mut self) {
425        BACKEND_INTERNAL.lock().consoles[self.active_console]
426            .console
427            .cls();
428    }
429
430    /// Request that the active console clear itself to a specified background color.
431    /// Has no effect on consoles that don't have a background color.
432    pub fn cls_bg<COLOR>(&mut self, background: COLOR)
433    where
434        COLOR: Into<RGBA>,
435    {
436        BACKEND_INTERNAL.lock().consoles[self.active_console]
437            .console
438            .cls_bg(background.into());
439    }
440
441    /// Print a string to the active console.
442    pub fn print<S, X, Y>(&mut self, x: X, y: Y, output: S)
443    where
444        S: ToString,
445        X: TryInto<i32>,
446        Y: TryInto<i32>,
447    {
448        BACKEND_INTERNAL.lock().consoles[self.active_console]
449            .console
450            .print(
451                x.try_into().ok().expect("Must be i32 convertible"),
452                y.try_into().ok().expect("Must be i32 convertible"),
453                &output.to_string(),
454            );
455    }
456
457    /// Print a string to the active console, in color.
458    pub fn print_color<S, COLOR, COLOR2, X, Y>(
459        &mut self,
460        x: X,
461        y: Y,
462        fg: COLOR,
463        bg: COLOR2,
464        output: S,
465    ) where
466        S: ToString,
467        COLOR: Into<RGBA>,
468        COLOR2: Into<RGBA>,
469        X: TryInto<i32>,
470        Y: TryInto<i32>,
471    {
472        BACKEND_INTERNAL.lock().consoles[self.active_console]
473            .console
474            .print_color(
475                x.try_into().ok().expect("Must be i32 convertible"),
476                y.try_into().ok().expect("Must be i32 convertible"),
477                fg.into(),
478                bg.into(),
479                &output.to_string(),
480            );
481    }
482
483    /// Set a single tile located at x/y to the specified foreground/background colors, and glyph.
484    pub fn set<COLOR, COLOR2, GLYPH, X, Y>(
485        &mut self,
486        x: X,
487        y: Y,
488        fg: COLOR,
489        bg: COLOR2,
490        glyph: GLYPH,
491    ) where
492        COLOR: Into<RGBA>,
493        COLOR2: Into<RGBA>,
494        GLYPH: TryInto<FontCharType>,
495        X: TryInto<i32>,
496        Y: TryInto<i32>,
497    {
498        BACKEND_INTERNAL.lock().consoles[self.active_console]
499            .console
500            .set(
501                x.try_into().ok().expect("Must be i32 convertible"),
502                y.try_into().ok().expect("Must be i32 convertible"),
503                fg.into(),
504                bg.into(),
505                glyph.try_into().ok().expect("Must be u16 convertible"),
506            );
507    }
508
509    /// Set a tile with "fancy" additional attributes
510    #[cfg(any(feature = "opengl", feature = "webgpu"))]
511    #[allow(clippy::too_many_arguments)]
512    pub fn set_fancy<COLOR, COLOR2, GLYPH, ANGLE>(
513        &mut self,
514        position: PointF,
515        z_order: i32,
516        rotation: ANGLE,
517        scale: PointF,
518        fg: COLOR,
519        bg: COLOR2,
520        glyph: GLYPH,
521    ) where
522        COLOR: Into<RGBA>,
523        COLOR2: Into<RGBA>,
524        GLYPH: TryInto<FontCharType>,
525        ANGLE: Into<Radians>,
526    {
527        let mut be = BACKEND_INTERNAL.lock();
528        let cons_any = be.consoles[self.active_console].console.as_any_mut();
529        if let Some(fc) = cons_any.downcast_mut::<FlexiConsole>() {
530            fc.set_fancy(
531                position,
532                z_order,
533                rotation.into().0,
534                scale,
535                fg.into(),
536                bg.into(),
537                glyph.try_into().ok().expect("Must be u16 convertible"),
538            );
539        }
540    }
541
542    /// Set a tile with "fancy" additional attributes
543    #[cfg(not(any(feature = "opengl", feature = "webgpu")))]
544    pub fn set_fancy<COLOR, COLOR2, GLYPH, ANGLE>(
545        &mut self,
546        _position: PointF,
547        _z_order: i32,
548        _rotation: ANGLE,
549        _scale: PointF,
550        _fg: COLOR,
551        _bg: COLOR2,
552        _glyph: GLYPH,
553    ) where
554        COLOR: Into<RGBA>,
555        COLOR2: Into<RGBA>,
556        GLYPH: TryInto<FontCharType>,
557        ANGLE: Into<Radians>,
558    {
559        // Does nothing
560    }
561
562    /// Sets the background color only of a specified tile.
563    pub fn set_bg<COLOR, X, Y>(&mut self, x: X, y: Y, bg: COLOR)
564    where
565        COLOR: Into<RGBA>,
566        X: TryInto<i32>,
567        Y: TryInto<i32>,
568    {
569        BACKEND_INTERNAL.lock().consoles[self.active_console]
570            .console
571            .set_bg(
572                x.try_into().ok().expect("Must be i32 convertible"),
573                y.try_into().ok().expect("Must be i32 convertible"),
574                bg.into(),
575            );
576    }
577
578    /// Draws a filled box, with single line characters.
579    pub fn draw_box<COLOR, COLOR2, X, Y, W, H>(
580        &mut self,
581        x: X,
582        y: Y,
583        width: W,
584        height: H,
585        fg: COLOR,
586        bg: COLOR2,
587    ) where
588        COLOR: Into<RGBA>,
589        COLOR2: Into<RGBA>,
590        X: TryInto<i32>,
591        Y: TryInto<i32>,
592        W: TryInto<i32>,
593        H: TryInto<i32>,
594    {
595        BACKEND_INTERNAL.lock().consoles[self.active_console]
596            .console
597            .draw_box(
598                x.try_into().ok().expect("Must be i32 convertible"),
599                y.try_into().ok().expect("Must be i32 convertible"),
600                width.try_into().ok().expect("Must be i32 convertible"),
601                height.try_into().ok().expect("Must be i32 convertible"),
602                fg.into(),
603                bg.into(),
604            );
605    }
606
607    /// Draws a filled box, with double line characters.
608    pub fn draw_box_double<COLOR, COLOR2, X, Y, W, H>(
609        &mut self,
610        x: X,
611        y: Y,
612        width: W,
613        height: H,
614        fg: COLOR,
615        bg: COLOR2,
616    ) where
617        COLOR: Into<RGBA>,
618        COLOR2: Into<RGBA>,
619        X: TryInto<i32>,
620        Y: TryInto<i32>,
621        W: TryInto<i32>,
622        H: TryInto<i32>,
623    {
624        BACKEND_INTERNAL.lock().consoles[self.active_console]
625            .console
626            .draw_box_double(
627                x.try_into().ok().expect("Must be i32 convertible"),
628                y.try_into().ok().expect("Must be i32 convertible"),
629                width.try_into().ok().expect("Must be i32 convertible"),
630                height.try_into().ok().expect("Must be i32 convertible"),
631                fg.into(),
632                bg.into(),
633            );
634    }
635
636    /// Draws a single-line box, without filling in the center.
637    pub fn draw_hollow_box<COLOR, COLOR2, X, Y, W, H>(
638        &mut self,
639        x: X,
640        y: Y,
641        width: W,
642        height: H,
643        fg: COLOR,
644        bg: COLOR2,
645    ) where
646        COLOR: Into<RGBA>,
647        COLOR2: Into<RGBA>,
648        X: TryInto<i32>,
649        Y: TryInto<i32>,
650        W: TryInto<i32>,
651        H: TryInto<i32>,
652    {
653        BACKEND_INTERNAL.lock().consoles[self.active_console]
654            .console
655            .draw_hollow_box(
656                x.try_into().ok().expect("Must be i32 convertible"),
657                y.try_into().ok().expect("Must be i32 convertible"),
658                width.try_into().ok().expect("Must be i32 convertible"),
659                height.try_into().ok().expect("Must be i32 convertible"),
660                fg.into(),
661                bg.into(),
662            );
663    }
664
665    /// Draws a double-line box, without filling in the contents.
666    pub fn draw_hollow_box_double<COLOR, COLOR2, X, Y, W, H>(
667        &mut self,
668        x: X,
669        y: Y,
670        width: W,
671        height: H,
672        fg: COLOR,
673        bg: COLOR2,
674    ) where
675        COLOR: Into<RGBA>,
676        COLOR2: Into<RGBA>,
677        X: TryInto<i32>,
678        Y: TryInto<i32>,
679        W: TryInto<i32>,
680        H: TryInto<i32>,
681    {
682        BACKEND_INTERNAL.lock().consoles[self.active_console]
683            .console
684            .draw_hollow_box_double(
685                x.try_into().ok().expect("Must be i32 convertible"),
686                y.try_into().ok().expect("Must be i32 convertible"),
687                width.try_into().ok().expect("Must be i32 convertible"),
688                height.try_into().ok().expect("Must be i32 convertible"),
689                fg.into(),
690                bg.into(),
691            );
692    }
693
694    /// Draws a horizontal bar, suitable for health-bars or progress bars.
695    #[allow(clippy::too_many_arguments)]
696    pub fn draw_bar_horizontal<COLOR, COLOR2, X, Y, W, N, MAX>(
697        &mut self,
698        x: X,
699        y: Y,
700        width: W,
701        n: N,
702        max: MAX,
703        fg: COLOR,
704        bg: COLOR2,
705    ) where
706        COLOR: Into<RGBA>,
707        COLOR2: Into<RGBA>,
708        X: TryInto<i32>,
709        Y: TryInto<i32>,
710        W: TryInto<i32>,
711        N: TryInto<i32>,
712        MAX: TryInto<i32>,
713    {
714        BACKEND_INTERNAL.lock().consoles[self.active_console]
715            .console
716            .draw_bar_horizontal(
717                x.try_into().ok().expect("Must be i32 convertible"),
718                y.try_into().ok().expect("Must be i32 convertible"),
719                width.try_into().ok().expect("Must be i32 convertible"),
720                n.try_into().ok().expect("Must be i32 convertible"),
721                max.try_into().ok().expect("Must be i32 convertible"),
722                fg.into(),
723                bg.into(),
724            );
725    }
726
727    /// Draws a vertical bar, suitable for health-bars or progress bars.
728    #[allow(clippy::too_many_arguments)]
729    pub fn draw_bar_vertical<COLOR, COLOR2, X, Y, H, N, MAX>(
730        &mut self,
731        x: X,
732        y: Y,
733        height: H,
734        n: N,
735        max: MAX,
736        fg: COLOR,
737        bg: COLOR2,
738    ) where
739        COLOR: Into<RGBA>,
740        COLOR2: Into<RGBA>,
741        X: TryInto<i32>,
742        Y: TryInto<i32>,
743        H: TryInto<i32>,
744        N: TryInto<i32>,
745        MAX: TryInto<i32>,
746    {
747        BACKEND_INTERNAL.lock().consoles[self.active_console]
748            .console
749            .draw_bar_vertical(
750                x.try_into().ok().expect("Must be i32 convertible"),
751                y.try_into().ok().expect("Must be i32 convertible"),
752                height.try_into().ok().expect("Must be i32 convertible"),
753                n.try_into().ok().expect("Must be i32 convertible"),
754                max.try_into().ok().expect("Must be i32 convertible"),
755                fg.into(),
756                bg.into(),
757            );
758    }
759
760    /// Fills a target region with the specified color/glyph combo.
761    pub fn fill_region<COLOR, COLOR2, GLYPH>(
762        &mut self,
763        target: Rect,
764        glyph: GLYPH,
765        fg: COLOR,
766        bg: COLOR2,
767    ) where
768        COLOR: Into<RGBA>,
769        COLOR2: Into<RGBA>,
770        GLYPH: TryInto<FontCharType>,
771    {
772        BACKEND_INTERNAL.lock().consoles[self.active_console]
773            .console
774            .fill_region(target, glyph.try_into().ok().unwrap(), fg.into(), bg.into());
775    }
776
777    /// Prints centered text, centered across the whole line
778    pub fn print_centered<S, Y>(&mut self, y: Y, text: S)
779    where
780        S: ToString,
781        Y: TryInto<i32>,
782    {
783        BACKEND_INTERNAL.lock().consoles[self.active_console]
784            .console
785            .print_centered(
786                y.try_into().ok().expect("Must be i32 convertible"),
787                &text.to_string(),
788            );
789    }
790
791    /// Prints centered text, centered across the whole line - in color
792    pub fn print_color_centered<S, COLOR, COLOR2, Y>(
793        &mut self,
794        y: Y,
795        fg: COLOR,
796        bg: COLOR2,
797        text: S,
798    ) where
799        S: ToString,
800        COLOR: Into<RGBA>,
801        COLOR2: Into<RGBA>,
802        Y: TryInto<i32>,
803    {
804        BACKEND_INTERNAL.lock().consoles[self.active_console]
805            .console
806            .print_color_centered(
807                y.try_into().ok().expect("Must be i32 convertible"),
808                fg.into(),
809                bg.into(),
810                &text.to_string(),
811            );
812    }
813
814    /// Prints text, centered on an arbitrary point
815    pub fn print_centered_at<S, X, Y>(&mut self, x: X, y: Y, text: S)
816    where
817        S: ToString,
818        X: TryInto<i32>,
819        Y: TryInto<i32>,
820    {
821        BACKEND_INTERNAL.lock().consoles[self.active_console]
822            .console
823            .print_centered_at(
824                x.try_into().ok().expect("Must be i32 convertible"),
825                y.try_into().ok().expect("Must be i32 convertible"),
826                &text.to_string(),
827            );
828    }
829
830    /// Prints colored text, centered on an arbitrary point
831    pub fn print_color_centered_at<S, COLOR, COLOR2, X, Y>(
832        &mut self,
833        x: X,
834        y: Y,
835        fg: COLOR,
836        bg: COLOR2,
837        text: S,
838    ) where
839        S: ToString,
840        COLOR: Into<RGBA>,
841        COLOR2: Into<RGBA>,
842        X: TryInto<i32>,
843        Y: TryInto<i32>,
844    {
845        BACKEND_INTERNAL.lock().consoles[self.active_console]
846            .console
847            .print_color_centered_at(
848                x.try_into().ok().expect("Must be i32 convertible"),
849                y.try_into().ok().expect("Must be i32 convertible"),
850                fg.into(),
851                bg.into(),
852                &text.to_string(),
853            );
854    }
855
856    /// Prints right-aligned text
857    pub fn print_right<S, X, Y>(&mut self, x: X, y: Y, text: S)
858    where
859        S: ToString,
860        X: TryInto<i32>,
861        Y: TryInto<i32>,
862    {
863        BACKEND_INTERNAL.lock().consoles[self.active_console]
864            .console
865            .print_right(
866                x.try_into().ok().expect("Must be i32 convertible"),
867                y.try_into().ok().expect("Must be i32 convertible"),
868                &text.to_string(),
869            );
870    }
871
872    /// Prints right-aligned text, in color
873    pub fn print_color_right<S, COLOR, COLOR2, X, Y>(
874        &mut self,
875        x: X,
876        y: Y,
877        fg: COLOR,
878        bg: COLOR2,
879        text: S,
880    ) where
881        S: ToString,
882        COLOR: Into<RGBA>,
883        COLOR2: Into<RGBA>,
884        X: TryInto<i32>,
885        Y: TryInto<i32>,
886    {
887        BACKEND_INTERNAL.lock().consoles[self.active_console]
888            .console
889            .print_color_right(
890                x.try_into().ok().expect("Must be i32 convertible"),
891                y.try_into().ok().expect("Must be i32 convertible"),
892                fg.into(),
893                bg.into(),
894                &text.to_string(),
895            );
896    }
897
898    /// Print a colorized string with the color encoding defined inline.
899    /// For example: printer(1, 1, "#[blue]This blue text contains a #[pink]pink#[] word")
900    /// You can get the same effect with a TextBlock, but this can be easier.
901    /// Thanks to doryen_rs for the idea.
902    pub fn printer<S, X, Y>(
903        &mut self,
904        x: X,
905        y: Y,
906        output: S,
907        align: TextAlign,
908        background: Option<RGBA>,
909    ) where
910        S: ToString,
911        X: TryInto<i32>,
912        Y: TryInto<i32>,
913    {
914        BACKEND_INTERNAL.lock().consoles[self.active_console]
915            .console
916            .printer(
917                x.try_into().ok().expect("Must be i32 convertible"),
918                y.try_into().ok().expect("Must be i32 convertible"),
919                &output.to_string(),
920                align,
921                background,
922            );
923    }
924
925    /// Exports the current layer to a REX Paint file
926    pub fn to_xp_layer(&self) -> XpLayer {
927        BACKEND_INTERNAL.lock().consoles[self.active_console]
928            .console
929            .to_xp_layer()
930    }
931
932    /// Sets the active offset for the current layer
933    pub fn set_offset(&mut self, x: f32, y: f32) {
934        BACKEND_INTERNAL.lock().consoles[self.active_console]
935            .console
936            .set_offset(x, y);
937    }
938
939    /// Sets the active scale for the current layer
940    pub fn set_scale(&mut self, scale: f32, center_x: i32, center_y: i32) {
941        BACKEND_INTERNAL.lock().consoles[self.active_console]
942            .console
943            .set_scale(scale, center_x, center_y);
944    }
945
946    /// Gets the active scale for the current layer
947    pub fn get_scale(&self) -> (f32, i32, i32) {
948        BACKEND_INTERNAL.lock().consoles[self.active_console]
949            .console
950            .get_scale()
951    }
952
953    /// Permits the creation of an arbitrary clipping rectangle. It's a really good idea
954    /// to make sure that this rectangle is entirely valid.
955    pub fn set_clipping(&mut self, clipping: Option<Rect>) {
956        BACKEND_INTERNAL.lock().consoles[self.active_console]
957            .console
958            .set_clipping(clipping);
959    }
960
961    /// Returns the current arbitrary clipping rectangle, None if there isn't one.
962    pub fn get_clipping(&self) -> Option<Rect> {
963        BACKEND_INTERNAL.lock().consoles[self.active_console]
964            .console
965            .get_clipping()
966    }
967
968    /// Sets ALL tiles foreground alpha (only tiles that exist, in sparse consoles).
969    pub fn set_all_fg_alpha(&mut self, alpha: f32) {
970        BACKEND_INTERNAL.lock().consoles[self.active_console]
971            .console
972            .set_all_fg_alpha(alpha);
973    }
974
975    /// Sets ALL tiles background alpha (only tiles that exist, in sparse consoles).
976    pub fn set_all_bg_alpha(&mut self, alpha: f32) {
977        BACKEND_INTERNAL.lock().consoles[self.active_console]
978            .console
979            .set_all_bg_alpha(alpha);
980    }
981
982    /// Sets ALL tiles foreground alpha (only tiles that exist, in sparse consoles).
983    pub fn set_all_alpha(&mut self, fg: f32, bg: f32) {
984        BACKEND_INTERNAL.lock().consoles[self.active_console]
985            .console
986            .set_all_alpha(fg, bg);
987    }
988
989    /// Sets the character translation mode on a console
990    pub fn set_translation_mode(&mut self, console: usize, translation: CharacterTranslationMode) {
991        BACKEND_INTERNAL.lock().consoles[console]
992            .console
993            .set_translation_mode(translation)
994    }
995
996    #[cfg(any(feature = "opengl", feature = "webgpu"))]
997    /// Change the active font for the layer. DO NOT USE WITH AMETHYST YET.
998    pub fn set_active_font(&mut self, font_index: usize, resize_to_natural_dimensions: bool) {
999        let mut be = BACKEND_INTERNAL.lock();
1000        if font_index > be.fonts.len() {
1001            panic!("Font index out of bounds.");
1002        }
1003        let old_font_size = be.fonts[be.consoles[self.active_console].font_index].tile_size;
1004        let new_font_size = be.fonts[font_index].tile_size;
1005        be.consoles[self.active_console].font_index = font_index;
1006
1007        if old_font_size != new_font_size && resize_to_natural_dimensions {
1008            let x_size = self.original_width_pixels / new_font_size.0;
1009            let y_size = self.original_height_pixels / new_font_size.1;
1010
1011            be.consoles[self.active_console]
1012                .console
1013                .set_char_size(x_size, y_size);
1014        }
1015    }
1016
1017    #[cfg(all(
1018        any(feature = "opengl", feature = "webgpu"),
1019        not(target_arch = "wasm32")
1020    ))]
1021    /// Manually override the character size for the current terminal. Use with caution!
1022    pub fn set_char_size(&mut self, width: u32, height: u32) {
1023        BACKEND_INTERNAL.lock().consoles[self.active_console]
1024            .console
1025            .set_char_size(width, height);
1026    }
1027
1028    #[cfg(all(
1029        any(feature = "opengl", feature = "webgpu"),
1030        not(target_arch = "wasm32")
1031    ))]
1032    /// Manually override the character size for the current terminal. Use with caution!
1033    pub fn set_char_size_and_resize_window(&mut self, _width: u32, _height: u32) {
1034        /*
1035        let be = BACKEND_INTERNAL.lock();
1036        let font_size = be.fonts[be.consoles[0].font_index].tile_size;
1037        let w = font_size.0 * width;
1038        let h = font_size.1 * height;
1039        crate::prelude::BACKEND.lock().resize_request = Some((w, h));
1040        */
1041        //panic!("This will be supported when `winit` stops crashing on resize request.");
1042    }
1043
1044    /// Take a screenshot - Native only
1045    #[cfg(all(
1046        any(feature = "opengl", feature = "webgpu"),
1047        not(target_arch = "wasm32")
1048    ))]
1049    pub fn screenshot<S: ToString>(&mut self, filename: S) {
1050        BACKEND.lock().request_screenshot = Some(filename.to_string());
1051    }
1052
1053    /// Take a screenshot - Native only
1054    #[cfg(not(all(
1055        any(feature = "opengl", feature = "webgpu"),
1056        not(target_arch = "wasm32")
1057    )))]
1058    pub fn screenshot<S: ToString>(&mut self, _filename: S) {
1059        // Do nothing
1060    }
1061
1062    /// Register a sprite sheet (OpenGL - native or WASM - only)
1063    #[cfg(any(feature = "opengl", feature = "webgpu"))]
1064    pub fn register_spritesheet(&mut self, ss: SpriteSheet) -> usize {
1065        let mut bi = BACKEND_INTERNAL.lock();
1066        let id = bi.sprite_sheets.len();
1067        bi.sprite_sheets.push(ss);
1068        id
1069    }
1070
1071    /// Add a sprite to the current console
1072    #[cfg(any(feature = "opengl", feature = "webgpu"))]
1073    pub fn add_sprite(&mut self, destination: Rect, z_order: i32, tint: RGBA, index: usize) {
1074        let mut bi = BACKEND_INTERNAL.lock();
1075        let as_any = bi.consoles[self.active_console].console.as_any_mut();
1076        if let Some(cons) = as_any.downcast_mut::<SpriteConsole>() {
1077            cons.render_sprite(RenderSprite {
1078                destination,
1079                z_order,
1080                tint,
1081                index,
1082            });
1083        }
1084    }
1085}
1086
1087/// Runs the BTerm application, calling into the provided gamestate handler every tick.
1088pub fn main_loop<GS: GameState>(bterm: BTerm, gamestate: GS) -> BResult<()> {
1089    super::hal::main_loop(bterm, gamestate)?;
1090    Ok(())
1091}
1092
1093/// For A-Z menus, translates the keys A through Z into 0..25
1094pub fn letter_to_option(key: VirtualKeyCode) -> i32 {
1095    match key {
1096        VirtualKeyCode::A => 0,
1097        VirtualKeyCode::B => 1,
1098        VirtualKeyCode::C => 2,
1099        VirtualKeyCode::D => 3,
1100        VirtualKeyCode::E => 4,
1101        VirtualKeyCode::F => 5,
1102        VirtualKeyCode::G => 6,
1103        VirtualKeyCode::H => 7,
1104        VirtualKeyCode::I => 8,
1105        VirtualKeyCode::J => 9,
1106        VirtualKeyCode::K => 10,
1107        VirtualKeyCode::L => 11,
1108        VirtualKeyCode::M => 12,
1109        VirtualKeyCode::N => 13,
1110        VirtualKeyCode::O => 14,
1111        VirtualKeyCode::P => 15,
1112        VirtualKeyCode::Q => 16,
1113        VirtualKeyCode::R => 17,
1114        VirtualKeyCode::S => 18,
1115        VirtualKeyCode::T => 19,
1116        VirtualKeyCode::U => 20,
1117        VirtualKeyCode::V => 21,
1118        VirtualKeyCode::W => 22,
1119        VirtualKeyCode::X => 23,
1120        VirtualKeyCode::Y => 24,
1121        VirtualKeyCode::Z => 25,
1122        _ => -1,
1123    }
1124}
1125
1126// Since num::clamp is still experimental, this is a simple integer clamper.
1127fn iclamp(val: i32, min: i32, max: i32) -> i32 {
1128    i32::max(min, i32::min(val, max))
1129}
1130
1131#[cfg(test)]
1132mod tests {
1133    use super::iclamp;
1134
1135    #[test]
1136    // Tests that we make an RGB triplet at defaults and it is black.
1137    fn test_iclamp() {
1138        assert!(iclamp(1, 0, 2) == 1);
1139        assert!(iclamp(5, 0, 2) == 2);
1140        assert!(iclamp(-5, 0, 2) == 0);
1141    }
1142}