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
10struct BuilderFont {
12 path: String,
13 dimensions: (u32, u32),
14 explicit_background: Option<RGB>,
15}
16
17enum 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
56pub 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 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 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 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 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 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 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 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 pub fn with_title<S: ToString>(mut self, title: S) -> Self {
271 self.title = Some(title.to_string());
272 self
273 }
274
275 pub fn with_resource_path<S: ToString>(mut self, path: S) -> Self {
278 self.resource_path = path.to_string();
279 self
280 }
281
282 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 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 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 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 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 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 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 #[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 #[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 pub fn with_vsync(mut self, vsync: bool) -> Self {
443 self.platform_hints.vsync = vsync;
444 self
445 }
446
447 pub fn with_fullscreen(mut self, fullscreen: bool) -> Self {
449 self.platform_hints.fullscreen = fullscreen;
450 self
451 }
452
453 pub fn with_fitscreen(mut self, fitscreen: bool) -> Self {
455 self.platform_hints.fitscreen = fitscreen;
456 self
457 }
458
459 pub fn with_platform_specific(mut self, hints: InitHints) -> Self {
461 self.platform_hints = hints;
462 self
463 }
464
465 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 pub fn with_advanced_input(mut self, advanced_input: bool) -> Self {
473 self.advanced_input = advanced_input;
474 self
475 }
476
477 #[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 #[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 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 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}