mirror of
https://github.com/storytold/storyteller-bevy.git
synced 2026-10-17 00:13:17 +00:00
Inspector: component view part (#55)
This PR allow to send entity components data to typescript with inspector-dom event Event data  Component register example  Read event example  This PR is the first step of new inspector backend logic Next parts is: - Allow to edit components fields - Allow to show asset data (like show Handle<StandartMaterial> data) - Allow to edit asset fields
This commit is contained in:
+1
-1
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "1.76.0"
|
||||
channel = "stable"
|
||||
|
||||
@@ -40,6 +40,13 @@ export class AppElement extends LitElement {
|
||||
return this;
|
||||
}
|
||||
|
||||
@on("inspector-changed")
|
||||
onInspectorChanged(event: studio.InspectorChanged): void {
|
||||
const data = event.detail ? JSON.parse(event.detail) : null;
|
||||
console.log("Inspector changed:", data);
|
||||
}
|
||||
|
||||
|
||||
@on("scene-saved")
|
||||
async onSceneSaved({ detail: scene }: SceneSavedEvent): Promise<void> {
|
||||
const file = new File([scene], "scene.scn.ron", { type: "text/ron" });
|
||||
|
||||
@@ -1 +1 @@
|
||||
import "./app/app.element";
|
||||
import "./app/app.element";
|
||||
@@ -0,0 +1,216 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use bevy::{
|
||||
prelude::*,
|
||||
reflect::{serde::ReflectSerializer, GetTypeRegistration},
|
||||
utils::HashMap,
|
||||
window::PrimaryWindow,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::*;
|
||||
use space_editor::space_prefab::component::MeshPrimitivePrefab;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::{interaction::Selected, wasm};
|
||||
|
||||
pub struct InspectorPlugin;
|
||||
|
||||
impl Plugin for InspectorPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.init_resource::<EntityDescription>();
|
||||
app.add_event::<UpdateEntityDescription>();
|
||||
|
||||
app.configure_sets(
|
||||
Update,
|
||||
(
|
||||
InspectorSystem::DetectChanges,
|
||||
InspectorSystem::PerComponentCollect,
|
||||
InspectorSystem::CollectChanges,
|
||||
)
|
||||
.chain(),
|
||||
);
|
||||
|
||||
app.add_systems(
|
||||
Update,
|
||||
update_description.in_set(InspectorSystem::CollectChanges),
|
||||
);
|
||||
|
||||
app.register_inspect::<Transform>();
|
||||
app.register_inspect::<Name>();
|
||||
app.register_inspect::<MeshPrimitivePrefab>();
|
||||
}
|
||||
}
|
||||
|
||||
trait AppInspectable {
|
||||
fn register_inspect<T: Component + Reflect + GetTypeRegistration>(&mut self);
|
||||
}
|
||||
|
||||
impl AppInspectable for App {
|
||||
fn register_inspect<T: Component + Reflect + GetTypeRegistration>(&mut self) {
|
||||
self.register_type::<T>()
|
||||
.add_event::<UpdateInspect<T>>()
|
||||
.add_systems(
|
||||
Update,
|
||||
(
|
||||
update_inspect_by_select_change::<T>,
|
||||
update_inspect_by_remove::<T>,
|
||||
update_inspect_by_change::<T>,
|
||||
)
|
||||
.in_set(InspectorSystem::DetectChanges),
|
||||
)
|
||||
.add_systems(
|
||||
Update,
|
||||
auto_serialize_system::<T>.in_set(InspectorSystem::PerComponentCollect),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(SystemSet, Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
pub enum InspectorSystem {
|
||||
DetectChanges,
|
||||
PerComponentCollect,
|
||||
CollectChanges,
|
||||
}
|
||||
|
||||
#[derive(Event, Clone, Copy)]
|
||||
pub struct UpdateInspect<T> {
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
#[derive(Event)]
|
||||
pub struct UpdateEntityDescription;
|
||||
|
||||
impl<T> Default for UpdateInspect<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource, Serialize, Deserialize, Default, Clone)]
|
||||
pub struct EntityDescription {
|
||||
pub components: HashMap<String, Value>,
|
||||
}
|
||||
|
||||
fn update_inspect_by_select_change<T: Component + Reflect + GetTypeRegistration>(
|
||||
mut ev_inspect: EventWriter<UpdateInspect<T>>,
|
||||
q_added: Query<(), Added<Selected>>,
|
||||
q_removed: RemovedComponents<Selected>,
|
||||
) {
|
||||
if !q_added.is_empty() {
|
||||
ev_inspect.send(UpdateInspect::<T>::default());
|
||||
}
|
||||
|
||||
if !q_removed.is_empty() {
|
||||
ev_inspect.send(UpdateInspect::<T>::default());
|
||||
}
|
||||
}
|
||||
|
||||
fn update_inspect_by_remove<T: Component + Reflect + GetTypeRegistration>(
|
||||
mut ev_inspect: EventWriter<UpdateInspect<T>>,
|
||||
mut q_t_removed: RemovedComponents<T>,
|
||||
) {
|
||||
if let Some(_t) = q_t_removed.read().next() {
|
||||
ev_inspect.send(UpdateInspect::<T>::default());
|
||||
}
|
||||
}
|
||||
|
||||
fn update_inspect_by_change<T: Component + Reflect + GetTypeRegistration>(
|
||||
mut ev_inspect: EventWriter<UpdateInspect<T>>,
|
||||
q_t_changed: Query<&T, (With<Selected>, Changed<T>)>,
|
||||
) {
|
||||
if q_t_changed.iter().next().is_some() {
|
||||
ev_inspect.send(UpdateInspect::<T>::default());
|
||||
}
|
||||
}
|
||||
|
||||
fn auto_serialize_system<T: Component + Reflect + GetTypeRegistration>(
|
||||
q_t: Query<&T, With<Selected>>,
|
||||
mut description: ResMut<EntityDescription>,
|
||||
registry: Res<AppTypeRegistry>,
|
||||
mut ev_inspect: EventReader<UpdateInspect<T>>,
|
||||
mut ev_update_description: EventWriter<UpdateEntityDescription>,
|
||||
) {
|
||||
if ev_inspect.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
ev_inspect.clear();
|
||||
|
||||
if let Some(t) = q_t.iter().next() {
|
||||
let registry = registry.read();
|
||||
let json = serde_json::to_value(ReflectSerializer::new(t, ®istry)).unwrap();
|
||||
info!("json: {:?}", json.to_string());
|
||||
description.components.insert(
|
||||
T::get_type_registration()
|
||||
.type_info()
|
||||
.type_path()
|
||||
.to_string(),
|
||||
json,
|
||||
);
|
||||
} else {
|
||||
description
|
||||
.components
|
||||
.remove(T::get_type_registration().type_info().type_path());
|
||||
}
|
||||
|
||||
ev_update_description.send(UpdateEntityDescription);
|
||||
}
|
||||
|
||||
fn update_description(
|
||||
description: ResMut<EntityDescription>,
|
||||
mut ev_update_description: EventReader<UpdateEntityDescription>,
|
||||
mut q_windows: Query<&Window, With<PrimaryWindow>>,
|
||||
) {
|
||||
if ev_update_description.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
ev_update_description.clear();
|
||||
|
||||
let values = description
|
||||
.components
|
||||
.iter()
|
||||
.map(|(name, v)| {
|
||||
let mut value = Value::Object(Map::new());
|
||||
value
|
||||
.as_object_mut()
|
||||
.unwrap()
|
||||
.insert("name".to_string(), Value::String(name.to_string()));
|
||||
value
|
||||
.as_object_mut()
|
||||
.unwrap()
|
||||
.insert("value".to_string(), v[name].clone());
|
||||
value
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
//Write pretty json
|
||||
// info!("description: {}", serde_json::to_string_pretty(&values).unwrap());
|
||||
|
||||
if let Some(window) = q_windows.iter_mut().next() {
|
||||
notify_changed(window, serde_json::to_string_pretty(&values).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(typescript_custom_section)]
|
||||
const EVENT_DECLARATION: &str = r#"
|
||||
|
||||
export interface InspectorChanged extends CustomEvent {
|
||||
detail: string | null;
|
||||
}
|
||||
|
||||
declare global {
|
||||
export interface HTMLElementEventMap {
|
||||
"inspector-changed": InspectorChanged;
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
pub(super) fn notify_changed(q_window: &Window, msg: String) {
|
||||
let res = wasm::dispatch_to_js(q_window, "inspector-changed", &msg);
|
||||
if let Err(e) = res {
|
||||
error!("Failed to dispatch inspector-changed: {}", e);
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,16 @@ use self::fps::FpsCounterPlugin;
|
||||
|
||||
mod fps;
|
||||
|
||||
#[cfg(feature = "wasm")]
|
||||
pub mod inspector;
|
||||
|
||||
pub struct UiLayerPlugin;
|
||||
|
||||
impl Plugin for UiLayerPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_plugins(FpsCounterPlugin);
|
||||
|
||||
#[cfg(feature = "wasm")]
|
||||
app.add_plugins(inspector::InspectorPlugin);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ export interface ITransform {
|
||||
}
|
||||
export type Transform = BevyObject<"Transform", ITransform>;
|
||||
export type GlobalTransform = BevyObject<"GlobalTransform", ITransform>;
|
||||
|
||||
|
||||
"#;
|
||||
|
||||
pub(crate) const SERIALIZER: Serializer = Serializer::new()
|
||||
|
||||
Reference in New Issue
Block a user