1use crate::prelude::*;
2use futures_signals::signal::SignalExt;
3pub use hobo_derive::Element;
4use std::{
5 any::TypeId,
6 borrow::Cow,
7 collections::{HashMap, HashSet},
8};
9
10#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Element)]
12pub struct SomeElement(pub Entity);
13
14#[derive(Default)]
15pub(crate) struct Classes {
16 pub(crate) marks: HashSet<TypeId>,
17 pub(crate) styles: HashMap<u64, css::Style>,
18}
19
20#[derive(Default)]
21struct SignalHandlesCollection(Vec<discard::DiscardOnDrop<futures_signals::CancelableFutureHandle>>);
22
23pub trait Element: AsEntity + Sized {
25 fn add_child(&self, child: impl Element) {
26 if self.is_dead() { log::warn!("add_child parent dead {:?}", self.as_entity()); return; }
27 if child.is_dead() { log::warn!("add_child child dead {:?}", child.as_entity()); return; }
28 self.get_cmp_mut_or_default::<Children>().push(child.as_entity());
29 child.get_cmp_mut_or_default::<Parent>().0 = self.as_entity();
30
31 if let (Some(parent_node), Some(child_node)) = (self.try_get_cmp::<web_sys::Node>(), child.try_get_cmp::<web_sys::Node>()) {
33 parent_node.append_child(&child_node).expect("can't append child");
34 }
35 }
36 fn child(self, child: impl Element) -> Self { self.add_child(child); self }
37 fn add_children<Item: Element>(&self, children: impl IntoIterator<Item = Item>) { for child in children.into_iter() { self.add_child(child); } }
38 fn children<Item: Element>(self, children: impl IntoIterator<Item = Item>) -> Self { self.add_children(children); self }
39 fn leave_parent(self) {
40 if self.is_dead() { log::warn!("leave_parent child dead {:?}", self.as_entity()); return; }
41 let parent = self.get_cmp::<Parent>().0;
42 if parent.is_dead() { log::warn!("leave_parent parent dead {:?}", self.as_entity()); return; }
43
44 if let (Some(parent_node), Some(child_node)) = (parent.try_get_cmp::<web_sys::Node>(), self.try_get_cmp::<web_sys::Node>()) {
45 parent_node.remove_child(&child_node).expect("can't remove child");
46 }
47
48 self.remove_cmp::<Parent>();
49 let mut siblings = parent.get_cmp_mut::<Children>();
50 if let Some(child_pos) = siblings.0.iter().position(|&x| x == self.as_entity()) {
51 siblings.0.remove(child_pos);
52 }
53 }
54
55 fn add_child_at(&self, at_id: usize, child: impl Element) {
57 if self.is_dead() { log::warn!("add_child_at parent dead {:?}", self.as_entity()); return; }
58 if child.is_dead() { log::warn!("add_child_at child dead {:?}", child.as_entity()); return; }
59 let mut children = self.get_cmp_mut_or_default::<Children>();
60 let shifted_sibling = children.get(at_id).copied();
61 children.insert(at_id, child.as_entity());
62 child.get_cmp_mut_or_default::<Parent>().0 = self.as_entity();
63
64 if let (Some(parent_node), Some(child_node), shifted_sibling_node) = (
65 self.try_get_cmp::<web_sys::Node>(),
66 child.try_get_cmp::<web_sys::Node>(),
67 shifted_sibling.map(|x| x.try_get_cmp::<web_sys::Node>()).flatten(),
68 ) {
69 parent_node
70 .insert_before(&child_node, shifted_sibling_node.as_ref().map(|x| &**x as &web_sys::Node))
71 .expect("can't append child");
72 }
73 }
74
75 fn add_child_signal<S, E>(&self, signal: S) where
77 E: Element,
78 S: futures_signals::signal::Signal<Item = E> + 'static,
79 {
80 let mut child = crate::components::div().class(crate::css::Display::None).erase();
82 self.add_child(child);
83 let (handle, fut) = futures_signals::cancelable_future(signal.for_each(move |new_child| {
84 let new_child = new_child.erase();
85 child.replace_with(new_child);
86 child = new_child;
87 async move {}
88 }), || {});
89
90 wasm_bindgen_futures::spawn_local(fut);
91 self.get_cmp_mut_or_default::<SignalHandlesCollection>().0.push(handle);
92 }
93 fn child_signal<S, E>(self, signal: S) -> Self where
94 E: Element,
95 S: futures_signals::signal::Signal<Item = E> + 'static,
96 { self.add_child_signal(signal); self }
97
98 fn set_class_tagged<Tag: std::hash::Hash + 'static>(&self, tag: Tag, style: impl Into<css::Style>) {
99 if self.is_dead() { log::warn!("set_class_tagged dead {:?}", self.as_entity()); return; }
100
101 let tag_hash = {
104 use std::hash::{Hash, Hasher};
105 let mut hasher = std::collections::hash_map::DefaultHasher::new();
106 TypeId::of::<Tag>().hash(&mut hasher);
107 tag.hash(&mut hasher);
108 hasher.finish()
109 };
110
111 self.get_cmp_mut_or_default::<Classes>().styles.insert(tag_hash, style.into());
112 }
113 fn set_class_typed<Type: 'static>(&self, style: css::Style) {
115 self.set_class_tagged(TypeId::of::<Type>(), style)
116 }
117 fn set_class(&self, style: impl Into<css::Style>) { self.set_class_tagged(0u64, style); }
118 fn add_class(&self, style: impl Into<css::Style>) {
119 let id = self.try_get_cmp::<Classes>().map(|x| x.styles.len() as u64).unwrap_or(0);
120 self.set_class_tagged(id, style);
121 }
122 fn class(self, style: impl Into<css::Style>) -> Self { self.add_class(style); self }
123 fn class_tagged<Tag: std::hash::Hash + 'static>(self, tag: Tag, style: impl Into<css::Style>) -> Self { self.set_class_tagged(tag, style); self }
124 fn class_typed<Type: 'static>(self, style: css::Style) -> Self { self.set_class_typed::<Type>(style); self }
125
126 fn set_class_signal<S, I>(&self, signal: S) where
127 I: Into<css::Style>,
128 S: futures_signals::signal::Signal<Item = I> + 'static,
129 {
130 let entity = self.as_entity();
131 if entity.is_dead() { log::warn!("set_class_signal dead entity {:?}", entity); return; }
132 let (handle, fut) = futures_signals::cancelable_future(signal.for_each(move |class| {
133 SomeElement(entity).set_class(class);
134 async move { }
135 }), || {});
136
137 wasm_bindgen_futures::spawn_local(fut);
138 self.get_cmp_mut_or_default::<SignalHandlesCollection>().0.push(handle);
139 }
140 fn class_signal<S, I>(self, signal: S) -> Self where
141 I: Into<css::Style>,
142 S: futures_signals::signal::Signal<Item = I> + 'static,
143 { self.set_class_signal(signal); self }
144
145 fn set_class_typed_signal<Type, S, I>(&self, signal: S) where
146 Type: 'static,
147 I: Into<css::Style>,
148 S: futures_signals::signal::Signal<Item = I> + 'static,
149 {
150 let entity = self.as_entity();
151 if entity.is_dead() { log::warn!("set_class_signal dead entity {:?}", entity); return; }
152 let (handle, fut) = futures_signals::cancelable_future(signal.for_each(move |class| {
153 SomeElement(entity).set_class_typed::<Type>(class.into());
154 async move { }
155 }), || {});
156
157 wasm_bindgen_futures::spawn_local(fut);
158 self.get_cmp_mut_or_default::<SignalHandlesCollection>().0.push(handle);
159 }
160 fn class_typed_signal<Type, S, I>(self, signal: S) -> Self where
161 Type: 'static,
162 I: Into<css::Style>,
163 S: futures_signals::signal::Signal<Item = I> + 'static,
164 { self.set_class_typed_signal::<Type, S, I>(signal); self }
165
166 fn set_class_tagged_signal<Tag, S, I>(&self, tag: Tag, signal: S) where
167 Tag: std::hash::Hash + Copy + 'static,
168 I: Into<css::Style>,
169 S: futures_signals::signal::Signal<Item = I> + 'static,
170 {
171 let entity = self.as_entity();
172 if entity.is_dead() { log::warn!("set_class_signal dead entity {:?}", entity); return; }
173 let (handle, fut) = futures_signals::cancelable_future(signal.for_each(move |class| {
174 SomeElement(entity).set_class_tagged(tag, class);
175 async move { }
176 }), || {});
177
178 wasm_bindgen_futures::spawn_local(fut);
179 self.get_cmp_mut_or_default::<SignalHandlesCollection>().0.push(handle);
180 }
181 fn class_tagged_signal<Tag, S, I>(self, tag: Tag, signal: S) -> Self where
182 Tag: std::hash::Hash + Copy + 'static,
183 I: Into<css::Style>,
184 S: futures_signals::signal::Signal<Item = I> + 'static,
185 { self.set_class_tagged_signal::<Tag, S, I>(tag, signal); self }
186
187 fn set_attr<'k, 'v>(&self, key: impl Into<Cow<'k, str>>, value: impl Into<Cow<'v, str>>) {
188 if self.is_dead() { log::warn!("set_attr dead {:?}", self.as_entity()); return; }
189 let key = key.into();
190 let value = value.into();
191 self.get_cmp::<web_sys::Element>().set_attribute(&key, &value).unwrap_or_else(|_| panic!("can't set attribute {} to {}", key, value));
192 }
193 fn attr<'k, 'v>(self, key: impl Into<Cow<'k, str>>, value: impl Into<Cow<'v, str>>) -> Self { self.set_attr(key, value); self }
194 fn set_bool_attr<'k>(&self, key: impl Into<Cow<'k, str>>, value: bool) { if value { self.set_attr(key, "") } else { self.remove_attr(key) } }
195 fn bool_attr<'k>(self, key: impl Into<Cow<'k, str>>, value: bool) -> Self { self.set_bool_attr(key, value); self }
196 fn remove_attr<'k>(&self, key: impl Into<Cow<'k, str>>) {
197 if self.is_dead() { log::warn!("remove_attr dead {:?}", self.as_entity()); return; }
198 self.get_cmp::<web_sys::Element>().remove_attribute(&key.into()).expect("can't remove attribute");
199 }
200
201 fn set_attr_signal<'k, 'v, S, K, V>(&self, signal: S) where
202 K: Into<Cow<'k, str>>,
203 V: Into<Cow<'v, str>>,
204 S: futures_signals::signal::Signal<Item = (K, V)> + 'static,
205 {
206 let entity = self.as_entity();
207 if entity.is_dead() { log::warn!("set_attr_signal dead entity {:?}", entity); return; }
208 let (handle, fut) = futures_signals::cancelable_future(signal.for_each(move |(k, v)| {
209 SomeElement(entity).set_attr(k, v);
210 async move { }
211 }), || {});
212
213 wasm_bindgen_futures::spawn_local(fut);
214 self.get_cmp_mut_or_default::<SignalHandlesCollection>().0.push(handle);
215 }
216 fn attr_signal<'k, 'v, S, K, V>(self, x: S) -> Self where
217 K: Into<Cow<'k, str>>,
218 V: Into<Cow<'v, str>>,
219 S: futures_signals::signal::Signal<Item = (K, V)> + 'static,
220 { self.set_attr_signal(x); self }
221
222 fn set_bool_attr_signal<'k, S, K>(&self, attr: K, signal: S) where
223 K: Into<Cow<'k, str>>,
224 S: futures_signals::signal::Signal<Item = bool> + 'static,
225 {
226 let entity = self.as_entity();
227 if entity.is_dead() { log::warn!("set_attr_signal dead entity {:?}", entity); return; }
228 let attr = attr.into().into_owned();
229 let (handle, fut) = futures_signals::cancelable_future(signal.for_each(move |v| {
230 SomeElement(entity).set_bool_attr(&attr, v);
231 async move { }
232 }), || {});
233
234 wasm_bindgen_futures::spawn_local(fut);
235 self.get_cmp_mut_or_default::<SignalHandlesCollection>().0.push(handle);
236 }
237 fn bool_attr_signal<'k, S, K>(self, attr: K, x: S) -> Self where
238 K: Into<Cow<'k, str>>,
239 S: futures_signals::signal::Signal<Item = bool> + 'static,
240 { self.set_bool_attr_signal(attr, x); self }
241
242 fn set_text<'a>(&self, text: impl Into<std::borrow::Cow<'a, str>>) {
243 if self.is_dead() { log::warn!("set_text dead entity {:?}", self.as_entity()); return; }
244 self.get_cmp::<web_sys::Node>().set_text_content(Some(&text.into()));
245 }
246 fn text<'a>(self, x: impl Into<std::borrow::Cow<'a, str>>) -> Self { self.set_text(x); self }
247
248 fn set_text_signal<'a, S, I>(&self, signal: S) where
249 I: Into<Cow<'a, str>>,
250 S: futures_signals::signal::Signal<Item = I> + 'static,
251 {
252 let entity = self.as_entity();
253 if entity.is_dead() { log::warn!("set_text_signal dead entity {:?}", entity); return; }
254 let (handle, fut) = futures_signals::cancelable_future(signal.for_each(move |text| {
255 SomeElement(entity).set_text(text);
256 async move { }
257 }), || {});
258
259 wasm_bindgen_futures::spawn_local(fut);
260 self.get_cmp_mut_or_default::<SignalHandlesCollection>().0.push(handle);
261 }
262 fn text_signal<'a, S, I>(self, x: S) -> Self where
263 I: Into<Cow<'a, str>>,
264 S: futures_signals::signal::Signal<Item = I> + 'static,
265 { self.set_text_signal(x); self }
266
267 fn set_style(&self, style: impl AppendProperty) {
268 let mut props = Vec::new();
269 style.append_property(&mut props);
270 self.set_attr(web_str::style(), props.iter().map(std::string::ToString::to_string).collect::<String>());
271 }
272 fn style(self, style: impl AppendProperty) -> Self { self.set_style(style); self }
273 fn remove_style(&self) { self.remove_attr(web_str::style()); }
274
275 fn set_style_signal<S, I>(&self, signal: S) where
276 I: AppendProperty,
277 S: futures_signals::signal::Signal<Item = I> + 'static,
278 {
279 let entity = self.as_entity();
280 if entity.is_dead() { log::warn!("set_style_signal dead entity {:?}", entity); return; }
281 let (handle, fut) = futures_signals::cancelable_future(signal.for_each(move |style| {
282 SomeElement(entity).set_style(style);
283 async move { }
284 }), || {});
285
286 wasm_bindgen_futures::spawn_local(fut);
287 self.get_cmp_mut_or_default::<SignalHandlesCollection>().0.push(handle);
288 }
289 fn style_signal<S, I>(self, signal: S) -> Self where
290 I: AppendProperty,
291 S: futures_signals::signal::Signal<Item = I> + 'static,
292 { self.set_style_signal(signal); self }
293
294 fn mark<T: 'static>(self) -> Self {
295 if self.is_dead() { log::warn!("mark dead {:?}", self.as_entity()); return self; }
296 self.get_cmp_mut_or_default::<Classes>().marks.insert(TypeId::of::<T>());
297 self
298 }
299 fn unmark<T: 'static>(self) -> Self {
300 if self.is_dead() { log::warn!("unmark dead {:?}", self.as_entity()); return self; }
301 self.get_cmp_mut_or_default::<Classes>().marks.remove(&TypeId::of::<T>());
302 self
303 }
304 fn mark_signal<T: 'static, S>(self, signal: S) -> Self where
305 S: futures_signals::signal::Signal<Item = bool> + 'static,
306 {
307 let entity = self.as_entity();
308 if entity.is_dead() { log::warn!("mark_signal dead entity {:?}", entity); return self; }
309 let (handle, fut) = futures_signals::cancelable_future(signal.for_each(move |enabled| {
310 if enabled { SomeElement(entity).mark::<T>(); } else { SomeElement(entity).unmark::<T>(); }
311 async move { }
312 }), || {});
313
314 wasm_bindgen_futures::spawn_local(fut);
315 self.get_cmp_mut_or_default::<SignalHandlesCollection>().0.push(handle);
316 self
317 }
318
319 fn with_component<T: 'static>(self, f: impl FnOnce(&Self) -> T) -> Self { self.add_component(f(&self)); self }
320
321 fn replace_with<T: Element>(&self, other: T) -> T {
326 let other_entity = other.as_entity();
327 if self.is_dead() { log::warn!("replace_with dead {:?}", self.as_entity()); return other; }
328
329 if let (Some(this), Some(other)) = (self.try_get_cmp::<web_sys::Element>(), other_entity.try_get_cmp::<web_sys::Node>()) {
331 this.replace_with_with_node_1(&other).unwrap();
332 }
333
334 if let Some(parent) = self.try_get_cmp::<Parent>().map(|x| x.0) {
336 if parent.is_dead() { log::warn!("replace_with parent dead {:?}", parent); return other; }
337 let mut children = parent.get_cmp_mut::<Children>();
338 let position = children.0.iter().position(|&x| x == self.as_entity()).expect("entity claims to be a child while missing in parent");
339 children.0[position] = other.as_entity();
340 other_entity.get_cmp_mut_or_default::<Parent>().0 = parent;
341 }
342
343 self.remove();
344 other
346 }
347
348 fn with(self, f: impl FnOnce(&Self)) -> Self { f(&self); self }
349 fn erase(&self) -> SomeElement { SomeElement(self.as_entity()) }
350}
351
352impl<T: Element> Element for &T {}
353impl<T: Element> Element for &mut T {}