craydate/graphics/graphics.rs
1use core::ffi::c_void;
2use core::ptr::NonNull;
3
4use super::active_font::ActiveFont;
5use super::bitmap::{Bitmap, BitmapRef};
6use super::bitmap_collider::BitmapCollider;
7use super::color::Color;
8use super::context_stack::ContextStackId;
9use super::font::Font;
10use super::framebuffer_stencil_bitmap::FramebufferStencilBitmap;
11#[cfg(not(all(target_arch = "arm", target_os = "none")))]
12use super::unowned_bitmap::UnownedBitmapMut;
13use crate::capi_state::CApiState;
14use crate::ctypes::*;
15use crate::null_terminated::ToNullTerminatedString;
16use crate::system::System;
17
18/// Access to drawing functions to draw to the Playdate device's screen.
19#[derive(Debug)]
20#[non_exhaustive]
21pub struct Graphics;
22impl Graphics {
23 pub(crate) fn new() -> Self {
24 Graphics
25 }
26
27 /// Test if the opaque pixels of two bitmaps overlap.
28 ///
29 /// The overlap testing is limited to witin the `in_rect`, and each bitmap has a position
30 /// specified in its `BitmapCollider` which may move them relative to the `in_rect`.
31 pub fn bitmaps_collide(
32 &self,
33 a: BitmapCollider,
34 b: BitmapCollider,
35 in_rect: euclid::default::Rect<i32>,
36 ) -> bool {
37 unsafe {
38 // checkMaskCollision expects `*mut CLCDBitmap` but it only reads from the bitmaps to check
39 // for collision.
40 Self::fns().checkMaskCollision.unwrap()(
41 a.bitmap.cptr() as *mut _,
42 a.x,
43 a.y,
44 a.flipped,
45 b.bitmap.cptr() as *mut _,
46 b.x,
47 b.y,
48 b.flipped,
49 super::craydate_rect_from_euclid(in_rect),
50 ) != 0
51 }
52 }
53
54 /// Clears the entire display, filling it with `color`.
55 pub fn clear<'a, C: Into<Color<'a>>>(&mut self, color: C) {
56 unsafe {
57 Self::fns().clear.unwrap()(color.into().to_c_color());
58 }
59 }
60
61 /// Sets the background color shown when the display is offset or for clearing dirty areas
62 /// in the sprite system.
63 pub fn set_background_color(&mut self, color: SolidColor) {
64 unsafe {
65 Self::fns().setBackgroundColor.unwrap()(color);
66 }
67 }
68
69 /// Manually flushes the current frame buffer out to the display. This function is automatically
70 /// called at the end of each frame, after yielding back to the Playdate system through the
71 /// `SystemEventWatcher`, so there shouldn’t be any need to call it yourself.
72 pub fn display(&mut self) {
73 unsafe {
74 Self::fns().display.unwrap()();
75 }
76 }
77
78 /// Returns the debug framebuffer as a bitmap.
79 ///
80 /// Only valid in the simulator, so not present in for-device builds.
81 #[cfg(not(all(target_arch = "arm", target_os = "none")))]
82 pub fn debug_frame_bitmap(&self) -> UnownedBitmapMut<'static> {
83 let bitmap_ptr = unsafe { Self::fns().getDebugBitmap.unwrap()() };
84 UnownedBitmapMut::from_ptr(NonNull::new(bitmap_ptr).unwrap())
85 }
86
87 /// Returns a copy of the contents of the display front buffer.
88 ///
89 /// The Playdate device is double-buffered, and this returns the currently displayed frame.
90 pub fn display_frame_bitmap(&self) -> Bitmap {
91 let bitmap_ptr = unsafe { Self::fns().getDisplayBufferBitmap.unwrap()() };
92 use alloc::borrow::ToOwned;
93 BitmapRef::from_ptr(NonNull::new(bitmap_ptr).unwrap()).to_owned()
94 }
95
96 /// Returns a copy the contents of the working frame buffer as a bitmap.
97 ///
98 /// The Playdate device is double-buffered, and this returns the buffer that will be displayed
99 /// next frame.
100 pub fn working_frame_bitmap(&self) -> Bitmap {
101 let bitmap_ptr = unsafe { Self::fns().copyFrameBufferBitmap.unwrap()() };
102 Bitmap::from_owned_ptr(NonNull::new(bitmap_ptr).unwrap())
103 }
104
105 /// After updating pixels in the buffer returned by `get_frame()`, you must tell the graphics
106 /// system which rows were updated. This function marks a contiguous range of rows as updated
107 /// (e.g., `mark_updated_rows(0, LCD_ROWS - 1)` tells the system to update the entire display).
108 /// Both "start" and "end" are included in the range.
109 pub fn mark_updated_rows(&mut self, start: i32, end: i32) {
110 unsafe { Self::fns().markUpdatedRows.unwrap()(start, end) }
111 }
112
113 /// Offsets the origin point for all drawing calls to x, y (can be negative).
114 pub fn set_draw_offset(&mut self, dx: i32, dy: i32) {
115 unsafe { Self::fns().setDrawOffset.unwrap()(dx, dy) }
116 }
117
118 /// Push a new drawing context that targets the display framebuffer.
119 ///
120 /// Drawing functions use a context stack to select the drawing target, for setting a stencil,
121 /// changing the draw mode, etc. The stack is unwound at the beginning of each update cycle, with
122 /// drawing restored to target the display framebuffer.
123 pub fn push_context(&mut self) {
124 CApiState::get().stack.borrow_mut().push_framebuffer()
125 }
126 /// Push a drawing context that targets a bitmap.
127 ///
128 /// Drawing functions use a context stack to select the drawing target, for setting a stencil,
129 /// changing the draw mode, etc. The stack is unwound at the beginning of each update cycle, with
130 /// drawing restored to target the display framebuffer.
131 ///
132 /// When the bitmap's drawing is popped, either by calling pop_context() or at the end of the
133 /// frame, it will be kept alive as long as the ContextStackId returned here (or a clone of it) is
134 /// kept alive.
135 pub fn push_context_bitmap(&mut self, bitmap: Bitmap) -> ContextStackId {
136 CApiState::get().stack.borrow_mut().push_bitmap(bitmap)
137 }
138 /// Pop the top (most recently pushed, and not yet popped) drawing context from the stack.
139 ///
140 /// Drawing functions use a context stack to select the drawing target, for setting a stencil,
141 /// changing the draw mode, etc. The stack is unwound at the beginning of each update cycle, with
142 /// drawing restored to target the display framebuffer.
143 ///
144 /// The returned ContextStackId, if present, can be used to get back the Bitmap that was drawn
145 /// into for the popped drawing context. A ContextStackId is not returned if the popped drawing
146 /// context was drawing into the display framebuffer.
147 pub fn pop_context(&mut self) -> Option<ContextStackId> {
148 CApiState::get().stack.borrow_mut().pop(CApiState::get())
149 }
150 /// Retrieve an Bitmap that was pushed into a drawing context with push_context_bitmap() and
151 /// since popped off the stack, either with pop_context() or at the end of the frame.
152 pub fn take_popped_context_bitmap(&mut self, id: ContextStackId) -> Option<Bitmap> {
153 CApiState::get().stack.borrow_mut().take_bitmap(id)
154 }
155
156 /// Sets the stencil used for drawing.
157 ///
158 /// If the image is smaller than full screen, its width should be a multiple of 32 pixels.
159 /// Stencils smaller than full screen will be tiled.
160 ///
161 /// The bitmap will remain the stencil as long as the FramebufferStencilBitmap is not dropped, or another
162 /// call to set_stencil() is made.
163 pub fn set_stencil<'a>(&mut self, bitmap: &'a BitmapRef) -> FramebufferStencilBitmap<'a> {
164 // setStencil() takes a mutable pointer to a bitmap, but it only reads from the bitmap (in order
165 // to perform stenciling).
166 unsafe { Self::fns().setStencil.unwrap()(bitmap.cptr() as *mut _) }
167 FramebufferStencilBitmap::new(bitmap)
168 }
169
170 /// Sets the font used for drawing.
171 ///
172 /// The font will remain active for drawing as long as the ActiveFont is not dropped, or another
173 /// call to set_font() is made.
174 pub fn set_font<'a>(&mut self, font: &'a Font) -> ActiveFont<'a> {
175 // setFont() takes a mutable pointer but does not write to the data.
176 unsafe { Self::fns().setFont.unwrap()(font.cptr() as *mut _) }
177 ActiveFont::new(font)
178 }
179
180 /// Sets the current clip rect, using world coordinates—that is, the given rectangle will be
181 /// translated by the current drawing offset.
182 ///
183 /// The clip rect is cleared at the beginning of each frame.
184 pub fn set_clip_rect(&mut self, rect: euclid::default::Rect<i32>) {
185 unsafe {
186 Self::fns().setClipRect.unwrap()(
187 rect.origin.x,
188 rect.origin.y,
189 rect.size.width,
190 rect.size.height,
191 )
192 }
193 }
194 /// Sets the current clip rect in screen coordinates.
195 ///
196 /// The clip rect is cleared at the beginning of each frame.
197 pub fn set_screen_clip_rect(&mut self, rect: euclid::default::Rect<i32>) {
198 unsafe {
199 Self::fns().setScreenClipRect.unwrap()(
200 rect.origin.x,
201 rect.origin.y,
202 rect.size.width,
203 rect.size.height,
204 )
205 }
206 }
207
208 /// Sets the mode used for drawing bitmaps. Note that text drawing uses bitmaps, so this
209 /// affects how fonts are displayed as well.
210 pub fn set_draw_mode(&mut self, mode: BitmapDrawMode) {
211 unsafe { Self::fns().setDrawMode.unwrap()(mode) }
212 }
213
214 /// Draws the bitmap to the screen.
215 ///
216 /// The bitmap's upper-left corner is positioned at location (`x`, `y`), and the contents have
217 /// the `flip` orientation applied.
218 pub fn draw_bitmap(&mut self, bitmap: &BitmapRef, x: i32, y: i32, flip: BitmapFlip) {
219 // drawBitmap() takes a mutable pointer to a bitmap, but it only reads from the bitmap.
220 unsafe { Self::fns().drawBitmap.unwrap()(bitmap.cptr() as *mut _, x, y, flip) }
221 }
222
223 /// Draws the bitmap to the screen, scaled by `xscale` and `yscale`.
224 ///
225 /// /// The bitmap's upper-left corner is positioned at location (`x`, `y`). Note that flip is not
226 /// available when drawing scaled bitmaps but negative scale values will achieve the same effect.
227 pub fn draw_scaled_bitmap(
228 &mut self,
229 bitmap: &BitmapRef,
230 x: i32,
231 y: i32,
232 xscale: f32,
233 yscale: f32,
234 ) {
235 // drawScaledBitmap() takes a mutable pointer to a bitmap, but it only reads from the bitmap.
236 unsafe { Self::fns().drawScaledBitmap.unwrap()(bitmap.cptr() as *mut _, x, y, xscale, yscale) }
237 }
238
239 /// Draws the bitmap to the screen, scaled by `xscale` and `yscale` then rotated by `degrees` with
240 /// its center as given by proportions `centerx` and `centery` at (`x`, `y`); that is: if
241 /// `centerx` and `centery` are both 0.5 the center of the image is at (`x`, `y`), if `centerx`
242 /// and `centery` are both 0 the top left corner of the image (before rotation) is at (`x`, `y`),
243 /// etc.
244 pub fn draw_rotated_bitmap(
245 &mut self,
246 bitmap: &BitmapRef,
247 x: i32,
248 y: i32,
249 degrees: f32,
250 centerx: f32,
251 centery: f32,
252 xscale: f32,
253 yscale: f32,
254 ) {
255 unsafe {
256 // drawRotatedBitmap() takes a mutable pointer to a bitmap, but it only reads from the bitmap.
257 Self::fns().drawRotatedBitmap.unwrap()(
258 bitmap.cptr() as *mut _,
259 x,
260 y,
261 degrees,
262 centerx,
263 centery,
264 xscale,
265 yscale,
266 )
267 }
268 }
269
270 /// Draws the bitmap to the screen with its upper-left corner at location (`x`, `y`) tiled inside
271 /// a `width` by `height` rectangle.
272 pub fn draw_tiled_bitmap(
273 &mut self,
274 bitmap: &BitmapRef,
275 x: i32,
276 y: i32,
277 width: i32,
278 height: i32,
279 flip: BitmapFlip,
280 ) {
281 // tileBitmap() takes a mutable pointer to a bitmap, but it only reads from the bitmap.
282 unsafe { Self::fns().tileBitmap.unwrap()(bitmap.cptr() as *mut _, x, y, width, height, flip) }
283 }
284
285 // BUG: Bitmap tables are incomplete in the C Api so we've omitted them. The C Api functions that
286 // do exist and are ommitted are:
287 // - getTableBitmap
288 // - loadBitmapTable
289 // - loadIntoBitmapTable
290 // - newBitmapTable
291
292 /// Draw a text string on the screen at the given (`x`, `y`) coordinates.
293 ///
294 /// If no font has been set with `Graphics::set_font()`, the default system font "Asheville Sans
295 /// 14 Light" is used.
296 pub fn draw_text(&mut self, text: &str, x: i32, y: i32) {
297 let null_term = text.to_null_terminated_utf8();
298 let ptr = null_term.as_ptr() as *const c_void;
299 let len = null_term.len() as u64;
300 let r =
301 unsafe { Self::fns().drawText.unwrap()(ptr, len, CStringEncoding::kUTF8Encoding, x, y) };
302 assert!(r != 0)
303 }
304
305 /// Draws the current FPS on the screen at the given (`x`, `y`) coordinates.
306 pub fn draw_fps(&mut self, x: i32, y: i32) {
307 // This function is part of CSystemApi, not CGraphicsApi, but it's a function that draws
308 // something to the screen, so its behaviour is more clear when part of the Graphics type.
309 unsafe { System::fns().drawFPS.unwrap()(x, y) }
310 }
311
312 /// Draws an ellipse inside the rectangle of width `line_width` (inset from the rectangle bounds).
313 ///
314 /// If `start_deg != end_deg`, this draws an arc between the given angles. Angles are given in
315 /// degrees, clockwise from due north.
316 pub fn draw_elipse<'a>(
317 &mut self,
318 rect: euclid::default::Rect<i32>,
319 line_width: i32,
320 start_deg: f32,
321 end_deg: f32,
322 color: Color<'a>,
323 ) {
324 unsafe {
325 Self::fns().drawEllipse.unwrap()(
326 rect.origin.x,
327 rect.origin.y,
328 rect.size.width,
329 rect.size.height,
330 line_width,
331 start_deg,
332 end_deg,
333 color.to_c_color(),
334 )
335 }
336 }
337 /// Fills an ellipse inside the rectangle.
338 ///
339 /// If `start_deg != end_deg`, this draws an arc between the given angles. Angles are given in
340 /// degrees, clockwise from due north.
341 pub fn fill_elipse<'a>(
342 &mut self,
343 rect: euclid::default::Rect<i32>,
344 start_deg: f32,
345 end_deg: f32,
346 color: Color<'a>,
347 ) {
348 unsafe {
349 Self::fns().fillEllipse.unwrap()(
350 rect.origin.x,
351 rect.origin.y,
352 rect.size.width,
353 rect.size.height,
354 start_deg,
355 end_deg,
356 color.to_c_color(),
357 )
358 }
359 }
360 /// Draws a line from `p1` to `p2` with a stroke width of `width`.
361 pub fn draw_line<'a>(
362 &mut self,
363 p1: euclid::default::Point2D<i32>,
364 p2: euclid::default::Point2D<i32>,
365 line_width: i32,
366 color: Color<'a>,
367 ) {
368 unsafe { Self::fns().drawLine.unwrap()(p1.x, p1.y, p2.x, p2.y, line_width, color.to_c_color()) }
369 }
370 /// Draws a `rect`.
371 pub fn draw_rect<'a>(&mut self, r: euclid::default::Rect<i32>, color: Color<'a>) {
372 unsafe {
373 Self::fns().drawRect.unwrap()(
374 r.origin.x,
375 r.origin.y,
376 r.size.width,
377 r.size.height,
378 color.to_c_color(),
379 )
380 }
381 }
382 /// Draws a filled `rect`.
383 pub fn fill_rect<'a>(&mut self, r: euclid::default::Rect<i32>, color: Color<'a>) {
384 unsafe {
385 Self::fns().fillRect.unwrap()(
386 r.origin.x,
387 r.origin.y,
388 r.size.width,
389 r.size.height,
390 color.to_c_color(),
391 )
392 }
393 }
394 /// Draws a filled triangle with points at `p1`, `p2`, and `p3`.
395 pub fn fill_triangle<'a>(
396 &mut self,
397 p1: euclid::default::Point2D<i32>,
398 p2: euclid::default::Point2D<i32>,
399 p3: euclid::default::Point2D<i32>,
400 color: Color<'a>,
401 ) {
402 unsafe {
403 Self::fns().fillTriangle.unwrap()(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, color.to_c_color())
404 }
405 }
406 /// Fills the polygon with vertices at the given coordinates (an array of points) using the given
407 /// color and fill, or winding, rule.
408 ///
409 /// See <https://en.wikipedia.org/wiki/Nonzero-rule> for an explanation of the winding rule.
410 pub fn fill_polygon<'a>(
411 &mut self,
412 points: &[euclid::default::Point2D<i32>],
413 color: Color<'a>,
414 fill_rule: PolygonFillRule,
415 ) {
416 // Point2D is a #[repr(C)] struct of x, y. It's alignment will be the same as i32, so an
417 // array of Point2D can be treated as an array of i32 with x and y alternating.
418 unsafe {
419 Self::fns().fillPolygon.unwrap()(
420 points.len() as i32,
421 points.as_ptr() as *mut i32,
422 color.to_c_color(),
423 fill_rule,
424 )
425 }
426 }
427
428 pub(crate) fn fns() -> &'static craydate_sys::playdate_graphics {
429 CApiState::get().cgraphics
430 }
431}