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