Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3594,6 +3594,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"
Expand Down
2 changes: 2 additions & 0 deletions crates/bevy_feathers/src/controls/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ 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};
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;

Expand Down
56 changes: 56 additions & 0 deletions crates/bevy_feathers/src/controls/virtual_keyboard.rs
Original file line number Diff line number Diff line change
@@ -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<T>(
keys: impl Iterator<Item = Vec<(String, T)>> + Send + Sync + 'static,
on_key_press: SystemId<In<Activate>>,
) -> 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<ChildOf>| {
for row in keys {
parent.spawn((
Node {
flex_direction: FlexDirection::Row,
column_gap: Val::Px(4.),
..Default::default()
},
Children::spawn(SpawnWith(move |parent: &mut RelatedSpawner<ChildOf>| {
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)),
));
}
})),
));
}
}),)),
)
}
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
95 changes: 95 additions & 0 deletions examples/ui/virtual_keyboard.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//! Virtual keyboard example

use bevy::{
color::palettes::css::NAVY,
core_widgets::{Activate, CoreWidgetsPlugins},
ecs::relationship::RelatedSpawnerCommands,
feathers::{
controls::virtual_keyboard, dark_theme::create_dark_theme, theme::UiTheme, FeathersPlugin,
},
input_focus::{tab_navigation::TabNavigationPlugin, InputDispatchPlugin},
prelude::*,
winit::WinitSettings,
};

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<Activate>,
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!["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_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 {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
align_items: AlignItems::End,
justify_content: JustifyContent::Center,
..default()
})
.with_children(|parent: &mut RelatedSpawnerCommands<ChildOf>| {
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<ChildOf>| {
parent.spawn(Text::new("virtual keyboard"));
parent.spawn(virtual_keyboard(keys_iter, callback));
});
});
}
5 changes: 3 additions & 2 deletions release-content/release-notes/feathers.md
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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 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
Expand Down
Loading