feat: move webview to wry
This commit is contained in:
1 parent
2547da36a0
commit
99d94b69ab
11 files changed
+419
-96
No files matched your search
@@ -8,13 +8,13 @@
|
|||||||
Dioxus is a portable, performant, and ergonomic framework for building cross-platform user experiences in Rust.
|
Dioxus is a portable, performant, and ergonomic framework for building cross-platform user experiences in Rust.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn Example(cx: Context<()>) -> VNode {
|
fn App(cx: Context<()>) -> VNode {
|
||||||
let name = use_state(cx, || "..?");
|
let mut count = use_state(cx, || 0);
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
h1 { "Hello, {name}" }
|
h1 { "Hi-Five counter: {count}" }
|
||||||
button { "?", onclick: move |_| name.set("world!")}
|
button { onclick: move |_| count += 1, "Up high!" }
|
||||||
button { "?", onclick: move |_| name.set("Dioxus 🎉")}
|
button { onclick: move |_| count -= 1, "Down low!" }
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|||||||
+5
-2
@@ -21,8 +21,11 @@ use dioxus::prelude::*;
|
|||||||
const STYLE: &str = include_str!("./assets/calculator.css");
|
const STYLE: &str = include_str!("./assets/calculator.css");
|
||||||
fn main() {
|
fn main() {
|
||||||
dioxus::desktop::launch(App, |cfg| {
|
dioxus::desktop::launch(App, |cfg| {
|
||||||
cfg.title("Calculator Demo").resizable(false).size(350, 550)
|
cfg.with_title("Calculator Demo")
|
||||||
});
|
.with_resizable(true)
|
||||||
|
.with_skip_taskbar(true)
|
||||||
|
})
|
||||||
|
.expect("failed to launch dioxus app");
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Operator {
|
enum Operator {
|
||||||
|
|||||||
+3
-6
@@ -20,12 +20,9 @@ static App: FC<()> = |cx| {
|
|||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
h1 { "Dioxus Desktop Demo" }
|
h1 { "Hifive counter: {count}" }
|
||||||
p { "Count is {count}" }
|
button { onclick: move |_| count += 1, "Up high!" }
|
||||||
button {
|
button { onclick: move |_| count -= 1, "Down low!" }
|
||||||
"Click to increment"
|
|
||||||
onclick: move |_| count += 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
@@ -102,7 +102,10 @@ where
|
|||||||
impl<'a, T: 'static> UseState<'a, T> {
|
impl<'a, T: 'static> UseState<'a, T> {
|
||||||
/// Tell the Dioxus Scheduler that we need to be processed
|
/// Tell the Dioxus Scheduler that we need to be processed
|
||||||
pub fn needs_update(&self) {
|
pub fn needs_update(&self) {
|
||||||
(self.inner.callback)();
|
if !self.inner.update_scheuled.get() {
|
||||||
|
self.inner.update_scheuled.set(true);
|
||||||
|
(self.inner.callback)();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set(&self, new_val: T) {
|
pub fn set(&self, new_val: T) {
|
||||||
@@ -143,7 +146,7 @@ impl<'a, T: 'static> std::ops::Deref for UseState<'a, T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use std::ops::{Add, AddAssign};
|
use std::ops::{Add, AddAssign, Sub, SubAssign};
|
||||||
impl<'a, T: Copy + Add<T, Output = T>> Add<T> for UseState<'a, T> {
|
impl<'a, T: Copy + Add<T, Output = T>> Add<T> for UseState<'a, T> {
|
||||||
type Output = T;
|
type Output = T;
|
||||||
|
|
||||||
@@ -156,6 +159,18 @@ impl<'a, T: Copy + Add<T, Output = T>> AddAssign<T> for UseState<'a, T> {
|
|||||||
self.set(self.inner.current_val.add(rhs));
|
self.set(self.inner.current_val.add(rhs));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl<'a, T: Copy + Sub<T, Output = T>> Sub<T> for UseState<'a, T> {
|
||||||
|
type Output = T;
|
||||||
|
|
||||||
|
fn sub(self, rhs: T) -> Self::Output {
|
||||||
|
self.inner.current_val.sub(rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a, T: Copy + Sub<T, Output = T>> SubAssign<T> for UseState<'a, T> {
|
||||||
|
fn sub_assign(&mut self, rhs: T) {
|
||||||
|
self.set(self.inner.current_val.sub(rhs));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// enable displaty for the handle
|
// enable displaty for the handle
|
||||||
impl<'a, T: 'static + Display> std::fmt::Display for UseState<'a, T> {
|
impl<'a, T: 'static + Display> std::fmt::Display for UseState<'a, T> {
|
||||||
@@ -165,6 +180,7 @@ impl<'a, T: 'static + Display> std::fmt::Display for UseState<'a, T> {
|
|||||||
}
|
}
|
||||||
struct UseStateInner<T: 'static> {
|
struct UseStateInner<T: 'static> {
|
||||||
current_val: T,
|
current_val: T,
|
||||||
|
update_scheuled: Cell<bool>,
|
||||||
callback: Rc<dyn Fn()>,
|
callback: Rc<dyn Fn()>,
|
||||||
wip: RefCell<Option<T>>,
|
wip: RefCell<Option<T>>,
|
||||||
}
|
}
|
||||||
@@ -213,9 +229,10 @@ pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T, P>(
|
|||||||
current_val: initial_state_fn(),
|
current_val: initial_state_fn(),
|
||||||
callback: cx.schedule_update(),
|
callback: cx.schedule_update(),
|
||||||
wip: RefCell::new(None),
|
wip: RefCell::new(None),
|
||||||
|
update_scheuled: Cell::new(false),
|
||||||
},
|
},
|
||||||
move |hook| {
|
move |hook| {
|
||||||
log::debug!("addr of hook: {:#?}", hook as *const _);
|
hook.update_scheuled.set(false);
|
||||||
let mut new_val = hook.wip.borrow_mut();
|
let mut new_val = hook.wip.borrow_mut();
|
||||||
if new_val.is_some() {
|
if new_val.is_some() {
|
||||||
hook.current_val = new_val.take().unwrap();
|
hook.current_val = new_val.take().unwrap();
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
/// network or through FFI boundaries.
|
/// network or through FFI boundaries.
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
pub enum DomEdits<'bump> {
|
pub enum DomEdit<'bump> {
|
||||||
PushRoot {
|
PushRoot {
|
||||||
root: u64,
|
root: u64,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
fn main() {
|
||||||
|
dioxus_webview::launch(App, |f| f.with_focus().with_maximized(true)).expect("Failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
static App: FC<()> = |cx| {
|
||||||
|
//
|
||||||
|
cx.render(rsx!(
|
||||||
|
div {
|
||||||
|
"hello world!"
|
||||||
|
}
|
||||||
|
))
|
||||||
|
};
|
||||||
|
|
||||||
|
use dioxus_core as dioxus;
|
||||||
|
use dioxus_core::prelude::*;
|
||||||
|
mod dioxus_elements {
|
||||||
|
use super::*;
|
||||||
|
pub struct div;
|
||||||
|
impl DioxusElement for div {
|
||||||
|
const TAG_NAME: &'static str = "div";
|
||||||
|
const NAME_SPACE: Option<&'static str> = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
class OPTABLE {
|
||||||
|
PushRoot(self, edit) {
|
||||||
|
const id = edit.root;
|
||||||
|
const node = self.nodes[id];
|
||||||
|
self.stack.push(node);
|
||||||
|
}
|
||||||
|
AppendChild(self, edit) {
|
||||||
|
// todo: prevent merging of text nodes
|
||||||
|
const node = self.pop();
|
||||||
|
self.top().appendChild(node);
|
||||||
|
}
|
||||||
|
ReplaceWith(self, edit) {
|
||||||
|
const newNode = self.pop();
|
||||||
|
const oldNode = self.pop();
|
||||||
|
oldNode.replaceWith(newNode);
|
||||||
|
self.stack.push(newNode);
|
||||||
|
}
|
||||||
|
Remove(self, edit) {
|
||||||
|
const node = self.stack.pop();
|
||||||
|
node.remove();
|
||||||
|
}
|
||||||
|
RemoveAllChildren(self, edit) {
|
||||||
|
// todo - we never actually call this one
|
||||||
|
}
|
||||||
|
CreateTextNode(self, edit) {
|
||||||
|
self.stack.push(document.createTextNode(edit.text));
|
||||||
|
}
|
||||||
|
CreateElement(self, edit) {
|
||||||
|
const tagName = edit.tag;
|
||||||
|
console.log(`creating element! ${edit}`);
|
||||||
|
self.stack.push(document.createElement(tagName));
|
||||||
|
}
|
||||||
|
CreateElementNs(self, edit) {
|
||||||
|
self.stack.push(document.createElementNS(edit.ns, edit.tag));
|
||||||
|
}
|
||||||
|
CreatePlaceholder(self, edit) {
|
||||||
|
self.stack.push(document.createElement("pre"));
|
||||||
|
}
|
||||||
|
NewEventListener(self, edit) {
|
||||||
|
// todo
|
||||||
|
}
|
||||||
|
RemoveEventListener(self, edit) {
|
||||||
|
// todo
|
||||||
|
}
|
||||||
|
SetText(self, edit) {
|
||||||
|
self.top().textContent = edit.text;
|
||||||
|
}
|
||||||
|
SetAttribute(self, edit) {
|
||||||
|
const name = edit.field;
|
||||||
|
const value = edit.value;
|
||||||
|
const node = self.top(self.stack);
|
||||||
|
node.setAttribute(name, value);
|
||||||
|
|
||||||
|
// Some attributes are "volatile" and don't work through `setAttribute`.
|
||||||
|
if ((name === "value", self)) {
|
||||||
|
node.value = value;
|
||||||
|
}
|
||||||
|
if ((name === "checked", self)) {
|
||||||
|
node.checked = true;
|
||||||
|
}
|
||||||
|
if ((name === "selected", self)) {
|
||||||
|
node.selected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RemoveAttribute(self, edit) {
|
||||||
|
const name = edit.field;
|
||||||
|
const node = self.top(self.stack);
|
||||||
|
node.removeAttribute(name);
|
||||||
|
|
||||||
|
// Some attributes are "volatile" and don't work through `removeAttribute`.
|
||||||
|
if ((name === "value", self)) {
|
||||||
|
node.value = null;
|
||||||
|
}
|
||||||
|
if ((name === "checked", self)) {
|
||||||
|
node.checked = false;
|
||||||
|
}
|
||||||
|
if ((name === "selected", self)) {
|
||||||
|
node.selected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// const op_table = new OPTABLE();
|
||||||
|
// const interpreter = new Interpreter(window.document.body);
|
||||||
|
|
||||||
|
// function EditListReceived(rawEditList) {
|
||||||
|
// let editList = JSON.parse(rawEditList);
|
||||||
|
// console.warn("hnelllo");
|
||||||
|
// editList.forEach(function (edit, index) {
|
||||||
|
// console.log(edit);
|
||||||
|
// op_table[edit.type](interpreter, edit);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// async function rinalize() {
|
||||||
|
// console.log("initialize...");
|
||||||
|
// let edits = await rpc.call("initiate");
|
||||||
|
// console.error(edits);
|
||||||
|
// }
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
class OPTABLE {
|
||||||
|
PushRoot(self, edit) {
|
||||||
|
const id = edit.root;
|
||||||
|
const node = self.nodes[id];
|
||||||
|
self.stack.push(node);
|
||||||
|
}
|
||||||
|
AppendChild(self, edit) {
|
||||||
|
// todo: prevent merging of text nodes
|
||||||
|
const node = self.pop();
|
||||||
|
self.top().appendChild(node);
|
||||||
|
}
|
||||||
|
ReplaceWith(self, edit) {
|
||||||
|
const newNode = self.pop();
|
||||||
|
const oldNode = self.pop();
|
||||||
|
oldNode.replaceWith(newNode);
|
||||||
|
self.stack.push(newNode);
|
||||||
|
}
|
||||||
|
Remove(self, edit) {
|
||||||
|
const node = self.stack.pop();
|
||||||
|
node.remove();
|
||||||
|
}
|
||||||
|
RemoveAllChildren(self, edit) {
|
||||||
|
// todo - we never actually call this one
|
||||||
|
}
|
||||||
|
CreateTextNode(self, edit) {
|
||||||
|
self.stack.push(document.createTextNode(edit.text));
|
||||||
|
}
|
||||||
|
CreateElement(self, edit) {
|
||||||
|
const tagName = edit.tag;
|
||||||
|
console.log(`creating element! ${edit}`);
|
||||||
|
self.stack.push(document.createElement(tagName));
|
||||||
|
}
|
||||||
|
CreateElementNs(self, edit) {
|
||||||
|
self.stack.push(document.createElementNS(edit.ns, edit.tag));
|
||||||
|
}
|
||||||
|
CreatePlaceholder(self, edit) {
|
||||||
|
self.stack.push(document.createElement("pre"));
|
||||||
|
}
|
||||||
|
NewEventListener(self, edit) {
|
||||||
|
// todo
|
||||||
|
}
|
||||||
|
RemoveEventListener(self, edit) {
|
||||||
|
// todo
|
||||||
|
}
|
||||||
|
SetText(self, edit) {
|
||||||
|
self.top().textContent = edit.text;
|
||||||
|
}
|
||||||
|
SetAttribute(self, edit) {
|
||||||
|
const name = edit.field;
|
||||||
|
const value = edit.value;
|
||||||
|
const node = self.top(self.stack);
|
||||||
|
node.setAttribute(name, value);
|
||||||
|
|
||||||
|
// Some attributes are "volatile" and don't work through `setAttribute`.
|
||||||
|
if ((name === "value", self)) {
|
||||||
|
node.value = value;
|
||||||
|
}
|
||||||
|
if ((name === "checked", self)) {
|
||||||
|
node.checked = true;
|
||||||
|
}
|
||||||
|
if ((name === "selected", self)) {
|
||||||
|
node.selected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RemoveAttribute(self, edit) {
|
||||||
|
const name = edit.field;
|
||||||
|
const node = self.top(self.stack);
|
||||||
|
node.removeAttribute(name);
|
||||||
|
|
||||||
|
// Some attributes are "volatile" and don't work through `removeAttribute`.
|
||||||
|
if ((name === "value", self)) {
|
||||||
|
node.value = null;
|
||||||
|
}
|
||||||
|
if ((name === "checked", self)) {
|
||||||
|
node.checked = false;
|
||||||
|
}
|
||||||
|
if ((name === "selected", self)) {
|
||||||
|
node.selected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,13 +4,13 @@ use dioxus_core as dioxus;
|
|||||||
use dioxus_core::prelude::*;
|
use dioxus_core::prelude::*;
|
||||||
use dioxus_core::{
|
use dioxus_core::{
|
||||||
diff::RealDom,
|
diff::RealDom,
|
||||||
serialize::DomEdits,
|
serialize::DomEdit,
|
||||||
virtual_dom::{RealDomNode, VirtualDom},
|
virtual_dom::{RealDomNode, VirtualDom},
|
||||||
};
|
};
|
||||||
use DomEdits::*;
|
use DomEdit::*;
|
||||||
|
|
||||||
pub struct WebviewDom<'bump> {
|
pub struct WebviewDom<'bump> {
|
||||||
pub edits: Vec<DomEdits<'bump>>,
|
pub edits: Vec<DomEdit<'bump>>,
|
||||||
pub node_counter: u64,
|
pub node_counter: u64,
|
||||||
}
|
}
|
||||||
impl WebviewDom<'_> {
|
impl WebviewDom<'_> {
|
||||||
|
|||||||
@@ -1,20 +1,22 @@
|
|||||||
<!-- a js-only interpreter for the dioxus patch stream :) -->
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
|
<!-- <link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet" /> -->
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet" />
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div></div>
|
<div></div>
|
||||||
</body>
|
</body>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
class Interpreter {
|
class Interpreter {
|
||||||
constructor(root) {
|
constructor(root) {
|
||||||
this.stack = [root];
|
this.stack = [root];
|
||||||
|
this.listeners = {};
|
||||||
this.lastNodeWasText = false;
|
this.lastNodeWasText = false;
|
||||||
this.nodes = {
|
this.nodes = {
|
||||||
0: root
|
0: root
|
||||||
@@ -30,6 +32,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class OPTABLE {
|
class OPTABLE {
|
||||||
PushRoot(self, edit) {
|
PushRoot(self, edit) {
|
||||||
const id = edit.root;
|
const id = edit.root;
|
||||||
@@ -37,7 +40,6 @@
|
|||||||
self.stack.push(node);
|
self.stack.push(node);
|
||||||
}
|
}
|
||||||
AppendChild(self, edit) {
|
AppendChild(self, edit) {
|
||||||
// todo: prevent merging of text nodes
|
|
||||||
const node = self.pop();
|
const node = self.pop();
|
||||||
self.top().appendChild(node);
|
self.top().appendChild(node);
|
||||||
}
|
}
|
||||||
@@ -52,14 +54,13 @@
|
|||||||
node.remove();
|
node.remove();
|
||||||
}
|
}
|
||||||
RemoveAllChildren(self, edit) {
|
RemoveAllChildren(self, edit) {
|
||||||
// todo - we never actually call this one
|
|
||||||
}
|
}
|
||||||
CreateTextNode(self, edit) {
|
CreateTextNode(self, edit) {
|
||||||
self.stack.push(document.createTextNode(edit.text));
|
self.stack.push(document.createTextNode(edit.text));
|
||||||
}
|
}
|
||||||
CreateElement(self, edit) {
|
CreateElement(self, edit) {
|
||||||
const tagName = edit.tag;
|
const tagName = edit.tag;
|
||||||
console.log(`creating element! ${edit}`);
|
console.log(`creating element: `, edit);
|
||||||
self.stack.push(document.createElement(tagName));
|
self.stack.push(document.createElement(tagName));
|
||||||
}
|
}
|
||||||
CreateElementNs(self, edit) {
|
CreateElementNs(self, edit) {
|
||||||
@@ -69,10 +70,8 @@
|
|||||||
self.stack.push(document.createElement("pre"));
|
self.stack.push(document.createElement("pre"));
|
||||||
}
|
}
|
||||||
NewEventListener(self, edit) {
|
NewEventListener(self, edit) {
|
||||||
// todo
|
|
||||||
}
|
}
|
||||||
RemoveEventListener(self, edit) {
|
RemoveEventListener(self, edit) {
|
||||||
// todo
|
|
||||||
}
|
}
|
||||||
SetText(self, edit) {
|
SetText(self, edit) {
|
||||||
self.top().textContent = edit.text;
|
self.top().textContent = edit.text;
|
||||||
@@ -83,7 +82,6 @@
|
|||||||
const node = self.top(self.stack);
|
const node = self.top(self.stack);
|
||||||
node.setAttribute(name, value);
|
node.setAttribute(name, value);
|
||||||
|
|
||||||
// Some attributes are "volatile" and don't work through `setAttribute`.
|
|
||||||
if ((name === "value", self)) {
|
if ((name === "value", self)) {
|
||||||
node.value = value;
|
node.value = value;
|
||||||
}
|
}
|
||||||
@@ -99,7 +97,6 @@
|
|||||||
const node = self.top(self.stack);
|
const node = self.top(self.stack);
|
||||||
node.removeAttribute(name);
|
node.removeAttribute(name);
|
||||||
|
|
||||||
// Some attributes are "volatile" and don't work through `removeAttribute`.
|
|
||||||
if ((name === "value", self)) {
|
if ((name === "value", self)) {
|
||||||
node.value = null;
|
node.value = null;
|
||||||
}
|
}
|
||||||
@@ -110,11 +107,9 @@
|
|||||||
node.selected = false;
|
node.selected = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const op_table = new OPTABLE();
|
|
||||||
const interpreter = new Interpreter(window.document.body);
|
|
||||||
|
|
||||||
function EditListReceived(rawEditList) {
|
function EditListReceived(rawEditList) {
|
||||||
let editList = JSON.parse(rawEditList);
|
let editList = JSON.parse(rawEditList);
|
||||||
@@ -125,7 +120,20 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
external.invoke("initiate");
|
const op_table = new OPTABLE();
|
||||||
|
const interpreter = new Interpreter(window.document.body);
|
||||||
|
|
||||||
|
async function initialize() {
|
||||||
|
const reply = await rpc.call('initiate');
|
||||||
|
console.log(reply);
|
||||||
|
reply.forEach(function (edit, index) {
|
||||||
|
console.log(edit);
|
||||||
|
op_table[edit.type](interpreter, edit);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log("initializing...");
|
||||||
|
initialize();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
+156
-61
@@ -1,24 +1,28 @@
|
|||||||
|
use std::borrow::BorrowMut;
|
||||||
use std::sync::mpsc::channel;
|
use std::sync::mpsc::channel;
|
||||||
use std::sync::Arc;
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
use dioxus_core::prelude::*;
|
|
||||||
use dioxus_core::virtual_dom::VirtualDom;
|
use dioxus_core::virtual_dom::VirtualDom;
|
||||||
use web_view::{escape, Handle};
|
use dioxus_core::{prelude::*, serialize::DomEdit};
|
||||||
use web_view::{WVResult, WebView, WebViewBuilder};
|
use wry::{
|
||||||
|
application::window::{Window, WindowBuilder},
|
||||||
|
webview::{RpcRequest, RpcResponse},
|
||||||
|
};
|
||||||
|
|
||||||
mod dom;
|
mod dom;
|
||||||
|
|
||||||
static HTML_CONTENT: &'static str = include_str!("./index.html");
|
static HTML_CONTENT: &'static str = include_str!("./index.html");
|
||||||
|
|
||||||
pub fn launch(
|
pub fn launch(
|
||||||
root: FC<()>,
|
root: FC<()>,
|
||||||
builder: impl FnOnce(DioxusWebviewBuilder) -> DioxusWebviewBuilder,
|
builder: impl FnOnce(WindowBuilder) -> WindowBuilder,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
launch_with_props(root, (), builder)
|
launch_with_props(root, (), builder)
|
||||||
}
|
}
|
||||||
pub fn launch_with_props<P: Properties + 'static>(
|
pub fn launch_with_props<P: Properties + 'static>(
|
||||||
root: FC<P>,
|
root: FC<P>,
|
||||||
props: P,
|
props: P,
|
||||||
builder: impl FnOnce(DioxusWebviewBuilder) -> DioxusWebviewBuilder,
|
builder: impl FnOnce(WindowBuilder) -> WindowBuilder,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
WebviewRenderer::run(root, props, builder)
|
WebviewRenderer::run(root, props, builder)
|
||||||
}
|
}
|
||||||
@@ -29,85 +33,125 @@ pub struct WebviewRenderer<T> {
|
|||||||
/// The root component used to render the Webview
|
/// The root component used to render the Webview
|
||||||
root: FC<T>,
|
root: FC<T>,
|
||||||
}
|
}
|
||||||
|
enum RpcEvent<'a> {
|
||||||
enum InnerEvent {
|
Initialize {
|
||||||
Initiate(Handle<()>),
|
//
|
||||||
|
edits: Vec<DomEdit<'a>>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Properties + 'static> WebviewRenderer<T> {
|
impl<T: Properties + 'static> WebviewRenderer<T> {
|
||||||
pub fn run(
|
pub fn run(
|
||||||
root: FC<T>,
|
root: FC<T>,
|
||||||
props: T,
|
props: T,
|
||||||
user_builder: impl FnOnce(DioxusWebviewBuilder) -> DioxusWebviewBuilder,
|
user_builder: impl FnOnce(WindowBuilder) -> WindowBuilder,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let (sender, receiver) = channel::<InnerEvent>();
|
use wry::{
|
||||||
|
application::{
|
||||||
|
event::{Event, StartCause, WindowEvent},
|
||||||
|
event_loop::{ControlFlow, EventLoop},
|
||||||
|
window::WindowBuilder,
|
||||||
|
},
|
||||||
|
webview::WebViewBuilder,
|
||||||
|
};
|
||||||
|
|
||||||
let DioxusWebviewBuilder {
|
let event_loop = EventLoop::new();
|
||||||
title,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
resizable,
|
|
||||||
debug,
|
|
||||||
frameless,
|
|
||||||
visible,
|
|
||||||
min_width,
|
|
||||||
min_height,
|
|
||||||
} = user_builder(DioxusWebviewBuilder::new());
|
|
||||||
|
|
||||||
let mut view = web_view::builder()
|
let window = user_builder(WindowBuilder::new()).build(&event_loop)?;
|
||||||
.invoke_handler(|view, arg| {
|
|
||||||
let handle = view.handle();
|
|
||||||
sender
|
|
||||||
.send(InnerEvent::Initiate(handle))
|
|
||||||
.expect("should not fail");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
.content(web_view::Content::Html(HTML_CONTENT))
|
|
||||||
.user_data(())
|
|
||||||
.title(title)
|
|
||||||
.size(width, height)
|
|
||||||
.resizable(resizable)
|
|
||||||
.debug(debug)
|
|
||||||
.frameless(frameless)
|
|
||||||
.visible(visible)
|
|
||||||
.min_size(min_width, min_height)
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut vdom = VirtualDom::new_with_props(root, props);
|
let mut vdom = VirtualDom::new_with_props(root, props);
|
||||||
let mut real_dom = dom::WebviewDom::new();
|
let mut real_dom = dom::WebviewDom::new();
|
||||||
vdom.rebuild(&mut real_dom)?;
|
vdom.rebuild(&mut real_dom)?;
|
||||||
|
|
||||||
let ref_edits = Arc::new(serde_json::to_string(&real_dom.edits)?);
|
let edits = Arc::new(RwLock::new(Some(serde_json::to_value(real_dom.edits)?)));
|
||||||
|
|
||||||
loop {
|
// let ref_edits = Arc::new(serde_json::to_string(&real_dom.edits)?);
|
||||||
view.step()
|
|
||||||
.expect("should not fail")
|
|
||||||
.expect("should not fail");
|
|
||||||
std::thread::sleep(std::time::Duration::from_millis(15));
|
|
||||||
|
|
||||||
if let Ok(event) = receiver.try_recv() {
|
let handler = move |window: &Window, mut req: RpcRequest| {
|
||||||
if let InnerEvent::Initiate(handle) = event {
|
//
|
||||||
let editlist = ref_edits.clone();
|
let d = edits.clone();
|
||||||
handle
|
match req.method.as_str() {
|
||||||
.dispatch(move |view| {
|
"initiate" => {
|
||||||
let escaped = escape(&editlist);
|
let mut ed = d.write().unwrap();
|
||||||
view.eval(&format!("EditListReceived({});", escaped))
|
let edits = match ed.as_mut() {
|
||||||
})
|
Some(ed) => Some(ed.take()),
|
||||||
.expect("Dispatch failed");
|
None => None,
|
||||||
|
};
|
||||||
|
Some(RpcResponse::new_result(req.id.take(), edits))
|
||||||
|
}
|
||||||
|
_ => todo!("this message failed"),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let webview = WebViewBuilder::new(window)?
|
||||||
|
.with_url(&format!("data:text/html,{}", HTML_CONTENT))?
|
||||||
|
.with_rpc_handler(handler)
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
event_loop.run(move |event, _, control_flow| {
|
||||||
|
*control_flow = ControlFlow::Wait;
|
||||||
|
|
||||||
|
match event {
|
||||||
|
Event::WindowEvent { event, .. } => {
|
||||||
|
//
|
||||||
|
match event {
|
||||||
|
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// let _ = webview.resize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
|
// let mut view = web_view::builder()
|
||||||
|
// .invoke_handler(|view, arg| {
|
||||||
|
// let handle = view.handle();
|
||||||
|
// sender
|
||||||
|
// .send(InnerEvent::Initiate(handle))
|
||||||
|
// .expect("should not fail");
|
||||||
|
|
||||||
|
// Ok(())
|
||||||
|
// })
|
||||||
|
// .content(web_view::Content::Html(HTML_CONTENT))
|
||||||
|
// .user_data(())
|
||||||
|
// .title(title)
|
||||||
|
// .size(width, height)
|
||||||
|
// .resizable(resizable)
|
||||||
|
// .debug(debug)
|
||||||
|
// .frameless(frameless)
|
||||||
|
// .visible(visible)
|
||||||
|
// .min_size(min_width, min_height)
|
||||||
|
// .build()
|
||||||
|
// .unwrap();
|
||||||
|
// loop {
|
||||||
|
// view.step()
|
||||||
|
// .expect("should not fail")
|
||||||
|
// .expect("should not fail");
|
||||||
|
// std::thread::sleep(std::time::Duration::from_millis(15));
|
||||||
|
|
||||||
|
// if let Ok(event) = receiver.try_recv() {
|
||||||
|
// if let InnerEvent::Initiate(handle) = event {
|
||||||
|
// let editlist = ref_edits.clone();
|
||||||
|
// handle
|
||||||
|
// .dispatch(move |view| {
|
||||||
|
// let escaped = escape(&editlist);
|
||||||
|
// view.eval(&format!("EditListReceived({});", escaped))
|
||||||
|
// })
|
||||||
|
// .expect("Dispatch failed");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new text-renderer instance from a functional component root.
|
/// Create a new text-renderer instance from a functional component root.
|
||||||
/// Automatically progresses the creation of the VNode tree to completion.
|
/// Automatically progresses the creation of the VNode tree to completion.
|
||||||
///
|
///
|
||||||
/// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom`
|
/// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom`
|
||||||
pub fn new(root: FC<T>, builder: impl FnOnce() -> WVResult<WebView<'static, ()>>) -> Self {
|
// pub fn new(root: FC<T>, builder: impl FnOnce() -> WVResult<WebView<'static, ()>>) -> Self {
|
||||||
Self { root }
|
// Self { root }
|
||||||
}
|
// }
|
||||||
|
|
||||||
/// Create a new text renderer from an existing Virtual DOM.
|
/// Create a new text renderer from an existing Virtual DOM.
|
||||||
/// This will progress the existing VDom's events to completion.
|
/// This will progress the existing VDom's events to completion.
|
||||||
@@ -126,6 +170,57 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct MessageParameters {
|
||||||
|
message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn HANDLER(window: &Window, mut req: RpcRequest) -> Option<RpcResponse> {
|
||||||
|
use wry::{
|
||||||
|
application::{
|
||||||
|
event::{Event, WindowEvent},
|
||||||
|
event_loop::{ControlFlow, EventLoop},
|
||||||
|
window::{Fullscreen, Window, WindowBuilder},
|
||||||
|
},
|
||||||
|
webview::{RpcRequest, RpcResponse, WebViewBuilder},
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut response = None;
|
||||||
|
if &req.method == "fullscreen" {
|
||||||
|
if let Some(params) = req.params.take() {
|
||||||
|
if let Ok(mut args) = serde_json::from_value::<Vec<bool>>(params) {
|
||||||
|
if !args.is_empty() {
|
||||||
|
if args.swap_remove(0) {
|
||||||
|
window.set_fullscreen(Some(Fullscreen::Borderless(None)));
|
||||||
|
} else {
|
||||||
|
window.set_fullscreen(None);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
response = Some(RpcResponse::new_result(req.id.take(), None));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if &req.method == "send-parameters" {
|
||||||
|
if let Some(params) = req.params.take() {
|
||||||
|
if let Ok(mut args) = serde_json::from_value::<Vec<MessageParameters>>(params) {
|
||||||
|
let result = if !args.is_empty() {
|
||||||
|
let msg = args.swap_remove(0);
|
||||||
|
Some(Value::String(format!("Hello, {}!", msg.message)))
|
||||||
|
} else {
|
||||||
|
// NOTE: in the real-world we should send an error response here!
|
||||||
|
None
|
||||||
|
};
|
||||||
|
// Must always send a response as this is a `call()`
|
||||||
|
response = Some(RpcResponse::new_result(req.id.take(), result));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response
|
||||||
|
}
|
||||||
|
|
||||||
pub struct DioxusWebviewBuilder<'a> {
|
pub struct DioxusWebviewBuilder<'a> {
|
||||||
pub(crate) title: &'a str,
|
pub(crate) title: &'a str,
|
||||||
pub(crate) width: i32,
|
pub(crate) width: i32,
|
||||||
|
|||||||
Reference in new issue
Block a user