mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-09-20 14:21:58 +00:00
Merge branch 'master' into jk/partialexpansion
This commit is contained in:
commit
b2472e3cfe
35 changed files with 503 additions and 417 deletions
18
Cargo.toml
18
Cargo.toml
|
@ -37,6 +37,12 @@ web = ["dioxus-web"]
|
|||
desktop = ["dioxus-desktop"]
|
||||
router = ["dioxus-router"]
|
||||
|
||||
devtool = ["dioxus-desktop/devtool"]
|
||||
fullscreen = ["dioxus-desktop/fullscreen"]
|
||||
transparent = ["dioxus-desktop/transparent"]
|
||||
|
||||
tray = ["dioxus-desktop/tray"]
|
||||
ayatana = ["dioxus-desktop/ayatana"]
|
||||
|
||||
# "dioxus-router/web"
|
||||
# "dioxus-router/desktop"
|
||||
|
@ -60,15 +66,15 @@ members = [
|
|||
]
|
||||
|
||||
[dev-dependencies]
|
||||
futures-util = "0.3.17"
|
||||
futures-util = "0.3.21"
|
||||
log = "0.4.14"
|
||||
num-format = "0.4.0"
|
||||
separator = "0.4.1"
|
||||
serde = { version = "1.0.131", features = ["derive"] }
|
||||
serde = { version = "1.0.136", features = ["derive"] }
|
||||
im-rc = "15.0.0"
|
||||
anyhow = "1.0.51"
|
||||
serde_json = "1.0.73"
|
||||
anyhow = "1.0.53"
|
||||
serde_json = "1.0.79"
|
||||
rand = { version = "0.8.4", features = ["small_rng"] }
|
||||
tokio = { version = "1.14.0", features = ["full"] }
|
||||
reqwest = { version = "0.11.8", features = ["json"] }
|
||||
tokio = { version = "1.16.1", features = ["full"] }
|
||||
reqwest = { version = "0.11.9", features = ["json"] }
|
||||
dioxus = { path = ".", features = ["desktop", "ssr", "router"] }
|
||||
|
|
|
@ -101,10 +101,10 @@ cargo run --example EXAMPLE
|
|||
<table style="width:100%" align="center">
|
||||
<tr >
|
||||
<th><a href="https://dioxuslabs.com/guide/">Tutorial</a></th>
|
||||
<th><a href="https://dioxuslabs.com/reference/web">Web</a></th>
|
||||
<th><a href="https://dioxuslabs.com/reference/desktop/">Desktop</a></th>
|
||||
<th><a href="https://dioxuslabs.com/reference/ssr/">SSR</a></th>
|
||||
<th><a href="https://dioxuslabs.com/reference/mobile/">Mobile</a></th>
|
||||
<th><a href="https://dioxuslabs.com/reference/platforms/web">Web</a></th>
|
||||
<th><a href="https://dioxuslabs.com/reference/platforms/desktop/">Desktop</a></th>
|
||||
<th><a href="https://dioxuslabs.com/reference/platforms/ssr/">SSR</a></th>
|
||||
<th><a href="https://dioxuslabs.com/reference/platforms/mobile/">Mobile</a></th>
|
||||
<th><a href="https://dioxuslabs.com/guide/concepts/managing_state.html">State</a></th>
|
||||
<tr>
|
||||
</table>
|
||||
|
|
|
@ -18,7 +18,7 @@ fn app(cx: Scope) -> Element {
|
|||
|
||||
In general, Dioxus and React share many functional similarities. If this guide is lacking in any general concept or an error message is confusing, React's documentation might be more helpful. We are dedicated to providing a *familiar* toolkit for UI in Rust, so we've chosen to follow in the footsteps of popular UI frameworks (React, Redux, etc). If you know React, then you already know Dioxus. If you don't know either, this guide will still help you!
|
||||
|
||||
> This is an introduction book! For advanced topics, check out the [Reference](https://dioxuslabs.com/reference) instead.
|
||||
> This is an introduction book! For advanced topics, check out the [Reference](/reference) instead.
|
||||
|
||||
## Multiplatform
|
||||
|
||||
|
@ -37,7 +37,7 @@ The Web is the best-supported target platform for Dioxus. To run on the Web, you
|
|||
|
||||
Because the web is a fairly mature platform, we expect there to be very little API churn for web-based features.
|
||||
|
||||
[Jump to the getting started guide for the web.]()
|
||||
[Jump to the getting started guide for the web.](/reference/platforms/web)
|
||||
|
||||
Examples:
|
||||
- [TodoMVC](https://github.com/DioxusLabs/example-projects/tree/master/todomvc)
|
||||
|
@ -55,7 +55,7 @@ For rendering statically to an `.html` file or from a WebServer, then you'll wan
|
|||
let contents = dioxus::ssr::render_vdom(&dom);
|
||||
```
|
||||
|
||||
[Jump to the getting started guide for SSR.]()
|
||||
[Jump to the getting started guide for SSR.](/reference/platforms/ssr)
|
||||
|
||||
Examples:
|
||||
- [Example DocSite](https://github.com/dioxusLabs/docsite)
|
||||
|
@ -68,13 +68,13 @@ The desktop is a powerful target for Dioxus, but is currently limited in capabil
|
|||
|
||||
Desktop APIs will likely be in flux as we figure out better patterns than our ElectronJS counterpart.
|
||||
|
||||
[Jump to the getting started guide for Desktop.]()
|
||||
[Jump to the getting started guide for Desktop.](/reference/platforms/desktop)
|
||||
|
||||
Examples:
|
||||
- [File explorer](https://github.com/dioxusLabs/file-explorer/)
|
||||
- [WiFi scanner](https://github.com/DioxusLabs/example-projects/blob/master/wifi-scanner)
|
||||
|
||||
[![File ExplorerExample](https://github.com/DioxusLabs/file-explorer-example/raw/master/image.png)](https://github.com/dioxusLabs/file-explorer/)
|
||||
[![File ExplorerExample](https://raw.githubusercontent.com/DioxusLabs/example-projects/master/file-explorer/image.png)](https://github.com/DioxusLabs/example-projects/tree/master/file-explorer)
|
||||
|
||||
### Mobile Support
|
||||
---
|
||||
|
@ -82,7 +82,7 @@ Mobile is currently the least-supported renderer target for Dioxus. Mobile apps
|
|||
|
||||
Mobile support is currently best suited for CRUD-style apps, ideally for internal teams who need to develop quickly but don't care much about animations or native widgets.
|
||||
|
||||
[Jump to the getting started guide for Mobile.]()
|
||||
[Jump to the getting started guide for Mobile.](/reference/platforms/mobile)
|
||||
|
||||
Examples:
|
||||
- [Todo App](https://github.com/DioxusLabs/example-projects/blob/master/ios_demo)
|
||||
|
|
|
@ -140,7 +140,7 @@ fn Child(cx: Scope, name: String) -> Element {
|
|||
|
||||
// ✅ Or, use a hashmap with use_ref
|
||||
```rust
|
||||
let ages = use_ref(&cx, |_| HashMap::new());
|
||||
let ages = use_ref(&cx, || HashMap::new());
|
||||
|
||||
names.iter().map(|name| {
|
||||
let age = ages.get(name).unwrap();
|
||||
|
|
|
@ -8,7 +8,7 @@ We'll learn about:
|
|||
- Suggested cargo extensions
|
||||
|
||||
|
||||
For platform-specific guides, check out the [Platform Specific Guides](../platforms/00-index.md).
|
||||
For platform-specific guides, check out the [Platform Specific Guides](/reference/platforms/index.md).
|
||||
|
||||
# Setting up Dioxus
|
||||
|
||||
|
|
|
@ -2,11 +2,12 @@
|
|||
|
||||
- [Introduction](README.md)
|
||||
|
||||
- [Web](web/index.md)
|
||||
- [Desktop](desktop/index.md)
|
||||
- [Mobile](mobile/index.md)
|
||||
- [SSR](ssr/index.md)
|
||||
- [TUI](tui/index.md)
|
||||
- [Platforms](platforms/index.md)
|
||||
- [Web](platforms/web.md)
|
||||
- [Server Side Rendering](platforms/ssr.md)
|
||||
- [Desktop](platforms/desktop.md)
|
||||
- [Mobile](platforms/mobile.md)
|
||||
- [TUI](platforms/tui.md)
|
||||
|
||||
- [Advanced Guides](guide/index.md)
|
||||
- [RSX in Depth](guide/rsx_in_depth.md)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Desktop
|
||||
# Getting Started: Desktop
|
||||
|
||||
One of Dioxus' killer features is the ability to quickly build a native desktop app that looks and feels the same across platforms. Apps built with Dioxus are typically <5mb in size and use existing system resources, so they won't hog extreme amounts of RAM or memory.
|
||||
|
10
docs/reference/src/platforms/index.md
Normal file
10
docs/reference/src/platforms/index.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
# Platforms
|
||||
|
||||
Dioxus supports many different platforms. Below are a list of guides that walk you through setting up Dioxus for a specific platform.
|
||||
|
||||
### Setup Guides
|
||||
- [Web](web.md)
|
||||
- [Server Side Rendering](ssr.md)
|
||||
- [Desktop](desktop.md)
|
||||
- [Mobile](mobile.md)
|
||||
- [TUI](tui.md)
|
|
@ -21,7 +21,7 @@ $ cargo install --git https://github.com/BrainiumLLC/cargo-mobile
|
|||
And then initialize your app for the right platform. Use the `winit` template for now. Right now, there's no "Dioxus" template in cargo-mobile.
|
||||
|
||||
```shell
|
||||
$ cargo moble init
|
||||
$ cargo mobile init
|
||||
```
|
||||
|
||||
We're going to completely clear out the `dependencies` it generates for us, swapping out `winit` with `dioxus-mobile`.
|
|
@ -1,4 +1,4 @@
|
|||
# TUI
|
||||
# Getting Started: TUI
|
||||
|
||||
TUI support is currently quite experimental. Even the project name will change. But, if you're willing to venture into the realm of the unknown, this guide will get you started.
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
# Getting Started: Dioxus for Web
|
||||
# Getting Started: Web
|
||||
|
||||
[*"Pack your things, we're going on an adventure!"*](https://trunkrs.dev)
|
||||
|
48
examples/login_form.rs
Normal file
48
examples/login_form.rs
Normal file
|
@ -0,0 +1,48 @@
|
|||
//! This example demonstrates the following:
|
||||
//! Futures in a callback, Router, and Forms
|
||||
|
||||
use dioxus::events::*;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let onsubmit = move |evt: FormEvent| {
|
||||
cx.spawn(async move {
|
||||
let resp = reqwest::Client::new()
|
||||
.post("http://localhost/login")
|
||||
.form(&[
|
||||
("username", &evt.values["username"]),
|
||||
("password", &evt.values["password"]),
|
||||
])
|
||||
.send()
|
||||
.await;
|
||||
|
||||
match resp {
|
||||
// Parse data from here, such as storing a response token
|
||||
Ok(_data) => println!("Login successful"),
|
||||
|
||||
//Handle any errors from the fetch here
|
||||
Err(_err) => println!("Login failed"),
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
cx.render(rsx! {
|
||||
h1 { "Login" }
|
||||
form {
|
||||
onsubmit: onsubmit,
|
||||
prevent_default: "onsubmit", // Prevent the default behavior of <form> to post
|
||||
|
||||
input { "type": "text" }
|
||||
label { "Username" }
|
||||
br {}
|
||||
input { "type": "password" }
|
||||
label { "Password" }
|
||||
br {}
|
||||
button { "Login" }
|
||||
}
|
||||
})
|
||||
}
|
|
@ -16,7 +16,6 @@ proc-macro = true
|
|||
|
||||
[dependencies]
|
||||
dioxus-macro-inner = { path = "../macro-inner" }
|
||||
once_cell = "1.8"
|
||||
proc-macro-error = "1.0.4"
|
||||
proc-macro2 = { version = "1.0.6" }
|
||||
quote = "1.0"
|
||||
|
|
|
@ -32,7 +32,7 @@ smallvec = "1.6"
|
|||
|
||||
slab = "0.4"
|
||||
|
||||
futures-channel = "0.3"
|
||||
futures-channel = "0.3.21"
|
||||
|
||||
# used for noderefs
|
||||
once_cell = "1.8"
|
||||
|
|
|
@ -13,21 +13,18 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
|
|||
|
||||
[dependencies]
|
||||
dioxus-core = { path = "../core", version = "^0.1.9", features = ["serialize"] }
|
||||
argh = "0.1.4"
|
||||
serde = "1.0.120"
|
||||
serde_json = "1.0.61"
|
||||
thiserror = "1.0.23"
|
||||
log = "0.4.13"
|
||||
html-escape = "0.2.9"
|
||||
wry = "0.12.2"
|
||||
futures-channel = "0.3"
|
||||
tokio = { version = "1.12.0", features = [
|
||||
serde = "1.0.136"
|
||||
serde_json = "1.0.79"
|
||||
thiserror = "1.0.30"
|
||||
log = "0.4.14"
|
||||
wry = { version = "0.13.1" }
|
||||
futures-channel = "0.3.21"
|
||||
tokio = { version = "1.16.1", features = [
|
||||
"sync",
|
||||
"rt-multi-thread",
|
||||
"rt",
|
||||
"time",
|
||||
], optional = true, default-features = false }
|
||||
dioxus-core-macro = { path = "../core-macro", version = "^0.1.7" }
|
||||
dioxus-html = { path = "../html", features = ["serialize"], version = "^0.1.6" }
|
||||
webbrowser = "0.5.5"
|
||||
mime_guess = "2.0.3"
|
||||
|
@ -38,7 +35,15 @@ dioxus-interpreter-js = { path = "../interpreter", version = "^0.0.0" }
|
|||
default = ["tokio_runtime"]
|
||||
tokio_runtime = ["tokio"]
|
||||
|
||||
devtool = ["wry/devtool"]
|
||||
fullscreen = ["wry/fullscreen"]
|
||||
transparent = ["wry/transparent"]
|
||||
|
||||
tray = ["wry/tray"]
|
||||
ayatana = ["wry/ayatana"]
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
dioxus-core-macro = { path = "../core-macro", version = "^0.1.7" }
|
||||
dioxus-hooks = { path = "../hooks" }
|
||||
# image = "0.24.0" # enable this when generating a new desktop image
|
||||
|
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 75 KiB |
|
@ -12,14 +12,14 @@ use wry::{
|
|||
pub(crate) type DynEventHandlerFn = dyn Fn(&mut EventLoop<()>, &mut WebView);
|
||||
|
||||
pub struct DesktopConfig {
|
||||
pub window: WindowBuilder,
|
||||
pub file_drop_handler: Option<Box<dyn Fn(&Window, FileDropEvent) -> bool>>,
|
||||
pub protocols: Vec<WryProtocol>,
|
||||
pub(crate) window: WindowBuilder,
|
||||
pub(crate) file_drop_handler: Option<Box<dyn Fn(&Window, FileDropEvent) -> bool>>,
|
||||
pub(crate) protocols: Vec<WryProtocol>,
|
||||
pub(crate) pre_rendered: Option<String>,
|
||||
pub(crate) event_handler: Option<Box<DynEventHandlerFn>>,
|
||||
}
|
||||
|
||||
pub type WryProtocol = (
|
||||
pub(crate) type WryProtocol = (
|
||||
String,
|
||||
Box<dyn Fn(&HttpRequest) -> WryResult<HttpResponse> + 'static>,
|
||||
);
|
||||
|
@ -88,7 +88,7 @@ impl DesktopConfig {
|
|||
|
||||
impl DesktopConfig {
|
||||
pub(crate) fn with_default_icon(mut self) -> Self {
|
||||
let bin: &[u8] = include_bytes!("default_icon.bin");
|
||||
let bin: &[u8] = include_bytes!("./assets/default_icon.bin");
|
||||
let rgba = Icon::from_rgba(bin.to_owned(), 460, 460).expect("image parse failed");
|
||||
self.window.window.window_icon = Some(rgba);
|
||||
self
|
||||
|
|
104
packages/desktop/src/controller.rs
Normal file
104
packages/desktop/src/controller.rs
Normal file
|
@ -0,0 +1,104 @@
|
|||
use crate::desktop_context::DesktopContext;
|
||||
use crate::user_window_events::UserWindowEvent;
|
||||
use dioxus_core::*;
|
||||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
sync::atomic::AtomicBool,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
use wry::{
|
||||
self,
|
||||
application::{event_loop::ControlFlow, event_loop::EventLoopProxy, window::WindowId},
|
||||
webview::WebView,
|
||||
};
|
||||
|
||||
pub(super) struct DesktopController {
|
||||
pub(super) webviews: HashMap<WindowId, WebView>,
|
||||
pub(super) sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
|
||||
pub(super) pending_edits: Arc<RwLock<VecDeque<String>>>,
|
||||
pub(super) quit_app_on_close: bool,
|
||||
pub(super) is_ready: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl DesktopController {
|
||||
// Launch the virtualdom on its own thread managed by tokio
|
||||
// returns the desktop state
|
||||
pub(super) fn new_on_tokio<P: Send + 'static>(
|
||||
root: Component<P>,
|
||||
props: P,
|
||||
proxy: EventLoopProxy<UserWindowEvent>,
|
||||
) -> Self {
|
||||
let edit_queue = Arc::new(RwLock::new(VecDeque::new()));
|
||||
let pending_edits = edit_queue.clone();
|
||||
|
||||
let (sender, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>();
|
||||
let return_sender = sender.clone();
|
||||
|
||||
let desktop_context_proxy = proxy.clone();
|
||||
std::thread::spawn(move || {
|
||||
// We create the runtime as multithreaded, so you can still "spawn" onto multiple threads
|
||||
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);
|
||||
|
||||
dom.base_scope().provide_context(window_context);
|
||||
|
||||
let edits = dom.rebuild();
|
||||
|
||||
edit_queue
|
||||
.write()
|
||||
.unwrap()
|
||||
.push_front(serde_json::to_string(&edits.edits).unwrap());
|
||||
|
||||
loop {
|
||||
dom.wait_for_work().await;
|
||||
let mut muts = dom.work_with_deadline(|| false);
|
||||
|
||||
while let Some(edit) = muts.pop() {
|
||||
edit_queue
|
||||
.write()
|
||||
.unwrap()
|
||||
.push_front(serde_json::to_string(&edit.edits).unwrap());
|
||||
}
|
||||
|
||||
let _ = proxy.send_event(UserWindowEvent::Update);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
Self {
|
||||
pending_edits,
|
||||
sender: return_sender,
|
||||
webviews: HashMap::new(),
|
||||
is_ready: Arc::new(AtomicBool::new(false)),
|
||||
quit_app_on_close: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn close_window(&mut self, window_id: WindowId, control_flow: &mut ControlFlow) {
|
||||
self.webviews.remove(&window_id);
|
||||
|
||||
if self.webviews.is_empty() && self.quit_app_on_close {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
}
|
||||
|
||||
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.write().unwrap();
|
||||
let (_id, view) = self.webviews.iter_mut().next().unwrap();
|
||||
|
||||
while let Some(edit) = queue.pop_back() {
|
||||
view.evaluate_script(&format!("window.interpreter.handleEdits({})", edit))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,7 +3,8 @@ use std::rc::Rc;
|
|||
use dioxus_core::ScopeState;
|
||||
use wry::application::event_loop::EventLoopProxy;
|
||||
|
||||
use crate::UserWindowEvent;
|
||||
use crate::user_window_events::UserWindowEvent;
|
||||
use UserWindowEvent::*;
|
||||
|
||||
type ProxyType = EventLoopProxy<UserWindowEvent>;
|
||||
|
||||
|
@ -35,75 +36,77 @@ impl DesktopContext {
|
|||
/// onmousedown: move |_| { desktop.drag_window(); }
|
||||
/// ```
|
||||
pub fn drag(&self) {
|
||||
let _ = self.proxy.send_event(UserWindowEvent::DragWindow);
|
||||
let _ = self.proxy.send_event(DragWindow);
|
||||
}
|
||||
|
||||
/// set window minimize state
|
||||
pub fn set_minimized(&self, minimized: bool) {
|
||||
let _ = self.proxy.send_event(UserWindowEvent::Minimize(minimized));
|
||||
let _ = self.proxy.send_event(Minimize(minimized));
|
||||
}
|
||||
|
||||
/// set window maximize state
|
||||
pub fn set_maximized(&self, maximized: bool) {
|
||||
let _ = self.proxy.send_event(UserWindowEvent::Maximize(maximized));
|
||||
let _ = self.proxy.send_event(Maximize(maximized));
|
||||
}
|
||||
|
||||
/// toggle window maximize state
|
||||
pub fn toggle_maximized(&self) {
|
||||
let _ = self.proxy.send_event(MaximizeToggle);
|
||||
}
|
||||
|
||||
/// set window visible or not
|
||||
pub fn set_visible(&self, visible: bool) {
|
||||
let _ = self.proxy.send_event(UserWindowEvent::Visible(visible));
|
||||
let _ = self.proxy.send_event(Visible(visible));
|
||||
}
|
||||
|
||||
/// close window
|
||||
pub fn close(&self) {
|
||||
let _ = self.proxy.send_event(UserWindowEvent::CloseWindow);
|
||||
let _ = self.proxy.send_event(CloseWindow);
|
||||
}
|
||||
|
||||
/// set window to focus
|
||||
pub fn focus(&self) {
|
||||
let _ = self.proxy.send_event(UserWindowEvent::FocusWindow);
|
||||
let _ = self.proxy.send_event(FocusWindow);
|
||||
}
|
||||
|
||||
/// change window to fullscreen
|
||||
pub fn set_fullscreen(&self, fullscreen: bool) {
|
||||
let _ = self
|
||||
.proxy
|
||||
.send_event(UserWindowEvent::Fullscreen(fullscreen));
|
||||
let _ = self.proxy.send_event(Fullscreen(fullscreen));
|
||||
}
|
||||
|
||||
/// set resizable state
|
||||
pub fn set_resizable(&self, resizable: bool) {
|
||||
let _ = self.proxy.send_event(UserWindowEvent::Resizable(resizable));
|
||||
let _ = self.proxy.send_event(Resizable(resizable));
|
||||
}
|
||||
|
||||
/// set the window always on top
|
||||
pub fn set_always_on_top(&self, top: bool) {
|
||||
let _ = self.proxy.send_event(UserWindowEvent::AlwaysOnTop(top));
|
||||
let _ = self.proxy.send_event(AlwaysOnTop(top));
|
||||
}
|
||||
|
||||
// set cursor visible or not
|
||||
pub fn set_cursor_visible(&self, visible: bool) {
|
||||
let _ = self
|
||||
.proxy
|
||||
.send_event(UserWindowEvent::CursorVisible(visible));
|
||||
let _ = self.proxy.send_event(CursorVisible(visible));
|
||||
}
|
||||
|
||||
// set cursor grab
|
||||
pub fn set_cursor_grab(&self, grab: bool) {
|
||||
let _ = self.proxy.send_event(UserWindowEvent::CursorGrab(grab));
|
||||
let _ = self.proxy.send_event(CursorGrab(grab));
|
||||
}
|
||||
|
||||
/// set window title
|
||||
pub fn set_title(&self, title: &str) {
|
||||
let _ = self
|
||||
.proxy
|
||||
.send_event(UserWindowEvent::SetTitle(String::from(title)));
|
||||
let _ = self.proxy.send_event(SetTitle(String::from(title)));
|
||||
}
|
||||
|
||||
/// change window to borderless
|
||||
pub fn set_decorations(&self, decoration: bool) {
|
||||
let _ = self
|
||||
.proxy
|
||||
.send_event(UserWindowEvent::SetDecorations(decoration));
|
||||
let _ = self.proxy.send_event(SetDecorations(decoration));
|
||||
}
|
||||
|
||||
/// opens DevTool window
|
||||
pub fn devtool(&self) {
|
||||
let _ = self.proxy.send_event(DevTool);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//! Convert a serialized event to an event Trigger
|
||||
//!
|
||||
//! Convert a serialized event to an event trigger
|
||||
|
||||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
|
@ -7,27 +6,49 @@ use std::sync::Arc;
|
|||
use dioxus_core::{ElementId, EventPriority, UserEvent};
|
||||
use dioxus_html::on::*;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub(crate) struct IpcMessage {
|
||||
method: String,
|
||||
params: serde_json::Value,
|
||||
}
|
||||
|
||||
impl IpcMessage {
|
||||
pub(crate) fn method(&self) -> &str {
|
||||
self.method.as_str()
|
||||
}
|
||||
|
||||
pub(crate) fn params(self) -> serde_json::Value {
|
||||
self.params
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn parse_ipc_message(payload: &str) -> Option<IpcMessage> {
|
||||
match serde_json::from_str(payload) {
|
||||
Ok(message) => Some(message),
|
||||
Err(e) => {
|
||||
log::error!("could not parse IPC message, error: {e}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
struct ImEvent {
|
||||
event: String,
|
||||
mounted_dom_id: u64,
|
||||
// scope: u64,
|
||||
contents: serde_json::Value,
|
||||
}
|
||||
|
||||
pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent {
|
||||
let ims: Vec<ImEvent> = serde_json::from_value(val).unwrap();
|
||||
|
||||
let ImEvent {
|
||||
event,
|
||||
mounted_dom_id,
|
||||
contents,
|
||||
} = ims.into_iter().next().unwrap();
|
||||
} = serde_json::from_value(val).unwrap();
|
||||
|
||||
// let scope_id = ScopeId(scope as usize);
|
||||
let mounted_dom_id = Some(ElementId(mounted_dom_id as usize));
|
||||
|
||||
let name = event_name_from_typ(&event);
|
||||
let name = event_name_from_type(&event);
|
||||
let event = make_synthetic_event(&event, contents);
|
||||
|
||||
UserEvent {
|
||||
|
@ -105,7 +126,7 @@ fn make_synthetic_event(name: &str, val: serde_json::Value) -> Arc<dyn Any + Sen
|
|||
}
|
||||
}
|
||||
|
||||
fn event_name_from_typ(typ: &str) -> &'static str {
|
||||
fn event_name_from_type(typ: &str) -> &'static str {
|
||||
match typ {
|
||||
"copy" => "copy",
|
||||
"cut" => "cut",
|
||||
|
|
|
@ -3,31 +3,28 @@
|
|||
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
|
||||
pub mod cfg;
|
||||
mod controller;
|
||||
pub mod desktop_context;
|
||||
pub mod escape;
|
||||
pub mod events;
|
||||
mod protocol;
|
||||
mod user_window_events;
|
||||
|
||||
use cfg::DesktopConfig;
|
||||
use controller::DesktopController;
|
||||
pub use desktop_context::use_window;
|
||||
use desktop_context::DesktopContext;
|
||||
use dioxus_core::*;
|
||||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
sync::atomic::AtomicBool,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
use events::parse_ipc_message;
|
||||
use tao::{
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::{Window, WindowId},
|
||||
window::Window,
|
||||
};
|
||||
pub use wry;
|
||||
pub use wry::application as tao;
|
||||
use wry::{
|
||||
application::{event_loop::EventLoopProxy, window::Fullscreen},
|
||||
webview::RpcRequest,
|
||||
webview::{WebView, WebViewBuilder},
|
||||
};
|
||||
use wry::webview::WebViewBuilder;
|
||||
|
||||
use crate::events::trigger_from_serialized;
|
||||
|
||||
/// Launch the WebView and run the event loop.
|
||||
///
|
||||
|
@ -132,23 +129,24 @@ pub fn launch_with_props<P: 'static + Send>(
|
|||
.with_transparent(cfg.window.window.transparent)
|
||||
.with_url("dioxus://index.html/")
|
||||
.unwrap()
|
||||
.with_rpc_handler(move |_window: &Window, req: RpcRequest| {
|
||||
match req.method.as_str() {
|
||||
"user_event" => {
|
||||
let event = events::trigger_from_serialized(req.params.unwrap());
|
||||
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" => {
|
||||
println!("browser_open");
|
||||
let data = req.params.unwrap();
|
||||
log::trace!("Open browser: {:?}", data);
|
||||
if let Some(arr) = data.as_array() {
|
||||
if let Some(temp) = arr[0].as_object() {
|
||||
.with_ipc_handler(move |_window: &Window, payload: String| {
|
||||
parse_ipc_message(&payload)
|
||||
.map(|message| match message.method() {
|
||||
"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(user_window_events::UserWindowEvent::Update);
|
||||
}
|
||||
"browser_open" => {
|
||||
println!("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) {
|
||||
|
@ -157,55 +155,13 @@ pub fn launch_with_props<P: 'static + Send>(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
None
|
||||
})
|
||||
.with_custom_protocol(String::from("dioxus"), move |request| {
|
||||
// Any content that that uses the `dioxus://` scheme will be shuttled through this handler as a "special case"
|
||||
// For now, we only serve two pieces of content which get included as bytes into the final binary.
|
||||
let path = request.uri().replace("dioxus://", "");
|
||||
|
||||
// all assets shouldbe called from index.html
|
||||
let trimmed = path.trim_start_matches("index.html/");
|
||||
|
||||
if trimmed.is_empty() {
|
||||
wry::http::ResponseBuilder::new()
|
||||
.mimetype("text/html")
|
||||
.body(include_bytes!("./index.html").to_vec())
|
||||
} else if trimmed == "index.js" {
|
||||
wry::http::ResponseBuilder::new()
|
||||
.mimetype("text/javascript")
|
||||
.body(dioxus_interpreter_js::INTERPRTER_JS.as_bytes().to_vec())
|
||||
} else {
|
||||
// Read the file content from file path
|
||||
use std::fs::read;
|
||||
|
||||
let path_buf = std::path::Path::new(trimmed).canonicalize()?;
|
||||
let cur_path = std::path::Path::new(".").canonicalize()?;
|
||||
|
||||
if !path_buf.starts_with(cur_path) {
|
||||
return wry::http::ResponseBuilder::new()
|
||||
.status(wry::http::status::StatusCode::FORBIDDEN)
|
||||
.body(String::from("Forbidden").into_bytes());
|
||||
}
|
||||
|
||||
if !path_buf.exists() {
|
||||
return wry::http::ResponseBuilder::new()
|
||||
.status(wry::http::status::StatusCode::NOT_FOUND)
|
||||
.body(String::from("Not Found").into_bytes());
|
||||
}
|
||||
|
||||
let mime = mime_guess::from_path(&path_buf).first_or_octet_stream();
|
||||
|
||||
// do not let path searching to go two layers beyond the caller level
|
||||
let data = read(path_buf)?;
|
||||
let meta = format!("{}", mime);
|
||||
|
||||
wry::http::ResponseBuilder::new().mimetype(&meta).body(data)
|
||||
}
|
||||
_ => (),
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
log::warn!("invalid IPC message received");
|
||||
});
|
||||
})
|
||||
.with_custom_protocol(String::from("dioxus"), protocol::desktop_handler)
|
||||
.with_file_drop_handler(move |window, evet| {
|
||||
file_handler
|
||||
.as_ref()
|
||||
|
@ -235,114 +191,8 @@ pub fn launch_with_props<P: 'static + Send>(
|
|||
_ => {}
|
||||
},
|
||||
|
||||
Event::UserEvent(_evt) => {
|
||||
//
|
||||
match _evt {
|
||||
UserWindowEvent::Update => desktop.try_load_ready_webviews(),
|
||||
UserWindowEvent::DragWindow => {
|
||||
// this loop just run once, because dioxus-desktop is unsupport multi-window.
|
||||
for webview in desktop.webviews.values() {
|
||||
let window = webview.window();
|
||||
// start to drag the window.
|
||||
// if the drag_window have any err. we don't do anything.
|
||||
|
||||
if window.fullscreen().is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
let _ = window.drag_window();
|
||||
}
|
||||
}
|
||||
UserWindowEvent::CloseWindow => {
|
||||
// close window
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
UserWindowEvent::Visible(state) => {
|
||||
for webview in desktop.webviews.values() {
|
||||
let window = webview.window();
|
||||
window.set_visible(state);
|
||||
}
|
||||
}
|
||||
UserWindowEvent::Minimize(state) => {
|
||||
// this loop just run once, because dioxus-desktop is unsupport multi-window.
|
||||
for webview in desktop.webviews.values() {
|
||||
let window = webview.window();
|
||||
// change window minimized state.
|
||||
window.set_minimized(state);
|
||||
}
|
||||
}
|
||||
UserWindowEvent::Maximize(state) => {
|
||||
// this loop just run once, because dioxus-desktop is unsupport multi-window.
|
||||
for webview in desktop.webviews.values() {
|
||||
let window = webview.window();
|
||||
// change window maximized state.
|
||||
window.set_maximized(state);
|
||||
}
|
||||
}
|
||||
UserWindowEvent::Fullscreen(state) => {
|
||||
for webview in desktop.webviews.values() {
|
||||
let window = webview.window();
|
||||
|
||||
let current_monitor = window.current_monitor();
|
||||
|
||||
if current_monitor.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
let fullscreen = if state {
|
||||
Some(Fullscreen::Borderless(current_monitor))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
window.set_fullscreen(fullscreen);
|
||||
}
|
||||
}
|
||||
UserWindowEvent::FocusWindow => {
|
||||
for webview in desktop.webviews.values() {
|
||||
let window = webview.window();
|
||||
window.set_focus();
|
||||
}
|
||||
}
|
||||
UserWindowEvent::Resizable(state) => {
|
||||
for webview in desktop.webviews.values() {
|
||||
let window = webview.window();
|
||||
window.set_resizable(state);
|
||||
}
|
||||
}
|
||||
UserWindowEvent::AlwaysOnTop(state) => {
|
||||
for webview in desktop.webviews.values() {
|
||||
let window = webview.window();
|
||||
window.set_always_on_top(state);
|
||||
}
|
||||
}
|
||||
|
||||
UserWindowEvent::CursorVisible(state) => {
|
||||
for webview in desktop.webviews.values() {
|
||||
let window = webview.window();
|
||||
window.set_cursor_visible(state);
|
||||
}
|
||||
}
|
||||
UserWindowEvent::CursorGrab(state) => {
|
||||
for webview in desktop.webviews.values() {
|
||||
let window = webview.window();
|
||||
let _ = window.set_cursor_grab(state);
|
||||
}
|
||||
}
|
||||
|
||||
UserWindowEvent::SetTitle(content) => {
|
||||
for webview in desktop.webviews.values() {
|
||||
let window = webview.window();
|
||||
window.set_title(&content);
|
||||
}
|
||||
}
|
||||
UserWindowEvent::SetDecorations(state) => {
|
||||
for webview in desktop.webviews.values() {
|
||||
let window = webview.window();
|
||||
window.set_decorations(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::UserEvent(user_event) => {
|
||||
user_window_events::handler(user_event, &mut desktop, control_flow)
|
||||
}
|
||||
Event::MainEventsCleared => {}
|
||||
Event::Resumed => {}
|
||||
|
@ -353,118 +203,3 @@ pub fn launch_with_props<P: 'static + Send>(
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub enum UserWindowEvent {
|
||||
Update,
|
||||
DragWindow,
|
||||
CloseWindow,
|
||||
FocusWindow,
|
||||
Visible(bool),
|
||||
Minimize(bool),
|
||||
Maximize(bool),
|
||||
Resizable(bool),
|
||||
AlwaysOnTop(bool),
|
||||
Fullscreen(bool),
|
||||
|
||||
CursorVisible(bool),
|
||||
CursorGrab(bool),
|
||||
|
||||
SetTitle(String),
|
||||
SetDecorations(bool),
|
||||
}
|
||||
|
||||
pub struct DesktopController {
|
||||
pub proxy: EventLoopProxy<UserWindowEvent>,
|
||||
pub webviews: HashMap<WindowId, WebView>,
|
||||
pub sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
|
||||
pub pending_edits: Arc<RwLock<VecDeque<String>>>,
|
||||
pub quit_app_on_close: bool,
|
||||
pub is_ready: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl DesktopController {
|
||||
// Launch the virtualdom on its own thread managed by tokio
|
||||
// returns the desktop state
|
||||
pub fn new_on_tokio<P: Send + 'static>(
|
||||
root: Component<P>,
|
||||
props: P,
|
||||
evt: EventLoopProxy<UserWindowEvent>,
|
||||
) -> Self {
|
||||
let edit_queue = Arc::new(RwLock::new(VecDeque::new()));
|
||||
let pending_edits = edit_queue.clone();
|
||||
|
||||
let (sender, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>();
|
||||
let return_sender = sender.clone();
|
||||
let proxy = evt.clone();
|
||||
|
||||
let desktop_context_proxy = proxy.clone();
|
||||
std::thread::spawn(move || {
|
||||
// We create the runtime as multithreaded, so you can still "spawn" onto multiple threads
|
||||
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);
|
||||
|
||||
dom.base_scope().provide_context(window_context);
|
||||
|
||||
let edits = dom.rebuild();
|
||||
|
||||
edit_queue
|
||||
.write()
|
||||
.unwrap()
|
||||
.push_front(serde_json::to_string(&edits.edits).unwrap());
|
||||
|
||||
loop {
|
||||
dom.wait_for_work().await;
|
||||
let mut muts = dom.work_with_deadline(|| false);
|
||||
|
||||
while let Some(edit) = muts.pop() {
|
||||
edit_queue
|
||||
.write()
|
||||
.unwrap()
|
||||
.push_front(serde_json::to_string(&edit.edits).unwrap());
|
||||
}
|
||||
|
||||
let _ = evt.send_event(UserWindowEvent::Update);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
Self {
|
||||
pending_edits,
|
||||
sender: return_sender,
|
||||
proxy,
|
||||
webviews: HashMap::new(),
|
||||
is_ready: Arc::new(AtomicBool::new(false)),
|
||||
quit_app_on_close: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close_window(&mut self, window_id: WindowId, control_flow: &mut ControlFlow) {
|
||||
self.webviews.remove(&window_id);
|
||||
|
||||
if self.webviews.is_empty() && self.quit_app_on_close {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_load_ready_webviews(&mut self) {
|
||||
if self.is_ready.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
let mut queue = self.pending_edits.write().unwrap();
|
||||
let (_id, view) = self.webviews.iter_mut().next().unwrap();
|
||||
|
||||
while let Some(edit) = queue.pop_back() {
|
||||
view.evaluate_script(&format!("window.interpreter.handleEdits({})", edit))
|
||||
.unwrap();
|
||||
}
|
||||
} else {
|
||||
println!("waiting for ready");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
47
packages/desktop/src/protocol.rs
Normal file
47
packages/desktop/src/protocol.rs
Normal file
|
@ -0,0 +1,47 @@
|
|||
use std::path::Path;
|
||||
use wry::{
|
||||
http::{status::StatusCode, Request, Response, ResponseBuilder},
|
||||
Result,
|
||||
};
|
||||
|
||||
pub(super) fn desktop_handler(request: &Request) -> Result<Response> {
|
||||
// Any content that uses the `dioxus://` scheme will be shuttled through this handler as a "special case".
|
||||
// For now, we only serve two pieces of content which get included as bytes into the final binary.
|
||||
let path = request.uri().replace("dioxus://", "");
|
||||
|
||||
// all assets should be called from index.html
|
||||
let trimmed = path.trim_start_matches("index.html/");
|
||||
|
||||
if trimmed.is_empty() {
|
||||
ResponseBuilder::new()
|
||||
.mimetype("text/html")
|
||||
.body(include_bytes!("./index.html").to_vec())
|
||||
} else if trimmed == "index.js" {
|
||||
ResponseBuilder::new()
|
||||
.mimetype("text/javascript")
|
||||
.body(dioxus_interpreter_js::INTERPRETER_JS.as_bytes().to_vec())
|
||||
} else {
|
||||
let path_buf = Path::new(trimmed).canonicalize()?;
|
||||
let cur_path = Path::new(".").canonicalize()?;
|
||||
|
||||
if !path_buf.starts_with(cur_path) {
|
||||
return ResponseBuilder::new()
|
||||
.status(StatusCode::FORBIDDEN)
|
||||
.body(String::from("Forbidden").into_bytes());
|
||||
}
|
||||
|
||||
if !path_buf.exists() {
|
||||
return ResponseBuilder::new()
|
||||
.status(StatusCode::NOT_FOUND)
|
||||
.body(String::from("Not Found").into_bytes());
|
||||
}
|
||||
|
||||
let mime = mime_guess::from_path(&path_buf).first_or_octet_stream();
|
||||
|
||||
// do not let path searching to go two layers beyond the caller level
|
||||
let data = std::fs::read(path_buf)?;
|
||||
let meta = format!("{}", mime);
|
||||
|
||||
ResponseBuilder::new().mimetype(&meta).body(data)
|
||||
}
|
||||
}
|
72
packages/desktop/src/user_window_events.rs
Normal file
72
packages/desktop/src/user_window_events.rs
Normal file
|
@ -0,0 +1,72 @@
|
|||
use wry::application::event_loop::ControlFlow;
|
||||
use wry::application::window::Fullscreen as WryFullscreen;
|
||||
|
||||
use crate::controller::DesktopController;
|
||||
|
||||
pub(crate) enum UserWindowEvent {
|
||||
Update,
|
||||
|
||||
CloseWindow,
|
||||
DragWindow,
|
||||
FocusWindow,
|
||||
|
||||
Visible(bool),
|
||||
Minimize(bool),
|
||||
Maximize(bool),
|
||||
MaximizeToggle,
|
||||
Resizable(bool),
|
||||
AlwaysOnTop(bool),
|
||||
Fullscreen(bool),
|
||||
|
||||
CursorVisible(bool),
|
||||
CursorGrab(bool),
|
||||
|
||||
SetTitle(String),
|
||||
SetDecorations(bool),
|
||||
|
||||
DevTool,
|
||||
}
|
||||
|
||||
use UserWindowEvent::*;
|
||||
|
||||
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;
|
||||
let webview = desktop.webviews.values().next().unwrap();
|
||||
let window = webview.window();
|
||||
|
||||
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(|| WryFullscreen::Borderless(Some(handle))));
|
||||
}
|
||||
}
|
||||
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),
|
||||
|
||||
DevTool => webview.devtool(),
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@ export function main() {
|
|||
let root = window.document.getElementById("main");
|
||||
if (root != null) {
|
||||
window.interpreter = new Interpreter(root);
|
||||
window.rpc.call("initialize");
|
||||
window.ipc.postMessage(serializeIpcMessage("initialize"));
|
||||
}
|
||||
}
|
||||
export class Interpreter {
|
||||
|
@ -105,7 +105,7 @@ export class Interpreter {
|
|||
if (ns === "style") {
|
||||
// @ts-ignore
|
||||
node.style[name] = value;
|
||||
} else if (ns != null || ns !== undefined) {
|
||||
} else if (ns != null || ns != undefined) {
|
||||
node.setAttributeNS(ns, name, value);
|
||||
} else {
|
||||
switch (name) {
|
||||
|
@ -207,7 +207,9 @@ export class Interpreter {
|
|||
event.preventDefault();
|
||||
const href = target.getAttribute("href");
|
||||
if (href !== "" && href !== null && href !== undefined) {
|
||||
window.rpc.call("browser_open", { href });
|
||||
window.ipc.postMessage(
|
||||
serializeIpcMessage("browser_open", { href })
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -261,11 +263,13 @@ export class Interpreter {
|
|||
if (realId == null) {
|
||||
return;
|
||||
}
|
||||
window.rpc.call("user_event", {
|
||||
event: edit.event_name,
|
||||
mounted_dom_id: parseInt(realId),
|
||||
contents: contents,
|
||||
});
|
||||
window.ipc.postMessage(
|
||||
serializeIpcMessage("user_event", {
|
||||
event: edit.event_name,
|
||||
mounted_dom_id: parseInt(realId),
|
||||
contents: contents,
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
this.NewEventListener(edit.event_name, edit.root, handler);
|
||||
|
@ -341,6 +345,7 @@ export function serialize_event(event) {
|
|||
}
|
||||
return {
|
||||
value: value,
|
||||
values: {},
|
||||
};
|
||||
}
|
||||
case "input":
|
||||
|
@ -544,6 +549,9 @@ export function serialize_event(event) {
|
|||
}
|
||||
}
|
||||
}
|
||||
function serializeIpcMessage(method, params = {}) {
|
||||
return JSON.stringify({ method, params });
|
||||
}
|
||||
const bool_attrs = {
|
||||
allowfullscreen: true,
|
||||
allowpaymentrequest: true,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
pub static INTERPRTER_JS: &str = include_str!("./interpreter.js");
|
||||
pub static INTERPRETER_JS: &str = include_str!("./interpreter.js");
|
||||
|
||||
#[cfg(feature = "web")]
|
||||
mod bindings;
|
||||
|
|
|
@ -16,8 +16,8 @@ dioxus-html = { path = "../html", version = "^0.1.6", default-features = false }
|
|||
dioxus-core-macro = { path = "../core-macro", version = "^0.1.7" }
|
||||
|
||||
serde = "1"
|
||||
url = "2.2.2"
|
||||
serde_urlencoded = "0.7"
|
||||
# url = "2.2.2"
|
||||
|
||||
# for wasm
|
||||
web-sys = { version = "0.3", features = [
|
||||
|
|
|
@ -34,6 +34,17 @@ pub struct LinkProps<'a> {
|
|||
#[props(default, strip_option)]
|
||||
title: Option<&'a str>,
|
||||
|
||||
#[props(default = true)]
|
||||
autodetect: bool,
|
||||
|
||||
/// Is this link an external link?
|
||||
#[props(default = false)]
|
||||
external: bool,
|
||||
|
||||
/// New tab?
|
||||
#[props(default = false)]
|
||||
new_tab: bool,
|
||||
|
||||
children: Element<'a>,
|
||||
|
||||
#[props(default)]
|
||||
|
@ -41,17 +52,38 @@ pub struct LinkProps<'a> {
|
|||
}
|
||||
|
||||
pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element {
|
||||
// log::trace!("render Link to {}", cx.props.to);
|
||||
if let Some(service) = cx.consume_context::<RouterService>() {
|
||||
let LinkProps {
|
||||
to,
|
||||
href,
|
||||
class,
|
||||
id,
|
||||
title,
|
||||
autodetect,
|
||||
external,
|
||||
new_tab,
|
||||
children,
|
||||
..
|
||||
} = cx.props;
|
||||
|
||||
let is_http = to.starts_with("http") || to.starts_with("https");
|
||||
let outerlink = (*autodetect && is_http) || *external;
|
||||
|
||||
let prevent_default = if outerlink { "" } else { "onclick" };
|
||||
|
||||
return cx.render(rsx! {
|
||||
a {
|
||||
href: "{cx.props.to}",
|
||||
class: format_args!("{}", cx.props.class.unwrap_or("")),
|
||||
id: format_args!("{}", cx.props.id.unwrap_or("")),
|
||||
title: format_args!("{}", cx.props.title.unwrap_or("")),
|
||||
|
||||
prevent_default: "onclick",
|
||||
onclick: move |_| service.push_route(cx.props.to),
|
||||
href: "{to}",
|
||||
class: format_args!("{}", class.unwrap_or("")),
|
||||
id: format_args!("{}", id.unwrap_or("")),
|
||||
title: format_args!("{}", title.unwrap_or("")),
|
||||
prevent_default: "{prevent_default}",
|
||||
target: format_args!("{}", if *new_tab { "_blank" } else { "" }),
|
||||
onclick: move |_| {
|
||||
if !outerlink {
|
||||
service.push_route(to);
|
||||
}
|
||||
},
|
||||
|
||||
&cx.props.children
|
||||
}
|
||||
|
|
|
@ -32,7 +32,5 @@ pub fn Router<'a>(cx: Scope<'a, RouterProps<'a>>) -> Element {
|
|||
cx.props.onchange.call(path.to_string());
|
||||
}
|
||||
|
||||
cx.render(rsx!(
|
||||
div { &cx.props.children }
|
||||
))
|
||||
cx.render(rsx!(&cx.props.children))
|
||||
}
|
||||
|
|
|
@ -106,7 +106,7 @@ impl Drop for UseRouteListener {
|
|||
}
|
||||
|
||||
/// This hook provides access to the `RouterService` for the app.
|
||||
pub fn use_router(cx: &ScopeState) -> &RouterService {
|
||||
pub fn use_router(cx: &ScopeState) -> &Rc<RouterService> {
|
||||
cx.use_hook(|_| {
|
||||
cx.consume_context::<RouterService>()
|
||||
.expect("Cannot call use_route outside the scope of a Router component")
|
||||
|
|
|
@ -15,21 +15,18 @@ dioxus-core = { path = "../core", version = "^0.1.9" }
|
|||
dioxus-html = { path = "../html", version = "^0.1.6" }
|
||||
js-sys = "0.3.56"
|
||||
wasm-bindgen = { version = "0.2.79", features = ["enable-interning"] }
|
||||
lazy_static = "1.4.0"
|
||||
wasm-bindgen-futures = "0.4.29"
|
||||
log = { version = "0.4.14", features = ["release_max_level_off"] }
|
||||
fxhash = "0.2.1"
|
||||
wasm-logger = "0.2.0"
|
||||
console_error_panic_hook = { version = "0.1.7", optional = true }
|
||||
wasm-bindgen-test = "0.3.29"
|
||||
once_cell = "1.9.0"
|
||||
async-channel = "1.6.1"
|
||||
anyhow = "1.0.53"
|
||||
gloo-timers = { version = "0.2.3", features = ["futures"] }
|
||||
futures-util = "0.3.19"
|
||||
smallstr = "0.2.0"
|
||||
dioxus-interpreter-js = { path = "../interpreter", version = "^0.0.0", features = ["web"] }
|
||||
serde-wasm-bindgen = "0.4.2"
|
||||
futures-channel = "0.3.21"
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3.56"
|
||||
|
@ -80,3 +77,5 @@ panic_hook = ["console_error_panic_hook"]
|
|||
dioxus-core-macro = { path = "../core-macro" }
|
||||
wasm-bindgen-test = "0.3.29"
|
||||
dioxus-ssr = { path = "../ssr" }
|
||||
wasm-logger = "0.2.0"
|
||||
|
||||
|
|
|
@ -87,14 +87,11 @@ impl WebsysDom {
|
|||
}
|
||||
});
|
||||
|
||||
// a match here in order to avoid some error during runtime browser test
|
||||
let document = load_document();
|
||||
let root = match document.get_element_by_id(&cfg.rootname) {
|
||||
Some(root) => root,
|
||||
// a match here in order to avoid some error during runtime browser test
|
||||
None => {
|
||||
let body = document.create_element("body").ok().unwrap();
|
||||
body
|
||||
}
|
||||
None => document.create_element("body").ok().unwrap(),
|
||||
};
|
||||
|
||||
Self {
|
||||
|
|
|
@ -211,7 +211,7 @@ pub async fn run_with_props<T: 'static + Send>(root: Component<T>, root_props: T
|
|||
websys_dom.apply_edits(edits.edits);
|
||||
}
|
||||
|
||||
let work_loop = ric_raf::RafLoop::new();
|
||||
let mut work_loop = ric_raf::RafLoop::new();
|
||||
|
||||
loop {
|
||||
log::trace!("waiting for work");
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
//! Because RIC doesn't work on Safari, we polyfill using the "ricpolyfill.js" file and use some basic detection to see
|
||||
//! if RIC is available.
|
||||
|
||||
use futures_util::StreamExt;
|
||||
use gloo_timers::future::TimeoutFuture;
|
||||
use js_sys::Function;
|
||||
use wasm_bindgen::{prelude::Closure, JsCast, JsValue};
|
||||
|
@ -14,21 +15,21 @@ use web_sys::{window, Window};
|
|||
|
||||
pub(crate) struct RafLoop {
|
||||
window: Window,
|
||||
ric_receiver: async_channel::Receiver<u32>,
|
||||
raf_receiver: async_channel::Receiver<()>,
|
||||
ric_receiver: futures_channel::mpsc::UnboundedReceiver<u32>,
|
||||
raf_receiver: futures_channel::mpsc::UnboundedReceiver<()>,
|
||||
ric_closure: Closure<dyn Fn(JsValue)>,
|
||||
raf_closure: Closure<dyn Fn(JsValue)>,
|
||||
}
|
||||
|
||||
impl RafLoop {
|
||||
pub fn new() -> Self {
|
||||
let (raf_sender, raf_receiver) = async_channel::unbounded();
|
||||
let (raf_sender, raf_receiver) = futures_channel::mpsc::unbounded();
|
||||
|
||||
let raf_closure: Closure<dyn Fn(JsValue)> = Closure::wrap(Box::new(move |_v: JsValue| {
|
||||
raf_sender.try_send(()).unwrap()
|
||||
raf_sender.unbounded_send(()).unwrap()
|
||||
}));
|
||||
|
||||
let (ric_sender, ric_receiver) = async_channel::unbounded();
|
||||
let (ric_sender, ric_receiver) = futures_channel::mpsc::unbounded();
|
||||
|
||||
let has_idle_callback = {
|
||||
let bo = window().unwrap().dyn_into::<js_sys::Object>().unwrap();
|
||||
|
@ -45,7 +46,7 @@ impl RafLoop {
|
|||
10
|
||||
};
|
||||
|
||||
ric_sender.try_send(time_remaining).unwrap()
|
||||
ric_sender.unbounded_send(time_remaining).unwrap()
|
||||
}));
|
||||
|
||||
// execute the polyfill for safari
|
||||
|
@ -64,16 +65,16 @@ impl RafLoop {
|
|||
}
|
||||
}
|
||||
/// waits for some idle time and returns a timeout future that expires after the idle time has passed
|
||||
pub async fn wait_for_idle_time(&self) -> TimeoutFuture {
|
||||
pub async fn wait_for_idle_time(&mut self) -> TimeoutFuture {
|
||||
let ric_fn = self.ric_closure.as_ref().dyn_ref::<Function>().unwrap();
|
||||
let _cb_id: u32 = self.window.request_idle_callback(ric_fn).unwrap();
|
||||
let deadline = self.ric_receiver.recv().await.unwrap();
|
||||
let deadline = self.ric_receiver.next().await.unwrap();
|
||||
TimeoutFuture::new(deadline)
|
||||
}
|
||||
|
||||
pub async fn wait_for_raf(&self) {
|
||||
pub async fn wait_for_raf(&mut self) {
|
||||
let raf_fn = self.raf_closure.as_ref().dyn_ref::<Function>().unwrap();
|
||||
let _id: i32 = self.window.request_animation_frame(raf_fn).unwrap();
|
||||
self.raf_receiver.recv().await.unwrap();
|
||||
self.raf_receiver.next().await.unwrap();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue