Skip to content

Commit

Permalink
xilem_html: Refactor DOM interface macros
Browse files Browse the repository at this point in the history
* Add ancestor *and* descendent composable macros
* Added a macro to correctly and conveniently implement dom interfaces for interface restricted Views, this is used currently for the Attr view and the event views
  • Loading branch information
Philipp-M committed Nov 7, 2023
1 parent 989f9e5 commit 02adbe5
Show file tree
Hide file tree
Showing 7 changed files with 282 additions and 193 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/xilem_html/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ xilem_core.workspace = true
kurbo.workspace = true
bitflags = "2"
wasm-bindgen = "0.2.87"
paste = "1"
log = "0.4.19"
gloo = { version = "0.8.1", default-features = false, features = ["events", "utils"] }

Expand Down
13 changes: 2 additions & 11 deletions crates/xilem_html/src/attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use xilem_core::{Id, MessageResult};

use crate::{interfaces::sealed::Sealed, AttributeValue, ChangeFlags, Cx, View, ViewMarker};

use super::interfaces::{for_all_dom_interfaces, Element};
use super::interfaces::Element;

pub struct Attr<T, A, E> {
pub(crate) element: E,
Expand Down Expand Up @@ -49,13 +49,4 @@ impl<T, A, E: Element<T, A>> View<T, A> for Attr<T, A, E> {
}
}

macro_rules! impl_dom_interface_for_attr {
($dom_interface:ident) => {
impl<T, A, E: $crate::interfaces::$dom_interface<T, A>>
$crate::interfaces::$dom_interface<T, A> for Attr<T, A, E>
{
}
};
}

for_all_dom_interfaces!(impl_dom_interface_for_attr);
crate::interfaces::impl_dom_interfaces_for_ty!(Element, Attr);
20 changes: 10 additions & 10 deletions crates/xilem_html/src/elements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::{
Pod, View, ViewMarker, ViewSequence,
};

use super::interfaces::{for_all_dom_interface_relatives, Element};
use super::interfaces::Element;

type CowStr = std::borrow::Cow<'static, str>;

Expand Down Expand Up @@ -161,7 +161,7 @@ impl<T, A, Children: ViewSequence<T, A>> crate::interfaces::HtmlElement<T, A>
}

