Merge branch 'master' into jk/global-window-fun

This commit is contained in:
Jonathan Kelley authored and GitHub committed 2024-01-04 10:22:10 -08:00
commit 799be5e4e3
200 files changed
+6082 -2375

No files matched your search

+12 -3
View File
@@ -6,7 +6,6 @@ use wry::{
application::window::{Window, WindowBuilder},
http::{Request as HttpRequest, Response as HttpResponse},
webview::FileDropEvent,
Result as WryResult,
};
// pub(crate) type DynEventHandlerFn = dyn Fn(&mut EventLoop<()>, &mut WebView);
@@ -36,13 +35,14 @@ pub struct Config {
pub(crate) root_name: String,
pub(crate) background_color: Option<(u8, u8, u8, u8)>,
pub(crate) last_window_close_behaviour: WindowCloseBehaviour,
pub(crate) enable_default_menu_bar: bool,
}
type DropHandler = Box<dyn Fn(&Window, FileDropEvent) -> bool>;
pub(crate) type WryProtocol = (
String,
Box<dyn Fn(&HttpRequest<Vec<u8>>) -> WryResult<HttpResponse<Cow<'static, [u8]>>> + 'static>,
Box<dyn Fn(HttpRequest<Vec<u8>>) -> HttpResponse<Cow<'static, [u8]>> + 'static>,
);
impl Config {
@@ -65,9 +65,18 @@ impl Config {
root_name: "main".to_string(),
background_color: None,
last_window_close_behaviour: WindowCloseBehaviour::LastWindowExitsApp,
enable_default_menu_bar: true,
}
}
/// Set whether the default menu bar should be enabled.
///
/// > Note: `enable` is `true` by default. To disable the default menu bar pass `false`.
pub fn with_default_menu_bar(mut self, enable: bool) -> Self {
self.enable_default_menu_bar = enable;
self
}
/// set the directory from which assets will be searched in release mode
pub fn with_resource_directory(mut self, path: impl Into<PathBuf>) -> Self {
self.resource_dir = Some(path.into());
@@ -120,7 +129,7 @@ impl Config {
/// Set a custom protocol
pub fn with_custom_protocol<F>(mut self, name: String, handler: F) -> Self
where
F: Fn(&HttpRequest<Vec<u8>>) -> WryResult<HttpResponse<Cow<'static, [u8]>>> + 'static,
F: Fn(HttpRequest<Vec<u8>>) -> HttpResponse<Cow<'static, [u8]>> + 'static,
{
self.protocols.push((name, Box::new(handler)));
self
+86 -20
View File
@@ -1,21 +1,27 @@
use std::cell::RefCell;
use std::rc::Rc;
use std::rc::Weak;
use crate::create_new_window;
use crate::events::IpcMessage;
use crate::protocol::AssetFuture;
use crate::protocol::AssetHandlerRegistry;
use crate::query::QueryEngine;
use crate::shortcut::ShortcutId;
use crate::shortcut::ShortcutRegistry;
use crate::shortcut::ShortcutRegistryError;
use crate::shortcut::{HotKey, ShortcutId, ShortcutRegistry, ShortcutRegistryError};
use crate::AssetHandler;
use crate::Config;
use crate::WebviewHandler;
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 wry::application::accelerator::Accelerator;
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;
@@ -45,6 +51,45 @@ pub fn use_window(cx: &ScopeState) -> &DesktopContext {
.unwrap()
}
/// This handles communication between the requests that the webview makes and the interpreter. The interpreter constantly makes long running requests to the webview to get any edits that should be made to the DOM almost like server side events.
/// It will hold onto the requests until the interpreter is ready to handle them and hold onto any pending edits until a new request is made.
#[derive(Default, Clone)]
pub(crate) struct EditQueue {
queue: Arc<Mutex<Vec<Vec<u8>>>>,
responder: Arc<Mutex<Option<wry::webview::RequestAsyncResponder>>>,
}
impl Debug for EditQueue {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EditQueue")
.field("queue", &self.queue)
.field("responder", {
&self.responder.lock().unwrap().as_ref().map(|_| ())
})
.finish()
}
}
impl EditQueue {
pub fn handle_request(&self, responder: wry::webview::RequestAsyncResponder) {
let mut queue = self.queue.lock().unwrap();
if let Some(bytes) = queue.pop() {
responder.respond(wry::http::Response::new(bytes));
} else {
*self.responder.lock().unwrap() = Some(responder);
}
}
pub fn add_edits(&self, edits: Vec<u8>) {
let mut responder = self.responder.lock().unwrap();
if let Some(responder) = responder.take() {
responder.respond(wry::http::Response::new(edits));
} else {
self.queue.lock().unwrap().push(edits);
}
}
}
pub(crate) type WebviewQueue = Rc<RefCell<Vec<WebviewHandler>>>;
/// An imperative interface to the current window.
@@ -77,6 +122,13 @@ pub struct DesktopService {
pub(crate) shortcut_manager: ShortcutRegistry,
pub(crate) edit_queue: EditQueue,
pub(crate) templates: RefCell<FxHashMap<String, u16>>,
pub(crate) max_template_count: AtomicU16,
pub(crate) channel: RefCell<Channel>,
pub(crate) asset_handlers: AssetHandlerRegistry,
#[cfg(target_os = "ios")]
pub(crate) views: Rc<RefCell<Vec<*mut objc::runtime::Object>>>,
}
@@ -101,6 +153,8 @@ impl DesktopService {
webviews: WebviewQueue,
event_handlers: WindowEventHandlers,
shortcut_manager: ShortcutRegistry,
edit_queue: EditQueue,
asset_handlers: AssetHandlerRegistry,
) -> Self {
Self {
webview: Rc::new(webview),
@@ -110,6 +164,11 @@ impl DesktopService {
pending_windows: webviews,
event_handlers,
shortcut_manager,
edit_queue,
templates: Default::default(),
max_template_count: Default::default(),
channel: Default::default(),
asset_handlers,
#[cfg(target_os = "ios")]
views: Default::default(),
}
@@ -243,11 +302,11 @@ impl DesktopService {
/// Linux: Only works on x11. See [this issue](https://github.com/tauri-apps/tao/issues/331) for more information.
pub fn create_shortcut(
&self,
accelerator: Accelerator,
hotkey: HotKey,
callback: impl FnMut() + 'static,
) -> Result<ShortcutId, ShortcutRegistryError> {
self.shortcut_manager
.add_shortcut(accelerator, Box::new(callback))
.add_shortcut(hotkey, Box::new(callback))
}
/// Remove a global shortcut
@@ -260,6 +319,20 @@ impl DesktopService {
self.shortcut_manager.remove_all()
}
/// Provide a callback to handle asset loading yourself.
///
/// See [`use_asset_handle`](crate::use_asset_handle) for a convenient hook.
pub async fn register_asset_handler<F: AssetFuture>(&self, f: impl AssetHandler<F>) -> usize {
self.asset_handlers.register_handler(f).await
}
/// Removes an asset handler by its identifier.
///
/// Returns `None` if the handler did not exist.
pub async fn remove_asset_handler(&self, id: usize) -> Option<()> {
self.asset_handlers.remove_handler(id).await
}
/// Push an objc view to the window
#[cfg(target_os = "ios")]
pub fn push_view(&self, view: objc_id::ShareId<objc::runtime::Object>) {
@@ -379,17 +452,10 @@ impl WryWindowEventHandlerInner {
target: &EventLoopWindowTarget<UserWindowEvent>,
) {
// if this event does not apply to the window this listener cares about, return
match event {
Event::WindowEvent { window_id, .. }
| Event::MenuEvent {
window_id: Some(window_id),
..
} => {
if *window_id != self.window_id {
return;
}
if let Event::WindowEvent { window_id, .. } = event {
if *window_id != self.window_id {
return;
}
_ => (),
}
(self.handler)(event, target)
}
+3 -3
View File
@@ -30,7 +30,7 @@ impl RenderedElementBacking for DesktopElement {
>,
>,
> {
let script = format!("return window.interpreter.GetClientRect({});", self.id.0);
let script = format!("return window.interpreter.getClientRect({});", self.id.0);
let fut = self
.query
@@ -54,7 +54,7 @@ impl RenderedElementBacking for DesktopElement {
behavior: dioxus_html::ScrollBehavior,
) -> std::pin::Pin<Box<dyn futures_util::Future<Output = dioxus_html::MountedResult<()>>>> {
let script = format!(
"return window.interpreter.ScrollTo({}, {});",
"return window.interpreter.scrollTo({}, {});",
self.id.0,
serde_json::to_string(&behavior).expect("Failed to serialize ScrollBehavior")
);
@@ -81,7 +81,7 @@ impl RenderedElementBacking for DesktopElement {
focus: bool,
) -> std::pin::Pin<Box<dyn futures_util::Future<Output = dioxus_html::MountedResult<()>>>> {
let script = format!(
"return window.interpreter.SetFocus({}, {});",
"return window.interpreter.setFocus({}, {});",
self.id.0, focus
);
+181 -23
View File
@@ -10,16 +10,16 @@ mod escape;
mod eval;
mod events;
mod file_upload;
#[cfg(any(target_os = "ios", target_os = "android"))]
mod mobile_shortcut;
mod protocol;
mod query;
mod shortcut;
mod waker;
mod webview;
#[cfg(any(target_os = "ios", target_os = "android"))]
mod mobile_shortcut;
use crate::query::QueryResult;
use crate::shortcut::GlobalHotKeyEvent;
pub use cfg::{Config, WindowCloseBehaviour};
pub use desktop_context::DesktopContext;
pub use desktop_context::{
@@ -27,15 +27,19 @@ pub use desktop_context::{
};
use desktop_context::{EventData, UserWindowEvent, WebviewQueue, WindowEventHandlers};
use dioxus_core::*;
use dioxus_html::MountedData;
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 rustc_hash::FxHashMap;
pub use protocol::{use_asset_handler, AssetFuture, AssetHandler, AssetRequest, AssetResponse};
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};
@@ -43,10 +47,12 @@ use tao::event_loop::{EventLoopProxy, EventLoopWindowTarget};
pub use tao::window::WindowBuilder;
use tao::{
event::{Event, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoop},
event_loop::ControlFlow,
};
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};
@@ -54,7 +60,7 @@ use wry::{application::window::WindowId, webview::WebContext};
///
/// This function will start a multithreaded Tokio runtime as well the WebView event loop.
///
/// ```rust, ignore
/// ```rust, no_run
/// use dioxus::prelude::*;
///
/// fn main() {
@@ -77,11 +83,12 @@ pub fn launch(root: Component) {
///
/// You can configure the WebView window with a configuration closure
///
/// ```rust, ignore
/// ```rust, no_run
/// use dioxus::prelude::*;
/// use dioxus_desktop::*;
///
/// fn main() {
/// dioxus_desktop::launch_cfg(app, |c| c.with_window(|w| w.with_title("My App")));
/// dioxus_desktop::launch_cfg(app, Config::default().with_window(WindowBuilder::new().with_title("My App")));
/// }
///
/// fn app(cx: Scope) -> Element {
@@ -100,8 +107,9 @@ pub fn launch_cfg(root: Component, config_builder: Config) {
///
/// You can configure the WebView window with a configuration closure
///
/// ```rust, ignore
/// ```rust, no_run
/// use dioxus::prelude::*;
/// use dioxus_desktop::Config;
///
/// fn main() {
/// dioxus_desktop::launch_with_props(app, AppProps { name: "asd" }, Config::default());
@@ -118,7 +126,7 @@ 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 = EventLoop::<UserWindowEvent>::with_user_event();
let event_loop = EventLoopBuilder::<UserWindowEvent>::with_user_event().build();
let proxy = event_loop.create_proxy();
@@ -155,18 +163,24 @@ pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config)
let queue = WebviewQueue::default();
let shortcut_manager = ShortcutRegistry::new(&event_loop);
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;
event_loop.run(move |window_event, event_loop, control_flow| {
*control_flow = ControlFlow::Wait;
*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);
}
match window_event {
Event::WindowEvent {
event, window_id, ..
@@ -210,6 +224,8 @@ pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config)
// 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,
@@ -267,7 +283,10 @@ pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config)
let evt = match serde_json::from_value::<HtmlEvent>(params) {
Ok(value) => value,
Err(_) => return,
Err(err) => {
tracing::error!("Error parsing user_event: {:?}", err);
return;
}
};
let HtmlEvent {
@@ -299,7 +318,7 @@ pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config)
view.dom.handle_event(&name, as_any, element, bubbles);
send_edits(view.dom.render_immediate(), &view.desktop_context.webview);
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
@@ -322,7 +341,11 @@ pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config)
EventData::Ipc(msg) if msg.method() == "initialize" => {
let view = webviews.get_mut(&event.1).unwrap();
send_edits(view.dom.rebuild(), &view.desktop_context.webview);
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" => {
@@ -360,13 +383,12 @@ pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config)
view.dom.handle_event(event_name, data, id, event_bubbles);
}
send_edits(view.dom.render_immediate(), &view.desktop_context.webview);
send_edits(view.dom.render_immediate(), &view.desktop_context);
}
}
_ => {}
},
Event::GlobalShortcutEvent(id) => shortcut_manager.call_handlers(id),
_ => {}
}
})
@@ -381,7 +403,8 @@ fn create_new_window(
event_handlers: &WindowEventHandlers,
shortcut_manager: ShortcutRegistry,
) -> WebviewHandler {
let (webview, web_context) = webview::build(&mut cfg, event_loop, proxy.clone());
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(),
@@ -389,6 +412,8 @@ fn create_new_window(
queue.clone(),
event_handlers.clone(),
shortcut_manager,
asset_handlers,
edit_queue,
));
let cx = dom.base_scope();
@@ -435,16 +460,149 @@ fn poll_vdom(view: &mut WebviewHandler) {
}
}
send_edits(view.dom.render_immediate(), &view.desktop_context.webview);
send_edits(view.dom.render_immediate(), &view.desktop_context);
}
}
/// Send a list of mutations to the webview
fn send_edits(edits: Mutations, webview: &WebView) {
let serialized = serde_json::to_string(&edits).unwrap();
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)
}
}
// todo: use SSE and binary data to send the edits with lower overhead
_ = webview.evaluate_script(&format!("window.interpreter.handleEdits({serialized})"));
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
+54 -20
View File
@@ -1,29 +1,51 @@
#![allow(unused)]
use super::*;
use wry::application::accelerator::Accelerator;
use std::str::FromStr;
use wry::application::event_loop::EventLoopWindowTarget;
pub struct GlobalShortcut();
pub struct ShortcutManager();
use dioxus_html::input_data::keyboard_types::Modifiers;
impl ShortcutManager {
pub fn new<T>(target: &EventLoopWindowTarget<T>) -> Self {
Self()
#[derive(Clone, Debug)]
pub struct Accelerator;
#[derive(Clone, Copy)]
pub struct HotKey;
impl HotKey {
pub fn new(mods: Option<Modifiers>, key: Code) -> Self {
Self
}
pub fn register(
&mut self,
accelerator: Accelerator,
) -> Result<GlobalShortcut, ShortcutManagerError> {
Ok(GlobalShortcut())
pub fn id(&self) -> u32 {
0
}
}
impl FromStr for HotKey {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(HotKey)
}
}
pub struct GlobalHotKeyManager();
impl GlobalHotKeyManager {
pub fn new() -> Result<Self, HotkeyError> {
Ok(Self())
}
pub fn unregister(&mut self, id: ShortcutId) -> Result<(), ShortcutManagerError> {
pub fn register(&mut self, accelerator: HotKey) -> Result<HotKey, HotkeyError> {
Ok(HotKey)
}
pub fn unregister(&mut self, id: HotKey) -> Result<(), HotkeyError> {
Ok(())
}
pub fn unregister_all(&mut self) -> Result<(), ShortcutManagerError> {
pub fn unregister_all(&mut self, _: &[HotKey]) -> Result<(), HotkeyError> {
Ok(())
}
}
@@ -33,23 +55,35 @@ use std::{error, fmt};
/// An error whose cause the `ShortcutManager` to fail.
#[non_exhaustive]
#[derive(Debug)]
pub enum ShortcutManagerError {
pub enum HotkeyError {
AcceleratorAlreadyRegistered(Accelerator),
AcceleratorNotRegistered(Accelerator),
InvalidAccelerator(String),
HotKeyParseError(String),
}
impl error::Error for ShortcutManagerError {}
impl fmt::Display for ShortcutManagerError {
impl error::Error for HotkeyError {}
impl fmt::Display for HotkeyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self {
ShortcutManagerError::AcceleratorAlreadyRegistered(e) => {
HotkeyError::AcceleratorAlreadyRegistered(e) => {
f.pad(&format!("hotkey already registered: {:?}", e))
}
ShortcutManagerError::AcceleratorNotRegistered(e) => {
HotkeyError::AcceleratorNotRegistered(e) => {
f.pad(&format!("hotkey not registered: {:?}", e))
}
ShortcutManagerError::InvalidAccelerator(e) => e.fmt(f),
HotkeyError::HotKeyParseError(e) => e.fmt(f),
}
}
}
pub struct GlobalHotKeyEvent {
pub id: u32,
}
impl GlobalHotKeyEvent {
pub fn receiver() -> crossbeam_channel::Receiver<GlobalHotKeyEvent> {
crossbeam_channel::unbounded().1
}
}
pub(crate) type Code = dioxus_html::input_data::keyboard_types::Code;
+238 -32
View File
@@ -1,14 +1,31 @@
use dioxus_core::ScopeState;
use dioxus_interpreter_js::{COMMON_JS, INTERPRETER_JS};
use slab::Slab;
use std::{
borrow::Cow,
future::Future,
ops::Deref,
path::{Path, PathBuf},
pin::Pin,
rc::Rc,
sync::Arc,
};
use tokio::{
runtime::Handle,
sync::{OnceCell, RwLock},
};
use wry::{
http::{status::StatusCode, Request, Response},
webview::RequestAsyncResponder,
Result,
};
use crate::{use_window, DesktopContext};
fn module_loader(root_name: &str) -> String {
use crate::desktop_context::EditQueue;
static MINIFIED: &str = include_str!("./minified.js");
fn module_loader(root_name: &str, headless: bool) -> String {
let js = INTERPRETER_JS.replace(
"/*POST_HANDLE_EDITS*/",
r#"// Prevent file inputs from opening the file dialog on click
@@ -35,35 +52,188 @@ fn module_loader(root_name: &str) -> String {
}
}"#,
);
format!(
r#"
<script type="module">
{js}
let rootname = "{root_name}";
let root = window.document.getElementById(rootname);
if (root != null) {{
window.interpreter = new Interpreter(root, new InterpreterConfig(true));
window.ipc.postMessage(serializeIpcMessage("initialize"));
{MINIFIED}
// Wait for the page to load
window.onload = function() {{
let rootname = "{root_name}";
let root_element = window.document.getElementById(rootname);
if (root_element != null) {{
window.interpreter.initialize(root_element);
window.ipc.postMessage(window.interpreter.serializeIpcMessage("initialize"));
}}
window.interpreter.wait_for_request({headless});
}}
</script>
"#
)
}
pub(super) fn desktop_handler(
request: &Request<Vec<u8>>,
/// An arbitrary asset is an HTTP response containing a binary body.
pub type AssetResponse = Response<Cow<'static, [u8]>>;
/// A future that returns an [`AssetResponse`]. This future may be spawned in a new thread,
/// so it must be [`Send`], [`Sync`], and `'static`.
pub trait AssetFuture: Future<Output = Option<AssetResponse>> + Send + Sync + 'static {}
impl<T: Future<Output = Option<AssetResponse>> + Send + Sync + 'static> AssetFuture for T {}
#[derive(Debug, Clone)]
/// A request for an asset. This is a wrapper around [`Request<Vec<u8>>`] that provides methods specific to asset requests.
pub struct AssetRequest {
path: PathBuf,
request: Arc<Request<Vec<u8>>>,
}
impl AssetRequest {
/// Get the path the asset request is for
pub fn path(&self) -> &Path {
&self.path
}
}
impl From<Request<Vec<u8>>> for AssetRequest {
fn from(request: Request<Vec<u8>>) -> Self {
let decoded = urlencoding::decode(request.uri().path().trim_start_matches('/'))
.expect("expected URL to be UTF-8 encoded");
let path = PathBuf::from(&*decoded);
Self {
request: Arc::new(request),
path,
}
}
}
impl Deref for AssetRequest {
type Target = Request<Vec<u8>>;
fn deref(&self) -> &Self::Target {
&self.request
}
}
/// A handler that takes an [`AssetRequest`] and returns a future that either loads the asset, or returns `None`.
/// This handler is stashed indefinitely in a context object, so it must be `'static`.
pub trait AssetHandler<F: AssetFuture>: Send + Sync + 'static {
/// Handle an asset request, returning a future that either loads the asset, or returns `None`
fn handle_request(&self, request: &AssetRequest) -> F;
}
impl<F: AssetFuture, T: Fn(&AssetRequest) -> F + Send + Sync + 'static> AssetHandler<F> for T {
fn handle_request(&self, request: &AssetRequest) -> F {
self(request)
}
}
type AssetHandlerRegistryInner =
Slab<Box<dyn Fn(&AssetRequest) -> Pin<Box<dyn AssetFuture>> + Send + Sync + 'static>>;
#[derive(Clone)]
pub struct AssetHandlerRegistry(Arc<RwLock<AssetHandlerRegistryInner>>);
impl AssetHandlerRegistry {
pub fn new() -> Self {
AssetHandlerRegistry(Arc::new(RwLock::new(Slab::new())))
}
pub async fn register_handler<F: AssetFuture>(&self, f: impl AssetHandler<F>) -> usize {
let mut registry = self.0.write().await;
registry.insert(Box::new(move |req| Box::pin(f.handle_request(req))))
}
pub async fn remove_handler(&self, id: usize) -> Option<()> {
let mut registry = self.0.write().await;
registry.try_remove(id).map(|_| ())
}
pub async fn try_handlers(&self, req: &AssetRequest) -> Option<AssetResponse> {
let registry = self.0.read().await;
for (_, handler) in registry.iter() {
if let Some(response) = handler(req).await {
return Some(response);
}
}
None
}
}
/// A handle to a registered asset handler.
pub struct AssetHandlerHandle {
desktop: DesktopContext,
handler_id: Rc<OnceCell<usize>>,
}
impl AssetHandlerHandle {
/// Returns the ID for this handle.
///
/// Because registering an ID is asynchronous, this may return `None` if the
/// registration has not completed yet.
pub fn handler_id(&self) -> Option<usize> {
self.handler_id.get().copied()
}
}
impl Drop for AssetHandlerHandle {
fn drop(&mut self) {
let cell = Rc::clone(&self.handler_id);
let desktop = Rc::clone(&self.desktop);
tokio::task::block_in_place(move || {
Handle::current().block_on(async move {
if let Some(id) = cell.get() {
desktop.asset_handlers.remove_handler(*id).await;
}
})
});
}
}
/// Provide a callback to handle asset loading yourself.
///
/// The callback takes a path as requested by the web view, and it should return `Some(response)`
/// if you want to load the asset, and `None` if you want to fallback on the default behavior.
pub fn use_asset_handler<F: AssetFuture>(
cx: &ScopeState,
handler: impl AssetHandler<F>,
) -> &AssetHandlerHandle {
let desktop = Rc::clone(use_window(cx));
cx.use_hook(|| {
let handler_id = Rc::new(OnceCell::new());
let handler_id_ref = Rc::clone(&handler_id);
let desktop_ref = Rc::clone(&desktop);
cx.push_future(async move {
let id = desktop.asset_handlers.register_handler(handler).await;
handler_id.set(id).unwrap();
});
AssetHandlerHandle {
desktop: desktop_ref,
handler_id: handler_id_ref,
}
})
}
pub(super) async fn desktop_handler(
request: Request<Vec<u8>>,
custom_head: Option<String>,
custom_index: Option<String>,
root_name: &str,
) -> Result<Response<Cow<'static, [u8]>>> {
asset_handlers: &AssetHandlerRegistry,
edit_queue: &EditQueue,
headless: bool,
) -> Result<AssetResponse> {
let request = AssetRequest::from(request);
// If the request is for the root, we'll serve the index.html file.
if request.uri().path() == "/" {
// If a custom index is provided, just defer to that, expecting the user to know what they're doing.
// we'll look for the closing </body> tag and insert our little module loader there.
let body = match custom_index {
Some(custom_index) => custom_index
.replace("</body>", &format!("{}</body>", module_loader(root_name)))
.replace(
"</body>",
&format!("{}</body>", module_loader(root_name, headless)),
)
.into_bytes(),
None => {
@@ -75,47 +245,83 @@ pub(super) fn desktop_handler(
}
template
.replace("<!-- MODULE LOADER -->", &module_loader(root_name))
.replace(
"<!-- MODULE LOADER -->",
&module_loader(root_name, headless),
)
.into_bytes()
}
};
return Response::builder()
match Response::builder()
.header("Content-Type", "text/html")
.header("Access-Control-Allow-Origin", "*")
.body(Cow::from(body))
.map_err(From::from);
} else if request.uri().path() == "/common.js" {
return Response::builder()
.header("Content-Type", "text/javascript")
.body(Cow::from(COMMON_JS.as_bytes()))
.map_err(From::from);
{
Ok(response) => {
responder.respond(response);
return;
}
Err(err) => tracing::error!("error building response: {}", err),
}
} else if request.uri().path().trim_matches('/') == "edits" {
edit_queue.handle_request(responder);
return;
}
// If the user provided a custom asset handler, then call it and return the response
// if the request was handled.
if let Some(response) = asset_handlers.try_handlers(&request).await {
return Ok(response);
}
// Else, try to serve a file from the filesystem.
let decoded = urlencoding::decode(request.uri().path().trim_start_matches('/'))
.expect("expected URL to be UTF-8 encoded");
let path = PathBuf::from(&*decoded);
// If the path is relative, we'll try to serve it from the assets directory.
let mut asset = get_asset_root()
.unwrap_or_else(|| Path::new(".").to_path_buf())
.join(&path);
.join(&request.path);
if !asset.exists() {
asset = PathBuf::from("/").join(path);
asset = PathBuf::from("/").join(request.path);
}
if asset.exists() {
return Response::builder()
.header("Content-Type", get_mime_from_path(&asset)?)
.body(Cow::from(std::fs::read(asset)?))
.map_err(From::from);
let content_type = match get_mime_from_path(&asset) {
Ok(content_type) => content_type,
Err(err) => {
tracing::error!("error getting mime type: {}", err);
return;
}
};
let asset = match std::fs::read(asset) {
Ok(asset) => asset,
Err(err) => {
tracing::error!("error reading asset: {}", err);
return;
}
};
match Response::builder()
.header("Content-Type", content_type)
.body(Cow::from(asset))
{
Ok(response) => {
responder.respond(response);
return;
}
Err(err) => tracing::error!("error building response: {}", err),
}
}
Response::builder()
match Response::builder()
.status(StatusCode::NOT_FOUND)
.body(Cow::from(String::from("Not Found").into_bytes()))
.map_err(From::from)
{
Ok(response) => {
responder.respond(response);
}
Err(err) => tracing::error!("error building response: {}", err),
}
}
#[allow(unreachable_code)]
@@ -153,7 +359,7 @@ fn get_asset_root() -> Option<PathBuf> {
/// Get the mime type from a path-like string
fn get_mime_from_path(trimmed: &Path) -> Result<&'static str> {
if trimmed.ends_with(".svg") {
if trimmed.extension().is_some_and(|ext| ext == "svg") {
return Ok("image/svg+xml");
}
+149 -158
View File
@@ -3,11 +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::{
accelerator::{Accelerator, AcceleratorId},
event_loop::EventLoopWindowTarget,
keyboard::{KeyCode, ModifiersState},
};
use wry::application::keyboard::ModifiersState;
use crate::{desktop_context::DesktopContext, use_window};
@@ -20,22 +16,25 @@ use crate::{desktop_context::DesktopContext, use_window};
target_os = "netbsd",
target_os = "openbsd"
))]
use wry::application::global_shortcut::{GlobalShortcut, ShortcutManager, ShortcutManagerError};
pub use global_hotkey::{
hotkey::{Code, HotKey},
Error as HotkeyError, GlobalHotKeyEvent, GlobalHotKeyManager,
};
#[cfg(any(target_os = "ios", target_os = "android"))]
pub use crate::mobile_shortcut::*;
#[derive(Clone)]
pub(crate) struct ShortcutRegistry {
manager: Rc<RefCell<ShortcutManager>>,
manager: Rc<RefCell<GlobalHotKeyManager>>,
shortcuts: ShortcutMap,
}
type ShortcutMap = Rc<RefCell<HashMap<AcceleratorId, Shortcut>>>;
type ShortcutMap = Rc<RefCell<HashMap<u32, Shortcut>>>;
struct Shortcut {
#[allow(unused)]
shortcut: GlobalShortcut,
shortcut: HotKey,
callbacks: Slab<Box<dyn FnMut()>>,
}
@@ -54,15 +53,15 @@ impl Shortcut {
}
impl ShortcutRegistry {
pub fn new<T>(target: &EventLoopWindowTarget<T>) -> Self {
pub fn new() -> Self {
Self {
manager: Rc::new(RefCell::new(ShortcutManager::new(target))),
manager: Rc::new(RefCell::new(GlobalHotKeyManager::new().unwrap())),
shortcuts: Rc::new(RefCell::new(HashMap::new())),
}
}
pub(crate) fn call_handlers(&self, id: AcceleratorId) {
if let Some(Shortcut { callbacks, .. }) = self.shortcuts.borrow_mut().get_mut(&id) {
pub(crate) fn call_handlers(&self, id: GlobalHotKeyEvent) {
if let Some(Shortcut { callbacks, .. }) = self.shortcuts.borrow_mut().get_mut(&id.id) {
for (_, callback) in callbacks.iter_mut() {
(callback)();
}
@@ -71,10 +70,10 @@ impl ShortcutRegistry {
pub(crate) fn add_shortcut(
&self,
accelerator: Accelerator,
hotkey: HotKey,
callback: Box<dyn FnMut()>,
) -> Result<ShortcutId, ShortcutRegistryError> {
let accelerator_id = accelerator.clone().id();
let accelerator_id = hotkey.clone().id();
let mut shortcuts = self.shortcuts.borrow_mut();
Ok(
if let Some(callbacks) = shortcuts.get_mut(&accelerator_id) {
@@ -84,12 +83,12 @@ impl ShortcutRegistry {
number: id,
}
} else {
match self.manager.borrow_mut().register(accelerator) {
Ok(global_shortcut) => {
match self.manager.borrow_mut().register(hotkey) {
Ok(_) => {
let mut slab = Slab::new();
let id = slab.insert(callback);
let shortcut = Shortcut {
shortcut: global_shortcut,
shortcut: hotkey,
callbacks: slab,
};
shortcuts.insert(accelerator_id, shortcut);
@@ -98,7 +97,7 @@ impl ShortcutRegistry {
number: id,
}
}
Err(ShortcutManagerError::InvalidAccelerator(shortcut)) => {
Err(HotkeyError::HotKeyParseError(shortcut)) => {
return Err(ShortcutRegistryError::InvalidShortcut(shortcut))
}
Err(err) => return Err(ShortcutRegistryError::Other(Box::new(err))),
@@ -113,15 +112,6 @@ impl ShortcutRegistry {
callbacks.remove(id.number);
if callbacks.is_empty() {
if let Some(_shortcut) = shortcuts.remove(&id.id) {
#[cfg(any(
target_os = "windows",
target_os = "macos",
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
let _ = self.manager.borrow_mut().unregister(_shortcut.shortcut);
}
}
@@ -130,8 +120,8 @@ impl ShortcutRegistry {
pub(crate) fn remove_all(&self) {
let mut shortcuts = self.shortcuts.borrow_mut();
shortcuts.clear();
let _ = self.manager.borrow_mut().unregister_all();
let hotkeys: Vec<_> = shortcuts.drain().map(|(_, v)| v.shortcut).collect();
let _ = self.manager.borrow_mut().unregister_all(&hotkeys);
}
}
@@ -148,7 +138,7 @@ pub enum ShortcutRegistryError {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
/// An global id for a shortcut.
pub struct ShortcutId {
id: AcceleratorId,
id: u32,
number: usize,
}
@@ -160,30 +150,30 @@ pub struct ShortcutHandle {
}
pub trait IntoAccelerator {
fn accelerator(&self) -> Accelerator;
fn accelerator(&self) -> HotKey;
}
impl IntoAccelerator for (dioxus_html::KeyCode, ModifiersState) {
fn accelerator(&self) -> Accelerator {
Accelerator::new(Some(self.1), self.0.into_key_code())
fn accelerator(&self) -> HotKey {
HotKey::new(Some(self.1.into_modifiers_state()), self.0.into_key_code())
}
}
impl IntoAccelerator for (ModifiersState, dioxus_html::KeyCode) {
fn accelerator(&self) -> Accelerator {
Accelerator::new(Some(self.0), self.1.into_key_code())
fn accelerator(&self) -> HotKey {
HotKey::new(Some(self.0.into_modifiers_state()), self.1.into_key_code())
}
}
impl IntoAccelerator for dioxus_html::KeyCode {
fn accelerator(&self) -> Accelerator {
Accelerator::new(None, self.into_key_code())
fn accelerator(&self) -> HotKey {
HotKey::new(None, self.into_key_code())
}
}
impl IntoAccelerator for &str {
fn accelerator(&self) -> Accelerator {
Accelerator::from_str(self).unwrap()
fn accelerator(&self) -> HotKey {
HotKey::from_str(self).unwrap()
}
}
@@ -220,143 +210,144 @@ impl Drop for ShortcutHandle {
}
pub trait IntoModifersState {
fn into_modifiers_state(self) -> ModifiersState;
fn into_modifiers_state(self) -> Modifiers;
}
impl IntoModifersState for ModifiersState {
fn into_modifiers_state(self) -> ModifiersState {
self
fn into_modifiers_state(self) -> Modifiers {
let mut modifiers = Modifiers::default();
if self.shift_key() {
modifiers |= Modifiers::SHIFT;
}
if self.control_key() {
modifiers |= Modifiers::CONTROL;
}
if self.alt_key() {
modifiers |= Modifiers::ALT;
}
if self.super_key() {
modifiers |= Modifiers::META;
}
modifiers
}
}
impl IntoModifersState for Modifiers {
fn into_modifiers_state(self) -> ModifiersState {
let mut state = ModifiersState::empty();
if self.contains(Modifiers::SHIFT) {
state |= ModifiersState::SHIFT
}
if self.contains(Modifiers::CONTROL) {
state |= ModifiersState::CONTROL
}
if self.contains(Modifiers::ALT) {
state |= ModifiersState::ALT
}
if self.contains(Modifiers::META) || self.contains(Modifiers::SUPER) {
state |= ModifiersState::SUPER
}
state
fn into_modifiers_state(self) -> Modifiers {
self
}
}
pub trait IntoKeyCode {
fn into_key_code(self) -> KeyCode;
fn into_key_code(self) -> Code;
}
impl IntoKeyCode for KeyCode {
fn into_key_code(self) -> KeyCode {
impl IntoKeyCode for Code {
fn into_key_code(self) -> Code {
self
}
}
impl IntoKeyCode for dioxus_html::KeyCode {
fn into_key_code(self) -> KeyCode {
fn into_key_code(self) -> Code {
match self {
dioxus_html::KeyCode::Backspace => KeyCode::Backspace,
dioxus_html::KeyCode::Tab => KeyCode::Tab,
dioxus_html::KeyCode::Clear => KeyCode::NumpadClear,
dioxus_html::KeyCode::Enter => KeyCode::Enter,
dioxus_html::KeyCode::Shift => KeyCode::ShiftLeft,
dioxus_html::KeyCode::Ctrl => KeyCode::ControlLeft,
dioxus_html::KeyCode::Alt => KeyCode::AltLeft,
dioxus_html::KeyCode::Pause => KeyCode::Pause,
dioxus_html::KeyCode::CapsLock => KeyCode::CapsLock,
dioxus_html::KeyCode::Escape => KeyCode::Escape,
dioxus_html::KeyCode::Space => KeyCode::Space,
dioxus_html::KeyCode::PageUp => KeyCode::PageUp,
dioxus_html::KeyCode::PageDown => KeyCode::PageDown,
dioxus_html::KeyCode::End => KeyCode::End,
dioxus_html::KeyCode::Home => KeyCode::Home,
dioxus_html::KeyCode::LeftArrow => KeyCode::ArrowLeft,
dioxus_html::KeyCode::UpArrow => KeyCode::ArrowUp,
dioxus_html::KeyCode::RightArrow => KeyCode::ArrowRight,
dioxus_html::KeyCode::DownArrow => KeyCode::ArrowDown,
dioxus_html::KeyCode::Insert => KeyCode::Insert,
dioxus_html::KeyCode::Delete => KeyCode::Delete,
dioxus_html::KeyCode::Num0 => KeyCode::Numpad0,
dioxus_html::KeyCode::Num1 => KeyCode::Numpad1,
dioxus_html::KeyCode::Num2 => KeyCode::Numpad2,
dioxus_html::KeyCode::Num3 => KeyCode::Numpad3,
dioxus_html::KeyCode::Num4 => KeyCode::Numpad4,
dioxus_html::KeyCode::Num5 => KeyCode::Numpad5,
dioxus_html::KeyCode::Num6 => KeyCode::Numpad6,
dioxus_html::KeyCode::Num7 => KeyCode::Numpad7,
dioxus_html::KeyCode::Num8 => KeyCode::Numpad8,
dioxus_html::KeyCode::Num9 => KeyCode::Numpad9,
dioxus_html::KeyCode::A => KeyCode::KeyA,
dioxus_html::KeyCode::B => KeyCode::KeyB,
dioxus_html::KeyCode::C => KeyCode::KeyC,
dioxus_html::KeyCode::D => KeyCode::KeyD,
dioxus_html::KeyCode::E => KeyCode::KeyE,
dioxus_html::KeyCode::F => KeyCode::KeyF,
dioxus_html::KeyCode::G => KeyCode::KeyG,
dioxus_html::KeyCode::H => KeyCode::KeyH,
dioxus_html::KeyCode::I => KeyCode::KeyI,
dioxus_html::KeyCode::J => KeyCode::KeyJ,
dioxus_html::KeyCode::K => KeyCode::KeyK,
dioxus_html::KeyCode::L => KeyCode::KeyL,
dioxus_html::KeyCode::M => KeyCode::KeyM,
dioxus_html::KeyCode::N => KeyCode::KeyN,
dioxus_html::KeyCode::O => KeyCode::KeyO,
dioxus_html::KeyCode::P => KeyCode::KeyP,
dioxus_html::KeyCode::Q => KeyCode::KeyQ,
dioxus_html::KeyCode::R => KeyCode::KeyR,
dioxus_html::KeyCode::S => KeyCode::KeyS,
dioxus_html::KeyCode::T => KeyCode::KeyT,
dioxus_html::KeyCode::U => KeyCode::KeyU,
dioxus_html::KeyCode::V => KeyCode::KeyV,
dioxus_html::KeyCode::W => KeyCode::KeyW,
dioxus_html::KeyCode::X => KeyCode::KeyX,
dioxus_html::KeyCode::Y => KeyCode::KeyY,
dioxus_html::KeyCode::Z => KeyCode::KeyZ,
dioxus_html::KeyCode::Numpad0 => KeyCode::Numpad0,
dioxus_html::KeyCode::Numpad1 => KeyCode::Numpad1,
dioxus_html::KeyCode::Numpad2 => KeyCode::Numpad2,
dioxus_html::KeyCode::Numpad3 => KeyCode::Numpad3,
dioxus_html::KeyCode::Numpad4 => KeyCode::Numpad4,
dioxus_html::KeyCode::Numpad5 => KeyCode::Numpad5,
dioxus_html::KeyCode::Numpad6 => KeyCode::Numpad6,
dioxus_html::KeyCode::Numpad7 => KeyCode::Numpad7,
dioxus_html::KeyCode::Numpad8 => KeyCode::Numpad8,
dioxus_html::KeyCode::Numpad9 => KeyCode::Numpad9,
dioxus_html::KeyCode::Multiply => KeyCode::NumpadMultiply,
dioxus_html::KeyCode::Add => KeyCode::NumpadAdd,
dioxus_html::KeyCode::Subtract => KeyCode::NumpadSubtract,
dioxus_html::KeyCode::DecimalPoint => KeyCode::NumpadDecimal,
dioxus_html::KeyCode::Divide => KeyCode::NumpadDivide,
dioxus_html::KeyCode::F1 => KeyCode::F1,
dioxus_html::KeyCode::F2 => KeyCode::F2,
dioxus_html::KeyCode::F3 => KeyCode::F3,
dioxus_html::KeyCode::F4 => KeyCode::F4,
dioxus_html::KeyCode::F5 => KeyCode::F5,
dioxus_html::KeyCode::F6 => KeyCode::F6,
dioxus_html::KeyCode::F7 => KeyCode::F7,
dioxus_html::KeyCode::F8 => KeyCode::F8,
dioxus_html::KeyCode::F9 => KeyCode::F9,
dioxus_html::KeyCode::F10 => KeyCode::F10,
dioxus_html::KeyCode::F11 => KeyCode::F11,
dioxus_html::KeyCode::F12 => KeyCode::F12,
dioxus_html::KeyCode::NumLock => KeyCode::NumLock,
dioxus_html::KeyCode::ScrollLock => KeyCode::ScrollLock,
dioxus_html::KeyCode::Semicolon => KeyCode::Semicolon,
dioxus_html::KeyCode::EqualSign => KeyCode::Equal,
dioxus_html::KeyCode::Comma => KeyCode::Comma,
dioxus_html::KeyCode::Period => KeyCode::Period,
dioxus_html::KeyCode::ForwardSlash => KeyCode::Slash,
dioxus_html::KeyCode::GraveAccent => KeyCode::Backquote,
dioxus_html::KeyCode::OpenBracket => KeyCode::BracketLeft,
dioxus_html::KeyCode::BackSlash => KeyCode::Backslash,
dioxus_html::KeyCode::CloseBraket => KeyCode::BracketRight,
dioxus_html::KeyCode::SingleQuote => KeyCode::Quote,
dioxus_html::KeyCode::Backspace => Code::Backspace,
dioxus_html::KeyCode::Tab => Code::Tab,
dioxus_html::KeyCode::Clear => Code::NumpadClear,
dioxus_html::KeyCode::Enter => Code::Enter,
dioxus_html::KeyCode::Shift => Code::ShiftLeft,
dioxus_html::KeyCode::Ctrl => Code::ControlLeft,
dioxus_html::KeyCode::Alt => Code::AltLeft,
dioxus_html::KeyCode::Pause => Code::Pause,
dioxus_html::KeyCode::CapsLock => Code::CapsLock,
dioxus_html::KeyCode::Escape => Code::Escape,
dioxus_html::KeyCode::Space => Code::Space,
dioxus_html::KeyCode::PageUp => Code::PageUp,
dioxus_html::KeyCode::PageDown => Code::PageDown,
dioxus_html::KeyCode::End => Code::End,
dioxus_html::KeyCode::Home => Code::Home,
dioxus_html::KeyCode::LeftArrow => Code::ArrowLeft,
dioxus_html::KeyCode::UpArrow => Code::ArrowUp,
dioxus_html::KeyCode::RightArrow => Code::ArrowRight,
dioxus_html::KeyCode::DownArrow => Code::ArrowDown,
dioxus_html::KeyCode::Insert => Code::Insert,
dioxus_html::KeyCode::Delete => Code::Delete,
dioxus_html::KeyCode::Num0 => Code::Numpad0,
dioxus_html::KeyCode::Num1 => Code::Numpad1,
dioxus_html::KeyCode::Num2 => Code::Numpad2,
dioxus_html::KeyCode::Num3 => Code::Numpad3,
dioxus_html::KeyCode::Num4 => Code::Numpad4,
dioxus_html::KeyCode::Num5 => Code::Numpad5,
dioxus_html::KeyCode::Num6 => Code::Numpad6,
dioxus_html::KeyCode::Num7 => Code::Numpad7,
dioxus_html::KeyCode::Num8 => Code::Numpad8,
dioxus_html::KeyCode::Num9 => Code::Numpad9,
dioxus_html::KeyCode::A => Code::KeyA,
dioxus_html::KeyCode::B => Code::KeyB,
dioxus_html::KeyCode::C => Code::KeyC,
dioxus_html::KeyCode::D => Code::KeyD,
dioxus_html::KeyCode::E => Code::KeyE,
dioxus_html::KeyCode::F => Code::KeyF,
dioxus_html::KeyCode::G => Code::KeyG,
dioxus_html::KeyCode::H => Code::KeyH,
dioxus_html::KeyCode::I => Code::KeyI,
dioxus_html::KeyCode::J => Code::KeyJ,
dioxus_html::KeyCode::K => Code::KeyK,
dioxus_html::KeyCode::L => Code::KeyL,
dioxus_html::KeyCode::M => Code::KeyM,
dioxus_html::KeyCode::N => Code::KeyN,
dioxus_html::KeyCode::O => Code::KeyO,
dioxus_html::KeyCode::P => Code::KeyP,
dioxus_html::KeyCode::Q => Code::KeyQ,
dioxus_html::KeyCode::R => Code::KeyR,
dioxus_html::KeyCode::S => Code::KeyS,
dioxus_html::KeyCode::T => Code::KeyT,
dioxus_html::KeyCode::U => Code::KeyU,
dioxus_html::KeyCode::V => Code::KeyV,
dioxus_html::KeyCode::W => Code::KeyW,
dioxus_html::KeyCode::X => Code::KeyX,
dioxus_html::KeyCode::Y => Code::KeyY,
dioxus_html::KeyCode::Z => Code::KeyZ,
dioxus_html::KeyCode::Numpad0 => Code::Numpad0,
dioxus_html::KeyCode::Numpad1 => Code::Numpad1,
dioxus_html::KeyCode::Numpad2 => Code::Numpad2,
dioxus_html::KeyCode::Numpad3 => Code::Numpad3,
dioxus_html::KeyCode::Numpad4 => Code::Numpad4,
dioxus_html::KeyCode::Numpad5 => Code::Numpad5,
dioxus_html::KeyCode::Numpad6 => Code::Numpad6,
dioxus_html::KeyCode::Numpad7 => Code::Numpad7,
dioxus_html::KeyCode::Numpad8 => Code::Numpad8,
dioxus_html::KeyCode::Numpad9 => Code::Numpad9,
dioxus_html::KeyCode::Multiply => Code::NumpadMultiply,
dioxus_html::KeyCode::Add => Code::NumpadAdd,
dioxus_html::KeyCode::Subtract => Code::NumpadSubtract,
dioxus_html::KeyCode::DecimalPoint => Code::NumpadDecimal,
dioxus_html::KeyCode::Divide => Code::NumpadDivide,
dioxus_html::KeyCode::F1 => Code::F1,
dioxus_html::KeyCode::F2 => Code::F2,
dioxus_html::KeyCode::F3 => Code::F3,
dioxus_html::KeyCode::F4 => Code::F4,
dioxus_html::KeyCode::F5 => Code::F5,
dioxus_html::KeyCode::F6 => Code::F6,
dioxus_html::KeyCode::F7 => Code::F7,
dioxus_html::KeyCode::F8 => Code::F8,
dioxus_html::KeyCode::F9 => Code::F9,
dioxus_html::KeyCode::F10 => Code::F10,
dioxus_html::KeyCode::F11 => Code::F11,
dioxus_html::KeyCode::F12 => Code::F12,
dioxus_html::KeyCode::NumLock => Code::NumLock,
dioxus_html::KeyCode::ScrollLock => Code::ScrollLock,
dioxus_html::KeyCode::Semicolon => Code::Semicolon,
dioxus_html::KeyCode::EqualSign => Code::Equal,
dioxus_html::KeyCode::Comma => Code::Comma,
dioxus_html::KeyCode::Period => Code::Period,
dioxus_html::KeyCode::ForwardSlash => Code::Slash,
dioxus_html::KeyCode::GraveAccent => Code::Backquote,
dioxus_html::KeyCode::OpenBracket => Code::BracketLeft,
dioxus_html::KeyCode::BackSlash => Code::Backslash,
dioxus_html::KeyCode::CloseBraket => Code::BracketRight,
dioxus_html::KeyCode::SingleQuote => Code::Quote,
key => panic!("Failed to convert {:?} to tao::keyboard::KeyCode, try using tao::keyboard::KeyCode directly", key),
}
}
+102 -9
View File
@@ -1,24 +1,32 @@
use crate::desktop_context::EventData;
use crate::protocol;
use crate::desktop_context::{EditQueue, EventData};
use crate::protocol::{self, AssetHandlerRegistry};
use crate::{desktop_context::UserWindowEvent, Config};
use tao::event_loop::{EventLoopProxy, EventLoopWindowTarget};
pub use wry;
pub use wry::application as tao;
use wry::application::menu::{MenuBar, MenuItem};
use wry::application::window::Window;
use wry::http::Response;
use wry::webview::{WebContext, WebView, WebViewBuilder};
pub fn build(
pub(crate) fn build(
cfg: &mut Config,
event_loop: &EventLoopWindowTarget<UserWindowEvent>,
proxy: EventLoopProxy<UserWindowEvent>,
) -> (WebView, WebContext) {
) -> (WebView, WebContext, AssetHandlerRegistry, EditQueue) {
let builder = cfg.window.clone();
let window = builder.build(event_loop).unwrap();
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();
let root_name = cfg.root_name.clone();
if cfg.enable_default_menu_bar {
builder = builder.with_menu(build_default_menu_bar());
}
let window = builder.with_visible(false).build(event_loop).unwrap();
// We assume that if the icon is None in cfg, then the user just didnt set it
if cfg.window.window.window_icon.is_none() {
window.set_window_icon(Some(
@@ -32,6 +40,10 @@ pub fn build(
}
let mut web_context = WebContext::new(cfg.data_dir.clone());
let edit_queue = EditQueue::default();
let headless = !cfg.window.window.visible;
let asset_handlers = AssetHandlerRegistry::new();
let asset_handlers_ref = asset_handlers.clone();
let mut webview = WebViewBuilder::new(window)
.unwrap()
@@ -44,8 +56,22 @@ pub fn build(
_ = proxy.send_event(UserWindowEvent(EventData::Ipc(message), window.id()));
}
})
.with_custom_protocol(String::from("dioxus"), move |r| {
protocol::desktop_handler(r, custom_head.clone(), index_file.clone(), &root_name)
.with_asynchronous_custom_protocol(String::from("dioxus"), move |request, responder| {
let custom_head = custom_head.clone();
let index_file = index_file.clone();
let root_name = root_name.clone();
let asset_handlers_ref = asset_handlers_ref.clone();
tokio::spawn(async move {
let response_res = protocol::desktop_handler(
request,
custom_head.clone(),
index_file.clone(),
&root_name,
&asset_handlers_ref,
)
.await;
responder.respond(response);
});
})
.with_file_drop_handler(move |window, evet| {
file_handler
@@ -71,7 +97,16 @@ pub fn build(
// .with_web_context(&mut web_context);
for (name, handler) in cfg.protocols.drain(..) {
webview = webview.with_custom_protocol(name, handler)
webview = webview.with_custom_protocol(name, move |r| match handler(&r) {
Ok(response) => response,
Err(err) => {
tracing::error!("Error: {}", err);
Response::builder()
.status(500)
.body(err.to_string().into_bytes().into())
.unwrap()
}
})
}
if cfg.disable_context_menu {
@@ -94,5 +129,63 @@ pub fn build(
webview = webview.with_devtools(true);
}
(webview.build().unwrap(), web_context)
(webview.build().unwrap(), web_context, asset_handlers, edit_queue)
}
/// 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();
// 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);
}
window_menu.add_native_item(MenuItem::Hide);
#[cfg(target_os = "macos")]
{
window_menu.add_native_item(MenuItem::HideOthers);
window_menu.add_native_item(MenuItem::ShowAll);
}
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
}