Merge branch 'upstream-master' into fix-hot-reloading
This commit is contained in:
commit
14dd568987
92 files changed
+3469
-2468
No files matched your search
@@ -1,10 +1,12 @@
|
||||
use crate::desktop_context::{DesktopContext, UserWindowEvent};
|
||||
use crate::events::{decode_event, EventMessage};
|
||||
use crate::events::IpcMessage;
|
||||
use dioxus_core::*;
|
||||
use dioxus_html::{Html, HtmlEvent};
|
||||
use futures_channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
|
||||
use futures_util::StreamExt;
|
||||
#[cfg(target_os = "ios")]
|
||||
use objc::runtime::Object;
|
||||
use serde_json::Value;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::Arc,
|
||||
@@ -19,11 +21,12 @@ use wry::{
|
||||
|
||||
pub(super) struct DesktopController {
|
||||
pub(super) webviews: HashMap<WindowId, WebView>,
|
||||
pub(super) eval_sender: tokio::sync::mpsc::UnboundedSender<Value>,
|
||||
pub(super) pending_edits: Arc<Mutex<Vec<String>>>,
|
||||
pub(super) quit_app_on_close: bool,
|
||||
pub(super) is_ready: Arc<AtomicBool>,
|
||||
pub(super) proxy: EventLoopProxy<UserWindowEvent>,
|
||||
pub(super) event_tx: UnboundedSender<serde_json::Value>,
|
||||
pub(super) event_tx: UnboundedSender<Html>,
|
||||
#[cfg(debug_assertions)]
|
||||
pub(super) templates_tx: UnboundedSender<Template<'static>>,
|
||||
|
||||
@@ -40,12 +43,13 @@ impl DesktopController {
|
||||
proxy: EventLoopProxy<UserWindowEvent>,
|
||||
) -> Self {
|
||||
let edit_queue = Arc::new(Mutex::new(Vec::new()));
|
||||
let (event_tx, mut event_rx) = unbounded();
|
||||
let (event_tx, mut event_rx) = unbounded::<HtmlEvent>();
|
||||
let (templates_tx, mut templates_rx) = unbounded();
|
||||
let proxy2 = proxy.clone();
|
||||
|
||||
let pending_edits = edit_queue.clone();
|
||||
let desktop_context_proxy = proxy.clone();
|
||||
let (eval_sender, eval_reciever) = tokio::sync::mpsc::unbounded_channel::<Value>();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
// We create the runtime as multithreaded, so you can still "tokio::spawn" onto multiple threads
|
||||
@@ -57,7 +61,7 @@ impl DesktopController {
|
||||
|
||||
runtime.block_on(async move {
|
||||
let mut dom = VirtualDom::new_with_props(root, props)
|
||||
.with_root_context(DesktopContext::new(desktop_context_proxy));
|
||||
.with_root_context(DesktopContext::new(desktop_context_proxy, eval_reciever));
|
||||
{
|
||||
let edits = dom.rebuild();
|
||||
let mut queue = edit_queue.lock().unwrap();
|
||||
@@ -80,14 +84,8 @@ impl DesktopController {
|
||||
dom.replace_template(template);
|
||||
}
|
||||
_ = dom.wait_for_work() => {}
|
||||
Some(json_value) = event_rx.next() => {
|
||||
if let Ok(value) = serde_json::from_value::<EventMessage>(json_value) {
|
||||
let name = value.event.clone();
|
||||
let el_id = ElementId(value.mounted_dom_id);
|
||||
if let Some(evt) = decode_event(value) {
|
||||
dom.handle_event(&name, evt, el_id, dioxus_html::events::event_bubbles(&name));
|
||||
}
|
||||
}
|
||||
Some(value) = event_rx.next() => {
|
||||
dom.handle_event(&value.name, value.data.into_any(), value.element, dioxus_html::events::event_bubbles(&value.name));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +101,7 @@ impl DesktopController {
|
||||
|
||||
Self {
|
||||
pending_edits,
|
||||
eval_sender,
|
||||
webviews: HashMap::new(),
|
||||
is_ready: Arc::new(AtomicBool::new(false)),
|
||||
quit_app_on_close: true,
|
||||
|
||||
@@ -2,6 +2,11 @@ use std::rc::Rc;
|
||||
|
||||
use crate::controller::DesktopController;
|
||||
use dioxus_core::ScopeState;
|
||||
use serde::de::Error;
|
||||
use serde_json::Value;
|
||||
use std::future::Future;
|
||||
use std::future::IntoFuture;
|
||||
use std::pin::Pin;
|
||||
use wry::application::event_loop::ControlFlow;
|
||||
use wry::application::event_loop::EventLoopProxy;
|
||||
#[cfg(target_os = "ios")]
|
||||
@@ -19,16 +24,6 @@ pub fn use_window(cx: &ScopeState) -> &DesktopContext {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Get a closure that executes any JavaScript in the WebView context.
|
||||
pub fn use_eval(cx: &ScopeState) -> &Rc<dyn Fn(String)> {
|
||||
let desktop = use_window(cx);
|
||||
|
||||
&*cx.use_hook(|| {
|
||||
let desktop = desktop.clone();
|
||||
Rc::new(move |script| desktop.eval(script))
|
||||
} as Rc<dyn Fn(String)>)
|
||||
}
|
||||
|
||||
/// An imperative interface to the current window.
|
||||
///
|
||||
/// To get a handle to the current window, use the [`use_window`] hook.
|
||||
@@ -45,11 +40,18 @@ pub fn use_eval(cx: &ScopeState) -> &Rc<dyn Fn(String)> {
|
||||
pub struct DesktopContext {
|
||||
/// The wry/tao proxy to the current window
|
||||
pub proxy: ProxyType,
|
||||
pub(super) eval_reciever: Rc<tokio::sync::Mutex<tokio::sync::mpsc::UnboundedReceiver<Value>>>,
|
||||
}
|
||||
|
||||
impl DesktopContext {
|
||||
pub(crate) fn new(proxy: ProxyType) -> Self {
|
||||
Self { proxy }
|
||||
pub(crate) fn new(
|
||||
proxy: ProxyType,
|
||||
eval_reciever: tokio::sync::mpsc::UnboundedReceiver<Value>,
|
||||
) -> Self {
|
||||
Self {
|
||||
proxy,
|
||||
eval_reciever: Rc::new(tokio::sync::Mutex::new(eval_reciever)),
|
||||
}
|
||||
}
|
||||
|
||||
/// trigger the drag-window event
|
||||
@@ -236,6 +238,18 @@ impl DesktopController {
|
||||
Resizable(state) => window.set_resizable(state),
|
||||
AlwaysOnTop(state) => window.set_always_on_top(state),
|
||||
|
||||
Eval(code) => {
|
||||
let script = format!(
|
||||
r#"window.ipc.postMessage(JSON.stringify({{"method":"eval_result", params: (function(){{
|
||||
{}
|
||||
}})()}}));"#,
|
||||
code
|
||||
);
|
||||
if let Err(e) = webview.evaluate_script(&script) {
|
||||
// we can't panic this error.
|
||||
log::warn!("Eval script error: {e}");
|
||||
}
|
||||
}
|
||||
CursorVisible(state) => window.set_cursor_visible(state),
|
||||
CursorGrab(state) => {
|
||||
let _ = window.set_cursor_grab(state);
|
||||
@@ -259,13 +273,6 @@ impl DesktopController {
|
||||
log::warn!("Devtools are disabled in release builds");
|
||||
}
|
||||
|
||||
Eval(code) => {
|
||||
if let Err(e) = webview.evaluate_script(code.as_str()) {
|
||||
// we can't panic this error.
|
||||
log::warn!("Eval script error: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
PushView(view) => unsafe {
|
||||
use objc::runtime::Object;
|
||||
@@ -295,6 +302,39 @@ impl DesktopController {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a closure that executes any JavaScript in the WebView context.
|
||||
pub fn use_eval<S: std::string::ToString>(cx: &ScopeState) -> &dyn Fn(S) -> EvalResult {
|
||||
let desktop = use_window(cx).clone();
|
||||
cx.use_hook(|| {
|
||||
move |script| {
|
||||
desktop.eval(script);
|
||||
let recv = desktop.eval_reciever.clone();
|
||||
EvalResult { reciever: recv }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// A future that resolves to the result of a JavaScript evaluation.
|
||||
pub struct EvalResult {
|
||||
reciever: Rc<tokio::sync::Mutex<tokio::sync::mpsc::UnboundedReceiver<serde_json::Value>>>,
|
||||
}
|
||||
|
||||
impl IntoFuture for EvalResult {
|
||||
type Output = Result<serde_json::Value, serde_json::Error>;
|
||||
|
||||
type IntoFuture = Pin<Box<dyn Future<Output = Result<serde_json::Value, serde_json::Error>>>>;
|
||||
|
||||
fn into_future(self) -> Self::IntoFuture {
|
||||
Box::pin(async move {
|
||||
let mut reciever = self.reciever.lock().await;
|
||||
match reciever.recv().await {
|
||||
Some(result) => Ok(result),
|
||||
None => Err(serde_json::Error::custom("No result returned")),
|
||||
}
|
||||
}) as Pin<Box<dyn Future<Output = Result<serde_json::Value, serde_json::Error>>>>
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
fn is_main_thread() -> bool {
|
||||
use objc::runtime::{Class, BOOL, NO};
|
||||
|
||||
@@ -6,7 +6,7 @@ use serde_json::from_value;
|
||||
use std::any::Any;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub(crate) struct IpcMessage {
|
||||
method: String,
|
||||
params: serde_json::Value,
|
||||
@@ -31,61 +31,3 @@ pub(crate) fn parse_ipc_message(payload: &str) -> Option<IpcMessage> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! match_data {
|
||||
(
|
||||
$m:ident;
|
||||
$name:ident;
|
||||
$(
|
||||
$tip:ty => $($mname:literal)|* ;
|
||||
)*
|
||||
) => {
|
||||
match $name {
|
||||
$( $($mname)|* => {
|
||||
let val: $tip = from_value::<$tip>($m).ok()?;
|
||||
Rc::new(val) as Rc<dyn Any>
|
||||
})*
|
||||
_ => return None,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EventMessage {
|
||||
pub contents: serde_json::Value,
|
||||
pub event: String,
|
||||
pub mounted_dom_id: usize,
|
||||
}
|
||||
|
||||
pub fn decode_event(value: EventMessage) -> Option<Rc<dyn Any>> {
|
||||
let val = value.contents;
|
||||
let name = value.event.as_str();
|
||||
type DragData = MouseData;
|
||||
|
||||
let evt = match_data! { val; name;
|
||||
MouseData => "click" | "contextmenu" | "dblclick" | "doubleclick" | "mousedown" | "mouseenter" | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup";
|
||||
ClipboardData => "copy" | "cut" | "paste";
|
||||
CompositionData => "compositionend" | "compositionstart" | "compositionupdate";
|
||||
KeyboardData => "keydown" | "keypress" | "keyup";
|
||||
FocusData => "blur" | "focus" | "focusin" | "focusout";
|
||||
FormData => "change" | "input" | "invalid" | "reset" | "submit";
|
||||
DragData => "drag" | "dragend" | "dragenter" | "dragexit" | "dragleave" | "dragover" | "dragstart" | "drop";
|
||||
PointerData => "pointerlockchange" | "pointerlockerror" | "pointerdown" | "pointermove" | "pointerup" | "pointerover" | "pointerout" | "pointerenter" | "pointerleave" | "gotpointercapture" | "lostpointercapture";
|
||||
SelectionData => "selectstart" | "selectionchange" | "select";
|
||||
TouchData => "touchcancel" | "touchend" | "touchmove" | "touchstart";
|
||||
ScrollData => "scroll";
|
||||
WheelData => "wheel";
|
||||
MediaData => "abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied"
|
||||
| "encrypted" | "ended" | "interruptbegin" | "interruptend" | "loadeddata"
|
||||
| "loadedmetadata" | "loadstart" | "pause" | "play" | "playing" | "progress"
|
||||
| "ratechange" | "seeked" | "seeking" | "stalled" | "suspend" | "timeupdate"
|
||||
| "volumechange" | "waiting" | "error" | "load" | "loadend" | "timeout";
|
||||
AnimationData => "animationstart" | "animationend" | "animationiteration";
|
||||
TransitionData => "transitionend";
|
||||
ToggleData => "toggle";
|
||||
// ImageData => "load" | "error";
|
||||
// OtherData => "abort" | "afterprint" | "beforeprint" | "beforeunload" | "hashchange" | "languagechange" | "message" | "offline" | "online" | "pagehide" | "pageshow" | "popstate" | "rejectionhandled" | "storage" | "unhandledrejection" | "unload" | "userproximity" | "vrdisplayactivate" | "vrdisplayblur" | "vrdisplayconnect" | "vrdisplaydeactivate" | "vrdisplaydisconnect" | "vrdisplayfocus" | "vrdisplaypointerrestricted" | "vrdisplaypointerunrestricted" | "vrdisplaypresentchange";
|
||||
};
|
||||
|
||||
Some(evt)
|
||||
}
|
||||
+22
-13
@@ -17,7 +17,8 @@ use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
|
||||
use desktop_context::UserWindowEvent;
|
||||
pub use desktop_context::{use_eval, use_window, DesktopContext};
|
||||
pub use desktop_context::{use_eval, use_window, DesktopContext, EvalResult};
|
||||
use dioxus_html::HtmlEvent;
|
||||
use futures_channel::mpsc::UnboundedSender;
|
||||
pub use wry;
|
||||
pub use wry::application as tao;
|
||||
@@ -145,6 +146,7 @@ impl DesktopController {
|
||||
event_loop,
|
||||
self.is_ready.clone(),
|
||||
self.proxy.clone(),
|
||||
self.eval_sender.clone(),
|
||||
self.event_tx.clone(),
|
||||
);
|
||||
|
||||
@@ -157,7 +159,8 @@ fn build_webview(
|
||||
event_loop: &tao::event_loop::EventLoopWindowTarget<UserWindowEvent>,
|
||||
is_ready: Arc<AtomicBool>,
|
||||
proxy: tao::event_loop::EventLoopProxy<UserWindowEvent>,
|
||||
event_tx: UnboundedSender<serde_json::Value>,
|
||||
eval_sender: tokio::sync::mpsc::UnboundedSender<serde_json::Value>,
|
||||
event_tx: UnboundedSender<HtmlEvent>,
|
||||
) -> wry::webview::WebView {
|
||||
let builder = cfg.window.clone();
|
||||
let window = builder.build(event_loop).unwrap();
|
||||
@@ -186,8 +189,14 @@ fn build_webview(
|
||||
.with_ipc_handler(move |_window: &Window, payload: String| {
|
||||
parse_ipc_message(&payload)
|
||||
.map(|message| match message.method() {
|
||||
"eval_result" => {
|
||||
let result = message.params();
|
||||
eval_sender.send(result).unwrap();
|
||||
}
|
||||
"user_event" => {
|
||||
_ = event_tx.unbounded_send(message.params());
|
||||
if let Ok(evt) = serde_json::from_value(message.params()) {
|
||||
_ = event_tx.unbounded_send(evt);
|
||||
}
|
||||
}
|
||||
"initialize" => {
|
||||
is_ready.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
@@ -234,16 +243,16 @@ fn build_webview(
|
||||
// in release mode, we don't want to show the dev tool or reload menus
|
||||
webview = webview.with_initialization_script(
|
||||
r#"
|
||||
if (document.addEventListener) {
|
||||
document.addEventListener('contextmenu', function(e) {
|
||||
e.preventDefault();
|
||||
}, false);
|
||||
} else {
|
||||
document.attachEvent('oncontextmenu', function() {
|
||||
window.event.returnValue = false;
|
||||
});
|
||||
}
|
||||
"#,
|
||||
if (document.addEventListener) {
|
||||
document.addEventListener('contextmenu', function(e) {
|
||||
e.preventDefault();
|
||||
}, false);
|
||||
} else {
|
||||
document.attachEvent('oncontextmenu', function() {
|
||||
window.event.returnValue = false;
|
||||
});
|
||||
}
|
||||
"#,
|
||||
)
|
||||
} else {
|
||||
// in debug, we are okay with the reload menu showing and dev tool
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export function main() {
|
||||
let root = window.document.getElementById("main");
|
||||
if (root != null) {
|
||||
window.interpreter = new Interpreter(root);
|
||||
window.ipc.postMessage(serializeIpcMessage("initialize"));
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,14 @@ pub(super) fn desktop_handler(
|
||||
} else if trimmed == "index.js" {
|
||||
Response::builder()
|
||||
.header("Content-Type", "text/javascript")
|
||||
.body(dioxus_interpreter_js::INTERPRETER_JS.as_bytes().to_vec())
|
||||
.body(
|
||||
format!(
|
||||
"{} {}",
|
||||
dioxus_interpreter_js::INTERPRETER_JS,
|
||||
include_str!("./main.js")
|
||||
)
|
||||
.into_bytes(),
|
||||
)
|
||||
.map_err(From::from)
|
||||
} else {
|
||||
let asset_root = asset_root
|
||||
@@ -104,10 +111,10 @@ fn get_asset_root() -> Option<PathBuf> {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let bundle = core_foundation::bundle::CFBundle::main_bundle();
|
||||
let bundle_path = dbg!(bundle.path()?);
|
||||
let resources_path = dbg!(bundle.resources_path()?);
|
||||
let absolute_resources_root = dbg!(bundle_path.join(resources_path));
|
||||
let canonical_resources_root = dbg!(dunce::canonicalize(absolute_resources_root).ok()?);
|
||||
let bundle_path = bundle.path()?;
|
||||
let resources_path = bundle.resources_path()?;
|
||||
let absolute_resources_root = bundle_path.join(resources_path);
|
||||
let canonical_resources_root = dunce::canonicalize(absolute_resources_root).ok()?;
|
||||
|
||||
return Some(canonical_resources_root);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user