Clean up desktop even more
This commit is contained in:
6 files changed
+137
-178
No files matched your search
+53
-89
@@ -1,48 +1,25 @@
|
|||||||
pub use crate::assets::{AssetFuture, AssetHandler, AssetRequest, AssetResponse};
|
|
||||||
pub use crate::cfg::{Config, WindowCloseBehaviour};
|
|
||||||
pub use crate::desktop_context::DesktopContext;
|
|
||||||
pub use crate::desktop_context::{window, DesktopService, WryEventHandler, WryEventHandlerId};
|
|
||||||
use crate::edits::{EditQueue, WebviewQueue};
|
|
||||||
use crate::element::DesktopElement;
|
|
||||||
use crate::eval::init_eval;
|
|
||||||
use crate::events::{IpcMessage, IpcMethod};
|
|
||||||
use crate::file_upload;
|
|
||||||
use crate::hooks::*;
|
|
||||||
use crate::query::QueryResult;
|
|
||||||
use crate::shortcut::GlobalHotKeyEvent;
|
|
||||||
use crate::shortcut::ShortcutRegistry;
|
|
||||||
pub use crate::shortcut::{ShortcutHandle, ShortcutId, ShortcutRegistryError};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
cfg::{Config, WindowCloseBehaviour},
|
||||||
desktop_context::{EventData, UserWindowEvent, WindowEventHandlers},
|
desktop_context::{EventData, UserWindowEvent, WindowEventHandlers},
|
||||||
|
edits::WebviewQueue,
|
||||||
|
element::DesktopElement,
|
||||||
|
events::IpcMessage,
|
||||||
|
file_upload::FileDialogRequest,
|
||||||
|
query::QueryResult,
|
||||||
|
shortcut::{GlobalHotKeyEvent, ShortcutRegistry},
|
||||||
webview::WebviewHandler,
|
webview::WebviewHandler,
|
||||||
};
|
};
|
||||||
use dioxus_core::*;
|
use dioxus_core::{Component, ElementId, VirtualDom};
|
||||||
use dioxus_html::{event_bubbles, MountedData};
|
use dioxus_html::MountedData;
|
||||||
use dioxus_html::{native_bind::NativeFileEngine, FormData, HtmlEvent};
|
use dioxus_html::{native_bind::NativeFileEngine, FormData, HtmlEvent};
|
||||||
use dioxus_interpreter_js::binary_protocol::Channel;
|
|
||||||
use futures_util::{pin_mut, FutureExt};
|
use futures_util::{pin_mut, FutureExt};
|
||||||
use global_hotkey::{
|
use std::cell::Cell;
|
||||||
hotkey::{Code, HotKey, Modifiers},
|
|
||||||
GlobalHotKeyManager,
|
|
||||||
};
|
|
||||||
use rustc_hash::FxHashMap;
|
|
||||||
use std::rc::Rc;
|
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};
|
use std::{collections::HashMap, sync::Arc};
|
||||||
pub use tao::dpi::{LogicalSize, PhysicalSize};
|
use tao::event_loop::EventLoopBuilder;
|
||||||
use tao::event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget};
|
use tao::event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget};
|
||||||
pub use tao::window::WindowBuilder;
|
|
||||||
use tao::window::WindowId;
|
use tao::window::WindowId;
|
||||||
use tao::{
|
use tao::{event::Event, event_loop::ControlFlow};
|
||||||
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;
|
use wry::WebView;
|
||||||
|
|
||||||
pub struct App<P> {
|
pub struct App<P> {
|
||||||
@@ -221,24 +198,14 @@ impl<P: 'static> App<P> {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
view.dom
|
view.desktop_context.query.send(result);
|
||||||
.base_scope()
|
|
||||||
.consume_context::<DesktopContext>()
|
|
||||||
.unwrap()
|
|
||||||
.query
|
|
||||||
.send(result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_user_event_msg(&mut self, msg: IpcMessage, id: WindowId) {
|
pub fn handle_user_event_msg(&mut self, msg: IpcMessage, id: WindowId) {
|
||||||
let params = msg.params();
|
let parsed_params = serde_json::from_value(msg.params())
|
||||||
|
.map_err(|err| tracing::error!("Error parsing user_event: {:?}", err));
|
||||||
|
|
||||||
let evt = match serde_json::from_value::<HtmlEvent>(params) {
|
let Ok(evt) = parsed_params else { return };
|
||||||
Ok(value) => value,
|
|
||||||
Err(err) => {
|
|
||||||
tracing::error!("Error parsing user_event: {:?}", err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let HtmlEvent {
|
let HtmlEvent {
|
||||||
element,
|
element,
|
||||||
@@ -250,20 +217,13 @@ impl<P: 'static> App<P> {
|
|||||||
let view = self.webviews.get_mut(&id).unwrap();
|
let view = self.webviews.get_mut(&id).unwrap();
|
||||||
|
|
||||||
// check for a mounted event placeholder and replace it with a desktop specific element
|
// 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 as_any = match data {
|
||||||
let query = view
|
dioxus_html::EventData::Mounted => Rc::new(MountedData::new(DesktopElement::new(
|
||||||
.dom
|
element,
|
||||||
.base_scope()
|
view.desktop_context.clone(),
|
||||||
.consume_context::<DesktopContext>()
|
view.desktop_context.query.clone(),
|
||||||
.unwrap()
|
))),
|
||||||
.query
|
_ => data.into_any(),
|
||||||
.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);
|
view.dom.handle_event(&name, as_any, element, bubbles);
|
||||||
@@ -278,9 +238,7 @@ impl<P: 'static> App<P> {
|
|||||||
webview.dom.replace_template(template);
|
webview.dom.replace_template(template);
|
||||||
}
|
}
|
||||||
|
|
||||||
let ids = self.webviews.keys().copied().collect::<Vec<_>>();
|
for id in self.webviews.keys().copied().collect::<Vec<_>>() {
|
||||||
|
|
||||||
for id in ids {
|
|
||||||
self.poll_vdom(id);
|
self.poll_vdom(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -291,31 +249,32 @@ impl<P: 'static> App<P> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_file_dialog_msg(&mut self, msg: IpcMessage, window: WindowId) {
|
pub fn handle_file_dialog_msg(&mut self, msg: IpcMessage, window: WindowId) {
|
||||||
if let Ok(file_diolog) =
|
let Ok(file_dialog) = serde_json::from_value::<FileDialogRequest>(msg.params()) else {
|
||||||
serde_json::from_value::<file_upload::FileDialogRequest>(msg.params())
|
return;
|
||||||
{
|
};
|
||||||
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();
|
let id = ElementId(file_dialog.target);
|
||||||
|
let event_name = &file_dialog.event;
|
||||||
|
let event_bubbles = file_dialog.bubbles;
|
||||||
|
let files = file_dialog.get_file_event();
|
||||||
|
|
||||||
if event_name == "change&input" {
|
let data = Rc::new(FormData {
|
||||||
view.dom
|
value: Default::default(),
|
||||||
.handle_event("input", data.clone(), id, event_bubbles);
|
values: Default::default(),
|
||||||
view.dom.handle_event("change", data, id, event_bubbles);
|
files: Some(Arc::new(NativeFileEngine::new(files))),
|
||||||
} else {
|
});
|
||||||
view.dom.handle_event(event_name, data, id, event_bubbles);
|
|
||||||
}
|
|
||||||
|
|
||||||
view.desktop_context.send_edits(view.dom.render_immediate());
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
view.desktop_context.send_edits(view.dom.render_immediate());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Poll the virtualdom until it's pending
|
/// Poll the virtualdom until it's pending
|
||||||
@@ -324,10 +283,15 @@ impl<P: 'static> App<P> {
|
|||||||
///
|
///
|
||||||
/// All IO is done on the tokio runtime we started earlier
|
/// All IO is done on the tokio runtime we started earlier
|
||||||
pub fn poll_vdom(&mut self, id: WindowId) {
|
pub fn poll_vdom(&mut self, id: WindowId) {
|
||||||
let view = self.webviews.get_mut(&id).unwrap();
|
let Some(view) = self.webviews.get_mut(&id) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
let mut cx = std::task::Context::from_waker(&view.waker);
|
let mut cx = std::task::Context::from_waker(&view.waker);
|
||||||
|
|
||||||
|
// Continously poll the virtualdom until it's pending
|
||||||
|
// Wait for work will return Ready when it has edits to be sent to the webview
|
||||||
|
// It will return Pending when it needs to be polled again - nothing is ready
|
||||||
loop {
|
loop {
|
||||||
{
|
{
|
||||||
let fut = view.dom.wait_for_work();
|
let fut = view.dom.wait_for_work();
|
||||||
@@ -335,7 +299,7 @@ impl<P: 'static> App<P> {
|
|||||||
|
|
||||||
match fut.poll_unpin(&mut cx) {
|
match fut.poll_unpin(&mut cx) {
|
||||||
std::task::Poll::Ready(_) => {}
|
std::task::Poll::Ready(_) => {}
|
||||||
std::task::Poll::Pending => break,
|
std::task::Poll::Pending => return,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
use crate::edits::EditQueue;
|
|
||||||
use crate::DesktopContext;
|
use crate::DesktopContext;
|
||||||
use dioxus_core::ScopeState;
|
|
||||||
use slab::Slab;
|
use slab::Slab;
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
@@ -15,10 +13,7 @@ use tokio::{
|
|||||||
runtime::Handle,
|
runtime::Handle,
|
||||||
sync::{OnceCell, RwLock},
|
sync::{OnceCell, RwLock},
|
||||||
};
|
};
|
||||||
use wry::{
|
use wry::http::{Request, Response};
|
||||||
http::{status::StatusCode, Request, Response},
|
|
||||||
Result,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// An arbitrary asset is an HTTP response containing a binary body.
|
/// An arbitrary asset is an HTTP response containing a binary body.
|
||||||
pub type AssetResponse = Response<Cow<'static, [u8]>>;
|
pub type AssetResponse = Response<Cow<'static, [u8]>>;
|
||||||
|
|||||||
@@ -146,25 +146,19 @@ impl DesktopService {
|
|||||||
self.shortcut_manager.clone(),
|
self.shortcut_manager.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let desktop_context = window
|
let cx = window.desktop_context.clone();
|
||||||
.dom
|
|
||||||
.base_scope()
|
|
||||||
.consume_context::<Rc<DesktopService>>()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let id = window.desktop_context.window.id();
|
|
||||||
|
|
||||||
self.proxy
|
self.proxy
|
||||||
.send_event(UserWindowEvent(EventData::NewWindow, id))
|
.send_event(UserWindowEvent(EventData::NewWindow, cx.id()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
self.proxy
|
self.proxy
|
||||||
.send_event(UserWindowEvent(EventData::Poll, id))
|
.send_event(UserWindowEvent(EventData::Poll, cx.id()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
self.pending_windows.borrow_mut().push(window);
|
self.pending_windows.borrow_mut().push(window);
|
||||||
|
|
||||||
Rc::downgrade(&desktop_context)
|
Rc::downgrade(&cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// trigger the drag-window event
|
/// trigger the drag-window event
|
||||||
|
|||||||
@@ -1,21 +1,19 @@
|
|||||||
#![allow(clippy::await_holding_refcell_ref)]
|
#![allow(clippy::await_holding_refcell_ref)]
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use dioxus_core::ScopeState;
|
|
||||||
use dioxus_html::prelude::{EvalError, EvalProvider, Evaluator};
|
use dioxus_html::prelude::{EvalError, EvalProvider, Evaluator};
|
||||||
use std::{cell::RefCell, rc::Rc};
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
use crate::{query::Query, DesktopContext};
|
use crate::{query::Query, DesktopContext};
|
||||||
|
|
||||||
/// Provides the DesktopEvalProvider through [`cx.provide_context`].
|
|
||||||
pub fn init_eval(cx: &ScopeState) {
|
|
||||||
let desktop_ctx = cx.consume_context::<DesktopContext>().unwrap();
|
|
||||||
let provider: Rc<dyn EvalProvider> = Rc::new(DesktopEvalProvider { desktop_ctx });
|
|
||||||
cx.provide_context(provider);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reprents the desktop-target's provider of evaluators.
|
/// Reprents the desktop-target's provider of evaluators.
|
||||||
pub struct DesktopEvalProvider {
|
pub struct DesktopEvalProvider {
|
||||||
desktop_ctx: DesktopContext,
|
pub(crate) desktop_ctx: DesktopContext,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DesktopEvalProvider {
|
||||||
|
pub fn new(desktop_ctx: DesktopContext) -> Self {
|
||||||
|
Self { desktop_ctx }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EvalProvider for DesktopEvalProvider {
|
impl EvalProvider for DesktopEvalProvider {
|
||||||
|
|||||||
@@ -14,74 +14,77 @@ pub(crate) struct FileDialogRequest {
|
|||||||
pub bubbles: bool,
|
pub bubbles: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(any(
|
#[allow(unused)]
|
||||||
target_os = "windows",
|
impl FileDialogRequest {
|
||||||
target_os = "macos",
|
#[cfg(not(any(
|
||||||
target_os = "linux",
|
target_os = "windows",
|
||||||
target_os = "dragonfly",
|
target_os = "macos",
|
||||||
target_os = "freebsd",
|
target_os = "linux",
|
||||||
target_os = "netbsd",
|
target_os = "dragonfly",
|
||||||
target_os = "openbsd"
|
target_os = "freebsd",
|
||||||
)))]
|
target_os = "netbsd",
|
||||||
pub(crate) fn get_file_event(_request: &FileDialogRequest) -> Vec<PathBuf> {
|
target_os = "openbsd"
|
||||||
vec![]
|
)))]
|
||||||
}
|
pub(crate) fn get_file_event(&self) -> Vec<PathBuf> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(any(
|
#[cfg(any(
|
||||||
target_os = "windows",
|
target_os = "windows",
|
||||||
target_os = "macos",
|
target_os = "macos",
|
||||||
target_os = "linux",
|
target_os = "linux",
|
||||||
target_os = "dragonfly",
|
target_os = "dragonfly",
|
||||||
target_os = "freebsd",
|
target_os = "freebsd",
|
||||||
target_os = "netbsd",
|
target_os = "netbsd",
|
||||||
target_os = "openbsd"
|
target_os = "openbsd"
|
||||||
))]
|
))]
|
||||||
pub(crate) fn get_file_event(request: &FileDialogRequest) -> Vec<PathBuf> {
|
pub(crate) fn get_file_event(&self) -> Vec<PathBuf> {
|
||||||
fn get_file_event_for_folder(
|
fn get_file_event_for_folder(
|
||||||
request: &FileDialogRequest,
|
request: &FileDialogRequest,
|
||||||
dialog: rfd::FileDialog,
|
dialog: rfd::FileDialog,
|
||||||
) -> Vec<PathBuf> {
|
) -> Vec<PathBuf> {
|
||||||
if request.multiple {
|
if request.multiple {
|
||||||
dialog.pick_folders().into_iter().flatten().collect()
|
dialog.pick_folders().into_iter().flatten().collect()
|
||||||
} else {
|
} else {
|
||||||
dialog.pick_folder().into_iter().collect()
|
dialog.pick_folder().into_iter().collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn get_file_event_for_file(
|
fn get_file_event_for_file(
|
||||||
request: &FileDialogRequest,
|
request: &FileDialogRequest,
|
||||||
mut dialog: rfd::FileDialog,
|
mut dialog: rfd::FileDialog,
|
||||||
) -> Vec<PathBuf> {
|
) -> Vec<PathBuf> {
|
||||||
let filters: Vec<_> = request
|
let filters: Vec<_> = request
|
||||||
.accept
|
.accept
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.split(',')
|
.split(',')
|
||||||
.filter_map(|s| Filters::from_str(s).ok())
|
.filter_map(|s| Filters::from_str(s).ok())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let file_extensions: Vec<_> = filters
|
let file_extensions: Vec<_> = filters
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|f| f.as_extensions().into_iter())
|
.flat_map(|f| f.as_extensions().into_iter())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
dialog = dialog.add_filter("name", file_extensions.as_slice());
|
dialog = dialog.add_filter("name", file_extensions.as_slice());
|
||||||
|
|
||||||
let files: Vec<_> = if request.multiple {
|
let files: Vec<_> = if request.multiple {
|
||||||
dialog.pick_files().into_iter().flatten().collect()
|
dialog.pick_files().into_iter().flatten().collect()
|
||||||
|
} else {
|
||||||
|
dialog.pick_file().into_iter().collect()
|
||||||
|
};
|
||||||
|
|
||||||
|
files
|
||||||
|
}
|
||||||
|
|
||||||
|
let dialog = rfd::FileDialog::new();
|
||||||
|
|
||||||
|
if self.directory {
|
||||||
|
get_file_event_for_folder(self, dialog)
|
||||||
} else {
|
} else {
|
||||||
dialog.pick_file().into_iter().collect()
|
get_file_event_for_file(self, dialog)
|
||||||
};
|
}
|
||||||
|
|
||||||
files
|
|
||||||
}
|
|
||||||
|
|
||||||
let dialog = rfd::FileDialog::new();
|
|
||||||
|
|
||||||
if request.directory {
|
|
||||||
get_file_event_for_folder(request, dialog)
|
|
||||||
} else {
|
|
||||||
get_file_event_for_file(request, dialog)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
use std::{rc::Rc, task::Waker};
|
use std::{rc::Rc, task::Waker};
|
||||||
|
|
||||||
use crate::edits::{EditQueue, WebviewQueue};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
assets::AssetHandlerRegistry, desktop_context::UserWindowEvent, waker::tao_waker, Config,
|
assets::AssetHandlerRegistry, desktop_context::UserWindowEvent, waker::tao_waker, Config,
|
||||||
DesktopContext,
|
DesktopContext,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
desktop_context::{EventData, WindowEventHandlers},
|
desktop_context::{EventData, WindowEventHandlers},
|
||||||
eval::init_eval,
|
|
||||||
shortcut::ShortcutRegistry,
|
shortcut::ShortcutRegistry,
|
||||||
};
|
};
|
||||||
|
use crate::{
|
||||||
|
edits::{EditQueue, WebviewQueue},
|
||||||
|
eval::DesktopEvalProvider,
|
||||||
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
protocol::{self},
|
protocol::{self},
|
||||||
DesktopService,
|
DesktopService,
|
||||||
@@ -162,9 +164,12 @@ pub fn create_new_window(
|
|||||||
asset_handlers,
|
asset_handlers,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
// Provide the desktop context to the virtualdom
|
||||||
dom.base_scope().provide_context(desktop_context.clone());
|
dom.base_scope().provide_context(desktop_context.clone());
|
||||||
|
|
||||||
init_eval(dom.base_scope());
|
// Also set up its eval provider
|
||||||
|
dom.base_scope()
|
||||||
|
.provide_context(Rc::new(DesktopEvalProvider::new(desktop_context.clone())));
|
||||||
|
|
||||||
WebviewHandler {
|
WebviewHandler {
|
||||||
// We want to poll the virtualdom and the event loop at the same time, so the waker will be connected to both
|
// We want to poll the virtualdom and the event loop at the same time, so the waker will be connected to both
|
||||||
|
|||||||
Reference in new issue
Block a user