Small batch of misc enhancements (#64)

This PR cherry-picks some improvements from #63 that are general-purpose
and not specifically related to IK.

* Some logic has been added to `AppElement` to track the currently
  selected Scene Element
* `disabled` property has been added to `ButtonElement`, with custom
  styling and accessibility features
* Tagging entities with their `SceneElement` type has been made more
  robust and reliable (previously, it was possible for all entities to
  appear in the hierarchy as "Generic" elements depending on the order of
  system execution)

Additionally:

* Forces the Netlify CI pipeline to always used the latest stable Rust
  version
This commit is contained in:
Danny McGee
2024-02-29 15:42:49 -05:00
committed by GitHub
parent ae88696213
commit 3413810773
7 changed files with 57 additions and 19 deletions
+3 -1
View File
@@ -3,10 +3,12 @@
set -euxo pipefail
# Install build dependencies
rustup update
rustup default stable
rustup target add wasm32-unknown-unknown
# Emit versions
rustup show active-toolchain
rustup show
npm version
# Install project dependencies
+28 -4
View File
@@ -1,12 +1,17 @@
import { on } from "@storyteller/framework";
import * as studio from "@storyteller/studio";
import {
type EntitySelectEvent,
type SceneSavedEvent,
type SceneStateEvent,
SceneState,
StudioMode,
} from "@storyteller/studio";
import { StudioURLParams } from "@storyteller/studio-web";
import {
SceneHierarchyElement,
type SceneObject,
StudioURLParams,
} from "@storyteller/studio-web";
import { CanvasProvider } from "@storyteller/studio-web/canvas-provider";
import { match } from "@storyteller/utility";
import { LitElement, html, nothing } from "lit";
@@ -33,6 +38,9 @@ export class AppElement extends LitElement {
@state() showHelp = false;
@state() loading = true;
@state() selection?: SceneObject;
#hierarchy = createRef<SceneHierarchyElement>();
#params = new StudioURLParams();
#canvasProvider: Ref<CanvasProvider> = createRef();
@@ -79,6 +87,15 @@ export class AppElement extends LitElement {
});
}
@on("entity-select")
onEntitySelected({ detail: id }: EntitySelectEvent): void {
if (!id) {
this.selection = undefined;
} else {
this.selection = this.#hierarchy.value?.entityMap.get(id);
}
}
toggleHelp(): void {
this.showHelp = !this.showHelp;
}
@@ -122,9 +139,16 @@ export class AppElement extends LitElement {
<!-- Studio-mode-only elements -->
${this.#params.mode === StudioMode.Editor ? html`
<sts-transform-toolbar class="v-start h-center"></sts-transform-toolbar>
<sts-scene-hierarchy class="hierarchy v-start h-end"></sts-scene-hierarchy>
<sts-anim-editor class="timeline v-end h-start h-before-hierarchy"></sts-anim-editor>
<sts-transform-toolbar
class="v-start h-center"
></sts-transform-toolbar>
<sts-scene-hierarchy
class="hierarchy v-start h-end"
${ref(this.#hierarchy)}
></sts-scene-hierarchy>
<sts-anim-editor
class="timeline v-end h-start h-before-hierarchy"
></sts-anim-editor>
` : nothing}
<!-- Dialogs and modals -->
+6
View File
@@ -31,6 +31,12 @@
color: #FFFF;
}
:host(.disabled),
:host(.disabled:hover) {
background: #0003;
color: #FFF6;
}
.icon {
fill: currentColor;
}
+12 -1
View File
@@ -1,5 +1,5 @@
import { on } from "@storyteller/framework";
import { LitElement, html, nothing, unsafeCSS } from "lit";
import { LitElement, type PropertyValues, html, nothing, unsafeCSS } from "lit";
import { customElement, property } from "lit/decorators.js";
import styles from "./button.element.scss?inline";
@@ -11,10 +11,21 @@ export class ButtonElement extends LitElement {
static override styles = unsafeCSS(styles);
@property() icon?: string;
@property({ type: Boolean }) disabled = false;
override role = "button";
override tabIndex = 0;
protected override willUpdate(changes: PropertyValues<this>): void {
if (changes.has("disabled")) {
this.ariaDisabled = this.disabled ? "true" : null;
this.tabIndex = this.disabled ? -1 : 0;
this.classList.toggle("disabled", this.disabled);
}
super.willUpdate(changes);
}
@on("keydown")
onKeyDown(event: KeyboardEvent) {
if (/^( |Enter)$/.test(event.key)) {
@@ -14,7 +14,7 @@ import styles from "./scene-hierarchy.element.scss?inline";
import "@storyteller/studio-ui/tree";
interface SceneObject extends Omit<studio.SceneObject, "icon"|"children"> {
export interface SceneObject extends Omit<studio.SceneObject, "children"> {
icon?: string;
children: SceneObject[];
}
+5 -1
View File
@@ -131,6 +131,7 @@ pub(super) fn notify_entity_spawn_changes(
mut l_missing_parents: Local<VecDeque<Entity>>,
q_window: Query<&Window, With<PrimaryWindow>>,
q_inspectables: Query<(Entity, &SceneElement)>,
q_element_changes: Query<Entity, Changed<SceneElement>>,
q_others: Query<Entity, Without<SceneElement>>,
q_names: Query<&Name>,
q_parents: Query<&Children>,
@@ -146,6 +147,7 @@ pub(super) fn notify_entity_spawn_changes(
ent,
type_,
&q_inspectables,
&q_element_changes,
&q_names,
&q_parents,
&q_children,
@@ -166,6 +168,7 @@ pub(super) fn notify_entity_spawn_changes(
ent,
SceneElement::Generic,
&q_inspectables,
&q_element_changes,
&q_names,
&q_parents,
&q_children,
@@ -204,11 +207,12 @@ fn build_scene_object(
ent: Entity,
type_: SceneElement,
q_inspectables: &Query<(Entity, &SceneElement)>,
q_element_changes: &Query<Entity, Changed<SceneElement>>,
q_names: &Query<&Name>,
q_parents: &Query<&Children>,
q_children: &Query<&Parent>,
) -> Option<SceneObject> {
if notified.contains(&ent) {
if notified.contains(&ent) && !q_element_changes.contains(ent) {
return None;
}
+2 -11
View File
@@ -233,13 +233,13 @@ pub struct ImportSceneEvent(pub String);
derive(Serialize_repr, Deserialize_repr)
)]
#[derive(Component, Clone, Copy, Debug, PartialEq, Eq)]
#[allow(dead_code)] // Some variants only used in `wasm`, some planned for later use
pub enum SceneElement {
Generic,
Mesh,
Skeleton,
Bone,
Light,
#[allow(dead_code)] // TODO: Will be used for camera animation
Camera,
}
@@ -395,17 +395,8 @@ fn tag_scene_elements(
mesh_entities: Query<Entity, With<Handle<Mesh>>>,
) {
for (e, children) in q_named_non_primitives.iter() {
let mut has_mesh = false;
for child in children {
if mesh_entities.contains(*child) {
has_mesh = true;
break;
}
}
if has_mesh {
if children.iter().any(|child| mesh_entities.contains(*child)) {
cmd.entity(e).insert(SceneElement::Mesh);
} else {
cmd.entity(e).insert(SceneElement::Generic);
}
}
}