Skip to main content

endbasic_sdl/
lib.rs

1// EndBASIC
2// Copyright 2021 Julio Merino
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU Affero General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17//! SDL2-based graphics terminal emulator.
18
19use async_channel::Sender;
20use endbasic_std::Signal;
21use endbasic_std::console::{Console, ConsoleSpec, Resolution};
22use std::cell::RefCell;
23use std::fs::File;
24use std::io::{self, Write};
25use std::num::NonZeroU32;
26use std::path::PathBuf;
27use std::rc::Rc;
28use tempfile::TempDir;
29
30mod console;
31mod font;
32mod host;
33
34/// Default resolution to use when none is provided.
35const DEFAULT_RESOLUTION_PIXELS: (u32, u32) = (800, 600);
36
37/// Default font to use when none is provided.
38const DEFAULT_FONT_BYTES: &[u8] = include_bytes!("IBMPlexMono-Regular-6.0.0.ttf");
39
40/// Default font size.
41const DEFAULT_FONT_SIZE: u16 = 16;
42
43/// Converts a flat string error message to an `io::Error`.
44fn string_error_to_io_error(e: String) -> io::Error {
45    io::Error::other(e)
46}
47
48/// Context to maintain a font on disk temporarily.
49pub(crate) struct TempFont {
50    dir: TempDir,
51}
52
53impl TempFont {
54    /// Gets an instance of the default font.
55    pub(crate) fn default_font() -> io::Result<Self> {
56        let dir = tempfile::tempdir()?;
57        let mut file = File::create(dir.path().join("font.ttf"))?;
58        file.write_all(DEFAULT_FONT_BYTES)?;
59        Ok(Self { dir })
60    }
61
62    /// Gets the path to the temporary font.
63    pub(crate) fn path(&self) -> PathBuf {
64        self.dir.path().join("font.ttf")
65    }
66}
67
68/// Creates the graphical console based on the given `spec`.
69pub fn setup(
70    spec: &mut ConsoleSpec,
71    signals_tx: Sender<Signal>,
72) -> io::Result<Rc<RefCell<dyn Console>>> {
73    let resolution: Resolution = spec.take_keyed_flag("resolution")?.unwrap_or_else(|| {
74        let width = NonZeroU32::new(DEFAULT_RESOLUTION_PIXELS.0).unwrap();
75        let height = NonZeroU32::new(DEFAULT_RESOLUTION_PIXELS.1).unwrap();
76        Resolution::Windowed((width, height))
77    });
78
79    let default_fg_color = spec.take_keyed_flag::<u8>("fg_color")?;
80    let default_bg_color = spec.take_keyed_flag::<u8>("bg_color")?;
81
82    let font_path = spec.take_keyed_flag::<PathBuf>("font_path")?;
83
84    let font_size = spec.take_keyed_flag("font_size")?.unwrap_or(DEFAULT_FONT_SIZE);
85
86    let console = match font_path {
87        None => {
88            let default_font = TempFont::default_font()?;
89            console::SdlConsole::new(
90                resolution,
91                default_fg_color,
92                default_bg_color,
93                default_font.path(),
94                font_size,
95                signals_tx,
96            )?
97            // The console has been created at this point, so it should be safe to drop
98            // default_font and clean up the on-disk file backing it up.
99        }
100        Some(font_path) => console::SdlConsole::new(
101            resolution,
102            default_fg_color,
103            default_bg_color,
104            font_path.to_owned(),
105            font_size,
106            signals_tx,
107        )?,
108    };
109    Ok(Rc::from(RefCell::from(console)))
110}