Inspector frontend UI (#68)

This PR begins fleshing out the front-end UI for inspecting the selected
entity's components.

### Implemented
* Support for the `Name` component
* Placeholder/WIP support for the `Transform` component

### To-do
* Support for sending changes made in the inspector back to Bevy
* A slider/field combo widget for numeric values (can be adapted from
  [this StackBlitz](https://stackblitz.com/edit/angular-wfikuy?file=src%2Ffloat-input%2Ffloat-input.component.ts))
* Quat -> Euler angles conversion ([example math here](https://github.com/dannymcgee/3DGameMath/blob/5cb7a8ff9310565329e57751cd8b9c33e9bd4cb7/libs/Math/include/math/quat.inl.hpp#L56-L78),
  or use a 3rd-party math library)

### Known issues
* Selecting entities from the hierarchy doesn't always update the
  components list. I suspect this is the same issue as #39
This commit is contained in:
Danny McGee
2024-03-04 15:13:03 -05:00
committed by GitHub
parent 9875ea7cca
commit c9904a5293
26 changed files with 1093 additions and 174 deletions
@@ -0,0 +1,37 @@
import { LitElement } from "lit";
import { property } from "lit/decorators.js";
export abstract class FormFieldBaseElement extends LitElement {
static formAssociated = true;
@property({ type: Boolean }) disabled = false;
@property({ type: Boolean }) readonly = false;
@property({ type: Number }) override tabIndex = 0;
get form() { return this.internals.form; }
get name() { return this.getAttribute("name"); }
get type() { return this.localName; }
get validity() { return this.internals.validity; }
get validationMessage() { return this.internals.validationMessage; }
get willValidate() { return this.internals.willValidate; }
protected internals: ElementInternals;
constructor () {
super();
this.internals = this.attachInternals();
}
checkValidity(): boolean {
return this.internals.checkValidity();
}
reportValidity(): boolean {
return this.internals.reportValidity();
}
formDisabledCallback(disabled: boolean): void {
this.disabled = disabled;
}
}
+1
View File
@@ -0,0 +1 @@
export * from "./text-field.element";
@@ -0,0 +1,40 @@
@use "sass:math";
:host {
box-sizing: border-box;
display: block;
width: 100%;
padding: 0.5rem 0.75rem;
border-radius: 0.25rem;
background: #0004;
border: 1px solid #FFF2;
color: #FFFC;
transition: {
property: background-color, border-color, color;
duration: 33.333ms;
timing-function: linear;
}
font: {
family: "Fira Sans", sans-serif;
size: math.div(15, 16) * 1rem;
}
}
:host(:focus) {
outline: none;
border-color: #FFF4;
background: #0006;
color: #FFFF;
}
.input {
width: 100%;
appearance: none;
background: transparent;
border: none;
font: inherit;
color: inherit;
outline: none !important;
}
@@ -0,0 +1,41 @@
import { on } from "@storyteller/framework";
import { html, unsafeCSS } from "lit";
import { customElement, property } from "lit/decorators.js";
import { FormFieldBaseElement } from "./form-field-base.element";
import styles from "./text-field.element.scss?inline";
@customElement("sts-text-field")
export class TextFieldElement extends FormFieldBaseElement {
static override styles = unsafeCSS(styles);
static override shadowRootOptions: ShadowRootInit = {
mode: "open",
delegatesFocus: true,
}
@property() value = "";
@on("keydown")
@on("keyup")
blockKeypressBubbling(event: KeyboardEvent): void {
if (event.key !== "Alt" && event.key !== "Control")
event.stopPropagation();
}
#onInput(event: InputEvent): void {
const updated = (event.target as HTMLInputElement).value;
console.log("updated:", updated);
this.dispatchEvent(new CustomEvent("value-change", { detail: updated }));
}
protected override render = () => html`
<input
class="input"
type="text"
.value=${this.value}
@input=${this.#onInput}
/>
`;
}