Refactor desktop into App type

This commit is contained in:
Jonathan Kelley committed 2024-01-04 16:12:37 -08:00
1 parent f6acaba061
commit 9a6d745339
15 files changed
+728 -645

No files matched your search

+1 -1
View File
@@ -35,7 +35,7 @@ frameworks = ["WebKit"]
[dependencies]
anyhow = "1.0.56"
log = "0.4.11"
wry = "0.34.0"
wry = "0.35.0"
dioxus = { path = "../../packages/dioxus" }
dioxus-desktop = { path = "../../packages/desktop", features = [
"tokio_runtime",
+3 -3
View File
@@ -6,7 +6,7 @@ use crate::{
use std::{
any::{Any, TypeId},
backtrace::Backtrace,
cell::RefCell,
cell::{Cell, RefCell},
error::Error,
fmt::{Debug, Display},
rc::Rc,
@@ -472,8 +472,8 @@ pub fn ErrorBoundary<'a>(cx: Scope<'a, ErrorBoundaryProps<'a>>) -> Element {
attr_paths: &[],
};
VNode {
parent: Default::default(),
stable_id: Default::default(),
parent: Cell::new(None),
stable_id: Cell::new(None),
key: None,
template: std::cell::Cell::new(TEMPLATE),
root_ids: bumpalo::collections::Vec::with_capacity_in(1usize, __cx.bump()).into(),
+7 -2
View File
@@ -19,7 +19,10 @@ serde = "1.0.136"
serde_json = "1.0.79"
thiserror = { workspace = true }
tracing = { workspace = true }
wry = { version = "0.34.0", default-features = false, features = ["tao", "protocol", "file-drop"] }
wry = { version = "0.35.0", default-features = false, features = [
"protocol",
"file-drop",
] }
futures-channel = { workspace = true }
tokio = { workspace = true, features = [
"sync",
@@ -39,11 +42,13 @@ futures-util = { workspace = true }
urlencoding = "2.1.2"
async-trait = "0.1.68"
crossbeam-channel = "0.5.8"
tao = { version = "0.24.0", features = ["rwh_05"] }
muda = "0.11.3"
[target.'cfg(any(target_os = "windows",target_os = "macos",target_os = "linux",target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))'.dependencies]
rfd = "0.12"
global-hotkey = { git = "https://github.com/tauri-apps/global-hotkey" }
global-hotkey = "0.4.1"
[target.'cfg(target_os = "ios")'.dependencies]
objc = "0.2.7"
+569
View File
@@ -0,0 +1,569 @@
pub use crate::cfg::{Config, WindowCloseBehaviour};
pub use crate::desktop_context::DesktopContext;
pub use crate::desktop_context::{
use_window, use_wry_event_handler, window, DesktopService, WryEventHandler, WryEventHandlerId,
};
use crate::desktop_context::{EventData, UserWindowEvent, WebviewQueue, WindowEventHandlers};
use crate::events::{IpcMessage, KnownIpcMethod};
use crate::file_upload;
use crate::query::QueryResult;
use crate::shortcut::GlobalHotKeyEvent;
use dioxus_core::*;
use dioxus_html::{event_bubbles, MountedData};
use dioxus_html::{native_bind::NativeFileEngine, FormData, HtmlEvent};
use global_hotkey::{
hotkey::{Code, HotKey, Modifiers},
GlobalHotKeyManager,
};
// use dioxus_interpreter_js::binary_protocol::Channel;
use crate::element::DesktopElement;
use crate::eval::init_eval;
pub use crate::protocol::{
use_asset_handler, AssetFuture, AssetHandler, AssetRequest, AssetResponse,
};
use crate::shortcut::ShortcutRegistry;
pub use crate::shortcut::{use_global_shortcut, ShortcutHandle, ShortcutId, ShortcutRegistryError};
use futures_util::{pin_mut, FutureExt};
use rustc_hash::FxHashMap;
use std::rc::Rc;
use std::sync::atomic::AtomicU16;
use std::task::Waker;
use std::{borrow::Borrow, cell::Cell};
use std::{collections::HashMap, sync::Arc};
pub use tao::dpi::{LogicalSize, PhysicalSize};
use tao::event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget};
pub use tao::window::WindowBuilder;
use tao::window::WindowId;
use tao::{
event::{Event, StartCause, WindowEvent},
event_loop::ControlFlow,
};
use tao::{event_loop::EventLoopBuilder, window::Window};
use tokio::runtime::Builder;
pub use wry;
use wry::WebContext;
use wry::WebView;
pub struct App<P> {
// move the props into a cell so we can pop it out later to create the first window
// iOS panics if we create a window before the event loop is started
pub(crate) props: Rc<Cell<Option<P>>>,
pub(crate) cfg: Rc<Cell<Option<Config>>>,
pub(crate) root: Component<P>,
pub(crate) webviews: HashMap<WindowId, WebviewHandler>,
pub(crate) event_handlers: WindowEventHandlers,
pub(crate) queue: WebviewQueue,
pub(crate) shortcut_manager: ShortcutRegistry,
pub(crate) global_hotkey_channel: crossbeam_channel::Receiver<GlobalHotKeyEvent>,
pub(crate) proxy: EventLoopProxy<UserWindowEvent>,
pub(crate) window_behavior: WindowCloseBehaviour,
pub(crate) control_flow: ControlFlow,
pub(crate) is_visible_before_start: bool,
}
impl<P> App<P> {
pub fn new(cfg: Config, props: P, root: Component<P>) -> (EventLoop<UserWindowEvent>, Self) {
let event_loop = EventLoopBuilder::<UserWindowEvent>::with_user_event().build();
let mut app = Self {
root,
window_behavior: cfg.last_window_close_behaviour,
is_visible_before_start: true,
webviews: HashMap::new(),
event_handlers: WindowEventHandlers::default(),
queue: WebviewQueue::default(),
shortcut_manager: ShortcutRegistry::new(),
global_hotkey_channel: GlobalHotKeyEvent::receiver().clone(),
proxy: event_loop.create_proxy(),
props: Rc::new(Cell::new(Some(props))),
cfg: Rc::new(Cell::new(Some(cfg))),
control_flow: ControlFlow::Wait,
};
#[cfg(all(feature = "hot-reload", debug_assertions))]
app.connect_hotreload();
(event_loop, app)
}
pub fn tick(
&mut self,
window_event: &Event<'_, UserWindowEvent>,
event_loop: &EventLoopWindowTarget<UserWindowEvent>,
) {
// Set the control flow here, but make sure to update it at the end of the match
self.control_flow = ControlFlow::Wait;
self.event_handlers.apply_event(window_event, event_loop);
if let Ok(event) = self.global_hotkey_channel.try_recv() {
self.shortcut_manager.call_handlers(event);
}
}
pub fn connect_hotreload(&mut self) {
let proxy = self.proxy.clone();
dioxus_hot_reload::connect({
move |template| {
let _ = proxy.send_event(UserWindowEvent(
EventData::HotReloadEvent(template),
unsafe { WindowId::dummy() },
));
}
});
}
//
pub fn handle_new_window(&mut self) {
for handler in self.queue.borrow_mut().drain(..) {
let id = handler.desktop_context.window.id();
self.webviews.insert(id, handler);
_ = self.proxy.send_event(UserWindowEvent(EventData::Poll, id));
}
}
pub fn handle_close_requested(&mut self, id: WindowId) {
use WindowCloseBehaviour::*;
match self.window_behavior {
LastWindowExitsApp => {
self.webviews.remove(&id);
if self.webviews.is_empty() {
self.control_flow = ControlFlow::Exit
}
}
LastWindowHides => {
let Some(webview) = self.webviews.get(&id) else {
return;
};
hide_app_window(&webview.desktop_context.webview);
}
CloseWindow => {
self.webviews.remove(&id);
}
}
}
pub fn window_destroyed(&mut self, id: WindowId) {
self.webviews.remove(&id);
if matches!(
self.window_behavior,
WindowCloseBehaviour::LastWindowExitsApp
) && self.webviews.is_empty()
{
self.control_flow = ControlFlow::Exit
}
}
pub fn handle_start_cause_init(&mut self, target: &EventLoopWindowTarget<UserWindowEvent>) {
let props = self.props.take().unwrap();
let cfg = self.cfg.take().unwrap();
let dom = VirtualDom::new_with_props(self.root, props);
self.is_visible_before_start = cfg.window.window.visible;
let handler = create_new_window(
cfg,
target,
&self.proxy,
dom,
&self.queue,
&self.event_handlers,
self.shortcut_manager.clone(),
);
let id = handler.desktop_context.window.id();
self.webviews.insert(id, handler);
_ = self.proxy.send_event(UserWindowEvent(EventData::Poll, id));
}
pub fn handle_browser_open(&mut self, msg: IpcMessage) {
if let Some(temp) = msg.params().as_object() {
if temp.contains_key("href") {
let open = webbrowser::open(temp["href"].as_str().unwrap());
if let Err(e) = open {
tracing::error!("Open Browser error: {:?}", e);
}
}
}
}
pub fn handle_initialize_msg(&mut self, id: WindowId) {
let view = self.webviews.get_mut(&id).unwrap();
send_edits(view.dom.rebuild(), &view.desktop_context);
view.desktop_context
.window
.set_visible(self.is_visible_before_start);
}
pub fn handle_close_msg(&mut self, id: WindowId) {
self.webviews.remove(&id);
if self.webviews.is_empty() {
self.control_flow = ControlFlow::Exit
}
}
pub fn handle_poll_msg(&mut self, id: WindowId) {
self.poll_vdom(id);
}
pub fn handle_query_msg(&mut self, msg: IpcMessage, id: WindowId) {
let Ok(result) = serde_json::from_value::<QueryResult>(msg.params()) else {
return;
};
let Some(view) = self.webviews.get(&id) else {
return;
};
view.dom
.base_scope()
.consume_context::<DesktopContext>()
.unwrap()
.query
.send(result);
}
pub fn handle_user_event_msg(&mut self, msg: IpcMessage, id: WindowId) {
let params = msg.params();
let evt = match serde_json::from_value::<HtmlEvent>(params) {
Ok(value) => value,
Err(err) => {
tracing::error!("Error parsing user_event: {:?}", err);
return;
}
};
let HtmlEvent {
element,
name,
bubbles,
data,
} = evt;
let view = self.webviews.get_mut(&id).unwrap();
// check for a mounted event placeholder and replace it with a desktop specific element
let as_any = if let dioxus_html::EventData::Mounted = &data {
let query = view
.dom
.base_scope()
.consume_context::<DesktopContext>()
.unwrap()
.query
.clone();
let element = DesktopElement::new(element, view.desktop_context.clone(), query);
Rc::new(MountedData::new(element))
} else {
data.into_any()
};
view.dom.handle_event(&name, as_any, element, bubbles);
send_edits(view.dom.render_immediate(), &view.desktop_context);
}
#[cfg(all(feature = "hot-reload", debug_assertions))]
pub fn handle_hot_reload_msg(&mut self, msg: dioxus_hot_reload::HotReloadMsg) {
match msg {
dioxus_hot_reload::HotReloadMsg::UpdateTemplate(template) => {
for webview in self.webviews.values_mut() {
webview.dom.replace_template(template);
// poll_vdom(webview);
todo!()
}
}
dioxus_hot_reload::HotReloadMsg::Shutdown => {
self.control_flow = ControlFlow::Exit;
}
}
}
pub fn handle_file_dialog_msg(&self, msg: IpcMessage, window: WindowId) {
if let Ok(file_diolog) =
serde_json::from_value::<file_upload::FileDialogRequest>(msg.params())
{
let id = ElementId(file_diolog.target);
let event_name = &file_diolog.event;
let event_bubbles = file_diolog.bubbles;
let files = file_upload::get_file_event(&file_diolog);
let data = Rc::new(FormData {
value: Default::default(),
values: Default::default(),
files: Some(Arc::new(NativeFileEngine::new(files))),
});
let view = self.webviews.get_mut(&window).unwrap();
if event_name == "change&input" {
view.dom
.handle_event("input", data.clone(), id, event_bubbles);
view.dom.handle_event("change", data, id, event_bubbles);
} else {
view.dom.handle_event(event_name, data, id, event_bubbles);
}
todo!()
// send_edits(view.dom.render_immediate(), &view.desktop_context);
}
}
/// Poll the virtualdom until it's pending
///
/// The waker we give it is connected to the event loop, so it will wake up the event loop when it's ready to be polled again
///
/// All IO is done on the tokio runtime we started earlier
pub fn poll_vdom(&mut self, id: WindowId) {
let view = self.webviews.get_mut(&id).unwrap();
let mut cx = std::task::Context::from_waker(&view.waker);
loop {
{
let fut = view.dom.wait_for_work();
pin_mut!(fut);
match fut.poll_unpin(&mut cx) {
std::task::Poll::Ready(_) => {}
std::task::Poll::Pending => break,
}
}
send_edits(view.dom.render_immediate(), &view.desktop_context);
}
}
}
pub fn create_new_window(
mut cfg: Config,
event_loop: &EventLoopWindowTarget<UserWindowEvent>,
proxy: &EventLoopProxy<UserWindowEvent>,
dom: VirtualDom,
queue: &WebviewQueue,
event_handlers: &WindowEventHandlers,
shortcut_manager: ShortcutRegistry,
) -> WebviewHandler {
let (webview, web_context, asset_handlers, edit_queue, window) =
crate::webview::build(&mut cfg, event_loop, proxy.clone());
let desktop_context = Rc::from(DesktopService::new(
window,
webview,
proxy.clone(),
event_loop.clone(),
queue.clone(),
event_handlers.clone(),
shortcut_manager,
edit_queue,
asset_handlers,
));
let cx = dom.base_scope();
cx.provide_context(desktop_context.clone());
// Init eval
init_eval(cx);
WebviewHandler {
// We want to poll the virtualdom and the event loop at the same time, so the waker will be connected to both
waker: crate::waker::tao_waker(proxy, desktop_context.window.id()),
desktop_context,
dom,
_web_context: web_context,
}
}
pub struct WebviewHandler {
dom: VirtualDom,
desktop_context: DesktopContext,
waker: Waker,
// Wry assumes the webcontext is alive for the lifetime of the webview.
// We need to keep the webcontext alive, otherwise the webview will crash
_web_context: WebContext,
}
/// Send a list of mutations to the webview
pub fn send_edits(edits: Mutations, desktop_context: &DesktopContext) {
let mut channel = desktop_context.channel.borrow_mut();
let mut templates = desktop_context.templates.borrow_mut();
if let Some(bytes) = apply_edits(
edits,
&mut channel,
&mut templates,
&desktop_context.max_template_count,
) {
desktop_context.edit_queue.add_edits(bytes)
}
}
pub struct Channel {}
pub fn apply_edits(
mutations: Mutations,
channel: &mut Channel,
templates: &mut FxHashMap<String, u16>,
max_template_count: &AtomicU16,
) -> Option<Vec<u8>> {
use dioxus_core::Mutation::*;
if mutations.templates.is_empty() && mutations.edits.is_empty() {
return None;
}
for template in mutations.templates {
add_template(&template, channel, templates, max_template_count);
}
for edit in mutations.edits {
match edit {
AppendChildren { id, m } => channel.append_children(id.0 as u32, m as u16),
AssignId { path, id } => channel.assign_id(path, id.0 as u32),
CreatePlaceholder { id } => channel.create_placeholder(id.0 as u32),
CreateTextNode { value, id } => channel.create_text_node(value, id.0 as u32),
HydrateText { path, value, id } => channel.hydrate_text(path, value, id.0 as u32),
LoadTemplate { name, index, id } => {
if let Some(tmpl_id) = templates.get(name) {
channel.load_template(*tmpl_id, index as u16, id.0 as u32)
}
}
ReplaceWith { id, m } => channel.replace_with(id.0 as u32, m as u16),
ReplacePlaceholder { path, m } => channel.replace_placeholder(path, m as u16),
InsertAfter { id, m } => channel.insert_after(id.0 as u32, m as u16),
InsertBefore { id, m } => channel.insert_before(id.0 as u32, m as u16),
SetAttribute {
name,
value,
id,
ns,
} => match value {
BorrowedAttributeValue::Text(txt) => {
channel.set_attribute(id.0 as u32, name, txt, ns.unwrap_or_default())
}
BorrowedAttributeValue::Float(f) => {
channel.set_attribute(id.0 as u32, name, &f.to_string(), ns.unwrap_or_default())
}
BorrowedAttributeValue::Int(n) => {
channel.set_attribute(id.0 as u32, name, &n.to_string(), ns.unwrap_or_default())
}
BorrowedAttributeValue::Bool(b) => channel.set_attribute(
id.0 as u32,
name,
if b { "true" } else { "false" },
ns.unwrap_or_default(),
),
BorrowedAttributeValue::None => {
channel.remove_attribute(id.0 as u32, name, ns.unwrap_or_default())
}
_ => unreachable!(),
},
SetText { value, id } => channel.set_text(id.0 as u32, value),
NewEventListener { name, id, .. } => {
channel.new_event_listener(name, id.0 as u32, event_bubbles(name) as u8)
}
RemoveEventListener { name, id } => {
channel.remove_event_listener(name, id.0 as u32, event_bubbles(name) as u8)
}
Remove { id } => channel.remove(id.0 as u32),
PushRoot { id } => channel.push_root(id.0 as u32),
}
}
let bytes: Vec<_> = channel.export_memory().collect();
channel.reset();
Some(bytes)
}
pub fn add_template(
template: &Template<'static>,
channel: &mut Channel,
templates: &mut FxHashMap<String, u16>,
max_template_count: &AtomicU16,
) {
let current_max_template_count = max_template_count.load(std::sync::atomic::Ordering::Relaxed);
for root in template.roots.iter() {
create_template_node(channel, root);
templates.insert(template.name.to_owned(), current_max_template_count);
}
channel.add_templates(current_max_template_count, template.roots.len() as u16);
max_template_count.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
}
pub fn create_template_node(channel: &mut Channel, v: &'static TemplateNode<'static>) {
use TemplateNode::*;
match v {
Element {
tag,
namespace,
attrs,
children,
..
} => {
// Push the current node onto the stack
match namespace {
Some(ns) => channel.create_element_ns(tag, ns),
None => channel.create_element(tag),
}
// Set attributes on the current node
for attr in *attrs {
if let TemplateAttribute::Static {
name,
value,
namespace,
} = attr
{
channel.set_top_attribute(name, value, namespace.unwrap_or_default())
}
}
// Add each child to the stack
for child in *children {
create_template_node(channel, child);
}
// Add all children to the parent
channel.append_children_to_top(children.len() as u16);
}
Text { text } => channel.create_raw_text(text),
DynamicText { .. } => channel.create_raw_text("p"),
Dynamic { .. } => channel.add_placeholder(),
}
}
/// Different hide implementations per platform
#[allow(unused)]
pub fn hide_app_window(webview: &WebView) {
#[cfg(target_os = "windows")]
{
use wry::application::platform::windows::WindowExtWindows;
window.set_visible(false);
window.set_skip_taskbar(true);
}
#[cfg(target_os = "linux")]
{
use wry::application::platform::unix::WindowExtUnix;
window.set_visible(false);
}
#[cfg(target_os = "macos")]
{
// window.set_visible(false); has the wrong behaviour on macOS
// It will hide the window but not show it again when the user switches
// back to the app. `NSApplication::hide:` has the correct behaviour
use objc::runtime::Object;
use objc::{msg_send, sel, sel_impl};
objc::rc::autoreleasepool(|| unsafe {
let app: *mut Object = msg_send![objc::class!(NSApplication), sharedApplication];
let nil = std::ptr::null_mut::<Object>();
let _: () = msg_send![app, hide: nil];
});
}
}
Whitespace-only changes.
+4 -5
View File
@@ -1,11 +1,10 @@
use std::borrow::Cow;
use std::path::PathBuf;
use wry::application::window::Icon;
use tao::window::{Icon, Window, WindowBuilder};
use wry::{
application::window::{Window, WindowBuilder},
http::{Request as HttpRequest, Response as HttpResponse},
webview::FileDropEvent,
FileDropEvent,
};
// pub(crate) type DynEventHandlerFn = dyn Fn(&mut EventLoop<()>, &mut WebView);
@@ -38,7 +37,7 @@ pub struct Config {
pub(crate) enable_default_menu_bar: bool,
}
type DropHandler = Box<dyn Fn(&Window, FileDropEvent) -> bool>;
type DropHandler = Box<dyn Fn(FileDropEvent) -> bool>;
pub(crate) type WryProtocol = (
String,
@@ -120,7 +119,7 @@ impl Config {
/// Set a file drop handler
pub fn with_file_drop_handler(
mut self,
handler: impl Fn(&Window, FileDropEvent) -> bool + 'static,
handler: impl Fn(FileDropEvent) -> bool + 'static,
) -> Self {
self.file_drop_handler = Some(Box::new(handler));
self
+35 -41
View File
@@ -1,38 +1,29 @@
use crate::create_new_window;
use crate::events::IpcMessage;
use crate::protocol::AssetFuture;
use crate::protocol::AssetHandlerRegistry;
use crate::query::QueryEngine;
use crate::shortcut::{HotKey, ShortcutId, ShortcutRegistry, ShortcutRegistryError};
use crate::AssetHandler;
use crate::Config;
use crate::WebviewHandler;
use crate::{app::WebviewHandler, events::IpcMessage};
use dioxus_core::ScopeState;
use dioxus_core::VirtualDom;
#[cfg(all(feature = "hot-reload", debug_assertions))]
use dioxus_hot_reload::HotReloadMsg;
use dioxus_interpreter_js::binary_protocol::Channel;
use rustc_hash::FxHashMap;
use slab::Slab;
use std::cell::RefCell;
use std::fmt::Debug;
use std::fmt::Formatter;
use std::rc::Rc;
use std::rc::Weak;
use std::sync::atomic::AtomicU16;
use std::sync::Arc;
use std::sync::Mutex;
use wry::application::event::Event;
use wry::application::event_loop::EventLoopProxy;
use wry::application::event_loop::EventLoopWindowTarget;
#[cfg(target_os = "ios")]
use wry::application::platform::ios::WindowExtIOS;
use wry::application::window::Fullscreen as WryFullscreen;
use wry::application::window::Window;
use wry::application::window::WindowId;
use wry::webview::WebView;
use std::{
cell::RefCell, fmt::Debug, fmt::Formatter, rc::Rc, rc::Weak, sync::atomic::AtomicU16,
sync::Arc, sync::Mutex,
};
use tao::{
event::Event,
event_loop::{EventLoopProxy, EventLoopWindowTarget},
window::{Fullscreen as WryFullscreen, Window, WindowId},
};
pub type ProxyType = EventLoopProxy<UserWindowEvent>;
use wry::{RequestAsyncResponder, WebView};
#[cfg(target_os = "ios")]
use tao::platform::ios::WindowExtIOS;
/// Get an imperative handle to the current window without using a hook
///
@@ -44,7 +35,6 @@ pub fn window() -> DesktopContext {
}
/// Get an imperative handle to the current window
#[deprecated = "Prefer the using the `window` function directly for cleaner code"]
pub fn use_window(cx: &ScopeState) -> &DesktopContext {
cx.use_hook(|| cx.consume_context::<DesktopContext>())
.as_ref()
@@ -56,7 +46,7 @@ pub fn use_window(cx: &ScopeState) -> &DesktopContext {
#[derive(Default, Clone)]
pub(crate) struct EditQueue {
queue: Arc<Mutex<Vec<Vec<u8>>>>,
responder: Arc<Mutex<Option<wry::webview::RequestAsyncResponder>>>,
responder: Arc<Mutex<Option<RequestAsyncResponder>>>,
}
impl Debug for EditQueue {
@@ -71,7 +61,7 @@ impl Debug for EditQueue {
}
impl EditQueue {
pub fn handle_request(&self, responder: wry::webview::RequestAsyncResponder) {
pub fn handle_request(&self, responder: RequestAsyncResponder) {
let mut queue = self.queue.lock().unwrap();
if let Some(bytes) = queue.pop() {
responder.respond(wry::http::Response::new(bytes));
@@ -106,10 +96,12 @@ pub(crate) type WebviewQueue = Rc<RefCell<Vec<WebviewHandler>>>;
/// ```
pub struct DesktopService {
/// The wry/tao proxy to the current window
pub webview: Rc<WebView>,
pub webview: WebView,
pub window: Window,
/// The proxy to the event loop
pub proxy: ProxyType,
pub proxy: EventLoopProxy<UserWindowEvent>,
/// The receiver for queries about the current window
pub(super) query: QueryEngine,
@@ -141,14 +133,15 @@ impl std::ops::Deref for DesktopService {
type Target = Window;
fn deref(&self) -> &Self::Target {
self.webview.window()
&self.window
}
}
impl DesktopService {
pub(crate) fn new(
window: Window,
webview: WebView,
proxy: ProxyType,
proxy: EventLoopProxy<UserWindowEvent>,
event_loop: EventLoopWindowTarget<UserWindowEvent>,
webviews: WebviewQueue,
event_handlers: WindowEventHandlers,
@@ -157,7 +150,8 @@ impl DesktopService {
asset_handlers: AssetHandlerRegistry,
) -> Self {
Self {
webview: Rc::new(webview),
window,
webview,
proxy,
event_loop,
query: Default::default(),
@@ -198,7 +192,7 @@ impl DesktopService {
.consume_context::<Rc<DesktopService>>()
.unwrap();
let id = window.desktop_context.webview.window().id();
let id = window.desktop_context.window.id();
self.proxy
.send_event(UserWindowEvent(EventData::NewWindow, id))
@@ -222,7 +216,7 @@ impl DesktopService {
/// onmousedown: move |_| { desktop.drag_window(); }
/// ```
pub fn drag(&self) {
let window = self.webview.window();
let window = &self.window;
// if the drag_window has any errors, we don't do anything
if window.fullscreen().is_none() {
@@ -232,7 +226,7 @@ impl DesktopService {
/// Toggle whether the window is maximized or not
pub fn toggle_maximized(&self) {
let window = self.webview.window();
let window = &self.window;
window.set_maximized(!window.is_maximized())
}
@@ -253,10 +247,10 @@ impl DesktopService {
/// change window to fullscreen
pub fn set_fullscreen(&self, fullscreen: bool) {
if let Some(handle) = self.webview.window().current_monitor() {
self.webview
.window()
.set_fullscreen(fullscreen.then_some(WryFullscreen::Borderless(Some(handle))));
if let Some(handle) = &self.window.current_monitor() {
self.window.set_fullscreen(
fullscreen.then_some(WryFullscreen::Borderless(Some(handle.clone()))),
);
}
}
@@ -336,7 +330,7 @@ impl DesktopService {
/// Push an objc view to the window
#[cfg(target_os = "ios")]
pub fn push_view(&self, view: objc_id::ShareId<objc::runtime::Object>) {
let window = self.webview.window();
let window = &self.window;
unsafe {
use objc::runtime::Object;
@@ -356,7 +350,7 @@ impl DesktopService {
/// Pop an objc view from the window
#[cfg(target_os = "ios")]
pub fn pop_view(&self) {
let window = self.webview.window();
let window = &self.window;
unsafe {
use objc::runtime::Object;
@@ -380,7 +374,7 @@ pub enum EventData {
Ipc(IpcMessage),
#[cfg(all(feature = "hot-reload", debug_assertions))]
HotReloadEvent(HotReloadMsg),
HotReloadEvent(dioxus_hot_reload::HotReloadMsg),
NewWindow,
+20 -2
View File
@@ -8,9 +8,27 @@ pub struct IpcMessage {
params: serde_json::Value,
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum KnownIpcMethod<'a> {
FileDialog,
UserEvent,
Query,
BrowserOpen,
Initialize,
Other(&'a str),
}
impl IpcMessage {
pub(crate) fn method(&self) -> &str {
self.method.as_str()
pub(crate) fn method(&self) -> KnownIpcMethod {
match self.method.as_str() {
// todo: this is a misspelling
"file_diolog" => KnownIpcMethod::FileDialog,
"user_event" => KnownIpcMethod::UserEvent,
"query" => KnownIpcMethod::Query,
"browser_open" => KnownIpcMethod::BrowserOpen,
"initialize" => KnownIpcMethod::Initialize,
_ => KnownIpcMethod::Other(&self.method),
}
}
pub(crate) fn params(self) -> serde_json::Value {
+1
View File
@@ -0,0 +1 @@
+34 -516
View File
@@ -3,6 +3,8 @@
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
#![deny(missing_docs)]
mod app;
mod asset_handler;
mod cfg;
mod desktop_context;
mod element;
@@ -10,6 +12,7 @@ mod escape;
mod eval;
mod events;
mod file_upload;
mod hooks;
#[cfg(any(target_os = "ios", target_os = "android"))]
mod mobile_shortcut;
mod protocol;
@@ -18,44 +21,24 @@ mod shortcut;
mod waker;
mod webview;
use crate::query::QueryResult;
use crate::shortcut::GlobalHotKeyEvent;
pub use cfg::{Config, WindowCloseBehaviour};
pub use desktop_context::use_window;
pub use desktop_context::DesktopContext;
#[allow(deprecated)]
pub use desktop_context::{
use_window, use_wry_event_handler, window, DesktopService, WryEventHandler, WryEventHandlerId,
use_wry_event_handler, window, DesktopService, WryEventHandler, WryEventHandlerId,
};
use desktop_context::{EventData, UserWindowEvent, WebviewQueue, WindowEventHandlers};
use desktop_context::{EventData, UserWindowEvent};
use dioxus_core::*;
use dioxus_html::{event_bubbles, MountedData};
use dioxus_html::{native_bind::NativeFileEngine, FormData, HtmlEvent};
use dioxus_interpreter_js::binary_protocol::Channel;
use element::DesktopElement;
use eval::init_eval;
use futures_util::{pin_mut, FutureExt};
use events::KnownIpcMethod;
pub use protocol::{use_asset_handler, AssetFuture, AssetHandler, AssetRequest, AssetResponse};
use rustc_hash::FxHashMap;
use shortcut::ShortcutRegistry;
pub use shortcut::{use_global_shortcut, ShortcutHandle, ShortcutId, ShortcutRegistryError};
use std::cell::Cell;
use std::rc::Rc;
use std::sync::atomic::AtomicU16;
use std::task::Waker;
use std::{collections::HashMap, sync::Arc};
pub use tao::dpi::{LogicalSize, PhysicalSize};
use tao::event_loop::{EventLoopProxy, EventLoopWindowTarget};
use tao::event::{Event, StartCause, WindowEvent};
pub use tao::window::WindowBuilder;
use tao::{
event::{Event, StartCause, WindowEvent},
event_loop::ControlFlow,
};
// pub use webview::build_default_menu_bar;
use tokio::runtime::Builder;
pub use webview::build_default_menu_bar;
pub use wry;
pub use wry::application as tao;
use wry::application::event_loop::EventLoopBuilder;
use wry::webview::WebView;
use wry::{application::window::WindowId, webview::WebContext};
/// Launch the WebView and run the event loop.
///
@@ -127,512 +110,47 @@ pub fn launch_cfg(root: Component, config_builder: Config) {
/// }
/// ```
pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config) {
let event_loop = EventLoopBuilder::<UserWindowEvent>::with_user_event().build();
let proxy = event_loop.create_proxy();
let window_behaviour = cfg.last_window_close_behaviour;
// Intialize hot reloading if it is enabled
#[cfg(all(feature = "hot-reload", debug_assertions))]
dioxus_hot_reload::connect({
let proxy = proxy.clone();
move |template| {
let _ = proxy.send_event(UserWindowEvent(
EventData::HotReloadEvent(template),
unsafe { WindowId::dummy() },
));
}
});
// We start the tokio runtime *on this thread*
// Any future we poll later will use this runtime to spawn tasks and for IO
let rt = tokio::runtime::Builder::new_multi_thread()
// I would love to just allow dioxus to work with any runtime... but tokio is weird
let _guard = Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
.unwrap()
.enter();
// We enter the runtime but we poll futures manually, circumventing the per-task runtime budget
let _guard = rt.enter();
// We only have one webview right now, but we'll have more later
// Store them in a hashmap so we can remove them when they're closed
let mut webviews = HashMap::<WindowId, WebviewHandler>::new();
// We use this to allow dynamically adding and removing window event handlers
let event_handlers = WindowEventHandlers::default();
let queue = WebviewQueue::default();
let shortcut_manager = ShortcutRegistry::new();
let global_hotkey_channel = GlobalHotKeyEvent::receiver();
// move the props into a cell so we can pop it out later to create the first window
// iOS panics if we create a window before the event loop is started
let props = Rc::new(Cell::new(Some(props)));
let cfg = Rc::new(Cell::new(Some(cfg)));
let mut is_visible_before_start = true;
let (event_loop, mut app) = app::App::new(cfg, props, root);
event_loop.run(move |window_event, event_loop, control_flow| {
*control_flow = ControlFlow::Poll;
event_handlers.apply_event(&window_event, event_loop);
if let Ok(event) = global_hotkey_channel.try_recv() {
shortcut_manager.call_handlers(event);
}
app.tick(&window_event, event_loop);
match window_event {
Event::NewEvents(StartCause::Init) => app.handle_start_cause_init(event_loop),
Event::WindowEvent {
event, window_id, ..
} => match event {
WindowEvent::CloseRequested => match window_behaviour {
cfg::WindowCloseBehaviour::LastWindowExitsApp => {
webviews.remove(&window_id);
if webviews.is_empty() {
*control_flow = ControlFlow::Exit
}
}
cfg::WindowCloseBehaviour::LastWindowHides => {
let Some(webview) = webviews.get(&window_id) else {
return;
};
hide_app_window(&webview.desktop_context.webview);
}
cfg::WindowCloseBehaviour::CloseWindow => {
webviews.remove(&window_id);
}
},
WindowEvent::Destroyed { .. } => {
webviews.remove(&window_id);
if matches!(
window_behaviour,
cfg::WindowCloseBehaviour::LastWindowExitsApp
) && webviews.is_empty()
{
*control_flow = ControlFlow::Exit
}
}
WindowEvent::CloseRequested => app.handle_close_requested(window_id),
WindowEvent::Destroyed { .. } => app.window_destroyed(window_id),
_ => {}
},
Event::NewEvents(StartCause::Init) => {
let props = props.take().unwrap();
let cfg = cfg.take().unwrap();
// Create a dom
let dom = VirtualDom::new_with_props(root, props);
is_visible_before_start = cfg.window.window.visible;
let handler = create_new_window(
cfg,
event_loop,
&proxy,
dom,
&queue,
&event_handlers,
shortcut_manager.clone(),
);
let id = handler.desktop_context.webview.window().id();
webviews.insert(id, handler);
_ = proxy.send_event(UserWindowEvent(EventData::Poll, id));
}
Event::UserEvent(UserWindowEvent(EventData::NewWindow, _)) => {
for handler in queue.borrow_mut().drain(..) {
let id = handler.desktop_context.webview.window().id();
webviews.insert(id, handler);
_ = proxy.send_event(UserWindowEvent(EventData::Poll, id));
}
}
Event::UserEvent(event) => match event.0 {
#[cfg(all(feature = "hot-reload", debug_assertions))]
EventData::HotReloadEvent(msg) => match msg {
dioxus_hot_reload::HotReloadMsg::UpdateTemplate(template) => {
for webview in webviews.values_mut() {
webview.dom.replace_template(template);
poll_vdom(webview);
}
}
dioxus_hot_reload::HotReloadMsg::Shutdown => {
*control_flow = ControlFlow::Exit;
}
Event::UserEvent(UserWindowEvent(event, id)) => match event {
EventData::Poll => app.handle_poll_msg(id),
EventData::NewWindow => app.handle_new_window(),
EventData::CloseWindow => app.handle_close_msg(id),
EventData::HotReloadEvent(msg) => app.handle_hot_reload_msg(msg),
EventData::Ipc(msg) => match msg.method() {
KnownIpcMethod::FileDialog => app.handle_file_dialog_msg(msg, id),
KnownIpcMethod::UserEvent => app.handle_user_event_msg(msg, id),
KnownIpcMethod::Query => app.handle_query_msg(msg, id),
KnownIpcMethod::BrowserOpen => app.handle_browser_open(msg),
KnownIpcMethod::Initialize => app.handle_initialize_msg(id),
KnownIpcMethod::Other(_) => {}
},
EventData::CloseWindow => {
webviews.remove(&event.1);
if webviews.is_empty() {
*control_flow = ControlFlow::Exit
}
}
EventData::Poll => {
if let Some(view) = webviews.get_mut(&event.1) {
poll_vdom(view);
}
}
EventData::Ipc(msg) if msg.method() == "user_event" => {
let params = msg.params();
let evt = match serde_json::from_value::<HtmlEvent>(params) {
Ok(value) => value,
Err(err) => {
tracing::error!("Error parsing user_event: {:?}", err);
return;
}
};
let HtmlEvent {
element,
name,
bubbles,
data,
} = evt;
let view = webviews.get_mut(&event.1).unwrap();
// check for a mounted event placeholder and replace it with a desktop specific element
let as_any = if let dioxus_html::EventData::Mounted = &data {
let query = view
.dom
.base_scope()
.consume_context::<DesktopContext>()
.unwrap()
.query
.clone();
let element =
DesktopElement::new(element, view.desktop_context.clone(), query);
Rc::new(MountedData::new(element))
} else {
data.into_any()
};
view.dom.handle_event(&name, as_any, element, bubbles);
send_edits(view.dom.render_immediate(), &view.desktop_context);
}
// When the webview sends a query, we need to send it to the query manager which handles dispatching the data to the correct pending query
EventData::Ipc(msg) if msg.method() == "query" => {
let params = msg.params();
if let Ok(result) = serde_json::from_value::<QueryResult>(params) {
let view = webviews.get(&event.1).unwrap();
let query = view
.dom
.base_scope()
.consume_context::<DesktopContext>()
.unwrap()
.query
.clone();
query.send(result);
}
}
EventData::Ipc(msg) if msg.method() == "initialize" => {
let view = webviews.get_mut(&event.1).unwrap();
send_edits(view.dom.rebuild(), &view.desktop_context);
view.desktop_context
.webview
.window()
.set_visible(is_visible_before_start);
}
EventData::Ipc(msg) if msg.method() == "browser_open" => {
if let Some(temp) = msg.params().as_object() {
if temp.contains_key("href") {
let open = webbrowser::open(temp["href"].as_str().unwrap());
if let Err(e) = open {
tracing::error!("Open Browser error: {:?}", e);
}
}
}
}
EventData::Ipc(msg) if msg.method() == "file_diolog" => {
if let Ok(file_diolog) =
serde_json::from_value::<file_upload::FileDialogRequest>(msg.params())
{
let id = ElementId(file_diolog.target);
let event_name = &file_diolog.event;
let event_bubbles = file_diolog.bubbles;
let files = file_upload::get_file_event(&file_diolog);
let data = Rc::new(FormData {
value: Default::default(),
values: Default::default(),
files: Some(Arc::new(NativeFileEngine::new(files))),
});
let view = webviews.get_mut(&event.1).unwrap();
if event_name == "change&input" {
view.dom
.handle_event("input", data.clone(), id, event_bubbles);
view.dom.handle_event("change", data, id, event_bubbles);
} else {
view.dom.handle_event(event_name, data, id, event_bubbles);
}
send_edits(view.dom.render_immediate(), &view.desktop_context);
}
}
_ => {}
},
_ => {}
}
// Update the control flow
*control_flow = app.control_flow;
})
}
fn create_new_window(
mut cfg: Config,
event_loop: &EventLoopWindowTarget<UserWindowEvent>,
proxy: &EventLoopProxy<UserWindowEvent>,
dom: VirtualDom,
queue: &WebviewQueue,
event_handlers: &WindowEventHandlers,
shortcut_manager: ShortcutRegistry,
) -> WebviewHandler {
let (webview, web_context, asset_handlers, edit_queue) =
webview::build(&mut cfg, event_loop, proxy.clone());
let desktop_context = Rc::from(DesktopService::new(
webview,
proxy.clone(),
event_loop.clone(),
queue.clone(),
event_handlers.clone(),
shortcut_manager,
edit_queue,
asset_handlers,
));
let cx = dom.base_scope();
cx.provide_context(desktop_context.clone());
// Init eval
init_eval(cx);
WebviewHandler {
// We want to poll the virtualdom and the event loop at the same time, so the waker will be connected to both
waker: waker::tao_waker(proxy, desktop_context.webview.window().id()),
desktop_context,
dom,
_web_context: web_context,
}
}
struct WebviewHandler {
dom: VirtualDom,
desktop_context: DesktopContext,
waker: Waker,
// Wry assumes the webcontext is alive for the lifetime of the webview.
// We need to keep the webcontext alive, otherwise the webview will crash
_web_context: WebContext,
}
/// Poll the virtualdom until it's pending
///
/// The waker we give it is connected to the event loop, so it will wake up the event loop when it's ready to be polled again
///
/// All IO is done on the tokio runtime we started earlier
fn poll_vdom(view: &mut WebviewHandler) {
let mut cx = std::task::Context::from_waker(&view.waker);
loop {
{
let fut = view.dom.wait_for_work();
pin_mut!(fut);
match fut.poll_unpin(&mut cx) {
std::task::Poll::Ready(_) => {}
std::task::Poll::Pending => break,
}
}
send_edits(view.dom.render_immediate(), &view.desktop_context);
}
}
/// Send a list of mutations to the webview
fn send_edits(edits: Mutations, desktop_context: &DesktopContext) {
let mut channel = desktop_context.channel.borrow_mut();
let mut templates = desktop_context.templates.borrow_mut();
if let Some(bytes) = apply_edits(
edits,
&mut channel,
&mut templates,
&desktop_context.max_template_count,
) {
desktop_context.edit_queue.add_edits(bytes)
}
}
fn apply_edits(
mutations: Mutations,
channel: &mut Channel,
templates: &mut FxHashMap<String, u16>,
max_template_count: &AtomicU16,
) -> Option<Vec<u8>> {
use dioxus_core::Mutation::*;
if mutations.templates.is_empty() && mutations.edits.is_empty() {
return None;
}
for template in mutations.templates {
add_template(&template, channel, templates, max_template_count);
}
for edit in mutations.edits {
match edit {
AppendChildren { id, m } => channel.append_children(id.0 as u32, m as u16),
AssignId { path, id } => channel.assign_id(path, id.0 as u32),
CreatePlaceholder { id } => channel.create_placeholder(id.0 as u32),
CreateTextNode { value, id } => channel.create_text_node(value, id.0 as u32),
HydrateText { path, value, id } => channel.hydrate_text(path, value, id.0 as u32),
LoadTemplate { name, index, id } => {
if let Some(tmpl_id) = templates.get(name) {
channel.load_template(*tmpl_id, index as u16, id.0 as u32)
}
}
ReplaceWith { id, m } => channel.replace_with(id.0 as u32, m as u16),
ReplacePlaceholder { path, m } => channel.replace_placeholder(path, m as u16),
InsertAfter { id, m } => channel.insert_after(id.0 as u32, m as u16),
InsertBefore { id, m } => channel.insert_before(id.0 as u32, m as u16),
SetAttribute {
name,
value,
id,
ns,
} => match value {
BorrowedAttributeValue::Text(txt) => {
channel.set_attribute(id.0 as u32, name, txt, ns.unwrap_or_default())
}
BorrowedAttributeValue::Float(f) => {
channel.set_attribute(id.0 as u32, name, &f.to_string(), ns.unwrap_or_default())
}
BorrowedAttributeValue::Int(n) => {
channel.set_attribute(id.0 as u32, name, &n.to_string(), ns.unwrap_or_default())
}
BorrowedAttributeValue::Bool(b) => channel.set_attribute(
id.0 as u32,
name,
if b { "true" } else { "false" },
ns.unwrap_or_default(),
),
BorrowedAttributeValue::None => {
channel.remove_attribute(id.0 as u32, name, ns.unwrap_or_default())
}
_ => unreachable!(),
},
SetText { value, id } => channel.set_text(id.0 as u32, value),
NewEventListener { name, id, .. } => {
channel.new_event_listener(name, id.0 as u32, event_bubbles(name) as u8)
}
RemoveEventListener { name, id } => {
channel.remove_event_listener(name, id.0 as u32, event_bubbles(name) as u8)
}
Remove { id } => channel.remove(id.0 as u32),
PushRoot { id } => channel.push_root(id.0 as u32),
}
}
let bytes: Vec<_> = channel.export_memory().collect();
channel.reset();
Some(bytes)
}
fn add_template(
template: &Template<'static>,
channel: &mut Channel,
templates: &mut FxHashMap<String, u16>,
max_template_count: &AtomicU16,
) {
let current_max_template_count = max_template_count.load(std::sync::atomic::Ordering::Relaxed);
for root in template.roots.iter() {
create_template_node(channel, root);
templates.insert(template.name.to_owned(), current_max_template_count);
}
channel.add_templates(current_max_template_count, template.roots.len() as u16);
max_template_count.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
}
fn create_template_node(channel: &mut Channel, v: &'static TemplateNode<'static>) {
use TemplateNode::*;
match v {
Element {
tag,
namespace,
attrs,
children,
..
} => {
// Push the current node onto the stack
match namespace {
Some(ns) => channel.create_element_ns(tag, ns),
None => channel.create_element(tag),
}
// Set attributes on the current node
for attr in *attrs {
if let TemplateAttribute::Static {
name,
value,
namespace,
} = attr
{
channel.set_top_attribute(name, value, namespace.unwrap_or_default())
}
}
// Add each child to the stack
for child in *children {
create_template_node(channel, child);
}
// Add all children to the parent
channel.append_children_to_top(children.len() as u16);
}
Text { text } => channel.create_raw_text(text),
DynamicText { .. } => channel.create_raw_text("p"),
Dynamic { .. } => channel.add_placeholder(),
}
}
/// Different hide implementations per platform
#[allow(unused)]
fn hide_app_window(webview: &WebView) {
#[cfg(target_os = "windows")]
{
use wry::application::platform::windows::WindowExtWindows;
webview.window().set_visible(false);
webview.window().set_skip_taskbar(true);
}
#[cfg(target_os = "linux")]
{
use wry::application::platform::unix::WindowExtUnix;
webview.window().set_visible(false);
}
#[cfg(target_os = "macos")]
{
// webview.window().set_visible(false); has the wrong behaviour on macOS
// It will hide the window but not show it again when the user switches
// back to the app. `NSApplication::hide:` has the correct behaviour
use objc::runtime::Object;
use objc::{msg_send, sel, sel_impl};
objc::rc::autoreleasepool(|| unsafe {
let app: *mut Object = msg_send![objc::class!(NSApplication), sharedApplication];
let nil = std::ptr::null_mut::<Object>();
let _: () = msg_send![app, hide: nil];
});
}
}
+4 -6
View File
@@ -1,4 +1,4 @@
use crate::{window, DesktopContext};
use crate::{use_window, DesktopContext};
use dioxus_core::ScopeState;
use dioxus_interpreter_js::INTERPRETER_JS;
use slab::Slab;
@@ -17,7 +17,7 @@ use tokio::{
};
use wry::{
http::{status::StatusCode, Request, Response},
Result,
RequestAsyncResponder, Result,
};
use crate::desktop_context::EditQueue;
@@ -259,8 +259,7 @@ pub(super) async fn desktop_handler(
.body(Cow::from(body))
{
Ok(response) => {
responder.respond(response);
return;
return Ok(response);
}
Err(err) => tracing::error!("error building response: {}", err),
}
@@ -307,8 +306,7 @@ pub(super) async fn desktop_handler(
.body(Cow::from(asset))
{
Ok(response) => {
responder.respond(response);
return;
return response;
}
Err(err) => tracing::error!("error building response: {}", err),
}
+1 -1
View File
@@ -3,7 +3,7 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc, str::FromStr};
use dioxus_core::ScopeState;
use dioxus_html::input_data::keyboard_types::Modifiers;
use slab::Slab;
use wry::application::keyboard::ModifiersState;
use tao::keyboard::ModifiersState;
use crate::{desktop_context::DesktopContext, window};
+2 -2
View File
@@ -1,7 +1,7 @@
use crate::desktop_context::{EventData, UserWindowEvent};
use futures_util::task::ArcWake;
use futures_util::{pin_mut, task::ArcWake, FutureExt};
use std::sync::Arc;
use wry::application::{event_loop::EventLoopProxy, window::WindowId};
use tao::{event_loop::EventLoopProxy, window::WindowId};
/// Create a waker that will send a poll event to the event loop.
///
+46 -65
View File
@@ -1,20 +1,18 @@
use crate::desktop_context::{EditQueue, EventData};
use crate::protocol::{self, AssetHandlerRegistry};
use crate::{desktop_context::UserWindowEvent, Config};
use muda::{Menu, MenuItem, PredefinedMenuItem, Submenu};
use tao::event_loop::{EventLoopProxy, EventLoopWindowTarget};
pub use wry;
pub use wry::application as tao;
use wry::application::window::Window;
use tao::window::Window;
use wry::http::Response;
use wry::webview::{WebContext, WebView, WebViewBuilder};
use wry::{WebContext, WebView, WebViewBuilder};
pub(crate) fn build(
cfg: &mut Config,
event_loop: &EventLoopWindowTarget<UserWindowEvent>,
proxy: EventLoopProxy<UserWindowEvent>,
) -> (WebView, WebContext, AssetHandlerRegistry, EditQueue) {
) -> (WebView, WebContext, AssetHandlerRegistry, EditQueue, Window) {
let builder = cfg.window.clone();
let window = builder.with_visible(false).build(event_loop).unwrap();
let file_handler = cfg.file_drop_handler.take();
let custom_head = cfg.custom_head.clone();
let index_file = cfg.custom_index.clone();
@@ -43,15 +41,14 @@ pub(crate) fn build(
let asset_handlers = AssetHandlerRegistry::new();
let asset_handlers_ref = asset_handlers.clone();
let mut webview = WebViewBuilder::new(window)
.unwrap()
let mut webview = WebViewBuilder::new(&window)
.with_transparent(cfg.window.window.transparent)
.with_url("dioxus://index.html/")
.unwrap()
.with_ipc_handler(move |window: &Window, payload: String| {
.with_ipc_handler(move |payload: String| {
// defer the event to the main thread
if let Ok(message) = serde_json::from_str(&payload) {
_ = proxy.send_event(UserWindowEvent(EventData::Ipc(message), window.id()));
_ = proxy.send_event(UserWindowEvent(EventData::Ipc(message), window_id));
}
})
.with_asynchronous_custom_protocol(String::from("dioxus"), {
@@ -77,10 +74,10 @@ pub(crate) fn build(
});
}
})
.with_file_drop_handler(move |window, evet| {
.with_file_drop_handler(move |event| {
file_handler
.as_ref()
.map(|handler| handler(window, evet))
.map(|handler| handler(event))
.unwrap_or_default()
})
.with_web_context(&mut web_context);
@@ -129,63 +126,47 @@ pub(crate) fn build(
web_context,
asset_handlers,
edit_queue,
window,
)
}
// /// Builds a standard menu bar depending on the users platform. It may be used as a starting point
// /// to further customize the menu bar and pass it to a [`WindowBuilder`](tao::window::WindowBuilder).
// /// > Note: The default menu bar enables macOS shortcuts like cut/copy/paste.
// /// > The menu bar differs per platform because of constraints introduced
// /// > by [`MenuItem`](tao::menu::MenuItem).
// pub fn build_default_menu_bar() -> MenuBar {
// let mut menu_bar = MenuBar::new();
/// Builds a standard menu bar depending on the users platform. It may be used as a starting point
/// to further customize the menu bar and pass it to a [`WindowBuilder`](tao::window::WindowBuilder).
/// > Note: The default menu bar enables macOS shortcuts like cut/copy/paste.
/// > The menu bar differs per platform because of constraints introduced
/// > by [`MenuItem`](tao::menu::MenuItem).
pub fn build_default_menu_bar() -> Menu {
let menu = Menu::new();
// // since it is uncommon on windows to have an "application menu"
// // we add a "window" menu to be more consistent across platforms with the standard menu
// let mut window_menu = MenuBar::new();
// #[cfg(target_os = "macos")]
// {
// window_menu.add_native_item(MenuItem::EnterFullScreen);
// window_menu.add_native_item(MenuItem::Zoom);
// window_menu.add_native_item(MenuItem::Separator);
// }
// since it is uncommon on windows to have an "application menu"
// we add a "window" menu to be more consistent across platforms with the standard menu
let window_menu = Submenu::new("Window", true);
window_menu.append_items(&[
&PredefinedMenuItem::fullscreen(None),
&PredefinedMenuItem::separator(),
&PredefinedMenuItem::hide(None),
&PredefinedMenuItem::hide_others(None),
&PredefinedMenuItem::show_all(None),
&PredefinedMenuItem::maximize(None),
&PredefinedMenuItem::minimize(None),
&PredefinedMenuItem::close_window(None),
&PredefinedMenuItem::separator(),
&PredefinedMenuItem::quit(None),
]);
// window_menu.add_native_item(MenuItem::Hide);
let edit_menu = Submenu::new("Window", true);
edit_menu.append_items(&[
&PredefinedMenuItem::undo(None),
&PredefinedMenuItem::redo(None),
&PredefinedMenuItem::separator(),
&PredefinedMenuItem::cut(None),
&PredefinedMenuItem::copy(None),
&PredefinedMenuItem::paste(None),
&PredefinedMenuItem::separator(),
&PredefinedMenuItem::select_all(None),
]);
// #[cfg(target_os = "macos")]
// {
// window_menu.add_native_item(MenuItem::HideOthers);
// window_menu.add_native_item(MenuItem::ShowAll);
// }
menu.append_items(&[&window_menu, &edit_menu]);
// window_menu.add_native_item(MenuItem::Minimize);
// window_menu.add_native_item(MenuItem::CloseWindow);
// window_menu.add_native_item(MenuItem::Separator);
// window_menu.add_native_item(MenuItem::Quit);
// menu_bar.add_submenu("Window", true, window_menu);
// // since tao supports none of the below items on linux we should only add them on macos/windows
// #[cfg(not(target_os = "linux"))]
// {
// let mut edit_menu = MenuBar::new();
// #[cfg(target_os = "macos")]
// {
// edit_menu.add_native_item(MenuItem::Undo);
// edit_menu.add_native_item(MenuItem::Redo);
// edit_menu.add_native_item(MenuItem::Separator);
// }
// edit_menu.add_native_item(MenuItem::Cut);
// edit_menu.add_native_item(MenuItem::Copy);
// edit_menu.add_native_item(MenuItem::Paste);
// #[cfg(target_os = "macos")]
// {
// edit_menu.add_native_item(MenuItem::Separator);
// edit_menu.add_native_item(MenuItem::SelectAll);
// }
// menu_bar.add_submenu("Edit", true, edit_menu);
// }
// menu_bar
// }
menu
}
+1 -1
View File
@@ -14,7 +14,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
wasm-bindgen = { workspace = true, optional = true }
js-sys = { version = "0.3.56", optional = true }
web-sys = { version = "0.3.56", optional = true, features = ["Element", "Node"] }
sledgehammer_bindgen = { git = "https://github.com/ealmloff/sledgehammer_bindgen", default-features = false, optional = true }
sledgehammer_bindgen = { version = "0.3.0", default-features = false, optional = true }
sledgehammer_utils = { version = "0.2", optional = true }
serde = { version = "1.0", features = ["derive"], optional = true }