Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose bindings for the Playdate System Menu API #16

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 114 additions & 2 deletions src/system.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
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;

static mut SYSTEM: System = System(ptr::null_mut());

Expand Down Expand Up @@ -36,6 +40,70 @@ 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<MenuItem, Error> {
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<MenuItem, Error> {
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<MenuItem, Error> {
let c_title = CString::new(title).map_err(Error::msg)?;

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> {
let mut current: PDButtons = PDButtons(0);
let mut pushed: PDButtons = PDButtons(0);
Expand Down Expand Up @@ -95,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<String, Error> {
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<i32, Error> {
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<bool, Error> {
Ok(self.get_value()? == 1)
}

pub fn set_checked (&self, new_value: bool) -> Result<(), Error> {
self.set_value(if new_value { 1 } else { 0 })
}
}