mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-11 07:04:13 +00:00
Merge branch 'master' of github.com:DioxusLabs/dioxus
This commit is contained in:
commit
dbf59f7c57
20 changed files with 535 additions and 308 deletions
|
@ -1,4 +1,5 @@
|
|||
use dioxus::prelude::*;
|
||||
use dioxus_desktop::EvalResult;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
|
@ -7,6 +8,15 @@ fn main() {
|
|||
fn app(cx: Scope) -> Element {
|
||||
let script = use_state(cx, String::new);
|
||||
let eval = dioxus_desktop::use_eval(cx);
|
||||
let future: &UseRef<Option<EvalResult>> = use_ref(cx, || None);
|
||||
if future.read().is_some() {
|
||||
let future_clone = future.clone();
|
||||
cx.spawn(async move {
|
||||
if let Some(fut) = future_clone.with_mut(|o| o.take()) {
|
||||
println!("{:?}", fut.await)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
|
@ -16,7 +26,10 @@ fn app(cx: Scope) -> Element {
|
|||
oninput: move |e| script.set(e.value.clone()),
|
||||
}
|
||||
button {
|
||||
onclick: move |_| eval(script.to_string()),
|
||||
onclick: move |_| {
|
||||
let fut = eval(script);
|
||||
future.set(Some(fut));
|
||||
},
|
||||
"Execute"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,8 @@ indexmap = "1.7"
|
|||
serde = { version = "1", features = ["derive"], optional = true }
|
||||
anyhow = "1.0.66"
|
||||
|
||||
smallbox = "0.8.1"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "*", features = ["full"] }
|
||||
dioxus = { path = "../dioxus" }
|
||||
|
|
|
@ -36,6 +36,9 @@ impl<'b> VirtualDom {
|
|||
|
||||
let cur_scope = self.scope_stack.last().copied().unwrap();
|
||||
|
||||
// we know that this will generate at least one mutation per node
|
||||
self.mutations.edits.reserve(template.template.roots.len());
|
||||
|
||||
let mut on_stack = 0;
|
||||
for (root_idx, root) in template.template.roots.iter().enumerate() {
|
||||
// We might need to generate an ID for the root node
|
||||
|
@ -208,19 +211,12 @@ impl<'b> VirtualDom {
|
|||
|
||||
// If it's all dynamic nodes, then we don't need to register it
|
||||
// Quickly run through and see if it's all just dynamic nodes
|
||||
let dynamic_roots = template
|
||||
.template
|
||||
.roots
|
||||
.iter()
|
||||
.filter(|root| {
|
||||
matches!(
|
||||
root,
|
||||
TemplateNode::Dynamic { .. } | TemplateNode::DynamicText { .. }
|
||||
)
|
||||
})
|
||||
.count();
|
||||
|
||||
if dynamic_roots == template.template.roots.len() {
|
||||
if template.template.roots.iter().all(|root| {
|
||||
matches!(
|
||||
root,
|
||||
TemplateNode::Dynamic { .. } | TemplateNode::DynamicText { .. }
|
||||
)
|
||||
}) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -291,7 +287,7 @@ impl<'b> VirtualDom {
|
|||
}
|
||||
|
||||
pub(crate) fn create_fragment(&mut self, nodes: &'b [VNode<'b>]) -> usize {
|
||||
nodes.iter().fold(0, |acc, child| acc + self.create(child))
|
||||
nodes.iter().map(|child| self.create(child)).sum()
|
||||
}
|
||||
|
||||
pub(super) fn create_component_node(
|
||||
|
@ -302,7 +298,7 @@ impl<'b> VirtualDom {
|
|||
) -> usize {
|
||||
let props = component
|
||||
.props
|
||||
.replace(None)
|
||||
.take()
|
||||
.expect("Props to always exist when a component is being created");
|
||||
|
||||
let unbounded_props = unsafe { std::mem::transmute(props) };
|
||||
|
|
|
@ -56,7 +56,9 @@ impl<'b> VirtualDom {
|
|||
fn diff_err_to_ok(&mut self, _e: &anyhow::Error, _l: &'b VNode<'b>) {}
|
||||
|
||||
fn diff_node(&mut self, left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) {
|
||||
if left_template.template.name != right_template.template.name {
|
||||
if !std::ptr::eq(left_template.template.name, right_template.template.name)
|
||||
&& left_template.template.name != right_template.template.name
|
||||
{
|
||||
return self.light_diff_templates(left_template, right_template);
|
||||
}
|
||||
|
||||
|
|
|
@ -11,8 +11,9 @@
|
|||
//! The logic for this was borrowed from <https://docs.rs/stack_dst/0.6.1/stack_dst/>. Unfortunately, this crate does not
|
||||
//! support non-static closures, so we've implemented the core logic of `ValueA` in this module.
|
||||
|
||||
use smallbox::{smallbox, space::S16, SmallBox};
|
||||
|
||||
use crate::{innerlude::VNode, ScopeState};
|
||||
use std::mem;
|
||||
|
||||
/// A concrete type provider for closures that build [`VNode`] structures.
|
||||
///
|
||||
|
@ -24,14 +25,7 @@ use std::mem;
|
|||
/// LazyNodes::new(|f| f.element("div", [], [], [] None))
|
||||
/// ```
|
||||
pub struct LazyNodes<'a, 'b> {
|
||||
inner: StackNodeStorage<'a, 'b>,
|
||||
}
|
||||
|
||||
type StackHeapSize = [usize; 16];
|
||||
|
||||
enum StackNodeStorage<'a, 'b> {
|
||||
Stack(LazyStack),
|
||||
Heap(Box<dyn FnMut(Option<&'a ScopeState>) -> Option<VNode<'a>> + 'b>),
|
||||
inner: SmallBox<dyn FnMut(&'a ScopeState) -> VNode<'a> + 'b, S16>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> LazyNodes<'a, 'b> {
|
||||
|
@ -44,114 +38,11 @@ impl<'a, 'b> LazyNodes<'a, 'b> {
|
|||
// there's no way to call FnOnce without a box, so we need to store it in a slot and use static dispatch
|
||||
let mut slot = Some(val);
|
||||
|
||||
let val = move |fac: Option<&'a ScopeState>| {
|
||||
fac.map(
|
||||
slot.take()
|
||||
.expect("LazyNodes closure to be called only once"),
|
||||
)
|
||||
};
|
||||
|
||||
// miri does not know how to work with mucking directly into bytes
|
||||
// just use a heap allocated type when miri is running
|
||||
if cfg!(miri) {
|
||||
Self {
|
||||
inner: StackNodeStorage::Heap(Box::new(val)),
|
||||
}
|
||||
} else {
|
||||
unsafe { LazyNodes::new_inner(val) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`LazyNodes`] closure, but force it onto the heap.
|
||||
pub fn new_boxed<F>(inner: F) -> Self
|
||||
where
|
||||
F: FnOnce(&'a ScopeState) -> VNode<'a> + 'b,
|
||||
{
|
||||
// there's no way to call FnOnce without a box, so we need to store it in a slot and use static dispatch
|
||||
let mut slot = Some(inner);
|
||||
|
||||
Self {
|
||||
inner: StackNodeStorage::Heap(Box::new(move |fac: Option<&'a ScopeState>| {
|
||||
fac.map(
|
||||
slot.take()
|
||||
.expect("LazyNodes closure to be called only once"),
|
||||
)
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn new_inner<F>(val: F) -> Self
|
||||
where
|
||||
F: FnMut(Option<&'a ScopeState>) -> Option<VNode<'a>> + 'b,
|
||||
{
|
||||
let mut ptr: *const _ = &val as &dyn FnMut(Option<&'a ScopeState>) -> Option<VNode<'a>>;
|
||||
|
||||
assert_eq!(
|
||||
ptr as *const u8, &val as *const _ as *const u8,
|
||||
"MISUSE: Closure returned different pointer"
|
||||
);
|
||||
assert_eq!(
|
||||
std::mem::size_of_val(&*ptr),
|
||||
std::mem::size_of::<F>(),
|
||||
"MISUSE: Closure returned a subset pointer"
|
||||
);
|
||||
|
||||
let words = ptr_as_slice(&mut ptr);
|
||||
assert!(
|
||||
words[0] == &val as *const _ as usize,
|
||||
"BUG: Pointer layout is not (data_ptr, info...)"
|
||||
);
|
||||
|
||||
// - Ensure that Self is aligned same as data requires
|
||||
assert!(
|
||||
std::mem::align_of::<F>() <= std::mem::align_of::<Self>(),
|
||||
"TODO: Enforce alignment >{} (requires {})",
|
||||
std::mem::align_of::<Self>(),
|
||||
std::mem::align_of::<F>()
|
||||
);
|
||||
|
||||
let info = &words[1..];
|
||||
let data = words[0] as *mut ();
|
||||
let size = mem::size_of::<F>();
|
||||
|
||||
let stored_size = info.len() * mem::size_of::<usize>() + size;
|
||||
let max_size = mem::size_of::<StackHeapSize>();
|
||||
|
||||
if stored_size > max_size {
|
||||
Self {
|
||||
inner: StackNodeStorage::Heap(Box::new(val)),
|
||||
}
|
||||
} else {
|
||||
let mut buf: StackHeapSize = StackHeapSize::default();
|
||||
|
||||
assert!(info.len() + round_to_words(size) <= buf.as_ref().len());
|
||||
|
||||
// Place pointer information at the end of the region
|
||||
// - Allows the data to be at the start for alignment purposes
|
||||
{
|
||||
let info_ofs = buf.as_ref().len() - info.len();
|
||||
let info_dst = &mut buf.as_mut()[info_ofs..];
|
||||
for (d, v) in Iterator::zip(info_dst.iter_mut(), info.iter()) {
|
||||
*d = *v;
|
||||
}
|
||||
}
|
||||
|
||||
let src_ptr = data as *const u8;
|
||||
let dataptr = buf.as_mut_ptr().cast::<u8>();
|
||||
|
||||
for i in 0..size {
|
||||
*dataptr.add(i) = *src_ptr.add(i);
|
||||
}
|
||||
|
||||
std::mem::forget(val);
|
||||
|
||||
Self {
|
||||
inner: StackNodeStorage::Stack(LazyStack {
|
||||
_align: [],
|
||||
buf,
|
||||
dropped: false,
|
||||
}),
|
||||
}
|
||||
inner: smallbox!(move |f| {
|
||||
let val = slot.take().expect("cannot call LazyNodes twice");
|
||||
val(f)
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -163,88 +54,10 @@ impl<'a, 'b> LazyNodes<'a, 'b> {
|
|||
/// let node = f.call(cac);
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn call(self, f: &'a ScopeState) -> VNode<'a> {
|
||||
match self.inner {
|
||||
StackNodeStorage::Heap(mut lazy) => {
|
||||
lazy(Some(f)).expect("Closure should not be called twice")
|
||||
}
|
||||
StackNodeStorage::Stack(mut stack) => stack.call(f),
|
||||
pub fn call(mut self, f: &'a ScopeState) -> VNode<'a> {
|
||||
if self.inner.is_heap() {
|
||||
panic!();
|
||||
}
|
||||
(self.inner)(f)
|
||||
}
|
||||
}
|
||||
|
||||
struct LazyStack {
|
||||
_align: [u64; 0],
|
||||
buf: StackHeapSize,
|
||||
dropped: bool,
|
||||
}
|
||||
|
||||
impl LazyStack {
|
||||
fn call<'a>(&mut self, f: &'a ScopeState) -> VNode<'a> {
|
||||
let LazyStack { buf, .. } = self;
|
||||
let data = buf.as_ref();
|
||||
|
||||
let info_size =
|
||||
mem::size_of::<*mut dyn FnMut(Option<&'a ScopeState>) -> Option<VNode<'a>>>()
|
||||
/ mem::size_of::<usize>()
|
||||
- 1;
|
||||
|
||||
let info_ofs = data.len() - info_size;
|
||||
|
||||
let g: *mut dyn FnMut(Option<&'a ScopeState>) -> Option<VNode<'a>> =
|
||||
unsafe { make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]) };
|
||||
|
||||
self.dropped = true;
|
||||
|
||||
let clos = unsafe { &mut *g };
|
||||
clos(Some(f)).unwrap()
|
||||
}
|
||||
}
|
||||
impl Drop for LazyStack {
|
||||
fn drop(&mut self) {
|
||||
if !self.dropped {
|
||||
let LazyStack { buf, .. } = self;
|
||||
let data = buf.as_ref();
|
||||
|
||||
let info_size =
|
||||
mem::size_of::<*mut dyn FnMut(Option<&ScopeState>) -> Option<VNode<'_>>>()
|
||||
/ mem::size_of::<usize>()
|
||||
- 1;
|
||||
|
||||
let info_ofs = data.len() - info_size;
|
||||
|
||||
let g: *mut dyn FnMut(Option<&ScopeState>) -> Option<VNode<'_>> =
|
||||
unsafe { make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]) };
|
||||
|
||||
self.dropped = true;
|
||||
|
||||
let clos = unsafe { &mut *g };
|
||||
clos(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtain mutable access to a pointer's words
|
||||
fn ptr_as_slice<T>(ptr: &mut T) -> &mut [usize] {
|
||||
assert!(mem::size_of::<T>() % mem::size_of::<usize>() == 0);
|
||||
let words = mem::size_of::<T>() / mem::size_of::<usize>();
|
||||
// SAFE: Points to valid memory (a raw pointer)
|
||||
unsafe { core::slice::from_raw_parts_mut(ptr as *mut _ as *mut usize, words) }
|
||||
}
|
||||
|
||||
/// Re-construct a fat pointer
|
||||
unsafe fn make_fat_ptr<T: ?Sized>(data_ptr: usize, meta_vals: &[usize]) -> *mut T {
|
||||
let mut rv = mem::MaybeUninit::<*mut T>::uninit();
|
||||
{
|
||||
let s = ptr_as_slice(&mut rv);
|
||||
s[0] = data_ptr;
|
||||
s[1..].copy_from_slice(meta_vals);
|
||||
}
|
||||
let rv = rv.assume_init();
|
||||
assert_eq!(rv as *const (), data_ptr as *const ());
|
||||
rv
|
||||
}
|
||||
|
||||
fn round_to_words(len: usize) -> usize {
|
||||
(len + mem::size_of::<usize>() - 1) / mem::size_of::<usize>()
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ use crate::{
|
|||
scopes::{ScopeId, ScopeState},
|
||||
virtual_dom::VirtualDom,
|
||||
};
|
||||
use bumpalo::Bump;
|
||||
use futures_util::FutureExt;
|
||||
use std::{
|
||||
mem,
|
||||
|
@ -34,8 +35,8 @@ impl VirtualDom {
|
|||
props: Some(props),
|
||||
name,
|
||||
placeholder: Default::default(),
|
||||
node_arena_1: BumpFrame::new(50),
|
||||
node_arena_2: BumpFrame::new(50),
|
||||
node_arena_1: BumpFrame::new(0),
|
||||
node_arena_2: BumpFrame::new(0),
|
||||
spawned_tasks: Default::default(),
|
||||
render_cnt: Default::default(),
|
||||
hook_arena: Default::default(),
|
||||
|
@ -62,7 +63,13 @@ impl VirtualDom {
|
|||
let mut new_nodes = unsafe {
|
||||
let scope = self.scopes[scope_id.0].as_mut();
|
||||
|
||||
scope.previous_frame_mut().bump.reset();
|
||||
// if this frame hasn't been intialized yet, we can guess the size of the next frame to be more efficient
|
||||
if scope.previous_frame().bump.allocated_bytes() == 0 {
|
||||
scope.previous_frame_mut().bump =
|
||||
Bump::with_capacity(scope.current_frame().bump.allocated_bytes());
|
||||
} else {
|
||||
scope.previous_frame_mut().bump.reset();
|
||||
}
|
||||
|
||||
// Make sure to reset the hook counter so we give out hooks in the right order
|
||||
scope.hook_idx.set(0);
|
||||
|
@ -128,8 +135,8 @@ impl VirtualDom {
|
|||
let frame = scope.previous_frame();
|
||||
|
||||
// set the new head of the bump frame
|
||||
let alloced = &*frame.bump.alloc(new_nodes);
|
||||
frame.node.set(alloced);
|
||||
let allocated = &*frame.bump.alloc(new_nodes);
|
||||
frame.node.set(allocated);
|
||||
|
||||
// And move the render generation forward by one
|
||||
scope.render_cnt.set(scope.render_cnt.get() + 1);
|
||||
|
@ -141,6 +148,6 @@ impl VirtualDom {
|
|||
});
|
||||
|
||||
// rebind the lifetime now that its stored internally
|
||||
unsafe { mem::transmute(alloced) }
|
||||
unsafe { mem::transmute(allocated) }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,10 +10,10 @@ use crate::{
|
|||
Attribute, AttributeValue, Element, Event, Properties, TaskId,
|
||||
};
|
||||
use bumpalo::{boxed::Box as BumpBox, Bump};
|
||||
use fxhash::{FxHashMap, FxHashSet};
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
cell::{Cell, RefCell},
|
||||
collections::{HashMap, HashSet},
|
||||
fmt::Arguments,
|
||||
future::Future,
|
||||
rc::Rc,
|
||||
|
@ -82,10 +82,10 @@ pub struct ScopeState {
|
|||
pub(crate) hook_list: RefCell<Vec<*mut dyn Any>>,
|
||||
pub(crate) hook_idx: Cell<usize>,
|
||||
|
||||
pub(crate) shared_contexts: RefCell<HashMap<TypeId, Box<dyn Any>>>,
|
||||
pub(crate) shared_contexts: RefCell<FxHashMap<TypeId, Box<dyn Any>>>,
|
||||
|
||||
pub(crate) tasks: Rc<Scheduler>,
|
||||
pub(crate) spawned_tasks: HashSet<TaskId>,
|
||||
pub(crate) spawned_tasks: FxHashSet<TaskId>,
|
||||
|
||||
pub(crate) props: Option<Box<dyn AnyProps<'static>>>,
|
||||
pub(crate) placeholder: Cell<Option<ElementId>>,
|
||||
|
|
|
@ -14,15 +14,9 @@ use crate::{
|
|||
AttributeValue, Element, Event, Scope, SuspenseContext,
|
||||
};
|
||||
use futures_util::{pin_mut, StreamExt};
|
||||
use fxhash::FxHashMap;
|
||||
use slab::Slab;
|
||||
use std::{
|
||||
any::Any,
|
||||
borrow::BorrowMut,
|
||||
cell::Cell,
|
||||
collections::{BTreeSet, HashMap},
|
||||
future::Future,
|
||||
rc::Rc,
|
||||
};
|
||||
use std::{any::Any, borrow::BorrowMut, cell::Cell, collections::BTreeSet, future::Future, rc::Rc};
|
||||
|
||||
/// A virtual node system that progresses user events and diffs UI trees.
|
||||
///
|
||||
|
@ -148,7 +142,7 @@ use std::{
|
|||
/// }
|
||||
/// ```
|
||||
pub struct VirtualDom {
|
||||
pub(crate) templates: HashMap<TemplateId, Template<'static>>,
|
||||
pub(crate) templates: FxHashMap<TemplateId, Template<'static>>,
|
||||
pub(crate) scopes: Slab<Box<ScopeState>>,
|
||||
pub(crate) dirty_scopes: BTreeSet<DirtyScope>,
|
||||
pub(crate) scheduler: Rc<Scheduler>,
|
||||
|
|
|
@ -5,6 +5,7 @@ use futures_channel::mpsc::{unbounded, UnboundedSender};
|
|||
use futures_util::StreamExt;
|
||||
#[cfg(target_os = "ios")]
|
||||
use objc::runtime::Object;
|
||||
use serde_json::Value;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::Arc,
|
||||
|
@ -19,6 +20,7 @@ use wry::{
|
|||
|
||||
pub(super) struct DesktopController {
|
||||
pub(super) webviews: HashMap<WindowId, WebView>,
|
||||
pub(super) eval_sender: tokio::sync::mpsc::UnboundedSender<Value>,
|
||||
pub(super) pending_edits: Arc<Mutex<Vec<String>>>,
|
||||
pub(super) quit_app_on_close: bool,
|
||||
pub(super) is_ready: Arc<AtomicBool>,
|
||||
|
@ -43,6 +45,7 @@ impl DesktopController {
|
|||
|
||||
let pending_edits = edit_queue.clone();
|
||||
let desktop_context_proxy = proxy.clone();
|
||||
let (eval_sender, eval_reciever) = tokio::sync::mpsc::unbounded_channel::<Value>();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
// We create the runtime as multithreaded, so you can still "tokio::spawn" onto multiple threads
|
||||
|
@ -54,7 +57,7 @@ impl DesktopController {
|
|||
|
||||
runtime.block_on(async move {
|
||||
let mut dom = VirtualDom::new_with_props(root, props)
|
||||
.with_root_context(DesktopContext::new(desktop_context_proxy));
|
||||
.with_root_context(DesktopContext::new(desktop_context_proxy, eval_reciever));
|
||||
{
|
||||
let edits = dom.rebuild();
|
||||
let mut queue = edit_queue.lock().unwrap();
|
||||
|
@ -88,6 +91,7 @@ impl DesktopController {
|
|||
|
||||
Self {
|
||||
pending_edits,
|
||||
eval_sender,
|
||||
webviews: HashMap::new(),
|
||||
is_ready: Arc::new(AtomicBool::new(false)),
|
||||
quit_app_on_close: true,
|
||||
|
|
|
@ -2,6 +2,11 @@ use std::rc::Rc;
|
|||
|
||||
use crate::controller::DesktopController;
|
||||
use dioxus_core::ScopeState;
|
||||
use serde::de::Error;
|
||||
use serde_json::Value;
|
||||
use std::future::Future;
|
||||
use std::future::IntoFuture;
|
||||
use std::pin::Pin;
|
||||
use wry::application::event_loop::ControlFlow;
|
||||
use wry::application::event_loop::EventLoopProxy;
|
||||
#[cfg(target_os = "ios")]
|
||||
|
@ -19,16 +24,6 @@ pub fn use_window(cx: &ScopeState) -> &DesktopContext {
|
|||
.unwrap()
|
||||
}
|
||||
|
||||
/// Get a closure that executes any JavaScript in the WebView context.
|
||||
pub fn use_eval(cx: &ScopeState) -> &Rc<dyn Fn(String)> {
|
||||
let desktop = use_window(cx);
|
||||
|
||||
&*cx.use_hook(|| {
|
||||
let desktop = desktop.clone();
|
||||
Rc::new(move |script| desktop.eval(script))
|
||||
} as Rc<dyn Fn(String)>)
|
||||
}
|
||||
|
||||
/// An imperative interface to the current window.
|
||||
///
|
||||
/// To get a handle to the current window, use the [`use_window`] hook.
|
||||
|
@ -45,11 +40,18 @@ pub fn use_eval(cx: &ScopeState) -> &Rc<dyn Fn(String)> {
|
|||
pub struct DesktopContext {
|
||||
/// The wry/tao proxy to the current window
|
||||
pub proxy: ProxyType,
|
||||
pub(super) eval_reciever: Rc<tokio::sync::Mutex<tokio::sync::mpsc::UnboundedReceiver<Value>>>,
|
||||
}
|
||||
|
||||
impl DesktopContext {
|
||||
pub(crate) fn new(proxy: ProxyType) -> Self {
|
||||
Self { proxy }
|
||||
pub(crate) fn new(
|
||||
proxy: ProxyType,
|
||||
eval_reciever: tokio::sync::mpsc::UnboundedReceiver<Value>,
|
||||
) -> Self {
|
||||
Self {
|
||||
proxy,
|
||||
eval_reciever: Rc::new(tokio::sync::Mutex::new(eval_reciever)),
|
||||
}
|
||||
}
|
||||
|
||||
/// trigger the drag-window event
|
||||
|
@ -242,6 +244,18 @@ impl DesktopController {
|
|||
Resizable(state) => window.set_resizable(state),
|
||||
AlwaysOnTop(state) => window.set_always_on_top(state),
|
||||
|
||||
Eval(code) => {
|
||||
let script = format!(
|
||||
r#"window.ipc.postMessage(JSON.stringify({{"method":"eval_result", params: (function(){{
|
||||
{}
|
||||
}})()}}));"#,
|
||||
code
|
||||
);
|
||||
if let Err(e) = webview.evaluate_script(&script) {
|
||||
// we can't panic this error.
|
||||
log::warn!("Eval script error: {e}");
|
||||
}
|
||||
}
|
||||
CursorVisible(state) => window.set_cursor_visible(state),
|
||||
CursorGrab(state) => {
|
||||
let _ = window.set_cursor_grab(state);
|
||||
|
@ -265,13 +279,6 @@ impl DesktopController {
|
|||
log::warn!("Devtools are disabled in release builds");
|
||||
}
|
||||
|
||||
Eval(code) => {
|
||||
if let Err(e) = webview.evaluate_script(code.as_str()) {
|
||||
// we can't panic this error.
|
||||
log::warn!("Eval script error: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
PushView(view) => unsafe {
|
||||
use objc::runtime::Object;
|
||||
|
@ -301,6 +308,39 @@ impl DesktopController {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get a closure that executes any JavaScript in the WebView context.
|
||||
pub fn use_eval<S: std::string::ToString>(cx: &ScopeState) -> &dyn Fn(S) -> EvalResult {
|
||||
let desktop = use_window(cx).clone();
|
||||
cx.use_hook(|| {
|
||||
move |script| {
|
||||
desktop.eval(script);
|
||||
let recv = desktop.eval_reciever.clone();
|
||||
EvalResult { reciever: recv }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// A future that resolves to the result of a JavaScript evaluation.
|
||||
pub struct EvalResult {
|
||||
reciever: Rc<tokio::sync::Mutex<tokio::sync::mpsc::UnboundedReceiver<serde_json::Value>>>,
|
||||
}
|
||||
|
||||
impl IntoFuture for EvalResult {
|
||||
type Output = Result<serde_json::Value, serde_json::Error>;
|
||||
|
||||
type IntoFuture = Pin<Box<dyn Future<Output = Result<serde_json::Value, serde_json::Error>>>>;
|
||||
|
||||
fn into_future(self) -> Self::IntoFuture {
|
||||
Box::pin(async move {
|
||||
let mut reciever = self.reciever.lock().await;
|
||||
match reciever.recv().await {
|
||||
Some(result) => Ok(result),
|
||||
None => Err(serde_json::Error::custom("No result returned")),
|
||||
}
|
||||
}) as Pin<Box<dyn Future<Output = Result<serde_json::Value, serde_json::Error>>>>
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
fn is_main_thread() -> bool {
|
||||
use objc::runtime::{Class, BOOL, NO};
|
||||
|
|
|
@ -17,7 +17,7 @@ use std::sync::atomic::AtomicBool;
|
|||
use std::sync::Arc;
|
||||
|
||||
use desktop_context::UserWindowEvent;
|
||||
pub use desktop_context::{use_eval, use_window, DesktopContext};
|
||||
pub use desktop_context::{use_eval, use_window, DesktopContext, EvalResult};
|
||||
use futures_channel::mpsc::UnboundedSender;
|
||||
pub use wry;
|
||||
pub use wry::application as tao;
|
||||
|
@ -142,6 +142,7 @@ impl DesktopController {
|
|||
event_loop,
|
||||
self.is_ready.clone(),
|
||||
self.proxy.clone(),
|
||||
self.eval_sender.clone(),
|
||||
self.event_tx.clone(),
|
||||
);
|
||||
|
||||
|
@ -154,6 +155,7 @@ fn build_webview(
|
|||
event_loop: &tao::event_loop::EventLoopWindowTarget<UserWindowEvent>,
|
||||
is_ready: Arc<AtomicBool>,
|
||||
proxy: tao::event_loop::EventLoopProxy<UserWindowEvent>,
|
||||
eval_sender: tokio::sync::mpsc::UnboundedSender<serde_json::Value>,
|
||||
event_tx: UnboundedSender<serde_json::Value>,
|
||||
) -> wry::webview::WebView {
|
||||
let builder = cfg.window.clone();
|
||||
|
@ -183,6 +185,10 @@ fn build_webview(
|
|||
.with_ipc_handler(move |_window: &Window, payload: String| {
|
||||
parse_ipc_message(&payload)
|
||||
.map(|message| match message.method() {
|
||||
"eval_result" => {
|
||||
let result = message.params();
|
||||
eval_sender.send(result).unwrap();
|
||||
}
|
||||
"user_event" => {
|
||||
_ = event_tx.unbounded_send(message.params());
|
||||
}
|
||||
|
|
|
@ -17,8 +17,10 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
|
|||
wasm-bindgen = { version = "0.2.79", optional = true }
|
||||
js-sys = { version = "0.3.56", optional = true }
|
||||
web-sys = { version = "0.3.56", optional = true, features = ["Element", "Node"] }
|
||||
|
||||
sledgehammer_bindgen = { version = "0.1.2", optional = true }
|
||||
sledgehammer_utils = { version = "0.1.0", optional = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
web = ["wasm-bindgen", "js-sys", "web-sys"]
|
||||
sledgehammer = ["wasm-bindgen", "js-sys", "web-sys", "sledgehammer_bindgen", "sledgehammer_utils"]
|
||||
|
|
|
@ -17,9 +17,6 @@ extern "C" {
|
|||
#[wasm_bindgen(method)]
|
||||
pub fn MountToRoot(this: &Interpreter);
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
pub fn AppendChildren(this: &Interpreter, m: u32, id: u32);
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
pub fn AssignId(this: &Interpreter, path: &[u8], id: u32);
|
||||
|
||||
|
@ -76,4 +73,7 @@ extern "C" {
|
|||
|
||||
#[wasm_bindgen(method)]
|
||||
pub fn PushRoot(this: &Interpreter, id: u32);
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
pub fn AppendChildren(this: &Interpreter, id: u32, m: u32);
|
||||
}
|
||||
|
|
|
@ -89,8 +89,8 @@ export class Interpreter {
|
|||
PopRoot() {
|
||||
this.stack.pop();
|
||||
}
|
||||
AppendChildren(many) {
|
||||
let root = this.stack[this.stack.length - (1 + many)];
|
||||
AppendChildren(id, many) {
|
||||
let root = this.nodes[id];
|
||||
let to_add = this.stack.splice(this.stack.length - many);
|
||||
for (let i = 0; i < many; i++) {
|
||||
root.appendChild(to_add[i]);
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
pub static INTERPRETER_JS: &str = include_str!("./interpreter.js");
|
||||
|
||||
#[cfg(feature = "sledgehammer")]
|
||||
mod sledgehammer_bindings;
|
||||
#[cfg(feature = "sledgehammer")]
|
||||
pub use sledgehammer_bindings::*;
|
||||
|
||||
#[cfg(feature = "web")]
|
||||
mod bindings;
|
||||
|
||||
|
|
221
packages/interpreter/src/sledgehammer_bindings.rs
Normal file
221
packages/interpreter/src/sledgehammer_bindings.rs
Normal file
|
@ -0,0 +1,221 @@
|
|||
use js_sys::Function;
|
||||
use sledgehammer_bindgen::bindgen;
|
||||
use web_sys::Node;
|
||||
|
||||
#[bindgen]
|
||||
mod js {
|
||||
const JS: &str = r#"
|
||||
class ListenerMap {
|
||||
constructor(root) {
|
||||
// bubbling events can listen at the root element
|
||||
this.global = {};
|
||||
// non bubbling events listen at the element the listener was created at
|
||||
this.local = {};
|
||||
this.root = null;
|
||||
this.handler = null;
|
||||
}
|
||||
|
||||
create(event_name, element, bubbles) {
|
||||
if (bubbles) {
|
||||
if (this.global[event_name] === undefined) {
|
||||
this.global[event_name] = {};
|
||||
this.global[event_name].active = 1;
|
||||
this.root.addEventListener(event_name, this.handler);
|
||||
} else {
|
||||
this.global[event_name].active++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
const id = element.getAttribute("data-dioxus-id");
|
||||
if (!this.local[id]) {
|
||||
this.local[id] = {};
|
||||
}
|
||||
element.addEventListener(event_name, this.handler);
|
||||
}
|
||||
}
|
||||
|
||||
remove(element, event_name, bubbles) {
|
||||
if (bubbles) {
|
||||
this.global[event_name].active--;
|
||||
if (this.global[event_name].active === 0) {
|
||||
this.root.removeEventListener(event_name, this.global[event_name].callback);
|
||||
delete this.global[event_name];
|
||||
}
|
||||
}
|
||||
else {
|
||||
const id = element.getAttribute("data-dioxus-id");
|
||||
delete this.local[id][event_name];
|
||||
if (this.local[id].length === 0) {
|
||||
delete this.local[id];
|
||||
}
|
||||
element.removeEventListener(event_name, this.handler);
|
||||
}
|
||||
}
|
||||
|
||||
removeAllNonBubbling(element) {
|
||||
const id = element.getAttribute("data-dioxus-id");
|
||||
delete this.local[id];
|
||||
}
|
||||
}
|
||||
function SetAttributeInner(node, field, value, ns) {
|
||||
const name = field;
|
||||
if (ns === "style") {
|
||||
// ????? why do we need to do this
|
||||
if (node.style === undefined) {
|
||||
node.style = {};
|
||||
}
|
||||
node.style[name] = value;
|
||||
} else if (ns !== null && ns !== undefined && ns !== "") {
|
||||
node.setAttributeNS(ns, name, value);
|
||||
} else {
|
||||
switch (name) {
|
||||
case "value":
|
||||
if (value !== node.value) {
|
||||
node.value = value;
|
||||
}
|
||||
break;
|
||||
case "checked":
|
||||
node.checked = value === "true";
|
||||
break;
|
||||
case "selected":
|
||||
node.selected = value === "true";
|
||||
break;
|
||||
case "dangerous_inner_html":
|
||||
node.innerHTML = value;
|
||||
break;
|
||||
default:
|
||||
// https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364
|
||||
if (value === "false" && bool_attrs.hasOwnProperty(name)) {
|
||||
node.removeAttribute(name);
|
||||
} else {
|
||||
node.setAttribute(name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
function LoadChild(ptr, len) {
|
||||
// iterate through each number and get that child
|
||||
node = stack[stack.length - 1];
|
||||
ptr_end = ptr + len;
|
||||
for (; ptr < ptr_end; ptr++) {
|
||||
end = m.getUint8(ptr);
|
||||
for (node = node.firstChild; end > 0; end--) {
|
||||
node = node.nextSibling;
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
const listeners = new ListenerMap();
|
||||
let nodes = [];
|
||||
let stack = [];
|
||||
const templates = {};
|
||||
let node, els, end, ptr_end, k;
|
||||
export function save_template(nodes, tmpl_id) {
|
||||
templates[tmpl_id] = nodes;
|
||||
}
|
||||
export function set_node(id, node) {
|
||||
nodes[id] = node;
|
||||
}
|
||||
export function initilize(root, handler) {
|
||||
listeners.handler = handler;
|
||||
nodes = [root];
|
||||
stack = [root];
|
||||
listeners.root = root;
|
||||
}
|
||||
function AppendChildren(id, many){
|
||||
root = nodes[id];
|
||||
els = stack.splice(stack.length-many);
|
||||
for (k = 0; k < many; k++) {
|
||||
root.appendChild(els[k]);
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
extern "C" {
|
||||
#[wasm_bindgen]
|
||||
pub fn save_template(nodes: Vec<Node>, tmpl_id: u32);
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn set_node(id: u32, node: Node);
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn initilize(root: Node, handler: &Function);
|
||||
}
|
||||
|
||||
fn mount_to_root() {
|
||||
"{AppendChildren(root, stack.length-1);}"
|
||||
}
|
||||
fn push_root(root: u32) {
|
||||
"{stack.push(nodes[$root$]);}"
|
||||
}
|
||||
fn append_children(id: u32, many: u32) {
|
||||
"{AppendChildren($id$, $many$);}"
|
||||
}
|
||||
fn pop_root() {
|
||||
"{stack.pop();}"
|
||||
}
|
||||
fn replace_with(id: u32, n: u32) {
|
||||
"{root = nodes[$id$]; els = stack.splice(stack.length-$n$); if (root.listening) { listeners.removeAllNonBubbling(root); } root.replaceWith(...els);}"
|
||||
}
|
||||
fn insert_after(id: u32, n: u32) {
|
||||
"{nodes[$id$].after(...stack.splice(stack.length-$n$));}"
|
||||
}
|
||||
fn insert_before(id: u32, n: u32) {
|
||||
"{nodes[$id$].before(...stack.splice(stack.length-$n$));}"
|
||||
}
|
||||
fn remove(id: u32) {
|
||||
"{node = nodes[$id$]; if (node !== undefined) { if (node.listening) { listeners.removeAllNonBubbling(node); } node.remove(); }}"
|
||||
}
|
||||
fn create_raw_text(text: &str) {
|
||||
"{stack.push(document.createTextNode($text$));}"
|
||||
}
|
||||
fn create_text_node(text: &str, id: u32) {
|
||||
"{node = document.createTextNode($text$); nodes[$id$] = node; stack.push(node);}"
|
||||
}
|
||||
fn create_placeholder(id: u32) {
|
||||
"{node = document.createElement('pre'); node.hidden = true; stack.push(node); nodes[$id$] = node;}"
|
||||
}
|
||||
fn new_event_listener(event_name: &str<u8, evt>, id: u32, bubbles: u8) {
|
||||
r#"node = nodes[id]; if(node.listening){node.listening += 1;}else{node.listening = 1;} node.setAttribute('data-dioxus-id', `\${id}`); listeners.create($event_name$, node, $bubbles$);"#
|
||||
}
|
||||
fn remove_event_listener(event_name: &str<u8, evt>, id: u32, bubbles: u8) {
|
||||
"{node = nodes[$id$]; node.listening -= 1; node.removeAttribute('data-dioxus-id'); listeners.remove(node, $event_name$, $bubbles$);}"
|
||||
}
|
||||
fn set_text(id: u32, text: &str) {
|
||||
"{nodes[$id$].textContent = $text$;}"
|
||||
}
|
||||
fn set_attribute(id: u32, field: &str<u8, attr>, value: &str, ns: &str<u8, ns_cache>) {
|
||||
"{node = nodes[$id$]; SetAttributeInner(node, $field$, $value$, $ns$);}"
|
||||
}
|
||||
fn remove_attribute(id: u32, field: &str<u8, attr>, ns: &str<u8, ns_cache>) {
|
||||
r#"{name = $field$;
|
||||
node = this.nodes[$id$];
|
||||
if (ns == "style") {
|
||||
node.style.removeProperty(name);
|
||||
} else if (ns !== null && ns !== undefined && ns !== "") {
|
||||
node.removeAttributeNS(ns, name);
|
||||
} else if (name === "value") {
|
||||
node.value = "";
|
||||
} else if (name === "checked") {
|
||||
node.checked = false;
|
||||
} else if (name === "selected") {
|
||||
node.selected = false;
|
||||
} else if (name === "dangerous_inner_html") {
|
||||
node.innerHTML = "";
|
||||
} else {
|
||||
node.removeAttribute(name);
|
||||
}}"#
|
||||
}
|
||||
fn assign_id(ptr: u32, len: u8, id: u32) {
|
||||
"{nodes[$id$] = LoadChild($ptr$, $len$);}"
|
||||
}
|
||||
fn hydrate_text(ptr: u32, len: u8, value: &str, id: u32) {
|
||||
"{node = LoadChild($ptr$, $len$); node.textContent = $value$; nodes[$id$] = node;}"
|
||||
}
|
||||
fn replace_placeholder(ptr: u32, len: u8, n: u32) {
|
||||
"{els = stack.splice(stack.length - $n$); node = LoadChild($ptr$, $len$); node.replaceWith(...els);}"
|
||||
}
|
||||
fn load_template(tmpl_id: u32, index: u32, id: u32) {
|
||||
"{node = templates[$tmpl_id$][$index$].cloneNode(true); nodes[$id$] = node; stack.push(node);}"
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
|
|||
dioxus-core = { path = "../core", version = "^0.2.1", features = ["serialize"] }
|
||||
dioxus-html = { path = "../html", version = "^0.2.1", features = ["wasm-bind"] }
|
||||
dioxus-interpreter-js = { path = "../interpreter", version = "^0.2.1", features = [
|
||||
"web"
|
||||
"sledgehammer"
|
||||
] }
|
||||
|
||||
js-sys = "0.3.56"
|
||||
|
@ -30,6 +30,7 @@ futures-util = "0.3.19"
|
|||
smallstr = "0.2.0"
|
||||
futures-channel = "0.3.21"
|
||||
serde_json = { version = "1.0" }
|
||||
serde = { version = "1.0" }
|
||||
serde-wasm-bindgen = "0.4.5"
|
||||
|
||||
[dependencies.web-sys]
|
||||
|
|
|
@ -7,20 +7,22 @@
|
|||
//! - tests to ensure dyn_into works for various event types.
|
||||
//! - Partial delegation?>
|
||||
|
||||
use dioxus_core::{Mutation, Template};
|
||||
use dioxus_core::{Mutation, Template, TemplateAttribute, TemplateNode};
|
||||
use dioxus_html::{event_bubbles, CompositionData, FormData};
|
||||
use dioxus_interpreter_js::Interpreter;
|
||||
use dioxus_interpreter_js::{save_template, Channel};
|
||||
use futures_channel::mpsc;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::{any::Any, rc::Rc};
|
||||
use wasm_bindgen::{closure::Closure, JsCast};
|
||||
use web_sys::{Document, Element, Event};
|
||||
use web_sys::{Document, Element, Event, HtmlElement};
|
||||
|
||||
use crate::Config;
|
||||
|
||||
pub struct WebsysDom {
|
||||
interpreter: Interpreter,
|
||||
handler: Closure<dyn FnMut(&Event)>,
|
||||
_root: Element,
|
||||
document: Document,
|
||||
templates: FxHashMap<String, u32>,
|
||||
max_template_id: u32,
|
||||
interpreter: Channel,
|
||||
}
|
||||
|
||||
impl WebsysDom {
|
||||
|
@ -32,67 +34,139 @@ impl WebsysDom {
|
|||
Some(root) => root,
|
||||
None => document.create_element("body").ok().unwrap(),
|
||||
};
|
||||
let interpreter = Channel::default();
|
||||
|
||||
Self {
|
||||
interpreter: Interpreter::new(root.clone()),
|
||||
_root: root,
|
||||
handler: Closure::wrap(Box::new(move |event: &web_sys::Event| {
|
||||
let handler: Closure<dyn FnMut(&Event)> =
|
||||
Closure::wrap(Box::new(move |event: &web_sys::Event| {
|
||||
let _ = event_channel.unbounded_send(event.clone());
|
||||
})),
|
||||
}));
|
||||
|
||||
dioxus_interpreter_js::initilize(root.unchecked_into(), handler.as_ref().unchecked_ref());
|
||||
handler.forget();
|
||||
Self {
|
||||
document,
|
||||
interpreter,
|
||||
templates: FxHashMap::default(),
|
||||
max_template_id: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mount(&mut self) {
|
||||
self.interpreter.MountToRoot();
|
||||
self.interpreter.mount_to_root();
|
||||
}
|
||||
|
||||
pub fn load_templates(&mut self, templates: &[Template]) {
|
||||
log::debug!("Loading templates {:?}", templates);
|
||||
|
||||
for template in templates {
|
||||
self.interpreter
|
||||
.SaveTemplate(serde_wasm_bindgen::to_value(&template).unwrap());
|
||||
let mut roots = vec![];
|
||||
|
||||
for root in template.roots {
|
||||
roots.push(self.create_template_node(root))
|
||||
}
|
||||
|
||||
self.templates
|
||||
.insert(template.name.to_owned(), self.max_template_id);
|
||||
save_template(roots, self.max_template_id);
|
||||
self.max_template_id += 1
|
||||
}
|
||||
}
|
||||
|
||||
fn create_template_node(&self, v: &TemplateNode) -> web_sys::Node {
|
||||
use TemplateNode::*;
|
||||
match v {
|
||||
Element {
|
||||
tag,
|
||||
namespace,
|
||||
attrs,
|
||||
children,
|
||||
..
|
||||
} => {
|
||||
let el = match namespace {
|
||||
Some(ns) => self.document.create_element_ns(Some(ns), tag).unwrap(),
|
||||
None => self.document.create_element(tag).unwrap(),
|
||||
};
|
||||
for attr in *attrs {
|
||||
if let TemplateAttribute::Static {
|
||||
name,
|
||||
value,
|
||||
namespace,
|
||||
} = attr
|
||||
{
|
||||
match namespace {
|
||||
Some(ns) if *ns == "style" => el
|
||||
.dyn_ref::<HtmlElement>()
|
||||
.unwrap()
|
||||
.style()
|
||||
.set_property(name, value)
|
||||
.unwrap(),
|
||||
Some(ns) => el.set_attribute_ns(Some(ns), name, value).unwrap(),
|
||||
None => el.set_attribute(name, value).unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
for child in *children {
|
||||
let _ = el.append_child(&self.create_template_node(child));
|
||||
}
|
||||
el.dyn_into().unwrap()
|
||||
}
|
||||
Text { text } => self.document.create_text_node(text).dyn_into().unwrap(),
|
||||
DynamicText { .. } => self.document.create_text_node("p").dyn_into().unwrap(),
|
||||
Dynamic { .. } => {
|
||||
let el = self.document.create_element("pre").unwrap();
|
||||
let _ = el.toggle_attribute("hidden");
|
||||
el.dyn_into().unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_edits(&mut self, mut edits: Vec<Mutation>) {
|
||||
use Mutation::*;
|
||||
let i = &self.interpreter;
|
||||
for edit in edits.drain(..) {
|
||||
let i = &mut self.interpreter;
|
||||
for edit in &edits {
|
||||
match edit {
|
||||
AppendChildren { id, m } => i.AppendChildren(m as u32, id.0 as u32),
|
||||
AssignId { path, id } => i.AssignId(path, id.0 as u32),
|
||||
CreatePlaceholder { id } => i.CreatePlaceholder(id.0 as u32),
|
||||
CreateTextNode { value, id } => i.CreateTextNode(value.into(), id.0 as u32),
|
||||
HydrateText { path, value, id } => i.HydrateText(path, value, id.0 as u32),
|
||||
LoadTemplate { name, index, id } => i.LoadTemplate(name, index as u32, id.0 as u32),
|
||||
ReplaceWith { id, m } => i.ReplaceWith(id.0 as u32, m as u32),
|
||||
ReplacePlaceholder { path, m } => i.ReplacePlaceholder(path, m as u32),
|
||||
InsertAfter { id, m } => i.InsertAfter(id.0 as u32, m as u32),
|
||||
InsertBefore { id, m } => i.InsertBefore(id.0 as u32, m as u32),
|
||||
AppendChildren { id, m } => i.append_children(id.0 as u32, *m as u32),
|
||||
AssignId { path, id } => {
|
||||
i.assign_id(path.as_ptr() as u32, path.len() as u8, id.0 as u32)
|
||||
}
|
||||
CreatePlaceholder { id } => i.create_placeholder(id.0 as u32),
|
||||
CreateTextNode { value, id } => i.create_text_node(value, id.0 as u32),
|
||||
HydrateText { path, value, id } => {
|
||||
i.hydrate_text(path.as_ptr() as u32, path.len() as u8, value, id.0 as u32)
|
||||
}
|
||||
LoadTemplate { name, index, id } => {
|
||||
if let Some(tmpl_id) = self.templates.get(*name) {
|
||||
i.load_template(*tmpl_id, *index as u32, id.0 as u32)
|
||||
}
|
||||
}
|
||||
ReplaceWith { id, m } => i.replace_with(id.0 as u32, *m as u32),
|
||||
ReplacePlaceholder { path, m } => {
|
||||
i.replace_placeholder(path.as_ptr() as u32, path.len() as u8, *m as u32)
|
||||
}
|
||||
InsertAfter { id, m } => i.insert_after(id.0 as u32, *m as u32),
|
||||
InsertBefore { id, m } => i.insert_before(id.0 as u32, *m as u32),
|
||||
SetAttribute {
|
||||
name,
|
||||
value,
|
||||
id,
|
||||
ns,
|
||||
} => i.SetAttribute(id.0 as u32, name, value.into(), ns),
|
||||
} => i.set_attribute(id.0 as u32, name, value, ns.unwrap_or_default()),
|
||||
SetBoolAttribute { name, value, id } => {
|
||||
i.SetBoolAttribute(id.0 as u32, name, value)
|
||||
i.set_attribute(id.0 as u32, name, if *value { "true" } else { "false" }, "")
|
||||
}
|
||||
SetText { value, id } => i.SetText(id.0 as u32, value.into()),
|
||||
SetText { value, id } => i.set_text(id.0 as u32, value),
|
||||
NewEventListener { name, id, .. } => {
|
||||
self.interpreter.NewEventListener(
|
||||
name,
|
||||
id.0 as u32,
|
||||
event_bubbles(&name[2..]),
|
||||
self.handler.as_ref().unchecked_ref(),
|
||||
);
|
||||
i.new_event_listener(name, id.0 as u32, event_bubbles(&name[2..]) as u8);
|
||||
}
|
||||
RemoveEventListener { name, id } => i.RemoveEventListener(name, id.0 as u32),
|
||||
Remove { id } => i.Remove(id.0 as u32),
|
||||
PushRoot { id } => i.PushRoot(id.0 as u32),
|
||||
RemoveEventListener { name, id } => {
|
||||
i.remove_event_listener(name, id.0 as u32, event_bubbles(&name[2..]) as u8)
|
||||
}
|
||||
Remove { id } => i.remove(id.0 as u32),
|
||||
PushRoot { id } => i.push_root(id.0 as u32),
|
||||
}
|
||||
}
|
||||
edits.clear();
|
||||
i.flush();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
|
||||
pub use crate::cfg::Config;
|
||||
use crate::dom::virtual_event_from_websys_event;
|
||||
pub use crate::util::use_eval;
|
||||
pub use crate::util::{use_eval, EvalResult};
|
||||
use dioxus_core::{Element, ElementId, Scope, VirtualDom};
|
||||
use futures_util::{pin_mut, FutureExt, StreamExt};
|
||||
|
||||
|
@ -195,7 +195,7 @@ pub async fn run_with_props<T: 'static>(root: fn(Scope<T>) -> Element, root_prop
|
|||
// the mutations come back with nothing - we need to actually mount them
|
||||
websys_dom.mount();
|
||||
|
||||
let mut work_loop = ric_raf::RafLoop::new();
|
||||
let _work_loop = ric_raf::RafLoop::new();
|
||||
|
||||
loop {
|
||||
log::debug!("waiting for work");
|
||||
|
@ -228,6 +228,7 @@ pub async fn run_with_props<T: 'static>(root: fn(Scope<T>) -> Element, root_prop
|
|||
res = rx.try_next().transpose().unwrap().ok();
|
||||
}
|
||||
|
||||
// Todo: This is currently disabled because it has a negative impact on responce times for events but it could be re-enabled for tasks
|
||||
// Jank free rendering
|
||||
//
|
||||
// 1. wait for the browser to give us "idle" time
|
||||
|
@ -236,13 +237,13 @@ pub async fn run_with_props<T: 'static>(root: fn(Scope<T>) -> Element, root_prop
|
|||
// 4. Wait for the animation frame to patch the dom
|
||||
|
||||
// wait for the mainthread to schedule us in
|
||||
let deadline = work_loop.wait_for_idle_time().await;
|
||||
// let deadline = work_loop.wait_for_idle_time().await;
|
||||
|
||||
// run the virtualdom work phase until the frame deadline is reached
|
||||
let edits = dom.render_with_deadline(deadline).await;
|
||||
let edits = dom.render_immediate();
|
||||
|
||||
// wait for the animation frame to fire so we can apply our changes
|
||||
work_loop.wait_for_raf().await;
|
||||
// work_loop.wait_for_raf().await;
|
||||
|
||||
websys_dom.load_templates(&edits.templates);
|
||||
websys_dom.apply_edits(edits.edits);
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
//! Utilities specific to websys
|
||||
|
||||
use std::{
|
||||
future::{IntoFuture, Ready},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use dioxus_core::*;
|
||||
use serde::de::Error;
|
||||
use serde_json::Value;
|
||||
|
||||
/// Get a closure that executes any JavaScript in the webpage.
|
||||
///
|
||||
|
@ -15,12 +22,51 @@ use dioxus_core::*;
|
|||
///
|
||||
/// The closure will panic if the provided script is not valid JavaScript code
|
||||
/// or if it returns an uncaught error.
|
||||
pub fn use_eval<S: std::string::ToString>(cx: &ScopeState) -> &dyn Fn(S) {
|
||||
pub fn use_eval<S: std::string::ToString>(cx: &ScopeState) -> &dyn Fn(S) -> EvalResult {
|
||||
cx.use_hook(|| {
|
||||
|script: S| {
|
||||
js_sys::Function::new_no_args(&script.to_string())
|
||||
.call0(&wasm_bindgen::JsValue::NULL)
|
||||
.expect("failed to eval script");
|
||||
let body = script.to_string();
|
||||
EvalResult {
|
||||
value: if let Ok(value) =
|
||||
js_sys::Function::new_no_args(&body).call0(&wasm_bindgen::JsValue::NULL)
|
||||
{
|
||||
if let Ok(stringified) = js_sys::JSON::stringify(&value) {
|
||||
if !stringified.is_undefined() && stringified.is_valid_utf16() {
|
||||
let string: String = stringified.into();
|
||||
Value::from_str(&string)
|
||||
} else {
|
||||
Err(serde_json::Error::custom("Failed to stringify result"))
|
||||
}
|
||||
} else {
|
||||
Err(serde_json::Error::custom("Failed to stringify result"))
|
||||
}
|
||||
} else {
|
||||
Err(serde_json::Error::custom("Failed to execute script"))
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// A wrapper around the result of a JavaScript evaluation.
|
||||
/// This implements IntoFuture to be compatible with the desktop renderer's EvalResult.
|
||||
pub struct EvalResult {
|
||||
value: Result<Value, serde_json::Error>,
|
||||
}
|
||||
|
||||
impl EvalResult {
|
||||
/// Get the result of the Javascript execution.
|
||||
pub fn get(self) -> Result<Value, serde_json::Error> {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoFuture for EvalResult {
|
||||
type Output = Result<Value, serde_json::Error>;
|
||||
|
||||
type IntoFuture = Ready<Result<Value, serde_json::Error>>;
|
||||
|
||||
fn into_future(self) -> Self::IntoFuture {
|
||||
std::future::ready(self.value)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue