mirror of
https://github.com/storytold/storyteller-bevy.git
synced 2026-10-25 08:30:24 +00:00
Material base_color editing support
This commit is contained in:
@@ -3,9 +3,10 @@ import { LitElement, type PropertyValues, html, unsafeCSS } from "lit";
|
|||||||
import { customElement } from "lit/decorators.js";
|
import { customElement } from "lit/decorators.js";
|
||||||
|
|
||||||
import { SCENE_DATA_PROVIDER, type SceneDataProvider } from "../scene-data-provider.element";
|
import { SCENE_DATA_PROVIDER, type SceneDataProvider } from "../scene-data-provider.element";
|
||||||
import { type InspectorComponent } from "./inspector.types";
|
import { isLinear, type InspectorComponent } from "./inspector.types";
|
||||||
|
|
||||||
import "../scene-data-provider.element";
|
import "../scene-data-provider.element";
|
||||||
|
import "./material-inspector.element";
|
||||||
import "./name-inspector.element";
|
import "./name-inspector.element";
|
||||||
import "./transform-inspector.element";
|
import "./transform-inspector.element";
|
||||||
|
|
||||||
@@ -48,13 +49,14 @@ export class EntityInspectorElement extends LitElement {
|
|||||||
})
|
})
|
||||||
.map(({ name, value }) => {
|
.map(({ name, value }) => {
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case "bevy_core::name::Name":
|
case "bevy_core::name::Name": {
|
||||||
return html`
|
return html`
|
||||||
<sts-name-inspector
|
<sts-name-inspector
|
||||||
.value=${value.name}
|
.value=${value.name}
|
||||||
></sts-name-inspector>
|
></sts-name-inspector>
|
||||||
`;
|
`;
|
||||||
case "bevy_transform::components::transform::Transform":
|
}
|
||||||
|
case "bevy_transform::components::transform::Transform": {
|
||||||
return html`
|
return html`
|
||||||
<sts-transform-inspector
|
<sts-transform-inspector
|
||||||
.translation=${value.translation}
|
.translation=${value.translation}
|
||||||
@@ -62,11 +64,25 @@ export class EntityInspectorElement extends LitElement {
|
|||||||
.scale=${value.scale}
|
.scale=${value.scale}
|
||||||
></sts-transform-inspector>
|
></sts-transform-inspector>
|
||||||
`;
|
`;
|
||||||
default:
|
}
|
||||||
|
case "bevy_pbr::pbr_material::StandardMaterial": {
|
||||||
|
const { colorSpace, baseColor } = isLinear(value.base_color)
|
||||||
|
? { colorSpace: "linear", baseColor: value.base_color.RgbaLinear }
|
||||||
|
: { colorSpace: "srgb", baseColor: value.base_color.Rgba };
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<sts-material-inspector
|
||||||
|
.colorSpace=${colorSpace}
|
||||||
|
.baseColor=${baseColor}
|
||||||
|
></sts-material-inspector>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
return html`
|
return html`
|
||||||
<h3>Unknown Component: ${name}</h3>
|
<h3>Unknown Component: ${name}</h3>
|
||||||
<pre>${JSON.stringify({ name, value }, null, " ")}</pre>
|
<pre>${JSON.stringify({ name, value }, null, " ")}</pre>
|
||||||
`;
|
`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export type InspectorComponent
|
export type InspectorComponent
|
||||||
= BevyReflect<"bevy_transform::components::transform::Transform", BevyTransform>
|
= BevyReflect<"bevy_transform::components::transform::Transform", BevyTransform>
|
||||||
| BevyReflect<"bevy_core::name::Name", BevyName>
|
| BevyReflect<"bevy_core::name::Name", BevyName>
|
||||||
|
| BevyReflect<"bevy_pbr::pbr_material::StandardMaterial", StandardMaterial>
|
||||||
;
|
;
|
||||||
|
|
||||||
export interface BevyReflect<K extends string, T> {
|
export interface BevyReflect<K extends string, T> {
|
||||||
@@ -31,3 +32,31 @@ export interface Vec3 {
|
|||||||
y: number;
|
y: number;
|
||||||
z: number;
|
z: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface StandardMaterial {
|
||||||
|
base_color: BevyColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BevyColor
|
||||||
|
= Color_RgbaLinear
|
||||||
|
| Color_Rgba
|
||||||
|
;
|
||||||
|
|
||||||
|
export interface Color_RgbaLinear {
|
||||||
|
RgbaLinear: Rgba;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Color_Rgba {
|
||||||
|
Rgba: Rgba;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Rgba {
|
||||||
|
red: number;
|
||||||
|
green: number;
|
||||||
|
blue: number;
|
||||||
|
alpha: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isLinear(color: BevyColor): color is Color_RgbaLinear {
|
||||||
|
return "RgbaLinear" in color;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
@use "sass:math";
|
||||||
|
|
||||||
|
:host {
|
||||||
|
color-scheme: dark;
|
||||||
|
font-family: "Fira Sans", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
*, *::before, *::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-weight: 500;
|
||||||
|
margin: 0 0 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
color: #FFF9;
|
||||||
|
font-size: math.div(14, 16) * 1rem;
|
||||||
|
margin-block-end: 0.333rem;
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
import * as studio from "@storyteller/studio";
|
||||||
|
import { match } from "@storyteller/utility";
|
||||||
|
import { LitElement, html, unsafeCSS } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators.js";
|
||||||
|
import { v4 as uuid } from "uuid";
|
||||||
|
|
||||||
|
import { Rgba } from "./inspector.types";
|
||||||
|
|
||||||
|
import styles from "./material-inspector.element.scss?inline";
|
||||||
|
|
||||||
|
@customElement("sts-material-inspector")
|
||||||
|
export class MaterialInspectorElement extends LitElement {
|
||||||
|
static override styles = unsafeCSS(styles);
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
baseColor: Rgba = { red: 1, green: 1, blue: 1, alpha: 1 };
|
||||||
|
|
||||||
|
@property()
|
||||||
|
inputColorSpace: "linear"|"srgb" = "linear";
|
||||||
|
|
||||||
|
#baseColorId = uuid();
|
||||||
|
|
||||||
|
onBaseColorInput(event: InputEvent): void {
|
||||||
|
const hex = (event.target as HTMLInputElement).value;
|
||||||
|
const value = match (this.inputColorSpace, {
|
||||||
|
"linear": () => RgbaLinear.fromHexSrgb(hex, this.baseColor.alpha),
|
||||||
|
"srgb": () => Srgba.fromHex(hex, this.baseColor.alpha),
|
||||||
|
});
|
||||||
|
|
||||||
|
studio.updateComponentField({
|
||||||
|
component: "bevy_pbr::pbr_material::StandardMaterial",
|
||||||
|
reflectPath: "base_color",
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override render() {
|
||||||
|
const [rgb, _] = match (this.inputColorSpace, {
|
||||||
|
"linear": () => RgbaLinear.toHexSrgb(this.baseColor),
|
||||||
|
"srgb": () => Srgba.toHex(this.baseColor),
|
||||||
|
});
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<h4>Material</h4>
|
||||||
|
<label for=${this.#baseColorId}>
|
||||||
|
Tint
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id=${this.#baseColorId}
|
||||||
|
type="color"
|
||||||
|
.value=${rgb}
|
||||||
|
@input=${this.onBaseColorInput}
|
||||||
|
/>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const HEX_PATTERN = /#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})?/i;
|
||||||
|
const HEX_BASE = 16;
|
||||||
|
const U8_MAX = 255;
|
||||||
|
|
||||||
|
function channelToHex(gamma: number) {
|
||||||
|
return (channel: number): string => {
|
||||||
|
return Math
|
||||||
|
.round(Math.pow(channel, gamma) * U8_MAX)
|
||||||
|
.toString(HEX_BASE)
|
||||||
|
.padStart(2, "0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function channelFromHex(gamma: number) {
|
||||||
|
return (channel: string): number => {
|
||||||
|
return Math.pow(parseInt(channel, HEX_BASE) / U8_MAX, gamma);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace RgbaLinear {
|
||||||
|
export function toHexSrgb({ red, green, blue, alpha }: Rgba): [string, number] {
|
||||||
|
const hexRgb = [red, green, blue]
|
||||||
|
.map(channelToHex(1 / 2.2))
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
return [`#${hexRgb}`, alpha];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fromHexSrgb(hex: string, alpha = 1): Rgba {
|
||||||
|
const [red, green, blue] = hex
|
||||||
|
.match(HEX_PATTERN)!
|
||||||
|
.slice(1)
|
||||||
|
.map(channelFromHex(2.2));
|
||||||
|
|
||||||
|
return { red, green, blue, alpha };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Srgba {
|
||||||
|
export function toHex({ red, green, blue, alpha }: Rgba): [string, number] {
|
||||||
|
const hexRgb = [red, green, blue]
|
||||||
|
.map(channelToHex(2.2))
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
return [`#${hexRgb}`, alpha];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fromHex(hex: string, alpha = 1): Rgba {
|
||||||
|
const [red, green, blue] = hex
|
||||||
|
.match(HEX_PATTERN)!
|
||||||
|
.slice(1)
|
||||||
|
.map(channelFromHex(1 / 2.2));
|
||||||
|
|
||||||
|
return { red, green, blue, alpha };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,7 +34,7 @@ export class TransformInspectorElement extends LitElement {
|
|||||||
protected override render = () => html`
|
protected override render = () => html`
|
||||||
<h4>Transform</h4>
|
<h4>Transform</h4>
|
||||||
<div class="fieldset" role="group">
|
<div class="fieldset" role="group">
|
||||||
<legend>Translation</legend>
|
<legend>Position</legend>
|
||||||
<sts-text-field
|
<sts-text-field
|
||||||
type="number"
|
type="number"
|
||||||
.value=${this.translation.x.toFixed(3)}
|
.value=${this.translation.x.toFixed(3)}
|
||||||
|
|||||||
+181
-59
@@ -48,7 +48,9 @@ impl Plugin for InspectorPlugin {
|
|||||||
|
|
||||||
app.add_systems(
|
app.add_systems(
|
||||||
Update,
|
Update,
|
||||||
update_field_by_ui.in_set(InspectorSystem::WriteChanges),
|
update_field_by_ui
|
||||||
|
.map(bevy::utils::error)
|
||||||
|
.in_set(InspectorSystem::WriteChanges),
|
||||||
);
|
);
|
||||||
|
|
||||||
app.editor_tab_by_trait(
|
app.editor_tab_by_trait(
|
||||||
@@ -88,14 +90,28 @@ impl Plugin for InspectorPlugin {
|
|||||||
.type_path()
|
.type_path()
|
||||||
.to_string(),
|
.to_string(),
|
||||||
Arc::new(|entity, path, world, f| unsafe {
|
Arc::new(|entity, path, world, f| unsafe {
|
||||||
let handle = entity.get::<Handle<StandardMaterial>>().unwrap();
|
let handle = entity.get::<Handle<StandardMaterial>>().ok_or_else(|| {
|
||||||
|
format!(
|
||||||
|
"Failed to get StandardMaterial for entity {:?}",
|
||||||
|
entity.id()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
let mut assets = world
|
let mut assets = world
|
||||||
.get_resource_mut::<Assets<StandardMaterial>>()
|
.get_resource_mut::<Assets<StandardMaterial>>()
|
||||||
.unwrap();
|
.ok_or_else(|| {
|
||||||
let material = assets.get_mut(handle).unwrap();
|
"Failed to get `Assets<StandadrMaterial>` resource".to_string()
|
||||||
|
})?;
|
||||||
|
|
||||||
let reflect_field = material.reflect_path_mut(path).unwrap();
|
let material = assets.get_mut(handle).ok_or_else(|| {
|
||||||
(f)(reflect_field);
|
format!("Failed to retrieve material from handle {handle:?}")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let reflect_field = material
|
||||||
|
.reflect_path_mut(path)
|
||||||
|
.map_err(|err| format!("{err}"))?;
|
||||||
|
|
||||||
|
(f)(reflect_field)
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -107,7 +123,12 @@ struct InspectGetters {
|
|||||||
getters: HashMap<
|
getters: HashMap<
|
||||||
String,
|
String,
|
||||||
Arc<
|
Arc<
|
||||||
dyn Fn(&mut EntityWorldMut, &str, &mut UnsafeWorldCell, &dyn Fn(&mut dyn Reflect))
|
dyn Fn(
|
||||||
|
&mut EntityWorldMut,
|
||||||
|
&str,
|
||||||
|
&mut UnsafeWorldCell,
|
||||||
|
&dyn Fn(&mut dyn Reflect) -> Result<(), String>,
|
||||||
|
) -> Result<(), String>
|
||||||
+ Send
|
+ Send
|
||||||
+ Sync,
|
+ Sync,
|
||||||
>,
|
>,
|
||||||
@@ -148,7 +169,7 @@ impl AppInspectable for App {
|
|||||||
Arc::new(|entity, path, _, f| {
|
Arc::new(|entity, path, _, f| {
|
||||||
let mut component = entity.get_mut::<T>().unwrap();
|
let mut component = entity.get_mut::<T>().unwrap();
|
||||||
let reflect_field = component.reflect_path_mut(path).unwrap();
|
let reflect_field = component.reflect_path_mut(path).unwrap();
|
||||||
(f)(reflect_field);
|
(f)(reflect_field)
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -223,94 +244,159 @@ impl<T> Default for UpdateInspect<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_field_by_ui(world: &mut World) {
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct Rgba {
|
||||||
|
red: f32,
|
||||||
|
green: f32,
|
||||||
|
blue: f32,
|
||||||
|
alpha: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_field_by_ui(world: &mut World) -> Result<(), String> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut unsafe_world = world.as_unsafe_world_cell();
|
let mut unsafe_world = world.as_unsafe_world_cell();
|
||||||
let Some(msg) = CHANGE_FROM_UI.take_message() else {
|
let Some(msg) = CHANGE_FROM_UI.take_message() else {
|
||||||
return;
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
info!("Processing update: {msg:?}");
|
||||||
|
|
||||||
let Some(selection) = unsafe_world.world_mut().get_resource::<Selection>() else {
|
let Some(selection) = unsafe_world.world_mut().get_resource::<Selection>() else {
|
||||||
return;
|
return Ok(());
|
||||||
};
|
};
|
||||||
let entity = **selection;
|
let entity = **selection;
|
||||||
|
|
||||||
let getters = unsafe_world.get_resource_mut::<InspectGetters>().unwrap();
|
let getters = unsafe_world.get_resource_mut::<InspectGetters>().unwrap();
|
||||||
|
let getter = getters
|
||||||
|
.getters
|
||||||
|
.get(&msg.component)
|
||||||
|
.ok_or_else(|| format!("Failed to find getter for component: {}", msg.component))?;
|
||||||
|
|
||||||
let mut entity_mut = unsafe_world.world_mut().entity_mut(entity);
|
// TODO: This is pretty ugly
|
||||||
|
let target = 'find_target: {
|
||||||
|
if &msg.component[..] == "bevy_pbr::pbr_material::StandardMaterial" {
|
||||||
|
let mut q_handles = unsafe_world
|
||||||
|
.world_mut()
|
||||||
|
.query::<&Handle<StandardMaterial>>();
|
||||||
|
|
||||||
let Some(getter) = getters.getters.get(&msg.component) else {
|
if q_handles.get(unsafe_world.world(), entity).is_ok() {
|
||||||
error!("Failed to find getter for component: {}", msg.component);
|
break 'find_target Some(entity);
|
||||||
return;
|
}
|
||||||
|
|
||||||
|
let mut q_children = unsafe_world.world_mut().query::<&Children>();
|
||||||
|
let Ok(ent_children) = q_children.get(unsafe_world.world(), entity) else {
|
||||||
|
break 'find_target None;
|
||||||
|
};
|
||||||
|
|
||||||
|
break 'find_target ent_children
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.find(|&child| q_handles.get(unsafe_world.world(), child).is_ok());
|
||||||
|
} else {
|
||||||
|
break 'find_target Some(entity);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let Some(target) = target else {
|
||||||
|
return Err(format!(
|
||||||
|
"Failed to find target for component {}",
|
||||||
|
&msg.component
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
info!("Applying to entity: {target:?}");
|
||||||
|
|
||||||
|
let mut entity_mut = unsafe_world.world_mut().entity_mut(target);
|
||||||
|
|
||||||
getter.as_ref()(
|
getter.as_ref()(
|
||||||
&mut entity_mut,
|
&mut entity_mut,
|
||||||
&msg.reflect_path,
|
&msg.reflect_path,
|
||||||
&mut unsafe_world,
|
&mut unsafe_world,
|
||||||
&|reflect_field| {
|
&|reflect_field| {
|
||||||
|
let fmt_err = |err: serde_json::Error| {
|
||||||
|
format!(
|
||||||
|
"Failed to parse value {} for path {}: {err}",
|
||||||
|
msg.value, &msg.reflect_path,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(val) = reflect_field.downcast_mut::<f32>() {
|
if let Some(val) = reflect_field.downcast_mut::<f32>() {
|
||||||
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
|
let msg_value = serde_json::from_value(msg.value.clone()).map_err(fmt_err)?;
|
||||||
error!("Failed to parse value: {}", msg.value);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
*val = msg_value;
|
*val = msg_value;
|
||||||
|
Ok(())
|
||||||
} else if let Some(val) = reflect_field.downcast_mut::<f64>() {
|
} else if let Some(val) = reflect_field.downcast_mut::<f64>() {
|
||||||
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
|
let msg_value = serde_json::from_value(msg.value.clone()).map_err(fmt_err)?;
|
||||||
error!("Failed to parse value: {}", msg.value);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
*val = msg_value;
|
*val = msg_value;
|
||||||
|
Ok(())
|
||||||
} else if let Some(val) = reflect_field.downcast_mut::<u32>() {
|
} else if let Some(val) = reflect_field.downcast_mut::<u32>() {
|
||||||
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
|
let msg_value = serde_json::from_value(msg.value.clone()).map_err(fmt_err)?;
|
||||||
error!("Failed to parse value: {}", msg.value);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
*val = msg_value;
|
*val = msg_value;
|
||||||
|
Ok(())
|
||||||
} else if let Some(val) = reflect_field.downcast_mut::<u64>() {
|
} else if let Some(val) = reflect_field.downcast_mut::<u64>() {
|
||||||
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
|
let msg_value = serde_json::from_value(msg.value.clone()).map_err(fmt_err)?;
|
||||||
error!("Failed to parse value: {}", msg.value);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
*val = msg_value;
|
*val = msg_value;
|
||||||
|
Ok(())
|
||||||
} else if let Some(val) = reflect_field.downcast_mut::<i32>() {
|
} else if let Some(val) = reflect_field.downcast_mut::<i32>() {
|
||||||
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
|
let msg_value = serde_json::from_value(msg.value.clone()).map_err(fmt_err)?;
|
||||||
error!("Failed to parse value: {}", msg.value);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
*val = msg_value;
|
*val = msg_value;
|
||||||
|
Ok(())
|
||||||
} else if let Some(val) = reflect_field.downcast_mut::<i64>() {
|
} else if let Some(val) = reflect_field.downcast_mut::<i64>() {
|
||||||
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
|
let msg_value = serde_json::from_value(msg.value.clone()).map_err(fmt_err)?;
|
||||||
error!("Failed to parse value: {}", msg.value);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
*val = msg_value;
|
*val = msg_value;
|
||||||
|
Ok(())
|
||||||
} else if let Some(val) = reflect_field.downcast_mut::<bool>() {
|
} else if let Some(val) = reflect_field.downcast_mut::<bool>() {
|
||||||
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
|
let msg_value = serde_json::from_value(msg.value.clone()).map_err(fmt_err)?;
|
||||||
error!("Failed to parse value: {}", msg.value);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
*val = msg_value;
|
*val = msg_value;
|
||||||
|
Ok(())
|
||||||
} else if let Some(val) = reflect_field.downcast_mut::<String>() {
|
} else if let Some(val) = reflect_field.downcast_mut::<String>() {
|
||||||
*val = msg.value.as_str().unwrap().to_string();
|
let msg_value = msg
|
||||||
|
.value
|
||||||
|
.as_str()
|
||||||
|
.ok_or_else(|| format!("Failed to parse value as string: {}", msg.value))?;
|
||||||
|
*val = msg_value.to_string();
|
||||||
|
Ok(())
|
||||||
} else if let Some(val) = reflect_field.downcast_mut::<Vec3>() {
|
} else if let Some(val) = reflect_field.downcast_mut::<Vec3>() {
|
||||||
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
|
let msg_value = serde_json::from_value(msg.value.clone()).map_err(fmt_err)?;
|
||||||
error!("Failed to parse value: {}", msg.value);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
*val = msg_value;
|
*val = msg_value;
|
||||||
|
Ok(())
|
||||||
} else if let Some(val) = reflect_field.downcast_mut::<Quat>() {
|
} else if let Some(val) = reflect_field.downcast_mut::<Quat>() {
|
||||||
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
|
let msg_value = serde_json::from_value(msg.value.clone()).map_err(fmt_err)?;
|
||||||
error!("Failed to parse value: {}", msg.value);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
*val = msg_value;
|
*val = msg_value;
|
||||||
|
Ok(())
|
||||||
|
} else if let Some(val) = reflect_field.downcast_mut::<Color>() {
|
||||||
|
let Rgba {
|
||||||
|
red,
|
||||||
|
green,
|
||||||
|
blue,
|
||||||
|
alpha,
|
||||||
|
} = serde_json::from_value(msg.value.clone()).map_err(fmt_err)?;
|
||||||
|
|
||||||
|
match *val {
|
||||||
|
Color::RgbaLinear { .. } => {
|
||||||
|
*val = Color::RgbaLinear {
|
||||||
|
red,
|
||||||
|
green,
|
||||||
|
blue,
|
||||||
|
alpha,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Color::Rgba { .. } => {
|
||||||
|
*val = Color::Rgba {
|
||||||
|
red,
|
||||||
|
green,
|
||||||
|
blue,
|
||||||
|
alpha,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
return Err(format!("Unsupported color format: {other:?}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
error!("Unsupported type {}!", &msg.reflect_path);
|
Err(format!("Unsupported type {}!", &msg.reflect_path))
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,11 +471,15 @@ fn notify_asset_change_system<T: Asset>(
|
|||||||
mut ev_inspect: EventWriter<UpdateInspect<Handle<T>>>,
|
mut ev_inspect: EventWriter<UpdateInspect<Handle<T>>>,
|
||||||
mut asset_changing: EventReader<AssetEvent<T>>,
|
mut asset_changing: EventReader<AssetEvent<T>>,
|
||||||
q_handles: Query<&Handle<T>>,
|
q_handles: Query<&Handle<T>>,
|
||||||
|
q_children: Query<&Children>,
|
||||||
) {
|
) {
|
||||||
for ev in asset_changing.read() {
|
for ev in asset_changing.read() {
|
||||||
if let Some(handle) = r_selection
|
if let Some(handle) = r_selection
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|selection| q_handles.get(***selection).ok())
|
.and_then(|selection| {
|
||||||
|
find_handle_in_self_or_children(***selection, &q_children, &q_handles)
|
||||||
|
})
|
||||||
|
.and_then(|target| q_handles.get(target).ok())
|
||||||
{
|
{
|
||||||
if ev.is_modified(handle.id()) {
|
if ev.is_modified(handle.id()) {
|
||||||
ev_inspect.send(UpdateInspect::<Handle<T>>::default());
|
ev_inspect.send(UpdateInspect::<Handle<T>>::default());
|
||||||
@@ -401,6 +491,7 @@ fn notify_asset_change_system<T: Asset>(
|
|||||||
fn auto_serialize_asset<T: Asset + Reflect + GetTypeRegistration>(
|
fn auto_serialize_asset<T: Asset + Reflect + GetTypeRegistration>(
|
||||||
r_selection: Option<Res<Selection>>,
|
r_selection: Option<Res<Selection>>,
|
||||||
q_t: Query<&Handle<T>>,
|
q_t: Query<&Handle<T>>,
|
||||||
|
q_children: Query<&Children>,
|
||||||
mut description: ResMut<EntityDescription>,
|
mut description: ResMut<EntityDescription>,
|
||||||
registry: Res<AppTypeRegistry>,
|
registry: Res<AppTypeRegistry>,
|
||||||
mut ev_inspect: EventReader<UpdateInspect<Handle<T>>>,
|
mut ev_inspect: EventReader<UpdateInspect<Handle<T>>>,
|
||||||
@@ -413,7 +504,11 @@ fn auto_serialize_asset<T: Asset + Reflect + GetTypeRegistration>(
|
|||||||
|
|
||||||
ev_inspect.clear();
|
ev_inspect.clear();
|
||||||
|
|
||||||
if let Some(handle) = r_selection.and_then(|selection| q_t.get(**selection).ok()) {
|
if let Some(handle) = r_selection
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|selection| find_handle_in_self_or_children(***selection, &q_children, &q_t))
|
||||||
|
.and_then(|target| q_t.get(target).ok())
|
||||||
|
{
|
||||||
if let Some(asset) = assets.get(handle) {
|
if let Some(asset) = assets.get(handle) {
|
||||||
let registry = registry.read();
|
let registry = registry.read();
|
||||||
info!(
|
info!(
|
||||||
@@ -458,7 +553,9 @@ impl ShortMaterial {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_standart_materal(
|
fn serialize_standart_materal(
|
||||||
|
r_selection: Option<Res<Selection>>,
|
||||||
q_m: Query<&Handle<StandardMaterial>>,
|
q_m: Query<&Handle<StandardMaterial>>,
|
||||||
|
q_children: Query<&Children>,
|
||||||
mut description: ResMut<EntityDescription>,
|
mut description: ResMut<EntityDescription>,
|
||||||
registry: Res<AppTypeRegistry>,
|
registry: Res<AppTypeRegistry>,
|
||||||
mut ev_inspect: EventReader<UpdateInspect<Handle<StandardMaterial>>>,
|
mut ev_inspect: EventReader<UpdateInspect<Handle<StandardMaterial>>>,
|
||||||
@@ -471,7 +568,11 @@ fn serialize_standart_materal(
|
|||||||
|
|
||||||
ev_inspect.clear();
|
ev_inspect.clear();
|
||||||
|
|
||||||
if let Some(handle) = q_m.iter().next() {
|
if let Some(handle) = r_selection
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|selection| find_handle_in_self_or_children(***selection, &q_children, &q_m))
|
||||||
|
.and_then(|target| q_m.get(target).ok())
|
||||||
|
{
|
||||||
if let Some(material) = assets.get(handle) {
|
if let Some(material) = assets.get(handle) {
|
||||||
let registry = registry.read();
|
let registry = registry.read();
|
||||||
let short_material = ShortMaterial::new(material);
|
let short_material = ShortMaterial::new(material);
|
||||||
@@ -545,7 +646,27 @@ fn update_description(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
/// Find a `Handle<T>` component on this entity or one of its direct children
|
||||||
|
fn find_handle_in_self_or_children<T: Asset>(
|
||||||
|
target: Entity,
|
||||||
|
q_children: &Query<&Children>,
|
||||||
|
q_handles: &Query<&Handle<T>>,
|
||||||
|
) -> Option<Entity> {
|
||||||
|
if q_handles.contains(target) {
|
||||||
|
return Some(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
let Ok(children) = q_children.get(target) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
children
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.find(|&child| q_handles.contains(child))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct UpdateField {
|
pub struct UpdateField {
|
||||||
pub component: String,
|
pub component: String,
|
||||||
@@ -587,6 +708,7 @@ static CHANGE_FROM_UI: WasmMessageBuffer<UpdateField> = WasmMessageBuffer::new()
|
|||||||
#[wasm_bindgen(js_name = updateComponentField, skip_typescript)]
|
#[wasm_bindgen(js_name = updateComponentField, skip_typescript)]
|
||||||
pub fn update_component_field(msg: JsValue) -> Result<(), JsValue> {
|
pub fn update_component_field(msg: JsValue) -> Result<(), JsValue> {
|
||||||
let msg = serde_wasm_bindgen::from_value(msg)?;
|
let msg = serde_wasm_bindgen::from_value(msg)?;
|
||||||
|
info!("Received update: {msg:?}");
|
||||||
CHANGE_FROM_UI.write_message(msg);
|
CHANGE_FROM_UI.write_message(msg);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user