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

View file

@ -1,8 +1,8 @@
//! This module provides a set of common events for all Dioxus apps to target, regardless of host platform.
//! -------------------------------------------------------------------------------------------------------
//! An event system that's less confusing than Traits + RC;
//! This should hopefully make it easier to port to other platforms.
//!
//! 3rd party renderers are responsible for converting their native events into these virtual event types. Events might
//! be heavy or need to interact through FFI, so the events themselves are designed to be lazy.
//! Unfortunately, it is less efficient than the original, but hopefully it's negligible.
use crate::{
innerlude::Listener,
innerlude::{ElementId, NodeFactory, ScopeId},
@ -34,6 +34,7 @@ pub struct UserEvent {
pub event: SyntheticEvent,
}
#[derive(Debug)]
pub enum SyntheticEvent {
AnimationEvent(on::AnimationEvent),
ClipboardEvent(on::ClipboardEvent),
@ -41,7 +42,7 @@ pub enum SyntheticEvent {
FocusEvent(on::FocusEvent),
FormEvent(on::FormEvent),
KeyboardEvent(on::KeyboardEvent),
GenericEvent(on::GenericEvent),
GenericEvent(DioxusEvent<()>),
TouchEvent(on::TouchEvent),
ToggleEvent(on::ToggleEvent),
MediaEvent(on::MediaEvent),
@ -51,31 +52,6 @@ pub enum SyntheticEvent {
TransitionEvent(on::TransitionEvent),
PointerEvent(on::PointerEvent),
}
// ImageEvent(event_data::ImageEvent),
impl std::fmt::Debug for SyntheticEvent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let name = match self {
SyntheticEvent::ClipboardEvent(_) => "ClipboardEvent",
SyntheticEvent::CompositionEvent(_) => "CompositionEvent",
SyntheticEvent::KeyboardEvent(_) => "KeyboardEvent",
SyntheticEvent::FocusEvent(_) => "FocusEvent",
SyntheticEvent::FormEvent(_) => "FormEvent",
SyntheticEvent::SelectionEvent(_) => "SelectionEvent",
SyntheticEvent::TouchEvent(_) => "TouchEvent",
SyntheticEvent::WheelEvent(_) => "WheelEvent",
SyntheticEvent::MediaEvent(_) => "MediaEvent",
SyntheticEvent::AnimationEvent(_) => "AnimationEvent",
SyntheticEvent::TransitionEvent(_) => "TransitionEvent",
SyntheticEvent::ToggleEvent(_) => "ToggleEvent",
SyntheticEvent::MouseEvent(_) => "MouseEvent",
SyntheticEvent::PointerEvent(_) => "PointerEvent",
SyntheticEvent::GenericEvent(_) => "GenericEvent",
};
f.debug_struct("VirtualEvent").field("type", &name).finish()
}
}
/// Priority of Event Triggers.
///
@ -129,82 +105,101 @@ pub enum EventPriority {
Low = 0,
}
pub(crate) fn event_meta(event: &UserEvent) -> (bool, EventPriority) {
use EventPriority::*;
match event.name {
// clipboard
"copy" | "cut" | "paste" => (true, Medium),
// Composition
"compositionend" | "compositionstart" | "compositionupdate" => (true, Low),
// Keyboard
"keydown" | "keypress" | "keyup" => (true, High),
// Focus
"focus" | "blur" => (true, Low),
// Form
"change" | "input" | "invalid" | "reset" | "submit" => (true, Medium),
// Mouse
"click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit"
| "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter"
| "mouseleave" | "mouseout" | "mouseover" | "mouseup" => (true, High),
"mousemove" => (false, Medium),
// Pointer
"pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
| "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => {
(true, Medium)
#[derive(Debug)]
pub struct DioxusEvent<T: Send> {
inner: T,
raw: Box<dyn Any + Send>,
}
// Selection
"select" | "touchcancel" | "touchend" => (true, Medium),
impl<T: Send + Sync> DioxusEvent<T> {
pub fn new<F: Send + 'static>(inner: T, raw: F) -> Self {
let raw = Box::new(raw);
Self { inner, raw }
}
// Touch
"touchmove" | "touchstart" => (true, Medium),
/// Return a reference to the raw event. User will need to downcast the event to the right platform-specific type.
pub fn native<E: 'static>(&self) -> Option<&E> {
self.raw.downcast_ref()
}
// Wheel
"scroll" | "wheel" => (false, Medium),
/// Returns whether or not a specific event is a bubbling event
pub fn bubbles(&self) -> bool {
todo!()
}
/// Sets or returns whether the event should propagate up the hierarchy or not
pub fn cancel_bubble(&self) {
todo!()
}
/// Returns whether or not an event can have its default action prevented
pub fn cancelable(&self) -> bool {
todo!()
}
/// Returns whether the event is composed or not
pub fn composed(&self) -> bool {
todo!()
}
// Media
"abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted"
| "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play"
| "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
| "timeupdate" | "volumechange" | "waiting" => (true, Medium),
// Currently not supported because those no way we could possibly support it
// just cast the event to the right platform-specific type and return it
// /// Returns the event's path
// pub fn composed_path(&self) -> String {
// todo!()
// }
// Animation
"animationstart" | "animationend" | "animationiteration" => (true, Medium),
/// Returns the element whose event listeners triggered the event
pub fn current_target(&self) {
todo!()
}
/// Returns whether or not the preventDefault method was called for the event
pub fn default_prevented(&self) -> bool {
todo!()
}
/// Returns which phase of the event flow is currently being evaluated
pub fn event_phase(&self) -> u16 {
todo!()
}
// Transition
"transitionend" => (true, Medium),
/// Returns whether or not an event is trusted
pub fn is_trusted(&self) -> bool {
todo!()
}
// Toggle
"toggle" => (true, Medium),
/// Cancels the event if it is cancelable, meaning that the default action that belongs to the event will
pub fn prevent_default(&self) {
todo!()
}
_ => (true, Low),
/// Prevents other listeners of the same event from being called
pub fn stop_immediate_propagation(&self) {
todo!()
}
/// Prevents further propagation of an event during event flow
pub fn stop_propagation(&self) {
todo!()
}
/// Returns the element that triggered the event
pub fn target(&self) -> Option<Box<dyn Any>> {
todo!()
}
/// Returns the time (in milliseconds relative to the epoch) at which the event was created
pub fn time_stamp(&self) -> f64 {
todo!()
}
}
pub use on::{
AnimationEvent, ClipboardEvent, CompositionEvent, FocusEvent, FormEvent, GenericEvent, KeyCode,
KeyboardEvent, MediaEvent, MouseEvent, PointerEvent, SelectionEvent, ToggleEvent, TouchEvent,
TransitionEvent, WheelEvent,
};
impl<T: Send + Sync> std::ops::Deref for DioxusEvent<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
pub mod on {
//! This module defines the synthetic events that all Dioxus apps enable. No matter the platform, every dioxus renderer
//! will implement the same events and same behavior (bubbling, cancelation, etc).
//!
//! Synthetic events are immutable and wrapped in Arc. It is the intention for Dioxus renderers to re-use the underyling
//! Arc allocation through "get_mut"
//!
//! React recently dropped support for re-using event allocation and just passes the real event along.
use super::*;
macro_rules! event_directory {
( $(
$( #[$attr:meta] )*
@ -217,13 +212,14 @@ pub mod on {
)* ) => {
$(
$(#[$attr])*
pub struct $wrapper(pub Arc<dyn $eventdata>);
#[derive(Debug)]
pub struct $wrapper(pub DioxusEvent<$eventdata>);
// todo: derefing to the event is fine (and easy) but breaks some IDE stuff like (go to source)
// going to source in fact takes you to the source of Rc which is... less than useful
// Either we ask for this to be changed in Rust-analyzer or manually impkement the trait
impl Deref for $wrapper {
type Target = Arc<dyn $eventdata>;
type Target = DioxusEvent<$eventdata>;
fn deref(&self) -> &Self::Target {
&self.0
}
@ -599,57 +595,20 @@ pub mod on {
];
}
pub struct GenericEvent(pub Arc<dyn GenericEventInner>);
pub trait GenericEventInner {
/// Return a reference to the raw event. User will need to downcast the event to the right platform-specific type.
fn raw_event(&self) -> &dyn Any;
/// Returns whether or not a specific event is a bubbling event
fn bubbles(&self) -> bool;
/// Sets or returns whether the event should propagate up the hierarchy or not
fn cancel_bubble(&self);
/// Returns whether or not an event can have its default action prevented
fn cancelable(&self) -> bool;
/// Returns whether the event is composed or not
fn composed(&self) -> bool;
// Currently not supported because those no way we could possibly support it
// just cast the event to the right platform-specific type and return it
// /// Returns the event's path
// fn composed_path(&self) -> String;
/// Returns the element whose event listeners triggered the event
fn current_target(&self);
/// Returns whether or not the preventDefault method was called for the event
fn default_prevented(&self) -> bool;
/// Returns which phase of the event flow is currently being evaluated
fn event_phase(&self) -> u16;
/// Returns whether or not an event is trusted
fn is_trusted(&self) -> bool;
/// Cancels the event if it is cancelable, meaning that the default action that belongs to the event will
fn prevent_default(&self);
/// Prevents other listeners of the same event from being called
fn stop_immediate_propagation(&self);
/// Prevents further propagation of an event during event flow
fn stop_propagation(&self);
/// Returns the element that triggered the event
fn target(&self);
/// Returns the time (in milliseconds relative to the epoch) at which the event was created
fn time_stamp(&self) -> f64;
}
pub trait ClipboardEventInner {
#[derive(Debug)]
pub struct ClipboardEventInner(
// DOMDataTransfer clipboardData
);
#[derive(Debug)]
pub struct CompositionEventInner {
pub data: String,
}
pub trait CompositionEventInner {
fn data(&self) -> String;
}
pub trait KeyboardEventInner {
fn alt_key(&self) -> bool;
fn char_code(&self) -> u32;
#[derive(Debug)]
pub struct KeyboardEventInner {
pub alt_key: bool,
pub char_code: u32,
/// Identify which "key" was entered.
///
@ -670,7 +629,7 @@ pub mod on {
/// }
/// ```
///
fn key(&self) -> String;
pub key: String,
/// Get the key code as an enum Variant.
///
@ -690,122 +649,122 @@ pub mod on {
/// }
/// ```
///
fn key_code(&self) -> KeyCode;
/// Check if the ctrl key was pressed down
fn ctrl_key(&self) -> bool;
fn get_modifier_state(&self, key_code: &str) -> bool;
fn locale(&self) -> String;
fn location(&self) -> usize;
fn meta_key(&self) -> bool;
fn repeat(&self) -> bool;
fn shift_key(&self) -> bool;
fn which(&self) -> usize;
pub key_code: KeyCode,
pub ctrl_key: bool,
pub locale: String,
pub location: usize,
pub meta_key: bool,
pub repeat: bool,
pub shift_key: bool,
pub which: usize,
// get_modifier_state: bool,
}
pub trait FocusEventInner {
/* DOMEventInnerTarget relatedTarget */
#[derive(Debug)]
pub struct FocusEventInner {/* DOMEventInner: Send + SyncTarget relatedTarget */}
#[derive(Debug)]
pub struct FormEventInner {
/* DOMEventInner: Send + SyncTarget relatedTarget */
pub value: String,
}
pub trait FormEventInner {
fn value(&self) -> String;
#[derive(Debug)]
pub struct MouseEventInner {
pub alt_key: bool,
pub button: i16,
pub buttons: u16,
pub client_x: i32,
pub client_y: i32,
pub ctrl_key: bool,
pub meta_key: bool,
pub page_x: i32,
pub page_y: i32,
pub screen_x: i32,
pub screen_y: i32,
pub shift_key: bool,
// fn get_modifier_state(&self, key_code: &str) -> bool;
}
pub trait MouseEventInner {
fn alt_key(&self) -> bool;
fn button(&self) -> i16;
fn buttons(&self) -> u16;
/// Get the X coordinate of the mouse relative to the window
fn client_x(&self) -> i32;
fn client_y(&self) -> i32;
fn ctrl_key(&self) -> bool;
fn meta_key(&self) -> bool;
fn page_x(&self) -> i32;
fn page_y(&self) -> i32;
fn screen_x(&self) -> i32;
fn screen_y(&self) -> i32;
fn shift_key(&self) -> bool;
fn get_modifier_state(&self, key_code: &str) -> bool;
}
pub trait PointerEventInner {
#[derive(Debug)]
pub struct PointerEventInner {
// Mouse only
fn alt_key(&self) -> bool;
fn button(&self) -> i16;
fn buttons(&self) -> u16;
fn client_x(&self) -> i32;
fn client_y(&self) -> i32;
fn ctrl_key(&self) -> bool;
fn meta_key(&self) -> bool;
fn page_x(&self) -> i32;
fn page_y(&self) -> i32;
fn screen_x(&self) -> i32;
fn screen_y(&self) -> i32;
fn shift_key(&self) -> bool;
fn get_modifier_state(&self, key_code: &str) -> bool;
fn pointer_id(&self) -> i32;
fn width(&self) -> i32;
fn height(&self) -> i32;
fn pressure(&self) -> f32;
fn tangential_pressure(&self) -> f32;
fn tilt_x(&self) -> i32;
fn tilt_y(&self) -> i32;
fn twist(&self) -> i32;
fn pointer_type(&self) -> String;
fn is_primary(&self) -> bool;
pub alt_key: bool,
pub button: i16,
pub buttons: u16,
pub client_x: i32,
pub client_y: i32,
pub ctrl_key: bool,
pub meta_key: bool,
pub page_x: i32,
pub page_y: i32,
pub screen_x: i32,
pub screen_y: i32,
pub shift_key: bool,
pub pointer_id: i32,
pub width: i32,
pub height: i32,
pub pressure: f32,
pub tangential_pressure: f32,
pub tilt_x: i32,
pub tilt_y: i32,
pub twist: i32,
pub pointer_type: String,
pub is_primary: bool,
// pub get_modifier_state: bool,
}
pub trait SelectionEventInner {}
#[derive(Debug)]
pub struct SelectionEventInner {}
pub trait TouchEventInner {
fn alt_key(&self) -> bool;
fn ctrl_key(&self) -> bool;
fn meta_key(&self) -> bool;
fn shift_key(&self) -> bool;
fn get_modifier_state(&self, key_code: &str) -> bool;
#[derive(Debug)]
pub struct TouchEventInner {
pub alt_key: bool,
pub ctrl_key: bool,
pub meta_key: bool,
pub shift_key: bool,
// get_modifier_state: bool,
// changedTouches: DOMTouchList,
// targetTouches: DOMTouchList,
// touches: DOMTouchList,
}
pub trait UIEventInner {
// DOMAbstractView view
fn detail(&self) -> i32;
#[derive(Debug)]
pub struct WheelEventInner {
pub delta_mode: u32,
pub delta_x: f64,
pub delta_y: f64,
pub delta_z: f64,
}
pub trait WheelEventInner {
fn delta_mode(&self) -> u32;
fn delta_x(&self) -> f64;
fn delta_y(&self) -> f64;
fn delta_z(&self) -> f64;
}
#[derive(Debug)]
pub struct MediaEventInner {}
pub trait MediaEventInner {}
pub trait ImageEventInner {
#[derive(Debug)]
pub struct ImageEventInner {
// load error
pub load_error: bool,
}
pub trait AnimationEventInner {
fn animation_name(&self) -> String;
fn pseudo_element(&self) -> String;
fn elapsed_time(&self) -> f32;
#[derive(Debug)]
pub struct AnimationEventInner {
pub animation_name: String,
pub pseudo_element: String,
pub elapsed_time: f32,
}
pub trait TransitionEventInner {
fn property_name(&self) -> String;
fn pseudo_element(&self) -> String;
fn elapsed_time(&self) -> f32;
#[derive(Debug)]
pub struct TransitionEventInner {
pub property_name: String,
pub pseudo_element: String,
pub elapsed_time: f32,
}
pub trait ToggleEventInner {}
#[derive(Debug)]
pub struct ToggleEventInner {}
}
pub use util::KeyCode;
mod util {
#[derive(Clone, Copy)]
#[derive(Clone, Copy, Debug)]
pub enum KeyCode {
Backspace = 8,
Tab = 9,
@ -1019,5 +978,63 @@ pub mod on {
*self as u32
}
}
pub(crate) fn event_meta(event: &UserEvent) -> (bool, EventPriority) {
use EventPriority::*;
match event.name {
// clipboard
"copy" | "cut" | "paste" => (true, Medium),
// Composition
"compositionend" | "compositionstart" | "compositionupdate" => (true, Low),
// Keyboard
"keydown" | "keypress" | "keyup" => (true, High),
// Focus
"focus" | "blur" => (true, Low),
// Form
"change" | "input" | "invalid" | "reset" | "submit" => (true, Medium),
// Mouse
"click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit"
| "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter"
| "mouseleave" | "mouseout" | "mouseover" | "mouseup" => (true, High),
"mousemove" => (false, Medium),
// Pointer
"pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
| "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => {
(true, Medium)
}
// Selection
"select" | "touchcancel" | "touchend" => (true, Medium),
// Touch
"touchmove" | "touchstart" => (true, Medium),
// Wheel
"scroll" | "wheel" => (false, Medium),
// Media
"abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted"
| "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play"
| "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
| "timeupdate" | "volumechange" | "waiting" => (true, Medium),
// Animation
"animationstart" | "animationend" | "animationiteration" => (true, Medium),
// Transition
"transitionend" => (true, Medium),
// Toggle
"toggle" => (true, Medium),
_ => (true, Low),
}
}

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
})
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));
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"),
sender_2
}
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!");
}