|
@@ -13,28 +13,27 @@ mod protocol;
|
|
#[cfg(all(feature = "hot-reload", debug_assertions))]
|
|
#[cfg(all(feature = "hot-reload", debug_assertions))]
|
|
mod hot_reload;
|
|
mod hot_reload;
|
|
|
|
|
|
|
|
+mod eval;
|
|
|
|
+mod waker;
|
|
|
|
+mod webview;
|
|
|
|
+
|
|
pub use cfg::Config;
|
|
pub use cfg::Config;
|
|
use desktop_context::UserWindowEvent;
|
|
use desktop_context::UserWindowEvent;
|
|
-pub use desktop_context::{use_eval, use_window, DesktopContext, EvalResult};
|
|
|
|
|
|
+pub use desktop_context::{use_window, DesktopContext};
|
|
use dioxus_core::*;
|
|
use dioxus_core::*;
|
|
use dioxus_html::HtmlEvent;
|
|
use dioxus_html::HtmlEvent;
|
|
-use events::parse_ipc_message;
|
|
|
|
-use futures_util::task::ArcWake;
|
|
|
|
use futures_util::{pin_mut, FutureExt};
|
|
use futures_util::{pin_mut, FutureExt};
|
|
use std::collections::HashMap;
|
|
use std::collections::HashMap;
|
|
use std::rc::Rc;
|
|
use std::rc::Rc;
|
|
-use std::sync::Arc;
|
|
|
|
|
|
+use std::task::Waker;
|
|
pub use tao::dpi::{LogicalSize, PhysicalSize};
|
|
pub use tao::dpi::{LogicalSize, PhysicalSize};
|
|
pub use tao::window::WindowBuilder;
|
|
pub use tao::window::WindowBuilder;
|
|
use tao::{
|
|
use tao::{
|
|
event::{Event, StartCause, WindowEvent},
|
|
event::{Event, StartCause, WindowEvent},
|
|
event_loop::{ControlFlow, EventLoop},
|
|
event_loop::{ControlFlow, EventLoop},
|
|
- window::Window,
|
|
|
|
};
|
|
};
|
|
pub use wry;
|
|
pub use wry;
|
|
pub use wry::application as tao;
|
|
pub use wry::application as tao;
|
|
-use wry::application::event_loop::EventLoopProxy;
|
|
|
|
-use wry::webview::WebViewBuilder;
|
|
|
|
|
|
|
|
/// Launch the WebView and run the event loop.
|
|
/// Launch the WebView and run the event loop.
|
|
///
|
|
///
|
|
@@ -82,9 +81,7 @@ pub fn launch_cfg(root: Component, config_builder: Config) {
|
|
|
|
|
|
/// Launch the WebView and run the event loop, with configuration and root props.
|
|
/// Launch the WebView and run the event loop, with configuration and root props.
|
|
///
|
|
///
|
|
-/// THIS WILL BLOCK THE CURRENT THREAD
|
|
|
|
-///
|
|
|
|
-/// This function will start a multithreaded Tokio runtime as well the WebView event loop.
|
|
|
|
|
|
+/// This function will start a multithreaded Tokio runtime as well the WebView event loop. This will block the current thread.
|
|
///
|
|
///
|
|
/// You can configure the WebView window with a configuration closure
|
|
/// You can configure the WebView window with a configuration closure
|
|
///
|
|
///
|
|
@@ -106,25 +103,27 @@ pub fn launch_cfg(root: Component, config_builder: Config) {
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
/// ```
|
|
pub fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cfg: Config) {
|
|
pub fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cfg: Config) {
|
|
- let event_loop = EventLoop::with_user_event();
|
|
|
|
-
|
|
|
|
let mut dom = VirtualDom::new_with_props(root, props);
|
|
let mut dom = VirtualDom::new_with_props(root, props);
|
|
|
|
|
|
- let proxy = event_loop.create_proxy();
|
|
|
|
|
|
+ let event_loop = EventLoop::with_user_event();
|
|
|
|
|
|
- let mut webviews = HashMap::new();
|
|
|
|
|
|
+ let proxy = event_loop.create_proxy();
|
|
|
|
|
|
- // todo: make this configurable
|
|
|
|
|
|
+ // We start the tokio runtime *on this thread*
|
|
|
|
+ // Any future we poll later will use this runtime to spawn tasks and for IO
|
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
|
.enable_all()
|
|
.enable_all()
|
|
.build()
|
|
.build()
|
|
.unwrap();
|
|
.unwrap();
|
|
|
|
|
|
- // We want to poll the virtualdom and the event loop at the same time
|
|
|
|
- // So the waker will be connected to both
|
|
|
|
- let waker = futures_util::task::waker(Arc::new(DomHandle {
|
|
|
|
- proxy: proxy.clone(),
|
|
|
|
- }));
|
|
|
|
|
|
+ // We enter the runtime but we poll futures manually, circumventing the per-task runtime budget
|
|
|
|
+ let _guard = rt.enter();
|
|
|
|
+
|
|
|
|
+ // We want to poll the virtualdom and the event loop at the same time, so the waker will be connected to both
|
|
|
|
+ let waker = waker::tao_waker(&proxy);
|
|
|
|
+
|
|
|
|
+ // We only have one webview right now, but we'll have more later
|
|
|
|
+ let mut webviews = HashMap::new();
|
|
|
|
|
|
event_loop.run(move |window_event, event_loop, control_flow| {
|
|
event_loop.run(move |window_event, event_loop, control_flow| {
|
|
*control_flow = ControlFlow::Wait;
|
|
*control_flow = ControlFlow::Wait;
|
|
@@ -132,11 +131,10 @@ pub fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cf
|
|
match window_event {
|
|
match window_event {
|
|
Event::NewEvents(StartCause::Init) => {
|
|
Event::NewEvents(StartCause::Init) => {
|
|
let (eval_sender, eval_reciever) = tokio::sync::mpsc::unbounded_channel();
|
|
let (eval_sender, eval_reciever) = tokio::sync::mpsc::unbounded_channel();
|
|
- let window = Rc::new(build_webview(&mut cfg, event_loop, proxy.clone()));
|
|
|
|
|
|
+ let window = Rc::new(webview::build(&mut cfg, event_loop, proxy.clone()));
|
|
let ctx = DesktopContext::new(window.clone(), proxy.clone(), eval_reciever);
|
|
let ctx = DesktopContext::new(window.clone(), proxy.clone(), eval_reciever);
|
|
dom.base_scope().provide_context(ctx);
|
|
dom.base_scope().provide_context(ctx);
|
|
webviews.insert(window.window().id(), window.clone());
|
|
webviews.insert(window.window().id(), window.clone());
|
|
-
|
|
|
|
proxy.send_event(UserWindowEvent::Poll).unwrap();
|
|
proxy.send_event(UserWindowEvent::Poll).unwrap();
|
|
}
|
|
}
|
|
|
|
|
|
@@ -156,200 +154,55 @@ pub fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cf
|
|
_ => {}
|
|
_ => {}
|
|
},
|
|
},
|
|
|
|
|
|
- Event::UserEvent(user_event) => {
|
|
|
|
- match user_event {
|
|
|
|
- UserWindowEvent::UserEvent(json_value) => {
|
|
|
|
- if let Ok(value) = serde_json::from_value::<HtmlEvent>(json_value) {
|
|
|
|
- let HtmlEvent {
|
|
|
|
- name,
|
|
|
|
- element,
|
|
|
|
- bubbles,
|
|
|
|
- data,
|
|
|
|
- } = value;
|
|
|
|
-
|
|
|
|
- dom.handle_event(&name, data.into_any(), element, bubbles);
|
|
|
|
-
|
|
|
|
- let edits = dom.render_immediate();
|
|
|
|
-
|
|
|
|
- let serialized = serde_json::to_string(&edits).unwrap();
|
|
|
|
-
|
|
|
|
- let (_id, view) = webviews.iter_mut().next().unwrap();
|
|
|
|
-
|
|
|
|
- view.evaluate_script(&format!(
|
|
|
|
- "window.interpreter.handleEdits({})",
|
|
|
|
- serialized
|
|
|
|
- ))
|
|
|
|
- .unwrap();
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- UserWindowEvent::Poll => {
|
|
|
|
- let mut cx = std::task::Context::from_waker(&waker);
|
|
|
|
-
|
|
|
|
- // using this will reset the budget for the task that we're blocking the main thread with
|
|
|
|
- let _guard = rt.enter();
|
|
|
|
-
|
|
|
|
- loop {
|
|
|
|
- {
|
|
|
|
- let fut = dom.wait_for_work();
|
|
|
|
- pin_mut!(fut);
|
|
|
|
-
|
|
|
|
- match fut.poll_unpin(&mut cx) {
|
|
|
|
- std::task::Poll::Ready(_) => {}
|
|
|
|
- std::task::Poll::Pending => break,
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- let edits = dom.render_immediate();
|
|
|
|
-
|
|
|
|
- // apply the edits
|
|
|
|
- let serialized = serde_json::to_string(&edits).unwrap();
|
|
|
|
-
|
|
|
|
- let (_id, view) = webviews.iter_mut().next().unwrap();
|
|
|
|
|
|
+ Event::UserEvent(UserWindowEvent::Initialize) => {
|
|
|
|
+ send_edits(dom.rebuild(), &mut webviews);
|
|
|
|
+ }
|
|
|
|
|
|
- view.evaluate_script(&format!(
|
|
|
|
- "window.interpreter.handleEdits({})",
|
|
|
|
- serialized
|
|
|
|
- ))
|
|
|
|
- .unwrap();
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ Event::UserEvent(UserWindowEvent::CloseWindow) => *control_flow = ControlFlow::Exit,
|
|
|
|
|
|
- UserWindowEvent::Initialize => {
|
|
|
|
- let edits = dom.rebuild();
|
|
|
|
|
|
+ Event::UserEvent(UserWindowEvent::EvalResult(_)) => todo!(),
|
|
|
|
|
|
- let (_id, view) = webviews.iter_mut().next().unwrap();
|
|
|
|
|
|
+ Event::UserEvent(UserWindowEvent::UserEvent(json_value)) => {
|
|
|
|
+ let evt = match serde_json::from_value::<HtmlEvent>(json_value) {
|
|
|
|
+ Ok(value) => value,
|
|
|
|
+ Err(_) => return,
|
|
|
|
+ };
|
|
|
|
|
|
- let serialized = serde_json::to_string(&edits).unwrap();
|
|
|
|
|
|
+ dom.handle_event(&evt.name, evt.data.into_any(), evt.element, evt.bubbles);
|
|
|
|
|
|
- view.evaluate_script(&format!(
|
|
|
|
- "window.interpreter.handleEdits({})",
|
|
|
|
- serialized
|
|
|
|
- ))
|
|
|
|
- .unwrap();
|
|
|
|
- }
|
|
|
|
|
|
+ send_edits(dom.render_immediate(), &mut webviews);
|
|
|
|
+ }
|
|
|
|
|
|
- other => {
|
|
|
|
- // desktop.handle_event(user_event, control_flow);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ Event::UserEvent(UserWindowEvent::Poll) => {
|
|
|
|
+ poll_vdom(&waker, &mut dom, &mut webviews);
|
|
}
|
|
}
|
|
|
|
|
|
- _ => {}
|
|
|
|
|
|
+ _ => todo!(),
|
|
}
|
|
}
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
|
|
-struct DomHandle {
|
|
|
|
- proxy: EventLoopProxy<UserWindowEvent>,
|
|
|
|
-}
|
|
|
|
|
|
+type Webviews = HashMap<tao::window::WindowId, Rc<wry::webview::WebView>>;
|
|
|
|
|
|
-impl ArcWake for DomHandle {
|
|
|
|
- fn wake_by_ref(arc_self: &Arc<Self>) {
|
|
|
|
- arc_self.proxy.send_event(UserWindowEvent::Poll).unwrap();
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
|
|
+fn poll_vdom(waker: &Waker, dom: &mut VirtualDom, webviews: &mut Webviews) {
|
|
|
|
+ let mut cx = std::task::Context::from_waker(waker);
|
|
|
|
+ loop {
|
|
|
|
+ {
|
|
|
|
+ let fut = dom.wait_for_work();
|
|
|
|
+ pin_mut!(fut);
|
|
|
|
|
|
-fn build_webview(
|
|
|
|
- cfg: &mut Config,
|
|
|
|
- event_loop: &tao::event_loop::EventLoopWindowTarget<UserWindowEvent>,
|
|
|
|
- proxy: tao::event_loop::EventLoopProxy<UserWindowEvent>,
|
|
|
|
-) -> wry::webview::WebView {
|
|
|
|
- let builder = cfg.window.clone();
|
|
|
|
- let window = builder.build(event_loop).unwrap();
|
|
|
|
- let file_handler = cfg.file_drop_handler.take();
|
|
|
|
- let custom_head = cfg.custom_head.clone();
|
|
|
|
- let resource_dir = cfg.resource_dir.clone();
|
|
|
|
- let index_file = cfg.custom_index.clone();
|
|
|
|
- let root_name = cfg.root_name.clone();
|
|
|
|
-
|
|
|
|
- // We assume that if the icon is None in cfg, then the user just didnt set it
|
|
|
|
- if cfg.window.window.window_icon.is_none() {
|
|
|
|
- window.set_window_icon(Some(
|
|
|
|
- tao::window::Icon::from_rgba(
|
|
|
|
- include_bytes!("./assets/default_icon.bin").to_vec(),
|
|
|
|
- 460,
|
|
|
|
- 460,
|
|
|
|
- )
|
|
|
|
- .expect("image parse failed"),
|
|
|
|
- ));
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- let mut webview = WebViewBuilder::new(window)
|
|
|
|
- .unwrap()
|
|
|
|
- .with_transparent(cfg.window.window.transparent)
|
|
|
|
- .with_url("dioxus://index.html/")
|
|
|
|
- .unwrap()
|
|
|
|
- .with_ipc_handler(move |_window: &Window, payload: String| {
|
|
|
|
- let message = match parse_ipc_message(&payload) {
|
|
|
|
- Some(message) => message,
|
|
|
|
- None => {
|
|
|
|
- log::error!("Failed to parse IPC message: {}", payload);
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- match message.method() {
|
|
|
|
- "eval_result" => {
|
|
|
|
- let _ = proxy.send_event(UserWindowEvent::EvalResult(message.params()));
|
|
|
|
- }
|
|
|
|
- "user_event" => {
|
|
|
|
- let _ = proxy.send_event(UserWindowEvent::UserEvent(message.params()));
|
|
|
|
- }
|
|
|
|
- "initialize" => {
|
|
|
|
- let _ = proxy.send_event(UserWindowEvent::Initialize);
|
|
|
|
- }
|
|
|
|
- "browser_open" => match message.params().as_object() {
|
|
|
|
- Some(temp) if temp.contains_key("href") => {
|
|
|
|
- let open = webbrowser::open(temp["href"].as_str().unwrap());
|
|
|
|
- if let Err(e) = open {
|
|
|
|
- log::error!("Open Browser error: {:?}", e);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- _ => (),
|
|
|
|
- },
|
|
|
|
- _ => (),
|
|
|
|
|
|
+ match fut.poll_unpin(&mut cx) {
|
|
|
|
+ std::task::Poll::Ready(_) => {}
|
|
|
|
+ std::task::Poll::Pending => break,
|
|
}
|
|
}
|
|
- })
|
|
|
|
- .with_custom_protocol(String::from("dioxus"), move |r| {
|
|
|
|
- protocol::desktop_handler(
|
|
|
|
- r,
|
|
|
|
- resource_dir.clone(),
|
|
|
|
- custom_head.clone(),
|
|
|
|
- index_file.clone(),
|
|
|
|
- &root_name,
|
|
|
|
- )
|
|
|
|
- })
|
|
|
|
- .with_file_drop_handler(move |window, evet| {
|
|
|
|
- file_handler
|
|
|
|
- .as_ref()
|
|
|
|
- .map(|handler| handler(window, evet))
|
|
|
|
- .unwrap_or_default()
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- for (name, handler) in cfg.protocols.drain(..) {
|
|
|
|
- webview = webview.with_custom_protocol(name, handler)
|
|
|
|
- }
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- if cfg.disable_context_menu {
|
|
|
|
- // 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;
|
|
|
|
- });
|
|
|
|
- }
|
|
|
|
- "#,
|
|
|
|
- )
|
|
|
|
- } else {
|
|
|
|
- // in debug, we are okay with the reload menu showing and dev tool
|
|
|
|
- webview = webview.with_devtools(true);
|
|
|
|
|
|
+ send_edits(dom.render_immediate(), webviews);
|
|
}
|
|
}
|
|
|
|
+}
|
|
|
|
|
|
- webview.build().unwrap()
|
|
|
|
|
|
+fn send_edits(edits: Mutations, webviews: &mut Webviews) {
|
|
|
|
+ let serialized = serde_json::to_string(&edits).unwrap();
|
|
|
|
+ let (_id, view) = webviews.iter_mut().next().unwrap();
|
|
|
|
+ _ = view.evaluate_script(&format!("window.interpreter.handleEdits({})", serialized));
|
|
}
|
|
}
|