use std::{
borrow::Borrow,
cell::RefCell,
collections::HashMap,
future::{pending, Future},
hash::Hash,
pin::Pin,
};
use async_executor::{LocalExecutor, Task};
use async_ui_web_core::{ContainerNodeFuture, DetachmentBlocker, SiblingNodeFuture};
use wasm_bindgen::UnwrapThrowExt;
use web_sys::{Comment, DocumentFragment};
#[derive(Clone, Debug)]
enum ContainingNode {
Real(web_sys::Node),
Fake(DocumentFragment),
}
pub struct DynamicList<'c, K: Eq + Hash, F: Future + 'c> {
inner: RefCell<DynamicListInner<K, F>>,
executor: LocalExecutor<'c>,
list_end_marker: web_sys::Node,
list_start_marker: web_sys::Node,
detachment_blocker: DetachmentBlocker,
}
struct DynamicListInner<K: Eq + Hash, F: Future> {
items: HashMap<K, Stored<F>>,
containing_node: ContainingNode,
}
struct Stored<F: Future> {
task: Task<F::Output>,
start_marker: web_sys::Node,
end_marker: web_sys::Node,
}
impl ContainingNode {
fn get(&self) -> &web_sys::Node {
match self {
ContainingNode::Real(real) => real,
ContainingNode::Fake(fake) => fake,
}
}
}
impl<'c, K: Eq + Hash, F: Future + 'c> Default for DynamicList<'c, K, F> {
fn default() -> Self {
Self::new()
}
}
impl<'c, K: Eq + Hash, F: Future + 'c> DynamicList<'c, K, F> {
pub fn new() -> Self {
let frag = DocumentFragment::new().unwrap_throw();
let list_end_marker = Comment::new().unwrap_throw().into();
let list_start_marker = Comment::new().unwrap_throw().into();
frag.append_child(&list_start_marker).unwrap_throw();
frag.append_child(&list_end_marker).unwrap_throw();
Self {
inner: RefCell::new(DynamicListInner {
containing_node: ContainingNode::Fake(frag),
items: HashMap::new(),
}),
executor: LocalExecutor::new(),
list_end_marker,
list_start_marker,
detachment_blocker: DetachmentBlocker,
}
}
pub fn insert(&self, key: K, future: F, before: Option<&K>) -> bool {
let mut inner = self.inner.borrow_mut();
let container = inner.containing_node.get();
let start_marker: web_sys::Node = Comment::new().unwrap_throw().into();
let end_marker: web_sys::Node = Comment::new().unwrap_throw().into();
let after = before
.map(|k| &inner.items.get(k).unwrap().start_marker)
.unwrap_or(&self.list_end_marker);
container
.insert_before(&end_marker, Some(after))
.unwrap_throw();
container
.insert_before(&start_marker, Some(&end_marker))
.unwrap_throw();
let end_marker_cloned = end_marker.clone();
let task = self
.executor
.spawn(SiblingNodeFuture::new(future, end_marker_cloned));
let stored = Stored {
task,
start_marker,
end_marker,
};
if let Some(Stored {
task,
start_marker,
end_marker,
}) = inner.items.insert(key, stored)
{
drop(task);
let container = inner.containing_node.get();
let _ = container.remove_child(&start_marker).unwrap_throw();
let _ = container.remove_child(&end_marker).unwrap_throw();
true
} else {
false
}
}
pub fn remove<Q>(&self, key: &Q) -> bool
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
let mut inner = self.inner.borrow_mut();
if let Some(Stored {
start_marker,
end_marker,
task,
}) = inner.items.remove(key)
{
drop(task);
let container = inner.containing_node.get();
let _ = container.remove_child(&start_marker).unwrap_throw();
let _ = container.remove_child(&end_marker).unwrap_throw();
true
} else {
false
}
}
pub fn contains_key<Q>(&self, key: &Q) -> bool
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
self.inner.borrow().items.contains_key(key)
}
pub fn move_before<Q>(&self, to_move: &Q, before: Option<&Q>)
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
let inner = self.inner.borrow();
let after = before
.map(|k| &inner.items.get(k).unwrap().start_marker)
.unwrap_or(&self.list_end_marker);
let to_move = inner.items.get(to_move).unwrap();
let container = inner.containing_node.get();
move_nodes_before(
container,
&to_move.start_marker,
&to_move.end_marker,
Some(after),
);
}
pub fn swap<Q>(&self, key_1: &Q, key_2: &Q)
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
let inner = self.inner.borrow();
let item_1 = inner.items.get(key_1).unwrap();
let item_2 = inner.items.get(key_2).unwrap();
if std::ptr::eq(item_1, item_2) {
return;
}
let after_key_1 = item_1.end_marker.next_sibling();
let container = inner.containing_node.get();
move_nodes_before(
container,
&item_1.start_marker,
&item_1.end_marker,
Some(&item_2.start_marker),
);
move_nodes_before(
container,
&item_2.start_marker,
&item_2.end_marker,
after_key_1.as_ref(),
);
}
#[doc(hidden)]
pub fn order<'t>(&self, _keys: impl IntoIterator<Item = &'t K>)
where
K: 't,
{
todo!()
}
pub fn retain(&self, mut func: impl FnMut(&K) -> bool) {
let inner = &mut *self.inner.borrow_mut();
let DynamicListInner {
items,
containing_node,
} = inner;
let container = containing_node.get();
items.retain(|key, value| {
let keep = func(key);
if !keep {
container.remove_child(&value.start_marker).unwrap_throw();
container.remove_child(&value.end_marker).unwrap_throw();
}
keep
});
}
pub async fn render(&self) {
let real_containing_node;
{
use async_ui_internal_utils::dummy_waker::dummy_waker;
use std::task::Context;
let waker = dummy_waker();
let mut context = Context::from_waker(&waker);
let mut insert_marker_fut =
ContainerNodeFuture::new(pending::<()>(), self.list_start_marker.clone());
let _ = Pin::new(&mut insert_marker_fut).poll(&mut context);
real_containing_node = self.list_start_marker.parent_node().unwrap_throw();
real_containing_node
.insert_before(&self.list_end_marker, Some(&self.list_start_marker))
.unwrap_throw();
drop(insert_marker_fut);
real_containing_node
.insert_before(&self.list_start_marker, Some(&self.list_end_marker))
.unwrap_throw();
}
let stored_fragment;
{
let mut inner = self.inner.borrow_mut();
match std::mem::replace(
&mut inner.containing_node,
ContainingNode::Real(real_containing_node.clone()),
) {
ContainingNode::Real(_) => panic!("rendering in more than one places not allowed"),
ContainingNode::Fake(fragment) => {
real_containing_node
.insert_before(fragment.as_ref(), Some(&self.list_end_marker))
.unwrap_throw();
stored_fragment = fragment;
}
}
}
let _guard = scopeguard::guard((), |_| {
let mut inner = self.inner.borrow_mut();
let fragment = stored_fragment.clone();
move_nodes_before(
fragment.as_ref(),
&self.list_start_marker,
&self.list_end_marker,
None,
);
inner.containing_node = ContainingNode::Fake(fragment);
});
self.executor.run(pending()).await
}
}
impl<'c, K: Eq + Hash, F: Future> Drop for DynamicList<'c, K, F> {
fn drop(&mut self) {
self.detachment_blocker.block_until_drop();
}
}
fn move_nodes_before(
container: &web_sys::Node,
start_marker: &web_sys::Node,
end_marker: &web_sys::Node,
after: Option<&web_sys::Node>,
) {
let mut node = start_marker.clone();
loop {
let next_node = node.next_sibling();
container.insert_before(&node, after).unwrap_throw();
if end_marker.is_same_node(Some(&node)) {
break;
}
node = next_node.unwrap();
}
}