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}