skima 0.1.0

Rust structural UI library for web
Documentation
use std::cell::RefCell;
use std::rc::Rc;

use wasm_bindgen::prelude::Closure;
use wasm_bindgen::JsCast;
use web_sys::HtmlElement;

use crate::tree::Tree;
use crate::web::{Callback, Markup, WebSys};

pub(crate) struct EventListener<F>
where
	F: Fn(web_sys::Event) + ?Sized,
{
	pub(crate) event: &'static str,
	pub(crate) callback: Rc<F>,
}

impl<F> EventListener<F>
where
	F: Fn(web_sys::Event),
{
	pub fn new(event: &'static str, func: F) -> Self {
		EventListener {
			event,
			callback: Rc::new(func),
		}
	}
}

struct EventListenerData<F>
where
	F: Fn(web_sys::Event) + ?Sized + 'static,
{
	func: RefCell<Rc<F>>,
	closure: Closure<dyn Fn(web_sys::Event)>,
}

impl<F> Markup<WebSys> for EventListener<F>
where
	F: Fn(web_sys::Event) + ?Sized + 'static,
{
	fn has_own_node() -> bool {
		false
	}

	fn render(&self, tree: &Tree<WebSys>) {
		tracing::info!("Rendering event {}", self.event);
		tracing::info!("Event tree {:?}", tree);

		let data =
			Rc::<EventListenerData<F>>::new_cyclic(|this| {
				let data = this.clone();
				let closure = Closure::wrap(Box::new(move |event| {
					(data.upgrade().unwrap().func.borrow())(event)
				}) as Box<dyn Fn(web_sys::Event)>);

				EventListenerData {
					func: RefCell::new(self.callback.clone()),
					closure,
				}
			});

		tree.closest_node()
			.unchecked_ref::<HtmlElement>()
			.add_event_listener_with_callback(&self.event, data.closure.as_ref().unchecked_ref())
			.unwrap();

		tree.set_data(data);
	}

	fn diff(&self, _prev: &Self, tree: &Tree<WebSys>) {
		let data = tree.data::<EventListenerData<F>>();
		*data.func.borrow_mut() = self.callback.clone();
	}

	fn drop(&self, tree: &Tree<WebSys>, _should_unmount: bool) {
		let data = tree.remove_data::<EventListenerData<F>>();
		tree.closest_node()
			.unchecked_ref::<HtmlElement>()
			.remove_event_listener_with_callback(self.event, data.closure.as_ref().unchecked_ref())
			.unwrap()
	}
}

pub trait IntoEventCallback {
	type Fn: Fn(web_sys::Event) + ?Sized + 'static;
	fn into_fn(self) -> Rc<Self::Fn>;
}

impl<F> IntoEventCallback for F
where
	F: Fn(web_sys::Event) + 'static,
{
	type Fn = Self;
	fn into_fn(self) -> Rc<Self::Fn> {
		Rc::new(self)
	}
}

impl IntoEventCallback for Callback<dyn Fn(web_sys::Event)> {
	type Fn = dyn Fn(web_sys::Event);
	fn into_fn(self) -> Rc<Self::Fn> {
		self.0.clone()
	}
}

pub fn on(event: &'static str, callback: impl IntoEventCallback) -> impl Markup {
	EventListener {
		event,
		callback: callback.into_fn(),
	}
}

pub fn on_if<T: 'static, F>(val: Option<T>, event: &'static str, callback: F) -> impl Markup
where
	F: Fn(&T, web_sys::Event) + 'static,
{
	val.map(|val| EventListener {
		event,
		callback: Rc::new(move |v| (callback)(&val, v)),
	})
}