From aecfa1f8970801c7d6def3442b4e6914b485d32c Mon Sep 17 00:00:00 2001 From: Adam Soutar Date: Sun, 24 Apr 2022 16:17:56 +0100 Subject: [PATCH 1/3] Expose the `set_draw_mode` function This is necessary for drawing things like text on black backgrounds (inverted draw mode) --- src/graphics.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/graphics.rs b/src/graphics.rs index 42fe95c..0756dd3 100644 --- a/src/graphics.rs +++ b/src/graphics.rs @@ -415,6 +415,10 @@ impl Graphics { Self::get().0 } + pub fn set_draw_mode(&self, draw_mode: LCDBitmapDrawMode) -> Result<(), Error> { + pd_func_caller!((*self.0).setDrawMode, draw_mode) + } + pub fn get_frame(&self) -> Result<&'static mut [u8], Error> { let ptr = pd_func_caller!((*self.0).getFrame)?; anyhow::ensure!( From 50e231e45a9c6c2bd35b05514917e9ae0844627d Mon Sep 17 00:00:00 2001 From: Adam Soutar Date: Sun, 24 Apr 2022 18:02:01 +0100 Subject: [PATCH 2/3] Provide bindings for addMenuItem This allows Rust code to provide Playdate Sytem Menu options --- src/system.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/system.rs b/src/system.rs index 218efd7..aa2e1ac 100644 --- a/src/system.rs +++ b/src/system.rs @@ -8,6 +8,7 @@ use { }; pub use crankstart_sys::PDButtons; +pub use crankstart_sys::PDMenuItem; static mut SYSTEM: System = System(ptr::null_mut()); @@ -36,6 +37,11 @@ impl System { pd_func_caller!((*self.0).setUpdateCallback, f, ptr::null_mut()) } + pub fn add_menu_item(&self, title: &str, f: crankstart_sys::PDMenuItemCallbackFunction) -> Result<*mut PDMenuItem, Error> { + let c_title = CString::new(title).map_err(Error::msg)?; + pd_func_caller!((*self.0).addMenuItem, c_title.as_ptr(), f, ptr::null_mut()) + } + pub fn get_button_state(&self) -> Result<(PDButtons, PDButtons, PDButtons), Error> { let mut current: PDButtons = PDButtons(0); let mut pushed: PDButtons = PDButtons(0); @@ -83,6 +89,14 @@ impl System { Ok(pd_func_caller!((*self.0).getCurrentTimeMilliseconds)? as usize) } + pub fn reset_elapsed_time(&self) -> Result<(), Error> { + pd_func_caller!((*self.0).resetElapsedTime) + } + + pub fn get_elapsed_time(&self) -> Result { + Ok(pd_func_caller!((*self.0).getElapsedTime)? as f32) + } + pub fn draw_fps(&self, x: i32, y: i32) -> Result<(), Error> { pd_func_caller!((*self.0).drawFPS, x, y) } From dacb2bb0a8d37f8d1e7f4aca1b8e09e99ff065bd Mon Sep 17 00:00:00 2001 From: Adam Soutar Date: Sun, 24 Apr 2022 23:17:15 +0100 Subject: [PATCH 3/3] Implement a more full MenuItem API This allows you to interact with the Playdate System Menu API providing standard, checkmark, and options items as well as performing actions on them after creation. --- src/system.rs | 114 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 110 insertions(+), 4 deletions(-) diff --git a/src/system.rs b/src/system.rs index aa2e1ac..d6bb6d4 100644 --- a/src/system.rs +++ b/src/system.rs @@ -1,12 +1,15 @@ use { crate::pd_func_caller, - alloc::format, + alloc::{format, string::String, vec::Vec}, anyhow::Error, - core::ptr, + core::{ptr, convert::TryInto}, crankstart_sys::{ctypes::c_void, size_t}, + cstr_core::CStr, cstr_core::CString, }; +use core::mem; + pub use crankstart_sys::PDButtons; pub use crankstart_sys::PDMenuItem; @@ -37,9 +40,68 @@ impl System { pd_func_caller!((*self.0).setUpdateCallback, f, ptr::null_mut()) } - pub fn add_menu_item(&self, title: &str, f: crankstart_sys::PDMenuItemCallbackFunction) -> Result<*mut PDMenuItem, Error> { + pub fn add_menu_item(&self, title: &str, f: crankstart_sys::PDMenuItemCallbackFunction) -> Result { + let c_title = CString::new(title).map_err(Error::msg)?; + let item = pd_func_caller!( + (*self.0).addMenuItem, + c_title.as_ptr(), + f, + ptr::null_mut() + )?; + Ok(MenuItem(item)) + } + + pub fn add_checkmark_menu_item(&self, title: &str, initially_checked: bool, f: crankstart_sys::PDMenuItemCallbackFunction) -> Result { + let c_title = CString::new(title).map_err(Error::msg)?; + let item = pd_func_caller!( + (*self.0).addCheckmarkMenuItem, + c_title.as_ptr(), + if initially_checked { 1 } else { 0 }, + f, + ptr::null_mut() + )?; + Ok(MenuItem(item)) + } + + pub fn add_options_menu_item(&self, title: &str, options: Vec<&str>, f: crankstart_sys::PDMenuItemCallbackFunction) -> Result { let c_title = CString::new(title).map_err(Error::msg)?; - pd_func_caller!((*self.0).addMenuItem, c_title.as_ptr(), f, ptr::null_mut()) + + let mut c_options = Vec::with_capacity(options.len()); + for option in options { + let c_option = CString::new(option).map_err(Error::msg)?; + let c_option_ptr = c_option.as_ptr(); + // Here, we need to forget our values or they won't live long enough + // for Playdate OS to use them + mem::forget(c_option); + c_options.push( + c_option_ptr + ) + } + + let opt_ptr = c_options.as_mut_ptr(); + let opt_len = c_options.len(); + let opt_len_i32: i32 = opt_len.try_into().map_err(Error::msg)?; + + let item = pd_func_caller!( + (*self.0).addOptionsMenuItem, + c_title.as_ptr(), + opt_ptr, + opt_len_i32, + f, + ptr::null_mut() + )?; + + // After the call, we manually drop our forgotten values so as to not + // leak memory. + for c_option in c_options { + mem::drop(c_option); + } + + Ok(MenuItem(item)) + } + + pub fn remove_all_menu_items (&self) -> Result<(), Error> { + pd_func_caller!((*self.0).removeAllMenuItems) } pub fn get_button_state(&self) -> Result<(PDButtons, PDButtons, PDButtons), Error> { @@ -101,3 +163,47 @@ impl System { pd_func_caller!((*self.0).drawFPS, x, y) } } + +#[derive(Clone, Debug)] +pub struct MenuItem(*mut crankstart_sys::PDMenuItem); + +impl MenuItem { + pub fn remove (&self) -> Result<(), Error> { + let system = System::get(); + pd_func_caller!((*system.0).removeMenuItem, self.0) + } + + pub fn get_title (&self) -> Result { + let system = System::get(); + let c_title = pd_func_caller!((*system.0).getMenuItemTitle, self.0)?; + let title = unsafe { + CStr::from_ptr(c_title).to_string_lossy().into_owned() + }; + Ok(title) + } + + pub fn set_title (&self, new_title: &str) -> Result<(), Error> { + let system = System::get(); + let c_title = CString::new(new_title).map_err(Error::msg)?; + pd_func_caller!((*system.0).setMenuItemTitle, self.0, c_title.as_ptr()) + } + + pub fn get_value (&self) -> Result { + let system = System::get(); + pd_func_caller!((*system.0).getMenuItemValue, self.0) + } + + pub fn set_value (&self, new_value: i32) -> Result<(), Error> { + let system = System::get(); + pd_func_caller!((*system.0).setMenuItemValue, self.0, new_value) + } + + // For checkmark menu items + pub fn get_checked (&self) -> Result { + Ok(self.get_value()? == 1) + } + + pub fn set_checked (&self, new_value: bool) -> Result<(), Error> { + self.set_value(if new_value { 1 } else { 0 }) + } +}