mirror of
https://github.com/storytold/storyteller-bevy.git
synced 2026-10-17 00:13:17 +00:00
Proof-of-concept inspector mutations
This commit is contained in:
@@ -24,6 +24,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
[part^="icon-"] {
|
||||
color: #FFF6;
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ export class TextFieldElement extends FormFieldBaseElement {
|
||||
` : nothing}
|
||||
<input
|
||||
class="input"
|
||||
part="input"
|
||||
.type=${this.fieldType}
|
||||
placeholder=${ifDefined(this.placeholder)}
|
||||
.value=${this.value}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import * as studio from "@storyteller/studio";
|
||||
import { bind } from "@storyteller/framework";
|
||||
import { LitElement, PropertyValues, type TemplateResult, html, unsafeCSS } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
@@ -13,8 +14,20 @@ export class NameInspectorElement extends LitElement {
|
||||
@property() value = "";
|
||||
|
||||
protected override updated(changes: PropertyValues<this>): void {
|
||||
if (changes.has("value"))
|
||||
console.log("New value:", this.value);
|
||||
// FIXME: `Name` component needs special handling due to:
|
||||
// 1. Its `hash` field, which doesn't get updated by just directly
|
||||
// mutating the `name` field
|
||||
// 2. Its use in our hierarchy. We'll need to add some logic to check
|
||||
// for changed names on existing entities and notify the frontend so
|
||||
// the hierarchy can reflect the new value.
|
||||
|
||||
// if (changes.has("value")) {
|
||||
// studio.updateComponentField({
|
||||
// component: "bevy_core::name::Name",
|
||||
// reflectPath: "name",
|
||||
// value: this.value,
|
||||
// });
|
||||
// }
|
||||
|
||||
super.updated(changes);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,17 @@ h4 {
|
||||
margin: 0 0 0.25rem;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
.fieldset {
|
||||
position: relative;
|
||||
padding-block-start: math.div(14, 16) + 0.333rem !important;
|
||||
|
||||
legend {
|
||||
position: absolute;
|
||||
inset-block-start: 0;
|
||||
}
|
||||
}
|
||||
|
||||
fieldset, .fieldset {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
@@ -33,14 +43,10 @@ fieldset {
|
||||
|
||||
input {
|
||||
appearance: textfield;
|
||||
width: math.div(1, 3) * 100%;
|
||||
padding: 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
|
||||
background: #0004;
|
||||
border: 1px solid #FFF2 {
|
||||
left: 4px solid #FFF2;
|
||||
}
|
||||
border: 1px solid #FFF2;
|
||||
color: #FFFC;
|
||||
transition: {
|
||||
property: background-color, border-color, color;
|
||||
@@ -60,6 +66,29 @@ fieldset {
|
||||
color: #FFFF;
|
||||
}
|
||||
|
||||
&::-webkit-outer-spin-button,
|
||||
&::-webkit-inner-spin-button {
|
||||
appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
sts-text-field::part(input) {
|
||||
flex: unset;
|
||||
width: 100%;
|
||||
|
||||
&::-webkit-outer-spin-button,
|
||||
&::-webkit-inner-spin-button {
|
||||
appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
input, sts-text-field {
|
||||
padding: 0.5rem;
|
||||
width: math.div(1, 3) * 100%;
|
||||
border-left-width: 4px;
|
||||
|
||||
&:nth-of-type(1) {
|
||||
border-left-color: rgb(220, 53, 69);
|
||||
&:focus {
|
||||
@@ -78,11 +107,5 @@ fieldset {
|
||||
border-left-color: rgb(47, 131, 255);
|
||||
}
|
||||
}
|
||||
|
||||
&::-webkit-outer-spin-button,
|
||||
&::-webkit-inner-spin-button {
|
||||
appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import * as studio from "@storyteller/studio"
|
||||
import { LitElement, html, unsafeCSS } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { type Quat, type Vec3 } from "./inspector.types";
|
||||
@@ -17,27 +18,76 @@ export class TransformInspectorElement extends LitElement {
|
||||
@property({ attribute: false })
|
||||
scale: Vec3 = { x: 1, y: 1, z: 1 };
|
||||
|
||||
updateField(reflectPath: `${"translation"|"rotation"|"scale"}.${"x"|"y"|"z"}`) {
|
||||
return ({ detail }: CustomEvent<string>) => {
|
||||
const value = parseFloat(detail);
|
||||
if (Number.isFinite(value) && !Number.isNaN(value)) {
|
||||
studio.updateComponentField({
|
||||
component: "bevy_transform::components::transform::Transform",
|
||||
reflectPath,
|
||||
value,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override render = () => html`
|
||||
<!-- TODO -->
|
||||
<h4>Transform</h4>
|
||||
<fieldset>
|
||||
<div class="fieldset" role="group">
|
||||
<legend>Translation</legend>
|
||||
<input type="number" value=${this.translation.x.toFixed(3)} />
|
||||
<input type="number" value=${this.translation.y.toFixed(3)} />
|
||||
<input type="number" value=${this.translation.z.toFixed(3)} />
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<sts-text-field
|
||||
type="number"
|
||||
.value=${this.translation.x.toFixed(3)}
|
||||
@value-change=${this.updateField("translation.x")}
|
||||
></sts-text-field>
|
||||
<sts-text-field
|
||||
type="number"
|
||||
.value=${this.translation.y.toFixed(3)}
|
||||
@value-change=${this.updateField("translation.y")}
|
||||
></sts-text-field>
|
||||
<sts-text-field
|
||||
type="number"
|
||||
.value=${this.translation.z.toFixed(3)}
|
||||
@value-change=${this.updateField("translation.z")}
|
||||
></sts-text-field>
|
||||
</div>
|
||||
<div class="fieldset" role="group">
|
||||
<legend>Rotation</legend>
|
||||
<input type="number" value=${this.rotation.x.toFixed(3)} />
|
||||
<input type="number" value=${this.rotation.y.toFixed(3)} />
|
||||
<input type="number" value=${this.rotation.z.toFixed(3)} />
|
||||
<!-- TODO: This is not how quaternions work lol -->
|
||||
<sts-text-field
|
||||
type="number"
|
||||
.value=${this.rotation.x.toFixed(3)}
|
||||
@value-change=${this.updateField("rotation.x")}
|
||||
></sts-text-field>
|
||||
<sts-text-field
|
||||
type="number"
|
||||
.value=${this.rotation.y.toFixed(3)}
|
||||
@value-change=${this.updateField("rotation.y")}
|
||||
></sts-text-field>
|
||||
<sts-text-field
|
||||
type="number"
|
||||
.value=${this.rotation.z.toFixed(3)}
|
||||
@value-change=${this.updateField("rotation.z")}
|
||||
></sts-text-field>
|
||||
<!-- <input type="number" value=${this.rotation.w.toFixed(3)} /> -->
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
</div>
|
||||
<div class="fieldset" role="group">
|
||||
<legend>Scale</legend>
|
||||
<input type="number" value=${this.scale.x.toFixed(3)} />
|
||||
<input type="number" value=${this.scale.y.toFixed(3)} />
|
||||
<input type="number" value=${this.scale.z.toFixed(3)} />
|
||||
</fieldset>
|
||||
<sts-text-field
|
||||
type="number"
|
||||
.value=${this.scale.x.toFixed(3)}
|
||||
@value-change=${this.updateField("scale.x")}
|
||||
></sts-text-field>
|
||||
<sts-text-field
|
||||
type="number"
|
||||
.value=${this.scale.y.toFixed(3)}
|
||||
@value-change=${this.updateField("scale.y")}
|
||||
></sts-text-field>
|
||||
<sts-text-field
|
||||
type="number"
|
||||
.value=${this.scale.z.toFixed(3)}
|
||||
@value-change=${this.updateField("scale.z")}
|
||||
></sts-text-field>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
type EntityMultiSpawnEvent,
|
||||
type EntitySelectEvent,
|
||||
type EntitySpawnEvent,
|
||||
type InspectorChanged,
|
||||
type InspectorChangedEvent,
|
||||
} from "@storyteller/studio";
|
||||
import { TREE_SELECTION_PROVIDER, TreeSelectionProvider } from "@storyteller/studio-ui/tree";
|
||||
import { match } from "@storyteller/utility";
|
||||
@@ -109,7 +109,7 @@ export class SceneDataProviderElement
|
||||
}
|
||||
|
||||
@on("window:inspector-changed")
|
||||
onInspectorChanged({ detail: json }: InspectorChanged): void {
|
||||
onInspectorChanged({ detail: json }: InspectorChangedEvent): void {
|
||||
this._components = json ? JSON.parse(json) : [];
|
||||
}
|
||||
|
||||
|
||||
+22
-15
@@ -8,7 +8,7 @@ use bevy::{
|
||||
window::PrimaryWindow,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::*;
|
||||
use serde_json::{self, json, Map as JsonMap, Value as JsonValue};
|
||||
use space_editor::{
|
||||
space_editor_ui::{
|
||||
editor_tab::{EditorTab, EditorTabName},
|
||||
@@ -189,7 +189,7 @@ impl EditorTab for TestInspectorTab {
|
||||
CHANGE_FROM_UI.write_message(UpdateField {
|
||||
component: "bevy_transform::components::transform::Transform".to_string(),
|
||||
reflect_path: ".translation.x".to_string(),
|
||||
value: serde_json::Value::from(0.0),
|
||||
value: JsonValue::from(0.0),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -316,7 +316,7 @@ fn update_field_by_ui(world: &mut World) {
|
||||
|
||||
#[derive(Resource, Serialize, Deserialize, Default, Clone)]
|
||||
pub struct EntityDescription {
|
||||
pub components: HashMap<String, Value>,
|
||||
pub components: HashMap<String, JsonValue>,
|
||||
}
|
||||
|
||||
fn update_inspect_by_select_change<T: Component + Reflect + GetTypeRegistration>(
|
||||
@@ -524,11 +524,11 @@ fn update_description(
|
||||
.components
|
||||
.iter()
|
||||
.map(|(name, v)| {
|
||||
let mut value = Value::Object(Map::new());
|
||||
let mut value = JsonValue::Object(JsonMap::new());
|
||||
value
|
||||
.as_object_mut()
|
||||
.unwrap()
|
||||
.insert("name".to_string(), Value::String(name.to_string()));
|
||||
.insert("name".to_string(), JsonValue::String(name.to_string()));
|
||||
value
|
||||
.as_object_mut()
|
||||
.unwrap()
|
||||
@@ -546,24 +546,33 @@ fn update_description(
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UpdateField {
|
||||
pub component: String,
|
||||
pub reflect_path: String,
|
||||
pub value: serde_json::Value,
|
||||
pub value: JsonValue,
|
||||
}
|
||||
|
||||
#[wasm_bindgen(typescript_custom_section)]
|
||||
const EVENT_DECLARATION: &str = r#"
|
||||
|
||||
export interface InspectorChanged extends CustomEvent {
|
||||
export interface InspectorChangedEvent extends CustomEvent {
|
||||
detail: string | null;
|
||||
}
|
||||
|
||||
declare global {
|
||||
export interface HTMLElementEventMap {
|
||||
"inspector-changed": InspectorChanged;
|
||||
export interface HTMLElementEventMap {
|
||||
"inspector-changed": InspectorChangedEvent;
|
||||
}
|
||||
}
|
||||
|
||||
export interface UpdateField<T> {
|
||||
component: string;
|
||||
reflectPath: string;
|
||||
value: T;
|
||||
}
|
||||
|
||||
export function updateComponentField<T>(params: UpdateField<T>): void;
|
||||
"#;
|
||||
|
||||
pub(super) fn notify_changed(q_window: &Window, msg: String) {
|
||||
@@ -575,11 +584,9 @@ pub(super) fn notify_changed(q_window: &Window, msg: String) {
|
||||
|
||||
static CHANGE_FROM_UI: WasmMessageBuffer<UpdateField> = WasmMessageBuffer::new();
|
||||
|
||||
#[wasm_bindgen(js_name = changeValue)]
|
||||
pub fn change_value(msg: JsValue) {
|
||||
let Ok(msg) = serde_wasm_bindgen::from_value(msg) else {
|
||||
error!("Failed to parse message");
|
||||
return;
|
||||
};
|
||||
#[wasm_bindgen(js_name = updateComponentField, skip_typescript)]
|
||||
pub fn update_component_field(msg: JsValue) -> Result<(), JsValue> {
|
||||
let msg = serde_wasm_bindgen::from_value(msg)?;
|
||||
CHANGE_FROM_UI.write_message(msg);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user