Add `OutputCommand` for copying text and opening URL:s (#5532)

Add `OutputCommand` for copying text and opening URL:s

* Part of https://github.com/emilk/egui/issues/5424
* Adds `egui::OutputComm
* Part of https://github.com/emilk/egui/issues/5424and`
* Adds `PlatformOutput::commands`
* Deprecates `PlatformOutput::open_url`
* Deprecates `PlatformOutput::copied_text`
This commit is contained in:
Emil Ernerfeldt 2024-12-29 11:59:51 +01:00 committed by GitHub
parent 1e0f3a5e2d
commit e2c7e9e733
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 71 additions and 15 deletions

View File

@ -292,12 +292,15 @@ impl AppRunner {
} }
fn handle_platform_output(&self, platform_output: egui::PlatformOutput) { fn handle_platform_output(&self, platform_output: egui::PlatformOutput) {
#![allow(deprecated)]
#[cfg(feature = "web_screen_reader")] #[cfg(feature = "web_screen_reader")]
if self.egui_ctx.options(|o| o.screen_reader) { if self.egui_ctx.options(|o| o.screen_reader) {
super::screen_reader::speak(&platform_output.events_description()); super::screen_reader::speak(&platform_output.events_description());
} }
let egui::PlatformOutput { let egui::PlatformOutput {
commands,
cursor_icon, cursor_icon,
open_url, open_url,
copied_text, copied_text,
@ -310,7 +313,19 @@ impl AppRunner {
request_discard_reasons: _, // handled by `Context::run` request_discard_reasons: _, // handled by `Context::run`
} = platform_output; } = platform_output;
for command in commands {
match command {
egui::OutputCommand::CopyText(text) => {
super::set_clipboard_text(&text);
}
egui::OutputCommand::OpenUrl(open_url) => {
super::open_url(&open_url.url, open_url.new_tab);
}
}
}
super::set_cursor_icon(cursor_icon); super::set_cursor_icon(cursor_icon);
if let Some(open) = open_url { if let Some(open) = open_url {
super::open_url(&open.url, open.new_tab); super::open_url(&open.url, open.new_tab);
} }

View File

@ -820,9 +820,11 @@ impl State {
window: &Window, window: &Window,
platform_output: egui::PlatformOutput, platform_output: egui::PlatformOutput,
) { ) {
#![allow(deprecated)]
profiling::function_scope!(); profiling::function_scope!();
let egui::PlatformOutput { let egui::PlatformOutput {
commands,
cursor_icon, cursor_icon,
open_url, open_url,
copied_text, copied_text,
@ -835,6 +837,17 @@ impl State {
request_discard_reasons: _, // `egui::Context::run` handles this request_discard_reasons: _, // `egui::Context::run` handles this
} = platform_output; } = platform_output;
for command in commands {
match command {
egui::OutputCommand::CopyText(text) => {
self.clipboard.set(text);
}
egui::OutputCommand::OpenUrl(open_url) => {
open_url_in_browser(&open_url.url);
}
}
}
self.set_cursor_icon(window, cursor_icon); self.set_cursor_icon(window, cursor_icon);
if let Some(open_url) = open_url { if let Some(open_url) = open_url {

View File

@ -1419,6 +1419,12 @@ impl Context {
self.output_mut(|o| o.cursor_icon = cursor_icon); self.output_mut(|o| o.cursor_icon = cursor_icon);
} }
/// Add a command to [`PlatformOutput::commands`],
/// for the integration to execute at the end of the frame.
pub fn send_cmd(&self, cmd: crate::OutputCommand) {
self.output_mut(|o| o.commands.push(cmd));
}
/// Open an URL in a browser. /// Open an URL in a browser.
/// ///
/// Equivalent to: /// Equivalent to:
@ -1428,24 +1434,16 @@ impl Context {
/// ctx.output_mut(|o| o.open_url = Some(open_url)); /// ctx.output_mut(|o| o.open_url = Some(open_url));
/// ``` /// ```
pub fn open_url(&self, open_url: crate::OpenUrl) { pub fn open_url(&self, open_url: crate::OpenUrl) {
self.output_mut(|o| o.open_url = Some(open_url)); self.send_cmd(crate::OutputCommand::OpenUrl(open_url));
} }
/// Copy the given text to the system clipboard. /// Copy the given text to the system clipboard.
/// ///
/// Empty strings are ignored.
///
/// Note that in wasm applications, the clipboard is only accessible in secure contexts (e.g., /// Note that in wasm applications, the clipboard is only accessible in secure contexts (e.g.,
/// HTTPS or localhost). If this method is used outside of a secure context, it will log an /// HTTPS or localhost). If this method is used outside of a secure context, it will log an
/// error and do nothing. See <https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts>. /// error and do nothing. See <https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts>.
///
/// Equivalent to:
/// ```
/// # let ctx = egui::Context::default();
/// ctx.output_mut(|o| o.copied_text = "Copy this".to_owned());
/// ```
pub fn copy_text(&self, text: String) { pub fn copy_text(&self, text: String) {
self.output_mut(|o| o.copied_text = text); self.send_cmd(crate::OutputCommand::CopyText(text));
} }
/// Format the given shortcut in a human-readable way (e.g. `Ctrl+Shift+X`). /// Format the given shortcut in a human-readable way (e.g. `Ctrl+Shift+X`).

View File

@ -79,6 +79,21 @@ pub struct IMEOutput {
pub cursor_rect: crate::Rect, pub cursor_rect: crate::Rect,
} }
/// Commands that the egui integration should execute at the end of a frame.
///
/// Commands that are specific to a viewport should be put in [`crate::ViewportCommand`] instead.
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum OutputCommand {
/// Put this text in the system clipboard.
///
/// This is often a response to [`crate::Event::Copy`] or [`crate::Event::Cut`].
CopyText(String),
/// Open this url in a browser.
OpenUrl(OpenUrl),
}
/// The non-rendering part of what egui emits each frame. /// The non-rendering part of what egui emits each frame.
/// ///
/// You can access (and modify) this with [`crate::Context::output`]. /// You can access (and modify) this with [`crate::Context::output`].
@ -87,10 +102,14 @@ pub struct IMEOutput {
#[derive(Default, Clone, PartialEq)] #[derive(Default, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct PlatformOutput { pub struct PlatformOutput {
/// Commands that the egui integration should execute at the end of a frame.
pub commands: Vec<OutputCommand>,
/// Set the cursor to this icon. /// Set the cursor to this icon.
pub cursor_icon: CursorIcon, pub cursor_icon: CursorIcon,
/// If set, open this url. /// If set, open this url.
#[deprecated = "Use `Context::open_url` instead"]
pub open_url: Option<OpenUrl>, pub open_url: Option<OpenUrl>,
/// If set, put this text in the system clipboard. Ignore if empty. /// If set, put this text in the system clipboard. Ignore if empty.
@ -104,6 +123,7 @@ pub struct PlatformOutput {
/// } /// }
/// # }); /// # });
/// ``` /// ```
#[deprecated = "Use `Context::copy_text` instead"]
pub copied_text: String, pub copied_text: String,
/// Events that may be useful to e.g. a screen reader. /// Events that may be useful to e.g. a screen reader.
@ -162,7 +182,10 @@ impl PlatformOutput {
/// Add on new output. /// Add on new output.
pub fn append(&mut self, newer: Self) { pub fn append(&mut self, newer: Self) {
#![allow(deprecated)]
let Self { let Self {
mut commands,
cursor_icon, cursor_icon,
open_url, open_url,
copied_text, copied_text,
@ -175,6 +198,7 @@ impl PlatformOutput {
mut request_discard_reasons, mut request_discard_reasons,
} = newer; } = newer;
self.commands.append(&mut commands);
self.cursor_icon = cursor_icon; self.cursor_icon = cursor_icon;
if open_url.is_some() { if open_url.is_some() {
self.open_url = open_url; self.open_url = open_url;
@ -213,7 +237,7 @@ impl PlatformOutput {
/// What URL to open, and how. /// What URL to open, and how.
/// ///
/// Use with [`crate::Context::open_url`]. /// Use with [`crate::Context::open_url`].
#[derive(Clone, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct OpenUrl { pub struct OpenUrl {
pub url: String, pub url: String,

View File

@ -482,7 +482,8 @@ pub use self::{
data::{ data::{
input::*, input::*,
output::{ output::{
self, CursorIcon, FullOutput, OpenUrl, PlatformOutput, UserAttentionType, WidgetInfo, self, CursorIcon, FullOutput, OpenUrl, OutputCommand, PlatformOutput,
UserAttentionType, WidgetInfo,
}, },
Key, UserData, Key, UserData,
}, },

View File

@ -936,13 +936,16 @@ pub enum ResizeDirection {
/// An output [viewport](crate::viewport)-command from egui to the backend, e.g. to change the window title or size. /// An output [viewport](crate::viewport)-command from egui to the backend, e.g. to change the window title or size.
/// ///
/// You can send a [`ViewportCommand`] to the viewport with [`Context::send_viewport_cmd`]. /// You can send a [`ViewportCommand`] to the viewport with [`Context::send_viewport_cmd`].
/// ///
/// See [`crate::viewport`] for how to build new viewports (native windows). /// See [`crate::viewport`] for how to build new viewports (native windows).
/// ///
/// All coordinates are in logical points. /// All coordinates are in logical points.
/// ///
/// This is essentially a way to diff [`ViewportBuilder`]. /// [`ViewportCommand`] is essentially a way to diff [`ViewportBuilder`]s.
///
/// Only commands specific to a viewport are part of [`ViewportCommand`].
/// Other commands should be put in [`crate::OutputCommand`].
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum ViewportCommand { pub enum ViewportCommand {

View File

@ -9,4 +9,6 @@ set -x
rustup target add wasm32-unknown-unknown rustup target add wasm32-unknown-unknown
# For generating JS bindings: # For generating JS bindings:
cargo install --force --quiet wasm-bindgen-cli --version 0.2.95 if ! cargo install --list | grep -q 'wasm-bindgen-cli v0.2.95'; then
cargo install --force --quiet wasm-bindgen-cli --version 0.2.95
fi