rust-dominator/src/operations.rs

262 lines
7.1 KiB
Rust
Raw Normal View History

2020-01-08 03:52:00 -05:00
use std::rc::Rc;
use std::cell::RefCell;
2019-04-28 20:30:55 -04:00
use std::future::Future;
use std::iter::IntoIterator;
2018-02-25 06:58:20 -05:00
use discard::{Discard, DiscardOnDrop};
use futures_util::future::ready;
use futures_signals::{cancelable_future, CancelableFutureHandle};
use futures_signals::signal::{Signal, SignalExt};
use futures_signals::signal_vec::{VecDiff, SignalVec, SignalVecExt};
use web_sys::Node;
2019-06-20 19:57:47 -04:00
use wasm_bindgen::UnwrapThrowExt;
use wasm_bindgen_futures::futures_0_3::spawn_local;
2019-06-20 19:57:47 -04:00
use crate::bindings;
use crate::dom::Dom;
use crate::callbacks::Callbacks;
2018-02-25 06:58:20 -05:00
#[inline]
pub(crate) fn spawn_future<F>(future: F) -> DiscardOnDrop<CancelableFutureHandle>
where F: Future<Output = ()> + 'static {
2018-02-25 06:58:20 -05:00
// TODO make this more efficient ?
let (handle, future) = cancelable_future(future, || ());
2018-02-25 06:58:20 -05:00
spawn_local(future);
2018-02-25 06:58:20 -05:00
handle
2018-02-25 06:58:20 -05:00
}
2018-03-15 06:54:18 -04:00
#[inline]
pub(crate) fn for_each<A, B>(signal: A, mut callback: B) -> CancelableFutureHandle
2018-03-15 06:54:18 -04:00
where A: Signal + 'static,
B: FnMut(A::Item) + 'static {
DiscardOnDrop::leak(spawn_future(signal.for_each(move |value| {
callback(value);
ready(())
})))
2018-03-15 06:54:18 -04:00
}
2018-03-15 06:54:18 -04:00
#[inline]
fn for_each_vec<A, B>(signal: A, mut callback: B) -> CancelableFutureHandle
where A: SignalVec + 'static,
B: FnMut(VecDiff<A::Item>) + 'static {
DiscardOnDrop::leak(spawn_future(signal.for_each(move |value| {
2018-03-15 06:54:18 -04:00
callback(value);
ready(())
})))
}
2018-02-25 06:58:20 -05:00
#[inline]
2019-06-20 19:57:47 -04:00
pub(crate) fn insert_children_iter<'a, A: IntoIterator<Item = &'a mut Dom>>(element: &Node, callbacks: &mut Callbacks, value: A) {
2018-02-25 06:58:20 -05:00
for dom in value.into_iter() {
// TODO can this be made more efficient ?
2018-02-25 06:58:20 -05:00
callbacks.after_insert.append(&mut dom.callbacks.after_insert);
callbacks.after_remove.append(&mut dom.callbacks.after_remove);
2019-06-20 19:57:47 -04:00
bindings::append_child(element, &dom.element);
2018-03-15 06:54:18 -04:00
}
}
2020-01-08 03:52:00 -05:00
fn after_insert(is_inserted: bool, callbacks: &mut Callbacks) {
callbacks.leak();
if is_inserted {
callbacks.trigger_after_insert();
}
}
pub(crate) fn insert_child_signal<A>(element: Node, callbacks: &mut Callbacks, signal: A)
where A: Signal<Item = Option<Dom>> + 'static {
struct State {
is_inserted: bool,
child: Option<Dom>,
}
let state = Rc::new(RefCell::new(State {
is_inserted: false,
child: None,
}));
{
let state = state.clone();
callbacks.after_insert(move |_| {
let mut state = state.borrow_mut();
if !state.is_inserted {
state.is_inserted = true;
if let Some(ref mut child) = state.child {
child.callbacks.trigger_after_insert();
}
}
});
}
// TODO verify that this will drop `child`
callbacks.after_remove(for_each(signal, move |mut child| {
let mut state = state.borrow_mut();
if let Some(old_child) = state.child.take() {
bindings::remove_child(&element, &old_child.element);
old_child.callbacks.discard();
}
if let Some(ref mut new_child) = child {
bindings::append_child(&element, &new_child.element);
after_insert(state.is_inserted, &mut new_child.callbacks);
}
state.child = child;
}));
}
pub(crate) fn insert_children_signal_vec<A>(element: Node, callbacks: &mut Callbacks, signal: A)
where A: SignalVec<Item = Dom> + 'static {
struct State {
is_inserted: bool,
children: Vec<Dom>,
}
2020-01-08 03:52:00 -05:00
let state = Rc::new(RefCell::new(State {
is_inserted: false,
children: vec![],
}));
{
let state = state.clone();
callbacks.after_insert(move |_| {
2020-01-08 03:52:00 -05:00
let mut state = state.borrow_mut();
if !state.is_inserted {
state.is_inserted = true;
for dom in state.children.iter_mut() {
dom.callbacks.trigger_after_insert();
}
}
});
}
fn clear(state: &mut State, element: &Node) {
// TODO is this correct ?
if state.children.len() > 0 {
bindings::remove_all_children(element);
for dom in state.children.drain(..) {
dom.callbacks.discard();
}
}
}
fn insert_at(state: &mut State, element: &Node, new_index: usize, child: &Node) {
if let Some(dom) = state.children.get(new_index) {
bindings::insert_child_before(element, child, &dom.element);
} else {
bindings::append_child(element, child);
}
}
2019-06-20 19:57:47 -04:00
fn process_change(state: &mut State, element: &Node, change: VecDiff<Dom>) {
match change {
VecDiff::Replace { values } => {
clear(state, element);
2018-03-15 06:54:18 -04:00
state.children = values;
2019-06-15 19:00:54 -04:00
let is_inserted = state.is_inserted;
for dom in state.children.iter_mut() {
2019-06-20 19:57:47 -04:00
bindings::append_child(element, &dom.element);
after_insert(is_inserted, &mut dom.callbacks);
}
},
VecDiff::InsertAt { index, mut value } => {
insert_at(state, element, index, &value.element);
2018-03-15 06:54:18 -04:00
after_insert(state.is_inserted, &mut value.callbacks);
// TODO figure out a way to move this to the top
state.children.insert(index, value);
},
VecDiff::Push { mut value } => {
2019-06-20 19:57:47 -04:00
bindings::append_child(element, &value.element);
2018-03-15 06:54:18 -04:00
after_insert(state.is_inserted, &mut value.callbacks);
// TODO figure out a way to move this to the top
state.children.push(value);
},
VecDiff::UpdateAt { index, mut value } => {
let dom = &mut state.children[index];
bindings::replace_child(element, &value.element, &dom.element);
after_insert(state.is_inserted, &mut value.callbacks);
// TODO figure out a way to move this to the top
2018-03-15 06:54:18 -04:00
// TODO test this
::std::mem::swap(dom, &mut value);
2018-03-15 06:54:18 -04:00
value.callbacks.discard();
},
VecDiff::Move { old_index, new_index } => {
let value = state.children.remove(old_index);
insert_at(state, element, new_index, &value.element);
2019-06-20 19:57:47 -04:00
state.children.insert(new_index, value);
},
VecDiff::RemoveAt { index } => {
let dom = state.children.remove(index);
2018-03-15 06:54:18 -04:00
bindings::remove_child(element, &dom.element);
dom.callbacks.discard();
},
VecDiff::Pop {} => {
let dom = state.children.pop().unwrap_throw();
bindings::remove_child(element, &dom.element);
dom.callbacks.discard();
},
VecDiff::Clear {} => {
clear(state, element);
},
}
2019-06-20 11:52:52 -04:00
}
// TODO maybe move this into the after_insert callback and remove State
2019-06-20 11:52:52 -04:00
// TODO verify that this will drop `children`
callbacks.after_remove(for_each_vec(signal, move |change| {
2020-01-08 03:52:00 -05:00
let mut state = state.borrow_mut();
2019-06-20 11:52:52 -04:00
2019-06-20 19:57:47 -04:00
process_change(&mut state, &element, change);
}));
}