From e5a901304eb83c668a86aaead9ec1e75a84965b2 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Thu, 31 Jul 2025 12:49:33 +0100 Subject: [PATCH 01/11] Added a very basic virtual keyboard widget to bevy_feathers and a `virtual_keyboard` example demonstrating it. --- Cargo.toml | 12 ++ crates/bevy_feathers/src/controls/mod.rs | 2 + .../src/controls/virtual_keyboard.rs | 56 +++++++++ examples/ui/virtual_keyboard.rs | 112 ++++++++++++++++++ 4 files changed, 182 insertions(+) create mode 100644 crates/bevy_feathers/src/controls/virtual_keyboard.rs create mode 100644 examples/ui/virtual_keyboard.rs diff --git a/Cargo.toml b/Cargo.toml index 85cafbfab70bd..d80e3be411cad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3593,6 +3593,18 @@ description = "Demonstrates how to control the relative depth (z-position) of UI category = "UI (User Interface)" wasm = true +[[example]] +name = "virtual_keyboard" +path = "examples/ui/virtual_keyboard.rs" +doc-scrape-examples = true +required-features = ["experimental_bevy_feathers"] + +[package.metadata.example.virtual_keyboard] +name = "Virtual Keyboard" +description = "Example demonstrating a virtual keyboard widget" +category = "UI (User Interface)" +wasm = true + [[example]] name = "ui_scaling" path = "examples/ui/ui_scaling.rs" diff --git a/crates/bevy_feathers/src/controls/mod.rs b/crates/bevy_feathers/src/controls/mod.rs index 32bcf2bfc80c6..c398b25e10bb0 100644 --- a/crates/bevy_feathers/src/controls/mod.rs +++ b/crates/bevy_feathers/src/controls/mod.rs @@ -7,6 +7,7 @@ mod color_swatch; mod radio; mod slider; mod toggle_switch; +mod virtual_keyboard; pub use button::{button, ButtonPlugin, ButtonProps, ButtonVariant}; pub use checkbox::{checkbox, CheckboxPlugin, CheckboxProps}; @@ -14,6 +15,7 @@ pub use color_swatch::{color_swatch, ColorSwatch, ColorSwatchFg}; pub use radio::{radio, RadioPlugin}; pub use slider::{slider, SliderPlugin, SliderProps}; pub use toggle_switch::{toggle_switch, ToggleSwitchPlugin, ToggleSwitchProps}; +pub use virtual_keyboard::virtual_keyboard; use crate::alpha_pattern::AlphaPatternPlugin; diff --git a/crates/bevy_feathers/src/controls/virtual_keyboard.rs b/crates/bevy_feathers/src/controls/virtual_keyboard.rs new file mode 100644 index 0000000000000..0d4e875a78274 --- /dev/null +++ b/crates/bevy_feathers/src/controls/virtual_keyboard.rs @@ -0,0 +1,56 @@ +use bevy_core_widgets::{Activate, Callback}; +use bevy_ecs::{ + bundle::Bundle, + component::Component, + hierarchy::{ChildOf, Children}, + relationship::RelatedSpawner, + spawn::{Spawn, SpawnRelated, SpawnWith}, + system::{In, SystemId}, +}; +use bevy_input_focus::tab_navigation::TabGroup; +use bevy_ui::Node; +use bevy_ui::Val; +use bevy_ui::{widget::Text, FlexDirection}; + +use crate::controls::{button, ButtonProps}; + +/// Function to spawn a virtual keyboard +pub fn virtual_keyboard( + keys: Vec>, + on_key_press: SystemId>, +) -> impl Bundle +where + T: Component, +{ + ( + Node { + flex_direction: FlexDirection::Column, + row_gap: Val::Px(4.), + ..Default::default() + }, + TabGroup::new(0), + Children::spawn((SpawnWith(move |parent: &mut RelatedSpawner| { + for row in keys.into_iter() { + parent.spawn(( + Node { + flex_direction: FlexDirection::Row, + column_gap: Val::Px(4.), + ..Default::default() + }, + Children::spawn(SpawnWith(move |parent: &mut RelatedSpawner| { + for (label, key_id) in row.into_iter() { + parent.spawn(button( + ButtonProps { + on_click: Callback::System(on_key_press), + ..Default::default() + }, + (key_id,), + Spawn(Text::new(label)), + )); + } + })), + )); + } + }),)), + ) +} diff --git a/examples/ui/virtual_keyboard.rs b/examples/ui/virtual_keyboard.rs new file mode 100644 index 0000000000000..7d542963d733c --- /dev/null +++ b/examples/ui/virtual_keyboard.rs @@ -0,0 +1,112 @@ +//! Virtual keyboard example + +use bevy::{ + color::palettes::css::{NAVY, YELLOW}, + core_widgets::{ + Activate, Callback, CoreRadio, CoreRadioGroup, CoreWidgetsPlugins, SliderPrecision, + SliderStep, + }, + feathers::{ + controls::{ + button, checkbox, color_swatch, radio, slider, toggle_switch, virtual_keyboard, + ButtonProps, ButtonVariant, CheckboxProps, SliderProps, ToggleSwitchProps, + }, + dark_theme::create_dark_theme, + rounded_corners::RoundedCorners, + theme::{ThemeBackgroundColor, ThemedText, UiTheme}, + tokens, FeathersPlugin, + }, + input_focus::{ + tab_navigation::{TabGroup, TabNavigationPlugin}, + InputDispatchPlugin, + }, + prelude::*, + ui::{Checked, InteractionDisabled}, + winit::WinitSettings, +}; +use bevy_ecs::relationship::{RelatedSpawner, RelatedSpawnerCommands}; + +fn main() { + App::new() + .add_plugins(( + DefaultPlugins, + CoreWidgetsPlugins, + InputDispatchPlugin, + TabNavigationPlugin, + FeathersPlugin, + )) + .insert_resource(UiTheme(create_dark_theme())) + // Only run the app when there is user input. This will significantly reduce CPU/GPU use. + .insert_resource(WinitSettings::desktop_app()) + .add_systems(Startup, setup) + .run(); +} + +#[derive(Component)] +struct VirtualKey(String); + +fn on_virtual_key_pressed( + In(Activate(virtual_key_entity)): In, + virtual_key_query: Query<&VirtualKey>, +) { + if let Ok(VirtualKey(label)) = virtual_key_query.get(virtual_key_entity) { + println!("key pressed: {label}"); + } +} + +fn setup(mut commands: Commands) { + // ui camera + commands.spawn(Camera2d); + let callback = commands.register_system(on_virtual_key_pressed); + + let layout = vec![ + vec!["1", "2", "3", "4", "5", "6", "7", "8", "9", "0", ".", ","], + vec!["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"], + vec!["A", "S", "D", "F", "G", "H", "J", "K", "L", "'"], + vec!["Z", "X", "C", "V", "B", "N", "M", "-", "/"], + vec!["space", "enter", "backspace"], + vec!["left", "right", "up", "down", "home", "end"], + ]; + + let keys = layout + .into_iter() + .map(|row| { + row.into_iter() + .map(|label| { + let label_string = label.to_string(); + (label_string.clone(), VirtualKey(label_string)) + }) + .collect() + }) + .collect(); + + commands + .spawn(Node { + width: Val::Percent(100.0), + height: Val::Percent(100.0), + align_items: AlignItems::End, + justify_content: JustifyContent::Center, + ..default() + }) + .with_children(|parent: &mut RelatedSpawnerCommands| { + parent + .spawn(( + Node { + flex_direction: FlexDirection::Column, + border: Val::Px(5.).into(), + row_gap: Val::Px(5.), + padding: Val::Px(5.).into(), + align_items: AlignItems::Center, + margin: Val::Px(25.).into(), + ..Default::default() + }, + BackgroundColor(NAVY.into()), + BorderColor::all(Color::WHITE), + BorderRadius::all(Val::Px(10.)), + )) + .with_children(|parent: &mut RelatedSpawnerCommands| { + parent.spawn(Text::new("virtual keyboard")); + parent.spawn(virtual_keyboard(keys, callback)); + }); + }); +} From 7eebebe90221f1564b33223ff8f9db8c1a5a4f01 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Thu, 31 Jul 2025 13:10:17 +0100 Subject: [PATCH 02/11] Fixed internal imports --- crates/bevy_feathers/src/controls/virtual_keyboard.rs | 4 ++-- examples/ui/virtual_keyboard.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_feathers/src/controls/virtual_keyboard.rs b/crates/bevy_feathers/src/controls/virtual_keyboard.rs index 0d4e875a78274..6f0473ffe0611 100644 --- a/crates/bevy_feathers/src/controls/virtual_keyboard.rs +++ b/crates/bevy_feathers/src/controls/virtual_keyboard.rs @@ -16,7 +16,7 @@ use crate::controls::{button, ButtonProps}; /// Function to spawn a virtual keyboard pub fn virtual_keyboard( - keys: Vec>, + keys: impl Iterator> + Send + Sync + 'static, on_key_press: SystemId>, ) -> impl Bundle where @@ -30,7 +30,7 @@ where }, TabGroup::new(0), Children::spawn((SpawnWith(move |parent: &mut RelatedSpawner| { - for row in keys.into_iter() { + for row in keys { parent.spawn(( Node { flex_direction: FlexDirection::Row, diff --git a/examples/ui/virtual_keyboard.rs b/examples/ui/virtual_keyboard.rs index 7d542963d733c..b5b8db48f7601 100644 --- a/examples/ui/virtual_keyboard.rs +++ b/examples/ui/virtual_keyboard.rs @@ -6,6 +6,7 @@ use bevy::{ Activate, Callback, CoreRadio, CoreRadioGroup, CoreWidgetsPlugins, SliderPrecision, SliderStep, }, + ecs::relationship::{RelatedSpawner, RelatedSpawnerCommands}, feathers::{ controls::{ button, checkbox, color_swatch, radio, slider, toggle_switch, virtual_keyboard, @@ -24,7 +25,6 @@ use bevy::{ ui::{Checked, InteractionDisabled}, winit::WinitSettings, }; -use bevy_ecs::relationship::{RelatedSpawner, RelatedSpawnerCommands}; fn main() { App::new() From 104e9481a5c5459bd8c6c411751d25628b5dc760 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Thu, 31 Jul 2025 13:11:08 +0100 Subject: [PATCH 03/11] use array instead of vec. --- examples/ui/virtual_keyboard.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/ui/virtual_keyboard.rs b/examples/ui/virtual_keyboard.rs index b5b8db48f7601..b7e934b209af8 100644 --- a/examples/ui/virtual_keyboard.rs +++ b/examples/ui/virtual_keyboard.rs @@ -59,7 +59,7 @@ fn setup(mut commands: Commands) { commands.spawn(Camera2d); let callback = commands.register_system(on_virtual_key_pressed); - let layout = vec![ + let layout = [ vec!["1", "2", "3", "4", "5", "6", "7", "8", "9", "0", ".", ","], vec!["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"], vec!["A", "S", "D", "F", "G", "H", "J", "K", "L", "'"], From 33784be045b97e3f7698401d9873f150ed81f274 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Thu, 31 Jul 2025 13:25:50 +0100 Subject: [PATCH 04/11] Cleaned up imports. --- examples/ui/virtual_keyboard.rs | 45 ++++++++++----------------------- 1 file changed, 14 insertions(+), 31 deletions(-) diff --git a/examples/ui/virtual_keyboard.rs b/examples/ui/virtual_keyboard.rs index b7e934b209af8..dc74760d19a96 100644 --- a/examples/ui/virtual_keyboard.rs +++ b/examples/ui/virtual_keyboard.rs @@ -1,28 +1,14 @@ //! Virtual keyboard example use bevy::{ - color::palettes::css::{NAVY, YELLOW}, - core_widgets::{ - Activate, Callback, CoreRadio, CoreRadioGroup, CoreWidgetsPlugins, SliderPrecision, - SliderStep, - }, - ecs::relationship::{RelatedSpawner, RelatedSpawnerCommands}, + color::palettes::css::NAVY, + core_widgets::{Activate, CoreWidgetsPlugins}, + ecs::relationship::RelatedSpawnerCommands, feathers::{ - controls::{ - button, checkbox, color_swatch, radio, slider, toggle_switch, virtual_keyboard, - ButtonProps, ButtonVariant, CheckboxProps, SliderProps, ToggleSwitchProps, - }, - dark_theme::create_dark_theme, - rounded_corners::RoundedCorners, - theme::{ThemeBackgroundColor, ThemedText, UiTheme}, - tokens, FeathersPlugin, - }, - input_focus::{ - tab_navigation::{TabGroup, TabNavigationPlugin}, - InputDispatchPlugin, + controls::virtual_keyboard, dark_theme::create_dark_theme, theme::UiTheme, FeathersPlugin, }, + input_focus::{tab_navigation::TabNavigationPlugin, InputDispatchPlugin}, prelude::*, - ui::{Checked, InteractionDisabled}, winit::WinitSettings, }; @@ -68,17 +54,14 @@ fn setup(mut commands: Commands) { vec!["left", "right", "up", "down", "home", "end"], ]; - let keys = layout - .into_iter() - .map(|row| { - row.into_iter() - .map(|label| { - let label_string = label.to_string(); - (label_string.clone(), VirtualKey(label_string)) - }) - .collect() - }) - .collect(); + let keys_iter = layout.into_iter().map(|row| { + row.into_iter() + .map(|label| { + let label_string = label.to_string(); + (label_string.clone(), VirtualKey(label_string)) + }) + .collect() + }); commands .spawn(Node { @@ -106,7 +89,7 @@ fn setup(mut commands: Commands) { )) .with_children(|parent: &mut RelatedSpawnerCommands| { parent.spawn(Text::new("virtual keyboard")); - parent.spawn(virtual_keyboard(keys, callback)); + parent.spawn(virtual_keyboard(keys_iter, callback)); }); }); } From 6b24202600ea6757c9e39a1369997f9968963124 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Thu, 31 Jul 2025 13:29:18 +0100 Subject: [PATCH 05/11] cargo run -p build-templated-pages -- update examples --- examples/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/README.md b/examples/README.md index 29420e66c53ac..38c73bd3e60b5 100644 --- a/examples/README.md +++ b/examples/README.md @@ -584,6 +584,7 @@ Example | Description [UI Z-Index](../examples/ui/z_index.rs) | Demonstrates how to control the relative depth (z-position) of UI elements [Viewport Debug](../examples/ui/viewport_debug.rs) | An example for debugging viewport coordinates [Viewport Node](../examples/ui/viewport_node.rs) | Demonstrates how to create a viewport node with picking support +[Virtual Keyboard](../examples/ui/virtual_keyboard.rs) | Example demonstrating a virtual keyboard widget [Window Fallthrough](../examples/ui/window_fallthrough.rs) | Illustrates how to access `winit::window::Window`'s `hittest` functionality. ## Usage From 27adbfa2e11b4db119827d168998e7f3a8784ffc Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Thu, 31 Jul 2025 13:34:28 +0100 Subject: [PATCH 06/11] Added release note --- release-content/release-notes/virtual_keyboard_widget.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 release-content/release-notes/virtual_keyboard_widget.md diff --git a/release-content/release-notes/virtual_keyboard_widget.md b/release-content/release-notes/virtual_keyboard_widget.md new file mode 100644 index 0000000000000..dc20b3ad95027 --- /dev/null +++ b/release-content/release-notes/virtual_keyboard_widget.md @@ -0,0 +1,7 @@ +--- +title: Virtual keyboard widget +authors: ["@ickshonpe"] +pull_requests: [20350] +--- + +Adds a new function `virtual_keyboard` to `bevy_feathers` that can be used to spawn an onscreen interactable keyboard. \ No newline at end of file From b17ed5afc8e70f40b68d96e9181756f73baa2198 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Thu, 31 Jul 2025 13:54:05 +0100 Subject: [PATCH 07/11] Fixed release note formatting. --- release-content/release-notes/virtual_keyboard_widget.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release-content/release-notes/virtual_keyboard_widget.md b/release-content/release-notes/virtual_keyboard_widget.md index dc20b3ad95027..f8293baff6429 100644 --- a/release-content/release-notes/virtual_keyboard_widget.md +++ b/release-content/release-notes/virtual_keyboard_widget.md @@ -4,4 +4,4 @@ authors: ["@ickshonpe"] pull_requests: [20350] --- -Adds a new function `virtual_keyboard` to `bevy_feathers` that can be used to spawn an onscreen interactable keyboard. \ No newline at end of file +Adds a new function `virtual_keyboard` to `bevy_feathers` that can be used to spawn an onscreen interactable keyboard. From 5f0406691958b48f8b3bd88f9cd5ba7db60d6907 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Thu, 31 Jul 2025 14:01:06 +0100 Subject: [PATCH 08/11] updated release note --- release-content/release-notes/virtual_keyboard_widget.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release-content/release-notes/virtual_keyboard_widget.md b/release-content/release-notes/virtual_keyboard_widget.md index f8293baff6429..9ed89d6c1a142 100644 --- a/release-content/release-notes/virtual_keyboard_widget.md +++ b/release-content/release-notes/virtual_keyboard_widget.md @@ -4,4 +4,4 @@ authors: ["@ickshonpe"] pull_requests: [20350] --- -Adds a new function `virtual_keyboard` to `bevy_feathers` that can be used to spawn an onscreen interactable keyboard. +A new function `virtual_keyboard` has been added to `bevy_feathers` that can be used to spawn an onscreen interactable keyboard. From 366065ba0ad9e35b841e781aec2a27fa5746c3e2 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Thu, 31 Jul 2025 19:06:49 +0100 Subject: [PATCH 09/11] Added the virtual keyboard to the bevy feathers release note --- release-content/release-notes/feathers.md | 5 +++-- release-content/release-notes/virtual_keyboard_widget.md | 7 ------- 2 files changed, 3 insertions(+), 9 deletions(-) delete mode 100644 release-content/release-notes/virtual_keyboard_widget.md diff --git a/release-content/release-notes/feathers.md b/release-content/release-notes/feathers.md index b8d4ca06f07cf..14ec4d265777d 100644 --- a/release-content/release-notes/feathers.md +++ b/release-content/release-notes/feathers.md @@ -1,7 +1,7 @@ --- title: Bevy Feathers -authors: ["@viridia", "@Atlas16A"] -pull_requests: [19730, 19900, 19928, 20237, 20169] +authors: ["@viridia", "@Atlas16A", "@ickshonpe"] +pull_requests: [19730, 19900, 19928, 20237, 20169, 20350] --- To make it easier for Bevy engine developers and third-party tool creators to make comfortable, visually cohesive tooling, @@ -15,6 +15,7 @@ we're pleased to introduce "Feathers" - a comprehensive widget set that offers: - Robust theming support ensuring consistent visual styling across applications - Accessibility features with built-in screen reader and assistive technology support - Interactive cursor behavior that changes appropriately when hovering over widgets +- A virtual keyboard suitable for touchscreen and controller-based text input. Feathers isn't meant as a toolkit for building exciting and cool game UIs: it has a somewhat plain and utilitarian look and feel suitable for editors and graphical utilities. That being said, using diff --git a/release-content/release-notes/virtual_keyboard_widget.md b/release-content/release-notes/virtual_keyboard_widget.md deleted file mode 100644 index 9ed89d6c1a142..0000000000000 --- a/release-content/release-notes/virtual_keyboard_widget.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: Virtual keyboard widget -authors: ["@ickshonpe"] -pull_requests: [20350] ---- - -A new function `virtual_keyboard` has been added to `bevy_feathers` that can be used to spawn an onscreen interactable keyboard. From bfd0dc91ca5e83eaf4ec750581acf97ca0ad7b82 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Thu, 31 Jul 2025 19:07:39 +0100 Subject: [PATCH 10/11] Removed full stop --- release-content/release-notes/feathers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release-content/release-notes/feathers.md b/release-content/release-notes/feathers.md index 14ec4d265777d..241b3a0ef6cc9 100644 --- a/release-content/release-notes/feathers.md +++ b/release-content/release-notes/feathers.md @@ -15,7 +15,7 @@ we're pleased to introduce "Feathers" - a comprehensive widget set that offers: - Robust theming support ensuring consistent visual styling across applications - Accessibility features with built-in screen reader and assistive technology support - Interactive cursor behavior that changes appropriately when hovering over widgets -- A virtual keyboard suitable for touchscreen and controller-based text input. +- A virtual keyboard suitable for touchscreen and controller-based text input Feathers isn't meant as a toolkit for building exciting and cool game UIs: it has a somewhat plain and utilitarian look and feel suitable for editors and graphical utilities. That being said, using From 6b1c8ec45c63e4465d848389a437b85c2f59ccb0 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Thu, 31 Jul 2025 19:09:24 +0100 Subject: [PATCH 11/11] release note edit --- release-content/release-notes/feathers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release-content/release-notes/feathers.md b/release-content/release-notes/feathers.md index 241b3a0ef6cc9..bf21ba16bc725 100644 --- a/release-content/release-notes/feathers.md +++ b/release-content/release-notes/feathers.md @@ -15,7 +15,7 @@ we're pleased to introduce "Feathers" - a comprehensive widget set that offers: - Robust theming support ensuring consistent visual styling across applications - Accessibility features with built-in screen reader and assistive technology support - Interactive cursor behavior that changes appropriately when hovering over widgets -- A virtual keyboard suitable for touchscreen and controller-based text input +- A virtual keyboard suitable for touchscreen text input Feathers isn't meant as a toolkit for building exciting and cool game UIs: it has a somewhat plain and utilitarian look and feel suitable for editors and graphical utilities. That being said, using