macro_rules! generate_dom_interface_impl {
($dom_interface:ident, $ty_name:ident, $t:ident, $a:ident, $vs:ident) => {
($dom_interface:ident, ($ty_name:ident, $t:ident, $a:ident, $vs:ident)) => {
impl<$t, $a, $vs> $crate::interfaces::$dom_interface<$t, $a> for $ty_name<$t, $a, $vs> where
$vs: $crate::view::ViewSequence<$t, $a>
{
Expand Down Expand Up @@ -265,14 +265,11 @@ macro_rules! define_html_element {
$ty_name(children, PhantomData)
}

for_all_dom_interface_relatives!(
$dom_interface,
generate_dom_interface_impl,
$ty_name,
$t,
$a,
$vs
);
generate_dom_interface_impl!($dom_interface, ($ty_name, $t, $a, $vs));

paste::paste! {
$crate::interfaces::[<for_all_ $dom_interface:snake _ancestors>]!(generate_dom_interface_impl, ($ty_name, $t, $a, $vs));
}
};
}

Expand All @@ -287,6 +284,8 @@ define_elements!(
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element
// DOM interfaces copied from https://html.spec.whatwg.org/multipage/grouping-content.html and friends

// TODO include document metadata elements?

// content sectioning
(Address, address, HtmlElement),
(Article, article, HtmlElement),
Expand All @@ -313,6 +312,7 @@ define_elements!(
(Figure, figure, HtmlElement),
(Hr, hr, HtmlHrElement),
(Li, li, HtmlLiElement),
(Link, link, HtmlLinkElement),
(Menu, menu, HtmlMenuElement),
(Ol, ol, HtmlOListElement),
(P, p, HtmlParagraphElement),
Expand Down
108 changes: 51 additions & 57 deletions crates/xilem_html/src/events.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
interfaces::{for_all_dom_interfaces, sealed::Sealed, Element},
interfaces::{sealed::Sealed, Element},
view::DomNode,
ChangeFlags, Cx, OptionalAction, View, ViewMarker,
};
Expand All @@ -12,15 +12,16 @@ pub use gloo::events::EventListenerOptions;
/// Wraps a [`View`] `V` and attaches an event listener.
///
/// The event type `E` should inherit from [`web_sys::Event`]
pub struct OnEvent<V, E, F> {
pub(crate) element: V,
pub struct OnEvent<T, A, E, Ev, F> {
pub(crate) element: E,
pub(crate) event: Cow<'static, str>,
pub(crate) options: EventListenerOptions,
pub(crate) handler: F,
pub(crate) phantom_event_ty: PhantomData<E>,
#[allow(clippy::type_complexity)]
pub(crate) phantom_event_ty: PhantomData<fn() -> (T, A, Ev)>,
}

impl<V, E, F> OnEvent<V, E, F>
impl<T, A, V, E, F> OnEvent<T, A, V, E, F>
where
E: JsCast + 'static,
{
Expand Down Expand Up @@ -60,7 +61,7 @@ where
}
}

fn create_event_listener<E: JsCast + 'static>(
fn create_event_listener<Ev: JsCast + 'static>(
target: &web_sys::EventTarget,
event: impl Into<Cow<'static, str>>,
options: EventListenerOptions,
Expand All @@ -72,7 +73,7 @@ fn create_event_listener<E: JsCast + 'static>(
event,
options,
move |event: &web_sys::Event| {
let event = (*event).clone().dyn_into::<E>().unwrap_throw();
let event = (*event).clone().dyn_into::<Ev>().unwrap_throw();
thunk.push_message(event);
},
)
Expand All @@ -86,24 +87,24 @@ pub struct OnEventState<S> {
child_state: S,
}

impl<V, E, F> ViewMarker for OnEvent<V, E, F> {}
impl<V, E, F> Sealed for OnEvent<V, E, F> {}
impl<T, A, E, Ev, F> ViewMarker for OnEvent<T, A, E, Ev, F> {}
impl<T, A, E, Ev, F> Sealed for OnEvent<T, A, E, Ev, F> {}

impl<T, A, E, F, V, OA> View<T, A> for OnEvent<V, E, F>
impl<T, A, E, F, Ev, OA> View<T, A> for OnEvent<T, A, E, Ev, F>
where
OA: OptionalAction<A>,
F: Fn(&mut T, E) -> OA,
V: Element<T, A>,
E: JsCast + 'static,
F: Fn(&mut T, Ev) -> OA,
E: Element<T, A>,
Ev: JsCast + 'static,
{
type State = OnEventState<V::State>;
type State = OnEventState<E::State>;

type Element = V::Element;
type Element = E::Element;

fn build(&self, cx: &mut Cx) -> (Id, Self::State, Self::Element) {
let (id, (element, state)) = cx.with_new_id(|cx| {
let (child_id, child_state, element) = self.element.build(cx);
let listener = create_event_listener::<E>(
let listener = create_event_listener::<Ev>(
element.as_node_ref(),
self.event.clone(),
self.options,
Expand Down Expand Up @@ -141,7 +142,7 @@ where
}
// TODO check equality of prev and current element somehow
if prev.event != self.event || changed.contains(ChangeFlags::STRUCTURE) {
state.listener = create_event_listener::<E>(
state.listener = create_event_listener::<Ev>(
element.as_node_ref(),
self.event.clone(),
self.options,
Expand All @@ -161,8 +162,8 @@ where
app_state: &mut T,
) -> MessageResult<A> {
match id_path {
[] if message.downcast_ref::<E>().is_some() => {
let event = message.downcast::<E>().unwrap();
[] if message.downcast_ref::<Ev>().is_some() => {
let event = message.downcast::<Ev>().unwrap();
match (self.handler)(app_state, *event).action() {
Some(a) => MessageResult::Action(a),
None => MessageResult::Nop,
Expand All @@ -177,48 +178,41 @@ where
}
}

macro_rules! impl_dom_interface_for_event_listener {
($dom_interface:ident) => {
impl<T, A, E, Ev, F, OA> $crate::interfaces::$dom_interface<T, A> for OnEvent<E, Ev, F>
where
F: Fn(&mut T, Ev) -> OA,
E: $crate::interfaces::$dom_interface<T, A>,
Ev: JsCast + 'static,
OA: OptionalAction<A>,
{
}
};
}

for_all_dom_interfaces!(impl_dom_interface_for_event_listener);

macro_rules! impl_dom_interface_for_event {
($dom_interface:ident, $event_ty:ident, $web_sys_ty: ident) => {
impl<T, A, E, C, OA> $crate::interfaces::$dom_interface<T, A>
for $crate::events::$event_ty<T, A, E, C>
where
E: $crate::interfaces::$dom_interface<T, A>,
OA: OptionalAction<A>,
C: Fn(&mut T, web_sys::$web_sys_ty) -> OA,
{
}
};
}
crate::interfaces::impl_dom_interfaces_for_ty!(
Element,
OnEvent,
vars: <Ev, F, OA,>,
vars_on_ty: <Ev, F,>,
bounds: {
Ev: JsCast + 'static,
OA: OptionalAction<A>,
F: Fn(&mut T, Ev) -> OA,
}
);

macro_rules! event_definitions {
($(($ty_name:ident, $event_name:literal, $web_sys_ty:ident)),*) => {
$(
for_all_dom_interfaces!(impl_dom_interface_for_event, $ty_name, $web_sys_ty);
$crate::interfaces::impl_dom_interfaces_for_ty!(
Element,
$ty_name,
vars: <C, OA,>,
vars_on_ty: <C,>,
bounds: {
OA: OptionalAction<A>,
C: Fn(&mut T, web_sys::$web_sys_ty ) -> OA,
}
);

pub struct $ty_name<T, A, ET, C> {
target: ET,
pub struct $ty_name<T, A, E, C> {
target: E,
callback: C,
options: EventListenerOptions,
phantom: PhantomData<fn() -> (T, A)>,
}

impl<T, A, ET, C> $ty_name<T, A, ET, C> {
pub fn new(target: ET, callback: C) -> Self {
impl<T, A, E, C> $ty_name<T, A, E, C> {
pub fn new(target: E, callback: C) -> Self {
Self {
target,
options: Default::default(),
Expand All @@ -238,18 +232,18 @@ macro_rules! event_definitions {
}
}

impl<T, A, ET, C> ViewMarker for $ty_name<T, A, ET, C> {}
impl<T, A, ET, C> Sealed for $ty_name<T, A, ET, C> {}
impl<T, A, E, C> ViewMarker for $ty_name<T, A, E, C> {}
impl<T, A, E, C> Sealed for $ty_name<T, A, E, C> {}

impl<T, A, C, ET, OA> View<T, A> for $ty_name<T, A, ET, C>
impl<T, A, E, C,OA> View<T, A> for $ty_name<T, A, E, C>
where
OA: OptionalAction<A>,
C: Fn(&mut T, web_sys::$web_sys_ty) -> OA,
ET: Element<T, A>,
E: Element<T, A>,
{
type State = OnEventState<ET::State>;
type State = OnEventState<E::State>;

type Element = ET::Element;
type Element = E::Element;

fn build(&self, cx: &mut Cx) -> (Id, Self::State, Self::Element) {
let (id, (element, state)) = cx.with_new_id(|cx| {
Expand Down
Loading

0 comments on commit 02adbe5

Please sign in to comment.