From efd0e9b5648c809057f339083ba9d454f810d483 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 5 Oct 2021 03:37:15 -0400 Subject: [PATCH] feat: support desktop more completely --- examples/file_explorer.rs | 3 +- examples/manual_edits.rs | 11 +- packages/core/src/diff.rs | 20 +- packages/core/src/diff_stack.rs | 6 - packages/core/src/events.rs | 2 - packages/core/src/mutations.rs | 24 +-- packages/core/src/scheduler.rs | 2 + packages/core/src/scope.rs | 1 - packages/core/tests/create_dom.rs | 123 +++++++---- packages/core/tests/diffing.rs | 256 ++++++++++++++++------- packages/core/tests/sharedstate.rs | 2 +- packages/desktop/.vscode/settings.json | 1 - packages/desktop/.vscode/spellright.dict | 1 + packages/desktop/Cargo.toml | 21 +- packages/desktop/README.md | 165 ++++++++++++--- packages/desktop/examples/core.rs | 30 +-- packages/desktop/examples/crm.rs | 4 +- packages/desktop/examples/kitchensink.rs | 8 + packages/desktop/examples/tauri.rs | 12 +- packages/desktop/src/desktop_context.rs | 106 ++++++++++ packages/desktop/src/err.rs | 0 packages/desktop/src/index.js | 57 ++--- packages/desktop/src/lib.rs | 222 ++++++++++---------- packages/desktop/src/logging.rs | 51 ----- packages/web/src/dom.rs | 14 +- 25 files changed, 701 insertions(+), 441 deletions(-) create mode 100644 packages/desktop/.vscode/spellright.dict create mode 100644 packages/desktop/examples/kitchensink.rs create mode 100644 packages/desktop/src/desktop_context.rs create mode 100644 packages/desktop/src/err.rs delete mode 100644 packages/desktop/src/logging.rs diff --git a/examples/file_explorer.rs b/examples/file_explorer.rs index e050ce0d4..113f52216 100644 --- a/examples/file_explorer.rs +++ b/examples/file_explorer.rs @@ -15,8 +15,7 @@ fn main() { dioxus::desktop::wry::application::dpi::LogicalSize::new(400.0, 800.0), ) }) - }) - .unwrap(); + }); } static App: FC<()> = |cx, props| { diff --git a/examples/manual_edits.rs b/examples/manual_edits.rs index 30e133d2c..17253eb92 100644 --- a/examples/manual_edits.rs +++ b/examples/manual_edits.rs @@ -14,13 +14,16 @@ fn main() { let edits = vec![ // create a container and push it onto the stack - CreateElement { tag: "div", id: 0 }, + CreateElement { + tag: "div", + root: 0, + }, // create an element and push it onto the stack - CreateElement { tag: "h1", id: 2 }, + CreateElement { tag: "h1", root: 2 }, // create a text node and push it onto the stack CreateTextNode { text: "hello world", - id: 3, + root: 3, }, // append the text node to the h1 element AppendChildren { many: 1 }, @@ -30,7 +33,7 @@ fn main() { AppendChildren { many: 1 }, ]; - dioxus_desktop::run(APP, (), |c| c.with_edits(edits)).unwrap(); + dioxus_desktop::run(APP, (), |c| c.with_edits(edits)); } const APP: FC<()> = |cx, _props| { diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 7cb4303cc..6c6be4dd3 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -467,7 +467,7 @@ impl<'bump> DiffMachine<'bump> { if !has_comitted { has_comitted = true; edits.push(PushRoot { - id: root.unwrap().as_u64(), + root: root.unwrap().as_u64(), }); } }; @@ -1026,14 +1026,6 @@ impl<'bump> DiffMachine<'bump> { } } - fn replace_and_create_one_with_many( - &mut self, - old: &'bump VNode<'bump>, - new: &'bump [VNode<'bump>], - ) { - // - } - fn replace_and_create_many_with_one( &mut self, old: &'bump [VNode<'bump>], @@ -1130,13 +1122,3 @@ impl<'bump> DiffMachine<'bump> { } } } - -fn compare_strs(a: &str, b: &str) -> bool { - // Check by pointer, optimizing for static strs - if !std::ptr::eq(a, b) { - // If the pointers are different then check by value - a == b - } else { - true - } -} diff --git a/packages/core/src/diff_stack.rs b/packages/core/src/diff_stack.rs index aa9716cb6..3843bb069 100644 --- a/packages/core/src/diff_stack.rs +++ b/packages/core/src/diff_stack.rs @@ -39,7 +39,6 @@ pub(crate) struct DiffStack<'bump> { instructions: Vec>, nodes_created_stack: SmallVec<[usize; 10]>, pub scope_stack: SmallVec<[ScopeId; 5]>, - pub element_id_stack: SmallVec<[ElementId; 5]>, } impl<'bump> DiffStack<'bump> { @@ -48,7 +47,6 @@ impl<'bump> DiffStack<'bump> { instructions: Vec::with_capacity(1000), nodes_created_stack: smallvec![], scope_stack: smallvec![], - element_id_stack: smallvec![], } } @@ -82,10 +80,6 @@ impl<'bump> DiffStack<'bump> { self.nodes_created_stack.push(count); } - pub fn _push_element_id(&mut self, id: ElementId) { - self.element_id_stack.push(id); - } - pub fn create_node(&mut self, node: &'bump VNode<'bump>, and: MountType<'bump>) { self.nodes_created_stack.push(0); self.instructions.push(DiffInstruction::Mount { and }); diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index 2b40802a0..6b475a220 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -13,8 +13,6 @@ use std::{ cell::{Cell, RefCell}, fmt::Debug, ops::Deref, - rc::Rc, - sync::Arc, }; pub use on::*; diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 0a91989b7..810d0a88c 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -23,7 +23,7 @@ impl<'a> Mutations<'a> { // Navigation pub(crate) fn push_root(&mut self, root: ElementId) { let id = root.as_u64(); - self.edits.push(PushRoot { id }); + self.edits.push(PushRoot { root: id }); } pub(crate) fn pop(&mut self) { @@ -53,7 +53,7 @@ impl<'a> Mutations<'a> { // Create pub(crate) fn create_text_node(&mut self, text: &'a str, id: ElementId) { let id = id.as_u64(); - self.edits.push(CreateTextNode { text, id }); + self.edits.push(CreateTextNode { text, root: id }); } pub(crate) fn create_element( @@ -64,14 +64,14 @@ impl<'a> Mutations<'a> { ) { let id = id.as_u64(); match ns { - Some(ns) => self.edits.push(CreateElementNs { id, ns, tag }), - None => self.edits.push(CreateElement { id, tag }), + Some(ns) => self.edits.push(CreateElementNs { root: id, ns, tag }), + None => self.edits.push(CreateElement { root: id, tag }), } } // placeholders are nodes that don't get rendered but still exist as an "anchor" in the real dom pub(crate) fn create_placeholder(&mut self, id: ElementId) { let id = id.as_u64(); - self.edits.push(CreatePlaceholder { id }); + self.edits.push(CreatePlaceholder { root: id }); } // events @@ -87,7 +87,7 @@ impl<'a> Mutations<'a> { self.edits.push(NewEventListener { scope, event_name: event, - mounted_node_id: element_id, + root: element_id, }); } pub(crate) fn remove_event_listener(&mut self, event: &'static str) { @@ -159,7 +159,7 @@ impl<'a> NodeRefMutation<'a> { )] pub enum DomEdit<'bump> { PushRoot { - id: u64, + root: u64, }, PopRoot, @@ -187,24 +187,24 @@ pub enum DomEdit<'bump> { CreateTextNode { text: &'bump str, - id: u64, + root: u64, }, CreateElement { tag: &'bump str, - id: u64, + root: u64, }, CreateElementNs { tag: &'bump str, - id: u64, + root: u64, ns: &'static str, }, CreatePlaceholder { - id: u64, + root: u64, }, NewEventListener { event_name: &'static str, scope: ScopeId, - mounted_node_id: u64, + root: u64, }, RemoveEventListener { event: &'static str, diff --git a/packages/core/src/scheduler.rs b/packages/core/src/scheduler.rs index 819355c78..304884c39 100644 --- a/packages/core/src/scheduler.rs +++ b/packages/core/src/scheduler.rs @@ -506,6 +506,8 @@ impl Scheduler { } } + log::debug!("work with deadline completed: {:#?}", committed_mutations); + committed_mutations } diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index 74f7db2c6..19006042e 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -7,7 +7,6 @@ use std::{ future::Future, pin::Pin, rc::Rc, - sync::Arc, }; /// Every component in Dioxus is represented by a `Scope`. diff --git a/packages/core/tests/create_dom.rs b/packages/core/tests/create_dom.rs index aadb6ebda..22b84031f 100644 --- a/packages/core/tests/create_dom.rs +++ b/packages/core/tests/create_dom.rs @@ -34,10 +34,16 @@ fn test_original_diff() { assert_eq!( mutations.edits, [ - CreateElement { id: 0, tag: "div" }, - CreateElement { id: 1, tag: "div" }, + CreateElement { + root: 0, + tag: "div" + }, + CreateElement { + root: 1, + tag: "div" + }, CreateTextNode { - id: 2, + root: 2, text: "Hello, world!" }, AppendChildren { many: 1 }, @@ -73,20 +79,32 @@ fn create() { assert_eq!( mutations.edits, [ - CreateElement { id: 0, tag: "div" }, - CreateElement { id: 1, tag: "div" }, + CreateElement { + root: 0, + tag: "div" + }, + CreateElement { + root: 1, + tag: "div" + }, CreateTextNode { - id: 2, + root: 2, text: "Hello, world!" }, - CreateElement { id: 3, tag: "div" }, - CreateElement { id: 4, tag: "div" }, + CreateElement { + root: 3, + tag: "div" + }, + CreateElement { + root: 4, + tag: "div" + }, CreateTextNode { - id: 5, + root: 5, text: "hello" }, CreateTextNode { - id: 6, + root: 6, text: "world" }, AppendChildren { many: 2 }, @@ -115,21 +133,30 @@ fn create_list() { assert_eq!( mutations.edits, [ - CreateElement { id: 0, tag: "div" }, + CreateElement { + root: 0, + tag: "div" + }, CreateTextNode { - id: 1, + root: 1, text: "hello" }, AppendChildren { many: 1 }, - CreateElement { id: 2, tag: "div" }, + CreateElement { + root: 2, + tag: "div" + }, CreateTextNode { - id: 3, + root: 3, text: "hello" }, AppendChildren { many: 1 }, - CreateElement { id: 4, tag: "div" }, + CreateElement { + root: 4, + tag: "div" + }, CreateTextNode { - id: 5, + root: 5, text: "hello" }, AppendChildren { many: 1 }, @@ -156,10 +183,22 @@ fn create_simple() { assert_eq!( mutations.edits, [ - CreateElement { id: 0, tag: "div" }, - CreateElement { id: 1, tag: "div" }, - CreateElement { id: 2, tag: "div" }, - CreateElement { id: 3, tag: "div" }, + CreateElement { + root: 0, + tag: "div" + }, + CreateElement { + root: 1, + tag: "div" + }, + CreateElement { + root: 2, + tag: "div" + }, + CreateElement { + root: 3, + tag: "div" + }, AppendChildren { many: 4 }, ] ); @@ -188,30 +227,39 @@ fn create_components() { assert_eq!( mutations.edits, [ - CreateElement { id: 0, tag: "h1" }, - CreateElement { id: 1, tag: "div" }, + CreateElement { root: 0, tag: "h1" }, + CreateElement { + root: 1, + tag: "div" + }, CreateTextNode { - id: 2, + root: 2, text: "abc1" }, AppendChildren { many: 1 }, - CreateElement { id: 3, tag: "p" }, - CreateElement { id: 4, tag: "h1" }, - CreateElement { id: 5, tag: "div" }, + CreateElement { root: 3, tag: "p" }, + CreateElement { root: 4, tag: "h1" }, + CreateElement { + root: 5, + tag: "div" + }, CreateTextNode { - id: 6, + root: 6, text: "abc2" }, AppendChildren { many: 1 }, - CreateElement { id: 7, tag: "p" }, - CreateElement { id: 8, tag: "h1" }, - CreateElement { id: 9, tag: "div" }, + CreateElement { root: 7, tag: "p" }, + CreateElement { root: 8, tag: "h1" }, + CreateElement { + root: 9, + tag: "div" + }, CreateTextNode { - id: 10, + root: 10, text: "abc3" }, AppendChildren { many: 1 }, - CreateElement { id: 11, tag: "p" }, + CreateElement { root: 11, tag: "p" }, AppendChildren { many: 9 }, ] ); @@ -230,13 +278,16 @@ fn anchors() { assert_eq!( mutations.edits, [ - CreateElement { id: 0, tag: "div" }, + CreateElement { + root: 0, + tag: "div" + }, CreateTextNode { - id: 1, + root: 1, text: "hello" }, AppendChildren { many: 1 }, - CreatePlaceholder { id: 2 }, + CreatePlaceholder { root: 2 }, AppendChildren { many: 2 }, ] ); @@ -260,6 +311,6 @@ fn suspended() { assert_eq!( mutations.edits, - [CreatePlaceholder { id: 0 }, AppendChildren { many: 1 },] + [CreatePlaceholder { root: 0 }, AppendChildren { many: 1 },] ); } diff --git a/packages/core/tests/diffing.rs b/packages/core/tests/diffing.rs index c8063c061..5becd6a70 100644 --- a/packages/core/tests/diffing.rs +++ b/packages/core/tests/diffing.rs @@ -30,9 +30,12 @@ fn html_and_rsx_generate_the_same_output() { assert_eq!( create.edits, [ - CreateElement { id: 0, tag: "div" }, + CreateElement { + root: 0, + tag: "div" + }, CreateTextNode { - id: 1, + root: 1, text: "Hello world" }, AppendChildren { many: 1 }, @@ -43,7 +46,7 @@ fn html_and_rsx_generate_the_same_output() { assert_eq!( change.edits, [ - PushRoot { id: 1 }, + PushRoot { root: 1 }, SetText { text: "Goodbye world" }, @@ -66,21 +69,30 @@ fn fragments_create_properly() { assert_eq!( create.edits, [ - CreateElement { id: 0, tag: "div" }, + CreateElement { + root: 0, + tag: "div" + }, CreateTextNode { - id: 1, + root: 1, text: "Hello a" }, AppendChildren { many: 1 }, - CreateElement { id: 2, tag: "div" }, + CreateElement { + root: 2, + tag: "div" + }, CreateTextNode { - id: 3, + root: 3, text: "Hello b" }, AppendChildren { many: 1 }, - CreateElement { id: 4, tag: "div" }, + CreateElement { + root: 4, + tag: "div" + }, CreateTextNode { - id: 5, + root: 5, text: "Hello c" }, AppendChildren { many: 1 }, @@ -101,12 +113,15 @@ fn empty_fragments_create_anchors() { assert_eq!( create.edits, - [CreatePlaceholder { id: 0 }, AppendChildren { many: 1 }] + [CreatePlaceholder { root: 0 }, AppendChildren { many: 1 }] ); assert_eq!( change.edits, [ - CreateElement { id: 1, tag: "div" }, + CreateElement { + root: 1, + tag: "div" + }, ReplaceWith { m: 1, root: 0 } ] ); @@ -123,16 +138,31 @@ fn empty_fragments_create_many_anchors() { let (create, change) = dom.lazy_diff(left, right); assert_eq!( create.edits, - [CreatePlaceholder { id: 0 }, AppendChildren { many: 1 }] + [CreatePlaceholder { root: 0 }, AppendChildren { many: 1 }] ); assert_eq!( change.edits, [ - CreateElement { id: 1, tag: "div" }, - CreateElement { id: 2, tag: "div" }, - CreateElement { id: 3, tag: "div" }, - CreateElement { id: 4, tag: "div" }, - CreateElement { id: 5, tag: "div" }, + CreateElement { + root: 1, + tag: "div" + }, + CreateElement { + root: 2, + tag: "div" + }, + CreateElement { + root: 3, + tag: "div" + }, + CreateElement { + root: 4, + tag: "div" + }, + CreateElement { + root: 5, + tag: "div" + }, ReplaceWith { m: 5, root: 0 } ] ); @@ -154,27 +184,36 @@ fn empty_fragments_create_anchors_with_many_children() { let (create, change) = dom.lazy_diff(left, right); assert_eq!( create.edits, - [CreatePlaceholder { id: 0 }, AppendChildren { many: 1 }] + [CreatePlaceholder { root: 0 }, AppendChildren { many: 1 }] ); assert_eq!( change.edits, [ - CreateElement { id: 1, tag: "div" }, + CreateElement { + root: 1, + tag: "div" + }, CreateTextNode { text: "hello: 0", - id: 2 + root: 2 }, AppendChildren { many: 1 }, - CreateElement { id: 3, tag: "div" }, + CreateElement { + root: 3, + tag: "div" + }, CreateTextNode { text: "hello: 1", - id: 4 + root: 4 }, AppendChildren { many: 1 }, - CreateElement { id: 5, tag: "div" }, + CreateElement { + root: 5, + tag: "div" + }, CreateTextNode { text: "hello: 2", - id: 6 + root: 6 }, AppendChildren { many: 1 }, ReplaceWith { m: 3, root: 0 } @@ -198,16 +237,22 @@ fn many_items_become_fragment() { assert_eq!( create.edits, [ - CreateElement { id: 0, tag: "div" }, + CreateElement { + root: 0, + tag: "div" + }, CreateTextNode { text: "hello", - id: 1 + root: 1 }, AppendChildren { many: 1 }, - CreateElement { id: 2, tag: "div" }, + CreateElement { + root: 2, + tag: "div" + }, CreateTextNode { text: "hello", - id: 3 + root: 3 }, AppendChildren { many: 1 }, AppendChildren { many: 2 }, @@ -219,7 +264,7 @@ fn many_items_become_fragment() { change.edits, [ Remove { root: 2 }, - CreatePlaceholder { id: 4 }, + CreatePlaceholder { root: 4 }, ReplaceWith { root: 0, m: 1 }, ] ); @@ -265,14 +310,14 @@ fn two_fragments_with_differrent_elements_are_differet() { changes.edits, [ // create the new h1s - CreateElement { tag: "h1", id: 3 }, - CreateElement { tag: "h1", id: 4 }, - CreateElement { tag: "h1", id: 5 }, + CreateElement { tag: "h1", root: 3 }, + CreateElement { tag: "h1", root: 4 }, + CreateElement { tag: "h1", root: 5 }, InsertAfter { root: 1, n: 3 }, // replace the divs with new h1s - CreateElement { tag: "h1", id: 6 }, + CreateElement { tag: "h1", root: 6 }, ReplaceWith { root: 0, m: 1 }, - CreateElement { tag: "h1", id: 7 }, + CreateElement { tag: "h1", root: 7 }, ReplaceWith { root: 1, m: 1 }, ] ); @@ -296,12 +341,27 @@ fn two_fragments_with_differrent_elements_are_differet_shorter() { assert_eq!( create.edits, [ - CreateElement { id: 0, tag: "div" }, - CreateElement { id: 1, tag: "div" }, - CreateElement { id: 2, tag: "div" }, - CreateElement { id: 3, tag: "div" }, - CreateElement { id: 4, tag: "div" }, - CreateElement { id: 5, tag: "p" }, + CreateElement { + root: 0, + tag: "div" + }, + CreateElement { + root: 1, + tag: "div" + }, + CreateElement { + root: 2, + tag: "div" + }, + CreateElement { + root: 3, + tag: "div" + }, + CreateElement { + root: 4, + tag: "div" + }, + CreateElement { root: 5, tag: "p" }, AppendChildren { many: 6 }, ] ); @@ -311,9 +371,9 @@ fn two_fragments_with_differrent_elements_are_differet_shorter() { Remove { root: 2 }, Remove { root: 3 }, Remove { root: 4 }, - CreateElement { id: 6, tag: "h1" }, + CreateElement { root: 6, tag: "h1" }, ReplaceWith { root: 0, m: 1 }, - CreateElement { id: 7, tag: "h1" }, + CreateElement { root: 7, tag: "h1" }, ReplaceWith { root: 1, m: 1 }, ] ); @@ -337,18 +397,33 @@ fn two_fragments_with_same_elements_are_differet() { assert_eq!( create.edits, [ - CreateElement { id: 0, tag: "div" }, - CreateElement { id: 1, tag: "div" }, - CreateElement { id: 2, tag: "p" }, + CreateElement { + root: 0, + tag: "div" + }, + CreateElement { + root: 1, + tag: "div" + }, + CreateElement { root: 2, tag: "p" }, AppendChildren { many: 3 }, ] ); assert_eq!( change.edits, [ - CreateElement { id: 3, tag: "div" }, - CreateElement { id: 4, tag: "div" }, - CreateElement { id: 5, tag: "div" }, + CreateElement { + root: 3, + tag: "div" + }, + CreateElement { + root: 4, + tag: "div" + }, + CreateElement { + root: 5, + tag: "div" + }, InsertAfter { root: 1, n: 3 }, ] ); @@ -396,7 +471,7 @@ fn keyed_diffing_out_of_order() { log::debug!("{:?}", &changes); assert_eq!( changes.edits, - [PushRoot { id: 6 }, InsertBefore { root: 4, n: 1 }] + [PushRoot { root: 6 }, InsertBefore { root: 4, n: 1 }] ); } @@ -421,8 +496,8 @@ fn keyed_diffing_out_of_order_adds() { assert_eq!( change.edits, [ - PushRoot { id: 4 }, - PushRoot { id: 3 }, + PushRoot { root: 4 }, + PushRoot { root: 3 }, InsertBefore { n: 2, root: 0 } ] ); @@ -448,8 +523,8 @@ fn keyed_diffing_out_of_order_adds_2() { assert_eq!( change.edits, [ - PushRoot { id: 3 }, - PushRoot { id: 4 }, + PushRoot { root: 3 }, + PushRoot { root: 4 }, InsertBefore { n: 2, root: 0 } ] ); @@ -476,8 +551,8 @@ fn keyed_diffing_out_of_order_adds_3() { assert_eq!( change.edits, [ - PushRoot { id: 4 }, - PushRoot { id: 3 }, + PushRoot { root: 4 }, + PushRoot { root: 3 }, InsertBefore { n: 2, root: 1 } ] ); @@ -504,8 +579,8 @@ fn keyed_diffing_out_of_order_adds_4() { assert_eq!( change.edits, [ - PushRoot { id: 4 }, - PushRoot { id: 3 }, + PushRoot { root: 4 }, + PushRoot { root: 3 }, InsertBefore { n: 2, root: 2 } ] ); @@ -531,7 +606,7 @@ fn keyed_diffing_out_of_order_adds_5() { let (_, change) = dom.lazy_diff(left, right); assert_eq!( change.edits, - [PushRoot { id: 4 }, InsertBefore { n: 1, root: 3 }] + [PushRoot { root: 4 }, InsertBefore { n: 1, root: 3 }] ); } @@ -555,8 +630,14 @@ fn keyed_diffing_additions() { assert_eq!( change.edits, [ - CreateElement { id: 5, tag: "div" }, - CreateElement { id: 6, tag: "div" }, + CreateElement { + root: 5, + tag: "div" + }, + CreateElement { + root: 6, + tag: "div" + }, InsertAfter { n: 2, root: 4 } ] ); @@ -584,11 +665,17 @@ fn keyed_diffing_additions_and_moves_on_ends() { change.edits, [ // create 11, 12 - CreateElement { tag: "div", id: 4 }, - CreateElement { tag: "div", id: 5 }, + CreateElement { + tag: "div", + root: 4 + }, + CreateElement { + tag: "div", + root: 5 + }, InsertAfter { root: 2, n: 2 }, // move 7 to the front - PushRoot { id: 3 }, + PushRoot { root: 3 }, InsertBefore { root: 0, n: 1 } ] ); @@ -617,15 +704,27 @@ fn keyed_diffing_additions_and_moves_in_middle() { change.edits, [ // create 13, 17 - CreateElement { tag: "div", id: 4 }, - CreateElement { tag: "div", id: 5 }, + CreateElement { + tag: "div", + root: 4 + }, + CreateElement { + tag: "div", + root: 5 + }, InsertBefore { root: 1, n: 2 }, // create 11, 12 - CreateElement { tag: "div", id: 6 }, - CreateElement { tag: "div", id: 7 }, + CreateElement { + tag: "div", + root: 6 + }, + CreateElement { + tag: "div", + root: 7 + }, InsertBefore { root: 2, n: 2 }, // move 7 - PushRoot { id: 3 }, + PushRoot { root: 3 }, InsertBefore { root: 0, n: 1 } ] ); @@ -654,15 +753,21 @@ fn controlled_keyed_diffing_out_of_order() { changes.edits, [ // move 4 to after 6 - PushRoot { id: 0 }, + PushRoot { root: 0 }, InsertAfter { n: 1, root: 2 }, // remove 7 // create 9 and insert before 6 - CreateElement { id: 4, tag: "div" }, + CreateElement { + root: 4, + tag: "div" + }, InsertBefore { n: 1, root: 2 }, // create 0 and insert before 5 - CreateElement { id: 5, tag: "div" }, + CreateElement { + root: 5, + tag: "div" + }, InsertBefore { n: 1, root: 1 }, ] ); @@ -689,9 +794,12 @@ fn controlled_keyed_diffing_out_of_order_max_test() { assert_eq!( changes.edits, [ - CreateElement { id: 5, tag: "div" }, + CreateElement { + root: 5, + tag: "div" + }, InsertBefore { n: 1, root: 2 }, - PushRoot { id: 3 }, + PushRoot { root: 3 }, InsertBefore { n: 1, root: 0 }, ] ); @@ -711,6 +819,6 @@ fn suspense() { })); assert_eq!( edits.edits, - [CreatePlaceholder { id: 0 }, AppendChildren { many: 1 }] + [CreatePlaceholder { root: 0 }, AppendChildren { many: 1 }] ); } diff --git a/packages/core/tests/sharedstate.rs b/packages/core/tests/sharedstate.rs index 3c88d52a5..59a541c75 100644 --- a/packages/core/tests/sharedstate.rs +++ b/packages/core/tests/sharedstate.rs @@ -34,7 +34,7 @@ fn shared_state_test() { edits, [ CreateTextNode { - id: 0, + root: 0, text: "Hello, world!" }, AppendChildren { many: 1 }, diff --git a/packages/desktop/.vscode/settings.json b/packages/desktop/.vscode/settings.json index c7ce540f2..b3bde867c 100644 --- a/packages/desktop/.vscode/settings.json +++ b/packages/desktop/.vscode/settings.json @@ -1,4 +1,3 @@ { - "rust-analyzer.inlayHints.enable": true, "rust-analyzer.cargo.allFeatures": true } diff --git a/packages/desktop/.vscode/spellright.dict b/packages/desktop/.vscode/spellright.dict new file mode 100644 index 000000000..59b2711ce --- /dev/null +++ b/packages/desktop/.vscode/spellright.dict @@ -0,0 +1 @@ +WebviewWindow diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index 12a86add8..9bfda07c9 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -9,35 +9,26 @@ license = "MIT/Apache-2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -# web-view = { git = "https://github.com/Boscop/web-view" } dioxus-core = { path = "../core", version = "0.1.2", features = ["serialize"] } -anyhow = "1.0" argh = "0.1.4" serde = "1.0.120" serde_json = "1.0.61" thiserror = "1.0.23" log = "0.4.13" -fern = { version = "0.6.0", features = ["colored"] } html-escape = "0.2.9" -# 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" +tokio = { version = "1.12.0", features = ["full"], optional = true } +dioxus-core-macro = { path = "../core-macro" } + +[features] +default = ["tokio_runtime"] +tokio_runtime = ["tokio"] [dev-dependencies] dioxus-html = { path = "../html" } -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"] } -# wasm-bindgen-cli-support = "0.2.71" -# anyhow = "1.0.38" -# argh = "0.1.4" -# async-std = { version = "1.9.0", features = ["attributes"] } -# serde = "1.0.120" -# serde_json = "1.0.61" [build-dependencies] diff --git a/packages/desktop/README.md b/packages/desktop/README.md index a581d16fd..0c1663488 100644 --- a/packages/desktop/README.md +++ b/packages/desktop/README.md @@ -1,26 +1,148 @@ -# Dioxus-webview +# Dioxus-Desktop -Dioxus-webview bridges virtual and Webview DOMs together to make simple, portable, desktop applications. - -Dioxus-webview is an attempt at making a simpler "Tauri" where creating desktop applications is as simple as: +This crate provides an ergonomic API for Dioxus to build desktop apps. ```rust -// main.rs fn main() { - dioxus_desktop::new(App, |c| c) - .launch() - .await; + dioxus::desktop::launch(App, |c| c) } -static App: FC<()> = |cx, props|{ + +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} - }) + + cx.render(rsx!( + WebviewWindow { + onclose: move |e| log::info!("save our counter state to disk"), + div { + h1 { "Dioxus Desktop Demo" } + p { "Count is {count}"} + button { onclick: move |_| count += 1} + } + } + )) +} +``` + +Window management, system trays, notifications, and other desktop-related functionality is managed using the declarative Dioxus API, making it easy to add new features without having to jump through hoops. + +## Features +- Your rust code runs natively and under a Tokio runtime +- Declarative application management (dedicated components for windows, models, handlers, task tray, etc) +- Cross platform (runs on Mac, Linux, Windows, etc and mobile through the dioxus-mobile sub crate) + +## Managing Windows +Managing windows is done by simply rendering content into a `WebviewWindow` component. + +```rust +static App: FC<()> = |cx, props| { + rsx!(cx, WebviewWindow { "hello world" } ) +} +``` +This will create a new window with only the content "hello world". As this crate matures, we'll have new types of windows for different functionality. + +## Managing Notifications +Notifications also use a declarative approach. Sending a notification has never been easier! + +The api has been somewhat modeled after https://github.com/mikaelbr/node-notifier + +```rust +static Notifications: FC<()> = |cx, props| { + cx.render(rsx!( + Notification { + title: "title" + subtitle: "subtitle" + message: "message" + sound: "Basso" + icon: "Terminal" + contentImage: "image.png" + open: "https://github.com" + wait: true, + timeout: 5, + closeLabel: "Cancel" + actions: ["send", "receive"] + dropdownLabel: "messaging" + reply: true + + onclose: move |e| {} + onreply: move |e| {} + ondropdownselected: move |e| {} + ontimeout: move |e| {} + onerror: move |e| {} + } + )) +} + +``` + +## App Tray +Dioxus Desktop supports app trays, which can be built with native menu groups or with a custom window. + +```rust +static Tray: FC<()> = |cx, props| { + cx.render(rsx!( + GlobalTray { + MenuGroup { + MenuGroupItem { title: "New File", shortcut: "cmd+N", onclick: move |e| {} } + MenuGroupItem { title: "New Window", shortcut: "shift+cmd+N", onclick: move |e| {} } + } + } + )) +}; + +// using a builder +static Tray: FC<()> = |cx, props| { + let menu = MenuGroup::builder(cx) + .with_items([ + MenuGroupItem::builder() + .title() + .shortcut() + .onclick(move |e| {}), + MenuGroupItem::builder() + .title() + .shortcut() + .onclick(move |e| {}) + ]).build(); + + rsx!(cx, GlobalTray { rawmenu: menu }) +} + +// or with a custom window +static Tray: FC<()> = |cx, props| { + rsx!(cx, GlobalTray { div { "custom buttons here" } }) }; ``` +## Menu Bar +Declaring menus is convenient and cross-platform. + +```rust +static Menu: FC<()> = |cx, props| { + cx.render(rsx!( + MenuBarMajorItem { title: "File" + MenuGroup { + MenuGroupItem { title: "New File", shortcut: "cmd+N", onclick: move |e| {} } + MenuGroupItem { title: "New Window", shortcut: "shift+cmd+N", onclick: move |e| {} } + } + MenuGroup { + MenuGroupList { + title: "Open Recent", shortcut: "cmd+N" + MenuGroup { + (recent_items.iter().map(|file| rsx!( + MenuGroupItem { + onclick: move |_| open_file(file), + title: "{file}" + } + ))) + } + } + } + } + )) +}; +``` + +## Building, bundling, etc + and then to create a native .app: ``` @@ -29,17 +151,6 @@ dioxus bundle --platform macOS ## Goals -Because the host VirtualDOM is running in its own native process, native applications can unlock their full potential. Dioxus-webview is designed to be a 100% rust alternative to ElectronJS without the memory overhead or bloat of ElectronJS apps. - -By bridging the native process, desktop apps can access full multithreading power, peripheral support, hardware access, and native filesystem controls without the hassle of web technologies. Our goal with Dioxus-webview is to make it easy to ship both a web and native application, and quickly see large performance boosts without having to re-write the whole stack. As the dioxus ecosystem grows, we hope to see 3rd parties providing wrappers for storage, offline mode, etc that supports both web and native technologies. - -## Tech - -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 - +Because the host VirtualDOM is running in its own native process, native applications can unlock their full potential. Dioxus-Desktop is designed to be a 100% rust alternative to ElectronJS without the memory overhead or bloat of ElectronJS apps. +By bridging the native process, desktop apps can access full multithreading power, peripheral support, hardware access, and native filesystem controls without the hassle of web technologies. Our goal with this desktop crate is to make it easy to ship both a web and native application, and quickly see large performance boosts without having to re-write the whole stack. As the dioxus ecosystem grows, we hope to see 3rd parties providing wrappers for storage, offline mode, etc that supports both web and native technologies. diff --git a/packages/desktop/examples/core.rs b/packages/desktop/examples/core.rs index 3130dfef5..de3e04b5d 100644 --- a/packages/desktop/examples/core.rs +++ b/packages/desktop/examples/core.rs @@ -2,32 +2,4 @@ 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 {})) -}; +fn main() {} diff --git a/packages/desktop/examples/crm.rs b/packages/desktop/examples/crm.rs index 14ac2d1ca..72d1c3a3b 100644 --- a/packages/desktop/examples/crm.rs +++ b/packages/desktop/examples/crm.rs @@ -2,12 +2,10 @@ 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(); + dioxus_desktop::launch(App, |c| c) } enum Scene { diff --git a/packages/desktop/examples/kitchensink.rs b/packages/desktop/examples/kitchensink.rs new file mode 100644 index 000000000..5ec049ee9 --- /dev/null +++ b/packages/desktop/examples/kitchensink.rs @@ -0,0 +1,8 @@ +use dioxus_core as dioxus; +use dioxus_core::prelude::*; +use dioxus_core_macro::*; +use dioxus_hooks::*; + +use dioxus_html as dioxus_elements; + +fn main() {} diff --git a/packages/desktop/examples/tauri.rs b/packages/desktop/examples/tauri.rs index 77d1a08cb..f328e4d9d 100644 --- a/packages/desktop/examples/tauri.rs +++ b/packages/desktop/examples/tauri.rs @@ -1,11 +1 @@ -fn main() { - tauri::AppBuilder::default().setup(move |app, name| { - // - let window = app.get_window(); - - let window = app.get_window(); - tauri::spawn(|| async move { - // - }); - }); -} +fn main() {} diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs new file mode 100644 index 000000000..ac99be534 --- /dev/null +++ b/packages/desktop/src/desktop_context.rs @@ -0,0 +1,106 @@ +use std::cell::RefCell; + +use dioxus_core as dioxus; +use dioxus_core::{Context, DomTree, LazyNodes, NodeFactory, Properties}; +use dioxus_core_macro::Props; + +/* +This module provides a set of Dioxus components to easily manage windows, tabs, etc. + +Windows can be created anywhere in the tree, making them very flexible for things like modals, etc. + +*/ +pub struct DesktopContext {} + +impl DesktopContext { + fn add_window(&mut self) { + // + } + fn close_window(&mut self) { + // + } +} + +enum WindowHandlers { + Resized(Box), + Moved(Box), + CloseRequested(Box), + Destroyed(Box), + DroppedFile(Box), + HoveredFile(Box), + HoverFileCancelled(Box), + ReceivedTimeText(Box), + Focused(Box), +} + +#[derive(Props)] +pub struct WebviewWindowProps<'a> { + onclose: &'a dyn FnMut(()), + + onopen: &'a dyn FnMut(()), + + /// focuse me + onfocused: &'a dyn FnMut(()), +} + +/// A handle to a +/// +/// +/// +/// +/// +/// +/// +/// +/// +pub fn WebviewWindow<'a>(cx: Context<'a>, props: &'a WebviewWindowProps) -> DomTree<'a> { + let dtcx = cx.use_consume_state::>()?; + + cx.use_hook( + |_| { + // + }, + |state| { + // + }, + |hook| { + // + }, + ); + + // render the children directly + cx.render(LazyNodes::new(move |f: NodeFactory| { + f.fragment_from_iter(cx.children()) + })) +} + +pub struct WindowHandle {} + +/// Get a handle to the current window from inside a component +pub fn use_current_window(cx: Context) -> Option { + todo!() +} + +#[test] +fn syntax_works() { + use dioxus_core as dioxus; + use dioxus_core::prelude::*; + use dioxus_core_macro::*; + use dioxus_hooks::*; + use dioxus_html as dioxus_elements; + + static App: FC<()> = |cx, props| { + cx.render(rsx! { + // left window + WebviewWindow { + onclose: move |evt| {} + onopen: move |evt| {} + onfocused: move |evt| {} + + div { + + } + } + }) + }; +} diff --git a/packages/desktop/src/err.rs b/packages/desktop/src/err.rs new file mode 100644 index 000000000..e69de29bb diff --git a/packages/desktop/src/index.js b/packages/desktop/src/index.js index b48878073..0245aabef 100644 --- a/packages/desktop/src/index.js +++ b/packages/desktop/src/index.js @@ -7,7 +7,7 @@ class Interpreter { "onclick": {} }; this.lastNodeWasText = false; - this.nodes = [root, root, root, root]; + this.nodes = [root]; } top() { @@ -19,9 +19,8 @@ class Interpreter { } PushRoot(edit) { - const id = edit.id; + const id = edit.root; const node = this.nodes[id]; - console.log("pushing root ", node, "with id", id); this.stack.push(node); } @@ -52,34 +51,36 @@ class Interpreter { } Remove(edit) { - let node = this.nodes[edit.element_id]; - node.remove(); + let node = this.nodes[edit.root]; + if (node !== undefined) { + node.remove(); + } } CreateTextNode(edit) { const node = document.createTextNode(edit.text); - this.nodes[edit.id] = node; + this.nodes[edit.root] = node; this.stack.push(node); } CreateElement(edit) { const tagName = edit.tag; const el = document.createElement(tagName); - this.nodes[edit.id] = el; + this.nodes[edit.root] = el; this.stack.push(el); } CreateElementNs(edit) { let el = document.createElementNS(edit.ns, edit.tag); this.stack.push(el); - this.nodes[edit.id] = el; + this.nodes[edit.root] = el; } CreatePlaceholder(edit) { let el = document.createElement("pre"); // let el = document.createComment("vroot"); this.stack.push(el); - this.nodes[edit.id] = el; + this.nodes[edit.root] = el; } RemoveEventListener(edit) { } @@ -127,30 +128,31 @@ class Interpreter { } InsertAfter(edit) { - let old = this.nodes[edit.element_id]; - let new_nodes = this.stack.splice(edit.many); + let old = this.nodes[edit.root]; + let new_nodes = this.stack.splice(this.stack.length - edit.n); + // console.log("inserting nodes after", new_nodes, old); old.after(...new_nodes); } InsertBefore(edit) { - let old = this.nodes[edit.element_id]; - let new_nodes = this.stack.splice(edit.many); + let old = this.nodes[edit.root]; + let new_nodes = this.stack.splice(this.stack.length - edit.n); old.before(...new_nodes); } NewEventListener(edit) { const event_name = edit.event_name; - const mounted_node_id = edit.mounted_node_id; + const mounted_node_id = edit.root; 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) => { + console.log("CLICKED"); const target = event.target; const val = target.getAttribute(`dioxus-event-${event_name}`); if (val == null) { @@ -161,8 +163,9 @@ class Interpreter { 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}`); + // console.log(`parsed event with scope_id ${scope_id} and real_id ${real_id}`); + console.log("message fired"); let contents = serialize_event(event); rpc.call('user_event', { event: event_name, @@ -170,33 +173,31 @@ class Interpreter { mounted_dom_id: real_id, contents: contents, }).then((reply) => { - console.log(reply); + console.log("reply received"); + + // 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); - }); + // console.log("initiated"); + }) }); } } } async function initialize() { - const reply = await rpc.call('initiate'); let root = window.document.getElementById("_dioxusroot"); const interpreter = new Interpreter(root); - console.log(reply); + + const reply = await rpc.call('initiate'); let pre_rendered = reply.pre_rendered; if (pre_rendered !== undefined) { @@ -209,14 +210,14 @@ async function initialize() { } function apply_edits(edits, interpreter) { + console.log(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("stack completed: ", interpreter.stack); } function serialize_event(event) { diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index cba3e518e..49fedf515 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -4,7 +4,8 @@ //! use std::borrow::BorrowMut; -use std::cell::RefCell; +use std::cell::{Cell, RefCell}; +use std::collections::HashMap; use std::ops::{Deref, DerefMut}; use std::rc::Rc; use std::sync::atomic::AtomicBool; @@ -14,15 +15,11 @@ 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::{Event, StartCause, WindowEvent}; use wry::application::event_loop::{self, ControlFlow, EventLoop}; use wry::application::window::Fullscreen; use wry::webview::{WebView, WebViewBuilder}; @@ -32,6 +29,7 @@ use wry::{ }; mod cfg; +mod desktop_context; mod dom; mod escape; mod events; @@ -41,7 +39,7 @@ static HTML_CONTENT: &'static str = include_str!("./index.html"); pub fn launch( root: FC<()>, config_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>, -) -> anyhow::Result<()> { +) { launch_with_props(root, (), config_builder) } @@ -49,7 +47,7 @@ pub fn launch_with_props( root: FC

, props: P, builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>, -) -> anyhow::Result<()> { +) { run(root, props, builder) } @@ -58,6 +56,7 @@ enum RpcEvent<'a> { Initialize { edits: Vec> }, } +#[derive(Debug)] enum BridgeEvent { Initialize(serde_json::Value), Update(serde_json::Value), @@ -69,11 +68,12 @@ struct Response<'a> { edits: Vec>, } -pub fn run( +pub fn run( root: FC, props: T, user_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>, -) -> anyhow::Result<()> { +) { + // Generate the config let mut cfg = DesktopConfig::new(); user_builder(&mut cfg); let DesktopConfig { @@ -83,67 +83,95 @@ pub fn run( .. } = cfg; + // All of our webview windows are stored in a way that we can look them up later + // The "DesktopContext" will provide functionality for spawning these windows + let mut webviews = HashMap::new(); let event_loop = EventLoop::new(); - let window = window.build(&event_loop)?; - let (event_tx, mut event_rx) = tokio::sync::mpsc::unbounded_channel(); + let props_shared = Cell::new(Some(props)); - let sender = launch_vdom_with_tokio(root, props, event_tx.clone()); + event_loop.run(move |event, event_loop, control_flow| { + *control_flow = ControlFlow::Wait; - let locked_receiver = Rc::new(RefCell::new(event_rx)); + match event { + Event::NewEvents(StartCause::Init) => { + let window = WindowBuilder::new().build(&event_loop).unwrap(); + let window_id = window.id(); - let webview = WebViewBuilder::new(window)? - .with_url("wry://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))) + let (event_tx, event_rx) = tokio::sync::mpsc::unbounded_channel(); + let my_props = props_shared.take().unwrap(); + + let sender = launch_vdom_with_tokio(root, my_props, event_tx.clone()); + + let locked_receiver = Rc::new(RefCell::new(event_rx)); + + let webview = WebViewBuilder::new(window) + .unwrap() + .with_url("wry://index.html") + .unwrap() + .with_rpc_handler(move |_window: &Window, mut req: RpcRequest| { + let mut rx = (*locked_receiver).borrow_mut(); + match req.method.as_str() { + "initiate" => { + if let Ok(BridgeEvent::Initialize(edits)) = rx.try_recv() { + Some(RpcResponse::new_result(req.id.take(), Some(edits))) + } else { + None + } + } + "user_event" => { + let event = events::trigger_from_serialized(req.params.unwrap()); + sender.unbounded_send(SchedulerMsg::UiEvent(event)).unwrap(); + if let Some(BridgeEvent::Update(edits)) = rx.blocking_recv() { + Some(RpcResponse::new_result(req.id.take(), Some(edits))) + } else { + None + } + } + _ => None, } - _ => None, - } - } - "user_event" => { - let data = req.params.unwrap(); - let event = events::trigger_from_serialized(data); - sender.unbounded_send(SchedulerMsg::UiEvent(event)).unwrap(); + }) + // Any content that that uses the `wry://` scheme will be shuttled through this handler as a "special case" + // For now, we only serve two pieces of content which get included as bytes into the final binary. + .with_custom_protocol("wry".into(), move |request| { + let path = request.uri().replace("wry://", ""); + let (data, meta) = match path.as_str() { + "index.html" => (include_bytes!("./index.html").to_vec(), "text/html"), + "index.html/index.js" => { + (include_bytes!("./index.js").to_vec(), "text/javascript") + } + _ => unimplemented!("path {}", path), + }; - let mut rx = (*locked_receiver).borrow_mut(); - match rx.blocking_recv() { - Some(BridgeEvent::Update(edits)) => { - Some(RpcResponse::new_result(req.id.take(), Some(edits))) - } - _ => None, - } - } - _ => todo!("this message failed"), + wry::http::ResponseBuilder::new().mimetype(meta).body(data) + }) + .build() + .unwrap(); + + webviews.insert(window_id, webview); } - }) - // 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://", ""); - let (data, meta) = match path.as_str() { - "index.html" => (include_bytes!("./index.html").to_vec(), "text/html"), - "index.html/index.js" => (include_bytes!("./index.js").to_vec(), "text/javascript"), - _ => unimplemented!("path {}", path), - }; + Event::WindowEvent { + event, window_id, .. + } => match event { + WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + WindowEvent::Resized(_) | WindowEvent::Moved(_) => { + if let Some(view) = webviews.get_mut(&window_id) { + let _ = view.resize(); + } + } + // TODO: we want to shuttle all of these events into the user's app + _ => {} + }, - ResponseBuilder::new().mimetype(meta).body(data) - }) - .build()?; + Event::MainEventsCleared => {} + Event::Resumed => {} + Event::Suspended => {} + Event::LoopDestroyed => {} - run_event_loop(event_loop, webview, event_tx); - - Ok(()) + _ => {} + } + }) } pub fn start( @@ -155,17 +183,14 @@ pub fn start( } // Create a new tokio runtime on a dedicated thread and then launch the apps VirtualDom. -fn launch_vdom_with_tokio( - root: FC, - props: C, +pub(crate) fn launch_vdom_with_tokio( + root: FC

, + props: P, event_tx: tokio::sync::mpsc::UnboundedSender, ) -> futures_channel::mpsc::UnboundedSender { - // 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::(); + let return_sender = sender.clone(); - let sender_2 = sender.clone(); std::thread::spawn(move || { // We create the runtim as multithreaded, so you can still "spawn" onto multiple threads let runtime = tokio::runtime::Builder::new_multi_thread() @@ -186,58 +211,29 @@ fn launch_vdom_with_tokio( } let edit_string = serde_json::to_value(Evt { edits: edits.edits }).unwrap(); - match event_tx.send(BridgeEvent::Initialize(edit_string)) { - Ok(_) => {} - Err(_) => {} - } + + event_tx + .send(BridgeEvent::Initialize(edit_string)) + .expect("Sending should not fail"); loop { vir.wait_for_work().await; - let mut muts = vir.run_with_deadline(|| false); - 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); - } - } - } + // we're running on our own thread, so we don't need to worry about blocking anything + // todo: maybe we want to schedule ourselves in + // on average though, the virtualdom running natively is stupid fast - log::info!("mutations sent on channel"); + let mut muts = vir.run_with_deadline(|| false); + + while let Some(edit) = muts.pop() { + let edit_string = serde_json::to_value(Evt { edits: edit.edits }) + .expect("serializing edits should never fail"); + event_tx + .send(BridgeEvent::Update(edit_string)) + .expect("Sending should not fail"); + } } }) }); - sender_2 -} - -fn run_event_loop( - event_loop: EventLoop<()>, - webview: WebView, - event_tx: tokio::sync::mpsc::UnboundedSender, -) { - let _ = event_tx.clone(); - event_loop.run(move |event, target, control_flow| { - *control_flow = ControlFlow::Wait; - - match event { - Event::WindowEvent { - event, window_id, .. - } => match event { - WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, - WindowEvent::Resized(_) | WindowEvent::Moved(_) => { - let _ = webview.resize(); - } - _ => {} - }, - - Event::MainEventsCleared => { - webview.resize(); - // window.request_redraw(); - } - - _ => {} - } - }) + return_sender } diff --git a/packages/desktop/src/logging.rs b/packages/desktop/src/logging.rs deleted file mode 100644 index db605be9e..000000000 --- a/packages/desktop/src/logging.rs +++ /dev/null @@ -1,51 +0,0 @@ -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(); -} diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index 972c1297c..c39ae71f3 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -105,19 +105,21 @@ impl WebsysDom { pub fn process_edits(&mut self, edits: &mut Vec) { for edit in edits.drain(..) { match edit { - DomEdit::PushRoot { id: root } => self.push(root), + DomEdit::PushRoot { root } => self.push(root), DomEdit::PopRoot => self.pop(), DomEdit::AppendChildren { many } => self.append_children(many), DomEdit::ReplaceWith { m, root } => self.replace_with(m, root), DomEdit::Remove { root } => self.remove(root), - 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), - DomEdit::CreatePlaceholder { id } => self.create_placeholder(id), + DomEdit::CreateTextNode { text, root: id } => self.create_text_node(text, id), + DomEdit::CreateElement { tag, root: id } => self.create_element(tag, None, id), + DomEdit::CreateElementNs { tag, root: id, ns } => { + self.create_element(tag, Some(ns), id) + } + DomEdit::CreatePlaceholder { root: id } => self.create_placeholder(id), DomEdit::NewEventListener { event_name, scope, - mounted_node_id, + root: mounted_node_id, } => self.new_event_listener(event_name, scope, mounted_node_id), DomEdit::RemoveEventListener { event } => todo!(),