feat: add edits back! and more webview support!
This commit adds a new type - the DomEdit - for serializing the changes made by the diffing machine. The architecture of how DomEdits fit into the cooperative scheduling is still TBD but it will allow us to build change lists without applying them immediately. This is more performant and allows us to only render parts of the page at a time. This commit also adds more infrastructure around webview. Dioxus can now run on the web, generate static pages, run in the desktop, and run on mobile, with a large part of thanks to webview.
This commit is contained in:
1 parent
e8f29a8f8a
commit
904b26f711
22 files changed
+1155
-735
No files matched your search
+8
-201
@@ -135,7 +135,7 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
|
||||
Then, we diff and queue an edit event (via chagelist). s single trees - when components show up, we save that traversal and then re-enter later.
|
||||
When re-entering, we reuse the EditList in DiffState
|
||||
*/
|
||||
log::debug!("diffing...");
|
||||
// log::debug!("diffing...");
|
||||
match (old_node, new_node) {
|
||||
// Handle the "sane" cases first.
|
||||
// The rsx and html macros strongly discourage dynamic lists not encapsulated by a "Fragment".
|
||||
@@ -155,13 +155,11 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
|
||||
//
|
||||
// In Dioxus, this is less likely to occur unless through a fragment
|
||||
if new.tag_name != old.tag_name || new.namespace != old.namespace {
|
||||
log::debug!("things have changed....?");
|
||||
self.dom.push_root(old.dom_id.get());
|
||||
self.create(new_node);
|
||||
self.dom.replace_with();
|
||||
return;
|
||||
}
|
||||
log::debug!("Important Things haven't changed!");
|
||||
new.dom_id.set(old.dom_id.get());
|
||||
|
||||
self.diff_listeners(old.listeners, new.listeners);
|
||||
@@ -184,7 +182,10 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
|
||||
let scope = self.components.try_get_mut(scope_id.unwrap()).unwrap();
|
||||
// .with_scope(scope_id.unwrap(), |scope| {
|
||||
scope.caller = Rc::downgrade(&new.caller);
|
||||
|
||||
// ack - this doesn't work on its own!
|
||||
scope.update_children(new.children);
|
||||
|
||||
// })
|
||||
// .unwrap();
|
||||
|
||||
@@ -201,10 +202,10 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
|
||||
// let f = self.components.try_get_mut(scope_id.unwrap()).unwrap();
|
||||
// self.components
|
||||
// .with_scope(scope_id.unwrap(), |f| {
|
||||
log::debug!("running scope during diff {:#?}", scope_id);
|
||||
// log::debug!("running scope during diff {:#?}", scope_id);
|
||||
scope.run_scope().unwrap();
|
||||
self.diff_node(scope.old_frame(), scope.next_frame());
|
||||
log::debug!("scope completed {:#?}", scope_id);
|
||||
// log::debug!("scope completed {:#?}", scope_id);
|
||||
self.seen_nodes.insert(scope_id.unwrap());
|
||||
// })
|
||||
// .unwrap();
|
||||
@@ -240,7 +241,6 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
|
||||
}
|
||||
|
||||
(VNode::Fragment(old), VNode::Fragment(new)) => {
|
||||
log::debug!("diffing as fragment");
|
||||
// This is the case where options or direct vnodes might be used.
|
||||
// In this case, it's faster to just skip ahead to their diff
|
||||
if old.children.len() == 1 && new.children.len() == 1 {
|
||||
@@ -292,200 +292,7 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
|
||||
|
||||
_ => {
|
||||
//
|
||||
} /*
|
||||
|
||||
*/
|
||||
//
|
||||
// VNode::Element(old) => match new_node {
|
||||
// // New node is an element, old node was en element, need to investiage more deeply
|
||||
// VNode::Element(new) => {
|
||||
// // If the element type is completely different, the element needs to be re-rendered completely
|
||||
// // This is an optimization React makes due to how users structure their code
|
||||
// if new.tag_name != old.tag_name || new.namespace != old.namespace {
|
||||
// self.create(new_node);
|
||||
// self.dom.replace_with();
|
||||
// return;
|
||||
// }
|
||||
// new.dom_id.set(old.dom_id.get());
|
||||
|
||||
// self.diff_listeners(old.listeners, new.listeners);
|
||||
// self.diff_attr(old.attributes, new.attributes, new.namespace);
|
||||
// self.diff_children(old.children, new.children);
|
||||
// }
|
||||
// // New node is a text element, need to replace the element with a simple text node
|
||||
// VNode::Text(_) => {
|
||||
// self.create(new_node);
|
||||
// self.dom.replace_with();
|
||||
// }
|
||||
|
||||
// // New node is a component
|
||||
// // Make the component and replace our element on the stack with it
|
||||
// VNode::Component(_) => {
|
||||
// self.create(new_node);
|
||||
// self.dom.replace_with();
|
||||
// }
|
||||
|
||||
// // New node is actually a sequence of nodes.
|
||||
// // We need to replace this one node with a sequence of nodes
|
||||
// // Not yet implement because it's kinda hairy
|
||||
// VNode::Fragment(new) => {
|
||||
// match new.children.len() {
|
||||
// 0 => {
|
||||
// // remove
|
||||
// }
|
||||
// 1 => {
|
||||
// // replace
|
||||
// self.create(&new.children[0]);
|
||||
// self.dom.replace_with();
|
||||
// }
|
||||
// _ => {
|
||||
// todo!()
|
||||
// // remove and mount many
|
||||
// // self.remove_self_and_next_siblings(old)
|
||||
// //
|
||||
// // let iter = ChildIterator::new(new_node, self.components).rev();
|
||||
// // for child in iter {}
|
||||
// // for child in new.children.iter().skip(1).rev() {
|
||||
// // self.dom.remove();
|
||||
// // }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// // New Node is actually suspended. Todo
|
||||
// VNode::Suspended { real } => todo!(),
|
||||
// },
|
||||
|
||||
// // Old element was text
|
||||
// VNode::Text(old) => match new_node {
|
||||
// VNode::Text(new) => {
|
||||
// if old.text != new.text {
|
||||
// log::debug!("Text has changed {}, {}", old.text, new.text);
|
||||
// self.dom.set_text(new.text);
|
||||
// }
|
||||
// new.dom_id.set(old.dom_id.get());
|
||||
// }
|
||||
// VNode::Element(_) | VNode::Component(_) => {
|
||||
// self.create(new_node);
|
||||
// self.dom.replace_with();
|
||||
// }
|
||||
|
||||
// // TODO on handling these types
|
||||
// VNode::Fragment(frag) => {
|
||||
// if frag.children.len() == 0 {
|
||||
// // do nothing
|
||||
// } else {
|
||||
// self.create(&frag.children[0]);
|
||||
// self.dom.replace_with();
|
||||
// for child in frag.children.iter().skip(1) {
|
||||
// self.create(child);
|
||||
// self.dom.append_child();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// VNode::Suspended { real } => todo!(),
|
||||
// },
|
||||
|
||||
// // Old element was a component
|
||||
// VNode::Component(old) => {
|
||||
// match new_node {
|
||||
// // It's something entirely different
|
||||
// VNode::Element(_) | VNode::Text(_) => {
|
||||
// self.create(new_node);
|
||||
// self.dom.replace_with();
|
||||
// }
|
||||
|
||||
// // It's also a component
|
||||
// VNode::Component(new) => {
|
||||
// if old.user_fc == new.user_fc {
|
||||
// // Make sure we're dealing with the same component (by function pointer)
|
||||
|
||||
// // Make sure the new component vnode is referencing the right scope id
|
||||
// let scope_id = old.ass_scope.get();
|
||||
// new.ass_scope.set(scope_id);
|
||||
// new.mounted_root.set(old.mounted_root.get());
|
||||
|
||||
// // make sure the component's caller function is up to date
|
||||
// self.components
|
||||
// .with_scope(scope_id.unwrap(), |scope| {
|
||||
// scope.caller = Rc::downgrade(&new.caller)
|
||||
// })
|
||||
// .unwrap();
|
||||
|
||||
// // React doesn't automatically memoize, but we do.
|
||||
// // The cost is low enough to make it worth checking
|
||||
// // Rust produces fairly performant comparison methods, sometimes SIMD accelerated
|
||||
// let should_render = match old.comparator {
|
||||
// Some(comparator) => comparator(new),
|
||||
// None => true,
|
||||
// };
|
||||
|
||||
// if should_render {
|
||||
// // // self.dom.commit_traversal();
|
||||
// self.components
|
||||
// .with_scope(scope_id.unwrap(), |f| {
|
||||
// f.run_scope().unwrap();
|
||||
// })
|
||||
// .unwrap();
|
||||
// // diff_machine.change_list.load_known_root(root_id);
|
||||
// // run the scope
|
||||
// //
|
||||
// } else {
|
||||
// // Component has memoized itself and doesn't need to be re-rendered.
|
||||
// // We still need to make sure the child's props are up-to-date.
|
||||
// // Don't commit traversal
|
||||
// }
|
||||
// } else {
|
||||
// // It's an entirely different component
|
||||
|
||||
// // A new component has shown up! We need to destroy the old node
|
||||
|
||||
// // Wipe the old one and plant the new one
|
||||
// // self.dom.commit_traversal();
|
||||
// // self.dom.replace_node_with(old.dom_id, new.dom_id);
|
||||
// // self.create(new_node);
|
||||
// // self.dom.replace_with();
|
||||
// self.create(new_node);
|
||||
// // self.create_and_repalce(new_node, old.mounted_root.get());
|
||||
|
||||
// // Now we need to remove the old scope and all of its descendents
|
||||
// let old_scope = old.ass_scope.get().unwrap();
|
||||
// self.destroy_scopes(old_scope);
|
||||
// }
|
||||
// }
|
||||
// VNode::Fragment(_) => todo!(),
|
||||
// VNode::Suspended { real } => todo!(),
|
||||
// }
|
||||
// }
|
||||
|
||||
// VNode::Fragment(old) => {
|
||||
// //
|
||||
// match new_node {
|
||||
// VNode::Fragment(new) => todo!(),
|
||||
|
||||
// // going from fragment to element means we're going from many (or potentially none) to one
|
||||
// VNode::Element(new) => {}
|
||||
// VNode::Text(_) => todo!(),
|
||||
// VNode::Suspended { real } => todo!(),
|
||||
// VNode::Component(_) => todo!(),
|
||||
// }
|
||||
// }
|
||||
|
||||
// // a suspended node will perform a mem-copy of the previous elements until it is ready
|
||||
// // this means that event listeners will need to be disabled and removed
|
||||
// // it also means that props will need to disabled - IE if the node "came out of hibernation" any props should be considered outdated
|
||||
// VNode::Suspended { real: old_real } => {
|
||||
// //
|
||||
// match new_node {
|
||||
// VNode::Suspended { real: new_real } => {
|
||||
// //
|
||||
// }
|
||||
// VNode::Element(_) => todo!(),
|
||||
// VNode::Text(_) => todo!(),
|
||||
// VNode::Fragment(_) => todo!(),
|
||||
// VNode::Component(_) => todo!(),
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -844,7 +651,7 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
|
||||
// self.diff_keyed_children(old, new);
|
||||
// self.dom.set_next_temporary(t);
|
||||
} else {
|
||||
log::debug!("diffing non keyed children");
|
||||
// log::debug!("diffing non keyed children");
|
||||
self.diff_non_keyed_children(old, new);
|
||||
}
|
||||
}
|
||||
|
||||
+360
-326
@@ -5,346 +5,380 @@
|
||||
//! - [ ] use_reducer
|
||||
//! - [ ] use_effect
|
||||
|
||||
pub use new_use_state_def::use_state_new;
|
||||
pub use use_reducer_def::use_reducer;
|
||||
pub use use_ref_def::use_ref;
|
||||
pub use use_state_def::use_state;
|
||||
|
||||
use crate::innerlude::Context;
|
||||
|
||||
mod use_state_def {
|
||||
use crate::innerlude::*;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
ops::{Deref, DerefMut},
|
||||
rc::Rc,
|
||||
};
|
||||
use crate::innerlude::*;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
ops::{Deref, DerefMut},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
/// Store state between component renders!
|
||||
/// When called, this hook retrives a stored value and provides a setter to update that value.
|
||||
/// When the setter is called, the component is re-ran with the new value.
|
||||
///
|
||||
/// This is behaves almost exactly the same way as React's "use_state".
|
||||
///
|
||||
/// Usage:
|
||||
/// ```ignore
|
||||
/// static Example: FC<()> = |cx| {
|
||||
/// let (counter, set_counter) = use_state(&cx, || 0);
|
||||
/// let increment = |_| set_couter(counter + 1);
|
||||
/// let decrement = |_| set_couter(counter + 1);
|
||||
///
|
||||
/// html! {
|
||||
/// <div>
|
||||
/// <h1>"Counter: {counter}" </h1>
|
||||
/// <button onclick={increment}> "Increment" </button>
|
||||
/// <button onclick={decrement}> "Decrement" </button>
|
||||
/// </div>
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T>(
|
||||
cx: &impl Scoped<'a>,
|
||||
initial_state_fn: F,
|
||||
) -> (&'a T, &'a Rc<dyn Fn(T)>) {
|
||||
struct UseState<T: 'static> {
|
||||
new_val: Rc<RefCell<Option<T>>>,
|
||||
current_val: T,
|
||||
caller: Rc<dyn Fn(T) + 'static>,
|
||||
}
|
||||
|
||||
cx.use_hook(
|
||||
move || UseState {
|
||||
new_val: Rc::new(RefCell::new(None)),
|
||||
current_val: initial_state_fn(),
|
||||
caller: Rc::new(|_| println!("setter called!")),
|
||||
},
|
||||
move |hook| {
|
||||
log::debug!("Use_state set called");
|
||||
let inner = hook.new_val.clone();
|
||||
let scheduled_update = cx.schedule_update();
|
||||
|
||||
// get ownership of the new val and replace the current with the new
|
||||
// -> as_ref -> borrow_mut -> deref_mut -> take
|
||||
// -> rc -> &RefCell -> RefMut -> &Option<T> -> T
|
||||
if let Some(new_val) = hook.new_val.as_ref().borrow_mut().deref_mut().take() {
|
||||
hook.current_val = new_val;
|
||||
}
|
||||
|
||||
// todo: swap out the caller with a subscription call and an internal update
|
||||
hook.caller = Rc::new(move |new_val| {
|
||||
// update the setter with the new value
|
||||
let mut new_inner = inner.as_ref().borrow_mut();
|
||||
*new_inner = Some(new_val);
|
||||
|
||||
// Ensure the component gets updated
|
||||
scheduled_update();
|
||||
});
|
||||
|
||||
// box gets derefed into a ref which is then taken as ref with the hook
|
||||
(&hook.current_val, &hook.caller)
|
||||
},
|
||||
|_| {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
mod new_use_state_def {
|
||||
use crate::innerlude::*;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
fmt::{Debug, Display},
|
||||
ops::{Deref, DerefMut},
|
||||
pin::Pin,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
pub struct UseState<T: 'static> {
|
||||
modifier: Rc<RefCell<Option<Box<dyn FnOnce(&mut T)>>>>,
|
||||
current_val: T,
|
||||
update: Box<dyn Fn() + 'static>,
|
||||
setter: Box<dyn Fn(T) + 'static>,
|
||||
// setter: Box<dyn Fn(T) + 'static>,
|
||||
}
|
||||
|
||||
// #[derive(Clone, Copy)]
|
||||
// pub struct UseStateHandle<'a, T: 'static> {
|
||||
// inner: &'a UseState<T>,
|
||||
// // pub setter: &'a dyn Fn(T),
|
||||
// // pub modifier: &'a dyn Fn(&mut T),
|
||||
// }
|
||||
|
||||
impl<'a, T: 'static> UseState<T> {
|
||||
pub fn setter(&self) -> &dyn Fn(T) {
|
||||
&self.setter
|
||||
// let r = self.setter.as_mut();
|
||||
// unsafe { Pin::get_unchecked_mut(r) }
|
||||
}
|
||||
|
||||
pub fn set(&self, new_val: T) {
|
||||
self.modify(|f| *f = new_val);
|
||||
}
|
||||
|
||||
// signal that we need to be updated
|
||||
// save the modifier
|
||||
pub fn modify(&self, f: impl FnOnce(&mut T) + 'static) {
|
||||
let mut slot = self.modifier.as_ref().borrow_mut();
|
||||
*slot = Some(Box::new(f));
|
||||
(self.update)();
|
||||
}
|
||||
}
|
||||
impl<'a, T: 'static> std::ops::Deref for UseState<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.current_val
|
||||
}
|
||||
}
|
||||
|
||||
// enable displaty for the handle
|
||||
impl<'a, T: 'static + Display> std::fmt::Display for UseState<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.current_val)
|
||||
}
|
||||
}
|
||||
|
||||
/// Store state between component renders!
|
||||
/// When called, this hook retrives a stored value and provides a setter to update that value.
|
||||
/// When the setter is called, the component is re-ran with the new value.
|
||||
///
|
||||
/// This is behaves almost exactly the same way as React's "use_state".
|
||||
///
|
||||
/// Usage:
|
||||
/// ```ignore
|
||||
/// static Example: FC<()> = |cx| {
|
||||
/// let (counter, set_counter) = use_state(&cx, || 0);
|
||||
/// let increment = |_| set_couter(counter + 1);
|
||||
/// let decrement = |_| set_couter(counter + 1);
|
||||
///
|
||||
/// html! {
|
||||
/// <div>
|
||||
/// <h1>"Counter: {counter}" </h1>
|
||||
/// <button onclick={increment}> "Increment" </button>
|
||||
/// <button onclick={decrement}> "Decrement" </button>
|
||||
/// </div>
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn use_state_new<'a, 'c, T: 'static, F: FnOnce() -> T>(
|
||||
cx: &impl Scoped<'a>,
|
||||
initial_state_fn: F,
|
||||
) -> &'a UseState<T> {
|
||||
cx.use_hook(
|
||||
move || UseState {
|
||||
modifier: Rc::new(RefCell::new(None)),
|
||||
current_val: initial_state_fn(),
|
||||
update: Box::new(|| {}),
|
||||
setter: Box::new(|_| {}),
|
||||
},
|
||||
move |hook| {
|
||||
log::debug!("addr of hook: {:#?}", hook as *const _);
|
||||
let scheduled_update = cx.schedule_update();
|
||||
|
||||
// log::debug!("Checking if new value {:#?}", &hook.current_val);
|
||||
// get ownership of the new val and replace the current with the new
|
||||
// -> as_ref -> borrow_mut -> deref_mut -> take
|
||||
// -> rc -> &RefCell -> RefMut -> &Option<T> -> T
|
||||
if let Some(new_val) = hook.modifier.as_ref().borrow_mut().deref_mut().take() {
|
||||
// log::debug!("setting prev {:#?}", &hook.current_val);
|
||||
(new_val)(&mut hook.current_val);
|
||||
// log::debug!("setting new value {:#?}", &hook.current_val);
|
||||
}
|
||||
|
||||
hook.update = Box::new(move || scheduled_update());
|
||||
|
||||
let modifier = hook.modifier.clone();
|
||||
hook.setter = Box::new(move |new_val: T| {
|
||||
let mut slot = modifier.as_ref().borrow_mut();
|
||||
|
||||
let slot2 = slot.deref_mut();
|
||||
*slot2 = Some(Box::new(move |old: &mut T| *old = new_val));
|
||||
});
|
||||
|
||||
&*hook
|
||||
},
|
||||
|_| {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
mod use_ref_def {
|
||||
use crate::innerlude::*;
|
||||
use std::{cell::RefCell, ops::DerefMut};
|
||||
|
||||
// pub struct UseRef<T: 'static> {
|
||||
// _current: RefCell<T>,
|
||||
// }
|
||||
// impl<T: 'static> UseRef<T> {
|
||||
// fn new(val: T) -> Self {
|
||||
// Self {
|
||||
// _current: RefCell::new(val),
|
||||
// }
|
||||
// }
|
||||
|
||||
// pub fn set(&self, new: T) {
|
||||
// let mut val = self._current.borrow_mut();
|
||||
// *val = new;
|
||||
// }
|
||||
|
||||
// pub fn modify(&self, modifier: impl FnOnce(&mut T)) {
|
||||
// let mut val = self._current.borrow_mut();
|
||||
// let val_as_ref = val.deref_mut();
|
||||
// modifier(val_as_ref);
|
||||
// }
|
||||
|
||||
// pub fn current(&self) -> std::cell::Ref<'_, T> {
|
||||
// self._current.borrow()
|
||||
// }
|
||||
// }
|
||||
|
||||
/// Store a mutable value between renders!
|
||||
/// To read the value, borrow the ref.
|
||||
/// To change it, use modify.
|
||||
/// Modifications to this value do not cause updates to the component
|
||||
/// Attach to inner context reference, so context can be consumed
|
||||
pub fn use_ref<'a, T: 'static>(
|
||||
cx: &impl Scoped<'a>,
|
||||
initial_state_fn: impl FnOnce() -> T + 'static,
|
||||
) -> &'a RefCell<T> {
|
||||
cx.use_hook(|| RefCell::new(initial_state_fn()), |state| &*state, |_| {})
|
||||
}
|
||||
}
|
||||
|
||||
mod use_reducer_def {
|
||||
use crate::innerlude::*;
|
||||
use std::{cell::RefCell, ops::DerefMut, rc::Rc};
|
||||
|
||||
struct UseReducer<T: 'static, R: 'static> {
|
||||
/// Store state between component renders!
|
||||
/// When called, this hook retrives a stored value and provides a setter to update that value.
|
||||
/// When the setter is called, the component is re-ran with the new value.
|
||||
///
|
||||
/// This is behaves almost exactly the same way as React's "use_state".
|
||||
///
|
||||
/// Usage:
|
||||
/// ```ignore
|
||||
/// static Example: FC<()> = |cx| {
|
||||
/// let (counter, set_counter) = use_state(&cx, || 0);
|
||||
/// let increment = |_| set_couter(counter + 1);
|
||||
/// let decrement = |_| set_couter(counter + 1);
|
||||
///
|
||||
/// html! {
|
||||
/// <div>
|
||||
/// <h1>"Counter: {counter}" </h1>
|
||||
/// <button onclick={increment}> "Increment" </button>
|
||||
/// <button onclick={decrement}> "Decrement" </button>
|
||||
/// </div>
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T>(
|
||||
cx: &impl Scoped<'a>,
|
||||
initial_state_fn: F,
|
||||
) -> (&'a T, &'a Rc<dyn Fn(T)>) {
|
||||
struct UseState<T: 'static> {
|
||||
new_val: Rc<RefCell<Option<T>>>,
|
||||
current_val: T,
|
||||
caller: Box<dyn Fn(R) + 'static>,
|
||||
caller: Rc<dyn Fn(T) + 'static>,
|
||||
}
|
||||
|
||||
/// Store state between component renders!
|
||||
/// When called, this hook retrives a stored value and provides a setter to update that value.
|
||||
/// When the setter is called, the component is re-ran with the new value.
|
||||
///
|
||||
/// This is behaves almost exactly the same way as React's "use_state".
|
||||
///
|
||||
pub fn use_reducer<'a, 'c, State: 'static, Action: 'static>(
|
||||
cx: &impl Scoped<'a>,
|
||||
initial_state_fn: impl FnOnce() -> State,
|
||||
_reducer: impl Fn(&mut State, Action),
|
||||
) -> (&'a State, &'a Box<dyn Fn(Action)>) {
|
||||
cx.use_hook(
|
||||
move || UseReducer {
|
||||
new_val: Rc::new(RefCell::new(None)),
|
||||
current_val: initial_state_fn(),
|
||||
caller: Box::new(|_| println!("setter called!")),
|
||||
},
|
||||
move |hook| {
|
||||
let _inner = hook.new_val.clone();
|
||||
let scheduled_update = cx.schedule_update();
|
||||
cx.use_hook(
|
||||
move || UseState {
|
||||
new_val: Rc::new(RefCell::new(None)),
|
||||
current_val: initial_state_fn(),
|
||||
caller: Rc::new(|_| println!("setter called!")),
|
||||
},
|
||||
move |hook| {
|
||||
log::debug!("Use_state set called");
|
||||
let inner = hook.new_val.clone();
|
||||
let scheduled_update = cx.schedule_update();
|
||||
|
||||
// get ownership of the new val and replace the current with the new
|
||||
// -> as_ref -> borrow_mut -> deref_mut -> take
|
||||
// -> rc -> &RefCell -> RefMut -> &Option<T> -> T
|
||||
if let Some(new_val) = hook.new_val.as_ref().borrow_mut().deref_mut().take() {
|
||||
hook.current_val = new_val;
|
||||
}
|
||||
// get ownership of the new val and replace the current with the new
|
||||
// -> as_ref -> borrow_mut -> deref_mut -> take
|
||||
// -> rc -> &RefCell -> RefMut -> &Option<T> -> T
|
||||
if let Some(new_val) = hook.new_val.as_ref().borrow_mut().deref_mut().take() {
|
||||
hook.current_val = new_val;
|
||||
}
|
||||
|
||||
// todo: swap out the caller with a subscription call and an internal update
|
||||
hook.caller = Box::new(move |_new_val| {
|
||||
// update the setter with the new value
|
||||
// let mut new_inner = inner.as_ref().borrow_mut();
|
||||
// *new_inner = Some(new_val);
|
||||
// todo: swap out the caller with a subscription call and an internal update
|
||||
hook.caller = Rc::new(move |new_val| {
|
||||
// update the setter with the new value
|
||||
let mut new_inner = inner.as_ref().borrow_mut();
|
||||
*new_inner = Some(new_val);
|
||||
|
||||
// Ensure the component gets updated
|
||||
scheduled_update();
|
||||
});
|
||||
// Ensure the component gets updated
|
||||
scheduled_update();
|
||||
});
|
||||
|
||||
// box gets derefed into a ref which is then taken as ref with the hook
|
||||
(&hook.current_val, &hook.caller)
|
||||
},
|
||||
|_| {},
|
||||
)
|
||||
// box gets derefed into a ref which is then taken as ref with the hook
|
||||
(&hook.current_val, &hook.caller)
|
||||
},
|
||||
|_| {},
|
||||
)
|
||||
}
|
||||
|
||||
use crate::innerlude::*;
|
||||
use std::{
|
||||
fmt::{Debug, Display},
|
||||
pin::Pin,
|
||||
};
|
||||
|
||||
pub struct UseState<T: 'static> {
|
||||
modifier: Rc<RefCell<Option<Box<dyn FnOnce(&mut T)>>>>,
|
||||
current_val: T,
|
||||
update: Box<dyn Fn() + 'static>,
|
||||
setter: Box<dyn Fn(T) + 'static>,
|
||||
// setter: Box<dyn Fn(T) + 'static>,
|
||||
}
|
||||
|
||||
// #[derive(Clone, Copy)]
|
||||
// pub struct UseStateHandle<'a, T: 'static> {
|
||||
// inner: &'a UseState<T>,
|
||||
// // pub setter: &'a dyn Fn(T),
|
||||
// // pub modifier: &'a dyn Fn(&mut T),
|
||||
// }
|
||||
|
||||
impl<'a, T: 'static> UseState<T> {
|
||||
pub fn setter(&self) -> &dyn Fn(T) {
|
||||
&self.setter
|
||||
// let r = self.setter.as_mut();
|
||||
// unsafe { Pin::get_unchecked_mut(r) }
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
enum Actions {
|
||||
Incr,
|
||||
Decr,
|
||||
}
|
||||
|
||||
// #[allow(unused)]
|
||||
// static Example: FC<()> = |cx| {
|
||||
// let (count, reduce) = use_reducer(
|
||||
// &cx,
|
||||
// || 0,
|
||||
// |count, action| match action {
|
||||
// Actions::Incr => *count += 1,
|
||||
// Actions::Decr => *count -= 1,
|
||||
// },
|
||||
// );
|
||||
|
||||
// cx.render(rsx! {
|
||||
// div {
|
||||
// h1 {"Count: {count}"}
|
||||
// button {
|
||||
// "Increment"
|
||||
// onclick: move |_| reduce(Actions::Incr)
|
||||
// }
|
||||
// button {
|
||||
// "Decrement"
|
||||
// onclick: move |_| reduce(Actions::Decr)
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// };
|
||||
pub fn set(&self, new_val: T) {
|
||||
self.modify(|f| *f = new_val);
|
||||
}
|
||||
|
||||
// signal that we need to be updated
|
||||
// save the modifier
|
||||
pub fn modify(&self, f: impl FnOnce(&mut T) + 'static) {
|
||||
let mut slot = self.modifier.as_ref().borrow_mut();
|
||||
*slot = Some(Box::new(f));
|
||||
(self.update)();
|
||||
}
|
||||
}
|
||||
impl<'a, T: 'static> std::ops::Deref for UseState<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.current_val
|
||||
}
|
||||
}
|
||||
|
||||
// enable displaty for the handle
|
||||
impl<'a, T: 'static + Display> std::fmt::Display for UseState<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.current_val)
|
||||
}
|
||||
}
|
||||
|
||||
/// Store state between component renders!
|
||||
/// When called, this hook retrives a stored value and provides a setter to update that value.
|
||||
/// When the setter is called, the component is re-ran with the new value.
|
||||
///
|
||||
/// This is behaves almost exactly the same way as React's "use_state".
|
||||
///
|
||||
/// Usage:
|
||||
/// ```ignore
|
||||
/// static Example: FC<()> = |cx| {
|
||||
/// let (counter, set_counter) = use_state(&cx, || 0);
|
||||
/// let increment = |_| set_couter(counter + 1);
|
||||
/// let decrement = |_| set_couter(counter + 1);
|
||||
///
|
||||
/// html! {
|
||||
/// <div>
|
||||
/// <h1>"Counter: {counter}" </h1>
|
||||
/// <button onclick={increment}> "Increment" </button>
|
||||
/// <button onclick={decrement}> "Decrement" </button>
|
||||
/// </div>
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn use_state_new<'a, 'c, T: 'static, F: FnOnce() -> T>(
|
||||
cx: &impl Scoped<'a>,
|
||||
initial_state_fn: F,
|
||||
) -> &'a UseState<T> {
|
||||
cx.use_hook(
|
||||
move || UseState {
|
||||
modifier: Rc::new(RefCell::new(None)),
|
||||
current_val: initial_state_fn(),
|
||||
update: Box::new(|| {}),
|
||||
setter: Box::new(|_| {}),
|
||||
},
|
||||
move |hook| {
|
||||
log::debug!("addr of hook: {:#?}", hook as *const _);
|
||||
let scheduled_update = cx.schedule_update();
|
||||
|
||||
// log::debug!("Checking if new value {:#?}", &hook.current_val);
|
||||
// get ownership of the new val and replace the current with the new
|
||||
// -> as_ref -> borrow_mut -> deref_mut -> take
|
||||
// -> rc -> &RefCell -> RefMut -> &Option<T> -> T
|
||||
if let Some(new_val) = hook.modifier.as_ref().borrow_mut().deref_mut().take() {
|
||||
// log::debug!("setting prev {:#?}", &hook.current_val);
|
||||
(new_val)(&mut hook.current_val);
|
||||
// log::debug!("setting new value {:#?}", &hook.current_val);
|
||||
}
|
||||
|
||||
hook.update = Box::new(move || scheduled_update());
|
||||
|
||||
let modifier = hook.modifier.clone();
|
||||
hook.setter = Box::new(move |new_val: T| {
|
||||
let mut slot = modifier.as_ref().borrow_mut();
|
||||
|
||||
let slot2 = slot.deref_mut();
|
||||
*slot2 = Some(Box::new(move |old: &mut T| *old = new_val));
|
||||
});
|
||||
|
||||
&*hook
|
||||
},
|
||||
|_| {},
|
||||
)
|
||||
}
|
||||
|
||||
// pub struct UseRef<T: 'static> {
|
||||
// _current: RefCell<T>,
|
||||
// }
|
||||
// impl<T: 'static> UseRef<T> {
|
||||
// fn new(val: T) -> Self {
|
||||
// Self {
|
||||
// _current: RefCell::new(val),
|
||||
// }
|
||||
// }
|
||||
|
||||
// pub fn set(&self, new: T) {
|
||||
// let mut val = self._current.borrow_mut();
|
||||
// *val = new;
|
||||
// }
|
||||
|
||||
// pub fn modify(&self, modifier: impl FnOnce(&mut T)) {
|
||||
// let mut val = self._current.borrow_mut();
|
||||
// let val_as_ref = val.deref_mut();
|
||||
// modifier(val_as_ref);
|
||||
// }
|
||||
|
||||
// pub fn current(&self) -> std::cell::Ref<'_, T> {
|
||||
// self._current.borrow()
|
||||
// }
|
||||
// }
|
||||
|
||||
/// Store a mutable value between renders!
|
||||
/// To read the value, borrow the ref.
|
||||
/// To change it, use modify.
|
||||
/// Modifications to this value do not cause updates to the component
|
||||
/// Attach to inner context reference, so context can be consumed
|
||||
pub fn use_ref<'a, T: 'static>(
|
||||
cx: &impl Scoped<'a>,
|
||||
initial_state_fn: impl FnOnce() -> T + 'static,
|
||||
) -> &'a RefCell<T> {
|
||||
cx.use_hook(|| RefCell::new(initial_state_fn()), |state| &*state, |_| {})
|
||||
}
|
||||
|
||||
struct UseReducer<T: 'static, R: 'static> {
|
||||
new_val: Rc<RefCell<Option<T>>>,
|
||||
current_val: T,
|
||||
caller: Box<dyn Fn(R) + 'static>,
|
||||
}
|
||||
|
||||
/// Store state between component renders!
|
||||
/// When called, this hook retrives a stored value and provides a setter to update that value.
|
||||
/// When the setter is called, the component is re-ran with the new value.
|
||||
///
|
||||
/// This is behaves almost exactly the same way as React's "use_state".
|
||||
///
|
||||
pub fn use_reducer<'a, 'c, State: 'static, Action: 'static>(
|
||||
cx: &impl Scoped<'a>,
|
||||
initial_state_fn: impl FnOnce() -> State,
|
||||
_reducer: impl Fn(&mut State, Action),
|
||||
) -> (&'a State, &'a Box<dyn Fn(Action)>) {
|
||||
cx.use_hook(
|
||||
move || UseReducer {
|
||||
new_val: Rc::new(RefCell::new(None)),
|
||||
current_val: initial_state_fn(),
|
||||
caller: Box::new(|_| println!("setter called!")),
|
||||
},
|
||||
move |hook| {
|
||||
let _inner = hook.new_val.clone();
|
||||
let scheduled_update = cx.schedule_update();
|
||||
|
||||
// get ownership of the new val and replace the current with the new
|
||||
// -> as_ref -> borrow_mut -> deref_mut -> take
|
||||
// -> rc -> &RefCell -> RefMut -> &Option<T> -> T
|
||||
if let Some(new_val) = hook.new_val.as_ref().borrow_mut().deref_mut().take() {
|
||||
hook.current_val = new_val;
|
||||
}
|
||||
|
||||
// todo: swap out the caller with a subscription call and an internal update
|
||||
hook.caller = Box::new(move |_new_val| {
|
||||
// update the setter with the new value
|
||||
// let mut new_inner = inner.as_ref().borrow_mut();
|
||||
// *new_inner = Some(new_val);
|
||||
|
||||
// Ensure the component gets updated
|
||||
scheduled_update();
|
||||
});
|
||||
|
||||
// box gets derefed into a ref which is then taken as ref with the hook
|
||||
(&hook.current_val, &hook.caller)
|
||||
},
|
||||
|_| {},
|
||||
)
|
||||
}
|
||||
|
||||
/// Use model makes it easy to use "models" as state for components. To modify the model, call "modify" and a clone of the
|
||||
/// current model will be made, with a RefMut lock on it. Dioxus will never run your components multithreaded, so you can
|
||||
/// be relatively sure that this won't fail in practice
|
||||
pub fn use_model<'a, T: ToOwned<Owned = T> + 'static>(
|
||||
cx: &impl Scoped<'a>,
|
||||
f: impl FnOnce() -> T,
|
||||
) -> &'a UseModel<T> {
|
||||
cx.use_hook(
|
||||
move || {
|
||||
let real = f();
|
||||
let wip = RefCell::new(real.to_owned());
|
||||
let update = cx.schedule_update();
|
||||
UseModel { real, wip, update }
|
||||
},
|
||||
|hook| {
|
||||
hook.real = hook.wip.borrow().to_owned();
|
||||
&*hook
|
||||
},
|
||||
|_| {},
|
||||
)
|
||||
}
|
||||
|
||||
pub struct UseModel<T: ToOwned> {
|
||||
real: T,
|
||||
wip: RefCell<T>,
|
||||
update: Rc<dyn Fn()>,
|
||||
}
|
||||
|
||||
use std::cell::{Ref, RefMut};
|
||||
impl<T: ToOwned> Deref for UseModel<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.real
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Display + ToOwned> Display for UseModel<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.real)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ToOwned> UseModel<T> {
|
||||
pub fn get_mut(&self) -> RefMut<'_, T> {
|
||||
(self.update)();
|
||||
self.wip.borrow_mut()
|
||||
}
|
||||
pub fn modify(&self, f: impl FnOnce(&mut T)) {
|
||||
(self.update)();
|
||||
let mut g = self.get_mut();
|
||||
let r = g.deref_mut();
|
||||
f(r)
|
||||
}
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
enum Actions {
|
||||
Incr,
|
||||
Decr,
|
||||
}
|
||||
|
||||
// #[allow(unused)]
|
||||
// static Example: FC<()> = |cx| {
|
||||
// let (count, reduce) = use_reducer(
|
||||
// &cx,
|
||||
// || 0,
|
||||
// |count, action| match action {
|
||||
// Actions::Incr => *count += 1,
|
||||
// Actions::Decr => *count -= 1,
|
||||
// },
|
||||
// );
|
||||
|
||||
// cx.render(rsx! {
|
||||
// div {
|
||||
// h1 {"Count: {count}"}
|
||||
// button {
|
||||
// "Increment"
|
||||
// onclick: move |_| reduce(Actions::Incr)
|
||||
// }
|
||||
// button {
|
||||
// "Decrement"
|
||||
// onclick: move |_| reduce(Actions::Decr)
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// };
|
||||
}
|
||||
|
||||
pub fn use_is_initialized<P>(cx: Context<P>) -> bool {
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
//!
|
||||
//!
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
pub mod serialize;
|
||||
|
||||
pub mod arena;
|
||||
pub mod component;
|
||||
pub mod util; // Logic for extending FC
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
use crate::prelude::ScopeIdx;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A `DomEdit` represents a serialzied form of the VirtualDom's trait-based API. This allows streaming edits across the
|
||||
/// network or through FFI boundaries.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum DomEdits<'bump> {
|
||||
PushRoot {
|
||||
root: u64,
|
||||
},
|
||||
AppendChild,
|
||||
ReplaceWith,
|
||||
Remove,
|
||||
RemoveAllChildren,
|
||||
CreateTextNode {
|
||||
text: &'bump str,
|
||||
id: u64,
|
||||
},
|
||||
CreateElement {
|
||||
tag: &'bump str,
|
||||
id: u64,
|
||||
},
|
||||
CreateElementNs {
|
||||
tag: &'bump str,
|
||||
id: u64,
|
||||
ns: &'bump str,
|
||||
},
|
||||
CreatePlaceholder {
|
||||
id: u64,
|
||||
},
|
||||
NewEventListener {
|
||||
event: &'bump str,
|
||||
scope: ScopeIdx,
|
||||
node: u64,
|
||||
idx: usize,
|
||||
},
|
||||
RemoveEventListener {
|
||||
event: &'bump str,
|
||||
},
|
||||
SetText {
|
||||
text: &'bump str,
|
||||
},
|
||||
SetAttribute {
|
||||
field: &'bump str,
|
||||
value: &'bump str,
|
||||
ns: Option<&'bump str>,
|
||||
},
|
||||
RemoveAttribute {
|
||||
name: &'bump str,
|
||||
},
|
||||
}
|
||||
Reference in new issue
Block a user