Skip to main content

endbasic_sdl/
lib.rs

1// EndBASIC
2// Copyright 2021 Julio Merino
3//
4// Licensed under the Apache License, Version 2.0 (the "License"); you may not
5// use this file except in compliance with the License.  You may obtain a copy
6// of the License at:
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
13// License for the specific language governing permissions and limitations
14// under the License.
15
16//! SDL2-based graphics terminal emulator.
17
18use async_channel::Sender;
19use endbasic_core::exec::Signal;
20use endbasic_std::console::{Console, ConsoleSpec, Resolution};
21use std::cell::RefCell;
22use std::fs::File;
23use std::io::{self, Write};
24use std::num::NonZeroU32;
25use std::path::PathBuf;
26use std::rc::Rc;
27use tempfile::TempDir;
28
29mod console;
30mod font;
31mod host;
32
33/// Default resolution to use when none is provided.
34const DEFAULT_RESOLUTION_PIXELS: (u32, u32) = (800, 600);
35
36/// Default font to use when none is provided.
37const DEFAULT_FONT_BYTES: &[u8] = include_bytes!("IBMPlexMono-Regular-6.0.0.ttf");
38
39/// Default font size.
40const DEFAULT_FONT_SIZE: u16 = 16;
41
42/// Converts a flat string error message to an `io::Error`.
43fn string_error_to_io_error(e: String) -> io::Error {
44    io::Error::other(e)
45}
46
47/// Context to maintain a font on disk temporarily.
48pub(crate) struct TempFont {
49    dir: TempDir,
50}
51
52impl TempFont {
53    /// Gets an instance of the default font.
54    pub(crate) fn default_font() -> io::Result<Self> {
55        let dir = tempfile::tempdir()?;
56        let mut file = File::create(dir.path().join("font.ttf"))?;
57        file.write_all(DEFAULT_FONT_BYTES)?;
58        Ok(Self { dir })
59    }
60
61    /// Gets the path to the temporary font.
62    pub(crate) fn path(&self) -> PathBuf {
63        self.dir.path().join("font.ttf")
64    }
65}
66
67/// Creates the graphical console based on the given `spec`.
68pub fn setup(
69    spec: &mut ConsoleSpec,
70    signals_tx: Sender<Signal>,
71) -> io::Result<Rc<RefCell<dyn Console>>> {
72    let resolution: Resolution = spec.take_keyed_flag("resolution")?.unwrap_or_else(|| {
73        let width = NonZeroU32::new(DEFAULT_RESOLUTION_PIXELS.0).unwrap();
74        let height = NonZeroU32::new(DEFAULT_RESOLUTION_PIXELS.1).unwrap();
75        Resolution::Windowed((width, height))
76    });
77
78    let default_fg_color = spec.take_keyed_flag::<u8>("fg_color")?;
79    let default_bg_color = spec.take_keyed_flag::<u8>("bg_color")?;
80
81    let font_path = spec.take_keyed_flag::<PathBuf>("font_path")?;
82
83    let font_size = spec.take_keyed_flag("font_size")?.unwrap_or(DEFAULT_FONT_SIZE);
84
85    let console = match font_path {
86        None => {
87            let default_font = TempFont::default_font()?;
88            console::SdlConsole::new(
89                resolution,
90                default_fg_color,
91                default_bg_color,
92                default_font.path(),
93                font_size,
94                signals_tx,
95            )?
96            // The console has been created at this point, so it should be safe to drop
97            // default_font and clean up the on-disk file backing it up.
98        }
99        Some(font_path) => console::SdlConsole::new(
100            resolution,
101            default_fg_color,
102            default_bg_color,
103            font_path.to_owned(),
104            font_size,
105            signals_tx,
106        )?,
107    };
108    Ok(Rc::from(RefCell::from(console)))
109}