chore: clean up desktop

This commit is contained in:
Jonathan Kelley committed 2022-12-06 16:37:28 -08:00
1 parent 67a6fa9eb8
commit 1b14b309e9
19 files changed
+305 -298

No files matched your search

+10 -22
View File
@@ -25,6 +25,7 @@ fn app(cx: Scope) -> Element {
if val.get() == "0" { if val.get() == "0" {
val.set(String::new()); val.set(String::new());
} }
val.make_mut().push_str(num.to_string().as_str()); val.make_mut().push_str(num.to_string().as_str());
}; };
@@ -99,12 +100,8 @@ fn app(cx: Scope) -> Element {
} }
} }
div { class: "digit-keys", div { class: "digit-keys",
button { class: "calculator-key key-0", onclick: move |_| input_digit(0), button { class: "calculator-key key-0", onclick: move |_| input_digit(0), "0" }
"0" button { class: "calculator-key key-dot", onclick: move |_| val.make_mut().push('.'), "●" }
}
button { class: "calculator-key key-dot", onclick: move |_| val.make_mut().push('.'),
"●"
}
(1..10).map(|k| rsx!{ (1..10).map(|k| rsx!{
button { button {
class: "calculator-key {k}", class: "calculator-key {k}",
@@ -116,22 +113,13 @@ fn app(cx: Scope) -> Element {
} }
} }
div { class: "operator-keys", div { class: "operator-keys",
button { class: "calculator-key key-divide", onclick: move |_| input_operator("/"), button { class: "calculator-key key-divide", onclick: move |_| input_operator("/"), "÷" }
"÷" button { class: "calculator-key key-multiply", onclick: move |_| input_operator("*"), "×" }
} button { class: "calculator-key key-subtract", onclick: move |_| input_operator("-"), "−" }
button { class: "calculator-key key-multiply", onclick: move |_| input_operator("*"), button { class: "calculator-key key-add", onclick: move |_| input_operator("+"), "+" }
"×" button {
} class: "calculator-key key-equals",
button { class: "calculator-key key-subtract", onclick: move |_| input_operator("-"), onclick: move |_| val.set(format!("{}", calc_val(val.as_str()))),
"−"
}
button { class: "calculator-key key-add", onclick: move |_| input_operator("+"),
"+"
}
button { class: "calculator-key key-equals",
onclick: move |_| {
val.set(format!("{}", calc_val(val.as_str())));
},
"=" "="
} }
} }
+2 -2
View File
@@ -5,7 +5,7 @@ fn main() {
} }
fn app(cx: Scope) -> Element { fn app(cx: Scope) -> Element {
let login = use_callback!(cx, || move |evt: MouseEvent| async move { let login = use_callback!(cx, move |_| async move {
let res = reqwest::get("https://dog.ceo/api/breeds/list/all") let res = reqwest::get("https://dog.ceo/api/breeds/list/all")
.await .await
.unwrap() .unwrap()
@@ -13,7 +13,7 @@ fn app(cx: Scope) -> Element {
.await .await
.unwrap(); .unwrap();
println!("{}, ", res); println!("{:#?}, ", res);
}); });
cx.render(rsx! { cx.render(rsx! {
+1 -1
View File
@@ -22,7 +22,7 @@ fn app(cx: Scope) -> Element {
button { button {
onclick: move |evt| { onclick: move |evt| {
println!("clicked! bottom no bubbling"); println!("clicked! bottom no bubbling");
evt.cancel_bubble(); evt.stop_propogation();
}, },
"Dont propogate" "Dont propogate"
} }
+1 -1
View File
@@ -6,7 +6,7 @@ use dioxus_ssr::config::SsrConfig;
fn main() { fn main() {
let mut vdom = VirtualDom::new(example); let mut vdom = VirtualDom::new(example);
vdom.rebuild(); _ = vdom.rebuild();
let out = dioxus_ssr::render_vdom_cfg(&vdom, SsrConfig::default().newline(true).indent(true)); let out = dioxus_ssr::render_vdom_cfg(&vdom, SsrConfig::default().newline(true).indent(true));
println!("{}", out); println!("{}", out);
-32
View File
@@ -1,32 +0,0 @@
use dioxus::prelude::*;
fn main() {
dioxus_desktop::launch(app);
}
fn app(cx: Scope) -> Element {
let mut idx = use_state(cx, || 0);
let onhover = |h| println!("go!");
cx.render(rsx! {
div {
button { onclick: move |_| idx += 1, "+" }
button { onclick: move |_| idx -= 1, "-" }
ul {
(0..**idx).map(|i| rsx! {
Child { i: i, onhover: onhover }
})
}
}
})
}
#[inline_props]
fn Child<'a>(cx: Scope<'a>, i: i32, onhover: EventHandler<'a, MouseEvent>) -> Element {
cx.render(rsx! {
li {
onmouseover: move |e| onhover.call(e),
"{i}"
}
})
}
-16
View File
@@ -1,16 +0,0 @@
use dioxus::prelude::*;
use dioxus_desktop::{Config, LogicalSize, WindowBuilder};
fn main() {
dioxus_desktop::launch(|cx| cx.render(rsx! { async_app {} }));
}
async fn async_app(cx: Scope<'_>) -> Element {
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
cx.render(rsx! {
div {
"hi!"
}
})
}
+6 -6
View File
@@ -35,13 +35,13 @@ fn app(cx: Scope) -> Element {
nav { class: "md:ml-auto flex flex-wrap items-center text-base justify-center" } nav { class: "md:ml-auto flex flex-wrap items-center text-base justify-center" }
button { button {
class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0", class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0",
onmousedown: |evt| evt.cancel_bubble(), onmousedown: |evt| evt.stop_propogation(),
onclick: move |_| window.set_minimized(true), onclick: move |_| window.set_minimized(true),
"Minimize" "Minimize"
} }
button { button {
class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0", class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0",
onmousedown: |evt| evt.cancel_bubble(), onmousedown: |evt| evt.stop_propogation(),
onclick: move |_| { onclick: move |_| {
window.set_fullscreen(!**fullscreen); window.set_fullscreen(!**fullscreen);
@@ -52,7 +52,7 @@ fn app(cx: Scope) -> Element {
} }
button { button {
class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0", class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0",
onmousedown: |evt| evt.cancel_bubble(), onmousedown: |evt| evt.stop_propogation(),
onclick: move |_| window.close(), onclick: move |_| window.close(),
"Close" "Close"
} }
@@ -66,7 +66,7 @@ fn app(cx: Scope) -> Element {
div { div {
button { button {
class: "inline-flex items-center text-white bg-green-500 border-0 py-1 px-3 hover:bg-green-700 rounded", class: "inline-flex items-center text-white bg-green-500 border-0 py-1 px-3 hover:bg-green-700 rounded",
onmousedown: |evt| evt.cancel_bubble(), onmousedown: |evt| evt.stop_propogation(),
onclick: move |_| { onclick: move |_| {
window.set_always_on_top(!always_on_top); window.set_always_on_top(!always_on_top);
always_on_top.set(!always_on_top); always_on_top.set(!always_on_top);
@@ -77,7 +77,7 @@ fn app(cx: Scope) -> Element {
div { div {
button { button {
class: "inline-flex items-center text-white bg-blue-500 border-0 py-1 px-3 hover:bg-green-700 rounded", class: "inline-flex items-center text-white bg-blue-500 border-0 py-1 px-3 hover:bg-green-700 rounded",
onmousedown: |evt| evt.cancel_bubble(), onmousedown: |evt| evt.stop_propogation(),
onclick: move |_| { onclick: move |_| {
window.set_decorations(!decorations); window.set_decorations(!decorations);
decorations.set(!decorations); decorations.set(!decorations);
@@ -88,7 +88,7 @@ fn app(cx: Scope) -> Element {
div { div {
button { button {
class: "inline-flex items-center text-white bg-blue-500 border-0 py-1 px-3 hover:bg-green-700 rounded", class: "inline-flex items-center text-white bg-blue-500 border-0 py-1 px-3 hover:bg-green-700 rounded",
onmousedown: |evt| evt.cancel_bubble(), onmousedown: |evt| evt.stop_propogation(),
onclick: move |_| window.set_title("Dioxus Application"), onclick: move |_| window.set_title("Dioxus Application"),
"Change Title" "Change Title"
} }
+12 -3
View File
@@ -159,15 +159,24 @@ pub enum TemplateNode<'a> {
}, },
/// This template node is just a piece of static text /// This template node is just a piece of static text
Text { text: &'a str }, Text {
/// The actual text
text: &'a str,
},
/// This template node is unknown, and needs to be created at runtime. /// This template node is unknown, and needs to be created at runtime.
Dynamic { id: usize }, Dynamic {
/// The index of the dynamic node in the VNode's dynamic_nodes list
id: usize,
},
/// This template node is known to be some text, but needs to be created at runtime /// This template node is known to be some text, but needs to be created at runtime
/// ///
/// This is separate from the pure Dynamic variant for various optimizations /// This is separate from the pure Dynamic variant for various optimizations
DynamicText { id: usize }, DynamicText {
/// The index of the dynamic node in the VNode's dynamic_nodes list
id: usize,
},
} }
/// A node created at runtime /// A node created at runtime
+3 -2
View File
@@ -33,7 +33,7 @@ webbrowser = "0.8.0"
infer = "0.11.0" infer = "0.11.0"
dunce = "1.0.2" dunce = "1.0.2"
interprocess = { version = "1.1.1" } interprocess = { version = "1.1.1", optional = true}
futures-util = "0.3.25" futures-util = "0.3.25"
[target.'cfg(target_os = "ios")'.dependencies] [target.'cfg(target_os = "ios")'.dependencies]
@@ -44,11 +44,12 @@ objc_id = "0.1.1"
core-foundation = "0.9.3" core-foundation = "0.9.3"
[features] [features]
default = ["tokio_runtime"] default = ["tokio_runtime", "hot-reload"]
tokio_runtime = ["tokio"] tokio_runtime = ["tokio"]
fullscreen = ["wry/fullscreen"] fullscreen = ["wry/fullscreen"]
transparent = ["wry/transparent"] transparent = ["wry/transparent"]
tray = ["wry/tray"] tray = ["wry/tray"]
hot-reload = ["interprocess"]
[dev-dependencies] [dev-dependencies]
dioxus-core-macro = { path = "../core-macro" } dioxus-core-macro = { path = "../core-macro" }
+12 -2
View File
@@ -1,7 +1,7 @@
use crate::desktop_context::{DesktopContext, UserWindowEvent}; use crate::desktop_context::{DesktopContext, UserWindowEvent};
use crate::events::{decode_event, EventMessage}; use crate::events::{decode_event, EventMessage};
use dioxus_core::*; use dioxus_core::*;
use futures_channel::mpsc::UnboundedReceiver; use futures_channel::mpsc::{unbounded, UnboundedSender};
use futures_util::StreamExt; use futures_util::StreamExt;
#[cfg(target_os = "ios")] #[cfg(target_os = "ios")]
use objc::runtime::Object; use objc::runtime::Object;
@@ -22,6 +22,9 @@ pub(super) struct DesktopController {
pub(super) pending_edits: Arc<Mutex<Vec<String>>>, pub(super) pending_edits: Arc<Mutex<Vec<String>>>,
pub(super) quit_app_on_close: bool, pub(super) quit_app_on_close: bool,
pub(super) is_ready: Arc<AtomicBool>, pub(super) is_ready: Arc<AtomicBool>,
pub(super) proxy: EventLoopProxy<UserWindowEvent>,
pub(super) event_tx: UnboundedSender<serde_json::Value>,
#[cfg(target_os = "ios")] #[cfg(target_os = "ios")]
pub(super) views: Vec<*mut Object>, pub(super) views: Vec<*mut Object>,
} }
@@ -33,9 +36,10 @@ impl DesktopController {
root: Component<P>, root: Component<P>,
props: P, props: P,
proxy: EventLoopProxy<UserWindowEvent>, proxy: EventLoopProxy<UserWindowEvent>,
mut event_rx: UnboundedReceiver<serde_json::Value>,
) -> Self { ) -> Self {
let edit_queue = Arc::new(Mutex::new(Vec::new())); let edit_queue = Arc::new(Mutex::new(Vec::new()));
let (event_tx, mut event_rx) = unbounded();
let proxy2 = proxy.clone();
let pending_edits = edit_queue.clone(); let pending_edits = edit_queue.clone();
let desktop_context_proxy = proxy.clone(); let desktop_context_proxy = proxy.clone();
@@ -87,6 +91,8 @@ impl DesktopController {
webviews: HashMap::new(), webviews: HashMap::new(),
is_ready: Arc::new(AtomicBool::new(false)), is_ready: Arc::new(AtomicBool::new(false)),
quit_app_on_close: true, quit_app_on_close: true,
proxy: proxy2,
event_tx,
#[cfg(target_os = "ios")] #[cfg(target_os = "ios")]
views: vec![], views: vec![],
} }
@@ -117,4 +123,8 @@ impl DesktopController {
} }
} }
} }
pub(crate) fn set_template(&self, serialized_template: String) {
todo!()
}
} }
+90 -81
View File
@@ -168,6 +168,11 @@ pub enum UserWindowEvent {
DragWindow, DragWindow,
FocusWindow, 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), Visible(bool),
Minimize(bool), Minimize(bool),
Maximize(bool), Maximize(bool),
@@ -195,97 +200,101 @@ pub enum UserWindowEvent {
PopView, PopView,
} }
pub(super) fn handler( impl DesktopController {
user_event: UserWindowEvent, pub(super) fn handle_event(
desktop: &mut DesktopController, &mut self,
control_flow: &mut ControlFlow, 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; // currently dioxus-desktop supports a single window only,
// on wayland it is possible that a user event is emitted // so we can grab the only webview from the map;
// before the webview is initialized. ignore the event. // on wayland it is possible that a user event is emitted
let webview = if let Some(webview) = desktop.webviews.values().next() { // before the webview is initialized. ignore the event.
webview let webview = if let Some(webview) = self.webviews.values().next() {
} else { webview
return; } else {
}; return;
let window = webview.window(); };
match user_event { let window = webview.window();
Initialize | EditsReady => desktop.try_load_ready_webviews(),
CloseWindow => *control_flow = ControlFlow::Exit, match user_event {
DragWindow => { Initialize | EditsReady => self.try_load_ready_webviews(),
// if the drag_window has any errors, we don't do anything SetTemplate(template) => self.set_template(template),
window.fullscreen().is_none().then(|| window.drag_window()); CloseWindow => *control_flow = ControlFlow::Exit,
} DragWindow => {
Visible(state) => window.set_visible(state), // if the drag_window has any errors, we don't do anything
Minimize(state) => window.set_minimized(state), window.fullscreen().is_none().then(|| window.drag_window());
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))));
} }
} Visible(state) => window.set_visible(state),
FocusWindow => window.set_focus(), Minimize(state) => window.set_minimized(state),
Resizable(state) => window.set_resizable(state), Maximize(state) => window.set_maximized(state),
AlwaysOnTop(state) => window.set_always_on_top(state), MaximizeToggle => window.set_maximized(!window.is_maximized()),
Fullscreen(state) => {
CursorVisible(state) => window.set_cursor_visible(state), if let Some(handle) = window.current_monitor() {
CursorGrab(state) => { window.set_fullscreen(state.then_some(WryFullscreen::Borderless(Some(handle))));
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}");
} }
} FocusWindow => window.set_focus(),
DevTool => { Resizable(state) => window.set_resizable(state),
#[cfg(debug_assertions)] AlwaysOnTop(state) => window.set_always_on_top(state),
webview.open_devtools();
#[cfg(not(debug_assertions))]
log::warn!("Devtools are disabled in release builds");
}
Eval(code) => { CursorVisible(state) => window.set_cursor_visible(state),
if let Err(e) = webview.evaluate_script(code.as_str()) { CursorGrab(state) => {
// we can't panic this error. let _ = window.set_cursor_grab(state);
log::warn!("Eval script error: {e}");
} }
}
#[cfg(target_os = "ios")] SetTitle(content) => window.set_title(&content),
PushView(view) => unsafe { SetDecorations(state) => window.set_decorations(state),
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];
let ui_view_controller = window.ui_view_controller() as *mut Object; SetZoomLevel(scale_factor) => webview.zoom(scale_factor),
let _: () = msg_send![ui_view_controller, setView: view];
desktop.views.push(ui_view); 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");
}
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;
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 ui_view_controller = window.ui_view_controller() as *mut Object;
let _: () = msg_send![ui_view_controller, setView: view]; let _: () = msg_send![ui_view_controller, setView: view];
} desktop.views.push(ui_view);
}, },
#[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];
}
},
}
} }
} }
+13 -10
View File
@@ -1,28 +1,31 @@
use dioxus_core::VirtualDom; use dioxus_core::VirtualDom;
use interprocess::local_socket::LocalSocketStream;
// use interprocess::local_socket::{LocalSocketListener, LocalSocketStream}; use interprocess::local_socket::{LocalSocketListener, LocalSocketStream};
use std::io::{BufRead, BufReader}; use std::io::{BufRead, BufReader};
use std::time::Duration; use std::time::Duration;
use std::{sync::Arc, sync::Mutex}; use std::{sync::Arc, sync::Mutex};
fn _handle_error(connection: std::io::Result<LocalSocketStream>) -> Option<LocalSocketStream> { fn handle_error(connection: std::io::Result<LocalSocketStream>) -> Option<LocalSocketStream> {
connection connection
.map_err(|error| eprintln!("Incoming connection failed: {}", error)) .map_err(|error| eprintln!("Incoming connection failed: {}", error))
.ok() .ok()
} }
pub(crate) fn _init(_dom: &VirtualDom) { pub(crate) fn init(_dom: &VirtualDom) {
let latest_in_connection: Arc<Mutex<Option<BufReader<LocalSocketStream>>>> = let latest_in_connection: Arc<Mutex<Option<BufReader<LocalSocketStream>>>> =
Arc::new(Mutex::new(None)); Arc::new(Mutex::new(None));
let _latest_in_connection_handle = latest_in_connection.clone();
let latest_in_connection_handle = latest_in_connection.clone();
// connect to processes for incoming data // connect to processes for incoming data
std::thread::spawn(move || { std::thread::spawn(move || {
// if let Ok(listener) = LocalSocketListener::bind("@dioxusin") { let temp_file = std::env::temp_dir().join("@dioxusin");
// for conn in listener.incoming().filter_map(handle_error) {
// *latest_in_connection_handle.lock().unwrap() = Some(BufReader::new(conn)); 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));
}
}
}); });
std::thread::spawn(move || { std::thread::spawn(move || {
+125 -111
View File
@@ -8,13 +8,17 @@ mod controller;
mod desktop_context; mod desktop_context;
mod escape; mod escape;
mod events; mod events;
#[cfg(any(feature = "hot-reload", debug_assertions))]
mod hot_reload;
mod protocol; 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; use desktop_context::UserWindowEvent;
pub use desktop_context::{use_eval, use_window, DesktopContext}; pub use desktop_context::{use_eval, use_window, DesktopContext};
use futures_channel::mpsc::unbounded; use futures_channel::mpsc::UnboundedSender;
pub use wry; pub use wry;
pub use wry::application as tao; pub use wry::application as tao;
@@ -100,15 +104,68 @@ 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 event_loop = EventLoop::with_user_event();
let mut desktop = DesktopController::new_on_tokio(root, props, event_loop.create_proxy());
let (event_tx, event_rx) = unbounded(); event_loop.run(move |window_event, event_loop, control_flow| {
let mut desktop = *control_flow = ControlFlow::Wait;
DesktopController::new_on_tokio(root, props, event_loop.create_proxy(), event_rx);
let proxy = event_loop.create_proxy();
// We assume that if the icon is None, then the user just didnt set it 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.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>,
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() { if cfg.window.window.window_icon.is_none() {
cfg.window = cfg.window.with_window_icon(Some( window.set_window_icon(Some(
tao::window::Icon::from_rgba( tao::window::Icon::from_rgba(
include_bytes!("./assets/default_icon.bin").to_vec(), include_bytes!("./assets/default_icon.bin").to_vec(),
460, 460,
@@ -118,84 +175,62 @@ pub fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cf
)); ));
} }
event_loop.run(move |window_event, event_loop, control_flow| { let mut webview = WebViewBuilder::new(window)
*control_flow = ControlFlow::Wait; .unwrap()
.with_transparent(cfg.window.window.transparent)
// println!("window event: {:?}", window_event); .with_url("dioxus://index.html/")
match window_event { .unwrap()
Event::NewEvents(StartCause::Init) => { .with_ipc_handler(move |_window: &Window, payload: String| {
let builder = cfg.window.clone(); parse_ipc_message(&payload)
.map(|message| match message.method() {
let window = builder.build(event_loop).unwrap(); "user_event" => {
let window_id = window.id(); _ = event_tx.unbounded_send(message.params());
}
let (is_ready, _) = (desktop.is_ready.clone(), ()); "initialize" => {
// let (is_ready, sender) = (desktop.is_ready.clone(), desktop.sender.clone()); is_ready.store(true, std::sync::atomic::Ordering::Relaxed);
let _ = proxy.send_event(UserWindowEvent::EditsReady);
let proxy = proxy.clone(); }
"browser_open" => {
let file_handler = cfg.file_drop_handler.take(); let data = message.params();
let custom_head = cfg.custom_head.clone(); log::trace!("Open browser: {:?}", data);
let resource_dir = cfg.resource_dir.clone(); if let Some(temp) = data.as_object() {
let index_file = cfg.custom_index.clone(); if temp.contains_key("href") {
let event_tx = event_tx.clone(); let url = temp.get("href").unwrap().as_str().unwrap();
if let Err(e) = webbrowser::open(url) {
let mut webview = WebViewBuilder::new(window) log::error!("Open Browser error: {:?}", e);
.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() {
"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(); .unwrap_or_else(|| {
log::trace!("Open browser: {:?}", data); log::warn!("invalid IPC message received");
if let Some(temp) = data.as_object() { });
if temp.contains_key("href") { })
let url = temp.get("href").unwrap().as_str().unwrap(); .with_custom_protocol(String::from("dioxus"), move |r| {
if let Err(e) = webbrowser::open(url) { protocol::desktop_handler(
log::error!("Open Browser error: {:?}", e); r,
} resource_dir.clone(),
} custom_head.clone(),
} index_file.clone(),
} )
_ => (), })
}) .with_file_drop_handler(move |window, evet| {
.unwrap_or_else(|| { file_handler
log::warn!("invalid IPC message received"); .as_ref()
}); .map(|handler| handler(window, evet))
}) .unwrap_or_default()
.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(..) { for (name, handler) in cfg.protocols.drain(..) {
webview = webview.with_custom_protocol(name, handler) webview = webview.with_custom_protocol(name, handler)
} }
if cfg.disable_context_menu { if cfg.disable_context_menu {
// in release mode, we don't want to show the dev tool or reload menus // in release mode, we don't want to show the dev tool or reload menus
webview = webview.with_initialization_script( webview = webview.with_initialization_script(
r#" r#"
if (document.addEventListener) { if (document.addEventListener) {
document.addEventListener('contextmenu', function(e) { document.addEventListener('contextmenu', function(e) {
e.preventDefault(); e.preventDefault();
@@ -206,32 +241,11 @@ pub fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cf
}); });
} }
"#, "#,
) )
} else { } else {
// in debug, we are okay with the reload menu showing and dev tool // in debug, we are okay with the reload menu showing and dev tool
webview = webview.with_devtools(true); webview = webview.with_devtools(true);
} }
desktop.webviews.insert(window_id, webview.build().unwrap()); 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) => {}
_ => {}
}
})
} }
+6
View File
@@ -17,6 +17,12 @@ macro_rules! use_callback {
move || $($rest)* move || $($rest)*
) )
}; };
($cx:ident, $($rest:tt)*) => {
use_callback(
$cx,
move || $($rest)*
)
};
} }
pub fn use_callback<'a, T, R, F>(cx: &'a ScopeState, make: impl FnOnce() -> R) -> impl FnMut(T) + 'a pub fn use_callback<'a, T, R, F>(cx: &'a ScopeState, make: impl FnOnce() -> R) -> impl FnMut(T) + 'a
where where
+3
View File
@@ -17,6 +17,9 @@ extern "C" {
#[wasm_bindgen(method)] #[wasm_bindgen(method)]
pub fn MountToRoot(this: &Interpreter); pub fn MountToRoot(this: &Interpreter);
#[wasm_bindgen(method)]
pub fn AppendChildren(this: &Interpreter, m: u32);
#[wasm_bindgen(method)] #[wasm_bindgen(method)]
pub fn AssignId(this: &Interpreter, path: &[u8], id: u32); pub fn AssignId(this: &Interpreter, path: &[u8], id: u32);
+10 -2
View File
@@ -210,7 +210,6 @@ export class Interpreter {
} }
} }
handleEdits(edits) { handleEdits(edits) {
for (let template of edits.templates) { for (let template of edits.templates) {
this.SaveTemplate(template); this.SaveTemplate(template);
} }
@@ -276,7 +275,16 @@ export class Interpreter {
} }
HydrateText(path, value, id) { HydrateText(path, value, id) {
let node = this.LoadChild(path); let node = this.LoadChild(path);
node.textContent = value;
if (node.nodeType == Node.TEXT_NODE) {
node.textContent = value;
} else {
// replace with a textnode
let text = document.createTextNode(value);
node.replaceWith(text);
node = text;
}
this.nodes[id] = node; this.nodes[id] = node;
} }
ReplacePlaceholder(path, m) { ReplacePlaceholder(path, m) {
+5
View File
@@ -220,7 +220,12 @@ impl<S: State> RealDom<S> {
let node = self.tree.get_mut(node_id).unwrap(); let node = self.tree.get_mut(node_id).unwrap();
if let NodeType::Text { text } = &mut node.node_data.node_type { if let NodeType::Text { text } = &mut node.node_data.node_type {
*text = value.to_string(); *text = value.to_string();
} else {
node.node_data.node_type = NodeType::Text {
text: value.to_string(),
};
} }
mark_dirty(node_id, NodeMask::new().with_text(), &mut nodes_updated); mark_dirty(node_id, NodeMask::new().with_text(), &mut nodes_updated);
} }
LoadTemplate { name, index, id } => { LoadTemplate { name, index, id } => {
+5 -7
View File
@@ -14,15 +14,14 @@ use futures_channel::mpsc;
use js_sys::Function; use js_sys::Function;
use std::{any::Any, rc::Rc}; use std::{any::Any, rc::Rc};
use wasm_bindgen::{closure::Closure, JsCast, JsValue}; use wasm_bindgen::{closure::Closure, JsCast, JsValue};
use web_sys::{Document, Element, Event, HtmlElement}; use web_sys::{Document, Element, Event};
use crate::Config; use crate::Config;
pub struct WebsysDom { pub struct WebsysDom {
document: Document,
interpreter: Interpreter, interpreter: Interpreter,
handler: Closure<dyn FnMut(&Event)>, handler: Closure<dyn FnMut(&Event)>,
root: Element, _root: Element,
} }
impl WebsysDom { impl WebsysDom {
@@ -36,9 +35,8 @@ impl WebsysDom {
}; };
Self { Self {
document,
interpreter: Interpreter::new(root.clone()), interpreter: Interpreter::new(root.clone()),
root, _root: root,
handler: Closure::wrap(Box::new(move |event: &web_sys::Event| { handler: Closure::wrap(Box::new(move |event: &web_sys::Event| {
let _ = event_channel.unbounded_send(event.clone()); let _ = event_channel.unbounded_send(event.clone());
})), })),
@@ -63,7 +61,7 @@ impl WebsysDom {
let i = &self.interpreter; let i = &self.interpreter;
for edit in edits.drain(..) { for edit in edits.drain(..) {
match edit { match edit {
AppendChildren { id, m } => i.AppendChildren(id, m), AppendChildren { id, m } => i.AppendChildren(m as u32),
AssignId { path, id } => i.AssignId(path, id.0 as u32), AssignId { path, id } => i.AssignId(path, id.0 as u32),
CreatePlaceholder { id } => i.CreatePlaceholder(id.0 as u32), CreatePlaceholder { id } => i.CreatePlaceholder(id.0 as u32),
CreateTextNode { value, id } => i.CreateTextNode(value.into(), id.0 as u32), CreateTextNode { value, id } => i.CreateTextNode(value.into(), id.0 as u32),
@@ -87,8 +85,8 @@ impl WebsysDom {
self.interpreter.NewEventListener( self.interpreter.NewEventListener(
name, name,
id.0 as u32, id.0 as u32,
self.handler.as_ref().unchecked_ref(),
event_bubbles(&name[2..]), event_bubbles(&name[2..]),
self.handler.as_ref().unchecked_ref(),
); );
} }
RemoveEventListener { name, id } => i.RemoveEventListener(name, id.0 as u32), RemoveEventListener { name, id } => i.RemoveEventListener(name, id.0 as u32),
+1
View File
@@ -211,6 +211,7 @@ pub async fn run_with_props<T: 'static>(root: fn(Scope<T>) -> Element, root_prop
let mut res = { let mut res = {
let work = dom.wait_for_work().fuse(); let work = dom.wait_for_work().fuse();
pin_mut!(work); pin_mut!(work);
futures_util::select! { futures_util::select! {
_ = work => None, _ = work => None,
new_template = hotreload_rx.next() => { new_template = hotreload_rx.next() => {