wip: overhaul event system

This commit is contained in:
Jonathan Kelley 2021-10-04 01:28:04 -04:00
parent 82953f2ac3
commit 7a03c1d2b4
34 changed files with 2489 additions and 1469 deletions

View file

@ -85,6 +85,7 @@ members = [
"packages/ssr",
"packages/desktop",
"packages/mobile",
"packages/webview-client"
]

View file

@ -1,3 +1,5 @@
use dioxus::events::on::MouseEvent;
use dioxus::events::DioxusEvent;
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
use dioxus_core_macro::*;
@ -37,6 +39,9 @@ struct RowProps {
label: Label,
}
fn Row<'a>(cx: Context<'a>, props: &'a RowProps) -> DomTree<'a> {
let handler = move |evt: MouseEvent| {
let g = evt.button;
};
cx.render(rsx! {
tr {
td { class:"col-md-1", "{props.row_id}" }
@ -44,7 +49,7 @@ fn Row<'a>(cx: Context<'a>, props: &'a RowProps) -> DomTree<'a> {
a { class: "lbl", "{props.label}" }
}
td { class: "col-md-1"
a { class: "remove", onclick: move |_| {/* remove */}
a { class: "remove", onclick: {handler}
span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" }
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -19,6 +19,7 @@ pub mod context;
pub mod diff;
pub mod diff_stack;
pub mod events;
// pub mod events2;
pub mod heuristics;
pub mod hooklist;
pub mod hooks;
@ -61,8 +62,8 @@ pub(crate) mod innerlude {
pub use crate::innerlude::{
Context, DioxusElement, DomEdit, DomTree, ElementId, EventPriority, LazyNodes, MountType,
Mutations, NodeFactory, Properties, ScopeId, SuspendedContext, SyntheticEvent, TaskHandle,
TestDom, ThreadsafeVirtualDom, UserEvent, VNode, VirtualDom, FC,
Mutations, NodeFactory, Properties, SchedulerMsg, ScopeId, SuspendedContext, SyntheticEvent,
TaskHandle, TestDom, ThreadsafeVirtualDom, UserEvent, VNode, VirtualDom, FC,
};
pub mod prelude {
@ -78,4 +79,5 @@ pub mod exports {
//! Important dependencies that are used by the rest of the library
// the foundation of this library
pub use bumpalo;
pub use futures_channel;
}

View file

@ -185,7 +185,6 @@ pub enum DomEdit<'bump> {
root: u64,
},
RemoveAllChildren,
CreateTextNode {
text: &'bump str,
id: u64,
@ -232,7 +231,6 @@ impl DomEdit<'_> {
DomEdit::AppendChildren { .. } => id == "AppendChildren",
DomEdit::ReplaceWith { .. } => id == "ReplaceWith",
DomEdit::Remove { .. } => id == "Remove",
DomEdit::RemoveAllChildren => id == "RemoveAllChildren",
DomEdit::CreateTextNode { .. } => id == "CreateTextNode",
DomEdit::CreateElement { .. } => id == "CreateElement",
DomEdit::CreateElementNs { .. } => id == "CreateElementNs",

View file

@ -43,16 +43,6 @@ impl ResourcePool {
inner.get_mut(idx.0)
}
// return a bumpframe with a lifetime attached to the arena borrow
// this is useful for merging lifetimes
pub fn with_scope_vnode<'b>(
&self,
_id: ScopeId,
_f: impl FnOnce(&mut Scope) -> &VNode<'b>,
) -> Option<&VNode<'b>> {
todo!()
}
pub fn try_remove(&self, id: ScopeId) -> Option<Scope> {
let inner = unsafe { &mut *self.components.get() };
Some(inner.remove(id.0))

View file

@ -106,7 +106,8 @@ pub enum SchedulerMsg {
}
pub enum TaskMsg {
SubmitTask(FiberTask, u64),
// SubmitTask(FiberTask, u64),
// SubmitTask(FiberTask, u64),
ToggleTask(u64),
PauseTask(u64),
ResumeTask(u64),
@ -163,7 +164,10 @@ pub(crate) struct Scheduler {
}
impl Scheduler {
pub(crate) fn new() -> Self {
pub(crate) fn new(
sender: UnboundedSender<SchedulerMsg>,
receiver: UnboundedReceiver<SchedulerMsg>,
) -> Self {
/*
Preallocate 2000 elements and 100 scopes to avoid dynamic allocation.
Perhaps this should be configurable from some external config?
@ -173,7 +177,6 @@ impl Scheduler {
let heuristics = HeuristicsEngine::new();
let (sender, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>();
let task_counter = Rc::new(Cell::new(0));
let channel = EventChannel {
@ -183,15 +186,19 @@ impl Scheduler {
let sender = sender.clone();
Rc::new(move |id| sender.unbounded_send(SchedulerMsg::Immediate(id)).unwrap())
},
// todo: we want to get the futures out of the scheduler message
// the scheduler message should be send/sync
submit_task: {
Rc::new(move |fiber_task| {
let task_id = task_counter.get();
task_counter.set(task_id + 1);
sender
.unbounded_send(SchedulerMsg::Task(TaskMsg::SubmitTask(
fiber_task, task_id,
)))
.unwrap();
todo!();
// sender
// .unbounded_send(SchedulerMsg::Task(TaskMsg::SubmitTask(
// fiber_task, task_id,
// )))
// .unwrap();
TaskHandle {
our_id: task_id,
sender: sender.clone(),

View file

@ -12,7 +12,8 @@ pub struct TestDom {
impl TestDom {
pub fn new() -> TestDom {
let bump = Bump::new();
let scheduler = Scheduler::new();
let (sender, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>();
let scheduler = Scheduler::new(sender, receiver);
TestDom { bump, scheduler }
}

View file

@ -1,5 +1,12 @@
//! A threadsafe wrapper for the VirtualDom
//!
//! This is an experimental module, and must be explicitly opted-into.
//!
//! It's not guaranteed that this module produces safe results, so use at your own peril.
//!
//! The only real "right" answer to a Send VirtualDom is by ensuring all hook data is Send
//!
//!
use std::sync::{Arc, Mutex, MutexGuard};
use crate::VirtualDom;
@ -18,9 +25,17 @@ use crate::VirtualDom;
/// directly. Even then, it's not possible to access any hook data. This means that non-Send types are only "in play"
/// while the VirtualDom is locked with a non-Send marker.
///
/// Note that calling "wait for work" on the regular VirtualDom is inherently non-Send. If there are async tasks that
/// need to be awaited, they must be done thread-local since we don't place any requirements on user tasks. This can be
/// done with the function "spawn_local" in either tokio or async_std.
/// Calling "wait for work" on the ThreadsafeVirtualDom does indeed work, because this method only accesses `Send` types.
/// Otherwise, the VirtualDom must be unlocked on the current thread to modify any data.
///
/// Dioxus does have the concept of local tasks and non-local tasks.
///
/// For the ThreadsafeVirtualDom, non-Send tasks are not ran - and will error out during a Debug build if one is submitted.
///
///
///
/// When Tasks are submitted to a thread-local executor,
///
pub struct ThreadsafeVirtualDom {
inner: Arc<Mutex<VirtualDom>>,
}

View file

@ -19,8 +19,10 @@
//! This module includes just the barebones for a complete VirtualDOM API.
//! Additional functionality is defined in the respective files.
use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
use crate::innerlude::*;
use std::{any::Any, rc::Rc, sync::Arc};
use std::{any::Any, rc::Rc};
/// An integrated virtual node system that progresses events and diffs UI trees.
///
@ -121,6 +123,20 @@ impl VirtualDom {
/// let mutations = dom.rebuild();
/// ```
pub fn new_with_props<P: 'static + Send>(root: FC<P>, root_props: P) -> Self {
let (sender, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>();
Self::new_with_props_and_scheduler(root, root_props, sender, receiver)
}
/// Launch the VirtualDom, but provide your own channel for receiving and sending messages into the scheduler.
///
/// This is useful when the VirtualDom must be driven from outside a thread and it doesn't make sense to wait for the
/// VirtualDom to be created just to retrive its channel receiver.
pub fn new_with_props_and_scheduler<P: 'static + Send>(
root: FC<P>,
root_props: P,
sender: UnboundedSender<SchedulerMsg>,
receiver: UnboundedReceiver<SchedulerMsg>,
) -> Self {
let root_fc = Box::new(root);
let root_props: Rc<dyn Any + Send> = Rc::new(root_props);
@ -132,7 +148,7 @@ impl VirtualDom {
std::mem::transmute(root(Context { scope }, props))
});
let scheduler = Scheduler::new();
let scheduler = Scheduler::new(sender, receiver);
let base_scope = scheduler.pool.insert_scope_with_key(|myidx| {
Scope::new(
@ -343,47 +359,46 @@ impl VirtualDom {
/// Waits for the scheduler to have work
/// This lets us poll async tasks during idle periods without blocking the main thread.
pub async fn wait_for_work(&mut self) {
todo!("making vdom send right now");
// if self.scheduler.has_any_work() {
// log::debug!("No need to wait for work, we already have some");
// return;
// }
if self.scheduler.has_any_work() {
log::debug!("No need to wait for work, we already have some");
return;
}
// log::debug!("No active work.... waiting for some...");
// use futures_util::StreamExt;
log::debug!("No active work.... waiting for some...");
use futures_util::StreamExt;
// // right now this won't poll events if there is ongoing work
// // in the future we want to prioritize some events over ongoing work
// // this is coming in the "priorities" PR
// right now this won't poll events if there is ongoing work
// in the future we want to prioritize some events over ongoing work
// this is coming in the "priorities" PR
// // Wait for any new events if we have nothing to do
// // todo: poll the events once even if there is work to do to prevent starvation
// futures_util::select! {
// _ = self.scheduler.async_tasks.next() => {}
// msg = self.scheduler.receiver.next() => {
// match msg.unwrap() {
// SchedulerMsg::Task(t) => todo!(),
// SchedulerMsg::Immediate(im) => {
// self.scheduler.dirty_scopes.insert(im);
// }
// SchedulerMsg::UiEvent(evt) => {
// self.scheduler.ui_events.push_back(evt);
// }
// }
// },
// }
// Wait for any new events if we have nothing to do
// todo: poll the events once even if there is work to do to prevent starvation
futures_util::select! {
_ = self.scheduler.async_tasks.next() => {}
msg = self.scheduler.receiver.next() => {
match msg.unwrap() {
SchedulerMsg::Task(t) => todo!(),
SchedulerMsg::Immediate(im) => {
self.scheduler.dirty_scopes.insert(im);
}
SchedulerMsg::UiEvent(evt) => {
self.scheduler.ui_events.push_back(evt);
}
}
},
}
// while let Ok(Some(msg)) = self.scheduler.receiver.try_next() {
// match msg {
// SchedulerMsg::Task(t) => todo!(),
// SchedulerMsg::Immediate(im) => {
// self.scheduler.dirty_scopes.insert(im);
// }
// SchedulerMsg::UiEvent(evt) => {
// self.scheduler.ui_events.push_back(evt);
// }
// }
// }
while let Ok(Some(msg)) = self.scheduler.receiver.try_next() {
match msg {
SchedulerMsg::Task(t) => todo!(),
SchedulerMsg::Immediate(im) => {
self.scheduler.dirty_scopes.insert(im);
}
SchedulerMsg::UiEvent(evt) => {
self.scheduler.ui_events.push_back(evt);
}
}
}
}
}
@ -407,14 +422,5 @@ impl std::fmt::Display for VirtualDom {
}
}
/*
Send safety...
The VirtualDom can only be "send" if the internals exposed to user code are also "send".
IE it's okay to move an Rc from one thread to another thread as long as it's only used internally
*/
// we never actually use the contents of this root caller
struct RootCaller(Rc<dyn for<'b> Fn(&'b Scope) -> DomTree<'b> + 'static>);

View file

@ -16,8 +16,4 @@ async fn event_queue_works() {
let mut dom = VirtualDom::new(App);
let edits = dom.rebuild();
async_std::task::spawn_local(async move {
// let mutations = dom.run_unbounded().await;
});
}

View file

@ -1,3 +1,4 @@
{
"rust-analyzer.inlayHints.enable": true
"rust-analyzer.inlayHints.enable": true,
"rust-analyzer.cargo.allFeatures": true
}

View file

@ -19,15 +19,16 @@ thiserror = "1.0.23"
log = "0.4.13"
fern = { version = "0.6.0", features = ["colored"] }
html-escape = "0.2.9"
wry = "0.11.0"
# wry = { version = "0.12.2", git = "https://github.com/jkelleyrtp/wry.git", branch = "jk/fnmut_rpc" }
wry = "0.12.2"
tokio = { version = "1.12.0", features = ["full"] }
futures-channel = "0.3.16"
[dev-dependencies]
dioxus-html = { path = "../html" }
tide = "0.15.0"
tide-websockets = "0.3.0"
dioxus-core-macro = { path = "../core-macro" }
dioxus-hooks = { path = "../hooks" }
# thiserror = "1.0.23"
# log = "0.4.13"
# fern = { version = "0.6.0", features = ["colored"] }

View file

@ -6,26 +6,19 @@ Dioxus-webview is an attempt at making a simpler "Tauri" where creating desktop
```rust
// main.rs
#[async_std::main]
async fn main() {
dioxus_desktop::new(|cx, props|{
let (count, set_count) = use_state(cx, || 0);
cx.render(html! {
<div>
<h1> "Dioxus Desktop Demo" </h1>
<p> "Count is {count}"</p>
<button onclick=|_| set_count(count + 1) >
"Click to increment"
</button>
</div>
})
})
.configure_webview(|view| {
// custom webview config options
})
.launch()
.await;
fn main() {
dioxus_desktop::new(App, |c| c)
.launch()
.await;
}
static App: FC<()> = |cx, props|{
let (count, set_count) = use_state(cx, || 0);
rsx!(cx, div {
h1 { "Dioxus Desktop Demo" }
p { "Count is {count}"}
button { onclick: move |_| count += 1}
})
};
```
and then to create a native .app:
@ -45,3 +38,8 @@ By bridging the native process, desktop apps can access full multithreading powe
Dioxus-desktop is a pure liveview application where all of the state and event handlers are proxied through the liveview and into the native process. For pure server-based liveview, this would normally be too slow (in both render performance and latency), but because the VDom is local, desktop apps are just as fast as Electron.
Dioxus-desktop leverages dioxus-liveview under the hood, but with convenience wrappers around setting up the VDom bridge, proxying events, and serving the initial WebSys-Renderer. The backend is served by Tide, so an async runtime _is_ needed - we recommend async-std in Tokio mode.
## Async Runtime

View file

@ -0,0 +1,33 @@
use dioxus_core::prelude::*;
use dioxus_core_macro::*;
use dioxus_html as dioxus_elements;
fn main() {
let (window_loop, tasks) = dioxus_desktop::start(App, |c| c);
std::thread::spawn(move || {
//
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
runtime.block_on(async move {
let mut vir = VirtualDom::new_with_props(root, props);
let channel = vir.get_event_sender();
loop {
vir.wait_for_work().await;
let edits = vir.run_with_deadline(|| false);
let edit_string = serde_json::to_string(&edits[0].edits).unwrap();
event_tx.send(edit_string).unwrap();
}
})
});
window_loop.run();
}
static App: FC<()> = |cx| {
//
cx.render(rsx!(div {}))
};

View file

@ -0,0 +1,111 @@
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
use dioxus_core_macro::*;
use dioxus_hooks::*;
use dioxus_html as dioxus_elements;
fn main() {
dioxus_desktop::set_up_logging(true);
dioxus_desktop::launch(App, |c| c).unwrap();
}
enum Scene {
ClientsList,
NewClientForm,
Settings,
}
#[derive(Clone, Debug, Default)]
pub struct Client {
pub first_name: String,
pub last_name: String,
pub description: String,
}
static App: FC<()> = |cx, _| {
let scene = use_state(cx, || Scene::ClientsList);
let clients = use_ref(cx, || vec![] as Vec<Client>);
let firstname = use_state(cx, || String::new());
let lastname = use_state(cx, || String::new());
let description = use_state(cx, || String::new());
let scene = match *scene {
Scene::ClientsList => {
rsx!(cx, div { class: "crm"
h2 { "List of clients" margin_bottom: "10px" }
div { class: "clients" margin_left: "10px"
{clients.read().iter().map(|client| rsx!(
div { class: "client" style: "margin-bottom: 50px"
p { "First Name: {client.first_name}" }
p { "Last Name: {client.last_name}" }
p {"Description: {client.description}"}
})
)}
}
button { class: "pure-button pure-button-primary" onclick: move |_| scene.set(Scene::NewClientForm), "Add New" }
button { class: "pure-button" onclick: move |_| scene.set(Scene::Settings), "Settings" }
})
}
Scene::NewClientForm => {
let add_new = move |_| {
clients.write().push(Client {
description: (*description).clone(),
first_name: (*firstname).clone(),
last_name: (*lastname).clone(),
});
description.set(String::new());
firstname.set(String::new());
lastname.set(String::new());
};
rsx!(cx, div { class: "crm"
h2 {"Add new client" margin_bottom: "10px" }
form { class: "pure-form"
input { class: "new-client firstname" placeholder: "First name" value: "{firstname}"
oninput: move |evt| firstname.set(evt.value())
}
input { class: "new-client lastname" placeholder: "Last name" value: "{lastname}"
oninput: move |evt| lastname.set(evt.value())
}
textarea { class: "new-client description" placeholder: "Description" value: "{description}"
oninput: move |evt| description.set(evt.value())
}
}
button { class: "pure-button pure-button-primary", onclick: {add_new}, "Add New" }
button { class: "pure-button", onclick: move |_| scene.set(Scene::ClientsList), "Go Back" }
})
}
Scene::Settings => {
rsx!(cx, div {
h2 { "Settings" margin_bottom: "10px" }
button {
background: "rgb(202, 60, 60)"
class: "pure-button pure-button-primary"
onclick: move |_| {
clients.write().clear();
scene.set(Scene::ClientsList);
},
"Remove all clients"
}
button {
class: "pure-button pure-button-primary"
onclick: move |_| scene.set(Scene::ClientsList),
"Go Back"
}
})
}
};
rsx!(cx, body {
link {
rel: "stylesheet"
href: "https://unpkg.com/purecss@2.0.6/build/pure-min.css"
integrity: "sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5"
crossorigin: "anonymous"
}
margin_left: "35%"
h1 {"Dioxus CRM Example"}
{scene}
})
};

View file

@ -5,20 +5,14 @@ use dioxus_core_macro::*;
use dioxus_html as dioxus_elements;
fn main() {
std::thread::spawn(|| {
let mut vdom = VirtualDom::new(App);
let f = async_std::task::block_on(vdom.wait_for_work());
});
let a = 10;
// async_std::task::spawn_blocking(|| async move {
// });
dioxus_desktop::launch(App, |c| c).unwrap();
}
static App: FC<()> = |cx, props| {
//
cx.render(rsx!(
div {
"hello world!"
}
{(0..10).map(|f| rsx!( div {"abc {f}"}))}
))
};

View file

@ -0,0 +1,11 @@
fn main() {
tauri::AppBuilder::default().setup(move |app, name| {
//
let window = app.get_window();
let window = app.get_window();
tauri::spawn(|| async move {
//
});
});
}

View file

@ -4,30 +4,36 @@ use dioxus_core::DomEdit;
use wry::{
application::{
error::OsError,
event_loop::EventLoopWindowTarget,
event_loop::{EventLoop, EventLoopWindowTarget},
menu::MenuBar,
window::{Fullscreen, Icon, Window, WindowBuilder},
},
webview::{RpcRequest, RpcResponse},
webview::{RpcRequest, RpcResponse, WebView},
};
pub struct DesktopConfig<'a> {
pub window: WindowBuilder,
pub(crate) manual_edits: Option<DomEdit<'a>>,
pub(crate) manual_edits: Option<Vec<DomEdit<'a>>>,
pub(crate) pre_rendered: Option<String>,
pub(crate) event_handler: Option<Box<dyn Fn(&mut EventLoop<()>, &mut WebView)>>,
}
impl DesktopConfig<'_> {
impl<'a> DesktopConfig<'a> {
/// Initializes a new `WindowBuilder` with default values.
#[inline]
pub fn new() -> Self {
Self {
event_handler: None,
window: Default::default(),
pre_rendered: None,
manual_edits: None,
}
}
pub fn with_edits(&mut self, edits: Vec<DomEdit<'a>>) {
self.manual_edits = Some(edits);
}
pub fn with_prerendered(&mut self, content: String) -> &mut Self {
self.pre_rendered = Some(content);
self

View file

@ -22,69 +22,100 @@ pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent {
let mut data: Vec<ImEvent> = serde_json::from_value(val).unwrap();
let data = data.drain(..).next().unwrap();
let event = SyntheticEvent::MouseEvent(MouseEvent(Arc::new(WebviewMouseEvent)));
let scope = ScopeId(data.scope as usize);
let mounted_dom_id = Some(ElementId(data.mounted_dom_id as usize));
UserEvent {
name: todo!(),
name: "click",
event,
scope,
mounted_dom_id,
}
}
#[derive(Debug)]
struct WebviewMouseEvent;
impl MouseEventInner for WebviewMouseEvent {
fn alt_key(&self) -> bool {
todo!()
}
fn button(&self) -> i16 {
todo!()
}
fn buttons(&self) -> u16 {
todo!()
}
fn client_x(&self) -> i32 {
todo!()
}
fn client_y(&self) -> i32 {
todo!()
}
fn ctrl_key(&self) -> bool {
todo!()
}
fn meta_key(&self) -> bool {
todo!()
}
fn page_x(&self) -> i32 {
todo!()
}
fn page_y(&self) -> i32 {
todo!()
}
fn screen_x(&self) -> i32 {
todo!()
}
fn screen_y(&self) -> i32 {
todo!()
}
fn shift_key(&self) -> bool {
todo!()
}
fn get_modifier_state(&self, key_code: &str) -> bool {
todo!()
fn event_name_from_typ(typ: &str) -> &'static str {
match typ {
"copy" => "copy",
"cut" => "cut",
"paste" => "paste",
"compositionend" => "compositionend",
"compositionstart" => "compositionstart",
"compositionupdate" => "compositionupdate",
"keydown" => "keydown",
"keypress" => "keypress",
"keyup" => "keyup",
"focus" => "focus",
"blur" => "blur",
"change" => "change",
"input" => "input",
"invalid" => "invalid",
"reset" => "reset",
"submit" => "submit",
"click" => "click",
"contextmenu" => "contextmenu",
"doubleclick" => "doubleclick",
"drag" => "drag",
"dragend" => "dragend",
"dragenter" => "dragenter",
"dragexit" => "dragexit",
"dragleave" => "dragleave",
"dragover" => "dragover",
"dragstart" => "dragstart",
"drop" => "drop",
"mousedown" => "mousedown",
"mouseenter" => "mouseenter",
"mouseleave" => "mouseleave",
"mousemove" => "mousemove",
"mouseout" => "mouseout",
"mouseover" => "mouseover",
"mouseup" => "mouseup",
"pointerdown" => "pointerdown",
"pointermove" => "pointermove",
"pointerup" => "pointerup",
"pointercancel" => "pointercancel",
"gotpointercapture" => "gotpointercapture",
"lostpointercapture" => "lostpointercapture",
"pointerenter" => "pointerenter",
"pointerleave" => "pointerleave",
"pointerover" => "pointerover",
"pointerout" => "pointerout",
"select" => "select",
"touchcancel" => "touchcancel",
"touchend" => "touchend",
"touchmove" => "touchmove",
"touchstart" => "touchstart",
"scroll" => "scroll",
"wheel" => "wheel",
"animationstart" => "animationstart",
"animationend" => "animationend",
"animationiteration" => "animationiteration",
"transitionend" => "transitionend",
"abort" => "abort",
"canplay" => "canplay",
"canplaythrough" => "canplaythrough",
"durationchange" => "durationchange",
"emptied" => "emptied",
"encrypted" => "encrypted",
"ended" => "ended",
"error" => "error",
"loadeddata" => "loadeddata",
"loadedmetadata" => "loadedmetadata",
"loadstart" => "loadstart",
"pause" => "pause",
"play" => "play",
"playing" => "playing",
"progress" => "progress",
"ratechange" => "ratechange",
"seeked" => "seeked",
"seeking" => "seeking",
"stalled" => "stalled",
"suspend" => "suspend",
"timeupdate" => "timeupdate",
"volumechange" => "volumechange",
"waiting" => "waiting",
"toggle" => "toggle",
_ => {
panic!("unsupported event type")
}
}
}

View file

@ -9,212 +9,7 @@
<div id="_dioxusroot">
</div>
</body>
<script>
class Interpreter {
constructor(root) {
this.root = root;
this.stack = [root];
this.listeners = {
"onclick": {}
};
this.lastNodeWasText = false;
this.nodes = [root, root, root, root];
}
top() {
return this.stack[this.stack.length - 1];
}
pop() {
return this.stack.pop();
}
PushRoot(edit) {
const id = edit.id;
const node = this.nodes[id];
console.log("pushing root ", node, "with id", id);
this.stack.push(node);
}
PopRoot(edit) {
this.stack.pop();
}
AppendChildren(edit) {
let root = this.stack[this.stack.length - (edit.many + 1)];
for (let i = 0; i < edit.many; i++) {
console.log("popping ", i, edit.many);
let node = this.pop();
root.appendChild(node);
}
}
ReplaceWith(edit) {
let root = this.stack[this.stack.length - (edit.many + 1)];
let els = [];
for (let i = 0; i < edit.many; i++) {
els.push(this.pop());
}
root.replaceWith(...els);
}
Remove(edit) {
const node = this.stack.pop();
node.remove();
}
RemoveAllChildren(edit) {}
CreateTextNode(edit) {
const node = document.createTextNode(edit.text);
this.nodes[edit.id] = node;
this.stack.push(node);
}
CreateElement(edit) {
const tagName = edit.tag;
const el = document.createElement(tagName);
this.nodes[edit.id] = el;
console.log(`creating element: `, edit);
this.stack.push(el);
}
CreateElementNs(edit) {
const tagName = edit.tag;
console.log(`creating namespaced element: `, edit);
this.stack.push(document.createElementNS(edit.ns, edit.tag));
}
CreatePlaceholder(edit) {
const a = `this.stack.push(document.createElement(" pre"))`;
this.stack.push(document.createComment("vroot"));
}
NewEventListener(edit) {
const element_id = edit.element_id;
const event_name = edit.event_name;
const mounted_node_id = edit.mounted_node_id;
const scope = edit.scope;
const element = this.top();
element.setAttribute(`dioxus-event-${event_name}`, `${scope}.${mounted_node_id}`);
console.log("listener map is", this.listeners);
if (this.listeners[event_name] === undefined) {
console.log("adding listener!");
this.listeners[event_name] = "bla";
this.root.addEventListener(event_name, (event) => {
const target = event.target;
const type = event.type;
const val = target.getAttribute(`dioxus-event-${event_name}`);
const fields = val.split(".");
const scope_id = parseInt(fields[0]);
const real_id = parseInt(fields[1]);
console.log(`parsed event with scope_id ${scope_id} and real_id ${real_id}`);
rpc.call('user_event', {
event: event_name,
scope: scope_id,
mounted_dom_id: real_id,
}).then((reply) => {
console.log(reply);
this.stack.push(this.root);
let edits = reply.edits;
for (let x = 0; x < edits.length; x++) {
let edit = edits[x];
console.log(edit);
let f = this[edit.type];
f.call(this, edit);
}
console.log("initiated");
}).catch((err) => {
console.log("failed to initiate", err);
});
});
}
}
RemoveEventListener(edit) {}
SetText(edit) {
this.top().textContent = edit.text;
}
SetAttribute(edit) {
const name = edit.field;
const value = edit.value;
const ns = edit.ns;
const node = this.top(this.stack);
if (ns == "style") {
node.style[name] = value;
} else if (ns !== undefined) {
node.setAttributeNS(ns, name, value);
} else {
node.setAttribute(name, value);
}
if (name === "value") {
node.value = value;
}
if (name === "checked") {
node.checked = true;
}
if (name === "selected") {
node.selected = true;
}
}
RemoveAttribute(edit) {
const name = edit.field;
const node = this.top(this.stack);
node.removeAttribute(name);
if (name === "value") {
node.value = null;
}
if (name === "checked") {
node.checked = false;
}
if (name === "selected") {
node.selected = false;
}
}
}
async function initialize() {
const reply = await rpc.call('initiate');
let root = window.document.getElementById("_dioxusroot");
const interpreter = new Interpreter(root);
console.log(reply);
let pre_rendered = reply.pre_rendered;
if (pre_rendered !== undefined) {
root.innerHTML = pre_rendered;
}
const edits = reply.edits;
for (let x = 0; x < edits.length; x++) {
let edit = edits[x];
console.log(edit);
let f = interpreter[edit.type];
f.call(interpreter, edit);
}
console.log("stack completed: ", interpreter.stack);
}
console.log("initializing...");
initialize();
<script type="text/javascript" src="/index.js">
</script>
</html>

View file

@ -0,0 +1,216 @@
class Interpreter {
constructor(root) {
this.root = root;
this.stack = [root];
this.listeners = {
"onclick": {}
};
this.lastNodeWasText = false;
this.nodes = [root, root, root, root];
}
top() {
return this.stack[this.stack.length - 1];
}
pop() {
return this.stack.pop();
}
PushRoot(edit) {
const id = edit.id;
const node = this.nodes[id];
console.log("pushing root ", node, "with id", id);
this.stack.push(node);
}
PopRoot(_edit) {
this.stack.pop();
}
AppendChildren(edit) {
let root = this.stack[this.stack.length - (1 + edit.many)];
let to_add = this.stack.splice(this.stack.length - edit.many);
for (let i = 0; i < edit.many; i++) {
root.appendChild(to_add[i]);
}
}
ReplaceWith(edit) {
console.log(edit);
let root = this.nodes[edit.root];
let els = this.stack.splice(this.stack.length - edit.m);
console.log(root);
console.log(els);
root.replaceWith(...els);
}
Remove(edit) {
let node = this.nodes[edit.element_id];
node.remove();
}
CreateTextNode(edit) {
const node = document.createTextNode(edit.text);
this.nodes[edit.id] = node;
this.stack.push(node);
}
CreateElement(edit) {
const tagName = edit.tag;
const el = document.createElement(tagName);
this.nodes[edit.id] = el;
this.stack.push(el);
}
CreateElementNs(edit) {
let el = document.createElementNS(edit.ns, edit.tag);
this.stack.push(el);
this.nodes[edit.id] = el;
}
CreatePlaceholder(edit) {
let el = document.createElement("pre");
// let el = document.createComment("vroot");
this.stack.push(el);
this.nodes[edit.id] = el;
}
RemoveEventListener(edit) { }
SetText(edit) {
this.top().textContent = edit.text;
}
SetAttribute(edit) {
const name = edit.field;
const value = edit.value;
const ns = edit.ns;
const node = this.top(this.stack);
if (ns == "style") {
node.style[name] = value;
} else if (ns !== undefined) {
node.setAttributeNS(ns, name, value);
} else {
node.setAttribute(name, value);
}
if (name === "value") {
node.value = value;
}
if (name === "checked") {
node.checked = true;
}
if (name === "selected") {
node.selected = true;
}
}
RemoveAttribute(edit) {
const name = edit.field;
const node = this.top(this.stack);
node.removeAttribute(name);
if (name === "value") {
node.value = null;
}
if (name === "checked") {
node.checked = false;
}
if (name === "selected") {
node.selected = false;
}
}
InsertAfter(edit) {
let old = this.nodes[edit.element_id];
let new_nodes = this.stack.splice(edit.many);
old.after(...new_nodes);
}
InsertBefore(edit) {
let old = this.nodes[edit.element_id];
let new_nodes = this.stack.splice(edit.many);
old.before(...new_nodes);
}
NewEventListener(edit) {
const event_name = edit.event_name;
const mounted_node_id = edit.mounted_node_id;
const scope = edit.scope;
const element = this.top();
element.setAttribute(`dioxus-event-${event_name}`, `${scope}.${mounted_node_id}`);
console.log("listener map is", this.listeners);
if (this.listeners[event_name] === undefined) {
console.log("adding listener!");
this.listeners[event_name] = "bla";
this.root.addEventListener(event_name, (event) => {
const target = event.target;
const val = target.getAttribute(`dioxus-event-${event_name}`);
const fields = val.split(".");
const scope_id = parseInt(fields[0]);
const real_id = parseInt(fields[1]);
console.log(`parsed event with scope_id ${scope_id} and real_id ${real_id}`);
rpc.call('user_event', {
event: event_name,
scope: scope_id,
mounted_dom_id: real_id,
}).then((reply) => {
console.log(reply);
this.stack.push(this.root);
let edits = reply.edits;
for (let x = 0; x < edits.length; x++) {
let edit = edits[x];
console.log(edit);
let f = this[edit.type];
f.call(this, edit);
}
console.log("initiated");
}).catch((err) => {
console.log("failed to initiate", err);
});
});
}
}
}
async function initialize() {
const reply = await rpc.call('initiate');
let root = window.document.getElementById("_dioxusroot");
const interpreter = new Interpreter(root);
console.log(reply);
let pre_rendered = reply.pre_rendered;
if (pre_rendered !== undefined) {
root.innerHTML = pre_rendered;
}
const edits = reply.edits;
apply_edits(edits, interpreter);
}
function apply_edits(edits, interpreter) {
for (let x = 0; x < edits.length; x++) {
let edit = edits[x];
console.log(edit);
let f = interpreter[edit.type];
f.call(interpreter, edit);
}
console.log("stack completed: ", interpreter.stack);
}
initialize();

View file

@ -4,20 +4,28 @@
//!
use std::borrow::BorrowMut;
use std::cell::RefCell;
use std::ops::{Deref, DerefMut};
use std::rc::Rc;
use std::sync::atomic::AtomicBool;
use std::sync::mpsc::channel;
use std::sync::{Arc, RwLock};
use cfg::DesktopConfig;
use dioxus_core::scheduler::SchedulerMsg;
use dioxus_core::*;
// use futures_channel::mpsc::UnboundedSender;
use serde::{Deserialize, Serialize};
mod logging;
pub use logging::set_up_logging;
pub use wry;
use wry::application::event::{Event, WindowEvent};
use wry::application::event_loop::{ControlFlow, EventLoop};
use wry::application::event_loop::{self, ControlFlow, EventLoop};
use wry::application::window::Fullscreen;
use wry::webview::WebViewBuilder;
use wry::webview::{WebView, WebViewBuilder};
use wry::{
application::window::{Window, WindowBuilder},
webview::{RpcRequest, RpcResponse},
@ -45,10 +53,16 @@ pub fn launch_with_props<P: Properties + 'static + Send + Sync>(
run(root, props, builder)
}
#[derive(Serialize)]
enum RpcEvent<'a> {
Initialize { edits: Vec<DomEdit<'a>> },
}
enum BridgeEvent {
Initialize(serde_json::Value),
Update(serde_json::Value),
}
#[derive(Serialize)]
struct Response<'a> {
pre_rendered: Option<String>,
@ -60,72 +74,175 @@ pub fn run<T: Properties + 'static + Send + Sync>(
props: T,
user_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
) -> anyhow::Result<()> {
run_with_edits(root, props, user_builder, None)
}
pub fn run_with_edits<
F: for<'a, 'b> FnOnce(&'a mut DesktopConfig<'b>) -> &'a mut DesktopConfig<'b>,
T: Properties + 'static + Send + Sync,
>(
root: FC<T>,
props: T,
user_builder: F,
redits: Option<Vec<DomEdit<'static>>>,
) -> anyhow::Result<()> {
/*
*/
let mut cfg = DesktopConfig::new();
user_builder(&mut cfg);
let DesktopConfig {
window,
manual_edits,
pre_rendered,
..
} = cfg;
let event_loop = EventLoop::new();
let window = window.build(&event_loop)?;
let (event_tx, mut event_rx) = tokio::sync::mpsc::unbounded_channel::<String>();
let (event_tx, mut event_rx) = tokio::sync::mpsc::unbounded_channel();
let sender = launch_vdom_with_tokio(root, props, event_tx.clone());
let locked_receiver = Rc::new(RefCell::new(event_rx));
let webview = WebViewBuilder::new(window)?
.with_url("wry://src/index.html")?
.with_rpc_handler(move |_window: &Window, mut req: RpcRequest| {
match req.method.as_str() {
"initiate" => {
//
let mut rx = (*locked_receiver).borrow_mut();
match rx.try_recv() {
Ok(BridgeEvent::Initialize(edits)) => {
Some(RpcResponse::new_result(req.id.take(), Some(edits)))
}
_ => None,
}
}
"user_event" => {
//
let data = req.params.unwrap();
log::debug!("Data: {:#?}", data);
let event = events::trigger_from_serialized(data);
sender.unbounded_send(SchedulerMsg::UiEvent(event)).unwrap();
let mut rx = (*locked_receiver).borrow_mut();
match rx.blocking_recv() {
Some(BridgeEvent::Update(edits)) => {
log::info!("Passing response back");
Some(RpcResponse::new_result(req.id.take(), Some(edits)))
}
None => {
log::error!("Sender half is gone");
None
}
_ => {
log::error!("No update event received");
None
}
}
}
_ => todo!("this message failed"),
}
})
// this isn't quite portable unfortunately :(
// todo: figure out a way to allow us to create the index.html with the index.js file separately
// it's a bit easier to hack with
.with_custom_protocol("wry".into(), move |request| {
use std::fs::{canonicalize, read};
use wry::http::ResponseBuilder;
// Remove url scheme
let path = request.uri().replace("wry://", "");
// Read the file content from file path
let content = read(canonicalize(&path)?)?;
// Return asset contents and mime types based on file extentions
// If you don't want to do this manually, there are some crates for you.
// Such as `infer` and `mime_guess`.
let (data, meta) = if path.ends_with(".html") {
(content, "text/html")
} else if path.ends_with(".js") {
(content, "text/javascript")
} else if path.ends_with(".png") {
(content, "image/png")
} else {
unimplemented!();
};
ResponseBuilder::new().mimetype(meta).body(data)
})
.build()?;
run_event_loop(event_loop, webview, event_tx);
Ok(())
}
pub fn start<P: 'static + Send>(
root: FC<P>,
config_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
) -> ((), ()) {
//
((), ())
}
// Create a new tokio runtime on a dedicated thread and then launch the apps VirtualDom.
fn launch_vdom_with_tokio<C: Send + 'static>(
root: FC<C>,
props: C,
event_tx: tokio::sync::mpsc::UnboundedSender<BridgeEvent>,
) -> futures_channel::mpsc::UnboundedSender<SchedulerMsg> {
// Spawn the virtualdom onto its own thread
// if it wants to spawn multithreaded tasks, it can use the executor directly
let (sender, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>();
let sender_2 = sender.clone();
std::thread::spawn(move || {
//
let runtime = tokio::runtime::Builder::new_current_thread()
// We create the runtim as multithreaded, so you can still "spawn" onto multiple threads
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
runtime.block_on(async move {
let mut vir = VirtualDom::new_with_props(root, props);
let channel = vir.get_event_sender();
let mut vir = VirtualDom::new_with_props_and_scheduler(root, props, sender, receiver);
let _ = vir.get_event_sender();
let edits = vir.rebuild();
#[derive(Serialize)]
struct Evt<'a> {
edits: Vec<DomEdit<'a>>,
}
// let msg = RpcEvent::Initialize { edits: edits.edits };
let edit_string = serde_json::to_value(Evt { edits: edits.edits }).unwrap();
match event_tx.send(BridgeEvent::Initialize(edit_string)) {
Ok(_) => {}
Err(_) => {}
}
loop {
vir.wait_for_work().await;
let edits = vir.run_with_deadline(|| false);
let edit_string = serde_json::to_string(&edits[0].edits).unwrap();
event_tx.send(edit_string).unwrap();
log::info!("{}", vir);
let mut muts = vir.run_with_deadline(|| false);
log::info!("muts {:#?}", muts);
while let Some(edit) = muts.pop() {
let edit_string = serde_json::to_value(Evt { edits: edit.edits }).unwrap();
match event_tx.send(BridgeEvent::Update(edit_string)) {
Ok(_) => {}
Err(er) => {
log::error!("Sending should not fail {}", er);
}
}
}
log::info!("mutations sent on channel");
}
})
});
let dioxus_requsted = Arc::new(AtomicBool::new(false));
sender_2
}
let webview = WebViewBuilder::new(window)?
.with_url(&format!("data:text/html,{}", HTML_CONTENT))?
.with_rpc_handler(move |_window: &Window, mut req: RpcRequest| {
match req.method.as_str() {
"initiate" => {}
"user_event" => {}
_ => todo!("this message failed"),
}
todo!()
})
.build()?;
event_loop.run(move |event, _, control_flow| {
fn run_event_loop(
event_loop: EventLoop<()>,
webview: WebView,
event_tx: tokio::sync::mpsc::UnboundedSender<BridgeEvent>,
) {
let _ = event_tx.clone();
event_loop.run(move |event, target, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
@ -146,74 +263,5 @@ pub fn run_with_edits<
_ => {}
}
});
Ok(())
})
}
// let edits = if let Some(edits) = &redits {
// serde_json::to_value(edits).unwrap()
// } else {
// let mut lock = vdom.write().unwrap();
// // let mut reg_lock = registry.write().unwrap();
// // Create the thin wrapper around the registry to collect the edits into
// let mut real = dom::WebviewDom::new();
// let pre = pre_rendered.clone();
// let response = match pre {
// Some(content) => {
// lock.rebuild_in_place().unwrap();
// Response {
// edits: Vec::new(),
// pre_rendered: Some(content),
// }
// }
// None => {
// //
// let edits = {
// // let mut edits = Vec::new();
// todo!()
// // lock.rebuild(&mut real, &mut edits).unwrap();
// // edits
// };
// Response {
// edits,
// pre_rendered: None,
// }
// }
// };
// serde_json::to_value(&response).unwrap()
// };
// // Return the edits into the webview runtime
// Some(RpcResponse::new_result(req.id.take(), Some(edits)))
// log::debug!("User event received");
// // let registry = registry.clone();
// let vdom = vdom.clone();
// let response = async_std::task::block_on(async move {
// let mut lock = vdom.write().unwrap();
// // let mut reg_lock = registry.write().unwrap();
// // a deserialized event
// let data = req.params.unwrap();
// log::debug!("Data: {:#?}", data);
// let event = trigger_from_serialized(data);
// // lock.queue_event(event);
// // Create the thin wrapper around the registry to collect the edits into
// let mut real = dom::WebviewDom::new();
// // Serialize the edit stream
// //
// let mut edits = Vec::new();
// // lock.run(&mut real, &mut edits)
// // .await
// // .expect("failed to progress");
// let response = Response {
// edits,
// pre_rendered: None,
// };
// let response = serde_json::to_value(&response).unwrap();
// // Give back the registry into its slot
// // *reg_lock = Some(real.consume());
// // Return the edits into the webview runtime
// Some(RpcResponse::new_result(req.id.take(), Some(response)))
// });
// response
// // spawn a task to clean up the garbage

View file

@ -0,0 +1,51 @@
pub fn set_up_logging(enabled: bool) {
use fern::colors::{Color, ColoredLevelConfig};
if !enabled {
return;
}
// configure colors for the whole line
let colors_line = ColoredLevelConfig::new()
.error(Color::Red)
.warn(Color::Yellow)
// we actually don't need to specify the color for debug and info, they are white by default
.info(Color::White)
.debug(Color::White)
// depending on the terminals color scheme, this is the same as the background color
.trace(Color::BrightBlack);
// configure colors for the name of the level.
// since almost all of them are the same as the color for the whole line, we
// just clone `colors_line` and overwrite our changes
let colors_level = colors_line.clone().info(Color::Green);
// here we set up our fern Dispatch
// when running tests in batch, the logger is re-used, so ignore the logger error
let _ = fern::Dispatch::new()
.format(move |out, message, record| {
out.finish(format_args!(
"{color_line}[{level}{color_line}] {message}\x1B[0m",
color_line = format_args!(
"\x1B[{}m",
colors_line.get_color(&record.level()).to_fg_str()
),
level = colors_level.color(record.level()),
message = message,
));
})
// set the default log level. to filter out verbose log messages from dependencies, set
// this to Warn and overwrite the log level for your crate.
.level(log::LevelFilter::Debug)
// .level(log::LevelFilter::Warn)
// change log levels for individual modules. Note: This looks for the record's target
// field which defaults to the module path but can be overwritten with the `target`
// parameter:
// `info!(target="special_target", "This log message is about special_target");`
// .level_for("dioxus", log::LevelFilter::Debug)
// .level_for("dioxus", log::LevelFilter::Info)
// .level_for("pretty_colored", log::LevelFilter::Trace)
// output to stdout
.chain(std::io::stdout())
.apply();
}

View file

@ -13,7 +13,7 @@ dioxus-core = { path = "../core", version = "0.1.2" }
log = "0.4.14"
serde = "1.0.126"
serde_json = "1.0.64"
wry = "0.11.0"
wry = "0.12.2"
[target.'cfg(target_os = "android")'.dependencies]

View file

@ -72,9 +72,9 @@ crate-type = ["cdylib", "rlib"]
im-rc = "15.0.0"
separator = "0.4.1"
uuid = { version = "0.8.2", features = ["v4", "wasm-bindgen"] }
dioxus-hooks = { path = "../hooks" }
serde = { version = "1.0.126", features = ["derive"] }
reqwest = { version = "0.11", features = ["json"] }
dioxus-hooks = { path = "../hooks" }
dioxus-core-macro = { path = "../core-macro" }
# rand = { version="0.8.4", features=["small_rng"] }
# surf = { version = "2.3.1", default-features = false, features = [

View file

@ -44,9 +44,9 @@ static App: FC<()> = |cx, props| {
"dynamic subtree {state}"
}
div {
button { onclick: move |_| state+=1, "incr" }
button { onclick: move |e| state+=1, "incr" }
br {}
button { onclick: move |_| state-=1, "decr" }
button { onclick: move |e| state-=1, "decr" }
}
}
}

View file

@ -8,7 +8,7 @@
//! - Partial delegation?>
use dioxus_core::{
events::{SyntheticEvent, UserEvent},
events::{DioxusEvent, KeyCode, SyntheticEvent, UserEvent},
mutations::NodeRefMutation,
scheduler::SchedulerMsg,
DomEdit, ElementId, ScopeId,
@ -110,7 +110,6 @@ impl WebsysDom {
DomEdit::AppendChildren { many } => self.append_children(many),
DomEdit::ReplaceWith { m, root } => self.replace_with(m, root),
DomEdit::Remove { root } => self.remove(root),
DomEdit::RemoveAllChildren => self.remove_all_children(),
DomEdit::CreateTextNode { text, id } => self.create_text_node(text, id),
DomEdit::CreateElement { tag, id } => self.create_element(tag, None, id),
DomEdit::CreateElementNs { tag, id, ns } => self.create_element(tag, Some(ns), id),
@ -208,10 +207,6 @@ impl WebsysDom {
}
}
fn remove_all_children(&mut self) {
todo!()
}
fn create_placeholder(&mut self, id: u64) {
self.create_element("pre", None, id);
self.set_attribute("hidden", "", None);
@ -464,87 +459,213 @@ impl Stack {
}
}
pub struct DioxusWebsysEvent(web_sys::Event);
unsafe impl Send for DioxusWebsysEvent {}
unsafe impl Sync for DioxusWebsysEvent {}
// trait MyTrait {}
// impl MyTrait for web_sys::Event {}
// todo: some of these events are being casted to the wrong event type.
// We need tests that simulate clicks/etc and make sure every event type works.
fn virtual_event_from_websys_event(event: web_sys::Event) -> SyntheticEvent {
use crate::events::*;
use dioxus_core::events::on::*;
match event.type_().as_str() {
"copy" | "cut" | "paste" => {
SyntheticEvent::ClipboardEvent(ClipboardEvent(Arc::new(WebsysClipboardEvent(event))))
}
"copy" | "cut" | "paste" => SyntheticEvent::ClipboardEvent(ClipboardEvent(
DioxusEvent::new(ClipboardEventInner(), DioxusWebsysEvent(event)),
)),
"compositionend" | "compositionstart" | "compositionupdate" => {
let evt: web_sys::CompositionEvent = event.dyn_into().unwrap();
SyntheticEvent::CompositionEvent(CompositionEvent(Arc::new(WebsysCompositionEvent(
evt,
))))
let evt: &web_sys::CompositionEvent = event.dyn_ref().unwrap();
SyntheticEvent::CompositionEvent(CompositionEvent(DioxusEvent::new(
CompositionEventInner {
data: evt.data().unwrap_or_default(),
},
DioxusWebsysEvent(event),
)))
}
"keydown" | "keypress" | "keyup" => {
let evt: web_sys::KeyboardEvent = event.dyn_into().unwrap();
SyntheticEvent::KeyboardEvent(KeyboardEvent(Arc::new(WebsysKeyboardEvent(evt))))
}
"focus" | "blur" => {
let evt: web_sys::FocusEvent = event.dyn_into().unwrap();
SyntheticEvent::FocusEvent(FocusEvent(Arc::new(WebsysFocusEvent(evt))))
}
"change" => {
let evt = event.dyn_into().unwrap();
SyntheticEvent::GenericEvent(GenericEvent(Arc::new(WebsysGenericUiEvent(evt))))
let evt: &web_sys::KeyboardEvent = event.dyn_ref().unwrap();
SyntheticEvent::KeyboardEvent(KeyboardEvent(DioxusEvent::new(
KeyboardEventInner {
alt_key: evt.alt_key(),
char_code: evt.char_code(),
key: evt.key(),
key_code: KeyCode::from_raw_code(evt.key_code() as u8),
ctrl_key: evt.ctrl_key(),
locale: "not implemented".to_string(),
location: evt.location() as usize,
meta_key: evt.meta_key(),
repeat: evt.repeat(),
shift_key: evt.shift_key(),
which: evt.which() as usize,
},
DioxusWebsysEvent(event),
)))
}
"focus" | "blur" => SyntheticEvent::FocusEvent(FocusEvent(DioxusEvent::new(
FocusEventInner {},
DioxusWebsysEvent(event),
))),
"change" => SyntheticEvent::GenericEvent(DioxusEvent::new((), DioxusWebsysEvent(event))),
// todo: these handlers might get really slow if the input box gets large and allocation pressure is heavy
// don't have a good solution with the serialized event problem
"input" | "invalid" | "reset" | "submit" => {
let evt: web_sys::Event = event.dyn_into().unwrap();
SyntheticEvent::FormEvent(FormEvent(Arc::new(WebsysFormEvent(evt))))
let evt: &web_sys::Event = event.dyn_ref().unwrap();
let target: web_sys::EventTarget = evt.target().unwrap();
let value: String = (&target)
.dyn_ref()
.map(|input: &web_sys::HtmlInputElement| input.value())
.or_else(|| {
target
.dyn_ref()
.map(|input: &web_sys::HtmlTextAreaElement| input.value())
})
// select elements are NOT input events - because - why woudn't they be??
.or_else(|| {
target
.dyn_ref()
.map(|input: &web_sys::HtmlSelectElement| input.value())
})
.or_else(|| {
target
.dyn_ref::<web_sys::HtmlElement>()
.unwrap()
.text_content()
})
.expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener");
SyntheticEvent::FormEvent(FormEvent(DioxusEvent::new(
FormEventInner { value },
DioxusWebsysEvent(event),
)))
}
"click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit"
| "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter"
| "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => {
let evt: web_sys::MouseEvent = event.dyn_into().unwrap();
SyntheticEvent::MouseEvent(MouseEvent(Arc::new(WebsysMouseEvent(evt))))
let evt: &web_sys::MouseEvent = event.dyn_ref().unwrap();
SyntheticEvent::MouseEvent(MouseEvent(DioxusEvent::new(
MouseEventInner {
alt_key: evt.alt_key(),
button: evt.button(),
buttons: evt.buttons(),
client_x: evt.client_x(),
client_y: evt.client_y(),
ctrl_key: evt.ctrl_key(),
meta_key: evt.meta_key(),
screen_x: evt.screen_x(),
screen_y: evt.screen_y(),
shift_key: evt.shift_key(),
page_x: evt.page_x(),
page_y: evt.page_y(),
},
DioxusWebsysEvent(event),
)))
}
"pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
| "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => {
let evt: web_sys::PointerEvent = event.dyn_into().unwrap();
SyntheticEvent::PointerEvent(PointerEvent(Arc::new(WebsysPointerEvent(evt))))
}
"select" => {
let evt: web_sys::UiEvent = event.dyn_into().unwrap();
SyntheticEvent::SelectionEvent(SelectionEvent(Arc::new(WebsysGenericUiEvent(evt))))
let evt: &web_sys::PointerEvent = event.dyn_ref().unwrap();
SyntheticEvent::PointerEvent(PointerEvent(DioxusEvent::new(
PointerEventInner {
alt_key: evt.alt_key(),
button: evt.button(),
buttons: evt.buttons(),
client_x: evt.client_x(),
client_y: evt.client_y(),
ctrl_key: evt.ctrl_key(),
meta_key: evt.meta_key(),
page_x: evt.page_x(),
page_y: evt.page_y(),
screen_x: evt.screen_x(),
screen_y: evt.screen_y(),
shift_key: evt.shift_key(),
pointer_id: evt.pointer_id(),
width: evt.width(),
height: evt.height(),
pressure: evt.pressure(),
tangential_pressure: evt.tangential_pressure(),
tilt_x: evt.tilt_x(),
tilt_y: evt.tilt_y(),
twist: evt.twist(),
pointer_type: evt.pointer_type(),
is_primary: evt.is_primary(),
// get_modifier_state: evt.get_modifier_state(),
},
DioxusWebsysEvent(event),
)))
}
"select" => SyntheticEvent::SelectionEvent(SelectionEvent(DioxusEvent::new(
SelectionEventInner {},
DioxusWebsysEvent(event),
))),
"touchcancel" | "touchend" | "touchmove" | "touchstart" => {
let evt: web_sys::TouchEvent = event.dyn_into().unwrap();
SyntheticEvent::TouchEvent(TouchEvent(Arc::new(WebsysTouchEvent(evt))))
}
"scroll" => {
let evt: web_sys::UiEvent = event.dyn_into().unwrap();
SyntheticEvent::GenericEvent(GenericEvent(Arc::new(WebsysGenericUiEvent(evt))))
let evt: &web_sys::TouchEvent = event.dyn_ref().unwrap();
SyntheticEvent::TouchEvent(TouchEvent(DioxusEvent::new(
TouchEventInner {
alt_key: evt.alt_key(),
ctrl_key: evt.ctrl_key(),
meta_key: evt.meta_key(),
shift_key: evt.shift_key(),
},
DioxusWebsysEvent(event),
)))
}
"scroll" => SyntheticEvent::GenericEvent(DioxusEvent::new((), DioxusWebsysEvent(event))),
"wheel" => {
let evt: web_sys::WheelEvent = event.dyn_into().unwrap();
SyntheticEvent::WheelEvent(WheelEvent(Arc::new(WebsysWheelEvent(evt))))
let evt: &web_sys::WheelEvent = event.dyn_ref().unwrap();
SyntheticEvent::WheelEvent(WheelEvent(DioxusEvent::new(
WheelEventInner {
delta_x: evt.delta_x(),
delta_y: evt.delta_y(),
delta_z: evt.delta_z(),
delta_mode: evt.delta_mode(),
},
DioxusWebsysEvent(event),
)))
}
"animationstart" | "animationend" | "animationiteration" => {
let evt: web_sys::AnimationEvent = event.dyn_into().unwrap();
SyntheticEvent::AnimationEvent(AnimationEvent(Arc::new(WebsysAnimationEvent(evt))))
let evt: &web_sys::AnimationEvent = event.dyn_ref().unwrap();
SyntheticEvent::AnimationEvent(AnimationEvent(DioxusEvent::new(
AnimationEventInner {
elapsed_time: evt.elapsed_time(),
animation_name: evt.animation_name(),
pseudo_element: evt.pseudo_element(),
},
DioxusWebsysEvent(event),
)))
}
"transitionend" => {
let evt: web_sys::TransitionEvent = event.dyn_into().unwrap();
SyntheticEvent::TransitionEvent(TransitionEvent(Arc::new(WebsysTransitionEvent(evt))))
let evt: &web_sys::TransitionEvent = event.dyn_ref().unwrap();
SyntheticEvent::TransitionEvent(TransitionEvent(DioxusEvent::new(
TransitionEventInner {
elapsed_time: evt.elapsed_time(),
property_name: evt.property_name(),
pseudo_element: evt.pseudo_element(),
},
DioxusWebsysEvent(event),
)))
}
"abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted"
| "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play"
| "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
| "timeupdate" | "volumechange" | "waiting" => {
let evt: web_sys::UiEvent = event.dyn_into().unwrap();
SyntheticEvent::MediaEvent(MediaEvent(Arc::new(WebsysMediaEvent(evt))))
}
"toggle" => {
let evt: web_sys::UiEvent = event.dyn_into().unwrap();
SyntheticEvent::ToggleEvent(ToggleEvent(Arc::new(WebsysToggleEvent(evt))))
}
_ => {
let evt: web_sys::UiEvent = event.dyn_into().unwrap();
SyntheticEvent::GenericEvent(GenericEvent(Arc::new(WebsysGenericUiEvent(evt))))
}
| "timeupdate" | "volumechange" | "waiting" => SyntheticEvent::MediaEvent(MediaEvent(
DioxusEvent::new(MediaEventInner {}, DioxusWebsysEvent(event)),
)),
"toggle" => SyntheticEvent::ToggleEvent(ToggleEvent(DioxusEvent::new(
ToggleEventInner {},
DioxusWebsysEvent(event),
))),
_ => SyntheticEvent::GenericEvent(DioxusEvent::new((), DioxusWebsysEvent(event))),
}
}

View file

@ -3,480 +3,3 @@
use dioxus_core::events::on::*;
use wasm_bindgen::JsCast;
use web_sys::{Event, UiEvent};
/// All events implement the generic event type - they're all `UI events`
trait WebsysGenericEvent {
fn as_ui_event(&self) -> &UiEvent;
}
impl GenericEventInner for &dyn WebsysGenericEvent {
/// On WebSys, this returns an &UiEvent which can be casted via dyn_ref into the correct sub type.
fn raw_event(&self) -> &dyn std::any::Any {
self.as_ui_event()
}
fn bubbles(&self) -> bool {
self.as_ui_event().bubbles()
}
fn cancel_bubble(&self) {
self.as_ui_event().cancel_bubble();
}
fn cancelable(&self) -> bool {
self.as_ui_event().cancelable()
}
fn composed(&self) -> bool {
self.as_ui_event().composed()
}
fn current_target(&self) {
if cfg!(debug_assertions) {
todo!("Current target does not return anything useful.\nPlease try casting the event directly.");
}
// self.as_ui_event().current_target();
}
fn default_prevented(&self) -> bool {
self.as_ui_event().default_prevented()
}
fn event_phase(&self) -> u16 {
self.as_ui_event().event_phase()
}
fn is_trusted(&self) -> bool {
self.as_ui_event().is_trusted()
}
fn prevent_default(&self) {
self.as_ui_event().prevent_default()
}
fn stop_immediate_propagation(&self) {
self.as_ui_event().stop_immediate_propagation()
}
fn stop_propagation(&self) {
self.as_ui_event().stop_propagation()
}
fn target(&self) {
todo!()
}
fn time_stamp(&self) -> f64 {
self.as_ui_event().time_stamp()
}
}
macro_rules! implement_generic_event {
(
$($event:ident),*
) => {
$(
impl WebsysGenericEvent for $event {
fn as_ui_event(&self) -> &UiEvent {
self.0.dyn_ref().unwrap()
}
}
)*
};
}
implement_generic_event! {
WebsysClipboardEvent,
WebsysCompositionEvent,
WebsysKeyboardEvent,
WebsysGenericUiEvent,
WebsysFocusEvent,
WebsysFormEvent,
WebsysMouseEvent,
WebsysPointerEvent,
WebsysWheelEvent,
WebsysAnimationEvent,
WebsysTransitionEvent,
WebsysTouchEvent,
WebsysMediaEvent,
WebsysToggleEvent
}
// unfortunately, currently experimental, and web_sys needs to be configured to use it :>(
pub struct WebsysClipboardEvent(pub Event);
impl ClipboardEventInner for WebsysClipboardEvent {}
pub struct WebsysCompositionEvent(pub web_sys::CompositionEvent);
impl CompositionEventInner for WebsysCompositionEvent {
fn data(&self) -> String {
self.0.data().unwrap_or_else(|| String::new())
}
}
pub struct WebsysKeyboardEvent(pub web_sys::KeyboardEvent);
impl KeyboardEventInner for WebsysKeyboardEvent {
fn alt_key(&self) -> bool {
self.0.alt_key()
}
fn char_code(&self) -> u32 {
self.0.char_code()
}
fn key(&self) -> String {
self.0.key()
}
fn key_code(&self) -> KeyCode {
KeyCode::from_raw_code(self.0.key_code() as u8)
}
fn ctrl_key(&self) -> bool {
self.0.ctrl_key()
}
fn get_modifier_state(&self, key_code: &str) -> bool {
self.0.get_modifier_state(key_code)
}
fn locale(&self) -> String {
if cfg!(debug_assertions) {
todo!("Locale is currently not supported. :(")
} else {
String::from("en-US")
}
}
fn location(&self) -> usize {
self.0.location() as usize
}
fn meta_key(&self) -> bool {
self.0.meta_key()
}
fn repeat(&self) -> bool {
self.0.repeat()
}
fn shift_key(&self) -> bool {
self.0.shift_key()
}
fn which(&self) -> usize {
self.0.which() as usize
}
}
pub struct WebsysGenericUiEvent(pub UiEvent);
impl GenericEventInner for WebsysGenericUiEvent {
fn raw_event(&self) -> &dyn std::any::Any {
// self.0.raw_event()
todo!()
}
fn bubbles(&self) -> bool {
self.0.bubbles()
}
fn cancel_bubble(&self) {
self.0.cancel_bubble();
}
fn cancelable(&self) -> bool {
self.0.cancelable()
}
fn composed(&self) -> bool {
self.0.composed()
}
fn current_target(&self) {
// self.0.current_target()
}
fn default_prevented(&self) -> bool {
self.0.default_prevented()
}
fn event_phase(&self) -> u16 {
self.0.event_phase()
}
fn is_trusted(&self) -> bool {
self.0.is_trusted()
}
fn prevent_default(&self) {
self.0.prevent_default()
}
fn stop_immediate_propagation(&self) {
self.0.stop_immediate_propagation()
}
fn stop_propagation(&self) {
self.0.stop_propagation()
}
fn target(&self) {
// self.0.target()
}
fn time_stamp(&self) -> f64 {
self.0.time_stamp()
}
}
impl UIEventInner for WebsysGenericUiEvent {
fn detail(&self) -> i32 {
todo!()
}
}
impl SelectionEventInner for WebsysGenericUiEvent {}
pub struct WebsysFocusEvent(pub web_sys::FocusEvent);
impl FocusEventInner for WebsysFocusEvent {}
pub struct WebsysFormEvent(pub web_sys::Event);
impl FormEventInner for WebsysFormEvent {
// technically a controlled component, so we need to manually grab out the target data
fn value(&self) -> String {
let this: web_sys::EventTarget = self.0.target().unwrap();
(&this)
.dyn_ref()
.map(|input: &web_sys::HtmlInputElement| input.value())
.or_else(|| {
this
.dyn_ref()
.map(|input: &web_sys::HtmlTextAreaElement| input.value())
})
// select elements are NOT input events - because - why woudn't they be??
.or_else(|| {
this
.dyn_ref()
.map(|input: &web_sys::HtmlSelectElement| input.value())
})
.or_else(|| {
this
.dyn_ref::<web_sys::HtmlElement>()
.unwrap()
.text_content()
})
.expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener")
}
}
pub struct WebsysMouseEvent(pub web_sys::MouseEvent);
impl MouseEventInner for WebsysMouseEvent {
fn alt_key(&self) -> bool {
self.0.alt_key()
}
fn button(&self) -> i16 {
self.0.button()
}
fn buttons(&self) -> u16 {
self.0.buttons()
}
fn client_x(&self) -> i32 {
self.0.client_x()
}
fn client_y(&self) -> i32 {
self.0.client_y()
}
fn ctrl_key(&self) -> bool {
self.0.ctrl_key()
}
fn meta_key(&self) -> bool {
self.0.meta_key()
}
fn page_x(&self) -> i32 {
self.0.page_x()
}
fn page_y(&self) -> i32 {
self.0.page_y()
}
fn screen_x(&self) -> i32 {
self.0.screen_x()
}
fn screen_y(&self) -> i32 {
self.0.screen_y()
}
fn shift_key(&self) -> bool {
self.0.shift_key()
}
// yikes
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
fn get_modifier_state(&self, key_code: &str) -> bool {
self.0.get_modifier_state(key_code)
}
}
pub struct WebsysPointerEvent(pub web_sys::PointerEvent);
impl PointerEventInner for WebsysPointerEvent {
fn alt_key(&self) -> bool {
self.0.alt_key()
}
fn button(&self) -> i16 {
self.0.button()
}
fn buttons(&self) -> u16 {
self.0.buttons()
}
fn client_x(&self) -> i32 {
self.0.client_x()
}
fn client_y(&self) -> i32 {
self.0.client_y()
}
fn ctrl_key(&self) -> bool {
self.0.ctrl_key()
}
fn meta_key(&self) -> bool {
self.0.meta_key()
}
fn page_x(&self) -> i32 {
self.0.page_x()
}
fn page_y(&self) -> i32 {
self.0.page_y()
}
fn screen_x(&self) -> i32 {
self.0.screen_x()
}
fn screen_y(&self) -> i32 {
self.0.screen_y()
}
fn shift_key(&self) -> bool {
self.0.shift_key()
}
// yikes
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
fn get_modifier_state(&self, key_code: &str) -> bool {
self.0.get_modifier_state(key_code)
}
fn pointer_id(&self) -> i32 {
self.0.pointer_id()
}
fn width(&self) -> i32 {
self.0.width()
}
fn height(&self) -> i32 {
self.0.height()
}
fn pressure(&self) -> f32 {
self.0.pressure()
}
fn tangential_pressure(&self) -> f32 {
self.0.tangential_pressure()
}
fn tilt_x(&self) -> i32 {
self.0.tilt_x()
}
fn tilt_y(&self) -> i32 {
self.0.tilt_y()
}
fn twist(&self) -> i32 {
self.0.twist()
}
fn pointer_type(&self) -> String {
self.0.pointer_type()
}
fn is_primary(&self) -> bool {
self.0.is_primary()
}
}
pub struct WebsysWheelEvent(pub web_sys::WheelEvent);
impl WheelEventInner for WebsysWheelEvent {
fn delta_mode(&self) -> u32 {
self.0.delta_mode()
}
fn delta_x(&self) -> f64 {
self.0.delta_x()
}
fn delta_y(&self) -> f64 {
self.0.delta_y()
}
fn delta_z(&self) -> f64 {
self.0.delta_z()
}
}
pub struct WebsysAnimationEvent(pub web_sys::AnimationEvent);
impl AnimationEventInner for WebsysAnimationEvent {
fn animation_name(&self) -> String {
self.0.animation_name()
}
fn pseudo_element(&self) -> String {
self.0.pseudo_element()
}
fn elapsed_time(&self) -> f32 {
self.0.elapsed_time()
}
}
pub struct WebsysTransitionEvent(pub web_sys::TransitionEvent);
impl TransitionEventInner for WebsysTransitionEvent {
fn property_name(&self) -> String {
self.0.property_name()
}
fn pseudo_element(&self) -> String {
self.0.pseudo_element()
}
fn elapsed_time(&self) -> f32 {
self.0.elapsed_time()
}
}
pub struct WebsysTouchEvent(pub web_sys::TouchEvent);
impl TouchEventInner for WebsysTouchEvent {
fn alt_key(&self) -> bool {
self.0.alt_key()
}
fn ctrl_key(&self) -> bool {
self.0.ctrl_key()
}
fn meta_key(&self) -> bool {
self.0.meta_key()
}
fn shift_key(&self) -> bool {
self.0.shift_key()
}
fn get_modifier_state(&self, key_code: &str) -> bool {
if cfg!(debug_assertions) {
todo!("get_modifier_state is not currently supported for touch events");
} else {
false
}
}
}
pub struct WebsysMediaEvent(pub web_sys::UiEvent);
impl MediaEventInner for WebsysMediaEvent {}
pub struct WebsysToggleEvent(pub web_sys::UiEvent);
impl ToggleEventInner for WebsysToggleEvent {}

View file

@ -116,7 +116,7 @@ pub fn launch(root_component: FC<()>, configuration: impl FnOnce(WebConfig) -> W
/// ```
pub fn launch_with_props<T, F>(root_component: FC<T>, root_properties: T, configuration_builder: F)
where
T: Properties + 'static,
T: Send + 'static,
F: FnOnce(WebConfig) -> WebConfig,
{
let config = configuration_builder(WebConfig::default());
@ -135,7 +135,7 @@ where
/// wasm_bindgen_futures::spawn_local(app_fut);
/// }
/// ```
pub async fn run_with_props<T: Properties + 'static>(root: FC<T>, root_props: T, cfg: WebConfig) {
pub async fn run_with_props<T: 'static + Send>(root: FC<T>, root_props: T, cfg: WebConfig) {
let mut dom = VirtualDom::new_with_props(root, root_props);
intern_cached_strings();

View file

@ -0,0 +1,8 @@
[package]
name = "dioxus-webview-client"
version = "0.0.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View file

@ -0,0 +1,8 @@
# the client part of the vdom
this crate is designed to be the "receiving end" of the dioxus virtualdom, using wasm_bindgen to expose an API for the receiving end.

View file

@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}