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, ConsoleFactory, ConsoleHost, ConsoleSpec, Resolution};
22use endbasic_std::gfx::lcd::fonts::{FONT_VGA8X16, Font, Fonts};
23use std::cell::RefCell;
24use std::io;
25use std::num::NonZeroU32;
26use std::rc::Rc;
27use std::sync::mpsc::{self, Receiver, SyncSender};
28
29mod console;
30use console::SdlConsole;
31#[cfg(feature = "testutils")]
32pub use console::testutils;
33
34mod host;
35use host::SdlConsoleHost;
36
37/// Default resolution to use when none is provided.
38const DEFAULT_RESOLUTION_PIXELS: (u32, u32) = (800, 600);
39
40/// Converts a flat string error message to an `io::Error`.
41fn string_error_to_io_error(e: String) -> io::Error {
42    io::Error::other(e)
43}
44
45/// Console factory for an SDL-backed graphical console.
46struct SdlConsoleFactory {
47    request_tx: SyncSender<host::Request>,
48    response_rx: Receiver<host::Response>,
49    on_key_rx: Receiver<endbasic_std::console::Key>,
50}
51
52impl SdlConsoleFactory {
53    /// Creates a new SDL console factory from prewired channels.
54    fn new(
55        request_tx: SyncSender<host::Request>,
56        response_rx: Receiver<host::Response>,
57        on_key_rx: Receiver<endbasic_std::console::Key>,
58    ) -> Self {
59        Self { request_tx, response_rx, on_key_rx }
60    }
61
62    /// Constructs the SDL console.
63    fn build_console(self) -> io::Result<SdlConsole> {
64        SdlConsole::new(self.request_tx, self.response_rx, self.on_key_rx)
65    }
66}
67
68impl ConsoleFactory for SdlConsoleFactory {
69    fn build(self: Box<Self>) -> io::Result<Rc<RefCell<dyn Console>>> {
70        let console = self.build_console()?;
71        Ok(Rc::from(RefCell::from(console)))
72    }
73}
74
75/// Creates a new SDL console host and a factory for the client.
76pub(crate) fn new_console_pair(
77    resolution: Resolution,
78    default_fg_color: Option<u8>,
79    default_bg_color: Option<u8>,
80    font: &'static Font,
81    signals_tx: Sender<Signal>,
82) -> (SdlConsoleHost, SdlConsoleFactory) {
83    let (request_tx, request_rx) = mpsc::sync_channel(1);
84    let (response_tx, response_rx) = mpsc::sync_channel(1);
85    let (on_key_tx, on_key_rx) = mpsc::channel();
86    let host = SdlConsoleHost {
87        resolution,
88        default_fg_color,
89        default_bg_color,
90        font,
91        request_rx,
92        response_tx,
93        on_key_tx,
94        signals_tx,
95    };
96    let factory = SdlConsoleFactory::new(request_tx, response_rx, on_key_rx);
97    (host, factory)
98}
99
100/// Creates an SDL console host and factory based on the given `spec`.
101pub fn setup(
102    spec: &mut ConsoleSpec<'_>,
103    signals_tx: Sender<Signal>,
104    fonts: &Fonts,
105) -> io::Result<(Box<dyn ConsoleHost>, Box<dyn ConsoleFactory>)> {
106    let resolution: Resolution = spec.take_keyed_flag("resolution")?.unwrap_or_else(|| {
107        let width = NonZeroU32::new(DEFAULT_RESOLUTION_PIXELS.0).unwrap();
108        let height = NonZeroU32::new(DEFAULT_RESOLUTION_PIXELS.1).unwrap();
109        Resolution::Windowed((width, height))
110    });
111
112    let default_fg_color = spec.take_keyed_flag::<u8>("fg_color")?;
113    let default_bg_color = spec.take_keyed_flag::<u8>("bg_color")?;
114
115    let font_name = spec.take_keyed_flag_str("font").unwrap_or(FONT_VGA8X16.name);
116    let font = fonts.get(font_name)?;
117
118    let (host, factory) =
119        new_console_pair(resolution, default_fg_color, default_bg_color, font, signals_tx);
120    Ok((Box::from(host), Box::from(factory)))
121}