deft 0.15.0

Cross platform ui framework
use crate::base::{UnsafeFnMut, UnsafeFnOnce};
use std::cell::RefCell;
use std::sync::{Arc, Mutex};

thread_local! {
    static JS_EVENT_LOOP_PROXY: RefCell<Option<JsEventLoopProxy>> = RefCell::new(None);
}

pub struct JsEventLoop {
    sender: Arc<
        Mutex<
            Box<dyn FnMut(JsEvent) -> Result<(), JsEventLoopClosedError> + Send + Sync + 'static>,
        >,
    >,
}

impl JsEventLoop {
    pub fn create_event_loop_proxy(&mut self) -> JsEventLoopProxy {
        JsEventLoopProxy {
            sender: self.sender.clone(),
        }
    }
}

#[derive(Clone)]
pub struct JsEventLoopProxy {
    sender: Arc<
        Mutex<
            Box<dyn FnMut(JsEvent) -> Result<(), JsEventLoopClosedError> + Send + Sync + 'static>,
        >,
    >,
}

impl JsEventLoopProxy {
    pub fn schedule_macro_task<F: FnOnce() + Send + Sync + 'static>(
        &self,
        callback: F,
    ) -> Result<(), JsEventLoopClosedError> {
        let event = JsEvent::MacroTask(Box::new(callback));
        let mut sender = self.sender.lock().unwrap();
        sender(event)
    }
}

pub enum JsEvent {
    MacroTask(Box<dyn FnOnce() + Send + Sync + 'static>),
}

#[derive(Debug)]
pub struct JsEventLoopClosedError {}

pub struct JsEventLoopCallback {
    event_loop_proxy: JsEventLoopProxy,
    callback: Option<UnsafeFnOnce>,
}

impl JsEventLoopCallback {
    pub fn call(mut self) {
        let callback = self.callback.take().unwrap();
        self.event_loop_proxy
            .schedule_macro_task(callback.into_box())
            .unwrap();
    }
}

#[derive(Clone)]
pub struct JsEventLoopFnMutCallback<P> {
    event_loop_proxy: JsEventLoopProxy,
    callback: Arc<Mutex<UnsafeFnMut<P>>>,
}

impl<P: Send + Sync + 'static> JsEventLoopFnMutCallback<P> {
    pub fn call(&mut self, param: P) {
        let cb = self.callback.clone();
        self.event_loop_proxy
            .schedule_macro_task(move || {
                let mut cb = cb.lock().unwrap();
                (cb.callback)(param);
            })
            .unwrap();
    }
}

pub fn js_init_event_loop<
    F: FnMut(JsEvent) -> Result<(), JsEventLoopClosedError> + Send + Sync + 'static,
>(
    sender: F,
) -> JsEventLoop {
    let mut js_event_loop = JsEventLoop {
        sender: Arc::new(Mutex::new(Box::new(sender))),
    };
    let proxy = js_event_loop.create_event_loop_proxy();
    JS_EVENT_LOOP_PROXY.with_borrow_mut(|cell| {
        if cell.is_some() {
            panic!("Attempting to initialize  js event loop twice");
        }
        cell.replace(proxy);
    });
    js_event_loop
}

pub fn js_is_in_event_loop() -> bool {
    JS_EVENT_LOOP_PROXY.with_borrow(|cell| cell.is_some())
}

pub fn js_create_event_loop_proxy() -> JsEventLoopProxy {
    JS_EVENT_LOOP_PROXY.with_borrow(|cell| {
        let el = cell
            .as_ref()
            .expect("Attempting to use event loop in non-main thread");
        el.clone()
    })
}

pub fn js_create_event_loop_callback<F: FnOnce() + 'static>(callback: F) -> JsEventLoopCallback {
    let callback = unsafe { UnsafeFnOnce::new(callback) };
    let event_loop_proxy = js_create_event_loop_proxy();
    JsEventLoopCallback {
        event_loop_proxy,
        callback: Some(callback),
    }
}

pub fn js_create_event_loop_fn_mut<P: Send + Sync, F: FnMut(P) + 'static>(
    callback: F,
) -> JsEventLoopFnMutCallback<P> {
    let fn_mut = UnsafeFnMut {
        callback: Box::new(callback),
    };
    let event_loop_proxy = js_create_event_loop_proxy();
    JsEventLoopFnMutCallback {
        event_loop_proxy,
        callback: Arc::new(Mutex::new(fn_mut)),
    }
}