Merge branch 'upstream' into return-from-js

This commit is contained in:
Evan Almloff committed 2022-12-08 11:25:15 -06:00
commit bf47f64852
281 files changed
+16169 -19328

No files matched your search

+50 -38
View File
@@ -1,6 +1,8 @@
use crate::desktop_context::{DesktopContext, UserWindowEvent};
use crate::events::{decode_event, EventMessage};
use dioxus_core::*;
use futures_channel::mpsc::{unbounded, UnboundedSender};
use futures_util::StreamExt;
#[cfg(target_os = "ios")]
use objc::runtime::Object;
use serde_json::Value;
@@ -8,6 +10,7 @@ use std::{
collections::HashMap,
sync::Arc,
sync::{atomic::AtomicBool, Mutex},
time::Duration,
};
use wry::{
self,
@@ -17,11 +20,13 @@ use wry::{
pub(super) struct DesktopController {
pub(super) webviews: HashMap<WindowId, WebView>,
pub(super) sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
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>,
#[cfg(target_os = "ios")]
pub(super) views: Vec<*mut Object>,
}
@@ -35,66 +40,63 @@ impl DesktopController {
proxy: EventLoopProxy<UserWindowEvent>,
) -> Self {
let edit_queue = Arc::new(Mutex::new(Vec::new()));
let (sender, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>();
let (event_tx, mut event_rx) = unbounded();
let proxy2 = proxy.clone();
let pending_edits = edit_queue.clone();
let return_sender = sender.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 "spawn" onto multiple threads
// We create the runtime as multithreaded, so you can still "tokio::spawn" onto multiple threads
// I'd personally not require tokio to be built-in to Dioxus-Desktop, but the DX is worse without it
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
runtime.block_on(async move {
let mut dom =
VirtualDom::new_with_props_and_scheduler(root, props, (sender, receiver));
let window_context = DesktopContext::new(desktop_context_proxy, eval_reciever);
dom.base_scope().provide_context(window_context);
// allow other proccesses to send the new rsx text to the @dioxusin ipc channel and recieve erros on the @dioxusout channel
#[cfg(any(feature = "hot-reload", debug_assertions))]
crate::hot_reload::init(&dom);
let edits = dom.rebuild();
edit_queue
.lock()
.unwrap()
.push(serde_json::to_string(&edits.edits).unwrap());
// Make sure the window is ready for any new updates
proxy.send_event(UserWindowEvent::Update).unwrap();
let mut dom = VirtualDom::new_with_props(root, props)
.with_root_context(DesktopContext::new(desktop_context_proxy, eval_reciever));
{
let edits = dom.rebuild();
let mut queue = edit_queue.lock().unwrap();
queue.push(serde_json::to_string(&edits).unwrap());
proxy.send_event(UserWindowEvent::EditsReady).unwrap();
}
loop {
dom.wait_for_work().await;
let muts = dom.work_with_deadline(|| false);
for edit in muts {
edit_queue
.lock()
.unwrap()
.push(serde_json::to_string(&edit.edits).unwrap());
tokio::select! {
_ = 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));
}
}
}
}
let _ = proxy.send_event(UserWindowEvent::Update);
let muts = dom
.render_with_deadline(tokio::time::sleep(Duration::from_millis(16)))
.await;
edit_queue.lock().unwrap().push(serde_json::to_string(&muts).unwrap());
let _ = proxy.send_event(UserWindowEvent::EditsReady);
}
})
});
Self {
pending_edits,
sender: return_sender,
eval_sender,
webviews: HashMap::new(),
is_ready: Arc::new(AtomicBool::new(false)),
quit_app_on_close: true,
proxy: proxy2,
event_tx,
#[cfg(target_os = "ios")]
views: vec![],
}
@@ -110,13 +112,23 @@ impl DesktopController {
pub(super) fn try_load_ready_webviews(&mut self) {
if self.is_ready.load(std::sync::atomic::Ordering::Relaxed) {
let mut queue = self.pending_edits.lock().unwrap();
let mut new_queue = Vec::new();
{
let mut queue = self.pending_edits.lock().unwrap();
std::mem::swap(&mut new_queue, &mut *queue);
}
let (_id, view) = self.webviews.iter_mut().next().unwrap();
for edit in queue.drain(..) {
for edit in new_queue.drain(..) {
view.evaluate_script(&format!("window.interpreter.handleEdits({})", edit))
.unwrap();
}
}
}
pub(crate) fn set_template(&self, _serialized_template: String) {
todo!("hot reloading currently WIP")
}
}
+99 -89
View File
@@ -1,3 +1,5 @@
use std::rc::Rc;
use crate::controller::DesktopController;
use dioxus_core::ScopeState;
use serde::de::Error;
@@ -5,7 +7,6 @@ use serde_json::Value;
use std::future::Future;
use std::future::IntoFuture;
use std::pin::Pin;
use std::rc::Rc;
use wry::application::event_loop::ControlFlow;
use wry::application::event_loop::EventLoopProxy;
#[cfg(target_os = "ios")]
@@ -165,12 +166,18 @@ impl DesktopContext {
#[derive(Debug)]
pub enum UserWindowEvent {
Update,
EditsReady,
Initialize,
CloseWindow,
DragWindow,
FocusWindow,
/// Set a new Dioxus template for hot-reloading
///
/// Is a no-op in release builds. Must fit the right format for templates
SetTemplate(String),
Visible(bool),
Minimize(bool),
Maximize(bool),
@@ -198,103 +205,106 @@ pub enum UserWindowEvent {
PopView,
}
pub(super) fn handler(
user_event: UserWindowEvent,
desktop: &mut DesktopController,
control_flow: &mut ControlFlow,
) {
// currently dioxus-desktop supports a single window only,
// so we can grab the only webview from the map;
// on wayland it is possible that a user event is emitted
// before the webview is initialized. ignore the event.
let webview = if let Some(webview) = desktop.webviews.values().next() {
webview
} else {
return;
};
let window = webview.window();
impl DesktopController {
pub(super) fn handle_event(
&mut self,
user_event: UserWindowEvent,
control_flow: &mut ControlFlow,
) {
// currently dioxus-desktop supports a single window only,
// so we can grab the only webview from the map;
// on wayland it is possible that a user event is emitted
// before the webview is initialized. ignore the event.
let webview = if let Some(webview) = self.webviews.values().next() {
webview
} else {
return;
};
match user_event {
Update => desktop.try_load_ready_webviews(),
CloseWindow => *control_flow = ControlFlow::Exit,
DragWindow => {
// if the drag_window has any errors, we don't do anything
window.fullscreen().is_none().then(|| window.drag_window());
}
Visible(state) => window.set_visible(state),
Minimize(state) => window.set_minimized(state),
Maximize(state) => window.set_maximized(state),
MaximizeToggle => window.set_maximized(!window.is_maximized()),
Fullscreen(state) => {
if let Some(handle) = window.current_monitor() {
window.set_fullscreen(state.then_some(WryFullscreen::Borderless(Some(handle))));
let window = webview.window();
match user_event {
Initialize | EditsReady => self.try_load_ready_webviews(),
SetTemplate(template) => self.set_template(template),
CloseWindow => *control_flow = ControlFlow::Exit,
DragWindow => {
// if the drag_window has any errors, we don't do anything
window.fullscreen().is_none().then(|| window.drag_window());
}
}
FocusWindow => window.set_focus(),
Resizable(state) => window.set_resizable(state),
AlwaysOnTop(state) => window.set_always_on_top(state),
CursorVisible(state) => window.set_cursor_visible(state),
CursorGrab(state) => {
let _ = window.set_cursor_grab(state);
}
SetTitle(content) => window.set_title(&content),
SetDecorations(state) => window.set_decorations(state),
SetZoomLevel(scale_factor) => webview.zoom(scale_factor),
Print => {
if let Err(e) = webview.print() {
// we can't panic this error.
log::warn!("Open print modal failed: {e}");
Visible(state) => window.set_visible(state),
Minimize(state) => window.set_minimized(state),
Maximize(state) => window.set_maximized(state),
MaximizeToggle => window.set_maximized(!window.is_maximized()),
Fullscreen(state) => {
if let Some(handle) = window.current_monitor() {
window.set_fullscreen(state.then_some(WryFullscreen::Borderless(Some(handle))));
}
}
}
DevTool => {
#[cfg(debug_assertions)]
webview.open_devtools();
#[cfg(not(debug_assertions))]
log::warn!("Devtools are disabled in release builds");
}
FocusWindow => window.set_focus(),
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}");
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);
}
}
#[cfg(target_os = "ios")]
PushView(view) => unsafe {
use objc::runtime::Object;
use objc::*;
assert!(is_main_thread());
let ui_view = window.ui_view() as *mut Object;
let ui_view_frame: *mut Object = msg_send![ui_view, frame];
let _: () = msg_send![view, setFrame: ui_view_frame];
let _: () = msg_send![view, setAutoresizingMask: 31];
SetTitle(content) => window.set_title(&content),
SetDecorations(state) => window.set_decorations(state),
let ui_view_controller = window.ui_view_controller() as *mut Object;
let _: () = msg_send![ui_view_controller, setView: view];
desktop.views.push(ui_view);
},
SetZoomLevel(scale_factor) => webview.zoom(scale_factor),
Print => {
if let Err(e) = webview.print() {
// we can't panic this error.
log::warn!("Open print modal failed: {e}");
}
}
DevTool => {
#[cfg(debug_assertions)]
webview.open_devtools();
#[cfg(not(debug_assertions))]
log::warn!("Devtools are disabled in release builds");
}
#[cfg(target_os = "ios")]
PushView(view) => unsafe {
use objc::runtime::Object;
use objc::*;
assert!(is_main_thread());
let ui_view = window.ui_view() as *mut Object;
let ui_view_frame: *mut Object = msg_send![ui_view, frame];
let _: () = msg_send![view, setFrame: ui_view_frame];
let _: () = msg_send![view, setAutoresizingMask: 31];
#[cfg(target_os = "ios")]
PopView => unsafe {
use objc::runtime::Object;
use objc::*;
assert!(is_main_thread());
if let Some(view) = desktop.views.pop() {
let ui_view_controller = window.ui_view_controller() as *mut Object;
let _: () = msg_send![ui_view_controller, setView: view];
}
},
self.views.push(ui_view);
},
#[cfg(target_os = "ios")]
PopView => unsafe {
use objc::runtime::Object;
use objc::*;
assert!(is_main_thread());
if let Some(view) = self.views.pop() {
let ui_view_controller = window.ui_view_controller() as *mut Object;
let _: () = msg_send![ui_view_controller, setView: view];
}
},
}
}
}
+54 -184
View File
@@ -1,13 +1,10 @@
//! Convert a serialized event to an event trigger
use std::any::Any;
use std::sync::Arc;
use dioxus_core::ElementId;
use dioxus_core::{EventPriority, UserEvent};
use dioxus_html::event_bubbles;
use dioxus_html::on::*;
use dioxus_html::events::*;
use serde::{Deserialize, Serialize};
use serde_json::from_value;
use std::any::Any;
use std::rc::Rc;
#[derive(Deserialize, Serialize)]
pub(crate) struct IpcMessage {
@@ -35,187 +32,60 @@ pub(crate) fn parse_ipc_message(payload: &str) -> Option<IpcMessage> {
}
}
#[derive(Deserialize, Serialize)]
struct ImEvent {
event: String,
mounted_dom_id: ElementId,
contents: serde_json::Value,
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,
}
};
}
pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent {
let ImEvent {
event,
mounted_dom_id,
contents,
} = serde_json::from_value(val).unwrap();
let mounted_dom_id = Some(mounted_dom_id);
let name = event_name_from_type(&event);
let event = make_synthetic_event(&event, contents);
UserEvent {
name,
priority: EventPriority::Low,
scope_id: None,
element: mounted_dom_id,
bubbles: event_bubbles(name),
data: event,
}
#[derive(Deserialize)]
pub struct EventMessage {
pub contents: serde_json::Value,
pub event: String,
pub mounted_dom_id: usize,
}
fn make_synthetic_event(name: &str, val: serde_json::Value) -> Arc<dyn Any + Send + Sync> {
match name {
"copy" | "cut" | "paste" => {
//
Arc::new(ClipboardData {})
}
"compositionend" | "compositionstart" | "compositionupdate" => {
Arc::new(serde_json::from_value::<CompositionData>(val).unwrap())
}
"keydown" | "keypress" | "keyup" => {
let evt = serde_json::from_value::<KeyboardData>(val).unwrap();
Arc::new(evt)
}
"focus" | "blur" | "focusout" | "focusin" => {
//
Arc::new(FocusData {})
}
pub fn decode_event(value: EventMessage) -> Option<Rc<dyn Any>> {
let val = value.contents;
let name = value.event.as_str();
type DragData = MouseData;
// todo: these handlers might get really slow if the input box gets large and allocation pressure is heavy
// don't have a good solution with the serialized event problem
"change" | "input" | "invalid" | "reset" | "submit" => {
Arc::new(serde_json::from_value::<FormData>(val).unwrap())
}
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";
};
"click" | "contextmenu" | "dblclick" | "doubleclick" | "drag" | "dragend" | "dragenter"
| "dragexit" | "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown"
| "mouseenter" | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => {
Arc::new(serde_json::from_value::<MouseData>(val).unwrap())
}
"pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
| "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => {
Arc::new(serde_json::from_value::<PointerData>(val).unwrap())
}
"select" => {
//
Arc::new(serde_json::from_value::<SelectionData>(val).unwrap())
}
"touchcancel" | "touchend" | "touchmove" | "touchstart" => {
Arc::new(serde_json::from_value::<TouchData>(val).unwrap())
}
"scroll" => Arc::new(ScrollData {}),
"wheel" => Arc::new(serde_json::from_value::<WheelData>(val).unwrap()),
"animationstart" | "animationend" | "animationiteration" => {
Arc::new(serde_json::from_value::<AnimationData>(val).unwrap())
}
"transitionend" => Arc::new(serde_json::from_value::<TransitionData>(val).unwrap()),
"abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted"
| "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play"
| "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
| "timeupdate" | "volumechange" | "waiting" => {
//
Arc::new(MediaData {})
}
"toggle" => Arc::new(ToggleData {}),
_ => Arc::new(()),
}
}
fn event_name_from_type(typ: &str) -> &'static str {
match typ {
"copy" => "copy",
"cut" => "cut",
"paste" => "paste",
"compositionend" => "compositionend",
"compositionstart" => "compositionstart",
"compositionupdate" => "compositionupdate",
"keydown" => "keydown",
"keypress" => "keypress",
"keyup" => "keyup",
"focus" => "focus",
"focusout" => "focusout",
"focusin" => "focusin",
"blur" => "blur",
"change" => "change",
"input" => "input",
"invalid" => "invalid",
"reset" => "reset",
"submit" => "submit",
"click" => "click",
"contextmenu" => "contextmenu",
"doubleclick" => "doubleclick",
"dblclick" => "dblclick",
"drag" => "drag",
"dragend" => "dragend",
"dragenter" => "dragenter",
"dragexit" => "dragexit",
"dragleave" => "dragleave",
"dragover" => "dragover",
"dragstart" => "dragstart",
"drop" => "drop",
"mousedown" => "mousedown",
"mouseenter" => "mouseenter",
"mouseleave" => "mouseleave",
"mousemove" => "mousemove",
"mouseout" => "mouseout",
"mouseover" => "mouseover",
"mouseup" => "mouseup",
"pointerdown" => "pointerdown",
"pointermove" => "pointermove",
"pointerup" => "pointerup",
"pointercancel" => "pointercancel",
"gotpointercapture" => "gotpointercapture",
"lostpointercapture" => "lostpointercapture",
"pointerenter" => "pointerenter",
"pointerleave" => "pointerleave",
"pointerover" => "pointerover",
"pointerout" => "pointerout",
"select" => "select",
"touchcancel" => "touchcancel",
"touchend" => "touchend",
"touchmove" => "touchmove",
"touchstart" => "touchstart",
"scroll" => "scroll",
"wheel" => "wheel",
"animationstart" => "animationstart",
"animationend" => "animationend",
"animationiteration" => "animationiteration",
"transitionend" => "transitionend",
"abort" => "abort",
"canplay" => "canplay",
"canplaythrough" => "canplaythrough",
"durationchange" => "durationchange",
"emptied" => "emptied",
"encrypted" => "encrypted",
"ended" => "ended",
"error" => "error",
"loadeddata" => "loadeddata",
"loadedmetadata" => "loadedmetadata",
"loadstart" => "loadstart",
"pause" => "pause",
"play" => "play",
"playing" => "playing",
"progress" => "progress",
"ratechange" => "ratechange",
"seeked" => "seeked",
"seeking" => "seeking",
"stalled" => "stalled",
"suspend" => "suspend",
"timeupdate" => "timeupdate",
"volumechange" => "volumechange",
"waiting" => "waiting",
"toggle" => "toggle",
a => {
panic!("unsupported event type {:?}", a);
}
}
Some(evt)
}
+14 -9
View File
@@ -1,4 +1,7 @@
use dioxus_core::{SchedulerMsg, SetTemplateMsg, VirtualDom};
#![allow(dead_code)]
use dioxus_core::VirtualDom;
use interprocess::local_socket::{LocalSocketListener, LocalSocketStream};
use std::io::{BufRead, BufReader};
use std::time::Duration;
@@ -10,32 +13,34 @@ fn handle_error(connection: std::io::Result<LocalSocketStream>) -> Option<LocalS
.ok()
}
pub(crate) fn init(dom: &VirtualDom) {
pub(crate) fn init(_dom: &VirtualDom) {
let latest_in_connection: Arc<Mutex<Option<BufReader<LocalSocketStream>>>> =
Arc::new(Mutex::new(None));
let latest_in_connection_handle = latest_in_connection.clone();
// connect to processes for incoming data
std::thread::spawn(move || {
if let Ok(listener) = LocalSocketListener::bind("@dioxusin") {
let temp_file = std::env::temp_dir().join("@dioxusin");
if let Ok(listener) = LocalSocketListener::bind(temp_file) {
for conn in listener.incoming().filter_map(handle_error) {
*latest_in_connection_handle.lock().unwrap() = Some(BufReader::new(conn));
}
}
});
let mut channel = dom.get_scheduler_channel();
std::thread::spawn(move || {
loop {
if let Some(conn) = &mut *latest_in_connection.lock().unwrap() {
let mut buf = String::new();
match conn.read_line(&mut buf) {
Ok(_) => {
let msg: SetTemplateMsg = serde_json::from_str(&buf).unwrap();
channel
.start_send(SchedulerMsg::SetTemplate(Box::new(msg)))
.unwrap();
todo!()
// let msg: SetTemplateMsg = serde_json::from_str(&buf).unwrap();
// channel
// .start_send(SchedulerMsg::SetTemplate(Box::new(msg)))
// .unwrap();
}
Err(err) => {
if err.kind() != std::io::ErrorKind::WouldBlock {
+132 -117
View File
@@ -8,16 +8,20 @@ mod controller;
mod desktop_context;
mod escape;
mod events;
#[cfg(any(feature = "hot-reload", debug_assertions))]
mod hot_reload;
mod protocol;
#[cfg(all(feature = "hot-reload", debug_assertions))]
mod hot_reload;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use desktop_context::UserWindowEvent;
pub use desktop_context::{use_eval, use_window, DesktopContext, EvalResult};
use futures_channel::mpsc::UnboundedSender;
pub use wry;
pub use wry::application as tao;
use crate::events::trigger_from_serialized;
pub use cfg::Config;
use controller::DesktopController;
use dioxus_core::*;
@@ -100,107 +104,139 @@ 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) {
let event_loop = EventLoop::with_user_event();
let mut desktop = DesktopController::new_on_tokio(root, props, event_loop.create_proxy());
let proxy = event_loop.create_proxy();
// We assume that if the icon is None, then the user just didnt set it
event_loop.run(move |window_event, event_loop, control_flow| {
*control_flow = ControlFlow::Wait;
match window_event {
Event::NewEvents(StartCause::Init) => desktop.start(&mut cfg, event_loop),
Event::WindowEvent {
event, window_id, ..
} => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::Destroyed { .. } => desktop.close_window(window_id, control_flow),
_ => {}
},
Event::UserEvent(user_event) => desktop.handle_event(user_event, control_flow),
Event::MainEventsCleared => {}
Event::Resumed => {}
Event::Suspended => {}
Event::LoopDestroyed => {}
Event::RedrawRequested(_id) => {}
_ => {}
}
})
}
impl DesktopController {
fn start(
&mut self,
cfg: &mut Config,
event_loop: &tao::event_loop::EventLoopWindowTarget<UserWindowEvent>,
) {
let webview = build_webview(
cfg,
event_loop,
self.is_ready.clone(),
self.proxy.clone(),
self.eval_sender.clone(),
self.event_tx.clone(),
);
self.webviews.insert(webview.window().id(), webview);
}
}
fn build_webview(
cfg: &mut Config,
event_loop: &tao::event_loop::EventLoopWindowTarget<UserWindowEvent>,
is_ready: Arc<AtomicBool>,
proxy: tao::event_loop::EventLoopProxy<UserWindowEvent>,
eval_sender: tokio::sync::mpsc::UnboundedSender<serde_json::Value>,
event_tx: UnboundedSender<serde_json::Value>,
) -> 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();
// 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() {
cfg.window.window.window_icon = Some(
window.set_window_icon(Some(
tao::window::Icon::from_rgba(
include_bytes!("./assets/default_icon.bin").to_vec(),
460,
460,
)
.expect("image parse failed"),
);
));
}
event_loop.run(move |window_event, event_loop, control_flow| {
*control_flow = ControlFlow::Wait;
match window_event {
Event::NewEvents(StartCause::Init) => {
let builder = cfg.window.clone();
let window = builder.build(event_loop).unwrap();
let window_id = window.id();
let (is_ready, sender, eval_sender) = (
desktop.is_ready.clone(),
desktop.sender.clone(),
desktop.eval_sender.clone(),
);
let proxy = proxy.clone();
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 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| {
parse_ipc_message(&payload)
.map(|message| match message.method() {
"eval_result" => {
let result = message.params();
eval_sender.send(result).unwrap();
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| {
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());
}
"initialize" => {
is_ready.store(true, std::sync::atomic::Ordering::Relaxed);
let _ = proxy.send_event(UserWindowEvent::EditsReady);
}
"browser_open" => {
let data = message.params();
log::trace!("Open browser: {:?}", data);
if let Some(temp) = data.as_object() {
if temp.contains_key("href") {
let url = temp.get("href").unwrap().as_str().unwrap();
if let Err(e) = webbrowser::open(url) {
log::error!("Open Browser error: {:?}", e);
}
"user_event" => {
let event = trigger_from_serialized(message.params());
log::trace!("User event: {:?}", event);
sender.unbounded_send(SchedulerMsg::Event(event)).unwrap();
}
"initialize" => {
is_ready.store(true, std::sync::atomic::Ordering::Relaxed);
let _ = proxy.send_event(UserWindowEvent::Update);
}
"browser_open" => {
let data = message.params();
log::trace!("Open browser: {:?}", data);
if let Some(temp) = data.as_object() {
if temp.contains_key("href") {
let url = temp.get("href").unwrap().as_str().unwrap();
if let Err(e) = webbrowser::open(url) {
log::error!("Open Browser error: {:?}", e);
}
}
}
}
_ => (),
})
.unwrap_or_else(|| {
log::warn!("invalid IPC message received");
});
})
.with_custom_protocol(String::from("dioxus"), move |r| {
protocol::desktop_handler(
r,
resource_dir.clone(),
custom_head.clone(),
index_file.clone(),
)
})
.with_file_drop_handler(move |window, evet| {
file_handler
.as_ref()
.map(|handler| handler(window, evet))
.unwrap_or_default()
});
}
}
}
_ => (),
})
.unwrap_or_else(|| {
log::warn!("invalid IPC message received");
});
})
.with_custom_protocol(String::from("dioxus"), move |r| {
protocol::desktop_handler(
r,
resource_dir.clone(),
custom_head.clone(),
index_file.clone(),
)
})
.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)
}
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 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();
@@ -211,32 +247,11 @@ pub fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cf
});
}
"#,
)
} else {
// in debug, we are okay with the reload menu showing and dev tool
webview = webview.with_devtools(true);
}
)
} else {
// in debug, we are okay with the reload menu showing and dev tool
webview = webview.with_devtools(true);
}
desktop.webviews.insert(window_id, webview.build().unwrap());
}
Event::WindowEvent {
event, window_id, ..
} => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::Destroyed { .. } => desktop.close_window(window_id, control_flow),
_ => {}
},
Event::UserEvent(user_event) => {
desktop_context::handler(user_event, &mut desktop, control_flow)
}
Event::MainEventsCleared => {}
Event::Resumed => {}
Event::Suspended => {}
Event::LoopDestroyed => {}
Event::RedrawRequested(_id) => {}
_ => {}
}
})
webview.build().unwrap()
}
+1 -1
View File
@@ -7,7 +7,7 @@ use wry::{
const MODULE_LOADER: &str = r#"
<script>
import("./index.js").then(function (module) {
module.main();
module.main();
});
</script>
"#;