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