bracket_terminal/
initializer.rs

1use crate::prelude::{
2    init_raw, BTerm, CharacterTranslationMode, FlexiConsole, Font, InitHints, SimpleConsole,
3    SparseConsole, SpriteConsole, SpriteSheet, INPUT,
4};
5use crate::BResult;
6use bracket_color::prelude::RGB;
7use std::collections::HashMap;
8use std::convert::*;
9
10/// Internal structure defining a font to be loaded.
11struct BuilderFont {
12    path: String,
13    dimensions: (u32, u32),
14    explicit_background: Option<RGB>,
15}
16
17/// Internal enum defining a console to be loaded.
18enum ConsoleType {
19    SimpleConsole {
20        width: u32,
21        height: u32,
22        font: String,
23        translator: CharacterTranslationMode,
24    },
25    SimpleConsoleNoBg {
26        width: u32,
27        height: u32,
28        font: String,
29        translator: CharacterTranslationMode,
30    },
31    SparseConsole {
32        width: u32,
33        height: u32,
34        font: String,
35        translator: CharacterTranslationMode,
36    },
37    SparseConsoleNoBg {
38        width: u32,
39        height: u32,
40        font: String,
41        translator: CharacterTranslationMode,
42    },
43    FlexiConsole {
44        width: u32,
45        height: u32,
46        font: String,
47        translator: CharacterTranslationMode,
48    },
49    SpriteConsole {
50        width: u32,
51        height: u32,
52        sprite_sheet: usize,
53    },
54}
55
56/// Provides a builder mechanism for initializing BTerm. You can chain builders together,
57/// and and with a call to `.build()`. This allows you to provide settings if you want to,
58/// or just use a simple initializer if you are in a hurry.
59pub struct BTermBuilder {
60    width: u32,
61    height: u32,
62    title: Option<String>,
63    resource_path: String,
64    fonts: Vec<BuilderFont>,
65    consoles: Vec<ConsoleType>,
66    tile_width: u32,
67    tile_height: u32,
68    platform_hints: InitHints,
69    advanced_input: bool,
70    sprite_sheets: Vec<SpriteSheet>,
71}
72
73impl Default for BTermBuilder {
74    fn default() -> Self {
75        Self {
76            width: 80,
77            height: 50,
78            title: None,
79            resource_path: "resources".to_string(),
80            fonts: Vec::new(),
81            consoles: Vec::new(),
82            tile_height: 8,
83            tile_width: 8,
84            platform_hints: InitHints::new(),
85            advanced_input: false,
86            sprite_sheets: Vec::new(),
87        }
88    }
89}
90
91impl BTermBuilder {
92    /// Provides a new, unconfigured, starting point for an BTerm session. You'll have to
93    /// specify everything manually.
94    pub fn new() -> Self {
95        Self {
96            width: 80,
97            height: 50,
98            title: None,
99            resource_path: "resources".to_string(),
100            fonts: Vec::new(),
101            consoles: Vec::new(),
102            tile_height: 8,
103            tile_width: 8,
104            platform_hints: InitHints::new(),
105            advanced_input: false,
106            sprite_sheets: Vec::new(),
107        }
108    }
109
110    /// Provides an 80x50 console in the baked-in 8x8 terminal font as your starting point.
111    pub fn simple80x50() -> Self {
112        let mut cb = Self {
113            width: 80,
114            height: 50,
115            title: None,
116            resource_path: "resources".to_string(),
117            fonts: Vec::new(),
118            consoles: Vec::new(),
119            tile_height: 8,
120            tile_width: 8,
121            platform_hints: InitHints::new(),
122            advanced_input: false,
123            sprite_sheets: Vec::new(),
124        };
125        cb.fonts.push(BuilderFont {
126            path: "terminal8x8.png".to_string(),
127            dimensions: (8, 8),
128            explicit_background: None,
129        });
130        cb.consoles.push(ConsoleType::SimpleConsole {
131            width: 80,
132            height: 50,
133            font: "terminal8x8.png".to_string(),
134            translator: CharacterTranslationMode::Codepage437,
135        });
136        cb
137    }
138
139    /// Provides an 8x8 terminal font simple console, with the specified dimensions as your starting point.
140    pub fn simple<T>(width: T, height: T) -> BResult<Self>
141    where
142        T: TryInto<u32>,
143    {
144        let w: u32 = width.try_into().or(Err("Must be convertible to a u32"))?;
145        let h: u32 = height.try_into().or(Err("Must be convertible to a u32"))?;
146        let mut cb = Self {
147            width: w,
148            height: h,
149            title: None,
150            resource_path: "resources".to_string(),
151            fonts: Vec::new(),
152            consoles: Vec::new(),
153            tile_height: 8,
154            tile_width: 8,
155            platform_hints: InitHints::new(),
156            advanced_input: false,
157            sprite_sheets: Vec::new(),
158        };
159        cb.fonts.push(BuilderFont {
160            path: "terminal8x8.png".to_string(),
161            dimensions: (8, 8),
162            explicit_background: None,
163        });
164        cb.consoles.push(ConsoleType::SimpleConsole {
165            width: w,
166            height: h,
167            font: "terminal8x8.png".to_string(),
168            translator: CharacterTranslationMode::Codepage437,
169        });
170        Ok(cb)
171    }
172
173    /// Provides an 80x50 terminal, in the VGA font as your starting point.
174    pub fn vga80x50() -> Self {
175        let mut cb = Self {
176            width: 80,
177            height: 50,
178            title: None,
179            resource_path: "resources".to_string(),
180            fonts: Vec::new(),
181            consoles: Vec::new(),
182            tile_height: 16,
183            tile_width: 8,
184            platform_hints: InitHints::new(),
185            advanced_input: false,
186            sprite_sheets: Vec::new(),
187        };
188        cb.fonts.push(BuilderFont {
189            path: "vga8x16.png".to_string(),
190            dimensions: (8, 16),
191            explicit_background: None,
192        });
193        cb.consoles.push(ConsoleType::SimpleConsole {
194            width: 80,
195            height: 50,
196            font: "vga8x16.png".to_string(),
197            translator: CharacterTranslationMode::Codepage437,
198        });
199        cb
200    }
201
202    /// Provides a VGA-font simple terminal with the specified dimensions as your starting point.
203    pub fn vga<T>(width: T, height: T) -> Self
204    where
205        T: TryInto<u32>,
206    {
207        let w: u32 = width.try_into().ok().expect("Must be convertible to a u32");
208        let h: u32 = height
209            .try_into()
210            .ok()
211            .expect("Must be convertible to a u32");
212        let mut cb = Self {
213            width: w,
214            height: h,
215            title: None,
216            resource_path: "resources".to_string(),
217            fonts: Vec::new(),
218            consoles: Vec::new(),
219            tile_height: 16,
220            tile_width: 8,
221            platform_hints: InitHints::new(),
222            advanced_input: false,
223            sprite_sheets: Vec::new(),
224        };
225        cb.fonts.push(BuilderFont {
226            path: "vga8x16.png".to_string(),
227            dimensions: (8, 16),
228            explicit_background: None,
229        });
230        cb.consoles.push(ConsoleType::SimpleConsole {
231            width: w,
232            height: h,
233            font: "vga8x16.png".to_string(),
234            translator: CharacterTranslationMode::Codepage437,
235        });
236        cb
237    }
238
239    /// Adds width/height dimensions to the BTerm builder.
240    pub fn with_dimensions<T>(mut self, width: T, height: T) -> Self
241    where
242        T: TryInto<u32>,
243    {
244        self.width = width.try_into().ok().expect("Must be convertible to a u32");
245        self.height = height
246            .try_into()
247            .ok()
248            .expect("Must be convertible to a u32");
249        self
250    }
251
252    /// Overrides the default assumption for tile sizes. Needed for a raw initialization.
253    /// If you have lots of fonts, the library will pick one (generally the first) to try
254    /// and determine what dimensions you want to use when figuring out your window size.
255    /// This method is used to override that assumption.
256    /// It's a great idea to use this when using multiple layers and fonts.
257    pub fn with_tile_dimensions<T>(mut self, width: T, height: T) -> Self
258    where
259        T: TryInto<u32>,
260    {
261        self.tile_width = width.try_into().ok().expect("Must be convertible to a u32");
262        self.tile_height = height
263            .try_into()
264            .ok()
265            .expect("Must be convertible to a u32");
266        self
267    }
268
269    /// Adds a window title to the BTerm builder.
270    pub fn with_title<S: ToString>(mut self, title: S) -> Self {
271        self.title = Some(title.to_string());
272        self
273    }
274
275    /// Adds a resource path to the BTerm builder. You only need to specify this if you aren't
276    /// embedding your resources.
277    pub fn with_resource_path<S: ToString>(mut self, path: S) -> Self {
278        self.resource_path = path.to_string();
279        self
280    }
281
282    /// Adds a font registration to the BTerm builder.
283    pub fn with_font<S: ToString, T>(mut self, font_path: S, width: T, height: T) -> Self
284    where
285        T: TryInto<u32>,
286    {
287        self.fonts.push(BuilderFont {
288            path: font_path.to_string(),
289            dimensions: (
290                width.try_into().ok().expect("Must be convertible to a u32"),
291                height
292                    .try_into()
293                    .ok()
294                    .expect("Must be convertible to a u32"),
295            ),
296            explicit_background: None,
297        });
298        self
299    }
300
301    /// Adds a font registration to the BTerm builder.
302    pub fn with_font_bg<S: ToString, T, COLOR>(
303        mut self,
304        font_path: S,
305        width: T,
306        height: T,
307        background: COLOR,
308    ) -> Self
309    where
310        T: TryInto<u32>,
311        COLOR: Into<RGB>,
312    {
313        self.fonts.push(BuilderFont {
314            path: font_path.to_string(),
315            dimensions: (
316                width.try_into().ok().expect("Must be convertible to a u32"),
317                height
318                    .try_into()
319                    .ok()
320                    .expect("Must be convertible to a u32"),
321            ),
322            explicit_background: Some(background.into()),
323        });
324        self
325    }
326
327    /// Adds a simple console layer to the BTerm builder.
328    pub fn with_simple_console<S: ToString, T>(mut self, width: T, height: T, font: S) -> Self
329    where
330        T: TryInto<u32>,
331    {
332        self.consoles.push(ConsoleType::SimpleConsole {
333            width: width.try_into().ok().expect("Must be convertible to a u32"),
334            height: height
335                .try_into()
336                .ok()
337                .expect("Must be convertible to a u32"),
338            font: font.to_string(),
339            translator: CharacterTranslationMode::Codepage437,
340        });
341        self
342    }
343
344    /// Adds a simple console layer to the BTerm builder, with no background.
345    pub fn with_simple_console_no_bg<S: ToString, T>(mut self, width: T, height: T, font: S) -> Self
346    where
347        T: TryInto<u32>,
348    {
349        self.consoles.push(ConsoleType::SimpleConsoleNoBg {
350            width: width.try_into().ok().expect("Must be convertible to a u32"),
351            height: height
352                .try_into()
353                .ok()
354                .expect("Must be convertible to a u32"),
355            font: font.to_string(),
356            translator: CharacterTranslationMode::Codepage437,
357        });
358        self
359    }
360
361    /// Adds a simple console, hard-coded to the baked-in 8x8 terminal font. This does NOT register the font.
362    pub fn with_simple8x8(mut self) -> Self {
363        self.consoles.push(ConsoleType::SimpleConsole {
364            width: self.width,
365            height: self.height,
366            font: "terminal8x8.png".to_string(),
367            translator: CharacterTranslationMode::Codepage437,
368        });
369        self
370    }
371
372    /// Adds a sparse console layer to the BTerm builder.
373    pub fn with_sparse_console<S: ToString, T>(mut self, width: T, height: T, font: S) -> Self
374    where
375        T: TryInto<u32>,
376    {
377        self.consoles.push(ConsoleType::SparseConsole {
378            width: width.try_into().ok().expect("Must be convertible to a u32"),
379            height: height
380                .try_into()
381                .ok()
382                .expect("Must be convertible to a u32"),
383            font: font.to_string(),
384            translator: CharacterTranslationMode::Codepage437,
385        });
386        self
387    }
388
389    /// Adds a sparse console with no bg rendering layer to the BTerm builder.
390    pub fn with_sparse_console_no_bg<S: ToString, T>(mut self, width: T, height: T, font: S) -> Self
391    where
392        T: TryInto<u32>,
393    {
394        self.consoles.push(ConsoleType::SparseConsoleNoBg {
395            width: width.try_into().ok().expect("Must be convertible to a u32"),
396            height: height
397                .try_into()
398                .ok()
399                .expect("Must be convertible to a u32"),
400            font: font.to_string(),
401            translator: CharacterTranslationMode::Codepage437,
402        });
403        self
404    }
405
406    /// Adds a fancy (supporting per-glyph offsets, rotation, etc.) console. OpenGL only for now.
407    #[cfg(any(feature = "opengl", feature = "webgpu"))]
408    pub fn with_fancy_console<S: ToString, T>(mut self, width: T, height: T, font: S) -> Self
409    where
410        T: TryInto<u32>,
411    {
412        self.consoles.push(ConsoleType::FlexiConsole {
413            width: width.try_into().ok().expect("Must be convertible to a u32"),
414            height: height
415                .try_into()
416                .ok()
417                .expect("Must be convertible to a u32"),
418            font: font.to_string(),
419            translator: CharacterTranslationMode::Codepage437,
420        });
421        self
422    }
423
424    /// Adds a sprite console
425    #[cfg(any(feature = "opengl", feature = "webgpu"))]
426    pub fn with_sprite_console<T>(mut self, width: T, height: T, sprite_sheet: usize) -> Self
427    where
428        T: TryInto<u32>,
429    {
430        self.consoles.push(ConsoleType::SpriteConsole {
431            width: width.try_into().ok().expect("Must be convertible to a u32"),
432            height: height
433                .try_into()
434                .ok()
435                .expect("Must be convertible to a u32"),
436            sprite_sheet,
437        });
438        self
439    }
440
441    /// Enables you to override the vsync default for native rendering.
442    pub fn with_vsync(mut self, vsync: bool) -> Self {
443        self.platform_hints.vsync = vsync;
444        self
445    }
446
447    /// Enables you to override the full screen setting for native rendering.
448    pub fn with_fullscreen(mut self, fullscreen: bool) -> Self {
449        self.platform_hints.fullscreen = fullscreen;
450        self
451    }
452
453    /// Enables you to auto adjust the window to fit the screen.
454    pub fn with_fitscreen(mut self, fitscreen: bool) -> Self {
455        self.platform_hints.fitscreen = fitscreen;
456        self
457    }
458
459    /// Push platform-specific initialization hints to the builder. THIS REMOVES CROSS-PLATFORM COMPATIBILITY
460    pub fn with_platform_specific(mut self, hints: InitHints) -> Self {
461        self.platform_hints = hints;
462        self
463    }
464
465    /// Instructs the back-end (not all of them honor it; WASM and Amethyst do their own thing) to try to limit frame-rate and CPU utilization.
466    pub fn with_fps_cap(mut self, fps: f32) -> Self {
467        self.platform_hints.frame_sleep_time = Some(1.0 / fps);
468        self
469    }
470
471    /// Enables input event queue
472    pub fn with_advanced_input(mut self, advanced_input: bool) -> Self {
473        self.advanced_input = advanced_input;
474        self
475    }
476
477    /// Enable resize changing console size, rather than scaling. Native OpenGL only.
478    #[cfg(all(
479        any(feature = "opengl", feature = "webgpu"),
480        not(target_arch = "wasm32")
481    ))]
482    pub fn with_automatic_console_resize(mut self, resize_scaling: bool) -> Self {
483        self.platform_hints.resize_scaling = resize_scaling;
484        self
485    }
486
487    /// Register a sprite sheet
488    #[cfg(any(feature = "opengl", feature = "webgpu"))]
489    pub fn with_sprite_sheet(mut self, ss: SpriteSheet) -> Self {
490        self.sprite_sheets.push(ss);
491        self
492    }
493
494    /// Request a pixel gutter around the rendered area. The gutter is
495    /// applied with half at each side of the screen, centering it. If you
496    /// want a 4 pixel gutter on every side, request a gutter size of 8.
497    pub fn with_gutter(mut self, desired_gutter: u32) -> Self {
498        #[cfg(any(feature = "opengl", feature = "webgpu"))]
499        {
500            self.platform_hints.desired_gutter = desired_gutter;
501        }
502        self
503    }
504
505    /// Combine all of the builder parameters, and return an BTerm context ready to go.
506    pub fn build(self) -> BResult<BTerm> {
507        let mut context = init_raw(
508            self.width * self.tile_width,
509            self.height * self.tile_height,
510            self.title.unwrap_or_else(|| "BTerm Window".to_string()),
511            self.platform_hints,
512        )?;
513
514        let mut font_map: HashMap<String, usize> = HashMap::new();
515        for font in &self.fonts {
516            let font_path = path_join(&self.resource_path, &font.path);
517            let font_id = context.register_font(Font::load(
518                font_path.clone(),
519                font.dimensions,
520                font.explicit_background,
521            ));
522            font_map.insert(font_path, font_id?);
523        }
524
525        #[cfg(any(feature = "opengl", feature = "webgpu"))]
526        for ss in self.sprite_sheets {
527            context.register_spritesheet(ss);
528        }
529
530        for console in &self.consoles {
531            match console {
532                ConsoleType::SimpleConsole {
533                    width,
534                    height,
535                    font,
536                    translator,
537                } => {
538                    let font_path = path_join(&self.resource_path, font);
539                    let font_id = font_map[&font_path];
540                    let cid =
541                        context.register_console(SimpleConsole::init(*width, *height), font_id);
542                    context.set_translation_mode(cid, *translator);
543                }
544                ConsoleType::SimpleConsoleNoBg {
545                    width,
546                    height,
547                    font,
548                    translator,
549                } => {
550                    let font_path = path_join(&self.resource_path, font);
551                    let font_id = font_map[&font_path];
552                    let cid = context
553                        .register_console_no_bg(SimpleConsole::init(*width, *height), font_id);
554                    context.set_translation_mode(cid, *translator);
555                }
556                ConsoleType::SparseConsole {
557                    width,
558                    height,
559                    font,
560                    translator,
561                } => {
562                    let font_path = path_join(&self.resource_path, font);
563                    let font_id = font_map[&font_path];
564                    let cid =
565                        context.register_console(SparseConsole::init(*width, *height), font_id);
566                    context.set_translation_mode(cid, *translator);
567                }
568                ConsoleType::SparseConsoleNoBg {
569                    width,
570                    height,
571                    font,
572                    translator,
573                } => {
574                    let font_path = path_join(&self.resource_path, font);
575                    let font_id = font_map[&font_path];
576                    let cid = context
577                        .register_console_no_bg(SparseConsole::init(*width, *height), font_id);
578                    context.set_translation_mode(cid, *translator);
579                }
580                ConsoleType::FlexiConsole {
581                    width,
582                    height,
583                    font,
584                    translator,
585                } => {
586                    let font_path = path_join(&self.resource_path, font);
587                    let font_id = font_map[&font_path];
588                    let cid = context
589                        .register_fancy_console(FlexiConsole::init(*width, *height), font_id);
590                    context.set_translation_mode(cid, *translator);
591                }
592                ConsoleType::SpriteConsole {
593                    width,
594                    height,
595                    sprite_sheet,
596                } => {
597                    context.register_sprite_console(SpriteConsole::init(
598                        *width,
599                        *height,
600                        *sprite_sheet,
601                    ));
602                }
603            }
604        }
605
606        if self.advanced_input {
607            INPUT.lock().activate_event_queue();
608        }
609
610        Ok(context)
611    }
612}
613
614fn path_join(a: &str, b: &str) -> String {
615    use std::path::Path;
616    let path = Path::new(&a).join(&b);
617    path.to_str().unwrap().to_string()
618}