diff --git a/crates/xilem_html/src/element/elements.rs b/crates/xilem_html/src/element/elements.rs index cdd3826f9..a02aa007d 100644 --- a/crates/xilem_html/src/element/elements.rs +++ b/crates/xilem_html/src/element/elements.rs @@ -15,7 +15,6 @@ use wasm_bindgen::JsCast; use crate::{event::EventMsg, Event, Pod}; use wasm_bindgen::UnwrapThrowExt; -use web_sys::console::log_1 as console_log; macro_rules! debug_warn { ($($arg:tt)*) => {{ @@ -24,13 +23,6 @@ macro_rules! debug_warn { }} } -macro_rules! debug_log { - ($($arg:tt)*) => {{ - #[cfg(debug_assertions)] - web_sys::console::log_1(&format!($($arg)*).into()); - }} -} - use super::{remove_attribute, set_attribute}; macro_rules! elements { () => {}; @@ -263,17 +255,8 @@ elements!( (Template, template, web_sys::HtmlTemplateElement), ); -// TODO think about using serialized values instead of Box for smaller compilation size (but likely worse performance) -// TODO consider enum for common attribute types and Box as fallback for more exotic cases -// TODO consider Vec<(&'static str, Box)> instead of BTreeMap (should likely be faster than BTreeMap for very few attributes (~ < 10-20 attributes)) -type Attrs = BTreeMap<&'static str, Box>; - -type CowStr = Cow<'static, str>; - pub struct VecMap(Vec<(K, V)>); -type AttrsNew = VecMap; - impl Default for VecMap { fn default() -> Self { Self(Vec::new()) @@ -339,12 +322,6 @@ impl VecMap { } } -impl AttrsNew { - fn insert_untyped(&mut self, name: impl Into, value: impl Into) { - self.insert(name.into(), AttributeValue::String(value.into())); - } -} - pub fn diff_tree_maps<'a, K: Ord, V: PartialEq>( prev: &'a BTreeMap, next: &'a BTreeMap, @@ -404,12 +381,15 @@ pub enum Diff { Change(K, V), } +type CowStr = Cow<'static, str>; + // TODO in the future it's likely there's an element that doesn't implement PartialEq, // but for now it's simpler for diffing, maybe also use some kind of serialization in that case #[derive(PartialEq, Debug)] pub enum AttributeValue { U32(u32), I32(i32), + F32(f32), F64(f64), String(CowStr), // for classes mostly @@ -431,6 +411,7 @@ impl AttributeValue { match self { AttributeValue::U32(n) => n.to_string().into(), AttributeValue::I32(n) => n.to_string().into(), + AttributeValue::F32(n) => n.to_string().into(), AttributeValue::F64(n) => n.to_string().into(), AttributeValue::String(s) => s.clone(), // currently just concatenates strings with spaces in between, @@ -451,27 +432,58 @@ impl AttributeValue { } } -const UNTYPED_ATTRS: &str = "____untyped_attrs____"; - -/// returns all attribute keys including untyped attributes -fn attr_keys(attrs: &Attrs) -> impl Iterator { - attrs.keys().copied().filter(|a| *a != UNTYPED_ATTRS).chain( - attrs - .get(UNTYPED_ATTRS) - .map(|untyped_attrs| { - untyped_attrs - .downcast_ref::>() - .unwrap() - .keys() - .map(|k| k.as_str()) - }) - .into_iter() - .flatten(), - ) +impl From for AttributeValue { + fn from(value: u32) -> Self { + AttributeValue::U32(value) + } } -pub trait Node { - fn node_name(&self) -> &str; +impl From for AttributeValue { + fn from(value: i32) -> Self { + AttributeValue::I32(value) + } +} + +impl From for AttributeValue { + fn from(value: f32) -> Self { + AttributeValue::F32(value) + } +} + +impl From for AttributeValue { + fn from(value: f64) -> Self { + AttributeValue::F64(value) + } +} + +impl From for AttributeValue { + fn from(value: String) -> Self { + AttributeValue::String(value.into()) + } +} + +impl From for AttributeValue { + fn from(value: CowStr) -> Self { + AttributeValue::String(value) + } +} + +impl From<&'static str> for AttributeValue { + fn from(value: &'static str) -> Self { + AttributeValue::String(value.into()) + } +} + +type Attrs = VecMap; + +impl Attrs { + fn insert_untyped(&mut self, name: impl Into, value: impl Into) { + self.insert(name.into(), AttributeValue::String(value.into())); + } + + fn insert_attr(&mut self, name: impl Into, value: impl Into) { + self.insert(name.into(), value.into()); + } } pub trait EventHandler { @@ -533,289 +545,62 @@ impl A> EventHandler for F { } } -// TODO might be useful, but is currently not needed -// // type erased event handler -// trait AnyEventHandler { -// fn as_any(&self) -> &dyn std::any::Any; - -// fn dyn_build(&self, cx: &mut crate::context::Cx) -> (xilem_core::Id, Box); - -// // TODO should id be mutable like in View::rebuild? -// fn dyn_rebuild( -// &self, -// cx: &mut crate::context::Cx, -// prev: &dyn AnyEventHandler, -// id: &mut xilem_core::Id, -// state: &mut Box, -// ) -> crate::ChangeFlags; - -// fn dyn_message( -// &self, -// id_path: &[xilem_core::Id], -// state: &mut dyn Any, -// message: Box, -// app_state: &mut T, -// ) -> xilem_core::MessageResult; -// } - -// impl AnyEventHandler for EH -// where -// EH: EventHandler + 'static, -// EH::State: 'static, -// { -// fn as_any(&self) -> &dyn std::any::Any { -// self -// } - -// fn dyn_build(&self, cx: &mut crate::context::Cx) -> (xilem_core::Id, Box) { -// let (id, state) = self.build(cx); -// (id, Box::new(state)) -// } - -// fn dyn_rebuild( -// &self, -// cx: &mut crate::context::Cx, -// prev: &dyn AnyEventHandler, -// id: &mut xilem_core::Id, -// state: &mut Box, -// ) -> crate::ChangeFlags { -// if let Some(prev) = prev.as_any().downcast_ref() { -// if let Some(state) = state.downcast_mut() { -// self.rebuild(cx, prev, id, state) -// } else { -// // TODO warning -// // eprintln!("downcast of state failed in dyn_rebuild"); -// crate::ChangeFlags::default() -// } -// } else { -// let (new_id, new_state) = self.build(cx); -// *id = new_id; -// *state = Box::new(new_state); -// crate::ChangeFlags::tree_structure() -// } -// } - -// fn dyn_message( -// &self, -// id_path: &[xilem_core::Id], -// state: &mut dyn std::any::Any, -// message: Box, -// app_state: &mut T, -// ) -> xilem_core::MessageResult { -// if let Some(state) = state.downcast_mut() { -// self.message(id_path, state, message, app_state) -// } else { -// // TODO warning -// // panic!("downcast error in dyn_event"); -// xilem_core::MessageResult::Stale(message) -// } -// } -// } - -pub trait ElementNew: Node + crate::view::View { - // TODO rename to class (currently conflicts with `ViewExt`) - fn classes(self, class: C) -> Self; - // TODO rename to class (currently conflicts with `ViewExt`) - fn add_classes(&mut self, class: C); - // TODO should this be in its own trait? (it doesn't have much to do with the DOM Node interface) - fn raw_attrs(&self) -> &AttrsNew; - // TODO should this be in Node? - fn attr, V: Into>(self, key: K, value: V) -> Self; - fn set_attr, V: Into>(&mut self, key: K, value: V); - - fn onclick(self, handler: EH) -> Self - where - T: 'static, - A: 'static, - EH: EventHandler> + 'static; - - fn onscroll(self, handler: EH) -> Self - where - T: 'static, - A: 'static, - EH: EventHandler> + 'static; -} - -type EventListenersState = Vec<(xilem_core::Id, Box)>; - -pub struct MyElementState { - children_states: ViewSeqState, - children_elements: Vec, - event_listener_state: EventListenersState, - scratch: Vec, -} - -// TODO not sure how much it helps reducing the code size, -// but the two attributes could be extracted into its own type, and the actual element type is just a single tuple struct wrapping this type, -pub struct MyHtmlElement { - pub(crate) attrs: AttrsNew, - // TODO maybe there's a better dynamic trait for this (event handlers can contain different event types...) - // event_listeners: VecMap, DynamicEventListener>, - event_listeners: Vec>, - children: VS, - phantom: std::marker::PhantomData (T, A)>, -} - -impl Node for MyHtmlElement { - fn node_name(&self) -> &str { - "address" - } +struct EventListener { + #[allow(clippy::complexity)] + phantom: PhantomData (T, A, E, El)>, + event: &'static str, + options: EventListenerOptions, + event_handler: EH, } -fn impl_build_element( - cx: &mut crate::context::Cx, - id: xilem_core::Id, - node_name: &str, - attrs: &AttrsNew, - children: &Vec, - event_listeners: &[DynamicEventListener], -) -> (web_sys::HtmlElement, EventListenersState) { - cx.with_id(id, |cx| { - let el = cx.create_html_element(node_name); - - for (name, value) in attrs.iter() { - el.set_attribute(name, &value.as_cow()).unwrap_throw(); - } - - for child in children { - el.append_child(child.0.as_node_ref()).unwrap_throw(); - } - - let event_listener_state = event_listeners - .iter() - .map(|listener| listener.build(&el, cx)) - .collect(); - - // Set the id used internally to the `data-debugid` attribute. - // This allows the user to see if an element has been re-created or only altered. - #[cfg(debug_assertions)] - el.set_attribute("data-debugid", &id.to_raw().to_string()) - .unwrap_throw(); - - (el, event_listener_state) - }) +struct EventListenerState { + #[allow(unused)] + listener: gloo::events::EventListener, + handler_id: xilem_core::Id, + handler_state: EHS, } -#[allow(clippy::too_many_arguments)] -fn impl_rebuild_element( - cx: &mut crate::context::Cx, - attrs: &AttrsNew, - prev_attrs: &AttrsNew, - element: &web_sys::Element, - prev_event_listeners: &[DynamicEventListener], - event_listeners: &[DynamicEventListener], - event_listeners_state: &mut EventListenersState, - mut children_changed: crate::ChangeFlags, - children: &[Pod], -) -> crate::ChangeFlags { - use crate::ChangeFlags; - let mut changed = ChangeFlags::empty(); - - // diff attributes - for itm in prev_attrs.diff(attrs) { - match itm { - Diff::Add(name, value) | Diff::Change(name, value) => { - set_attribute(element, name, &value.as_cow()); - changed |= ChangeFlags::OTHER_CHANGE; - } - Diff::Remove(name) => { - remove_attribute(element, name); - changed |= ChangeFlags::OTHER_CHANGE; - } - } - } - - if children_changed.contains(ChangeFlags::STRUCTURE) { - // This is crude and will result in more DOM traffic than needed. - // The right thing to do is diff the new state of the children id - // vector against the old, and derive DOM mutations from that. - while let Some(child) = element.first_child() { - element.remove_child(&child).unwrap_throw(); - } - for child in children { - element.append_child(child.0.as_node_ref()).unwrap_throw(); - } - children_changed.remove(ChangeFlags::STRUCTURE); - } - - for ((listener, listener_prev), (listener_id, listener_state)) in event_listeners - .iter() - .zip(prev_event_listeners.iter()) - .zip(event_listeners_state.iter_mut()) - { - let listener_changed = - listener.rebuild(element, cx, listener_prev, listener_id, listener_state); - changed |= listener_changed; - } - - let cur_listener_len = event_listeners.len(); - let state_len = event_listeners_state.len(); - - #[allow(clippy::comparison_chain)] - if cur_listener_len < state_len { - event_listeners_state.truncate(cur_listener_len); - changed |= ChangeFlags::STRUCTURE; - } else if cur_listener_len > state_len { - for listener in &event_listeners[state_len..cur_listener_len] { - event_listeners_state.push(listener.build(element, cx)); +impl EventListener +where + E: JsCast + 'static, + El: 'static, + EH: EventHandler>, +{ + fn new(event: &'static str, event_handler: EH, options: EventListenerOptions) -> Self { + EventListener { + phantom: PhantomData, + event, + options, + event_handler, } - changed |= ChangeFlags::STRUCTURE; } - changed -} -fn impl_message_element( - id_path: &[xilem_core::Id], - event_listeners: &[DynamicEventListener], - event_listeners_state: &mut EventListenersState, - message: Box, - app_state: &mut T, -) -> xilem_core::MessageResult { - if let Some((first, rest_path)) = id_path.split_first() { - if let Some((idx, (_, listener_state))) = event_listeners_state - .iter_mut() - .enumerate() - .find(|(_, (id, _))| id == first) - { - let listener = &event_listeners[idx]; - return listener.message(rest_path, listener_state.as_mut(), message, app_state); - } - } - xilem_core::MessageResult::Stale(message) -} + fn build( + &self, + cx: &mut crate::context::Cx, + event_target: &web_sys::EventTarget, + ) -> (xilem_core::Id, EventListenerState) { + cx.with_new_id(|cx| { + let thunk = cx.message_thunk(); + let listener = gloo::events::EventListener::new_with_options( + event_target, + self.event, + self.options, + move |event: &web_sys::Event| { + let event = (*event).clone().dyn_into::().unwrap_throw(); + let event: Event = Event::new(event); + thunk.push_message(EventMsg { event }); + }, + ); -impl crate::view::ViewMarker for MyHtmlElement {} + let (handler_id, handler_state) = self.event_handler.build(cx); -impl crate::view::View for MyHtmlElement -where - VS: crate::view::ViewSequence, -{ - type State = MyElementState; - type Element = web_sys::HtmlElement; - - fn build(&self, cx: &mut crate::context::Cx) -> (xilem_core::Id, Self::State, Self::Element) { - // TODO remove - debug_log!("new element built: {}", self.node_name()); - - let mut children_elements = vec![]; - let (id, children_states) = - cx.with_new_id(|cx| self.children.build(cx, &mut children_elements)); - let (el, event_listener_state) = impl_build_element( - cx, - id, - self.node_name(), - &self.attrs, - &children_elements, - &self.event_listeners, - ); - - let state = MyElementState { - children_states, - children_elements, - event_listener_state, - scratch: vec![], - }; - (id, state, el) + EventListenerState { + listener, + handler_id, + handler_state, + } + }) } fn rebuild( @@ -823,114 +608,50 @@ where cx: &mut crate::context::Cx, prev: &Self, id: &mut xilem_core::Id, - state: &mut Self::State, - element: &mut Self::Element, + state: &mut EventListenerState, + event_target: &web_sys::EventTarget, ) -> crate::ChangeFlags { - debug_assert!(prev.node_name() == self.node_name()); - - cx.with_id(*id, |cx| { - let mut splice = - xilem_core::VecSplice::new(&mut state.children_elements, &mut state.scratch); - let children_changed = - self.children - .rebuild(cx, &prev.children, &mut state.children_states, &mut splice); - impl_rebuild_element( - cx, - &self.attrs, - &prev.attrs, - element, - &prev.event_listeners, - &self.event_listeners, - &mut state.event_listener_state, - children_changed, - &state.children_elements, - ) - }) - } - - fn message( - &self, - id_path: &[xilem_core::Id], - state: &mut Self::State, + if prev.event != self.event + || self.options.passive != prev.options.passive + || matches!(self.options.phase, EventListenerPhase::Bubble) + != matches!(prev.options.phase, EventListenerPhase::Bubble) + { + let (new_id, new_state) = self.build(cx, event_target); + *id = new_id; + *state = new_state; + crate::ChangeFlags::STRUCTURE + } else { + self.event_handler.rebuild( + cx, + &prev.event_handler, + &mut state.handler_id, + &mut state.handler_state, + ) + } + } + + fn message( + &self, + id_path: &[xilem_core::Id], + state: &mut EventListenerState, message: Box, app_state: &mut T, ) -> xilem_core::MessageResult { - debug_assert!(state.event_listener_state.len() == self.event_listeners.len()); - impl_message_element( - id_path, - &self.event_listeners, - &mut state.event_listener_state, - message, - app_state, - ) - .or(|message| { - self.children - .message(id_path, &mut state.children_states, message, app_state) - }) - } -} - -/// Builder function for a my_element element view. -pub fn my_element(children: VS) -> MyHtmlElement -where - VS: crate::view::ViewSequence, -{ - MyHtmlElement { - attrs: Default::default(), - children, - phantom: std::marker::PhantomData, - event_listeners: Default::default(), - } -} - -impl ElementNew for MyHtmlElement -where - VS: crate::view::ViewSequence, -{ - fn classes(mut self, class: C) -> Self { - add_class_new(&mut self.attrs, class); - self - } - - fn add_classes(&mut self, class: C) { - add_class_new(&mut self.attrs, class); - } - - fn raw_attrs(&self) -> &AttrsNew { - todo!() - } - - fn attr, V: Into>(mut self, key: K, value: V) -> Self { - self.attrs.insert_untyped(key, value); - self - } - - fn set_attr, V: Into>(&mut self, key: K, value: V) { - self.attrs.insert_untyped(key, value); - } - - fn onclick(mut self, handler: EH) -> Self - where - T: 'static, - A: 'static, - EH: EventHandler> + 'static, // V::Element, but this results in better docs - { - let listener = EventListener::new("click", handler, Default::default()); - self.event_listeners - .push(DynamicEventListener::new(listener)); - self - } - - fn onscroll(mut self, handler: EH) -> Self - where - T: 'static, - A: 'static, - EH: EventHandler> + 'static, // V::Element, but this results in better docs - { - let listener = EventListener::new("scroll", handler, Default::default()); - self.event_listeners - .push(DynamicEventListener::new(listener)); - self + if id_path.is_empty() { + return self + .event_handler + .message(&[], &mut state.handler_state, message, app_state); + } else if let Some((first, rest_path)) = id_path.split_first() { + if *first == state.handler_id { + return self.event_handler.message( + rest_path, + &mut state.handler_state, + message, + app_state, + ); + } + } + xilem_core::MessageResult::Stale(message) } } @@ -1057,114 +778,139 @@ impl DynamicEventListener { } } -struct EventListener { - #[allow(clippy::complexity)] - phantom: PhantomData (T, A, E, El)>, - event: &'static str, - options: EventListenerOptions, - event_handler: EH, -} +type EventListenersState = Vec<(xilem_core::Id, Box)>; -struct EventListenerState { - #[allow(unused)] - listener: gloo::events::EventListener, - handler_id: xilem_core::Id, - handler_state: EHS, +pub struct ElementState { + children_states: ViewSeqState, + children_elements: Vec, + event_listener_state: EventListenersState, + scratch: Vec, } -impl EventListener -where - E: JsCast + 'static, - El: 'static, - EH: EventHandler>, -{ - fn new(event: &'static str, event_handler: EH, options: EventListenerOptions) -> Self { - EventListener { - phantom: PhantomData, - event, - options, - event_handler, +fn impl_build_element( + cx: &mut crate::context::Cx, + id: xilem_core::Id, + node_name: &str, + attrs: &Attrs, + children: &Vec, + event_listeners: &[DynamicEventListener], +) -> (web_sys::HtmlElement, EventListenersState) { + cx.with_id(id, |cx| { + let el = cx.create_html_element(node_name); + + for (name, value) in attrs.iter() { + el.set_attribute(name, &value.as_cow()).unwrap_throw(); } - } - fn build( - &self, - cx: &mut crate::context::Cx, - event_target: &web_sys::EventTarget, - ) -> (xilem_core::Id, EventListenerState) { - cx.with_new_id(|cx| { - let thunk = cx.message_thunk(); - let listener = gloo::events::EventListener::new_with_options( - event_target, - self.event, - self.options, - move |event: &web_sys::Event| { - let event = (*event).clone().dyn_into::().unwrap_throw(); - let event: Event = Event::new(event); - thunk.push_message(EventMsg { event }); - }, - ); + for child in children { + el.append_child(child.0.as_node_ref()).unwrap_throw(); + } - let (handler_id, handler_state) = self.event_handler.build(cx); + let event_listener_state = event_listeners + .iter() + .map(|listener| listener.build(&el, cx)) + .collect(); - EventListenerState { - listener, - handler_id, - handler_state, + // Set the id used internally to the `data-debugid` attribute. + // This allows the user to see if an element has been re-created or only altered. + #[cfg(debug_assertions)] + el.set_attribute("data-debugid", &id.to_raw().to_string()) + .unwrap_throw(); + + (el, event_listener_state) + }) +} + +#[allow(clippy::too_many_arguments)] +fn impl_rebuild_element( + cx: &mut crate::context::Cx, + attrs: &Attrs, + prev_attrs: &Attrs, + element: &web_sys::Element, + prev_event_listeners: &[DynamicEventListener], + event_listeners: &[DynamicEventListener], + event_listeners_state: &mut EventListenersState, + mut children_changed: crate::ChangeFlags, + children: &[Pod], +) -> crate::ChangeFlags { + use crate::ChangeFlags; + let mut changed = ChangeFlags::empty(); + + // diff attributes + for itm in prev_attrs.diff(attrs) { + match itm { + Diff::Add(name, value) | Diff::Change(name, value) => { + set_attribute(element, name, &value.as_cow()); + changed |= ChangeFlags::OTHER_CHANGE; } - }) + Diff::Remove(name) => { + remove_attribute(element, name); + changed |= ChangeFlags::OTHER_CHANGE; + } + } } - fn rebuild( - &self, - cx: &mut crate::context::Cx, - prev: &Self, - id: &mut xilem_core::Id, - state: &mut EventListenerState, - event_target: &web_sys::EventTarget, - ) -> crate::ChangeFlags { - if prev.event != self.event - || self.options.passive != prev.options.passive - || matches!(self.options.phase, EventListenerPhase::Bubble) - != matches!(prev.options.phase, EventListenerPhase::Bubble) - { - let (new_id, new_state) = self.build(cx, event_target); - *id = new_id; - *state = new_state; - crate::ChangeFlags::STRUCTURE - } else { - self.event_handler.rebuild( - cx, - &prev.event_handler, - &mut state.handler_id, - &mut state.handler_state, - ) + if children_changed.contains(ChangeFlags::STRUCTURE) { + // This is crude and will result in more DOM traffic than needed. + // The right thing to do is diff the new state of the children id + // vector against the old, and derive DOM mutations from that. + while let Some(child) = element.first_child() { + element.remove_child(&child).unwrap_throw(); + } + for child in children { + element.append_child(child.0.as_node_ref()).unwrap_throw(); } + children_changed.remove(ChangeFlags::STRUCTURE); } - fn message( - &self, - id_path: &[xilem_core::Id], - state: &mut EventListenerState, - message: Box, - app_state: &mut T, - ) -> xilem_core::MessageResult { - if id_path.is_empty() { - return self - .event_handler - .message(&[], &mut state.handler_state, message, app_state); - } else if let Some((first, rest_path)) = id_path.split_first() { - if *first == state.handler_id { - return self.event_handler.message( - rest_path, - &mut state.handler_state, - message, - app_state, - ); - } + for ((listener, listener_prev), (listener_id, listener_state)) in event_listeners + .iter() + .zip(prev_event_listeners.iter()) + .zip(event_listeners_state.iter_mut()) + { + let listener_changed = + listener.rebuild(element, cx, listener_prev, listener_id, listener_state); + changed |= listener_changed; + } + + let cur_listener_len = event_listeners.len(); + let state_len = event_listeners_state.len(); + + #[allow(clippy::comparison_chain)] + if cur_listener_len < state_len { + event_listeners_state.truncate(cur_listener_len); + changed |= ChangeFlags::STRUCTURE; + } else if cur_listener_len > state_len { + for listener in &event_listeners[state_len..cur_listener_len] { + event_listeners_state.push(listener.build(element, cx)); + } + changed |= ChangeFlags::STRUCTURE; + } + changed | children_changed +} + +fn impl_message_element( + id_path: &[xilem_core::Id], + event_listeners: &[DynamicEventListener], + event_listeners_state: &mut EventListenersState, + message: Box, + app_state: &mut T, +) -> xilem_core::MessageResult { + if let Some((first, rest_path)) = id_path.split_first() { + if let Some((idx, (_, listener_state))) = event_listeners_state + .iter_mut() + .enumerate() + .find(|(_, (id, _))| id == first) + { + let listener = &event_listeners[idx]; + return listener.message(rest_path, listener_state.as_mut(), message, app_state); } - xilem_core::MessageResult::Stale(message) } + xilem_core::MessageResult::Stale(message) +} + +pub trait Node { + fn node_name(&self) -> &str; } /// These traits should mirror the respective DOM interfaces @@ -1174,16 +920,45 @@ where /// /// I'm generally not sure yet, if it makes sense to do all of this via traits (there are advantages though, but it's (likely) not compilation size...) /// (thinking about AsRef and AsMut, and implementing all builder methods on the concrete type, similar as with wasm-bindgen) -pub trait Element: Node { +/// but on the other hand most of these methods should inline to the non-generic Attrs::insert_attr (or similar non-generic impls) +pub trait Element: Node + crate::view::View { // TODO rename to class (currently conflicts with `ViewExt`) + // TODO should classes be additive? Currently they are. fn classes(self, class: C) -> Self; - // TODO rename to class (currently conflicts with `ViewExt`) - fn add_classes(&mut self, class: C); + fn add_class(&mut self, class: C); // TODO should this be in its own trait? (it doesn't have much to do with the DOM Node interface) fn raw_attrs(&self) -> &Attrs; // TODO should this be in Node? - fn attr, V: Into>(self, key: K, value: V) -> Self; - fn set_attr, V: Into>(&mut self, key: K, value: V); + fn attr, V: Into>(self, key: K, value: V) -> Self; + fn set_attr, V: Into>(&mut self, key: K, value: V); + + // TODO generate all this event listener boilerplate with macros + fn on_click(self, handler: EH) -> Self + where + T: 'static, + A: 'static, + EH: EventHandler> + 'static; + + fn on_click_with_options(self, handler: EH, options: EventListenerOptions) -> Self + where + T: 'static, + A: 'static, + EH: EventHandler> + 'static; + + fn on_scroll(self, handler: EH) -> Self + where + Self: Sized, + T: 'static, + A: 'static, + EH: EventHandler> + 'static; + + fn on_scroll_with_options(self, handler: EH, options: EventListenerOptions) -> Self + where + T: 'static, + A: 'static, + EH: EventHandler> + 'static; + + // TODO rest of all the methods allowed on an element } pub trait HtmlElement: Element {} @@ -1207,62 +982,7 @@ pub trait HtmlCanvasElement: HtmlElement { fn set_height(&mut self, height: u32); } -fn simple_attr_impl( - attrs: &mut BTreeMap<&'static str, Box>, - key: &'static str, - value: T, -) { - match attrs.entry(key) { - std::collections::btree_map::Entry::Vacant(entry) => { - entry.insert(Box::new(value)); - } - std::collections::btree_map::Entry::Occupied(class_attr) => { - *class_attr.into_mut().downcast_mut::().unwrap() = value; - } - }; -} - -macro_rules! impl_simple_attr_new { - ($name:ident, $setter_name: ident, $internal_fn: ident, $ty: ty, $el: ident) => { - #[inline(always)] - fn $name(mut self, $name: $ty) -> $el { - $internal_fn(&mut self.attrs, stringify!($name), $name); - self - } - - #[inline(always)] - fn $setter_name(&mut self, $name: $ty) { - $internal_fn(&mut self.attrs, stringify!($name), $name); - } - }; -} - -macro_rules! impl_simple_attr { - ($name:ident, $setter_name: ident, $ty: ty, $el: ident) => { - #[inline(always)] - fn $name(mut self, $name: $ty) -> $el { - simple_attr_impl(&mut self.attrs, stringify!($name), $name); - self - } - - #[inline(always)] - fn $setter_name(&mut self, $name: $ty) { - simple_attr_impl(&mut self.attrs, stringify!($name), $name); - } - }; -} - -macro_rules! impl_node { - ($ty_name:ident, $name: ident) => { - impl Node for $ty_name { - fn node_name(&self) -> &str { - stringify!($name) - } - } - }; -} - -fn add_class_new(attrs: &mut AttrsNew, class: C) { +fn add_class(attrs: &mut Attrs, class: C) { let mut classes = class.classes().peekable(); if classes.peek().is_none() { @@ -1297,37 +1017,17 @@ fn add_class_new(attrs: &mut AttrsNew, class: C) { }; } -fn add_class(attrs: &mut Attrs, class: C) { - match attrs.entry("class") { - std::collections::btree_map::Entry::Vacant(entry) => { - let classes = BTreeSet::from_iter(class.classes()); - if !classes.is_empty() { - entry.insert(Box::new(classes)); - } - } - std::collections::btree_map::Entry::Occupied(class_attr) => { - class_attr - .into_mut() - .downcast_mut::>() - .unwrap() - .extend(class.classes()); +macro_rules! impl_simple_attr { + ($name:ident, $setter_name: ident, $ty: ty, $el: ident) => { + #[inline(always)] + fn $name(mut self, $name: $ty) -> $el { + self.attrs.insert_attr(stringify!($name), $name); + self } - }; -} -// TODO currently untyped attributes are overwritten by typed ones, -// if they're defined, maybe (some) can be merged nicely -fn add_untyped_attr(attrs: &mut Attrs, key: String, value: String) { - match attrs.entry(UNTYPED_ATTRS) { - std::collections::btree_map::Entry::Vacant(entry) => { - entry.insert(Box::new(BTreeMap::from([(key, value)]))); - } - std::collections::btree_map::Entry::Occupied(attrs) => { - attrs - .into_mut() - .downcast_mut::>() - .unwrap() - .insert(key, value); + #[inline(always)] + fn $setter_name(&mut self, $name: $ty) { + self.attrs.insert_attr(stringify!($name), $name); } }; } @@ -1336,16 +1036,17 @@ fn add_untyped_attr(attrs: &mut Attrs, key: String, value: String) { // it might make sense to add an extra inner impl function if possible // (see below at `simple_attr_impl` for an example) to avoid big compilation code size macro_rules! impl_element { - ($ty_name:ident, $name: ident) => { - impl_node!($ty_name, $name); - - impl Element for $ty_name { - fn classes(mut self, class: C) -> $ty_name { + ($ty_name:ident, $name: ident, $concrete_dom_interface: ident) => { + impl Element for $ty_name + where + VS: crate::view::ViewSequence, + { + fn classes(mut self, class: C) -> Self { add_class(&mut self.attrs, class); self } - fn add_classes(&mut self, class: C) { + fn add_class(&mut self, class: C) { add_class(&mut self.attrs, class); } @@ -1353,295 +1054,137 @@ macro_rules! impl_element { &self.attrs } - fn set_attr, V: Into>(&mut self, key: K, value: V) { - add_untyped_attr(&mut self.attrs, key.into(), value.into()); - } - - fn attr, V: Into>( + fn attr, V: Into>( mut self, key: K, value: V, ) -> $ty_name { - add_untyped_attr(&mut self.attrs, key.into(), value.into()); + self.attrs.insert_untyped(key, value); self } - } - }; -} - -macro_rules! impl_html_element { - ($ty_name:ident, $name: ident) => { - impl_element!($ty_name, $name); - impl HtmlElement for $ty_name {} - }; -} - -macro_rules! generate_html_element_derivation { - ($ty_name:ident, $name:ident, $dom_interface:ident) => { - generate_html_element_derivation!($ty_name, $name, $dom_interface, {}); - }; - ($ty_name:ident, $name:ident, $dom_interface:ident, $body: tt) => { - impl_html_element!($ty_name, $name); + fn set_attr, V: Into>(&mut self, key: K, value: V) { + self.attrs.insert_untyped(key, value); + } - impl $dom_interface for $ty_name $body - }; -} + fn on_click(self, handler: EH) -> $ty_name + where + T: 'static, + A: 'static, + EH: EventHandler< + T, + A, + crate::Event, + > + 'static, // V::Element, but this results in better docs + { + self.on_click_with_options(handler, EventListenerOptions::default()) + } -macro_rules! impl_html_dom_interface { - ($ty_name: ident, $name: ident, Element) => { - impl_element!($ty_name, $name); - }; - ($ty_name: ident, $name: ident, HtmlElement) => { - impl_html_element!($ty_name, $name); - }; - ($ty_name: ident, $name: ident, HtmlDivElement) => { - generate_html_element_derivation!($ty_name, $name, HtmlDivElement); - }; - ($ty_name: ident, $name: ident, HtmlSpanElement) => { - generate_html_element_derivation!($ty_name, $name, HtmlSpanElement); - }; - ($ty_name: ident, $name: ident, HtmlHeadingElement) => { - generate_html_element_derivation!($ty_name, $name, HtmlHeadingElement); - }; - ($ty_name: ident, $name: ident, HtmlCanvasElement) => { - generate_html_element_derivation!($ty_name, $name, HtmlCanvasElement, { - impl_simple_attr!(width, set_width, u32, $ty_name); - impl_simple_attr!(height, set_height, u32, $ty_name); - }); - }; -} + fn on_click_with_options( + mut self, + handler: EH, + options: EventListenerOptions, + ) -> $ty_name + where + T: 'static, + A: 'static, + EH: EventHandler< + T, + A, + crate::Event, + > + 'static, // V::Element, but this results in better docs + { + let listener = EventListener::new("click", handler, options); + self.event_listeners + .push(DynamicEventListener::new(listener)); + self + } -fn serialize_attr<'a, T, F>( - attrs: &Attrs, - key: &'a str, - attr_map: &mut BTreeMap<&'a str, String>, - serialize_fn: F, -) where - T: PartialEq + 'static, - F: Fn(&T) -> String, -{ - if let Some(attr) = attrs.get(key) { - let attr = attr.downcast_ref::().unwrap(); - let serialized_attr = serialize_fn(attr); - attr_map.insert(key, serialized_attr); - } -} + fn on_scroll(self, handler: EH) -> $ty_name + where + T: 'static, + A: 'static, + EH: EventHandler< + T, + A, + crate::Event, + > + 'static, // V::Element, but this results in better docs + { + self.on_scroll_with_options(handler, EventListenerOptions::default()) + } -fn serialize_classes(classes: &BTreeSet) -> String { - classes.iter().fold(String::new(), |mut acc, s| { - if !acc.is_empty() { - acc += " "; - } - if !s.is_empty() { - acc += s; + fn on_scroll_with_options( + mut self, + handler: EH, + options: EventListenerOptions, + ) -> $ty_name + where + T: 'static, + A: 'static, + EH: EventHandler< + T, + A, + crate::Event, + > + 'static, // V::Element, but this results in better docs + { + let listener = EventListener::new("scroll", handler, options); + self.event_listeners + .push(DynamicEventListener::new(listener)); + self + } } - acc - }) -} - -fn serialize_element_attrs<'a>(attrs: &'a Attrs, attr_map: &mut BTreeMap<&'a str, String>) { - if let Some(untyped_attrs) = attrs - .get(UNTYPED_ATTRS) - .and_then(|attrs| attrs.downcast_ref::>()) - { - attr_map.extend(untyped_attrs.iter().map(|(k, v)| (k.as_str(), v.clone()))); - } - serialize_attr(attrs, "class", attr_map, serialize_classes); -} - -fn serialize_html_canvas_element_attrs<'a>( - attrs: &'a Attrs, - attr_map: &mut BTreeMap<&'a str, String>, -) { - serialize_element_attrs(attrs, attr_map); - serialize_attr(attrs, "width", attr_map, u32::to_string); - serialize_attr(attrs, "height", attr_map, u32::to_string); + }; } -macro_rules! serialize_attrs { - ($element_attrs: expr, $attr_map: expr, Element) => { - serialize_element_attrs($element_attrs, $attr_map); - }; - ($element_attrs: expr, $attr_map: expr, HtmlElement) => { - serialize_element_attrs($element_attrs, $attr_map); - }; - ($element_attrs: expr, $attr_map: expr, HtmlDivElement) => { - serialize_element_attrs($element_attrs, $attr_map); - }; - ($element_attrs: expr, $attr_map: expr, HtmlSpanElement) => { - serialize_element_attrs($element_attrs, $attr_map); - }; - ($element_attrs: expr, $attr_map: expr, HtmlHeadingElement) => { - serialize_element_attrs($element_attrs, $attr_map); +macro_rules! generate_dom_interface_impl { + ($ty_name:ident, $name:ident, $dom_interface:ident) => { + generate_dom_interface_impl!($ty_name, $name, $dom_interface, {}); }; - ($element_attrs: expr, $attr_map: expr, HtmlCanvasElement) => { - serialize_html_canvas_element_attrs($element_attrs, $attr_map); + ($ty_name:ident, $name:ident, $dom_interface:ident, $body: tt) => { + impl $dom_interface for $ty_name + where + VS: crate::view::ViewSequence, + $body }; } -fn simple_diff_attr<'a, T, F>( - old_attrs: &Attrs, - new_attrs: &'a Attrs, - key: &'a str, - new_serialized_attrs: &mut BTreeMap<&'a str, String>, - serialize_fn: F, -) where - T: PartialEq + 'static, - F: Fn(&T) -> String, -{ - match (old_attrs.get(key), new_attrs.get(key)) { - (_, None) => (), - (None, Some(new)) => { - let new = new.downcast_ref::().unwrap(); - let serialized_attr = serialize_fn(new); - new_serialized_attrs.insert(key, serialized_attr); - } - (Some(old), Some(new)) => { - let new = new.downcast_ref::().unwrap(); - if old.downcast_ref::().unwrap() != new { - let serialized_attr = serialize_fn(new); - new_serialized_attrs.insert(key, serialized_attr); +macro_rules! impl_html_dom_interface { + ($ty_name: ident, $name: ident, Node) => { + impl Node for $ty_name { + fn node_name(&self) -> &str { + stringify!($name) } } }; -} - -fn element_diff<'a>( - old: &Attrs, - new: &'a Attrs, - new_serialized_attrs: &mut BTreeMap<&'a str, String>, -) { - match (old.get(UNTYPED_ATTRS), new.get(UNTYPED_ATTRS)) { - (_, None) => (), - (None, Some(new)) => { - let new = new.downcast_ref::>().unwrap(); - new_serialized_attrs.extend(new.iter().map(|(k, v)| (k.as_str(), v.clone()))); - } - (Some(old), Some(new)) => { - let new = new.downcast_ref::>().unwrap(); - let old = old.downcast_ref::>().unwrap(); - // TODO there are likely faster ways to do this... - let new_untyped_attrs = new.iter().filter_map(|(new_name, new_value)| { - if let Some(old_value) = old.get(new_name) { - if new_value == old_value { - None - } else { - Some((new_name.as_str(), new_value.clone())) - } - } else { - Some((new_name.as_str(), new_value.clone())) - } - }); - new_serialized_attrs.extend(new_untyped_attrs); - } - }; - simple_diff_attr(old, new, "class", new_serialized_attrs, serialize_classes); -} - -#[allow(unused)] -#[inline(always)] -pub fn html_element_diff<'a>( - old: &Attrs, - new: &'a Attrs, - new_attrs: &mut BTreeMap<&'a str, String>, -) { - element_diff(old, new, new_attrs) -} - -/// returns whether attributes belonging to the HTMLCanvasElement interface are different -#[allow(unused)] -#[inline(always)] -pub fn html_canvas_element_diff<'a>( - old: &Attrs, - new: &'a Attrs, - new_serialized_attrs: &mut BTreeMap<&'a str, String>, -) { - html_element_diff(old, new, new_serialized_attrs); - simple_diff_attr(old, new, "width", new_serialized_attrs, u32::to_string); - simple_diff_attr(old, new, "height", new_serialized_attrs, u32::to_string); -} - -macro_rules! impl_rebuild_diff { - ($old: expr, $new: expr, $diff_map: expr, Element) => { - element_diff($old, $new, $diff_map); + ($ty_name: ident, $name: ident, $concrete_dom_interface: ident, Element) => { + impl_html_dom_interface!($ty_name, $name, Node); + impl_element!($ty_name, $name, $concrete_dom_interface); }; - ($old: expr, $new: expr, $diff_map: expr, HtmlElement) => { - html_element_diff($old, $new, $diff_map); + ($ty_name: ident, $name: ident, $concrete_dom_interface: ident, HtmlElement) => { + impl_html_dom_interface!($ty_name, $name, $concrete_dom_interface, Element); + generate_dom_interface_impl!($ty_name, $name, HtmlElement); }; - ($old: expr, $new: expr, $diff_map: expr, HtmlDivElement) => { - html_element_diff($old, $new, $diff_map); + ($ty_name: ident, $name: ident, $concrete_dom_interface: ident, HtmlDivElement) => { + impl_html_dom_interface!($ty_name, $name, $concrete_dom_interface, HtmlElement); + generate_dom_interface_impl!($ty_name, $name, HtmlDivElement); }; - ($old: expr, $new: expr, $diff_map: expr, HtmlSpanElement) => { - html_element_diff($old, $new, $diff_map); + ($ty_name: ident, $name: ident, $concrete_dom_interface: ident, HtmlSpanElement) => { + impl_html_dom_interface!($ty_name, $name, $concrete_dom_interface, HtmlElement); + generate_dom_interface_impl!($ty_name, $name, HtmlSpanElement); }; - ($old: expr, $new: expr, $diff_map: expr, HtmlHeadingElement) => { - html_element_diff($old, $new, $diff_map); + ($ty_name: ident, $name: ident, $concrete_dom_interface: ident, HtmlHeadingElement) => { + impl_html_dom_interface!($ty_name, $name, $concrete_dom_interface, HtmlElement); + generate_dom_interface_impl!($ty_name, $name, HtmlHeadingElement); }; - ($old: expr, $new: expr, $diff_map: expr, HtmlCanvasElement) => { - html_canvas_element_diff($old, $new, $diff_map); + ($ty_name: ident, $name: ident, $concrete_dom_interface: ident, HtmlCanvasElement) => { + impl_html_dom_interface!($ty_name, $name, $concrete_dom_interface, HtmlElement); + generate_dom_interface_impl!($ty_name, $name, HtmlCanvasElement, { + impl_simple_attr!(width, set_width, u32, $ty_name); + impl_simple_attr!(height, set_height, u32, $ty_name); + }); }; } -fn create_element( - name: &str, - id: xilem_core::Id, - attributes: &mut BTreeMap<&str, String>, - children: &Vec, - cx: &crate::context::Cx, -) -> web_sys::HtmlElement { - let el = cx.create_html_element(name); - - for (name, value) in attributes { - el.set_attribute(name, value).unwrap_throw(); - } - // Set the id used internally to the `data-debugid` attribute. - // This allows the user to see if an element has been re-created or only altered. - #[cfg(debug_assertions)] - el.set_attribute("data-debugid", &id.to_raw().to_string()) - .unwrap_throw(); - - for child in children { - el.append_child(child.0.as_node_ref()).unwrap_throw(); - } - el -} - -fn update_element( - element: &web_sys::Element, - prev_attrs: &Attrs, - new_attrs: &Attrs, - changed_attrs: &BTreeMap<&str, String>, - children_updated: bool, - children: &Vec, -) { - // TODO there's likely a faster way... - let removed_attrs = attr_keys(prev_attrs).filter(|prev_attr_name| { - !attr_keys(new_attrs).any(|new_attr_name| new_attr_name == *prev_attr_name) - && !changed_attrs.contains_key(*prev_attr_name) - }); - - for attr in removed_attrs { - remove_attribute(element, attr); - } - - for (attr, value) in changed_attrs { - set_attribute(element, attr, value); - } - - if children_updated { - // This is crude and will result in more DOM traffic than needed. - // The right thing to do is diff the new state of the children id - // vector against the old, and derive DOM mutations from that. - while let Some(child) = element.first_child() { - element.remove_child(&child).unwrap_throw(); - } - for child in children { - element.append_child(child.0.as_node_ref()).unwrap_throw(); - } - } -} - macro_rules! define_html_elements { ($(($ty_name:ident, $name:ident, $dom_interface:ident),)*) => { $( @@ -1649,7 +1192,7 @@ macro_rules! define_html_elements { // but the two attributes could be extracted into its own type, and the actual element type is just a single tuple struct wrapping this type, pub struct $ty_name { pub(crate) attrs: Attrs, - #[allow(unused)] + event_listeners: Vec>, children: VS, phantom: std::marker::PhantomData (T, A)>, } @@ -1660,30 +1203,33 @@ macro_rules! define_html_elements { where VS: crate::view::ViewSequence, { - type State = crate::ElementState; + type State = ElementState; type Element = web_sys::$dom_interface; - fn build( - &self, - cx: &mut crate::context::Cx, - ) -> (xilem_core::Id, Self::State, Self::Element) { + fn build(&self, cx: &mut crate::context::Cx) -> (xilem_core::Id, Self::State, Self::Element) { // TODO remove - console_log(&format!("new element built: {}", self.node_name()).into()); - use wasm_bindgen::UnwrapThrowExt; + // debug_log!("new element built: {}", self.node_name()); + + let mut children_elements = vec![]; + let (id, children_states) = + cx.with_new_id(|cx| self.children.build(cx, &mut children_elements)); + let (el, event_listener_state) = impl_build_element( + cx, + id, + self.node_name(), + &self.attrs, + &children_elements, + &self.event_listeners, + ); - let mut child_elements = vec![]; - let (id, child_states) = cx.with_new_id(|cx| self.children.build(cx, &mut child_elements)); - let mut attributes: BTreeMap<&'_ str, String> = BTreeMap::new(); - serialize_attrs!(&self.attrs, &mut attributes, $dom_interface); - let el = create_element(self.node_name(), id, &mut attributes, &child_elements, cx) - .dyn_into() - .unwrap_throw(); - - let state = crate::ElementState { - child_states, - child_elements, + let state = ElementState { + children_states, + children_elements, + event_listener_state, scratch: vec![], }; + use wasm_bindgen::UnwrapThrowExt; + let el = el.dyn_into().unwrap_throw(); (id, state, el) } @@ -1695,44 +1241,26 @@ macro_rules! define_html_elements { state: &mut Self::State, element: &mut Self::Element, ) -> crate::ChangeFlags { - use crate::ChangeFlags; debug_assert!(prev.node_name() == self.node_name()); - let mut new_serialized_attrs = BTreeMap::new(); - - impl_rebuild_diff!(&prev.attrs, &self.attrs, &mut new_serialized_attrs, $dom_interface); - - // TODO remove - console_log(&format!("updated element: {}, diff_attrs: {:?}", self.node_name(), new_serialized_attrs).into()); - - let mut changed = if new_serialized_attrs.is_empty() { - ChangeFlags::empty() - } else { - ChangeFlags::OTHER_CHANGE - }; - - // update children - let mut splice = xilem_core::VecSplice::new(&mut state.child_elements, &mut state.scratch); - changed |= cx.with_id(*id, |cx| { - self.children - .rebuild(cx, &prev.children, &mut state.child_states, &mut splice) - }); - - let children_updated = changed.contains(ChangeFlags::STRUCTURE); - changed.remove(ChangeFlags::STRUCTURE); - - let element = element.dyn_ref().unwrap_throw(); - - update_element( - element, - prev.raw_attrs(), - self.raw_attrs(), - &new_serialized_attrs, - children_updated, - &state.child_elements - ); - - changed + cx.with_id(*id, |cx| { + let mut splice = + xilem_core::VecSplice::new(&mut state.children_elements, &mut state.scratch); + let children_changed = + self.children + .rebuild(cx, &prev.children, &mut state.children_states, &mut splice); + impl_rebuild_element( + cx, + &self.attrs, + &prev.attrs, + element, + &prev.event_listeners, + &self.event_listeners, + &mut state.event_listener_state, + children_changed, + &state.children_elements, + ) + }) } fn message( @@ -1742,8 +1270,18 @@ macro_rules! define_html_elements { message: Box, app_state: &mut T, ) -> xilem_core::MessageResult { - self.children - .message(id_path, &mut state.child_states, message, app_state) + debug_assert!(state.event_listener_state.len() == self.event_listeners.len()); + impl_message_element( + id_path, + &self.event_listeners, + &mut state.event_listener_state, + message, + app_state, + ) + .or(|message| { + self.children + .message(id_path, &mut state.children_states, message, app_state) + }) } } @@ -1758,11 +1296,12 @@ macro_rules! define_html_elements { $ty_name { attrs: Default::default(), children, - phantom: std::marker::PhantomData + phantom: std::marker::PhantomData, + event_listeners: Default::default(), } } - impl_html_dom_interface!($ty_name, $name, $dom_interface); + impl_html_dom_interface!($ty_name, $name, $dom_interface, $dom_interface); )* }; } @@ -1894,79 +1433,44 @@ define_html_elements!( // A few experiments for more flexible attributes (el.class(class: C)) pub trait IntoClass { - type ClassIter: Iterator; - fn classes(self) -> Self::ClassIter; -} - -impl IntoClass for &str { - type ClassIter = std::option::IntoIter; - fn classes(self) -> Self::ClassIter { - Some(self.into()).into_iter() - } -} - -impl IntoClass for String { - type ClassIter = std::option::IntoIter; - fn classes(self) -> Self::ClassIter { - Some(self).into_iter() - } -} - -impl IntoClass for [T; N] { - // we really need impl - type ClassIter = - std::iter::FlatMap, T::ClassIter, fn(T) -> T::ClassIter>; - fn classes(self) -> Self::ClassIter { - self.into_iter().flat_map(IntoClass::classes) - } -} - -impl IntoClass for Vec { - type ClassIter = std::iter::FlatMap, T::ClassIter, fn(T) -> T::ClassIter>; - fn classes(self) -> Self::ClassIter { - self.into_iter().flat_map(IntoClass::classes) - } -} - -pub trait IntoClassNew { type ClassIter: Iterator; fn classes(self) -> Self::ClassIter; } -impl IntoClassNew for &'static str { +impl IntoClass for &'static str { type ClassIter = std::option::IntoIter; fn classes(self) -> Self::ClassIter { Some(self.into()).into_iter() } } -impl IntoClassNew for String { +impl IntoClass for String { type ClassIter = std::option::IntoIter; fn classes(self) -> Self::ClassIter { Some(self.into()).into_iter() } } -impl IntoClassNew for CowStr { +impl IntoClass for CowStr { type ClassIter = std::option::IntoIter; fn classes(self) -> Self::ClassIter { Some(self).into_iter() } } -impl IntoClassNew for [T; N] { +impl IntoClass for [T; N] { // we really need impl type ClassIter = std::iter::FlatMap, T::ClassIter, fn(T) -> T::ClassIter>; fn classes(self) -> Self::ClassIter { - self.into_iter().flat_map(IntoClassNew::classes) + self.into_iter().flat_map(IntoClass::classes) } } -impl IntoClassNew for Vec { +impl IntoClass for Vec { type ClassIter = std::iter::FlatMap, T::ClassIter, fn(T) -> T::ClassIter>; fn classes(self) -> Self::ClassIter { - self.into_iter().flat_map(IntoClassNew::classes) + self.into_iter().flat_map(IntoClass::classes) } }