gb/
drawing.rs

1//! All Points Addressable (APA) mode drawing library.
2//! 
3//! This provies wrapper of `drawing.h` routines from GBDK.
4//!
5//! # Caution
6//! The GameBoy graphics hardware is not well suited to frame-buffer style
7//! graphics such as the kind provided in `drawing`. Due to that, most drawing
8//! functions will slow.
9//!
10//! When possible it's much faster and more efficient to work will the tiles
11//! and tiles maps that the GameBoy hardware is built around.
12//!
13//! We do not recommend using this function in Rust-GB.
14//!
15//! # Safety
16//! Due to the complex side effect of APA mode, `drawing` functions can cause
17//! unexpected issues. Most of expected issues are wrapped in Rust-GB, but it is
18//! your own risk to use this module.
19
20use core::{ffi::c_char, fmt::{Error, Write}};
21
22use super::gbdk_c::gb::{drawing::{self, r#box, circle, line, plot_point, M_FILL, M_NOFILL}, gb::{mode, M_DRAWING}};
23
24pub const SCREEN_WIDTH: u8 = drawing::GRAPHICS_WIDTH;
25pub const SCREEN_HEIGHT: u8 = drawing::GRAPHICS_WIDTH;
26pub const TILE_WIDTH: u8 = SCREEN_WIDTH / 8;
27pub const TILE_HEIGHT: u8 = SCREEN_HEIGHT / 8;
28
29/// Drawing mode of `drawing` functions.
30///
31/// Determines how each drawing function overwrites pixels that already drawn.
32///
33/// The constant value follows the value of GBDK `drawing.h`
34#[repr(u8)]
35#[derive(PartialEq, Clone, Copy)]
36pub enum DrawingMode {
37    /// 0x00, Overwrites the existing pixels
38    Solid = drawing::SOLID,
39
40    /// 0x01, Performs a logical OR
41    Or = drawing::OR,
42
43    /// 0x02, Performs a logical XOR
44    Xor = drawing::XOR,
45
46    /// 0x03, Performs a logical AND
47    And = drawing::AND
48}
49
50impl From<u8> for DrawingMode {
51    fn from(value: u8) -> Self {
52        match value {
53            drawing::SOLID => Self::Solid,
54            drawing::OR => Self::Or,
55            drawing::XOR => Self::Xor,
56            drawing::AND => Self::And,
57            _ => panic!("DrawingMode from u8 outbounded\0")
58        }
59    }
60}
61
62/// Color set of original GameBoy.
63/// 
64/// The constant value follows the value of GBDK `drawing.h`
65#[repr(u8)]
66#[derive(PartialEq, Clone, Copy)]
67pub enum DmgColor {
68    /// 0x00, WHITE color
69    White = drawing::WHITE,
70
71    /// 0x01, LTGREY color
72    LightGrey = drawing::LTGREY,
73
74    /// 0x02, DKGREY color
75    DarkGrey = drawing::DKGREY,
76
77    /// 0x03, BLACK color
78    Black = drawing::BLACK,
79}
80
81impl From<u8> for DmgColor {
82    fn from(value: u8) -> Self {
83        match value {
84            drawing::WHITE => Self::White,
85            drawing::LTGREY => Self::LightGrey,
86            drawing::DKGREY => Self::DarkGrey,
87            drawing::BLACK => Self::Black,
88            _ => panic!("DmgColor from u8 outbounded\0")
89        }
90    }
91}
92
93/// The style in how `drawing` functions work.
94///
95/// Corresponds to the `color` function of GBDK.
96///
97/// Specify a color combination similar to the builder pattern, and apply it 
98/// with a method `apply()`.
99///
100/// # Examples
101/// ```
102/// let mut w = unsafe {DrawingStream::new()};
103/// DrawingStyle::default()
104///     .foreground(DmgColor::LTGREY);
105///     .apply(&w);
106///
107/// w.set_style(DrawingStyle::reversed());
108/// ```
109#[derive(PartialEq, Clone, Copy)]
110pub struct DrawingStyle {
111    pub foreground: DmgColor,
112    pub background: DmgColor,
113    pub drawing_mode: DrawingMode,
114}
115
116impl Default for DrawingStyle {
117    /// Creates default `DrawingStyle`.
118    ///
119    /// Black drawings on a white background.
120    ///
121    /// Same as when GameBoy starts.
122    fn default() -> Self {
123        DrawingStyle {
124            foreground: DmgColor::Black, 
125            background: DmgColor::White, 
126            drawing_mode: DrawingMode::Solid
127        }
128    }
129}
130
131impl DrawingStyle {
132    /// Creates reversed `DrawingStyle`.
133    ///
134    /// Black drawings on a white background.
135    pub fn reversed() -> Self {
136        DrawingStyle {
137            foreground: DmgColor::White, 
138            background: DmgColor::Black, 
139            drawing_mode: DrawingMode::Solid
140        }
141    }
142
143    /// Set foreground of `DrawingStyle`.
144    pub fn foreground(&mut self, color: DmgColor) -> &mut Self {
145        self.foreground = color;
146        self
147    }
148
149    /// Set background of `DrawingStyle`.
150    pub fn background(&mut self, color: DmgColor) -> &mut Self {
151        self.background = color;
152        self
153    }
154
155    /// Set drawing mode of `DrawingStyle`.
156    pub fn drawing_mode(&mut self, mode: DrawingMode) -> &mut Self {
157        self.drawing_mode = mode;
158        self
159    }
160
161    /// Apply drawing style.
162    ///
163    /// DrawingStream needed as parameter to ensure GameBoy is in `APA` mode. 
164    pub fn apply(&self, stream: &DrawingStream) {
165        stream.set_style(*self);
166    }
167}
168
169/// Byte drawing stream of GameBoy.
170///
171/// It simillars with [`crate::io::GbStream`]. But there are some
172/// significant differences.
173///
174/// 1. `DrawingStream` uses `APA` mode drawing library of GBDK. this causes
175/// many side effects, For example, if you try to use `DrawingStream` inside a
176/// VBL interrupt, it will have unexpected result. For more detail, please refer
177/// [GBDK Docs](https://gbdk-2020.github.io/gbdk-2020/docs/api/drawing_8h.html#aa8abfd58ea514228abd69d8f6330e91d)
178/// 
179/// 2. Unable to change line with `\n`, this means, when you want to make a new
180/// line, you should use the [`DrawingStream::cursor`] function.
181///
182/// 3. `DrawingStream` can also draw shapes in addition to texts.
183///
184/// # Examples
185///
186/// ```
187/// let mut s = unsafe {DrawingStream::new()}; // prints to second line.
188/// write!(s, "Hello, APA!");
189/// ```
190pub struct DrawingStream {
191    private: ()
192}
193
194impl DrawingStream {
195    /// Creates new `DrawingStream`.
196    ///
197    /// Enable `APA` mode to draw texts.
198    ///
199    /// # Safety
200    ///
201    /// This will break [`crate::io::GbStream`].
202    ///
203    /// After this function, you cannot use GbStream dependent functions such as
204    /// `println!`.
205    pub unsafe fn new() -> Self {
206        unsafe { mode(M_DRAWING) };
207        DrawingStream { private: () }
208    }
209
210    /// Apply drawing style.
211    ///
212    /// Internally, call `color` function of GBDK.
213    pub fn set_style(&self, style: DrawingStyle) {
214        unsafe {
215            drawing::color(
216                style.foreground as u8,
217                style.background as u8,
218                style.drawing_mode as u8
219            )
220        }
221    }
222
223    /// Set cursor of [`DrawingStream`].
224    ///
225    /// # Panics
226    ///
227    /// Panics if coordinate parameter out of bounds.
228    ///
229    /// # Safety
230    /// 
231    /// Because of the bound check, it is guaranteed to move the cursor to a
232    /// valid range.
233    ///
234    /// # Examples
235    ///
236    /// ```
237    /// let mut s = unsafe {DrawingStream::new()};
238    /// DrawingStream::cursor(0, 1); //prints to second line.
239    /// write!(s, "Hello, Cursor!");
240    ///
241    /// ```
242    pub fn cursor(&self, x: u8, y: u8) {
243            if x >= TILE_WIDTH {
244                panic!("Cursor x outbounded");
245            }
246
247            if y >= TILE_HEIGHT {
248                panic!("Cursor y outbounded");
249            }
250
251            unsafe {drawing::gotogxy(x, y)}
252    }
253
254    fn panic_screen_bound(x: u8, y: u8)  {
255        if x >= SCREEN_WIDTH || y >= SCREEN_HEIGHT {
256            panic!("DrawingStream coordinate out of bounds");
257        }
258    }
259
260    /// Draw a point to screen.
261    ///
262    /// # Panics
263    /// 
264    /// Panics if coordinates out of bounds.
265    pub fn draw_point(&self, (x, y): (u8, u8)) {
266        Self::panic_screen_bound(x, y);
267
268        unsafe { plot_point(x, y) }
269    }
270
271    /// Draw a line to screen.
272    ///
273    /// # Panics
274    /// 
275    /// Panics if coordinates out of bounds.
276    pub fn draw_line(&self, (x1, y1): (u8, u8), (x2, y2): (u8, u8)) {
277        Self::panic_screen_bound(x1, y1);
278        Self::panic_screen_bound(x2, y2);
279
280        unsafe { line(x1, y1, x2, y2) }
281    }
282
283    /// Draw a box to screen.
284    ///
285    /// # Panics
286    /// 
287    /// Panics if coordinates out of bounds.
288    pub fn draw_box(&self, (x1, y1): (u8, u8), (x2, y2): (u8, u8), fill: bool) {
289        Self::panic_screen_bound(x1, y1);
290        Self::panic_screen_bound(x2, y2);
291
292        if fill {
293            unsafe { r#box(x1, y1, x2, y2, M_FILL) }
294        } else {
295            unsafe { r#box(x1, y1, x2, y2, M_NOFILL) }
296        }
297    }
298
299    /// Draw a circle to screen.
300    ///
301    /// # Panics
302    /// 
303    /// Panics if coordinates out of bounds.
304    pub fn draw_circle(&self, (x, y): (u8, u8), radius: u8, fill: bool) {
305        Self::panic_screen_bound(x, y);
306
307        if fill {
308            unsafe { circle(x, y, radius, M_FILL) }
309        } else {
310            unsafe { circle(x, y, radius, M_NOFILL) }
311        }
312    }
313
314    /// Writes a byte into this writer, returning whether the write succeeded.
315    ///
316    /// write_char assumes that the input is valid Unicode character. However,
317    /// GBDK maps one byte to one character or symbol.
318    ///
319    /// Therefore, `write_byte` is recommended when you want to print one
320    /// character to the GameBoy.
321    ///
322    /// # Errors
323    ///
324    /// This function will return an instance of `Error` on error.
325    pub fn write_byte(&mut self, b: u8) -> Result<(), Error> {
326        unsafe { drawing::wrtchr(b as c_char) }
327        Ok(())
328    }
329}
330
331impl Write for DrawingStream {
332    #[inline(never)]
333    fn write_str(&mut self, s: &str) -> core::fmt::Result {
334        for c in s.bytes() {
335            unsafe { drawing::wrtchr(c as c_char) }
336        }
337        Ok(())
338    }
339}
340