Skip to content

Instantly share code, notes, and snippets.

@dmlary
Last active August 18, 2023 23:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dmlary/a40e29de0e9ec78950bb5f352115710a to your computer and use it in GitHub Desktop.
Save dmlary/a40e29de0e9ec78950bb5f352115710a to your computer and use it in GitHub Desktop.
Bevy v0.10 SystemParam-based egui Widgets
/// implemention of SystemParam-based egui Widgets for bevy 0.10
/// adapted from: https://github.com/bevyengine/bevy/discussions/5522
///
/// Effectively widgets can access world components/resources similar
/// to systems.
#![allow(clippy::type_complexity)]
use bevy::ecs::system::{SystemParam, SystemState};
use bevy::prelude::*;
use bevy_egui::egui;
use bevy_inspector_egui::quick::WorldInspectorPlugin;
use std::collections::HashMap;
use std::marker::PhantomData;
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
title: "scene_aabb".to_string(),
..default()
}),
..default()
}))
.add_plugin(WorldInspectorPlugin::new())
.add_startup_system(setup)
.add_system(draw_ui)
.run();
}
fn setup(mut commands: Commands) {
for i in 0..10 {
commands.spawn(Tileset { id: i });
}
}
#[derive(Component, Default)]
struct Tileset {
id: usize,
}
pub trait WidgetSystem: SystemParam {
fn system(world: &mut World, state: &mut SystemState<Self>, ui: &mut egui::Ui, id: egui::Id);
}
pub fn widget<S: 'static + WidgetSystem>(world: &mut World, ui: &mut egui::Ui, id: egui::Id) {
// We need to cache `SystemState` to allow for a system's locally tracked state
if !world.contains_resource::<StateInstances<S>>() {
// Note, this message should only appear once! If you see it twice in the logs, the function
// may have been called recursively, and will panic.
debug!("Init system state {}", std::any::type_name::<S>());
world.insert_resource(StateInstances::<S> {
instances: HashMap::new(),
});
}
world.resource_scope(|world, mut states: Mut<StateInstances<S>>| {
if !states.instances.contains_key(&id) {
debug!(
"Registering system state for widget {id:?} of type {}",
std::any::type_name::<S>()
);
states.instances.insert(id, SystemState::new(world));
}
let cached_state = states.instances.get_mut(&id).unwrap();
S::system(world, cached_state, ui, id);
cached_state.apply(world);
});
}
/// A UI widget may have multiple instances. We need to ensure the local state of these instances is
/// not shared. This hashmap allows us to dynamically store instance states.
#[derive(Default, Resource)]
struct StateInstances<T: 'static + WidgetSystem> {
instances: HashMap<egui::Id, SystemState<T>>,
}
pub fn with_world_and_egui_context<T>(
world: &mut World,
f: impl FnOnce(&mut World, egui::Context) -> T,
) -> T {
use bevy::window::PrimaryWindow;
use bevy_egui::EguiContext;
let mut state = world.query_filtered::<Entity, (With<EguiContext>, With<PrimaryWindow>)>();
let entity = state.single(world);
let mut egui_context = world.get_mut::<EguiContext>(entity).unwrap();
let ctx = egui_context.get_mut().clone();
f(world, ctx)
}
fn draw_ui(world: &mut World) {
with_world_and_egui_context(world, |world, ctx| {
egui::Window::new("window").show(&ctx, |ui| {
widget::<TilesetList>(world, ui, ui.id().with("list"));
});
});
}
#[derive(SystemParam)]
pub struct TilesetList<'w, 's> {
tilesets: Query<'w, 's, &'static Tileset>,
}
impl<'w, 's> WidgetSystem for TilesetList<'w, 's> {
fn system(world: &mut World, state: &mut SystemState<Self>, ui: &mut egui::Ui, _id: egui::Id) {
let params = state.get(world);
for tileset in &params.tilesets {
ui.label(format!("tileset {}", tileset.id));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment