extern crate alloc;
use alloc::boxed::Box;
use core::{any::TypeId, cell::Cell, marker::PhantomData, mem};
use crate::{
alloc::PebbleAlloc,
sys,
ui::{ClickHandlers, UiGuard, layer::RootLayer},
};
pub struct Window<C: Layout<D> + 'static, D: 'static = ()> {
pub(super) ptr: *mut sys::Window,
data: PhantomData<Box<WindowData<C, D>>>,
}
pub struct WindowCtx<'w, C, D = ()> {
ptr: *mut sys::Window,
data: &'w mut Option<D>,
_layout: PhantomData<fn() -> C>,
}
pub struct WindowRef<'r, C, D = ()> {
ptr: *mut sys::Window,
_p: PhantomData<(&'r C, &'r D)>,
}
#[derive(Clone, Copy)]
pub struct AnyWindow<'w> {
pub(super) ptr: *mut sys::Window,
_p: PhantomData<&'w sys::Window>,
}
#[repr(C)]
pub(super) struct WindowData<C: Layout<D> + 'static, D: 'static = ()> {
pub(super) header: Header,
pub(super) layout: Option<C>,
pub(super) data: Option<D>,
pub(super) pending: Option<C::Arg>,
}
#[repr(C)]
pub(super) struct Header {
pub(super) tag: TypeId,
pub(super) clicks: ClickHandlers,
}
fn create(handlers: sys::WindowHandlers) -> Option<*mut sys::Window> {
unsafe {
let raw = sys::window_create();
if raw.is_null() {
return None;
}
sys::window_set_window_handlers(raw, handlers);
Some(raw)
}
}
unsafe fn destroy(window: *mut sys::Window) -> *mut core::ffi::c_void {
unsafe {
let user_data = sys::window_get_user_data(window);
assert!(
!sys::window_is_loaded(window),
"Currently invalid to drop a loaded window"
);
sys::window_stack_remove(window, false);
sys::window_destroy(window);
user_data
}
}
pub(super) unsafe fn header(window: *mut sys::Window) -> *mut Header {
unsafe { sys::window_get_user_data(window).cast() }
}
pub unsafe auto trait NoWindow {}
impl<C: Layout<D>, D> !NoWindow for Window<C, D> {}
#[allow(unused, reason = "warning that no-op default don't use args is noise")]
pub trait Layout<D = ()>: NoWindow + Sized {
type Arg: 'static = ();
fn load(parent: &mut WindowCtx<'_, Self, D>) -> Self;
fn appear(&mut self, parent: &mut WindowCtx<'_, Self, D>, arg: Option<Self::Arg>) {}
fn disappear(&mut self, parent: &mut WindowCtx<'_, Self, D>) {}
fn unload(self, parent: &mut WindowCtx<'_, Self, D>) {}
}
impl<C: Layout<D>, D> Window<C, D> {
pub fn new(data: Option<D>) -> Option<Self> {
let ptr = create(sys::WindowHandlers {
load: Some(Self::__load),
appear: Some(Self::__appear),
disappear: Some(Self::__disappear),
unload: Some(Self::__unload),
})?;
let data = Box::new_in(
WindowData::<C, D> {
header: Header {
tag: TypeId::of::<WindowData<C, D>>(),
clicks: ClickHandlers::new(),
},
layout: None,
data,
pending: None,
},
PebbleAlloc,
);
unsafe { sys::window_set_user_data(ptr, Box::into_raw_with_allocator(data).0.cast()) };
Some(Self {
ptr,
data: PhantomData,
})
}
pub fn as_ctx<'g>(&'g mut self, _ui: &'g mut UiGuard) -> WindowCtx<'g, C, D> {
WindowCtx::new(self.ptr, unsafe { &mut Self::window_data(self.ptr).data })
}
pub fn layout<'g>(&'g mut self, _ui: &'g mut UiGuard) -> Option<&'g mut C> {
unsafe { Self::window_data(self.ptr) }.layout.as_mut()
}
pub(super) unsafe fn window_data_ref<'a>(window: *mut sys::Window) -> &'a WindowData<C, D> {
unsafe {
sys::window_get_user_data(window)
.cast::<WindowData<C, D>>()
.as_ref()
.unwrap_unchecked()
}
}
pub(super) unsafe fn window_data<'a>(window: *mut sys::Window) -> &'a mut WindowData<C, D> {
unsafe {
sys::window_get_user_data(window)
.cast::<WindowData<C, D>>()
.as_mut()
.unwrap_unchecked()
}
}
#[must_use]
pub fn leak(self) -> AnyWindow<'static> {
let ptr = self.ptr;
mem::forget(self);
AnyWindow {
ptr,
_p: PhantomData,
}
}
}
impl<C: Layout<D>, D> Window<C, D> {
unsafe extern "C" fn __load(window: *mut sys::Window) {
let _ui = UiGuard::enter();
let WindowData { layout, data, .. } = unsafe { Self::window_data(window) };
debug_assert!(layout.is_none());
layout.get_or_insert_with(|| C::load(&mut WindowCtx::new(window, data)));
}
unsafe extern "C" fn __appear(window: *mut sys::Window) {
let _ui = UiGuard::enter();
let WindowData {
layout,
data,
pending,
..
} = unsafe { Self::window_data(window) };
if let Some(layout) = layout {
layout.appear(&mut WindowCtx::new(window, data), pending.take());
}
}
unsafe extern "C" fn __disappear(window: *mut sys::Window) {
let _ui = UiGuard::enter();
let WindowData { layout, data, .. } = unsafe { Self::window_data(window) };
if let Some(layout) = layout {
layout.disappear(&mut WindowCtx::new(window, data));
}
}
unsafe extern "C" fn __unload(window: *mut sys::Window) {
let _ui = UiGuard::enter();
let WindowData { layout, data, .. } = unsafe { Self::window_data(window) };
if let Some(layout) = layout.take() {
layout.unload(&mut WindowCtx::new(window, data));
}
}
}
impl<C: Layout<D>, D> Drop for Window<C, D> {
fn drop(&mut self) {
unsafe {
let data_ptr = destroy(self.ptr).cast::<WindowData<C, D>>();
let _ = Box::from_raw_in(data_ptr, PebbleAlloc);
}
}
}
impl<'w, C, D> WindowCtx<'w, C, D> {
pub(super) fn new(ptr: *mut sys::Window, data: &'w mut Option<D>) -> Self {
Self {
ptr,
data,
_layout: PhantomData,
}
}
pub fn data(&mut self) -> Option<&mut D> {
self.data.as_mut()
}
pub fn ptr(&self) -> *mut sys::Window {
self.ptr
}
pub fn root(&mut self) -> RootLayer {
RootLayer::from_ctx(self)
}
}
impl<'r, C: Layout<D> + 'static, D: 'static> WindowRef<'r, C, D> {
pub fn layout(&self) -> &'r C {
unsafe {
Window::<C, D>::window_data_ref(self.ptr)
.layout
.as_ref()
.unwrap_unchecked()
}
}
pub fn data(&self) -> Option<&'r D> {
unsafe { Window::<C, D>::window_data_ref(self.ptr) }
.data
.as_ref()
}
pub fn ptr(&self) -> *mut sys::Window {
self.ptr
}
}
impl<'w> AnyWindow<'w> {
pub fn as_ptr(self) -> *mut sys::Window {
self.ptr
}
pub fn downcast<'g, C: Layout<D> + 'static, D: 'static>(
self,
_ui: &'g UiGuard,
) -> Option<WindowRef<'g, C, D>>
where
'w: 'g,
{
let tag = unsafe {
sys::window_get_user_data(self.ptr)
.cast::<TypeId>()
.as_ref()
}?;
if *tag != TypeId::of::<WindowData<C, D>>() {
return None;
}
unsafe { Window::<C, D>::window_data_ref(self.ptr) }
.layout
.as_ref()?;
Some(WindowRef {
ptr: self.ptr,
_p: PhantomData,
})
}
}
unsafe impl Sync for AnyWindow<'_> {}
impl<'w, C: Layout<D>, D> From<&'w Window<C, D>> for AnyWindow<'w> {
fn from(window: &'w Window<C, D>) -> Self {
Self {
ptr: window.ptr,
_p: PhantomData,
}
}
}
pub struct StaticWindow<C: Layout<D> + 'static, D: 'static = ()> {
inner: Cell<Option<AnyWindow<'static>>>,
_win: PhantomData<fn() -> (C, D)>,
}
unsafe impl<C: Layout<D>, D> Sync for StaticWindow<C, D> {}
impl<C: Layout<D>, D> StaticWindow<C, D> {
pub const fn new() -> Self {
Self {
inner: Cell::new(None),
_win: PhantomData,
}
}
pub fn set(&self, window: Window<C, D>) {
slot_set(&self.inner, window.leak());
}
pub fn get(&self) -> AnyWindow<'static> {
slot_get(&self.inner)
}
pub fn borrow<'g>(&self, ui: &'g UiGuard) -> Option<WindowRef<'g, C, D>> {
self.get().downcast(ui)
}
pub fn with<R>(
&self,
_ui: &mut UiGuard,
f: impl FnOnce(Option<&mut C>, &mut WindowCtx<'_, C, D>) -> R,
) -> R {
let ptr = self.get().ptr;
let WindowData { layout, data, .. } = unsafe { Window::<C, D>::window_data(ptr) };
f(layout.as_mut(), &mut WindowCtx::new(ptr, data))
}
}
fn slot_set(slot: &Cell<Option<AnyWindow<'static>>>, window: AnyWindow<'static>) {
assert!(slot.get().is_none(), "StaticWindow set twice");
slot.set(Some(window));
}
fn slot_get(slot: &Cell<Option<AnyWindow<'static>>>) -> AnyWindow<'static> {
slot.get().expect("StaticWindow used before set")
}
impl<C: Layout<D>, D> Default for StaticWindow<C, D> {
fn default() -> Self {
Self::new()
}
}
pub trait TypedWindow<'w>: Into<AnyWindow<'w>> + Copy {
type Layout: Layout<Self::Data> + 'static;
type Data: 'static;
}
impl<'w, C: Layout<D>, D> TypedWindow<'w> for &'w Window<C, D> {
type Data = D;
type Layout = C;
}
impl<'w, C: Layout<D>, D> TypedWindow<'w> for &'w StaticWindow<C, D> {
type Data = D;
type Layout = C;
}
impl<C: Layout<D>, D> From<&StaticWindow<C, D>> for AnyWindow<'_> {
fn from(slot: &StaticWindow<C, D>) -> Self {
slot.get()
}
}