-
Notifications
You must be signed in to change notification settings - Fork 256
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
web clipboard handling #178
Changes from 13 commits
493a21f
205cdde
9e85a90
69c447d
0ec3ed9
9032a4e
ea933e7
b45bea2
070f108
0e7dd24
e3fa6e3
43af68a
9d743fd
8768a9c
5c4f550
fe877d7
f97ae8d
d6b726b
feb884c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
[alias] | ||
run-wasm = ["run", "--release", "--package", "run-wasm", "--"] | ||
|
||
# Using unstable APIs is required for writing to clipboard | ||
[target.wasm32-unknown-unknown] | ||
rustflags = ["--cfg=web_sys_unstable_apis"] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
[package] | ||
name = "run-wasm" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[dependencies] | ||
cargo-run-wasm = "0.2.0" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
fn main() { | ||
cargo_run_wasm::run_wasm_with_css("body { margin: 0px; }"); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -58,7 +58,13 @@ pub mod systems; | |
/// Egui render node. | ||
pub mod egui_node; | ||
|
||
/// Clipboard management for web | ||
#[cfg(all(feature = "manage_clipboard", target_arch = "wasm32"))] | ||
pub mod web_clipboard; | ||
|
||
pub use egui; | ||
#[cfg(all(feature = "manage_clipboard", target_arch = "wasm32"))] | ||
use web_clipboard::{WebEventCopy, WebEventCut, WebEventPaste}; | ||
|
||
use crate::{ | ||
egui_node::{EguiPipeline, EGUI_SHADER_HANDLE}, | ||
|
@@ -138,6 +144,10 @@ impl Default for EguiSettings { | |
#[derive(Component, Clone, Debug, Default, Deref, DerefMut)] | ||
pub struct EguiInput(pub egui::RawInput); | ||
|
||
/// A resource to check if we're on a mac, correctly detects on web too. | ||
#[derive(Resource)] | ||
pub struct IsMac(pub bool); | ||
Vrixyz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/// A resource for accessing clipboard. | ||
/// | ||
/// The resource is available only if `manage_clipboard` feature is enabled. | ||
|
@@ -146,8 +156,15 @@ pub struct EguiInput(pub egui::RawInput); | |
pub struct EguiClipboard { | ||
#[cfg(not(target_arch = "wasm32"))] | ||
clipboard: ThreadLocal<Option<RefCell<Clipboard>>>, | ||
/// for copy events. | ||
#[cfg(target_arch = "wasm32")] | ||
pub web_copy: web_clipboard::WebChannel<WebEventCopy>, | ||
/// for copy events. | ||
#[cfg(target_arch = "wasm32")] | ||
pub web_cut: web_clipboard::WebChannel<WebEventCut>, | ||
/// for paste events, only supporting strings. | ||
#[cfg(target_arch = "wasm32")] | ||
clipboard: String, | ||
pub web_paste: web_clipboard::WebChannel<WebEventPaste>, | ||
} | ||
|
||
#[cfg(feature = "manage_clipboard")] | ||
|
@@ -159,12 +176,20 @@ impl EguiClipboard { | |
|
||
/// Gets clipboard contents. Returns [`None`] if clipboard provider is unavailable or returns an error. | ||
#[must_use] | ||
pub fn get_contents(&self) -> Option<String> { | ||
#[cfg(not(target_arch = "wasm32"))] | ||
pub fn get_contents(&mut self) -> Option<String> { | ||
self.get_contents_impl() | ||
} | ||
|
||
/// Gets clipboard contents. Returns [`None`] if clipboard provider is unavailable or returns an error. | ||
#[must_use] | ||
#[cfg(target_arch = "wasm32")] | ||
pub fn get_contents(&mut self) -> Option<String> { | ||
self.get_contents_impl() | ||
} | ||
|
||
#[cfg(not(target_arch = "wasm32"))] | ||
fn set_contents_impl(&self, contents: &str) { | ||
fn set_contents_impl(&mut self, contents: &str) { | ||
Vrixyz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if let Some(mut clipboard) = self.get() { | ||
if let Err(err) = clipboard.set_text(contents.to_owned()) { | ||
log::error!("Failed to set clipboard contents: {:?}", err); | ||
|
@@ -174,11 +199,11 @@ impl EguiClipboard { | |
|
||
#[cfg(target_arch = "wasm32")] | ||
fn set_contents_impl(&mut self, contents: &str) { | ||
self.clipboard = contents.to_owned(); | ||
web_clipboard::clipboard_copy(contents.to_owned()); | ||
} | ||
|
||
#[cfg(not(target_arch = "wasm32"))] | ||
fn get_contents_impl(&self) -> Option<String> { | ||
fn get_contents_impl(&mut self) -> Option<String> { | ||
if let Some(mut clipboard) = self.get() { | ||
match clipboard.get_text() { | ||
Ok(contents) => return Some(contents), | ||
|
@@ -190,8 +215,8 @@ impl EguiClipboard { | |
|
||
#[cfg(target_arch = "wasm32")] | ||
#[allow(clippy::unnecessary_wraps)] | ||
fn get_contents_impl(&self) -> Option<String> { | ||
Some(self.clipboard.clone()) | ||
fn get_contents_impl(&mut self) -> Option<String> { | ||
self.web_paste.try_read_clipboard_event().map(|e| e.0) | ||
} | ||
|
||
#[cfg(not(target_arch = "wasm32"))] | ||
|
@@ -532,7 +557,24 @@ impl Plugin for EguiPlugin { | |
world.init_resource::<EguiClipboard>(); | ||
world.init_resource::<EguiUserTextures>(); | ||
world.init_resource::<EguiMousePosition>(); | ||
if !world.contains_resource::<IsMac>() { | ||
let mut is_mac = cfg!(target_os = "macos"); | ||
#[cfg(target_arch = "wasm32")] | ||
{ | ||
let window = web_sys::window().expect("window"); | ||
|
||
let nav = window.navigator(); | ||
let platform = nav.platform(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm.. It looks like this property is deprecated: https://developer.mozilla.org/en-US/docs/Web/API/Navigator/platform There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's just read from the user agent string for now (by testing whether it contains There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I went with |
||
if let Ok(platform) = platform { | ||
is_mac = platform.starts_with("Mac"); | ||
} | ||
} | ||
log::info!(is_mac); | ||
world.insert_resource(IsMac(is_mac)); | ||
} | ||
|
||
#[cfg(all(feature = "manage_clipboard", target_arch = "wasm32"))] | ||
app.add_startup_system(web_clipboard::startup_setup_web_events); | ||
app.add_startup_systems( | ||
( | ||
setup_new_windows_system, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
use crate::{ | ||
EguiContext, EguiContextQuery, EguiInput, EguiMousePosition, EguiSettings, WindowSize, | ||
EguiContext, EguiContextQuery, EguiInput, EguiMousePosition, EguiSettings, IsMac, WindowSize, | ||
}; | ||
#[cfg(feature = "open_url")] | ||
use bevy::log; | ||
|
@@ -54,7 +54,7 @@ impl<'w, 's> InputEvents<'w, 's> { | |
#[derive(SystemParam)] | ||
pub struct InputResources<'w, 's> { | ||
#[cfg(feature = "manage_clipboard")] | ||
pub egui_clipboard: Res<'w, crate::EguiClipboard>, | ||
pub egui_clipboard: ResMut<'w, crate::EguiClipboard>, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. see #178 (comment) ; to be noted we can have mut only for wasm if we want. |
||
pub keyboard_input: Res<'w, Input<KeyCode>>, | ||
#[system_param(ignore)] | ||
_marker: PhantomData<&'s ()>, | ||
|
@@ -71,8 +71,9 @@ pub struct ContextSystemParams<'w, 's> { | |
|
||
/// Processes Bevy input and feeds it to Egui. | ||
pub fn process_input_system( | ||
mut is_mac: Res<IsMac>, | ||
mut input_events: InputEvents, | ||
input_resources: InputResources, | ||
mut input_resources: InputResources, | ||
mut context_params: ContextSystemParams, | ||
egui_settings: Res<EguiSettings>, | ||
mut egui_mouse_position: ResMut<EguiMousePosition>, | ||
|
@@ -101,12 +102,8 @@ pub fn process_input_system( | |
let win = input_resources.keyboard_input.pressed(KeyCode::LWin) | ||
|| input_resources.keyboard_input.pressed(KeyCode::RWin); | ||
|
||
let mac_cmd = if cfg!(target_os = "macos") { | ||
win | ||
} else { | ||
false | ||
}; | ||
let command = if cfg!(target_os = "macos") { win } else { ctrl }; | ||
let mac_cmd = if is_mac.0 { win } else { false }; | ||
let command = if is_mac.0 { win } else { ctrl }; | ||
|
||
let modifiers = egui::Modifiers { | ||
alt, | ||
|
@@ -250,20 +247,47 @@ pub fn process_input_system( | |
// We also check that it's an `ButtonState::Pressed` event, as we don't want to | ||
// copy, cut or paste on the key release. | ||
#[cfg(feature = "manage_clipboard")] | ||
if command && pressed { | ||
match key { | ||
egui::Key::C => { | ||
{ | ||
#[cfg(not(target_arch = "wasm32"))] | ||
if command && pressed { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (to be clear, this is not a but introduced in this PR) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, good catch. This makes me wonder how we can detect running on MacOS in wasm. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. eframe uses events for all those, deferring the responsibility to the browser: https://github.com/emilk/egui/blob/307565efa55158cfa6b82d2e8fdc4c4914b954ed/crates/eframe/src/web/events.rs#L184 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It feels like we should adopt the same approach. Let's keep the whole block enabled only for non-wasm targets, whereas for wasm we'll just read from channels (which in turn will get messages on web_sys events). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is important to fix, as atm on MacOS, users need to do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I feel like it means we would bypass winit/bevy key inputs in wasm :/ could this possibly fixed upstream 🤔 ? for the record, ctrl-A works (kinda) on wasm; on azerty it's mapped to In all hindsight, I'd suggest a simpler approach where we detect if we run on mac or not through https://docs.rs/web-sys/latest/web_sys/struct.Navigator.html#method.platform, and adapt our meta key from there in case of Is it ok for you? |
||
match key { | ||
egui::Key::C => { | ||
focused_input.events.push(egui::Event::Copy); | ||
} | ||
egui::Key::X => { | ||
focused_input.events.push(egui::Event::Cut); | ||
} | ||
egui::Key::V => { | ||
if let Some(contents) = | ||
input_resources.egui_clipboard.get_contents() | ||
{ | ||
focused_input.events.push(egui::Event::Text(contents)) | ||
} | ||
} | ||
_ => {} | ||
} | ||
} | ||
#[cfg(target_arch = "wasm32")] | ||
{ | ||
if input_resources | ||
.egui_clipboard | ||
.web_copy | ||
.try_read_clipboard_event() | ||
.is_some() | ||
{ | ||
focused_input.events.push(egui::Event::Copy); | ||
} | ||
egui::Key::X => { | ||
if input_resources | ||
.egui_clipboard | ||
.web_cut | ||
.try_read_clipboard_event() | ||
.is_some() | ||
{ | ||
focused_input.events.push(egui::Event::Cut); | ||
} | ||
egui::Key::V => { | ||
if let Some(contents) = input_resources.egui_clipboard.get_contents() { | ||
focused_input.events.push(egui::Event::Text(contents)) | ||
} | ||
if let Some(contents) = input_resources.egui_clipboard.get_contents() { | ||
focused_input.events.push(egui::Event::Text(contents)); | ||
} | ||
_ => {} | ||
} | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think cargo run-wasm is quite handy to easily test a wasm version ; I can make another PR if interested