From f16a1b9fe94c290c31234d1d3116f40dbcd00db5 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Tue, 6 May 2025 23:58:49 +0100 Subject: [PATCH 01/35] Added new very basic clipboard crate. Enabled with the `bevy_clipboard` feature. Supports setting and getting text to and from the clipboard. Only supports desktop targets. --- Cargo.toml | 4 + crates/bevy_clipboard/Cargo.toml | 22 +++ crates/bevy_clipboard/LICENSE-APACHE | 176 ++++++++++++++++++++ crates/bevy_clipboard/LICENSE-MIT | 19 +++ crates/bevy_clipboard/README.md | 7 + crates/bevy_clipboard/src/desktop.rs | 34 ++++ crates/bevy_clipboard/src/dummy.rs | 30 ++++ crates/bevy_clipboard/src/lib.rs | 23 +++ crates/bevy_internal/Cargo.toml | 4 + crates/bevy_internal/src/default_plugins.rs | 2 + crates/bevy_internal/src/lib.rs | 2 + examples/ui/text.rs | 11 ++ 12 files changed, 334 insertions(+) create mode 100644 crates/bevy_clipboard/Cargo.toml create mode 100644 crates/bevy_clipboard/LICENSE-APACHE create mode 100644 crates/bevy_clipboard/LICENSE-MIT create mode 100644 crates/bevy_clipboard/README.md create mode 100644 crates/bevy_clipboard/src/desktop.rs create mode 100644 crates/bevy_clipboard/src/dummy.rs create mode 100644 crates/bevy_clipboard/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index a3d3a2ab63..9ba5aae4f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -131,6 +131,7 @@ default = [ "animation", "bevy_asset", "bevy_audio", + "bevy_clipboard", "bevy_color", "bevy_core_pipeline", "bevy_anti_aliasing", @@ -291,6 +292,9 @@ bevy_log = ["bevy_internal/bevy_log"] # Enable input focus subsystem bevy_input_focus = ["bevy_internal/bevy_input_focus"] +# Clipboard access +bevy_clipboard = ["bevy_internal/bevy_clipboard"] + # Use the configurable global error handler as the default error handler. configurable_error_handler = ["bevy_internal/configurable_error_handler"] diff --git a/crates/bevy_clipboard/Cargo.toml b/crates/bevy_clipboard/Cargo.toml new file mode 100644 index 0000000000..968612a3bf --- /dev/null +++ b/crates/bevy_clipboard/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "bevy_clipboard" +version = "0.16.0-dev" +edition = "2024" +description = "Provides accessibility support for Bevy Engine" +homepage = "https://bevyengine.org" +repository = "https://github.com/bevyengine/bevy" +license = "MIT OR Apache-2.0" +keywords = ["bevy", "clipboard"] + +[dependencies] +# bevy +bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false } +bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false } +arboard = "3.5.0" + +[lints] +workspace = true + +[package.metadata.docs.rs] +rustdoc-args = ["-Zunstable-options", "--generate-link-to-definition"] +all-features = true diff --git a/crates/bevy_clipboard/LICENSE-APACHE b/crates/bevy_clipboard/LICENSE-APACHE new file mode 100644 index 0000000000..d9a10c0d8e --- /dev/null +++ b/crates/bevy_clipboard/LICENSE-APACHE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/crates/bevy_clipboard/LICENSE-MIT b/crates/bevy_clipboard/LICENSE-MIT new file mode 100644 index 0000000000..9cf106272a --- /dev/null +++ b/crates/bevy_clipboard/LICENSE-MIT @@ -0,0 +1,19 @@ +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/crates/bevy_clipboard/README.md b/crates/bevy_clipboard/README.md new file mode 100644 index 0000000000..d96116fb9d --- /dev/null +++ b/crates/bevy_clipboard/README.md @@ -0,0 +1,7 @@ +# Bevy Clipboard + +[![License](https://img.shields.io/badge/license-MIT%2FApache-blue.svg)](https://github.com/bevyengine/bevy#license) +[![Crates.io](https://img.shields.io/crates/v/bevy_clipboard.svg)](https://crates.io/crates/bevy_clipboard) +[![Downloads](https://img.shields.io/crates/d/bevy_clipboard.svg)](https://crates.io/crates/bevy_clipboard) +[![Docs](https://docs.rs/bevy_clipboard/badge.svg)](https://docs.rs/bevy_clipboard/latest/bevy_clipboard/) +[![Discord](https://img.shields.io/discord/691052431525675048.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/bevy) diff --git a/crates/bevy_clipboard/src/desktop.rs b/crates/bevy_clipboard/src/desktop.rs new file mode 100644 index 0000000000..698f5d6dc8 --- /dev/null +++ b/crates/bevy_clipboard/src/desktop.rs @@ -0,0 +1,34 @@ +extern crate alloc; + +use bevy_ecs::resource::Resource; + +/// Resource providing access to the clipboard +#[derive(Resource, Default)] +pub struct Clipboard; + +impl Clipboard { + fn new() -> Result { + arboard::Clipboard::new() + } + + /// Fetches UTF-8 text from the clipboard and returns it. + /// + /// # Errors + /// + /// Returns error if clipboard is empty or contents are not UTF-8 text. + pub fn get_text(&mut self) -> Result { + Self::new().and_then(|mut clipboard| clipboard.get_text()) + } + + /// Places the text onto the clipboard. Any valid UTF-8 string is accepted. + /// + /// # Errors + /// + /// Returns error if `text` failed to be stored on the clipboard. + pub fn set_text<'a, T: Into>>( + &mut self, + text: T, + ) -> Result<(), arboard::Error> { + Self::new().and_then(|mut clipboard| clipboard.set_text(text)) + } +} diff --git a/crates/bevy_clipboard/src/dummy.rs b/crates/bevy_clipboard/src/dummy.rs new file mode 100644 index 0000000000..c8b9f3c2e1 --- /dev/null +++ b/crates/bevy_clipboard/src/dummy.rs @@ -0,0 +1,30 @@ +use bevy_ecs::resource::Resource; +use std::borrow::Cow; + +/// Resource providing access to the clipboard +#[derive(Resource, Default)] +pub struct Clipboard; + +impl Clipboard { + fn new() -> Result { + Err(arboard::Error::ClipboardNotSupported) + } + + /// Fetches UTF-8 text from the clipboard and returns it. + /// + /// # Errors + /// + /// Returns error if clipboard is empty or contents are not UTF-8 text. + pub fn get_text(&mut self) -> Result { + Err(arboard::Error::ClipboardNotSupported) + } + + /// Places the text onto the clipboard. Any valid UTF-8 string is accepted. + /// + /// # Errors + /// + /// Returns error if `text` failed to be stored on the clipboard. + pub fn set_text<'a, T: Into>>(&mut self, text: T) -> Result<(), arboard::Error> { + Err(arboard::Error::ClipboardNotSupported) + } +} diff --git a/crates/bevy_clipboard/src/lib.rs b/crates/bevy_clipboard/src/lib.rs new file mode 100644 index 0000000000..fb88b8c17d --- /dev/null +++ b/crates/bevy_clipboard/src/lib.rs @@ -0,0 +1,23 @@ +//! This crate provides a platform-agnostic interface for accessing the clipboard + +#[cfg(any(windows, all(unix)))] +mod desktop; + +#[cfg(not(any(windows, all(unix))))] +mod dummy; + +#[cfg(any(windows, all(unix)))] +pub use desktop::*; + +#[cfg(not(any(windows, all(unix))))] +pub use dummy::*; + +/// Clipboard plugin +#[derive(Default)] +pub struct ClipboardPlugin; + +impl bevy_app::Plugin for ClipboardPlugin { + fn build(&self, app: &mut bevy_app::App) { + app.init_resource::(); + } +} diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index 28d234f2b4..53db35078b 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -349,6 +349,9 @@ web = [ "bevy_tasks/web", ] +# Clipboard support +bevy_clipboard = ["dep:bevy_clipboard"] + [dependencies] # bevy (no_std) bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, features = [ @@ -425,6 +428,7 @@ bevy_window = { path = "../bevy_window", optional = true, version = "0.16.0-dev" "bevy_reflect", ] } bevy_winit = { path = "../bevy_winit", optional = true, version = "0.16.0-dev", default-features = false } +bevy_clipboard = { path = "../bevy_clipboard", optional = true, version = "0.16.0-dev" } [lints] workspace = true diff --git a/crates/bevy_internal/src/default_plugins.rs b/crates/bevy_internal/src/default_plugins.rs index db1152a362..fc7165e521 100644 --- a/crates/bevy_internal/src/default_plugins.rs +++ b/crates/bevy_internal/src/default_plugins.rs @@ -66,6 +66,8 @@ plugin_group! { bevy_dev_tools:::DevToolsPlugin, #[cfg(feature = "bevy_ci_testing")] bevy_dev_tools::ci_testing:::CiTestingPlugin, + #[cfg(feature = "bevy_clipboard")] + bevy_clipboard:::ClipboardPlugin, #[plugin_group] #[cfg(feature = "bevy_picking")] bevy_picking:::DefaultPickingPlugins, diff --git a/crates/bevy_internal/src/lib.rs b/crates/bevy_internal/src/lib.rs index 07dd936ab1..049544be54 100644 --- a/crates/bevy_internal/src/lib.rs +++ b/crates/bevy_internal/src/lib.rs @@ -25,6 +25,8 @@ pub use bevy_app as app; pub use bevy_asset as asset; #[cfg(feature = "bevy_audio")] pub use bevy_audio as audio; +#[cfg(feature = "bevy_clipboard")] +pub use bevy_clipboard as clipboard; #[cfg(feature = "bevy_color")] pub use bevy_color as color; #[cfg(feature = "bevy_core_pipeline")] diff --git a/examples/ui/text.rs b/examples/ui/text.rs index 8bf34cc96e..77592f102b 100644 --- a/examples/ui/text.rs +++ b/examples/ui/text.rs @@ -4,6 +4,7 @@ //! in the bottom right. For text within a scene, please see the text2d example. use bevy::{ + clipboard::*, color::palettes::css::GOLD, diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin}, prelude::*, @@ -14,9 +15,19 @@ fn main() { .add_plugins((DefaultPlugins, FrameTimeDiagnosticsPlugin::default())) .add_systems(Startup, setup) .add_systems(Update, (text_update_system, text_color_system)) + .add_systems(Startup, clipboard_test) .run(); } +fn clipboard_test(mut clipboard: ResMut) { + let text = clipboard.get_text().unwrap(); + info!("clipboard contents = {}", text); + info!("set clipboard text"); + clipboard.set_text("Hello bevy!").unwrap(); + let text = clipboard.get_text().unwrap(); + info!("clipboard contents = {}", text); +} + // Marker struct to help identify the FPS UI component, since there may be many Text components #[derive(Component)] struct FpsText; From 92b74cdba8990b332b4b7d4e82c08a63dcc91dae Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Wed, 7 May 2025 00:00:55 +0100 Subject: [PATCH 02/35] Added new very basic clipboard crate. Enabled with the `bevy_clipboard` feature. Supports setting and getting text to and from the clipboard. Only supports desktop targets. --- Cargo.toml | 4 + crates/bevy_clipboard/Cargo.toml | 22 +++ crates/bevy_clipboard/LICENSE-APACHE | 176 ++++++++++++++++++++ crates/bevy_clipboard/LICENSE-MIT | 19 +++ crates/bevy_clipboard/README.md | 7 + crates/bevy_clipboard/src/desktop.rs | 34 ++++ crates/bevy_clipboard/src/dummy.rs | 30 ++++ crates/bevy_clipboard/src/lib.rs | 23 +++ crates/bevy_internal/Cargo.toml | 4 + crates/bevy_internal/src/default_plugins.rs | 2 + crates/bevy_internal/src/lib.rs | 2 + 11 files changed, 323 insertions(+) create mode 100644 crates/bevy_clipboard/Cargo.toml create mode 100644 crates/bevy_clipboard/LICENSE-APACHE create mode 100644 crates/bevy_clipboard/LICENSE-MIT create mode 100644 crates/bevy_clipboard/README.md create mode 100644 crates/bevy_clipboard/src/desktop.rs create mode 100644 crates/bevy_clipboard/src/dummy.rs create mode 100644 crates/bevy_clipboard/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index a3d3a2ab63..9ba5aae4f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -131,6 +131,7 @@ default = [ "animation", "bevy_asset", "bevy_audio", + "bevy_clipboard", "bevy_color", "bevy_core_pipeline", "bevy_anti_aliasing", @@ -291,6 +292,9 @@ bevy_log = ["bevy_internal/bevy_log"] # Enable input focus subsystem bevy_input_focus = ["bevy_internal/bevy_input_focus"] +# Clipboard access +bevy_clipboard = ["bevy_internal/bevy_clipboard"] + # Use the configurable global error handler as the default error handler. configurable_error_handler = ["bevy_internal/configurable_error_handler"] diff --git a/crates/bevy_clipboard/Cargo.toml b/crates/bevy_clipboard/Cargo.toml new file mode 100644 index 0000000000..968612a3bf --- /dev/null +++ b/crates/bevy_clipboard/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "bevy_clipboard" +version = "0.16.0-dev" +edition = "2024" +description = "Provides accessibility support for Bevy Engine" +homepage = "https://bevyengine.org" +repository = "https://github.com/bevyengine/bevy" +license = "MIT OR Apache-2.0" +keywords = ["bevy", "clipboard"] + +[dependencies] +# bevy +bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false } +bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false } +arboard = "3.5.0" + +[lints] +workspace = true + +[package.metadata.docs.rs] +rustdoc-args = ["-Zunstable-options", "--generate-link-to-definition"] +all-features = true diff --git a/crates/bevy_clipboard/LICENSE-APACHE b/crates/bevy_clipboard/LICENSE-APACHE new file mode 100644 index 0000000000..d9a10c0d8e --- /dev/null +++ b/crates/bevy_clipboard/LICENSE-APACHE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/crates/bevy_clipboard/LICENSE-MIT b/crates/bevy_clipboard/LICENSE-MIT new file mode 100644 index 0000000000..9cf106272a --- /dev/null +++ b/crates/bevy_clipboard/LICENSE-MIT @@ -0,0 +1,19 @@ +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/crates/bevy_clipboard/README.md b/crates/bevy_clipboard/README.md new file mode 100644 index 0000000000..d96116fb9d --- /dev/null +++ b/crates/bevy_clipboard/README.md @@ -0,0 +1,7 @@ +# Bevy Clipboard + +[![License](https://img.shields.io/badge/license-MIT%2FApache-blue.svg)](https://github.com/bevyengine/bevy#license) +[![Crates.io](https://img.shields.io/crates/v/bevy_clipboard.svg)](https://crates.io/crates/bevy_clipboard) +[![Downloads](https://img.shields.io/crates/d/bevy_clipboard.svg)](https://crates.io/crates/bevy_clipboard) +[![Docs](https://docs.rs/bevy_clipboard/badge.svg)](https://docs.rs/bevy_clipboard/latest/bevy_clipboard/) +[![Discord](https://img.shields.io/discord/691052431525675048.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/bevy) diff --git a/crates/bevy_clipboard/src/desktop.rs b/crates/bevy_clipboard/src/desktop.rs new file mode 100644 index 0000000000..698f5d6dc8 --- /dev/null +++ b/crates/bevy_clipboard/src/desktop.rs @@ -0,0 +1,34 @@ +extern crate alloc; + +use bevy_ecs::resource::Resource; + +/// Resource providing access to the clipboard +#[derive(Resource, Default)] +pub struct Clipboard; + +impl Clipboard { + fn new() -> Result { + arboard::Clipboard::new() + } + + /// Fetches UTF-8 text from the clipboard and returns it. + /// + /// # Errors + /// + /// Returns error if clipboard is empty or contents are not UTF-8 text. + pub fn get_text(&mut self) -> Result { + Self::new().and_then(|mut clipboard| clipboard.get_text()) + } + + /// Places the text onto the clipboard. Any valid UTF-8 string is accepted. + /// + /// # Errors + /// + /// Returns error if `text` failed to be stored on the clipboard. + pub fn set_text<'a, T: Into>>( + &mut self, + text: T, + ) -> Result<(), arboard::Error> { + Self::new().and_then(|mut clipboard| clipboard.set_text(text)) + } +} diff --git a/crates/bevy_clipboard/src/dummy.rs b/crates/bevy_clipboard/src/dummy.rs new file mode 100644 index 0000000000..c8b9f3c2e1 --- /dev/null +++ b/crates/bevy_clipboard/src/dummy.rs @@ -0,0 +1,30 @@ +use bevy_ecs::resource::Resource; +use std::borrow::Cow; + +/// Resource providing access to the clipboard +#[derive(Resource, Default)] +pub struct Clipboard; + +impl Clipboard { + fn new() -> Result { + Err(arboard::Error::ClipboardNotSupported) + } + + /// Fetches UTF-8 text from the clipboard and returns it. + /// + /// # Errors + /// + /// Returns error if clipboard is empty or contents are not UTF-8 text. + pub fn get_text(&mut self) -> Result { + Err(arboard::Error::ClipboardNotSupported) + } + + /// Places the text onto the clipboard. Any valid UTF-8 string is accepted. + /// + /// # Errors + /// + /// Returns error if `text` failed to be stored on the clipboard. + pub fn set_text<'a, T: Into>>(&mut self, text: T) -> Result<(), arboard::Error> { + Err(arboard::Error::ClipboardNotSupported) + } +} diff --git a/crates/bevy_clipboard/src/lib.rs b/crates/bevy_clipboard/src/lib.rs new file mode 100644 index 0000000000..fb88b8c17d --- /dev/null +++ b/crates/bevy_clipboard/src/lib.rs @@ -0,0 +1,23 @@ +//! This crate provides a platform-agnostic interface for accessing the clipboard + +#[cfg(any(windows, all(unix)))] +mod desktop; + +#[cfg(not(any(windows, all(unix))))] +mod dummy; + +#[cfg(any(windows, all(unix)))] +pub use desktop::*; + +#[cfg(not(any(windows, all(unix))))] +pub use dummy::*; + +/// Clipboard plugin +#[derive(Default)] +pub struct ClipboardPlugin; + +impl bevy_app::Plugin for ClipboardPlugin { + fn build(&self, app: &mut bevy_app::App) { + app.init_resource::(); + } +} diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index 28d234f2b4..53db35078b 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -349,6 +349,9 @@ web = [ "bevy_tasks/web", ] +# Clipboard support +bevy_clipboard = ["dep:bevy_clipboard"] + [dependencies] # bevy (no_std) bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, features = [ @@ -425,6 +428,7 @@ bevy_window = { path = "../bevy_window", optional = true, version = "0.16.0-dev" "bevy_reflect", ] } bevy_winit = { path = "../bevy_winit", optional = true, version = "0.16.0-dev", default-features = false } +bevy_clipboard = { path = "../bevy_clipboard", optional = true, version = "0.16.0-dev" } [lints] workspace = true diff --git a/crates/bevy_internal/src/default_plugins.rs b/crates/bevy_internal/src/default_plugins.rs index db1152a362..fc7165e521 100644 --- a/crates/bevy_internal/src/default_plugins.rs +++ b/crates/bevy_internal/src/default_plugins.rs @@ -66,6 +66,8 @@ plugin_group! { bevy_dev_tools:::DevToolsPlugin, #[cfg(feature = "bevy_ci_testing")] bevy_dev_tools::ci_testing:::CiTestingPlugin, + #[cfg(feature = "bevy_clipboard")] + bevy_clipboard:::ClipboardPlugin, #[plugin_group] #[cfg(feature = "bevy_picking")] bevy_picking:::DefaultPickingPlugins, diff --git a/crates/bevy_internal/src/lib.rs b/crates/bevy_internal/src/lib.rs index 07dd936ab1..049544be54 100644 --- a/crates/bevy_internal/src/lib.rs +++ b/crates/bevy_internal/src/lib.rs @@ -25,6 +25,8 @@ pub use bevy_app as app; pub use bevy_asset as asset; #[cfg(feature = "bevy_audio")] pub use bevy_audio as audio; +#[cfg(feature = "bevy_clipboard")] +pub use bevy_clipboard as clipboard; #[cfg(feature = "bevy_color")] pub use bevy_color as color; #[cfg(feature = "bevy_core_pipeline")] From 5e78819ad1313add2579041b5838ef3bbc2c7992 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Wed, 7 May 2025 00:02:14 +0100 Subject: [PATCH 03/35] Revert changes to example --- examples/ui/text.rs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/examples/ui/text.rs b/examples/ui/text.rs index 77592f102b..8bf34cc96e 100644 --- a/examples/ui/text.rs +++ b/examples/ui/text.rs @@ -4,7 +4,6 @@ //! in the bottom right. For text within a scene, please see the text2d example. use bevy::{ - clipboard::*, color::palettes::css::GOLD, diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin}, prelude::*, @@ -15,19 +14,9 @@ fn main() { .add_plugins((DefaultPlugins, FrameTimeDiagnosticsPlugin::default())) .add_systems(Startup, setup) .add_systems(Update, (text_update_system, text_color_system)) - .add_systems(Startup, clipboard_test) .run(); } -fn clipboard_test(mut clipboard: ResMut) { - let text = clipboard.get_text().unwrap(); - info!("clipboard contents = {}", text); - info!("set clipboard text"); - clipboard.set_text("Hello bevy!").unwrap(); - let text = clipboard.get_text().unwrap(); - info!("clipboard contents = {}", text); -} - // Marker struct to help identify the FPS UI component, since there may be many Text components #[derive(Component)] struct FpsText; From a9c79384f55763dd86fced4c2beb1f557311fd62 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Wed, 7 May 2025 00:49:04 +0100 Subject: [PATCH 04/35] ci fixes --- crates/bevy_clipboard/src/lib.rs | 8 ++++---- docs/cargo_features.md | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/bevy_clipboard/src/lib.rs b/crates/bevy_clipboard/src/lib.rs index fb88b8c17d..d3ea590f3d 100644 --- a/crates/bevy_clipboard/src/lib.rs +++ b/crates/bevy_clipboard/src/lib.rs @@ -1,15 +1,15 @@ //! This crate provides a platform-agnostic interface for accessing the clipboard -#[cfg(any(windows, all(unix)))] +#[cfg(any(windows, unix))] mod desktop; -#[cfg(not(any(windows, all(unix))))] +#[cfg(not(any(windows, unix)))] mod dummy; -#[cfg(any(windows, all(unix)))] +#[cfg(any(windows, unix))] pub use desktop::*; -#[cfg(not(any(windows, all(unix))))] +#[cfg(not(any(windows, unix)))] pub use dummy::*; /// Clipboard plugin diff --git a/docs/cargo_features.md b/docs/cargo_features.md index f15fa1c4c6..251c302cbe 100644 --- a/docs/cargo_features.md +++ b/docs/cargo_features.md @@ -19,6 +19,7 @@ The default feature set enables most of the expected features of a game engine, |bevy_anti_aliasing|Provides various anti aliasing solutions| |bevy_asset|Provides asset functionality| |bevy_audio|Provides audio functionality| +|bevy_clipboard|Clipboard access| |bevy_color|Provides shared color types and operations| |bevy_core_pipeline|Provides cameras and other basic render pipeline features| |bevy_gilrs|Adds gamepad support| From 586ee404de68bde38d49b9cf9e03d284e9bd0b04 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Wed, 7 May 2025 00:56:36 +0100 Subject: [PATCH 05/35] removed `new` method --- crates/bevy_clipboard/src/desktop.rs | 8 ++------ crates/bevy_clipboard/src/dummy.rs | 4 ---- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/crates/bevy_clipboard/src/desktop.rs b/crates/bevy_clipboard/src/desktop.rs index 698f5d6dc8..80788a0f06 100644 --- a/crates/bevy_clipboard/src/desktop.rs +++ b/crates/bevy_clipboard/src/desktop.rs @@ -7,17 +7,13 @@ use bevy_ecs::resource::Resource; pub struct Clipboard; impl Clipboard { - fn new() -> Result { - arboard::Clipboard::new() - } - /// Fetches UTF-8 text from the clipboard and returns it. /// /// # Errors /// /// Returns error if clipboard is empty or contents are not UTF-8 text. pub fn get_text(&mut self) -> Result { - Self::new().and_then(|mut clipboard| clipboard.get_text()) + arboard::Clipboard::new().and_then(|mut clipboard| clipboard.get_text()) } /// Places the text onto the clipboard. Any valid UTF-8 string is accepted. @@ -29,6 +25,6 @@ impl Clipboard { &mut self, text: T, ) -> Result<(), arboard::Error> { - Self::new().and_then(|mut clipboard| clipboard.set_text(text)) + arboard::Clipboard::new().and_then(|mut clipboard| clipboard.set_text(text)) } } diff --git a/crates/bevy_clipboard/src/dummy.rs b/crates/bevy_clipboard/src/dummy.rs index c8b9f3c2e1..9f5616852e 100644 --- a/crates/bevy_clipboard/src/dummy.rs +++ b/crates/bevy_clipboard/src/dummy.rs @@ -6,10 +6,6 @@ use std::borrow::Cow; pub struct Clipboard; impl Clipboard { - fn new() -> Result { - Err(arboard::Error::ClipboardNotSupported) - } - /// Fetches UTF-8 text from the clipboard and returns it. /// /// # Errors From 0ed01c95e598d0770c0376fe6ba8d89bc6ab7759 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Wed, 7 May 2025 12:06:23 +0100 Subject: [PATCH 06/35] Update crates/bevy_clipboard/Cargo.toml Co-authored-by: Gilles Henaux --- crates/bevy_clipboard/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_clipboard/Cargo.toml b/crates/bevy_clipboard/Cargo.toml index 968612a3bf..9a40296304 100644 --- a/crates/bevy_clipboard/Cargo.toml +++ b/crates/bevy_clipboard/Cargo.toml @@ -12,7 +12,7 @@ keywords = ["bevy", "clipboard"] # bevy bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false } -arboard = "3.5.0" +arboard = { version = "3.5.0", default-features = false } [lints] workspace = true From 9617802bf35f9b8dfaa9775d831db6ad270c3397 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Wed, 7 May 2025 13:03:49 +0100 Subject: [PATCH 07/35] * Added `ClipboardError` enum. * On unix targets an instance of `arboard::Clipboard` is stored in the `Clipboard` resource. * Removed the desktop and dummy modules. * Made `arboard` dependency conditional. --- crates/bevy_clipboard/Cargo.toml | 2 + crates/bevy_clipboard/src/desktop.rs | 30 ----- crates/bevy_clipboard/src/dummy.rs | 26 ---- crates/bevy_clipboard/src/lib.rs | 192 +++++++++++++++++++++++++-- 4 files changed, 184 insertions(+), 66 deletions(-) delete mode 100644 crates/bevy_clipboard/src/desktop.rs delete mode 100644 crates/bevy_clipboard/src/dummy.rs diff --git a/crates/bevy_clipboard/Cargo.toml b/crates/bevy_clipboard/Cargo.toml index 9a40296304..08391671c6 100644 --- a/crates/bevy_clipboard/Cargo.toml +++ b/crates/bevy_clipboard/Cargo.toml @@ -12,6 +12,8 @@ keywords = ["bevy", "clipboard"] # bevy bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false } + +[target.'cfg(any(windows, unix))'.dependencies] arboard = { version = "3.5.0", default-features = false } [lints] diff --git a/crates/bevy_clipboard/src/desktop.rs b/crates/bevy_clipboard/src/desktop.rs deleted file mode 100644 index 80788a0f06..0000000000 --- a/crates/bevy_clipboard/src/desktop.rs +++ /dev/null @@ -1,30 +0,0 @@ -extern crate alloc; - -use bevy_ecs::resource::Resource; - -/// Resource providing access to the clipboard -#[derive(Resource, Default)] -pub struct Clipboard; - -impl Clipboard { - /// Fetches UTF-8 text from the clipboard and returns it. - /// - /// # Errors - /// - /// Returns error if clipboard is empty or contents are not UTF-8 text. - pub fn get_text(&mut self) -> Result { - arboard::Clipboard::new().and_then(|mut clipboard| clipboard.get_text()) - } - - /// Places the text onto the clipboard. Any valid UTF-8 string is accepted. - /// - /// # Errors - /// - /// Returns error if `text` failed to be stored on the clipboard. - pub fn set_text<'a, T: Into>>( - &mut self, - text: T, - ) -> Result<(), arboard::Error> { - arboard::Clipboard::new().and_then(|mut clipboard| clipboard.set_text(text)) - } -} diff --git a/crates/bevy_clipboard/src/dummy.rs b/crates/bevy_clipboard/src/dummy.rs deleted file mode 100644 index 9f5616852e..0000000000 --- a/crates/bevy_clipboard/src/dummy.rs +++ /dev/null @@ -1,26 +0,0 @@ -use bevy_ecs::resource::Resource; -use std::borrow::Cow; - -/// Resource providing access to the clipboard -#[derive(Resource, Default)] -pub struct Clipboard; - -impl Clipboard { - /// Fetches UTF-8 text from the clipboard and returns it. - /// - /// # Errors - /// - /// Returns error if clipboard is empty or contents are not UTF-8 text. - pub fn get_text(&mut self) -> Result { - Err(arboard::Error::ClipboardNotSupported) - } - - /// Places the text onto the clipboard. Any valid UTF-8 string is accepted. - /// - /// # Errors - /// - /// Returns error if `text` failed to be stored on the clipboard. - pub fn set_text<'a, T: Into>>(&mut self, text: T) -> Result<(), arboard::Error> { - Err(arboard::Error::ClipboardNotSupported) - } -} diff --git a/crates/bevy_clipboard/src/lib.rs b/crates/bevy_clipboard/src/lib.rs index d3ea590f3d..1bc276326a 100644 --- a/crates/bevy_clipboard/src/lib.rs +++ b/crates/bevy_clipboard/src/lib.rs @@ -1,16 +1,8 @@ //! This crate provides a platform-agnostic interface for accessing the clipboard -#[cfg(any(windows, unix))] -mod desktop; +extern crate alloc; -#[cfg(not(any(windows, unix)))] -mod dummy; - -#[cfg(any(windows, unix))] -pub use desktop::*; - -#[cfg(not(any(windows, unix)))] -pub use dummy::*; +pub use clipboard::*; /// Clipboard plugin #[derive(Default)] @@ -21,3 +13,183 @@ impl bevy_app::Plugin for ClipboardPlugin { app.init_resource::(); } } + +#[cfg(windows)] +mod clipboard { + use bevy_ecs::resource::Resource; + + use crate::ClipboardError; + + /// Resource providing access to the clipboard + #[derive(Resource, Default)] + pub struct Clipboard; + + impl Clipboard { + /// Fetches UTF-8 text from the clipboard and returns it. + /// + /// # Errors + /// + /// Returns error if clipboard is empty or contents are not UTF-8 text. + pub fn get_text(&mut self) -> Result { + arboard::Clipboard::new() + .and_then(|mut clipboard| clipboard.get_text()) + .map_err(ClipboardError::from) + } + + /// Places the text onto the clipboard. Any valid UTF-8 string is accepted. + /// + /// # Errors + /// + /// Returns error if `text` failed to be stored on the clipboard. + pub fn set_text<'a, T: Into>>( + &mut self, + text: T, + ) -> Result<(), ClipboardError> { + arboard::Clipboard::new() + .and_then(|mut clipboard| clipboard.set_text(text)) + .map_err(ClipboardError::from) + } + } +} + +#[cfg(unix)] +mod clipboard { + use crate::ClipboardError; + use bevy_ecs::resource::Resource; + + /// Resource providing access to the clipboard + #[derive(Resource)] + pub struct Clipboard(Option); + + impl Default for Clipboard { + fn default() -> Self { + Self(arboard::Clipboard::new().ok()) + } + } + + impl Clipboard { + /// Fetches UTF-8 text from the clipboard and returns it. + /// + /// # Errors + /// + /// Returns error if clipboard is empty or contents are not UTF-8 text. + pub fn get_text(&mut self) -> Result { + if let Some(clipboard) = self.0.as_mut() { + clipboard.get_text().map_err(ClipboardError::from) + } else { + Err(ClipboardError::ClipboardNotSupported) + } + } + + /// Places the text onto the clipboard. Any valid UTF-8 string is accepted. + /// + /// # Errors + /// + /// Returns error if `text` failed to be stored on the clipboard. + pub fn set_text<'a, T: Into>>( + &mut self, + text: T, + ) -> Result<(), ClipboardError> { + if let Some(clipboard) = self.0.as_mut() { + clipboard.set_text(text).map_err(ClipboardError::from) + } else { + Err(arboard::Error::ClipboardNotSupported) + } + } + } +} + +#[cfg(not(any(windows, unix)))] +mod clipboard { + use crate::ClipboardError; + use bevy_ecs::resource::Resource; + + /// Resource providing access to the clipboard + #[derive(Resource, Default)] + pub struct Clipboard; + + impl Clipboard { + /// Fetches UTF-8 text from the clipboard and returns it. + /// + /// # Errors + /// + /// Returns error if clipboard is empty or contents are not UTF-8 text. + pub fn get_text(&mut self) -> Result { + Err(ClipboardError::ClipboardNotSupported) + } + + /// Places the text onto the clipboard. Any valid UTF-8 string is accepted. + /// + /// # Errors + /// + /// Returns error if `text` failed to be stored on the clipboard. + pub fn set_text<'a, T: Into>>( + &mut self, + text: T, + ) -> Result<(), ClipboardError> { + Err(ClipboardError::ClipboardNotSupported) + } + } +} + +/// An error that might happen during a clipboard operation. +/// +/// Note that both the `Display` and the `Debug` trait is implemented for this type in such a way +/// that they give a short human-readable description of the error; however the documentation +/// gives a more detailed explanation for each error kind. +/// +/// Copied from `arboard::Error` +#[non_exhaustive] +pub enum ClipboardError { + /// The clipboard contents were not available in the requested format. + /// This could either be due to the clipboard being empty or the clipboard contents having + /// an incompatible format to the requested one (eg when calling `get_image` on text) + ContentNotAvailable, + + /// The selected clipboard is not supported by the current configuration (system and/or environment). + /// + /// This can be caused by a few conditions: + /// - Using the Primary clipboard with an older Wayland compositor (that doesn't support version 2) + /// - Using the Secondary clipboard on Wayland + ClipboardNotSupported, + + /// The native clipboard is not accessible due to being held by an other party. + /// + /// This "other party" could be a different process or it could be within + /// the same program. So for example you may get this error when trying + /// to interact with the clipboard from multiple threads at once. + /// + /// Note that it's OK to have multiple `Clipboard` instances. The underlying + /// implementation will make sure that the native clipboard is only + /// opened for transferring data and then closed as soon as possible. + ClipboardOccupied, + + /// The image or the text that was about the be transferred to/from the clipboard could not be + /// converted to the appropriate format. + ConversionFailure, + + /// Any error that doesn't fit the other error types. + /// + /// The `description` field is only meant to help the developer and should not be relied on as a + /// means to identify an error case during runtime. + Unknown { + /// String describing the error + description: String, + }, +} + +#[cfg(any(windows, unix))] +impl From for ClipboardError { + fn from(value: arboard::Error) -> Self { + match value { + arboard::Error::ContentNotAvailable => ClipboardError::ContentNotAvailable, + arboard::Error::ClipboardNotSupported => ClipboardError::ClipboardNotSupported, + arboard::Error::ClipboardOccupied => ClipboardError::ClipboardOccupied, + arboard::Error::ConversionFailure => ClipboardError::ConversionFailure, + arboard::Error::Unknown { description } => ClipboardError::Unknown { description }, + _ => ClipboardError::Unknown { + description: "".to_owned(), + }, + } + } +} From cb5fc373fbcf040247044ae29fef234af3dce9fb Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Wed, 7 May 2025 13:05:52 +0100 Subject: [PATCH 08/35] Fixed incorrect error type. --- crates/bevy_clipboard/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_clipboard/src/lib.rs b/crates/bevy_clipboard/src/lib.rs index 1bc276326a..600decda4f 100644 --- a/crates/bevy_clipboard/src/lib.rs +++ b/crates/bevy_clipboard/src/lib.rs @@ -93,7 +93,7 @@ mod clipboard { if let Some(clipboard) = self.0.as_mut() { clipboard.set_text(text).map_err(ClipboardError::from) } else { - Err(arboard::Error::ClipboardNotSupported) + Err(ClipboardError::ClipboardNotSupported) } } } From ef2f97188ad694a4fd1c1893616abe9303059559 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Wed, 7 May 2025 13:28:45 +0100 Subject: [PATCH 09/35] Cleanup --- crates/bevy_clipboard/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/bevy_clipboard/src/lib.rs b/crates/bevy_clipboard/src/lib.rs index 600decda4f..0cc2595bfc 100644 --- a/crates/bevy_clipboard/src/lib.rs +++ b/crates/bevy_clipboard/src/lib.rs @@ -16,9 +16,8 @@ impl bevy_app::Plugin for ClipboardPlugin { #[cfg(windows)] mod clipboard { - use bevy_ecs::resource::Resource; - use crate::ClipboardError; + use bevy_ecs::resource::Resource; /// Resource providing access to the clipboard #[derive(Resource, Default)] From bfce9eede53eb8ab02da564d1ebeda51827d7fa7 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Wed, 7 May 2025 15:32:50 +0100 Subject: [PATCH 10/35] Removed unused variable --- crates/bevy_clipboard/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_clipboard/src/lib.rs b/crates/bevy_clipboard/src/lib.rs index 0cc2595bfc..92a3df73eb 100644 --- a/crates/bevy_clipboard/src/lib.rs +++ b/crates/bevy_clipboard/src/lib.rs @@ -124,7 +124,7 @@ mod clipboard { /// Returns error if `text` failed to be stored on the clipboard. pub fn set_text<'a, T: Into>>( &mut self, - text: T, + _: T, ) -> Result<(), ClipboardError> { Err(ClipboardError::ClipboardNotSupported) } From 3237ed4caacb2d554783b15fbd86d89a3e8868e4 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Fri, 9 May 2025 13:57:58 +0100 Subject: [PATCH 11/35] added wasm32 support --- crates/bevy_clipboard/Cargo.toml | 7 ++ crates/bevy_clipboard/src/lib.rs | 120 +++++++++++++++++++++++++++++-- examples/ui/text.rs | 12 +++- 3 files changed, 132 insertions(+), 7 deletions(-) diff --git a/crates/bevy_clipboard/Cargo.toml b/crates/bevy_clipboard/Cargo.toml index 08391671c6..8fdfdd3831 100644 --- a/crates/bevy_clipboard/Cargo.toml +++ b/crates/bevy_clipboard/Cargo.toml @@ -12,10 +12,17 @@ keywords = ["bevy", "clipboard"] # bevy bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false } +bevy_log = { path = "../bevy_log", version = "0.16.0-dev", default-features = false } [target.'cfg(any(windows, unix))'.dependencies] arboard = { version = "3.5.0", default-features = false } +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = { version = "0.2" } +web-sys = { version = "0.3", features = ["Navigator", "Clipboard"] } +wasm-bindgen-futures = "0.4" +futures = "0.3" + [lints] workspace = true diff --git a/crates/bevy_clipboard/src/lib.rs b/crates/bevy_clipboard/src/lib.rs index 92a3df73eb..82b3065a84 100644 --- a/crates/bevy_clipboard/src/lib.rs +++ b/crates/bevy_clipboard/src/lib.rs @@ -17,21 +17,32 @@ impl bevy_app::Plugin for ClipboardPlugin { #[cfg(windows)] mod clipboard { use crate::ClipboardError; + use bevy_ecs::resource::Resource; /// Resource providing access to the clipboard #[derive(Resource, Default)] pub struct Clipboard; + pub struct ClipboardText(Option); + + impl ClipboardText { + pub fn poll(&mut self) -> Option { + self.0.take() + } + } + impl Clipboard { /// Fetches UTF-8 text from the clipboard and returns it. /// /// # Errors /// /// Returns error if clipboard is empty or contents are not UTF-8 text. - pub fn get_text(&mut self) -> Result { + pub fn get_text(&mut self) -> Result { arboard::Clipboard::new() - .and_then(|mut clipboard| clipboard.get_text()) + .and_then(|mut clipboard| { + clipboard.get_text().map(|text| ClipboardText(Some(text))) + }) .map_err(ClipboardError::from) } @@ -66,15 +77,26 @@ mod clipboard { } } + pub struct ClipboardText(Option); + + impl ClipboardText { + pub fn poll(&mut self) -> Option { + self.0.take() + } + } + impl Clipboard { /// Fetches UTF-8 text from the clipboard and returns it. /// /// # Errors /// /// Returns error if clipboard is empty or contents are not UTF-8 text. - pub fn get_text(&mut self) -> Result { + pub fn get_text(&mut self) -> Result { if let Some(clipboard) = self.0.as_mut() { - clipboard.get_text().map_err(ClipboardError::from) + clipboard + .get_text() + .and_then(|mut clipboard| clipboard.set_text(text)) + .map_err(ClipboardError::from) } else { Err(ClipboardError::ClipboardNotSupported) } @@ -98,7 +120,84 @@ mod clipboard { } } -#[cfg(not(any(windows, unix)))] +#[cfg(target_arch = "wasm32")] +mod clipboard { + use crate::ClipboardError; + use bevy_ecs::resource::Resource; + use futures::task::noop_waker; + use futures::task::Context; + use futures::task::Poll; + use futures::FutureExt; + use std::pin::Pin; + use wasm_bindgen_futures::JsFuture; + + /// Resource providing access to the clipboard + #[derive(Resource, Default)] + pub struct Clipboard; + + pub struct ClipboardText(Option); + + fn poll_future(fut: &mut F) -> Poll { + // create a dummy waker & context + let waker = noop_waker(); + let mut cx = Context::from_waker(&waker); + // poll by mutable reference—doesn't consume the future + Pin::new(fut).poll(&mut cx) + } + + impl ClipboardText { + pub fn poll(&mut self) -> Option> { + let mut f = self.0.take().unwrap(); + + match poll_future(&mut f) { + Poll::Ready(Ok(js_val)) => { + let text = js_val + .as_string() + .ok_or(ClipboardError::ContentNotAvailable); + Some(text) + } + Poll::Ready(Err(_)) => Some(Err(ClipboardError::ContentNotAvailable)), + Poll::Pending => { + // still pending, put it back + self.0 = Some(f); + None + } + } + } + } + + impl Clipboard { + pub fn get_text(&mut self) -> Result { + if let Some(clipboard) = web_sys::window().map(|w| w.navigator().clipboard()) { + Ok(ClipboardText(Some(JsFuture::from(clipboard.read_text())))) + } else { + Err(ClipboardError::ClipboardNotSupported) + } + } + + /// Places the text onto the clipboard. Any valid UTF-8 string is accepted. + /// + /// # Errors + /// + /// Returns error if `text` failed to be stored on the clipboard. + pub fn set_text<'a, T: Into>>( + &mut self, + text: T, + ) -> Result<(), ClipboardError> { + let text = text.into().to_string(); + if let Some(clipboard) = web_sys::window().map(|w| w.navigator().clipboard()) { + wasm_bindgen_futures::spawn_local(async move { + let _ = JsFuture::from(clipboard.write_text(&text)).await; + }); + Ok(()) + } else { + Err(ClipboardError::ClipboardNotSupported) + } + } + } +} + +#[cfg(not(any(windows, unix, target_arch = "wasm32")))] mod clipboard { use crate::ClipboardError; use bevy_ecs::resource::Resource; @@ -107,13 +206,21 @@ mod clipboard { #[derive(Resource, Default)] pub struct Clipboard; + pub struct ClipboardText; + + impl ClipboardText { + pub fn poll(&self) -> Option { + None + } + } + impl Clipboard { /// Fetches UTF-8 text from the clipboard and returns it. /// /// # Errors /// /// Returns error if clipboard is empty or contents are not UTF-8 text. - pub fn get_text(&mut self) -> Result { + pub fn get_text(&mut self) -> Result { Err(ClipboardError::ClipboardNotSupported) } @@ -139,6 +246,7 @@ mod clipboard { /// /// Copied from `arboard::Error` #[non_exhaustive] +#[derive(Debug)] pub enum ClipboardError { /// The clipboard contents were not available in the requested format. /// This could either be due to the clipboard being empty or the clipboard contents having diff --git a/examples/ui/text.rs b/examples/ui/text.rs index 8bf34cc96e..d6549c4653 100644 --- a/examples/ui/text.rs +++ b/examples/ui/text.rs @@ -4,6 +4,7 @@ //! in the bottom right. For text within a scene, please see the text2d example. use bevy::{ + clipboard::Clipboard, color::palettes::css::GOLD, diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin}, prelude::*, @@ -12,7 +13,7 @@ use bevy::{ fn main() { App::new() .add_plugins((DefaultPlugins, FrameTimeDiagnosticsPlugin::default())) - .add_systems(Startup, setup) + .add_systems(Startup, (setup, clipboard_test)) .add_systems(Update, (text_update_system, text_color_system)) .run(); } @@ -25,6 +26,15 @@ struct FpsText; #[derive(Component)] struct AnimatedText; +fn clipboard_test(mut clipboard: ResMut) { + let text = clipboard.get_text().unwrap().poll(); + info!("clipboard contents = {:?}", text); + info!("set clipboard text"); + clipboard.set_text("Hello bevy!").unwrap(); + let text = clipboard.get_text().unwrap().poll(); + info!("clipboard contents = {:?}", text); +} + fn setup(mut commands: Commands, asset_server: Res) { // UI camera commands.spawn(Camera2d); From 242368c10b09aba722a047833084cbfbaf71d2bf Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Fri, 9 May 2025 18:33:44 +0100 Subject: [PATCH 12/35] Replaced `ClipboardText` with `ClipboardContents` enum. --- crates/bevy_clipboard/src/lib.rs | 134 +++++++++++++------------------ 1 file changed, 58 insertions(+), 76 deletions(-) diff --git a/crates/bevy_clipboard/src/lib.rs b/crates/bevy_clipboard/src/lib.rs index 82b3065a84..19c3458a99 100644 --- a/crates/bevy_clipboard/src/lib.rs +++ b/crates/bevy_clipboard/src/lib.rs @@ -2,6 +2,9 @@ extern crate alloc; +use alloc::sync::Arc; +use std::sync::Mutex; + pub use clipboard::*; /// Clipboard plugin @@ -14,36 +17,46 @@ impl bevy_app::Plugin for ClipboardPlugin { } } +/// Contents of the clipboard. +#[derive(Debug)] +pub enum ClipboardContents { + /// The clipboard contents are ready to be accessed. + Ready(Result), + /// The clipboard contents are still being fetched. + Pending(Arc>>>), +} + +impl ClipboardContents { + /// Returns the contents of the clipboard if they are ready. + pub fn try_get(&self) -> Option> { + match self { + ClipboardContents::Ready(result) => Some(result.clone()), + ClipboardContents::Pending(shared) => shared.lock().unwrap().clone(), + } + } +} + #[cfg(windows)] mod clipboard { + use crate::ClipboardContents; use crate::ClipboardError; - use bevy_ecs::resource::Resource; /// Resource providing access to the clipboard #[derive(Resource, Default)] pub struct Clipboard; - - pub struct ClipboardText(Option); - - impl ClipboardText { - pub fn poll(&mut self) -> Option { - self.0.take() - } - } - impl Clipboard { /// Fetches UTF-8 text from the clipboard and returns it. /// /// # Errors /// /// Returns error if clipboard is empty or contents are not UTF-8 text. - pub fn get_text(&mut self) -> Result { - arboard::Clipboard::new() - .and_then(|mut clipboard| { - clipboard.get_text().map(|text| ClipboardText(Some(text))) - }) - .map_err(ClipboardError::from) + pub fn get_text(&mut self) -> ClipboardContents { + ClipboardContents::Ready({ + arboard::Clipboard::new() + .and_then(|mut clipboard| clipboard.get_text()) + .map_err(ClipboardError::from) + }) } /// Places the text onto the clipboard. Any valid UTF-8 string is accepted. @@ -64,7 +77,8 @@ mod clipboard { #[cfg(unix)] mod clipboard { - use crate::ClipboardError; + use super::*; + use bevy_ecs::resource::Resource; /// Resource providing access to the clipboard @@ -77,29 +91,18 @@ mod clipboard { } } - pub struct ClipboardText(Option); - - impl ClipboardText { - pub fn poll(&mut self) -> Option { - self.0.take() - } - } - impl Clipboard { /// Fetches UTF-8 text from the clipboard and returns it. /// /// # Errors /// /// Returns error if clipboard is empty or contents are not UTF-8 text. - pub fn get_text(&mut self) -> Result { - if let Some(clipboard) = self.0.as_mut() { - clipboard - .get_text() - .and_then(|mut clipboard| clipboard.set_text(text)) - .map_err(ClipboardError::from) + pub fn get_text(&mut self) -> ClipboardContents { + ClipboardContents::Ready(if let Some(clipboard) = self.0.as_mut() { + clipboard.get_text().map_err(ClipboardError::from) } else { Err(ClipboardError::ClipboardNotSupported) - } + }) } /// Places the text onto the clipboard. Any valid UTF-8 string is accepted. @@ -122,6 +125,7 @@ mod clipboard { #[cfg(target_arch = "wasm32")] mod clipboard { + use super::*; use crate::ClipboardError; use bevy_ecs::resource::Resource; use futures::task::noop_waker; @@ -129,49 +133,35 @@ mod clipboard { use futures::task::Poll; use futures::FutureExt; use std::pin::Pin; + use std::sync::Arc; + use std::sync::Mutex; use wasm_bindgen_futures::JsFuture; /// Resource providing access to the clipboard #[derive(Resource, Default)] pub struct Clipboard; - pub struct ClipboardText(Option); - - fn poll_future(fut: &mut F) -> Poll { - // create a dummy waker & context - let waker = noop_waker(); - let mut cx = Context::from_waker(&waker); - // poll by mutable reference—doesn't consume the future - Pin::new(fut).poll(&mut cx) - } - - impl ClipboardText { - pub fn poll(&mut self) -> Option> { - let mut f = self.0.take().unwrap(); - - match poll_future(&mut f) { - Poll::Ready(Ok(js_val)) => { - let text = js_val - .as_string() - .ok_or(ClipboardError::ContentNotAvailable); - Some(text) - } - Poll::Ready(Err(_)) => Some(Err(ClipboardError::ContentNotAvailable)), - Poll::Pending => { - // still pending, put it back - self.0 = Some(f); - None - } - } - } - } - impl Clipboard { - pub fn get_text(&mut self) -> Result { + /// Fetches UTF-8 text from the clipboard and returns it. + /// + /// # Errors + /// + /// Returns error if clipboard is empty or contents are not UTF-8 text. + pub fn get_text(&mut self) -> ClipboardContents { if let Some(clipboard) = web_sys::window().map(|w| w.navigator().clipboard()) { - Ok(ClipboardText(Some(JsFuture::from(clipboard.read_text())))) + let shared = Arc::new(Mutex::new(None)); + let shared_clone = shared.clone(); + wasm_bindgen_futures::spawn_local(async move { + let text = JsFuture::from(clipboard.read_text()).await; + let text = match text { + Ok(text) => text.as_string().ok_or(ClipboardError::ConversionFailure), + Err(_) => Err(ClipboardError::ContentNotAvailable), + }; + shared.lock().unwrap().replace(text); + }); + ClipboardContents::Pending(shared_clone) } else { - Err(ClipboardError::ClipboardNotSupported) + ClipboardContents::Ready(Err(ClipboardError::ClipboardNotSupported)) } } @@ -206,21 +196,13 @@ mod clipboard { #[derive(Resource, Default)] pub struct Clipboard; - pub struct ClipboardText; - - impl ClipboardText { - pub fn poll(&self) -> Option { - None - } - } - impl Clipboard { /// Fetches UTF-8 text from the clipboard and returns it. /// /// # Errors /// /// Returns error if clipboard is empty or contents are not UTF-8 text. - pub fn get_text(&mut self) -> Result { + pub fn get_text(&mut self) -> Result { Err(ClipboardError::ClipboardNotSupported) } @@ -246,7 +228,7 @@ mod clipboard { /// /// Copied from `arboard::Error` #[non_exhaustive] -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum ClipboardError { /// The clipboard contents were not available in the requested format. /// This could either be due to the clipboard being empty or the clipboard contents having From 91ab7c5bf1053ed9ac23fc7183f38479e6ec68df Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Fri, 9 May 2025 18:35:58 +0100 Subject: [PATCH 13/35] Reverted example changes --- examples/ui/text.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/examples/ui/text.rs b/examples/ui/text.rs index d6549c4653..8bf34cc96e 100644 --- a/examples/ui/text.rs +++ b/examples/ui/text.rs @@ -4,7 +4,6 @@ //! in the bottom right. For text within a scene, please see the text2d example. use bevy::{ - clipboard::Clipboard, color::palettes::css::GOLD, diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin}, prelude::*, @@ -13,7 +12,7 @@ use bevy::{ fn main() { App::new() .add_plugins((DefaultPlugins, FrameTimeDiagnosticsPlugin::default())) - .add_systems(Startup, (setup, clipboard_test)) + .add_systems(Startup, setup) .add_systems(Update, (text_update_system, text_color_system)) .run(); } @@ -26,15 +25,6 @@ struct FpsText; #[derive(Component)] struct AnimatedText; -fn clipboard_test(mut clipboard: ResMut) { - let text = clipboard.get_text().unwrap().poll(); - info!("clipboard contents = {:?}", text); - info!("set clipboard text"); - clipboard.set_text("Hello bevy!").unwrap(); - let text = clipboard.get_text().unwrap().poll(); - info!("clipboard contents = {:?}", text); -} - fn setup(mut commands: Commands, asset_server: Res) { // UI camera commands.spawn(Camera2d); From 5f4b4c8ecaa725a16440155959008d200ab377d7 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Fri, 9 May 2025 18:43:10 +0100 Subject: [PATCH 14/35] Renamed `try_get` to `get_or_poll`, `get_text` to `fetch_text` --- crates/bevy_clipboard/src/lib.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/bevy_clipboard/src/lib.rs b/crates/bevy_clipboard/src/lib.rs index 19c3458a99..3934261333 100644 --- a/crates/bevy_clipboard/src/lib.rs +++ b/crates/bevy_clipboard/src/lib.rs @@ -28,7 +28,7 @@ pub enum ClipboardContents { impl ClipboardContents { /// Returns the contents of the clipboard if they are ready. - pub fn try_get(&self) -> Option> { + pub fn get_or_poll(&self) -> Option> { match self { ClipboardContents::Ready(result) => Some(result.clone()), ClipboardContents::Pending(shared) => shared.lock().unwrap().clone(), @@ -45,13 +45,14 @@ mod clipboard { /// Resource providing access to the clipboard #[derive(Resource, Default)] pub struct Clipboard; + impl Clipboard { /// Fetches UTF-8 text from the clipboard and returns it. /// /// # Errors /// /// Returns error if clipboard is empty or contents are not UTF-8 text. - pub fn get_text(&mut self) -> ClipboardContents { + pub fn fetch_text(&mut self) -> ClipboardContents { ClipboardContents::Ready({ arboard::Clipboard::new() .and_then(|mut clipboard| clipboard.get_text()) @@ -97,7 +98,7 @@ mod clipboard { /// # Errors /// /// Returns error if clipboard is empty or contents are not UTF-8 text. - pub fn get_text(&mut self) -> ClipboardContents { + pub fn fetch_text(&mut self) -> ClipboardContents { ClipboardContents::Ready(if let Some(clipboard) = self.0.as_mut() { clipboard.get_text().map_err(ClipboardError::from) } else { @@ -147,7 +148,7 @@ mod clipboard { /// # Errors /// /// Returns error if clipboard is empty or contents are not UTF-8 text. - pub fn get_text(&mut self) -> ClipboardContents { + pub fn fetch_text(&mut self) -> ClipboardContents { if let Some(clipboard) = web_sys::window().map(|w| w.navigator().clipboard()) { let shared = Arc::new(Mutex::new(None)); let shared_clone = shared.clone(); @@ -202,7 +203,7 @@ mod clipboard { /// # Errors /// /// Returns error if clipboard is empty or contents are not UTF-8 text. - pub fn get_text(&mut self) -> Result { + pub fn fetch_text(&mut self) -> Result { Err(ClipboardError::ClipboardNotSupported) } From 970ce9bfd5e0c76924737d5b8a20b099f3a164e7 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Fri, 9 May 2025 18:57:50 +0100 Subject: [PATCH 15/35] use Mutex from bevy_platform --- crates/bevy_clipboard/Cargo.toml | 2 +- crates/bevy_clipboard/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_clipboard/Cargo.toml b/crates/bevy_clipboard/Cargo.toml index 8fdfdd3831..4df676e9a6 100644 --- a/crates/bevy_clipboard/Cargo.toml +++ b/crates/bevy_clipboard/Cargo.toml @@ -12,7 +12,7 @@ keywords = ["bevy", "clipboard"] # bevy bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false } -bevy_log = { path = "../bevy_log", version = "0.16.0-dev", default-features = false } +bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false } [target.'cfg(any(windows, unix))'.dependencies] arboard = { version = "3.5.0", default-features = false } diff --git a/crates/bevy_clipboard/src/lib.rs b/crates/bevy_clipboard/src/lib.rs index 3934261333..5e9e3c3dc5 100644 --- a/crates/bevy_clipboard/src/lib.rs +++ b/crates/bevy_clipboard/src/lib.rs @@ -3,7 +3,7 @@ extern crate alloc; use alloc::sync::Arc; -use std::sync::Mutex; +use bevy_platform::sync::Mutex; pub use clipboard::*; From e6ad9f266422db7619c727c463520ab821aa7040 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Fri, 9 May 2025 19:22:14 +0100 Subject: [PATCH 16/35] Removed unused imports. --- crates/bevy_clipboard/src/lib.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/crates/bevy_clipboard/src/lib.rs b/crates/bevy_clipboard/src/lib.rs index 5e9e3c3dc5..29ed222ed7 100644 --- a/crates/bevy_clipboard/src/lib.rs +++ b/crates/bevy_clipboard/src/lib.rs @@ -129,13 +129,6 @@ mod clipboard { use super::*; use crate::ClipboardError; use bevy_ecs::resource::Resource; - use futures::task::noop_waker; - use futures::task::Context; - use futures::task::Poll; - use futures::FutureExt; - use std::pin::Pin; - use std::sync::Arc; - use std::sync::Mutex; use wasm_bindgen_futures::JsFuture; /// Resource providing access to the clipboard From 1025380f8aafe7b0e0d5d8dfbd2465354ad26428 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Fri, 9 May 2025 20:03:24 +0100 Subject: [PATCH 17/35] Added extra comment --- crates/bevy_clipboard/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/bevy_clipboard/src/lib.rs b/crates/bevy_clipboard/src/lib.rs index 29ed222ed7..23d3cdb70f 100644 --- a/crates/bevy_clipboard/src/lib.rs +++ b/crates/bevy_clipboard/src/lib.rs @@ -18,6 +18,8 @@ impl bevy_app::Plugin for ClipboardPlugin { } /// Contents of the clipboard. +/// +/// Depending on the platform, the contents may be available immediately or fetched asynchronously. #[derive(Debug)] pub enum ClipboardContents { /// The clipboard contents are ready to be accessed. From f77aef97e87ef0bac54050aed915d99049cc0678 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Fri, 9 May 2025 20:34:34 +0100 Subject: [PATCH 18/35] Removed futures depedency --- crates/bevy_clipboard/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/bevy_clipboard/Cargo.toml b/crates/bevy_clipboard/Cargo.toml index 4df676e9a6..1e27afa91b 100644 --- a/crates/bevy_clipboard/Cargo.toml +++ b/crates/bevy_clipboard/Cargo.toml @@ -21,7 +21,6 @@ arboard = { version = "3.5.0", default-features = false } wasm-bindgen = { version = "0.2" } web-sys = { version = "0.3", features = ["Navigator", "Clipboard"] } wasm-bindgen-futures = "0.4" -futures = "0.3" [lints] workspace = true From 42fd98af552a961935e56d6099f905e1b6053822 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Mon, 19 May 2025 11:49:43 +0100 Subject: [PATCH 19/35] Added `clipboard` example --- Cargo.toml | 11 +++ examples/README.md | 1 + examples/ui/clipboard.rs | 160 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 172 insertions(+) create mode 100644 examples/ui/clipboard.rs diff --git a/Cargo.toml b/Cargo.toml index 9ba5aae4f8..721337f05e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3248,6 +3248,17 @@ description = "Illustrates creating and updating a button" category = "UI (User Interface)" wasm = true +[[example]] +name = "clipboard" +path = "examples/ui/clipboard.rs" +doc-scrape-examples = true + +[package.metadata.example.clipboard] +name = "Clipboard" +description = "Demonstrates accessing the clipboard to retrieve and display text" +category = "UI (User Interface)" +wasm = true + [[example]] name = "display_and_visibility" path = "examples/ui/display_and_visibility.rs" diff --git a/examples/README.md b/examples/README.md index 060683f96d..4f0ae59c9a 100644 --- a/examples/README.md +++ b/examples/README.md @@ -542,6 +542,7 @@ Example | Description [Box Shadow](../examples/ui/box_shadow.rs) | Demonstrates how to create a node with a shadow [Button](../examples/ui/button.rs) | Illustrates creating and updating a button [CSS Grid](../examples/ui/grid.rs) | An example for CSS Grid layout +[Clipboard](../examples/ui/clipboard.rs) | Demonstrates accessing the clipboard to retrieve and display text [Directional Navigation](../examples/ui/directional_navigation.rs) | Demonstration of Directional Navigation between UI elements [Display and Visibility](../examples/ui/display_and_visibility.rs) | Demonstrates how Display and Visibility work in the UI. [Flex Layout](../examples/ui/flex_layout.rs) | Demonstrates how the AlignItems and JustifyContent properties can be composed to layout nodes and position text diff --git a/examples/ui/clipboard.rs b/examples/ui/clipboard.rs new file mode 100644 index 0000000000..8dfd797869 --- /dev/null +++ b/examples/ui/clipboard.rs @@ -0,0 +1,160 @@ +//! This example demonstrates accessing the clipboard to retrieve and display text. + +use bevy::{ + clipboard::{Clipboard, ClipboardContents}, + color::palettes::css::{GREY, NAVY, RED}, + diagnostic::FrameTimeDiagnosticsPlugin, + prelude::*, +}; + +fn main() { + App::new() + .add_plugins((DefaultPlugins, FrameTimeDiagnosticsPlugin::default())) + .add_systems(Startup, setup) + .add_systems(Update, paste_text_system) + .run(); +} + +const NORMAL_BUTTON: Color = Color::srgb(0.15, 0.15, 0.15); +const HOVERED_BUTTON: Color = Color::srgb(0.25, 0.25, 0.25); + +/// Button discriminator +#[derive(Component)] +pub enum ButtonAction { + /// The button pastes some text from the clipboard + PasteText, + /// The button sends some text to the clipboard + SetText, +} + +/// Marker component for text box paste target +#[derive(Component)] +pub struct PasteTarget; + +fn setup(mut commands: Commands) { + // UI camera + commands.spawn(Camera2d); + + commands.spawn(( + Node { + width: Val::Percent(100.0), + height: Val::Percent(100.0), + justify_content: JustifyContent::Center, + align_items: AlignItems::Center, + ..default() + }, + children![( + Node { + flex_direction: FlexDirection::Column, + padding: UiRect::all(Val::Px(30.)), + row_gap: Val::Px(20.), + justify_content: JustifyContent::SpaceBetween, + align_items: AlignItems::Stretch, + ..default() + }, + BackgroundColor(NAVY.into()), + children![ + ( + Node { + align_self: AlignSelf::Center, + ..Default::default() + }, + Text::new("Bevy clipboard example"), + ), + ( + Node { + width: Val::Px(500.), + height: Val::Px(250.), + padding: UiRect::all(Val::Px(3.)), + border: UiRect::all(Val::Px(2.)), + ..Default::default() + }, + BorderColor(Color::WHITE), + BackgroundColor(Color::BLACK), + children![( + Text::new("Nothing pasted yet."), + TextColor(GREY.into()), + PasteTarget + )], + ), + ( + Node { + border: UiRect::all(Val::Px(2.)), + padding: UiRect::all(Val::Px(10.)), + align_self: AlignSelf::Center, + ..Default::default() + }, + Button, + ButtonAction::PasteText, + BorderColor(Color::WHITE), + BackgroundColor(Color::BLACK), + children![Text::new("Click to paste text")], + ), + ( + Node { + border: UiRect::all(Val::Px(2.)), + padding: UiRect::all(Val::Px(10.)), + align_self: AlignSelf::Center, + ..Default::default() + }, + Button, + ButtonAction::SetText, + BorderColor(Color::WHITE), + BackgroundColor(Color::BLACK), + children![Text::new("Click to copy 'Hello bevy!'\nto the clipboard")], + ), + ] + ),], + )); +} + +fn paste_text_system( + mut paste: Local>, + mut clipboard: ResMut, + mut interaction_query: Query< + ( + &Interaction, + &mut BackgroundColor, + &mut BorderColor, + &ButtonAction, + ), + (Changed, With