1#![cfg_attr(not(test), no_std)]
2
3#[macro_use]
4extern crate sys;
5extern crate alloc;
6
7pub mod error;
8
9
10use alloc::borrow::Cow;
11use alloc::boxed::Box;
12use alloc::ffi::NulError;
13use core::ffi::c_int;
14use core::ffi::c_char;
15use core::ffi::c_void;
16use core::marker::PhantomData;
17use sys::ffi::PDMenuItemCallbackFunction;
18use sys::ffi::PDMenuItem;
19use sys::ffi::CStr;
20use sys::ffi::CString;
21
22use error::{Error, ApiError};
23
24
25pub type SimpleMenuItem<UserData = (), Api = api::Default, const REMOVE_ON_DROP: bool = true> =
26 MenuItem<kind::Simple, UserData, Api, REMOVE_ON_DROP>;
27
28pub type CheckMenuItem<UserData = (), Api = api::Default, const REMOVE_ON_DROP: bool = true> =
29 MenuItem<kind::Check, UserData, Api, REMOVE_ON_DROP>;
30
31pub type OptionsMenuItem<UserData = (), Api = api::Default, const REMOVE_ON_DROP: bool = true> =
32 MenuItem<kind::Options, UserData, Api, REMOVE_ON_DROP>;
33
34
35#[cfg_attr(feature = "bindings-derive-debug", derive(Debug))]
36pub struct MenuItem<Kind, UserData = (), Api= api::Default, const REMOVE_ON_DROP: bool = true>(*mut PDMenuItem, Api, PhantomData<Kind>, PhantomData<UserData>) where Kind: kind::Kind, UserData: Into<Box<UserData>>, Api: api::Api;
37
38
39impl<UD, K: kind::Kind, Api: api::Api, const REM: bool> MenuItem<K, UD, Api, REM> {
40 #[doc(alias = "sys::ffi::playdate_sys::getMenuItemTitle")]
44 pub fn title(&self) -> Cow<'_, str> {
45 let f = self.1.get_menu_item_title();
46 unsafe { CStr::from_ptr(f(self.0) as _) }.to_string_lossy()
47 }
48
49 #[doc(alias = "sys::ffi::playdate_sys::setMenuItemTitle")]
53 pub fn set_title<S: AsRef<str>>(&self, title: S) -> Result<(), NulError> {
54 let f = self.1.set_menu_item_title();
55 let s = CString::new(title.as_ref())?;
56 unsafe { f(self.0, s.as_ptr() as *mut c_char) };
57 core::mem::drop(s);
58 Ok(())
59 }
60
61 pub fn get_userdata(&self) -> Option<&mut UD> { self.get_userdata_full().map(|(_, ud)| ud) }
62
63 fn get_userdata_full(&self) -> Option<&mut CallbackUserData<UD>> {
64 let f = self.1.get_menu_item_userdata();
65 let ptr = unsafe { f(self.0) };
66 if ptr.is_null()
67 {
69 return None;
70 }
71
72 unsafe { (ptr as *mut CallbackUserData<UD>).as_mut() }
73 }
74
75 pub fn set_userdata(&self, userdata: UD) -> Option<UD> {
77 if let Some(existing) = self.get_userdata() {
78 core::mem::replace(existing, userdata).into()
79 } else {
80 todo!()
81 }
82 }
83
84 #[doc(alias = "sys::ffi::playdate_sys::getMenuItemValue")]
90 pub fn value(&self) -> c_int {
91 let f = self.1.get_menu_item_value();
92 unsafe { f(self.0) }
93 }
94
95 #[doc(alias = "sys::ffi::playdate_sys::setMenuItemValue")]
105 pub fn set_value(&self, value: c_int) {
106 let f = self.1.set_menu_item_value();
107 unsafe { f(self.0, value) }
108 }
109
110
111 fn take_userdata(&mut self) -> Option<UD> {
112 if self.0.is_null() {
113 return None;
114 }
115
116 let f = self.1.get_menu_item_userdata();
117 let ptr = unsafe { f(self.0) };
118 if ptr.is_null() {
119 return None;
120 } else if core::mem::size_of::<UD>() == 0
121 {
123 return None;
125 }
126
127 let ud: CallbackUserData<UD> = *unsafe { Box::from_raw(ptr as *mut CallbackUserData<UD>) };
128 let (_, userdata) = ud;
129 Some(userdata)
130 }
131}
132
133
134impl<UD: Sized, Api: api::Api, const REM: bool> MenuItem<kind::Check, UD, Api, REM> {
135 #[inline(always)]
136 pub fn new<S: AsRef<str>>(title: S,
137 checked: bool,
138 callback: Option<MenuItemCallback<UD>>,
139 userdata: UD)
140 -> Result<Self, ApiError>
141 where Api: Default
142 {
143 Self::new_with(Api::default(), title, checked, callback, userdata)
144 }
145
146 pub fn new_with<S: AsRef<str>>(api: Api,
147 title: S,
148 checked: bool,
149 callback: Option<MenuItemCallback<UD>>,
150 userdata: UD)
151 -> Result<Self, ApiError> {
152 let (callback, userdata) = proxy_menu_parts::<_, _>(callback, userdata);
153 let title = CString::new(title.as_ref())?;
154
155 let ctor = api.add_checkmark_menu_item();
156 let ptr = unsafe { ctor(title.as_ptr() as *mut c_char, checked as _, callback, userdata) };
157
158 if ptr.is_null() {
159 Err(Error::Alloc.into())
160 } else {
161 Ok(MenuItem(ptr, api, PhantomData, PhantomData))
162 }
163 }
164}
165
166
167impl<UD: Sized, Api: api::Api, const REM: bool> MenuItem<kind::Simple, UD, Api, REM> {
168 #[inline(always)]
169 pub fn new<S: AsRef<str>>(title: S,
170 callback: Option<MenuItemCallback<UD>>,
171 userdata: UD)
172 -> Result<Self, ApiError>
173 where Api: Default
174 {
175 Self::new_with(Api::default(), title, callback, userdata)
176 }
177
178 pub fn new_with<S: AsRef<str>>(api: Api,
179 title: S,
180 callback: Option<MenuItemCallback<UD>>,
181 userdata: UD)
182 -> Result<Self, ApiError> {
183 let (callback, userdata) = proxy_menu_parts::<_, _>(callback, userdata);
184 let title = CString::new(title.as_ref())?;
185
186 let ctor = api.add_menu_item();
187 let ptr = unsafe { ctor(title.as_ptr() as *mut c_char, callback, userdata) };
188
189 if ptr.is_null() {
190 Err(Error::Alloc.into())
191 } else {
192 Ok(MenuItem(ptr, api, PhantomData, PhantomData::<UD>))
193 }
194 }
195}
196
197impl<UD: Sized, Api: api::Api, const REM: bool> MenuItem<kind::Options, UD, Api, REM> {
198 #[inline(always)]
199 pub fn new<S: AsRef<str>, O: AsRef<[S]>>(title: S,
200 options: O,
201 callback: Option<MenuItemCallback<UD>>,
202 userdata: UD)
203 -> Result<Self, ApiError>
204 where Api: Default
205 {
206 Self::new_with(Api::default(), title, options, callback, userdata)
207 }
208
209 pub fn new_with<S: AsRef<str>, O: AsRef<[S]>>(api: Api,
210 title: S,
211 options: O,
212 callback: Option<MenuItemCallback<UD>>,
213 userdata: UD)
214 -> Result<Self, ApiError> {
215 #[allow(unused_imports)]
216 use alloc::vec::Vec;
217
218
219 let (callback, userdata) = proxy_menu_parts::<_, _>(callback, userdata);
220 let title = CString::new(title.as_ref())?;
221
222 let options = options.as_ref();
223 let mut opts = Vec::with_capacity(options.len());
224 for opt in options {
225 let opt = CString::new(opt.as_ref())?;
226 opts.push(opt)
227 }
228 let mut ptrs = Vec::with_capacity(options.len());
229 ptrs.extend(opts.iter().map(|s| s.as_ptr()));
230
231 let ctor = api.add_options_menu_item();
232 let ptr = unsafe {
233 ctor(
234 title.as_ptr() as *mut c_char,
235 ptrs.as_mut_ptr() as _,
236 ptrs.len() as _,
237 callback,
238 userdata,
239 )
240 };
241
242 core::mem::drop(ptrs);
243 core::mem::drop(opts);
244
245 if ptr.is_null() {
246 Err(Error::Alloc.into())
247 } else {
248 Ok(MenuItem(ptr, api, PhantomData, PhantomData::<UD>))
249 }
250 }
251}
252
253
254#[inline(always)]
255fn proxy_menu_parts<UD: Sized, F: FnMut(&mut UD)>(cb: Option<F>,
256 ud: UD)
257 -> (PDMenuItemCallbackFunction, *mut c_void) {
258 unsafe extern "C" fn proxy<UserData: Sized>(userdata: *mut c_void) {
259 if let Some((callback, userdata)) = (userdata as *mut CallbackUserData<UserData>).as_mut() {
260 callback(userdata)
261 } else {
262 panic!("user callback missed");
263 }
264 }
265
266 if let Some(callback) = cb {
267 let ptr = Box::into_raw(Box::from((callback, ud)));
269 (Some(proxy::<UD> as _), ptr as *mut _)
270 } else {
271 fn noop<UserData: Sized>(_: &mut UserData) {}
279 let ptr = Box::into_raw(Box::from((noop::<UD>, ud)));
280 (None, ptr as *mut _)
281 }
282}
283
284
285impl<UD, Api: api::Api, const REM: bool> MenuItem<kind::Check, UD, Api, REM> {
286 #[doc(alias = "sys::ffi::playdate_sys::getMenuItemValue")]
288 #[inline(always)]
289 pub fn is_checked(&self) -> bool { self.value() == 1 }
290}
291
292
293impl<UD, Api: api::Api, const REM: bool> MenuItem<kind::Options, UD, Api, REM> {
294 #[doc(alias = "sys::ffi::playdate_sys::getMenuItemValue")]
298 #[inline(always)]
299 pub fn selected_option(&self) -> i32 { self.value() }
300}
301
302
303impl<UD, K: kind::Kind, Api: api::Api, const REM: bool> Drop for MenuItem<K, UD, Api, REM> {
304 fn drop(&mut self) {
305 if REM && !self.0.is_null() {
306 self.take_userdata();
308 let f = self.1.remove_menu_item();
309 unsafe { f(self.0) };
310 }
311 }
312}
313
314impl<UD, K: kind::Kind, Api: api::Api, const REM: bool> MenuItem<K, UD, Api, REM> {
315 #[inline(always)]
316 pub fn remove(mut self) -> Option<UD> {
317 let ud = self.take_userdata();
318
319 let f = self.1.remove_menu_item();
320 unsafe { f(self.0) };
321 self.0 = core::ptr::null_mut() as _;
322
323 ud
324 }
325}
326
327
328#[doc(alias = "sys::ffi::playdate_sys::removeAllMenuItems")]
332#[inline(always)]
333pub fn remove_all_menu_items() { remove_all_menu_items_with(api::Default::default()) }
334
335#[doc(alias = "sys::ffi::playdate_sys::removeAllMenuItems")]
341pub fn remove_all_menu_items_with<Api: api::Api>(api: Api) {
342 let f = api.remove_all_menu_items();
343 unsafe { f() };
344}
345
346
347pub trait SystemMenu<Api: api::Api + Copy> {
348 #[doc(alias = "sys::ffi::playdate_sys::removeAllMenuItems")]
352 fn remove_all_menu_items(&self);
353}
354
355impl<Api: system::api::Api + api::Api + Copy> SystemMenu<Api> for system::System<Api> {
356 #[inline(always)]
357 fn remove_all_menu_items(&self) { remove_all_menu_items_with(self.inner()) }
358}
359
360
361type CallbackUserData<UserData> = (MenuItemCallback<UserData>, UserData);
362type MenuItemCallback<T> = fn(userdata: &mut T);
363
364
365pub mod kind {
366 pub trait Kind {}
367
368
369 #[derive(Debug)]
370 pub struct Simple;
371
372 #[derive(Debug)]
373 pub struct Check;
374
375 #[derive(Debug)]
376 pub struct Options;
377
378 impl Kind for Simple {}
379 impl Kind for Check {}
380 impl Kind for Options {}
381}
382
383
384pub mod api {
385 use core::ffi::c_char;
386 use core::ffi::c_int;
387 use core::ffi::c_void;
388 use core::ptr::NonNull;
389 use sys::ffi::PDMenuItem;
390 use sys::ffi::PDMenuItemCallbackFunction;
391 use sys::ffi::playdate_sys;
392
393
394 #[derive(Debug, Clone, Copy, core::default::Default)]
398 pub struct Default;
399 impl Api for Default {}
400
401
402 #[derive(Clone, Copy)]
408 #[cfg_attr(feature = "bindings-derive-debug", derive(Debug))]
409 pub struct Cache(&'static playdate_sys);
410
411 impl core::default::Default for Cache {
412 fn default() -> Self { Self(api!(system)) }
413 }
414
415 impl From<*const playdate_sys> for Cache {
416 #[inline(always)]
417 fn from(ptr: *const playdate_sys) -> Self { Self(unsafe { ptr.as_ref() }.expect("system")) }
418 }
419
420 impl From<&'static playdate_sys> for Cache {
421 #[inline(always)]
422 fn from(r: &'static playdate_sys) -> Self { Self(r) }
423 }
424
425 impl From<NonNull<playdate_sys>> for Cache {
426 #[inline(always)]
427 fn from(ptr: NonNull<playdate_sys>) -> Self { Self(unsafe { ptr.as_ref() }) }
428 }
429
430 impl From<&'_ NonNull<playdate_sys>> for Cache {
431 #[inline(always)]
432 fn from(ptr: &NonNull<playdate_sys>) -> Self { Self(unsafe { ptr.as_ref() }) }
433 }
434
435 impl From<system::api::Cache> for Cache {
436 #[inline(always)]
437 fn from(api: system::api::Cache) -> Self { Self(api.as_inner()) }
438 }
439
440 impl Api for system::api::Default {}
441
442 impl Api for system::api::Cache {
443 #[inline(always)]
444 fn add_menu_item(
445 &self)
446 -> unsafe extern "C" fn(title: *const c_char,
447 callback: PDMenuItemCallbackFunction,
448 userdata: *mut c_void) -> *mut PDMenuItem {
449 self.as_inner().addMenuItem.expect("addMenuItem")
450 }
451
452 #[inline(always)]
453 fn add_checkmark_menu_item(
454 &self)
455 -> unsafe extern "C" fn(title: *const c_char,
456 value: c_int,
457 callback: PDMenuItemCallbackFunction,
458 userdata: *mut c_void) -> *mut PDMenuItem {
459 self.as_inner()
460 .addCheckmarkMenuItem
461 .expect("addCheckmarkMenuItem")
462 }
463
464 #[inline(always)]
465 fn add_options_menu_item(
466 &self)
467 -> unsafe extern "C" fn(title: *const c_char,
468 optionTitles: *mut *const c_char,
469 optionsCount: c_int,
470 f: PDMenuItemCallbackFunction,
471 userdata: *mut c_void) -> *mut PDMenuItem {
472 self.as_inner().addOptionsMenuItem.expect("addOptionsMenuItem")
473 }
474
475 #[inline(always)]
476 fn remove_menu_item(&self) -> unsafe extern "C" fn(menuItem: *mut PDMenuItem) {
477 self.as_inner().removeMenuItem.expect("removeMenuItem")
478 }
479
480 #[inline(always)]
481 fn get_menu_item_value(&self) -> unsafe extern "C" fn(menuItem: *mut PDMenuItem) -> c_int {
482 self.as_inner().getMenuItemValue.expect("getMenuItemValue")
483 }
484
485 #[inline(always)]
486 fn set_menu_item_value(&self) -> unsafe extern "C" fn(menuItem: *mut PDMenuItem, value: c_int) {
487 self.as_inner().setMenuItemValue.expect("setMenuItemValue")
488 }
489
490 #[inline(always)]
491 fn get_menu_item_title(&self) -> unsafe extern "C" fn(menuItem: *mut PDMenuItem) -> *const c_char {
492 self.as_inner().getMenuItemTitle.expect("getMenuItemTitle")
493 }
494
495 #[inline(always)]
496 fn set_menu_item_title(&self) -> unsafe extern "C" fn(menuItem: *mut PDMenuItem, title: *const c_char) {
497 self.as_inner().setMenuItemTitle.expect("setMenuItemTitle")
498 }
499
500 #[inline(always)]
501 fn get_menu_item_userdata(&self) -> unsafe extern "C" fn(menuItem: *mut PDMenuItem) -> *mut c_void {
502 self.as_inner().getMenuItemUserdata.expect("getMenuItemUserdata")
503 }
504
505 #[inline(always)]
506 fn remove_all_menu_items(&self) -> unsafe extern "C" fn() {
507 self.as_inner().removeAllMenuItems.expect("removeAllMenuItems")
508 }
509 }
510
511
512 impl Api for Cache {
513 #[inline(always)]
514 fn add_menu_item(
515 &self)
516 -> unsafe extern "C" fn(title: *const c_char,
517 callback: PDMenuItemCallbackFunction,
518 userdata: *mut c_void) -> *mut PDMenuItem {
519 self.0.addMenuItem.expect("addMenuItem")
520 }
521
522 #[inline(always)]
523 fn add_checkmark_menu_item(
524 &self)
525 -> unsafe extern "C" fn(title: *const c_char,
526 value: c_int,
527 callback: PDMenuItemCallbackFunction,
528 userdata: *mut c_void) -> *mut PDMenuItem {
529 self.0.addCheckmarkMenuItem.expect("addCheckmarkMenuItem")
530 }
531
532 #[inline(always)]
533 fn add_options_menu_item(
534 &self)
535 -> unsafe extern "C" fn(title: *const c_char,
536 optionTitles: *mut *const c_char,
537 optionsCount: c_int,
538 f: PDMenuItemCallbackFunction,
539 userdata: *mut c_void) -> *mut PDMenuItem {
540 self.0.addOptionsMenuItem.expect("addOptionsMenuItem")
541 }
542
543 #[inline(always)]
544 fn remove_menu_item(&self) -> unsafe extern "C" fn(menuItem: *mut PDMenuItem) {
545 self.0.removeMenuItem.expect("removeMenuItem")
546 }
547
548 #[inline(always)]
549 fn get_menu_item_value(&self) -> unsafe extern "C" fn(menuItem: *mut PDMenuItem) -> c_int {
550 self.0.getMenuItemValue.expect("getMenuItemValue")
551 }
552
553 #[inline(always)]
554 fn set_menu_item_value(&self) -> unsafe extern "C" fn(menuItem: *mut PDMenuItem, value: c_int) {
555 self.0.setMenuItemValue.expect("setMenuItemValue")
556 }
557
558 #[inline(always)]
559 fn get_menu_item_title(&self) -> unsafe extern "C" fn(menuItem: *mut PDMenuItem) -> *const c_char {
560 self.0.getMenuItemTitle.expect("getMenuItemTitle")
561 }
562
563 #[inline(always)]
564 fn set_menu_item_title(&self) -> unsafe extern "C" fn(menuItem: *mut PDMenuItem, title: *const c_char) {
565 self.0.setMenuItemTitle.expect("setMenuItemTitle")
566 }
567
568 #[inline(always)]
569 fn get_menu_item_userdata(&self) -> unsafe extern "C" fn(menuItem: *mut PDMenuItem) -> *mut c_void {
570 self.0.getMenuItemUserdata.expect("getMenuItemUserdata")
571 }
572
573 #[inline(always)]
574 fn remove_all_menu_items(&self) -> unsafe extern "C" fn() {
575 self.0.removeAllMenuItems.expect("removeAllMenuItems")
576 }
577 }
578
579
580 pub trait Api {
581 #[doc(alias = "sys::ffi::playdate_sys::addMenuItem")]
583 fn add_menu_item(
584 &self)
585 -> unsafe extern "C" fn(title: *const c_char,
586 callback: PDMenuItemCallbackFunction,
587 userdata: *mut c_void) -> *mut PDMenuItem {
588 *sys::api!(system.addMenuItem)
589 }
590
591 #[doc(alias = "sys::ffi::playdate_sys::addCheckmarkMenuItem")]
593 fn add_checkmark_menu_item(
594 &self)
595 -> unsafe extern "C" fn(title: *const c_char,
596 value: c_int,
597 callback: PDMenuItemCallbackFunction,
598 userdata: *mut c_void) -> *mut PDMenuItem {
599 *sys::api!(system.addCheckmarkMenuItem)
600 }
601
602 #[doc(alias = "sys::ffi::playdate_sys::addOptionsMenuItem")]
604 fn add_options_menu_item(
605 &self)
606 -> unsafe extern "C" fn(title: *const c_char,
607 optionTitles: *mut *const c_char,
608 optionsCount: c_int,
609 f: PDMenuItemCallbackFunction,
610 userdata: *mut c_void) -> *mut PDMenuItem {
611 *sys::api!(system.addOptionsMenuItem)
612 }
613
614 #[doc(alias = "sys::ffi::playdate_sys::removeMenuItem")]
616 fn remove_menu_item(&self) -> unsafe extern "C" fn(menuItem: *mut PDMenuItem) {
617 *sys::api!(system.removeMenuItem)
618 }
619
620 #[doc(alias = "sys::ffi::playdate_sys::getMenuItemValue")]
622 fn get_menu_item_value(&self) -> unsafe extern "C" fn(menuItem: *mut PDMenuItem) -> c_int {
623 *sys::api!(system.getMenuItemValue)
624 }
625
626 #[doc(alias = "sys::ffi::playdate_sys::setMenuItemValue")]
628 fn set_menu_item_value(&self) -> unsafe extern "C" fn(menuItem: *mut PDMenuItem, value: c_int) {
629 *sys::api!(system.setMenuItemValue)
630 }
631
632 #[doc(alias = "sys::ffi::playdate_sys::getMenuItemTitle")]
634 fn get_menu_item_title(&self) -> unsafe extern "C" fn(menuItem: *mut PDMenuItem) -> *const c_char {
635 *sys::api!(system.getMenuItemTitle)
636 }
637
638 #[doc(alias = "sys::ffi::playdate_sys::setMenuItemTitle")]
640 fn set_menu_item_title(&self) -> unsafe extern "C" fn(menuItem: *mut PDMenuItem, title: *const c_char) {
641 *sys::api!(system.setMenuItemTitle)
642 }
643
644 #[doc(alias = "sys::ffi::playdate_sys::getMenuItemUserdata")]
646 fn get_menu_item_userdata(&self) -> unsafe extern "C" fn(menuItem: *mut PDMenuItem) -> *mut c_void {
647 *sys::api!(system.getMenuItemUserdata)
648 }
649
650 #[doc(alias = "sys::ffi::playdate_sys::removeAllMenuItems")]
652 fn remove_all_menu_items(&self) -> unsafe extern "C" fn() { *sys::api!(system.removeAllMenuItems) }
653 }
654}