chore: clean up desktop

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

View file

@ -25,6 +25,7 @@ fn app(cx: Scope) -> Element {
if val.get() == "0" {
val.set(String::new());
}
val.make_mut().push_str(num.to_string().as_str());
};
@ -99,12 +100,8 @@ fn app(cx: Scope) -> Element {
}
}
div { class: "digit-keys",
button { class: "calculator-key key-0", onclick: move |_| input_digit(0),
"0"
}
button { class: "calculator-key key-dot", onclick: move |_| val.make_mut().push('.'),
""
}
button { class: "calculator-key key-0", onclick: move |_| input_digit(0), "0" }
button { class: "calculator-key key-dot", onclick: move |_| val.make_mut().push('.'), "" }
(1..10).map(|k| rsx!{
button {
class: "calculator-key {k}",
@ -116,22 +113,13 @@ fn app(cx: Scope) -> Element {
}
}
div { class: "operator-keys",
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-add", onclick: move |_| input_operator("+"),
"+"
}
button { class: "calculator-key key-equals",
onclick: move |_| {
val.set(format!("{}", calc_val(val.as_str())));
},
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-add", onclick: move |_| input_operator("+"), "+" }
button {
class: "calculator-key key-equals",
onclick: move |_| val.set(format!("{}", calc_val(val.as_str()))),
"="
}
}

View file

@ -5,7 +5,7 @@ fn main() {
}
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")
.await
.unwrap()
@ -13,7 +13,7 @@ fn app(cx: Scope) -> Element {
.await
.unwrap();
println!("{}, ", res);
println!("{:#?}, ", res);
});
cx.render(rsx! {

View file

@ -22,7 +22,7 @@ fn app(cx: Scope) -> Element {
button {
onclick: move |evt| {
println!("clicked! bottom no bubbling");
evt.cancel_bubble();
evt.stop_propogation();
},
"Dont propogate"
}

View file

@ -6,7 +6,7 @@ use dioxus_ssr::config::SsrConfig;
fn main() {
let mut vdom = VirtualDom::new(example);
vdom.rebuild();
_ = vdom.rebuild();
let out = dioxus_ssr::render_vdom_cfg(&vdom, SsrConfig::default().newline(true).indent(true));
println!("{}", out);

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}"
}
})
}

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!"
}
})
}

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" }
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",
onmousedown: |evt| evt.cancel_bubble(),
onmousedown: |evt| evt.stop_propogation(),
onclick: move |_| window.set_minimized(true),
"Minimize"
}
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",
onmousedown: |evt| evt.cancel_bubble(),
onmousedown: |evt| evt.stop_propogation(),
onclick: move |_| {
window.set_fullscreen(!**fullscreen);
@ -52,7 +52,7 @@ fn app(cx: Scope) -> Element {
}
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",
onmousedown: |evt| evt.cancel_bubble(),
onmousedown: |evt| evt.stop_propogation(),
onclick: move |_| window.close(),
"Close"
}
@ -66,7 +66,7 @@ fn app(cx: Scope) -> Element {
div {
button {
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 |_| {
window.set_always_on_top(!always_on_top);
always_on_top.set(!always_on_top);
@ -77,7 +77,7 @@ fn app(cx: Scope) -> Element {
div {
button {
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_decorations(!decorations);
decorations.set(!decorations);
@ -88,7 +88,7 @@ fn app(cx: Scope) -> Element {
div {
button {
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"),
"Change Title"
}

View file

@ -159,15 +159,24 @@ pub enum TemplateNode<'a> {
},
/// 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.
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 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

View file

@ -33,7 +33,7 @@ webbrowser = "0.8.0"
infer = "0.11.0"
dunce = "1.0.2"
interprocess = { version = "1.1.1" }
interprocess = { version = "1.1.1", optional = true}
futures-util = "0.3.25"
[target.'cfg(target_os = "ios")'.dependencies]
@ -44,11 +44,12 @@ objc_id = "0.1.1"
core-foundation = "0.9.3"
[features]
default = ["tokio_runtime"]
default = ["tokio_runtime", "hot-reload"]
tokio_runtime = ["tokio"]
fullscreen = ["wry/fullscreen"]
transparent = ["wry/transparent"]
tray = ["wry/tray"]
hot-reload = ["interprocess"]
[dev-dependencies]
dioxus-core-macro = { path = "../core-macro" }

View file

@ -1,7 +1,7 @@
use crate::desktop_context::{DesktopContext, UserWindowEvent};
use crate::events::{decode_event, EventMessage};
use dioxus_core::*;
use futures_channel::mpsc::UnboundedReceiver;
use futures_channel::mpsc::{unbounded, UnboundedSender};
use futures_util::StreamExt;
#[cfg(target_os = "ios")]
use objc::runtime::Object;
@ -22,6 +22,9 @@ pub(super) struct DesktopController {
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>,
}
@ -33,9 +36,10 @@ impl DesktopController {
root: Component<P>,
props: P,
proxy: EventLoopProxy<UserWindowEvent>,
mut event_rx: UnboundedReceiver<serde_json::Value>,
) -> Self {
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 desktop_context_proxy = proxy.clone();
@ -87,6 +91,8 @@ impl DesktopController {
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![],
}
@ -117,4 +123,8 @@ impl DesktopController {
}
}
}
pub(crate) fn set_template(&self, serialized_template: String) {
todo!()
}
}

View file

@ -168,6 +168,11 @@ pub enum UserWindowEvent {
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),
@ -195,24 +200,27 @@ pub enum UserWindowEvent {
PopView,
}
pub(super) fn handler(
impl DesktopController {
pub(super) fn handle_event(
&mut self,
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() {
let webview = if let Some(webview) = self.webviews.values().next() {
webview
} else {
return;
};
let window = webview.window();
match user_event {
Initialize | EditsReady => desktop.try_load_ready_webviews(),
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
@ -287,6 +295,7 @@ pub(super) fn handler(
}
},
}
}
}
#[cfg(target_os = "ios")]

View file

@ -1,28 +1,31 @@
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::time::Duration;
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
.map_err(|error| eprintln!("Incoming connection failed: {}", error))
.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();
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") {
// for conn in listener.incoming().filter_map(handle_error) {
// *latest_in_connection_handle.lock().unwrap() = Some(BufReader::new(conn));
// }
// }
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));
}
}
});
std::thread::spawn(move || {

View file

@ -8,13 +8,17 @@ 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};
use futures_channel::mpsc::unbounded;
use futures_channel::mpsc::UnboundedSender;
pub use wry;
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) {
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();
let mut desktop =
DesktopController::new_on_tokio(root, props, event_loop.create_proxy(), event_rx);
let proxy = event_loop.create_proxy();
event_loop.run(move |window_event, event_loop, control_flow| {
*control_flow = ControlFlow::Wait;
// 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() {
cfg.window = cfg.window.with_window_icon(Some(
window.set_window_icon(Some(
tao::window::Icon::from_rgba(
include_bytes!("./assets/default_icon.bin").to_vec(),
460,
@ -118,28 +175,6 @@ 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| {
*control_flow = ControlFlow::Wait;
// println!("window event: {:?}", window_event);
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, _) = (desktop.is_ready.clone(), ());
// let (is_ready, sender) = (desktop.is_ready.clone(), desktop.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 event_tx = event_tx.clone();
let mut webview = WebViewBuilder::new(window)
.unwrap()
.with_transparent(cfg.window.window.transparent)
@ -212,26 +247,5 @@ pub fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cf
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()
}

View file

@ -17,6 +17,12 @@ macro_rules! use_callback {
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
where

View file

@ -17,6 +17,9 @@ extern "C" {
#[wasm_bindgen(method)]
pub fn MountToRoot(this: &Interpreter);
#[wasm_bindgen(method)]
pub fn AppendChildren(this: &Interpreter, m: u32);
#[wasm_bindgen(method)]
pub fn AssignId(this: &Interpreter, path: &[u8], id: u32);

View file

@ -210,7 +210,6 @@ export class Interpreter {
}
}
handleEdits(edits) {
for (let template of edits.templates) {
this.SaveTemplate(template);
}
@ -276,7 +275,16 @@ export class Interpreter {
}
HydrateText(path, value, id) {
let node = this.LoadChild(path);
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;
}
ReplacePlaceholder(path, m) {

View file

@ -220,7 +220,12 @@ impl<S: State> RealDom<S> {
let node = self.tree.get_mut(node_id).unwrap();
if let NodeType::Text { text } = &mut node.node_data.node_type {
*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);
}
LoadTemplate { name, index, id } => {

View file

@ -14,15 +14,14 @@ use futures_channel::mpsc;
use js_sys::Function;
use std::{any::Any, rc::Rc};
use wasm_bindgen::{closure::Closure, JsCast, JsValue};
use web_sys::{Document, Element, Event, HtmlElement};
use web_sys::{Document, Element, Event};
use crate::Config;
pub struct WebsysDom {
document: Document,
interpreter: Interpreter,
handler: Closure<dyn FnMut(&Event)>,
root: Element,
_root: Element,
}
impl WebsysDom {
@ -36,9 +35,8 @@ impl WebsysDom {
};
Self {
document,
interpreter: Interpreter::new(root.clone()),
root,
_root: root,
handler: Closure::wrap(Box::new(move |event: &web_sys::Event| {
let _ = event_channel.unbounded_send(event.clone());
})),
@ -63,7 +61,7 @@ impl WebsysDom {
let i = &self.interpreter;
for edit in edits.drain(..) {
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),
CreatePlaceholder { id } => i.CreatePlaceholder(id.0 as u32),
CreateTextNode { value, id } => i.CreateTextNode(value.into(), id.0 as u32),
@ -87,8 +85,8 @@ impl WebsysDom {
self.interpreter.NewEventListener(
name,
id.0 as u32,
self.handler.as_ref().unchecked_ref(),
event_bubbles(&name[2..]),
self.handler.as_ref().unchecked_ref(),
);
}
RemoveEventListener { name, id } => i.RemoveEventListener(name, id.0 as u32),

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 work = dom.wait_for_work().fuse();
pin_mut!(work);
futures_util::select! {
_ = work => None,
new_template = hotreload_rx.next() => {