extern crate alloc;
use alloc::boxed::Box;
use core::ffi;
use crate::{
alloc::PebbleAlloc,
sys,
ui::{
Layout, Nav, NoWindow, UiGuard, Window,
window::{Header, WindowData, header},
},
};
pub struct ClickHandlers {
up: Option<ClickHandler>,
select: Option<ClickHandler>,
down: Option<ClickHandler>,
back: Option<ClickHandler>,
back_overridden: bool,
}
impl ClickHandlers {
pub(super) const fn new() -> Self {
Self {
up: None,
select: None,
down: None,
back: None,
back_overridden: false,
}
}
fn get_mut(&mut self, button: Button) -> &mut Option<ClickHandler> {
match button {
Button::Back => &mut self.back,
Button::Up => &mut self.up,
Button::Select => &mut self.select,
Button::Down => &mut self.down,
}
}
}
#[derive(Clone, Copy)]
#[repr(u8)]
pub enum Button {
Back = sys::ButtonId_BUTTON_ID_BACK,
Up = sys::ButtonId_BUTTON_ID_UP,
Select = sys::ButtonId_BUTTON_ID_SELECT,
Down = sys::ButtonId_BUTTON_ID_DOWN,
}
impl Button {
const ALL: [Self; 4] = [Self::Back, Self::Up, Self::Select, Self::Down];
fn from_raw(id: sys::ButtonId) -> Option<Self> {
match id {
sys::ButtonId_BUTTON_ID_BACK => Some(Self::Back),
sys::ButtonId_BUTTON_ID_UP => Some(Self::Up),
sys::ButtonId_BUTTON_ID_SELECT => Some(Self::Select),
sys::ButtonId_BUTTON_ID_DOWN => Some(Self::Down),
_ => None,
}
}
}
bitflags::bitflags! {
pub struct ClickKinds: u8 {
const SINGLE = 1;
const MULTI = 1 << 1;
const LONG = 1 << 2;
const LONG_RELEASE = 1 << 3;
const RAW_DOWN = 1 << 4;
const RAW_UP = 1 << 5;
}
}
pub enum Click {
Single {
repeating: bool,
},
Multi {
count: u8,
},
Long,
LongRelease,
RawDown,
RawUp,
}
pub struct ClickHandler {
subscribed: ClickKinds,
closure: *mut (),
call: for<'n> unsafe fn(*mut (), *mut Header, Click, &mut Nav<'n>),
drop: Option<unsafe fn(*mut ())>,
}
impl ClickHandler {
fn new<F: for<'n> FnMut(*mut Header, Click, &mut Nav<'n>) + 'static>(
subscribed: ClickKinds,
closure: F,
) -> Self {
unsafe fn call<F: for<'n> FnMut(*mut Header, Click, &mut Nav<'n>)>(
closure: *mut (),
header: *mut Header,
click: Click,
nav: &mut Nav<'_>,
) {
unsafe { (*closure.cast::<F>())(header, click, nav) }
}
unsafe fn drop_box<F>(closure: *mut ()) {
drop(unsafe { Box::from_raw_in(closure.cast::<F>(), PebbleAlloc) });
}
Self {
subscribed,
closure: Box::into_raw_with_allocator(Box::new_in(closure, PebbleAlloc))
.0
.cast(),
call: call::<F>,
drop: (size_of::<F>() != 0 || core::mem::needs_drop::<F>())
.then_some(drop_box::<F> as unsafe fn(*mut ())),
}
}
}
impl Drop for ClickHandler {
fn drop(&mut self) {
if let Some(drop) = self.drop {
unsafe { drop(self.closure) };
}
}
}
impl<C: Layout<D>, D> Window<C, D> {
pub fn set_click_handler(
&mut self,
ui: &mut UiGuard,
button: Button,
kinds: ClickKinds,
mut handler: impl for<'n> FnMut(&'n mut C, Option<&'n mut D>, Click, &mut Nav<'n>)
+ NoWindow
+ 'static,
) {
let erased = ClickHandler::new(
kinds,
move |header: *mut Header, click, nav: &mut Nav<'_>| {
let window = header.cast::<WindowData<C, D>>();
let (layout, data) = unsafe { (&mut (*window).layout, &mut (*window).data) };
if let Some(layout) = layout {
handler(layout, data.as_mut(), click, nav);
}
},
);
unsafe { replace(self.ptr, ui, button, Some(erased)) };
}
pub fn clear_click_handler(&mut self, ui: &mut UiGuard, button: Button) {
unsafe { replace(self.ptr, ui, button, None) };
}
}
unsafe fn replace(
window: *mut sys::Window,
ui: &mut UiGuard,
button: Button,
handler: Option<ClickHandler>,
) {
*unsafe { &mut (*header(window)).clicks }.get_mut(button) = handler;
ui.os_lend(|| unsafe {
sys::window_set_click_config_provider(window, Some(click_config));
});
}
unsafe extern "C" fn click_config(context: *mut ffi::c_void) {
let _ui = UiGuard::enter();
let clicks = unsafe { &mut (*header(context.cast())).clicks };
let mut back_overridden = clicks.back_overridden;
for button in Button::ALL {
let Some(handler) = clicks.get_mut(button) else {
continue;
};
let kinds = &handler.subscribed;
let id = button as sys::ButtonId;
if matches!(button, Button::Back)
&& kinds.intersects(ClickKinds::SINGLE | ClickKinds::MULTI)
{
back_overridden = true;
}
let slot =
|kind: ClickKinds, f: sys::ClickHandler| kinds.contains(kind).then_some(f).flatten();
unsafe {
if kinds.contains(ClickKinds::SINGLE) {
sys::window_single_click_subscribe(
id,
Some(click::<{ ClickKinds::SINGLE.bits() }>),
);
}
if kinds.contains(ClickKinds::MULTI) {
sys::window_multi_click_subscribe(
id,
2,
0,
0,
false,
Some(click::<{ ClickKinds::MULTI.bits() }>),
);
}
if kinds.intersects(ClickKinds::LONG | ClickKinds::LONG_RELEASE) {
sys::window_long_click_subscribe(
id,
0,
slot(ClickKinds::LONG, Some(click::<{ ClickKinds::LONG.bits() }>)),
slot(
ClickKinds::LONG_RELEASE,
Some(click::<{ ClickKinds::LONG_RELEASE.bits() }>),
),
);
}
if kinds.intersects(ClickKinds::RAW_DOWN | ClickKinds::RAW_UP) {
sys::window_raw_click_subscribe(
id,
slot(
ClickKinds::RAW_DOWN,
Some(click::<{ ClickKinds::RAW_DOWN.bits() }>),
),
slot(
ClickKinds::RAW_UP,
Some(click::<{ ClickKinds::RAW_UP.bits() }>),
),
core::ptr::null_mut(),
);
}
}
}
clicks.back_overridden = back_overridden;
if back_overridden && clicks.back.is_none() {
unsafe {
sys::window_single_click_subscribe(sys::ButtonId_BUTTON_ID_BACK, Some(back_pop));
}
}
}
unsafe extern "C" fn back_pop(_recognizer: sys::ClickRecognizerRef, _context: *mut ffi::c_void) {
UiGuard::enter().os_lend(|| unsafe { sys::window_stack_pop(true) });
}
unsafe extern "C" fn click<const KIND: u8>(
recognizer: sys::ClickRecognizerRef,
context: *mut ffi::c_void,
) {
let mut ui = UiGuard::enter();
let id = unsafe { sys::click_recognizer_get_button_id(recognizer) };
let Some(button) = Button::from_raw(id) else {
return;
};
let header = unsafe { header(context.cast()) };
let Some(handler) = unsafe { &mut (*header).clicks }.get_mut(button) else {
return;
};
let kind = ClickKinds::from_bits_retain(KIND);
let click = unsafe {
if kind.contains(ClickKinds::SINGLE) {
Click::Single {
repeating: sys::click_recognizer_is_repeating(recognizer),
}
} else if kind.contains(ClickKinds::MULTI) {
Click::Multi {
count: sys::click_number_of_clicks_counted(recognizer),
}
} else if kind.contains(ClickKinds::LONG) {
Click::Long
} else if kind.contains(ClickKinds::LONG_RELEASE) {
Click::LongRelease
} else if kind.contains(ClickKinds::RAW_DOWN) {
Click::RawDown
} else {
Click::RawUp
}
};
let mut nav = Nav::new(&mut ui);
unsafe { (handler.call)(handler.closure, header, click, &mut nav) };
}