bevy_gooey
Game interfaces made easy for Bevy 0.20: a skin, ready-made game widgets and keyboard and gamepad
navigation, built on Bevy’s own headless widgets (bevy_ui_widgets) and input focus
(bevy_input_focus). A menu is a few nested calls, every widget looks like one family, and a pad or a
keyboard reaches everything a mouse does — with the WCAG 2.2 contrast and target-size checks the crate
runs on your own skin.
Add it
cargo add bevy_gooey
use bevy::prelude::*;
use bevy_gooey::*;
fn main() {
App::new()
.add_plugins((DefaultPlugins, GooeyPlugins))
.add_systems(Startup, setup)
.run();
}
fn setup(mut commands: Commands) {
commands.spawn(Camera2d);
commands.spawn((
screen(),
children![(
panel(),
children![
heading("Paused"),
(
menu(),
children![
(button("Resume"), observe(|_: On<Activate>| info!("resume"))),
button("Quit"),
]
),
]
)],
));
}
Or run the gallery first: cargo run --example gallery.
What it gives you
- A skin as data (skins):
GooeySkinholds every colour and size, with thedarkandparchmentpresets andproblems(), which checks WCAG 2.2 contrast and target size. Change it and everything restyles that frame. Fonts live inGooeyFonts, never in the skin. - Every widget a game screen needs (the widget set):
screen,panel,menu,heading,label,button,meter(a HUD bar),toggle,choice,slider,list,toast,toast_alert,Tooltipand the modaldialog— each a free function returning a bundle, so they compose inchildren![]and withobserve. - Settings controls that are the widget (controls):
a
toggle,choiceandslidereach root on Bevy’s ownCheckbox,ButtonandSlider, so the entity you spawn carries the value, takesobserveand says each change once inValueChange. - A list that scrolls and follows focus (lists):
list(4.5, children![..])shows four rows and a half, cuts the fifth so the player sees there is more, and keeps the next row in sight as the arrows walk down it. - Navigation with no mode to enter (focus): arrows and the D-pad move focus to the nearest widget, Enter, Space and South activate, Tab cycles, an open dialog holds focus, and the focus ring appears the moment a key is pressed.
- Toasts that read at the player’s pace (toasts):
commands.spawn(toast("Game saved"))and it stacks, times itself to its own length and ages on real time, so a paused game’s menu still lets it go. - One interface at every resolution (scale):
GooeyScalePluginkeeps the skin’s sizes the same share of the screen, and every border is a whole number of device pixels at any scale. - A pointer that passes through a HUD (over a game world):
the empty part of a
screenisPickable::IGNORE, so a click on the world under a HUD still reaches the world.
Use it in the Cauldron
The Cauldron, the Coven’s level editor, takes bevy_gooey as an optional cargo feature (bevy_gooey) and
a project lists it in its family.ron. It adds the crate’s GooeyStylePlugin (not GooeyNavPlugin:
the editor owns the arrow keys) and reads a project’s skin from <project>/family/bevy_gooey.ron, which
is the crate’s own GooeySkin type written as RON — the same file a game ships:
(accent: Srgba((red: 0.2, green: 0.6, blue: 0.9, alpha: 1.0)), text_scale: 1.25)
GooeySkin is #[serde(default)], so a partial file is the default skin with only that field changed.
Editing the file live swaps the running GooeySkin resource and the crate’s own apply_skin restyles
every widget that frame; a file that is not a skin is a problem on the status line and the last good
skin stays; a skin that breaks the crate’s own rules is applied and each broken rule
(GooeySkin::problems()) is a problem. Removing the file puts the default skin back.
There is no panel for the skin yet. One would edit GooeySkin’s fields — the colours (background,
surface, surface_raised, text, text_muted, accent, accent_text, danger, focus_ring,
outline, scrim), the sizes (text_size, heading_size, gap, padding, radius, border,
meter_height, min_target) and the player’s text_scale — with problems() listed beside them, and
it would read UiScaleSettings { reference_short_side, scale } and ToastSettings { anchor, max_visible, base_seconds, chars_per_second, duration_scale, fade_seconds } the same way: each is
serde data, so each is one line of the project’s family/ folder.