feat: support desktop more completely

This commit is contained in:
Jonathan Kelley committed 2021-10-05 03:37:15 -04:00
1 parent a2b0c50a34
commit efd0e9b564
25 files changed
+701 -441

No files matched your search

+1 -2
View File
@@ -15,8 +15,7 @@ fn main() {
dioxus::desktop::wry::application::dpi::LogicalSize::new(400.0, 800.0), dioxus::desktop::wry::application::dpi::LogicalSize::new(400.0, 800.0),
) )
}) })
}) });
.unwrap();
} }
static App: FC<()> = |cx, props| { static App: FC<()> = |cx, props| {
+7 -4
View File
@@ -14,13 +14,16 @@ fn main() {
let edits = vec![ let edits = vec![
// create a container and push it onto the stack // 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 // 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 // create a text node and push it onto the stack
CreateTextNode { CreateTextNode {
text: "hello world", text: "hello world",
id: 3, root: 3,
}, },
// append the text node to the h1 element // append the text node to the h1 element
AppendChildren { many: 1 }, AppendChildren { many: 1 },
@@ -30,7 +33,7 @@ fn main() {
AppendChildren { many: 1 }, 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| { const APP: FC<()> = |cx, _props| {
+1 -19
View File
@@ -467,7 +467,7 @@ impl<'bump> DiffMachine<'bump> {
if !has_comitted { if !has_comitted {
has_comitted = true; has_comitted = true;
edits.push(PushRoot { 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( fn replace_and_create_many_with_one(
&mut self, &mut self,
old: &'bump [VNode<'bump>], 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
}
}
-6
View File
@@ -39,7 +39,6 @@ pub(crate) struct DiffStack<'bump> {
instructions: Vec<DiffInstruction<'bump>>, instructions: Vec<DiffInstruction<'bump>>,
nodes_created_stack: SmallVec<[usize; 10]>, nodes_created_stack: SmallVec<[usize; 10]>,
pub scope_stack: SmallVec<[ScopeId; 5]>, pub scope_stack: SmallVec<[ScopeId; 5]>,
pub element_id_stack: SmallVec<[ElementId; 5]>,
} }
impl<'bump> DiffStack<'bump> { impl<'bump> DiffStack<'bump> {
@@ -48,7 +47,6 @@ impl<'bump> DiffStack<'bump> {
instructions: Vec::with_capacity(1000), instructions: Vec::with_capacity(1000),
nodes_created_stack: smallvec![], nodes_created_stack: smallvec![],
scope_stack: smallvec![], scope_stack: smallvec![],
element_id_stack: smallvec![],
} }
} }
@@ -82,10 +80,6 @@ impl<'bump> DiffStack<'bump> {
self.nodes_created_stack.push(count); 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>) { pub fn create_node(&mut self, node: &'bump VNode<'bump>, and: MountType<'bump>) {
self.nodes_created_stack.push(0); self.nodes_created_stack.push(0);
self.instructions.push(DiffInstruction::Mount { and }); self.instructions.push(DiffInstruction::Mount { and });
-2
View File
@@ -13,8 +13,6 @@ use std::{
cell::{Cell, RefCell}, cell::{Cell, RefCell},
fmt::Debug, fmt::Debug,
ops::Deref, ops::Deref,
rc::Rc,
sync::Arc,
}; };
pub use on::*; pub use on::*;
+12 -12
View File
@@ -23,7 +23,7 @@ impl<'a> Mutations<'a> {
// Navigation // Navigation
pub(crate) fn push_root(&mut self, root: ElementId) { pub(crate) fn push_root(&mut self, root: ElementId) {
let id = root.as_u64(); let id = root.as_u64();
self.edits.push(PushRoot { id }); self.edits.push(PushRoot { root: id });
} }
pub(crate) fn pop(&mut self) { pub(crate) fn pop(&mut self) {
@@ -53,7 +53,7 @@ impl<'a> Mutations<'a> {
// Create // Create
pub(crate) fn create_text_node(&mut self, text: &'a str, id: ElementId) { pub(crate) fn create_text_node(&mut self, text: &'a str, id: ElementId) {
let id = id.as_u64(); let id = id.as_u64();
self.edits.push(CreateTextNode { text, id }); self.edits.push(CreateTextNode { text, root: id });
} }
pub(crate) fn create_element( pub(crate) fn create_element(
@@ -64,14 +64,14 @@ impl<'a> Mutations<'a> {
) { ) {
let id = id.as_u64(); let id = id.as_u64();
match ns { match ns {
Some(ns) => self.edits.push(CreateElementNs { id, ns, tag }), Some(ns) => self.edits.push(CreateElementNs { root: id, ns, tag }),
None => self.edits.push(CreateElement { id, 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 // 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) { pub(crate) fn create_placeholder(&mut self, id: ElementId) {
let id = id.as_u64(); let id = id.as_u64();
self.edits.push(CreatePlaceholder { id }); self.edits.push(CreatePlaceholder { root: id });
} }
// events // events
@@ -87,7 +87,7 @@ impl<'a> Mutations<'a> {
self.edits.push(NewEventListener { self.edits.push(NewEventListener {
scope, scope,
event_name: event, event_name: event,
mounted_node_id: element_id, root: element_id,
}); });
} }
pub(crate) fn remove_event_listener(&mut self, event: &'static str) { pub(crate) fn remove_event_listener(&mut self, event: &'static str) {
@@ -159,7 +159,7 @@ impl<'a> NodeRefMutation<'a> {
)] )]
pub enum DomEdit<'bump> { pub enum DomEdit<'bump> {
PushRoot { PushRoot {
id: u64, root: u64,
}, },
PopRoot, PopRoot,
@@ -187,24 +187,24 @@ pub enum DomEdit<'bump> {
CreateTextNode { CreateTextNode {
text: &'bump str, text: &'bump str,
id: u64, root: u64,
}, },
CreateElement { CreateElement {
tag: &'bump str, tag: &'bump str,
id: u64, root: u64,
}, },
CreateElementNs { CreateElementNs {
tag: &'bump str, tag: &'bump str,
id: u64, root: u64,
ns: &'static str, ns: &'static str,
}, },
CreatePlaceholder { CreatePlaceholder {
id: u64, root: u64,
}, },
NewEventListener { NewEventListener {
event_name: &'static str, event_name: &'static str,
scope: ScopeId, scope: ScopeId,
mounted_node_id: u64, root: u64,
}, },
RemoveEventListener { RemoveEventListener {
event: &'static str, event: &'static str,
+2
View File
@@ -506,6 +506,8 @@ impl Scheduler {
} }
} }
log::debug!("work with deadline completed: {:#?}", committed_mutations);
committed_mutations committed_mutations
} }
-1
View File
@@ -7,7 +7,6 @@ use std::{
future::Future, future::Future,
pin::Pin, pin::Pin,
rc::Rc, rc::Rc,
sync::Arc,
}; };
/// Every component in Dioxus is represented by a `Scope`. /// Every component in Dioxus is represented by a `Scope`.
+87 -36
View File
@@ -34,10 +34,16 @@ fn test_original_diff() {
assert_eq!( assert_eq!(
mutations.edits, mutations.edits,
[ [
CreateElement { id: 0, tag: "div" }, CreateElement {
CreateElement { id: 1, tag: "div" }, root: 0,
tag: "div"
},
CreateElement {
root: 1,
tag: "div"
},
CreateTextNode { CreateTextNode {
id: 2, root: 2,
text: "Hello, world!" text: "Hello, world!"
}, },
AppendChildren { many: 1 }, AppendChildren { many: 1 },
@@ -73,20 +79,32 @@ fn create() {
assert_eq!( assert_eq!(
mutations.edits, mutations.edits,
[ [
CreateElement { id: 0, tag: "div" }, CreateElement {
CreateElement { id: 1, tag: "div" }, root: 0,
tag: "div"
},
CreateElement {
root: 1,
tag: "div"
},
CreateTextNode { CreateTextNode {
id: 2, root: 2,
text: "Hello, world!" text: "Hello, world!"
}, },
CreateElement { id: 3, tag: "div" }, CreateElement {
CreateElement { id: 4, tag: "div" }, root: 3,
tag: "div"
},
CreateElement {
root: 4,
tag: "div"
},
CreateTextNode { CreateTextNode {
id: 5, root: 5,
text: "hello" text: "hello"
}, },
CreateTextNode { CreateTextNode {
id: 6, root: 6,
text: "world" text: "world"
}, },
AppendChildren { many: 2 }, AppendChildren { many: 2 },
@@ -115,21 +133,30 @@ fn create_list() {
assert_eq!( assert_eq!(
mutations.edits, mutations.edits,
[ [
CreateElement { id: 0, tag: "div" }, CreateElement {
root: 0,
tag: "div"
},
CreateTextNode { CreateTextNode {
id: 1, root: 1,
text: "hello" text: "hello"
}, },
AppendChildren { many: 1 }, AppendChildren { many: 1 },
CreateElement { id: 2, tag: "div" }, CreateElement {
root: 2,
tag: "div"
},
CreateTextNode { CreateTextNode {
id: 3, root: 3,
text: "hello" text: "hello"
}, },
AppendChildren { many: 1 }, AppendChildren { many: 1 },
CreateElement { id: 4, tag: "div" }, CreateElement {
root: 4,
tag: "div"
},
CreateTextNode { CreateTextNode {
id: 5, root: 5,
text: "hello" text: "hello"
}, },
AppendChildren { many: 1 }, AppendChildren { many: 1 },
@@ -156,10 +183,22 @@ fn create_simple() {
assert_eq!( assert_eq!(
mutations.edits, mutations.edits,
[ [
CreateElement { id: 0, tag: "div" }, CreateElement {
CreateElement { id: 1, tag: "div" }, root: 0,
CreateElement { id: 2, tag: "div" }, tag: "div"
CreateElement { id: 3, tag: "div" }, },
CreateElement {
root: 1,
tag: "div"
},
CreateElement {
root: 2,
tag: "div"
},
CreateElement {
root: 3,
tag: "div"
},
AppendChildren { many: 4 }, AppendChildren { many: 4 },
] ]
); );
@@ -188,30 +227,39 @@ fn create_components() {
assert_eq!( assert_eq!(
mutations.edits, mutations.edits,
[ [
CreateElement { id: 0, tag: "h1" }, CreateElement { root: 0, tag: "h1" },
CreateElement { id: 1, tag: "div" }, CreateElement {
root: 1,
tag: "div"
},
CreateTextNode { CreateTextNode {
id: 2, root: 2,
text: "abc1" text: "abc1"
}, },
AppendChildren { many: 1 }, AppendChildren { many: 1 },
CreateElement { id: 3, tag: "p" }, CreateElement { root: 3, tag: "p" },
CreateElement { id: 4, tag: "h1" }, CreateElement { root: 4, tag: "h1" },
CreateElement { id: 5, tag: "div" }, CreateElement {
root: 5,
tag: "div"
},
CreateTextNode { CreateTextNode {
id: 6, root: 6,
text: "abc2" text: "abc2"
}, },
AppendChildren { many: 1 }, AppendChildren { many: 1 },
CreateElement { id: 7, tag: "p" }, CreateElement { root: 7, tag: "p" },
CreateElement { id: 8, tag: "h1" }, CreateElement { root: 8, tag: "h1" },
CreateElement { id: 9, tag: "div" }, CreateElement {
root: 9,
tag: "div"
},
CreateTextNode { CreateTextNode {
id: 10, root: 10,
text: "abc3" text: "abc3"
}, },
AppendChildren { many: 1 }, AppendChildren { many: 1 },
CreateElement { id: 11, tag: "p" }, CreateElement { root: 11, tag: "p" },
AppendChildren { many: 9 }, AppendChildren { many: 9 },
] ]
); );
@@ -230,13 +278,16 @@ fn anchors() {
assert_eq!( assert_eq!(
mutations.edits, mutations.edits,
[ [
CreateElement { id: 0, tag: "div" }, CreateElement {
root: 0,
tag: "div"
},
CreateTextNode { CreateTextNode {
id: 1, root: 1,
text: "hello" text: "hello"
}, },
AppendChildren { many: 1 }, AppendChildren { many: 1 },
CreatePlaceholder { id: 2 }, CreatePlaceholder { root: 2 },
AppendChildren { many: 2 }, AppendChildren { many: 2 },
] ]
); );
@@ -260,6 +311,6 @@ fn suspended() {
assert_eq!( assert_eq!(
mutations.edits, mutations.edits,
[CreatePlaceholder { id: 0 }, AppendChildren { many: 1 },] [CreatePlaceholder { root: 0 }, AppendChildren { many: 1 },]
); );
} }
+182 -74
View File
@@ -30,9 +30,12 @@ fn html_and_rsx_generate_the_same_output() {
assert_eq!( assert_eq!(
create.edits, create.edits,
[ [
CreateElement { id: 0, tag: "div" }, CreateElement {
root: 0,
tag: "div"
},
CreateTextNode { CreateTextNode {
id: 1, root: 1,
text: "Hello world" text: "Hello world"
}, },
AppendChildren { many: 1 }, AppendChildren { many: 1 },
@@ -43,7 +46,7 @@ fn html_and_rsx_generate_the_same_output() {
assert_eq!( assert_eq!(
change.edits, change.edits,
[ [
PushRoot { id: 1 }, PushRoot { root: 1 },
SetText { SetText {
text: "Goodbye world" text: "Goodbye world"
}, },
@@ -66,21 +69,30 @@ fn fragments_create_properly() {
assert_eq!( assert_eq!(
create.edits, create.edits,
[ [
CreateElement { id: 0, tag: "div" }, CreateElement {
root: 0,
tag: "div"
},
CreateTextNode { CreateTextNode {
id: 1, root: 1,
text: "Hello a" text: "Hello a"
}, },
AppendChildren { many: 1 }, AppendChildren { many: 1 },
CreateElement { id: 2, tag: "div" }, CreateElement {
root: 2,
tag: "div"
},
CreateTextNode { CreateTextNode {
id: 3, root: 3,
text: "Hello b" text: "Hello b"
}, },
AppendChildren { many: 1 }, AppendChildren { many: 1 },
CreateElement { id: 4, tag: "div" }, CreateElement {
root: 4,
tag: "div"
},
CreateTextNode { CreateTextNode {
id: 5, root: 5,
text: "Hello c" text: "Hello c"
}, },
AppendChildren { many: 1 }, AppendChildren { many: 1 },
@@ -101,12 +113,15 @@ fn empty_fragments_create_anchors() {
assert_eq!( assert_eq!(
create.edits, create.edits,
[CreatePlaceholder { id: 0 }, AppendChildren { many: 1 }] [CreatePlaceholder { root: 0 }, AppendChildren { many: 1 }]
); );
assert_eq!( assert_eq!(
change.edits, change.edits,
[ [
CreateElement { id: 1, tag: "div" }, CreateElement {
root: 1,
tag: "div"
},
ReplaceWith { m: 1, root: 0 } ReplaceWith { m: 1, root: 0 }
] ]
); );
@@ -123,16 +138,31 @@ fn empty_fragments_create_many_anchors() {
let (create, change) = dom.lazy_diff(left, right); let (create, change) = dom.lazy_diff(left, right);
assert_eq!( assert_eq!(
create.edits, create.edits,
[CreatePlaceholder { id: 0 }, AppendChildren { many: 1 }] [CreatePlaceholder { root: 0 }, AppendChildren { many: 1 }]
); );
assert_eq!( assert_eq!(
change.edits, change.edits,
[ [
CreateElement { id: 1, tag: "div" }, CreateElement {
CreateElement { id: 2, tag: "div" }, root: 1,
CreateElement { id: 3, tag: "div" }, tag: "div"
CreateElement { id: 4, tag: "div" }, },
CreateElement { id: 5, 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 } 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); let (create, change) = dom.lazy_diff(left, right);
assert_eq!( assert_eq!(
create.edits, create.edits,
[CreatePlaceholder { id: 0 }, AppendChildren { many: 1 }] [CreatePlaceholder { root: 0 }, AppendChildren { many: 1 }]
); );
assert_eq!( assert_eq!(
change.edits, change.edits,
[ [
CreateElement { id: 1, tag: "div" }, CreateElement {
root: 1,
tag: "div"
},
CreateTextNode { CreateTextNode {
text: "hello: 0", text: "hello: 0",
id: 2 root: 2
}, },
AppendChildren { many: 1 }, AppendChildren { many: 1 },
CreateElement { id: 3, tag: "div" }, CreateElement {
root: 3,
tag: "div"
},
CreateTextNode { CreateTextNode {
text: "hello: 1", text: "hello: 1",
id: 4 root: 4
}, },
AppendChildren { many: 1 }, AppendChildren { many: 1 },
CreateElement { id: 5, tag: "div" }, CreateElement {
root: 5,
tag: "div"
},
CreateTextNode { CreateTextNode {
text: "hello: 2", text: "hello: 2",
id: 6 root: 6
}, },
AppendChildren { many: 1 }, AppendChildren { many: 1 },
ReplaceWith { m: 3, root: 0 } ReplaceWith { m: 3, root: 0 }
@@ -198,16 +237,22 @@ fn many_items_become_fragment() {
assert_eq!( assert_eq!(
create.edits, create.edits,
[ [
CreateElement { id: 0, tag: "div" }, CreateElement {
root: 0,
tag: "div"
},
CreateTextNode { CreateTextNode {
text: "hello", text: "hello",
id: 1 root: 1
}, },
AppendChildren { many: 1 }, AppendChildren { many: 1 },
CreateElement { id: 2, tag: "div" }, CreateElement {
root: 2,
tag: "div"
},
CreateTextNode { CreateTextNode {
text: "hello", text: "hello",
id: 3 root: 3
}, },
AppendChildren { many: 1 }, AppendChildren { many: 1 },
AppendChildren { many: 2 }, AppendChildren { many: 2 },
@@ -219,7 +264,7 @@ fn many_items_become_fragment() {
change.edits, change.edits,
[ [
Remove { root: 2 }, Remove { root: 2 },
CreatePlaceholder { id: 4 }, CreatePlaceholder { root: 4 },
ReplaceWith { root: 0, m: 1 }, ReplaceWith { root: 0, m: 1 },
] ]
); );
@@ -265,14 +310,14 @@ fn two_fragments_with_differrent_elements_are_differet() {
changes.edits, changes.edits,
[ [
// create the new h1s // create the new h1s
CreateElement { tag: "h1", id: 3 }, CreateElement { tag: "h1", root: 3 },
CreateElement { tag: "h1", id: 4 }, CreateElement { tag: "h1", root: 4 },
CreateElement { tag: "h1", id: 5 }, CreateElement { tag: "h1", root: 5 },
InsertAfter { root: 1, n: 3 }, InsertAfter { root: 1, n: 3 },
// replace the divs with new h1s // replace the divs with new h1s
CreateElement { tag: "h1", id: 6 }, CreateElement { tag: "h1", root: 6 },
ReplaceWith { root: 0, m: 1 }, ReplaceWith { root: 0, m: 1 },
CreateElement { tag: "h1", id: 7 }, CreateElement { tag: "h1", root: 7 },
ReplaceWith { root: 1, m: 1 }, ReplaceWith { root: 1, m: 1 },
] ]
); );
@@ -296,12 +341,27 @@ fn two_fragments_with_differrent_elements_are_differet_shorter() {
assert_eq!( assert_eq!(
create.edits, create.edits,
[ [
CreateElement { id: 0, tag: "div" }, CreateElement {
CreateElement { id: 1, tag: "div" }, root: 0,
CreateElement { id: 2, tag: "div" }, tag: "div"
CreateElement { id: 3, tag: "div" }, },
CreateElement { id: 4, tag: "div" }, CreateElement {
CreateElement { id: 5, tag: "p" }, 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 }, AppendChildren { many: 6 },
] ]
); );
@@ -311,9 +371,9 @@ fn two_fragments_with_differrent_elements_are_differet_shorter() {
Remove { root: 2 }, Remove { root: 2 },
Remove { root: 3 }, Remove { root: 3 },
Remove { root: 4 }, Remove { root: 4 },
CreateElement { id: 6, tag: "h1" }, CreateElement { root: 6, tag: "h1" },
ReplaceWith { root: 0, m: 1 }, ReplaceWith { root: 0, m: 1 },
CreateElement { id: 7, tag: "h1" }, CreateElement { root: 7, tag: "h1" },
ReplaceWith { root: 1, m: 1 }, ReplaceWith { root: 1, m: 1 },
] ]
); );
@@ -337,18 +397,33 @@ fn two_fragments_with_same_elements_are_differet() {
assert_eq!( assert_eq!(
create.edits, create.edits,
[ [
CreateElement { id: 0, tag: "div" }, CreateElement {
CreateElement { id: 1, tag: "div" }, root: 0,
CreateElement { id: 2, tag: "p" }, tag: "div"
},
CreateElement {
root: 1,
tag: "div"
},
CreateElement { root: 2, tag: "p" },
AppendChildren { many: 3 }, AppendChildren { many: 3 },
] ]
); );
assert_eq!( assert_eq!(
change.edits, change.edits,
[ [
CreateElement { id: 3, tag: "div" }, CreateElement {
CreateElement { id: 4, tag: "div" }, root: 3,
CreateElement { id: 5, tag: "div" }, tag: "div"
},
CreateElement {
root: 4,
tag: "div"
},
CreateElement {
root: 5,
tag: "div"
},
InsertAfter { root: 1, n: 3 }, InsertAfter { root: 1, n: 3 },
] ]
); );
@@ -396,7 +471,7 @@ fn keyed_diffing_out_of_order() {
log::debug!("{:?}", &changes); log::debug!("{:?}", &changes);
assert_eq!( assert_eq!(
changes.edits, 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!( assert_eq!(
change.edits, change.edits,
[ [
PushRoot { id: 4 }, PushRoot { root: 4 },
PushRoot { id: 3 }, PushRoot { root: 3 },
InsertBefore { n: 2, root: 0 } InsertBefore { n: 2, root: 0 }
] ]
); );
@@ -448,8 +523,8 @@ fn keyed_diffing_out_of_order_adds_2() {
assert_eq!( assert_eq!(
change.edits, change.edits,
[ [
PushRoot { id: 3 }, PushRoot { root: 3 },
PushRoot { id: 4 }, PushRoot { root: 4 },
InsertBefore { n: 2, root: 0 } InsertBefore { n: 2, root: 0 }
] ]
); );
@@ -476,8 +551,8 @@ fn keyed_diffing_out_of_order_adds_3() {
assert_eq!( assert_eq!(
change.edits, change.edits,
[ [
PushRoot { id: 4 }, PushRoot { root: 4 },
PushRoot { id: 3 }, PushRoot { root: 3 },
InsertBefore { n: 2, root: 1 } InsertBefore { n: 2, root: 1 }
] ]
); );
@@ -504,8 +579,8 @@ fn keyed_diffing_out_of_order_adds_4() {
assert_eq!( assert_eq!(
change.edits, change.edits,
[ [
PushRoot { id: 4 }, PushRoot { root: 4 },
PushRoot { id: 3 }, PushRoot { root: 3 },
InsertBefore { n: 2, root: 2 } InsertBefore { n: 2, root: 2 }
] ]
); );
@@ -531,7 +606,7 @@ fn keyed_diffing_out_of_order_adds_5() {
let (_, change) = dom.lazy_diff(left, right); let (_, change) = dom.lazy_diff(left, right);
assert_eq!( assert_eq!(
change.edits, 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!( assert_eq!(
change.edits, change.edits,
[ [
CreateElement { id: 5, tag: "div" }, CreateElement {
CreateElement { id: 6, tag: "div" }, root: 5,
tag: "div"
},
CreateElement {
root: 6,
tag: "div"
},
InsertAfter { n: 2, root: 4 } InsertAfter { n: 2, root: 4 }
] ]
); );
@@ -584,11 +665,17 @@ fn keyed_diffing_additions_and_moves_on_ends() {
change.edits, change.edits,
[ [
// create 11, 12 // create 11, 12
CreateElement { tag: "div", id: 4 }, CreateElement {
CreateElement { tag: "div", id: 5 }, tag: "div",
root: 4
},
CreateElement {
tag: "div",
root: 5
},
InsertAfter { root: 2, n: 2 }, InsertAfter { root: 2, n: 2 },
// move 7 to the front // move 7 to the front
PushRoot { id: 3 }, PushRoot { root: 3 },
InsertBefore { root: 0, n: 1 } InsertBefore { root: 0, n: 1 }
] ]
); );
@@ -617,15 +704,27 @@ fn keyed_diffing_additions_and_moves_in_middle() {
change.edits, change.edits,
[ [
// create 13, 17 // create 13, 17
CreateElement { tag: "div", id: 4 }, CreateElement {
CreateElement { tag: "div", id: 5 }, tag: "div",
root: 4
},
CreateElement {
tag: "div",
root: 5
},
InsertBefore { root: 1, n: 2 }, InsertBefore { root: 1, n: 2 },
// create 11, 12 // create 11, 12
CreateElement { tag: "div", id: 6 }, CreateElement {
CreateElement { tag: "div", id: 7 }, tag: "div",
root: 6
},
CreateElement {
tag: "div",
root: 7
},
InsertBefore { root: 2, n: 2 }, InsertBefore { root: 2, n: 2 },
// move 7 // move 7
PushRoot { id: 3 }, PushRoot { root: 3 },
InsertBefore { root: 0, n: 1 } InsertBefore { root: 0, n: 1 }
] ]
); );
@@ -654,15 +753,21 @@ fn controlled_keyed_diffing_out_of_order() {
changes.edits, changes.edits,
[ [
// move 4 to after 6 // move 4 to after 6
PushRoot { id: 0 }, PushRoot { root: 0 },
InsertAfter { n: 1, root: 2 }, InsertAfter { n: 1, root: 2 },
// remove 7 // remove 7
// create 9 and insert before 6 // create 9 and insert before 6
CreateElement { id: 4, tag: "div" }, CreateElement {
root: 4,
tag: "div"
},
InsertBefore { n: 1, root: 2 }, InsertBefore { n: 1, root: 2 },
// create 0 and insert before 5 // create 0 and insert before 5
CreateElement { id: 5, tag: "div" }, CreateElement {
root: 5,
tag: "div"
},
InsertBefore { n: 1, root: 1 }, InsertBefore { n: 1, root: 1 },
] ]
); );
@@ -689,9 +794,12 @@ fn controlled_keyed_diffing_out_of_order_max_test() {
assert_eq!( assert_eq!(
changes.edits, changes.edits,
[ [
CreateElement { id: 5, tag: "div" }, CreateElement {
root: 5,
tag: "div"
},
InsertBefore { n: 1, root: 2 }, InsertBefore { n: 1, root: 2 },
PushRoot { id: 3 }, PushRoot { root: 3 },
InsertBefore { n: 1, root: 0 }, InsertBefore { n: 1, root: 0 },
] ]
); );
@@ -711,6 +819,6 @@ fn suspense() {
})); }));
assert_eq!( assert_eq!(
edits.edits, edits.edits,
[CreatePlaceholder { id: 0 }, AppendChildren { many: 1 }] [CreatePlaceholder { root: 0 }, AppendChildren { many: 1 }]
); );
} }
+1 -1
View File
@@ -34,7 +34,7 @@ fn shared_state_test() {
edits, edits,
[ [
CreateTextNode { CreateTextNode {
id: 0, root: 0,
text: "Hello, world!" text: "Hello, world!"
}, },
AppendChildren { many: 1 }, AppendChildren { many: 1 },
-1
View File
@@ -1,4 +1,3 @@
{ {
"rust-analyzer.inlayHints.enable": true,
"rust-analyzer.cargo.allFeatures": true "rust-analyzer.cargo.allFeatures": true
} }
+1
View File
@@ -0,0 +1 @@
WebviewWindow
+6 -15
View File
@@ -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 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
# web-view = { git = "https://github.com/Boscop/web-view" }
dioxus-core = { path = "../core", version = "0.1.2", features = ["serialize"] } dioxus-core = { path = "../core", version = "0.1.2", features = ["serialize"] }
anyhow = "1.0"
argh = "0.1.4" argh = "0.1.4"
serde = "1.0.120" serde = "1.0.120"
serde_json = "1.0.61" serde_json = "1.0.61"
thiserror = "1.0.23" thiserror = "1.0.23"
log = "0.4.13" log = "0.4.13"
fern = { version = "0.6.0", features = ["colored"] }
html-escape = "0.2.9" 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" wry = "0.12.2"
tokio = { version = "1.12.0", features = ["full"] }
futures-channel = "0.3.16" 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] [dev-dependencies]
dioxus-html = { path = "../html" } dioxus-html = { path = "../html" }
dioxus-core-macro = { path = "../core-macro" }
dioxus-hooks = { path = "../hooks" } 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] [build-dependencies]
+138 -27
View File
@@ -1,26 +1,148 @@
# Dioxus-webview # Dioxus-Desktop
Dioxus-webview bridges virtual and Webview DOMs together to make simple, portable, desktop applications. This crate provides an ergonomic API for Dioxus to build desktop apps.
Dioxus-webview is an attempt at making a simpler "Tauri" where creating desktop applications is as simple as:
```rust ```rust
// main.rs
fn main() { fn main() {
dioxus_desktop::new(App, |c| c) dioxus::desktop::launch(App, |c| c)
.launch()
.await;
} }
static App: FC<()> = |cx, props|{
static App: FC<()> = |cx, props| {
let (count, set_count) = use_state(cx, || 0); let (count, set_count) = use_state(cx, || 0);
rsx!(cx, div {
h1 { "Dioxus Desktop Demo" } cx.render(rsx!(
p { "Count is {count}"} WebviewWindow {
button { onclick: move |_| count += 1} 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: and then to create a native .app:
``` ```
@@ -29,17 +151,6 @@ dioxus bundle --platform macOS
## Goals ## 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. 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 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
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.
+1 -29
View File
@@ -2,32 +2,4 @@ use dioxus_core::prelude::*;
use dioxus_core_macro::*; use dioxus_core_macro::*;
use dioxus_html as dioxus_elements; use dioxus_html as dioxus_elements;
fn main() { 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 {}))
};
+1 -3
View File
@@ -2,12 +2,10 @@ use dioxus_core as dioxus;
use dioxus_core::prelude::*; use dioxus_core::prelude::*;
use dioxus_core_macro::*; use dioxus_core_macro::*;
use dioxus_hooks::*; use dioxus_hooks::*;
use dioxus_html as dioxus_elements; use dioxus_html as dioxus_elements;
fn main() { fn main() {
dioxus_desktop::set_up_logging(true); dioxus_desktop::launch(App, |c| c)
dioxus_desktop::launch(App, |c| c).unwrap();
} }
enum Scene { enum Scene {
+8
View File
@@ -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() {}
+1 -11
View File
@@ -1,11 +1 @@
fn main() { fn main() {}
tauri::AppBuilder::default().setup(move |app, name| {
//
let window = app.get_window();
let window = app.get_window();
tauri::spawn(|| async move {
//
});
});
}
+106
View File
@@ -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<dyn Fn()>),
Moved(Box<dyn Fn()>),
CloseRequested(Box<dyn Fn()>),
Destroyed(Box<dyn Fn()>),
DroppedFile(Box<dyn Fn()>),
HoveredFile(Box<dyn Fn()>),
HoverFileCancelled(Box<dyn Fn()>),
ReceivedTimeText(Box<dyn Fn()>),
Focused(Box<dyn Fn()>),
}
#[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::<RefCell<DesktopContext>>()?;
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<WindowHandle> {
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 {
}
}
})
};
}
View File
Whitespace-only changes.
+29 -28
View File
@@ -7,7 +7,7 @@ class Interpreter {
"onclick": {} "onclick": {}
}; };
this.lastNodeWasText = false; this.lastNodeWasText = false;
this.nodes = [root, root, root, root]; this.nodes = [root];
} }
top() { top() {
@@ -19,9 +19,8 @@ class Interpreter {
} }
PushRoot(edit) { PushRoot(edit) {
const id = edit.id; const id = edit.root;
const node = this.nodes[id]; const node = this.nodes[id];
console.log("pushing root ", node, "with id", id);
this.stack.push(node); this.stack.push(node);
} }
@@ -52,34 +51,36 @@ class Interpreter {
} }
Remove(edit) { Remove(edit) {
let node = this.nodes[edit.element_id]; let node = this.nodes[edit.root];
node.remove(); if (node !== undefined) {
node.remove();
}
} }
CreateTextNode(edit) { CreateTextNode(edit) {
const node = document.createTextNode(edit.text); const node = document.createTextNode(edit.text);
this.nodes[edit.id] = node; this.nodes[edit.root] = node;
this.stack.push(node); this.stack.push(node);
} }
CreateElement(edit) { CreateElement(edit) {
const tagName = edit.tag; const tagName = edit.tag;
const el = document.createElement(tagName); const el = document.createElement(tagName);
this.nodes[edit.id] = el; this.nodes[edit.root] = el;
this.stack.push(el); this.stack.push(el);
} }
CreateElementNs(edit) { CreateElementNs(edit) {
let el = document.createElementNS(edit.ns, edit.tag); let el = document.createElementNS(edit.ns, edit.tag);
this.stack.push(el); this.stack.push(el);
this.nodes[edit.id] = el; this.nodes[edit.root] = el;
} }
CreatePlaceholder(edit) { CreatePlaceholder(edit) {
let el = document.createElement("pre"); let el = document.createElement("pre");
// let el = document.createComment("vroot"); // let el = document.createComment("vroot");
this.stack.push(el); this.stack.push(el);
this.nodes[edit.id] = el; this.nodes[edit.root] = el;
} }
RemoveEventListener(edit) { } RemoveEventListener(edit) { }
@@ -127,30 +128,31 @@ class Interpreter {
} }
InsertAfter(edit) { InsertAfter(edit) {
let old = this.nodes[edit.element_id]; let old = this.nodes[edit.root];
let new_nodes = this.stack.splice(edit.many); let new_nodes = this.stack.splice(this.stack.length - edit.n);
// console.log("inserting nodes after", new_nodes, old);
old.after(...new_nodes); old.after(...new_nodes);
} }
InsertBefore(edit) { InsertBefore(edit) {
let old = this.nodes[edit.element_id]; let old = this.nodes[edit.root];
let new_nodes = this.stack.splice(edit.many); let new_nodes = this.stack.splice(this.stack.length - edit.n);
old.before(...new_nodes); old.before(...new_nodes);
} }
NewEventListener(edit) { NewEventListener(edit) {
const event_name = edit.event_name; 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 scope = edit.scope;
const element = this.top(); const element = this.top();
element.setAttribute(`dioxus-event-${event_name}`, `${scope}.${mounted_node_id}`); element.setAttribute(`dioxus-event-${event_name}`, `${scope}.${mounted_node_id}`);
console.log("listener map is", this.listeners);
if (this.listeners[event_name] === undefined) { if (this.listeners[event_name] === undefined) {
console.log("adding listener!");
this.listeners[event_name] = "bla"; this.listeners[event_name] = "bla";
this.root.addEventListener(event_name, (event) => { this.root.addEventListener(event_name, (event) => {
console.log("CLICKED");
const target = event.target; const target = event.target;
const val = target.getAttribute(`dioxus-event-${event_name}`); const val = target.getAttribute(`dioxus-event-${event_name}`);
if (val == null) { if (val == null) {
@@ -161,8 +163,9 @@ class Interpreter {
const scope_id = parseInt(fields[0]); const scope_id = parseInt(fields[0]);
const real_id = parseInt(fields[1]); 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); let contents = serialize_event(event);
rpc.call('user_event', { rpc.call('user_event', {
event: event_name, event: event_name,
@@ -170,33 +173,31 @@ class Interpreter {
mounted_dom_id: real_id, mounted_dom_id: real_id,
contents: contents, contents: contents,
}).then((reply) => { }).then((reply) => {
console.log(reply); console.log("reply received");
// console.log(reply);
this.stack.push(this.root); this.stack.push(this.root);
let edits = reply.edits; let edits = reply.edits;
for (let x = 0; x < edits.length; x++) { for (let x = 0; x < edits.length; x++) {
let edit = edits[x]; let edit = edits[x];
console.log(edit);
let f = this[edit.type]; let f = this[edit.type];
f.call(this, edit); f.call(this, edit);
} }
console.log("initiated"); // console.log("initiated");
}).catch((err) => { })
console.log("failed to initiate", err);
});
}); });
} }
} }
} }
async function initialize() { async function initialize() {
const reply = await rpc.call('initiate');
let root = window.document.getElementById("_dioxusroot"); let root = window.document.getElementById("_dioxusroot");
const interpreter = new Interpreter(root); const interpreter = new Interpreter(root);
console.log(reply);
const reply = await rpc.call('initiate');
let pre_rendered = reply.pre_rendered; let pre_rendered = reply.pre_rendered;
if (pre_rendered !== undefined) { if (pre_rendered !== undefined) {
@@ -209,14 +210,14 @@ async function initialize() {
} }
function apply_edits(edits, interpreter) { function apply_edits(edits, interpreter) {
console.log(edits);
for (let x = 0; x < edits.length; x++) { for (let x = 0; x < edits.length; x++) {
let edit = edits[x]; let edit = edits[x];
console.log(edit);
let f = interpreter[edit.type]; let f = interpreter[edit.type];
f.call(interpreter, edit); f.call(interpreter, edit);
} }
console.log("stack completed: ", interpreter.stack); // console.log("stack completed: ", interpreter.stack);
} }
function serialize_event(event) { function serialize_event(event) {
+109 -113
View File
@@ -4,7 +4,8 @@
//! //!
use std::borrow::BorrowMut; use std::borrow::BorrowMut;
use std::cell::RefCell; use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::rc::Rc; use std::rc::Rc;
use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicBool;
@@ -14,15 +15,11 @@ use std::sync::{Arc, RwLock};
use cfg::DesktopConfig; use cfg::DesktopConfig;
use dioxus_core::scheduler::SchedulerMsg; use dioxus_core::scheduler::SchedulerMsg;
use dioxus_core::*; use dioxus_core::*;
// use futures_channel::mpsc::UnboundedSender;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
mod logging;
pub use logging::set_up_logging;
pub use wry; 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::event_loop::{self, ControlFlow, EventLoop};
use wry::application::window::Fullscreen; use wry::application::window::Fullscreen;
use wry::webview::{WebView, WebViewBuilder}; use wry::webview::{WebView, WebViewBuilder};
@@ -32,6 +29,7 @@ use wry::{
}; };
mod cfg; mod cfg;
mod desktop_context;
mod dom; mod dom;
mod escape; mod escape;
mod events; mod events;
@@ -41,7 +39,7 @@ static HTML_CONTENT: &'static str = include_str!("./index.html");
pub fn launch( pub fn launch(
root: FC<()>, root: FC<()>,
config_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>, config_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
) -> anyhow::Result<()> { ) {
launch_with_props(root, (), config_builder) launch_with_props(root, (), config_builder)
} }
@@ -49,7 +47,7 @@ pub fn launch_with_props<P: Properties + 'static + Send + Sync>(
root: FC<P>, root: FC<P>,
props: P, props: P,
builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>, builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
) -> anyhow::Result<()> { ) {
run(root, props, builder) run(root, props, builder)
} }
@@ -58,6 +56,7 @@ enum RpcEvent<'a> {
Initialize { edits: Vec<DomEdit<'a>> }, Initialize { edits: Vec<DomEdit<'a>> },
} }
#[derive(Debug)]
enum BridgeEvent { enum BridgeEvent {
Initialize(serde_json::Value), Initialize(serde_json::Value),
Update(serde_json::Value), Update(serde_json::Value),
@@ -69,11 +68,12 @@ struct Response<'a> {
edits: Vec<DomEdit<'a>>, edits: Vec<DomEdit<'a>>,
} }
pub fn run<T: Properties + 'static + Send + Sync>( pub fn run<T: 'static + Send + Sync>(
root: FC<T>, root: FC<T>,
props: T, props: T,
user_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>, 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(); let mut cfg = DesktopConfig::new();
user_builder(&mut cfg); user_builder(&mut cfg);
let DesktopConfig { let DesktopConfig {
@@ -83,67 +83,95 @@ pub fn run<T: Properties + 'static + Send + Sync>(
.. ..
} = cfg; } = 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 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)? let (event_tx, event_rx) = tokio::sync::mpsc::unbounded_channel();
.with_url("wry://index.html")? let my_props = props_shared.take().unwrap();
.with_rpc_handler(move |_window: &Window, mut req: RpcRequest| {
// let sender = launch_vdom_with_tokio(root, my_props, event_tx.clone());
match req.method.as_str() {
"initiate" => { let locked_receiver = Rc::new(RefCell::new(event_rx));
let mut rx = (*locked_receiver).borrow_mut();
match rx.try_recv() { let webview = WebViewBuilder::new(window)
Ok(BridgeEvent::Initialize(edits)) => { .unwrap()
Some(RpcResponse::new_result(req.id.take(), Some(edits))) .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, })
} // 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.
"user_event" => { .with_custom_protocol("wry".into(), move |request| {
let data = req.params.unwrap(); let path = request.uri().replace("wry://", "");
let event = events::trigger_from_serialized(data); let (data, meta) = match path.as_str() {
sender.unbounded_send(SchedulerMsg::UiEvent(event)).unwrap(); "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(); wry::http::ResponseBuilder::new().mimetype(meta).body(data)
match rx.blocking_recv() { })
Some(BridgeEvent::Update(edits)) => { .build()
Some(RpcResponse::new_result(req.id.take(), Some(edits))) .unwrap();
}
_ => None, webviews.insert(window_id, webview);
}
}
_ => 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://", "");
let (data, meta) = match path.as_str() { Event::WindowEvent {
"index.html" => (include_bytes!("./index.html").to_vec(), "text/html"), event, window_id, ..
"index.html/index.js" => (include_bytes!("./index.js").to_vec(), "text/javascript"), } => match event {
_ => unimplemented!("path {}", path), 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) Event::MainEventsCleared => {}
}) Event::Resumed => {}
.build()?; Event::Suspended => {}
Event::LoopDestroyed => {}
run_event_loop(event_loop, webview, event_tx); _ => {}
}
Ok(()) })
} }
pub fn start<P: 'static + Send>( pub fn start<P: 'static + Send>(
@@ -155,17 +183,14 @@ pub fn start<P: 'static + Send>(
} }
// Create a new tokio runtime on a dedicated thread and then launch the apps VirtualDom. // Create a new tokio runtime on a dedicated thread and then launch the apps VirtualDom.
fn launch_vdom_with_tokio<C: Send + 'static>( pub(crate) fn launch_vdom_with_tokio<P: Send + 'static>(
root: FC<C>, root: FC<P>,
props: C, props: P,
event_tx: tokio::sync::mpsc::UnboundedSender<BridgeEvent>, event_tx: tokio::sync::mpsc::UnboundedSender<BridgeEvent>,
) -> futures_channel::mpsc::UnboundedSender<SchedulerMsg> { ) -> 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, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>();
let return_sender = sender.clone();
let sender_2 = sender.clone();
std::thread::spawn(move || { std::thread::spawn(move || {
// We create the runtim as multithreaded, so you can still "spawn" onto multiple threads // We create the runtim as multithreaded, so you can still "spawn" onto multiple threads
let runtime = tokio::runtime::Builder::new_multi_thread() let runtime = tokio::runtime::Builder::new_multi_thread()
@@ -186,58 +211,29 @@ fn launch_vdom_with_tokio<C: Send + 'static>(
} }
let edit_string = serde_json::to_value(Evt { edits: edits.edits }).unwrap(); let edit_string = serde_json::to_value(Evt { edits: edits.edits }).unwrap();
match event_tx.send(BridgeEvent::Initialize(edit_string)) {
Ok(_) => {} event_tx
Err(_) => {} .send(BridgeEvent::Initialize(edit_string))
} .expect("Sending should not fail");
loop { loop {
vir.wait_for_work().await; vir.wait_for_work().await;
let mut muts = vir.run_with_deadline(|| false); // we're running on our own thread, so we don't need to worry about blocking anything
while let Some(edit) = muts.pop() { // todo: maybe we want to schedule ourselves in
let edit_string = serde_json::to_value(Evt { edits: edit.edits }).unwrap(); // on average though, the virtualdom running natively is stupid fast
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 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 return_sender
}
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 {
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();
}
_ => {}
}
})
} }
-51
View File
@@ -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();
}
+8 -6
View File
@@ -105,19 +105,21 @@ impl WebsysDom {
pub fn process_edits(&mut self, edits: &mut Vec<DomEdit>) { pub fn process_edits(&mut self, edits: &mut Vec<DomEdit>) {
for edit in edits.drain(..) { for edit in edits.drain(..) {
match edit { match edit {
DomEdit::PushRoot { id: root } => self.push(root), DomEdit::PushRoot { root } => self.push(root),
DomEdit::PopRoot => self.pop(), DomEdit::PopRoot => self.pop(),
DomEdit::AppendChildren { many } => self.append_children(many), DomEdit::AppendChildren { many } => self.append_children(many),
DomEdit::ReplaceWith { m, root } => self.replace_with(m, root), DomEdit::ReplaceWith { m, root } => self.replace_with(m, root),
DomEdit::Remove { root } => self.remove(root), DomEdit::Remove { root } => self.remove(root),
DomEdit::CreateTextNode { text, id } => self.create_text_node(text, id), DomEdit::CreateTextNode { text, root: id } => self.create_text_node(text, id),
DomEdit::CreateElement { tag, id } => self.create_element(tag, None, id), DomEdit::CreateElement { tag, root: id } => self.create_element(tag, None, id),
DomEdit::CreateElementNs { tag, id, ns } => self.create_element(tag, Some(ns), id), DomEdit::CreateElementNs { tag, root: id, ns } => {
DomEdit::CreatePlaceholder { id } => self.create_placeholder(id), self.create_element(tag, Some(ns), id)
}
DomEdit::CreatePlaceholder { root: id } => self.create_placeholder(id),
DomEdit::NewEventListener { DomEdit::NewEventListener {
event_name, event_name,
scope, scope,
mounted_node_id, root: mounted_node_id,
} => self.new_event_listener(event_name, scope, mounted_node_id), } => self.new_event_listener(event_name, scope, mounted_node_id),
DomEdit::RemoveEventListener { event } => todo!(), DomEdit::RemoveEventListener { event } => todo!(),