Lee-Orr
2024-03-28 11:25:02 -04:00
committed by GitHub
parent 1163192d24
commit da152b8585
9 changed files with 334 additions and 285 deletions
@@ -1,8 +1,30 @@
:host { :host {
display: grid;
grid-template-rows: subgrid;
grid-template-columns: subgrid;
}
.timeline-controls {
align-self: end;
justify-self: center;
display: flex;
justify-content: center;
grid-column: center-toolbar;
grid-row: bottom-toolbar;
background-color: var(--color-toolbar-background);
padding: var(--padding);
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
border-top-right-radius: var(--corner-radius);
border-top-left-radius: var(--corner-radius);
}
.timeline-wrapper {
grid-area: timeline;
display: grid; display: grid;
border-radius: 8px; border-radius: 8px;
background: #0006; background: var(--color-panel-background);
backdrop-filter: blur(16px);
grid-template-columns: grid-template-columns:
[sidebar-start] min-content [sidebar-end [sidebar-start] min-content [sidebar-end
divider-start] 8px [divider-end divider-start] 8px [divider-end
+11 -7
View File
@@ -323,14 +323,9 @@ export class AnimEditorElement extends LitElement
const none_selected = !selected; const none_selected = !selected;
return html` return html`
<div class="sidebar" <div class="timeline-controls">
style=${styleMap({
width: `${this.sidebarWidth}px`,
...timeline_css_variables
})}
>
<sts-toolbar> <sts-toolbar>
<sts-toolbar-group> <sts-toolbar-group>
${this.keyingMode === "automatic" ? html` ${this.keyingMode === "automatic" ? html`
<sts-button class="btn-autokey active" <sts-button class="btn-autokey active"
icon="record" icon="record"
@@ -387,6 +382,14 @@ export class AnimEditorElement extends LitElement
></sts-button> ></sts-button>
</sts-toolbar-group> </sts-toolbar-group>
</sts-toolbar> </sts-toolbar>
</div>
<div class="timeline-wrapper">
<div class="sidebar"
style=${styleMap({
width: `${this.sidebarWidth}px`,
...timeline_css_variables
})}
>
<div class="tracklist" role="list"> <div class="tracklist" role="list">
${tracklist} ${tracklist}
</div> </div>
@@ -409,6 +412,7 @@ export class AnimEditorElement extends LitElement
.track_ordered=${track_order_hashed} .track_ordered=${track_order_hashed}
.track_animation_target_ordered=${track_animation_target_order_hashed} .track_animation_target_ordered=${track_animation_target_order_hashed}
></sts-anim-timeline> ></sts-anim-timeline>
</div>
` `
}; };
}; };
+136 -102
View File
@@ -5,120 +5,152 @@ $hierarchy-width: 290px;
.app-root { .app-root {
position: fixed; position: fixed;
inset: 0; inset: 0;
--sidebar-width: 300px;
--timeline-height: max-content;
} }
// Layout utilities .wrapper {
.v-start { position: fixed;
position: absolute; top: 0;
top: 16px; bottom: 0;
} left: 0;
.v-end { right: 0;
position: absolute; display: grid;
bottom: 16px;
} grid-template-columns: [timeline-start screen-start] 2rem [left-toolbar-start help-start] max-content [left-toolbar-end] auto [center-toolbar-start] max-content [center-toolbar-end] auto [right-toolbar-start] max-content [right-toolbar-end] 2rem [screen-end help-end sidebar-start] var(--sidebar-width) [sidebar-end timeline-end];
.v-after-toolbar { grid-template-rows: [top-toolbar-start sidebar-start screen-start] min-content [top-toolbar-end help-start sidebar-view-start] max-content [help-end] auto [bottom-toolbar-start] max-content [bottom-toolbar-end sidebar-end sidebar-view-end screen-end timeline-start] var(--timeline-height) [timeline-end];
position: absolute;
top: 60px; .studio-scene {
} position: fixed;
.h-start { left: 16px } bottom: 0;
.h-end { right: 16px } left: 0;
.h-center { right: 0;
left: 50%; }
transform: translateX(-50%);
.action-toolbar {
grid-column: left-toolbar;
grid-row: top-toolbar;
padding-top: var(--padding);
sts-toolbar-group {
gap: var(--padding);
}
sts-button {
background-color: var(--color-toolbar-background);
}
z-index: 1;
}
.timeline {
grid-row-start: bottom-toolbar-start;
grid-row-end: timeline-end;
grid-column: timeline;
z-index: 1;
border-radius: 0;
}
.sidebar {
display: grid;
grid-area: sidebar;
grid-template-rows: subgrid;
z-index: 1;
background-color: var(--color-panel-background);
border-radius: 0;
position: relative;
padding: var(--padding);
padding-left: 0.5rem;
.resize-handle {
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 0.5rem;
background: #0004;
transition: 66.666ms background linear;
&:hover {
background: #FFF1;
}
}
.sidebar-header {
grid-row: top-toolbar;
display: flex;
flex-direction: row;
justify-content: stretch;
gap: 0;
.sidebar-tab {
border: none;
background: none;
border-bottom: 2px var(--color-toolbar-background) solid;
flex-grow: 1;
border-radius: 0;
&.current-tab {
color: var(--color-text-bright);
border-bottom-color: var(--color-accent);
}
}
}
.sidebar-view {
grid-row: sidebar-view;
}
}
.help {
grid-area: help;
z-index: 1;
display: flex;
flex-direction: row;
justify-content: start;
}
.entity-inspector {
grid-column: left-toolbar;
grid-row: bottom-toolbar;
z-index: 1;
padding-bottom: var(--padding);
}
.transform-toolbar {
grid-column: center-toolbar;
grid-row: top-toolbar;
background-color: var(--color-toolbar-background);
z-index: 1;
border-top-right-radius: 0;
border-top-left-radius: 0;
border-bottom-right-radius: var(--corner-radius);
border-bottom-left-radius: var(--corner-radius);
padding: var(--padding);
display: flex;
flex-direction: row;
gap: 1rem;
.add-to-scene-dropdown {
background-color: var(--color-toolbar-background-light);
font-size: 16px;
padding: 10px 12px;
}
}
} }
.studio-scene.block-pointer-events::part(canvas) { .studio-scene.block-pointer-events::part(canvas) {
pointer-events: none; pointer-events: none;
} }
sts-button.active {
background: #7A2C34E3;
color: #FFF;
}
// TODO: Probably move this
.inspector-open {
display: flex;
align-items: center;
justify-content: center;
width: 52px;
height: 52px;
border-radius: 24px;
appearance: none;
border: none;
background: #0006;
backdrop-filter: blur(16px);
color: #FFFC;
transition: {
property: background-color, color;
duration: 100ms;
timing-function: linear;
}
cursor: pointer;
font-size: 20px;
&:hover {
background: #0008;
color: #FFFF;
}
}
.inspector {
display: flex;
flex-direction: column;
border-radius: 0.5rem;
border: 0.5rem solid #0004 {
block-start: none;
inline-start: none;
}
padding: {
block-start: 2rem;
inline-start: 0.5rem;
}
background: #0006;
backdrop-filter: blur(16px);
&__hierarchy {
flex: 0 0 calc(50% - 0.25rem);
}
&__entity {
flex: 1 0 0;
height: 0;
}
.resize-handle {
background: #0004;
transition: 66.666ms background linear;
&:hover {
background: #FFF1;
}
&--h-start {
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 0.5rem;
cursor: ew-resize;
}
&--v-center {
height: 0.5rem;
cursor: ns-resize
}
}
}
.inspector-header { .inspector-header {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
height: 2rem; height: 2rem;
padding: 0 { padding: 0 {
inline-start: 0.75rem; inline-start: 0.75rem;
} }
@@ -133,6 +165,7 @@ sts-button.active {
&__label { &__label {
margin: 0; margin: 0;
color: #FFF9; color: #FFF9;
font: { font: {
family: "Fira Sans", sans-serif; family: "Fira Sans", sans-serif;
size: math.div(15, 16) * 1rem; size: math.div(15, 16) * 1rem;
@@ -151,6 +184,7 @@ sts-button.active {
background: transparent; background: transparent;
border: none; border: none;
color: #FFFC; color: #FFFC;
transition: { transition: {
property: color, background-color; property: color, background-color;
duration: 66.666ms; duration: 66.666ms;
@@ -170,4 +204,4 @@ sts-button.active {
.menu-button-icon { .menu-button-icon {
font-size: 12px; font-size: 12px;
opacity: 0.75; opacity: 0.75;
} }
+71 -117
View File
@@ -14,7 +14,7 @@ import {
StudioURLParams, StudioURLParams,
} from "@storyteller/studio-web"; } from "@storyteller/studio-web";
import { match } from "@storyteller/utility"; import { match } from "@storyteller/utility";
import { LitElement, html, nothing } from "lit"; import { LitElement, TemplateResult, html, nothing } from "lit";
import { customElement, state } from "lit/decorators.js"; import { customElement, state } from "lit/decorators.js";
import { classMap } from "lit/directives/class-map.js"; import { classMap } from "lit/directives/class-map.js";
import { createRef, ref } from "lit/directives/ref.js"; import { createRef, ref } from "lit/directives/ref.js";
@@ -48,14 +48,15 @@ import { mock_save_load } from "./util";
}) })
export class AppElement extends LitElement { export class AppElement extends LitElement {
@state() showHelp = false; @state() showHelp = false;
@state() showInspector = false;
@state() loading = true; @state() loading = true;
@state() resizingPanel = false; @state() resizingPanel = false;
@state() inspectorWidth = 320; @state() inspectorWidth = 320;
@state() hierarchyHeight?: number;
#enableAnimTimeline = true; // TODO @state() sidebars: Array<{ title: string, name: string}> = [{title: 'Hierarchy', name: 'hierarchy'}];
@state() currentSidebar : string = 'hierarchy';
private api = new StorytellerApi(); private api = new StorytellerApi();
#addToSceneButtonRef = createRef<ButtonElement>(); #addToSceneButtonRef = createRef<ButtonElement>();
@@ -120,7 +121,7 @@ export class AppElement extends LitElement {
onSceneStateChange({ detail: state }: SceneStateEvent): void { onSceneStateChange({ detail: state }: SceneStateEvent): void {
console.log("scene state change:", SceneState[state]); console.log("scene state change:", SceneState[state]);
this.loading = match (state, { this.loading = match(state, {
[SceneState.Active]: () => false, [SceneState.Active]: () => false,
_: () => true, _: () => true,
}); });
@@ -139,41 +140,43 @@ export class AppElement extends LitElement {
this.inspectorWidth = Math.max(Math.min(updated, 1024), 128); this.inspectorWidth = Math.max(Math.min(updated, 1024), 128);
} }
resizeHierarchyHeight(event: PointerEvent): void {
let availableHeight = this.#inspectorRef.value?.clientHeight;
if (availableHeight == null) return;
// Account for:
// - `section.inspector` padding (2rem ~= 32px)
// - resize-handle (0.5rem ~= 8px)
// - minimal space for `sts-entity-inspector` (2rem ~= 32px)
availableHeight -= 72;
const height = this.hierarchyHeight ?? this.#hierarchyRef.value?.offsetHeight;
if (height == null) return;
const updated = height + event.movementY;
this.hierarchyHeight = Math.max(Math.min(updated, availableHeight), 8);
}
minimizeInspector(): void {
this.showInspector = false;
}
openInspector(): void {
this.showInspector = true;
}
toggleHelp(): void { toggleHelp(): void {
this.showHelp = !this.showHelp; this.showHelp = !this.showHelp;
} }
renderSidebar(): TemplateResult<1> {
if (this.currentSidebar === 'hierarchy') {
return html`<sts-scene-hierarchy
${ref(this.#hierarchyRef)}
class="hierarchy sidebar-view"
></sts-scene-hierarchy>`;
}
return html`<></>`;
}
protected override render() { protected override render() {
return html` return html`
<sts-canvas-provider class="app-root"> <sts-canvas-provider class="app-root">
<sts-scene-data-provider> <sts-scene-data-provider>
<sts-overlay-provider> <sts-overlay-provider>
<sts-iframe-messaging-bridge></sts-iframe-messaging-bridge> <sts-iframe-messaging-bridge></sts-iframe-messaging-bridge>
${this.#params.mode === StudioMode.Editor ? html`
<sts-add-to-scene-menu
.trigger=${this.#addToSceneButtonRef}
@open-media-library=${() => {
this.#mediaLibraryRef.value?.open();
}}
></sts-add-to-scene-menu>
<sts-dialog
${ref(this.#mediaLibraryRef)}
tag="sts-media-library-dialog"
nonBlocking
></sts-dialog>
` : html`<></>`}
<div class="wrapper" style=${styleMap({ "--sidebar-width": `${this.inspectorWidth}px` })}>
<!-- Scene view --> <!-- Scene view -->
<sts-studio <sts-studio
class=${classMap({ class=${classMap({
@@ -190,29 +193,17 @@ export class AppElement extends LitElement {
></sts-studio> ></sts-studio>
<!-- Actions toolbar --> <!-- Actions toolbar -->
<sts-toolbar class="v-start h-start"> <sts-toolbar class="action-toolbar">
<sts-toolbar-group> <sts-toolbar-group>
${this.#params.mode === StudioMode.Editor ? html` ${this.#params.mode === StudioMode.Editor ? html`
<sts-button <sts-button
icon="floppy-disk"
@click=${() => studio.saveScene()} @click=${() => studio.saveScene()}
> >
Save Save Scene
</sts-button>
<sts-button
${ref(this.#addToSceneButtonRef)}
icon="plus-large"
>
Add to Scene
<sts-icon
class="menu-button-icon"
icon="chevron-down"
></sts-icon>
</sts-button> </sts-button>
` : nothing} ` : nothing}
<sts-button <sts-button
class=${classMap({ active: this.showHelp })} class=${classMap({ active: this.showHelp })}
icon="question"
@click=${this.toggleHelp} @click=${this.toggleHelp}
> >
Help Help
@@ -222,99 +213,62 @@ export class AppElement extends LitElement {
<!-- Studio-mode-only elements --> <!-- Studio-mode-only elements -->
${this.#params.mode === StudioMode.Editor ? html` ${this.#params.mode === StudioMode.Editor ? html`
<sts-add-to-scene-menu <div class="transform-toolbar">
.trigger=${this.#addToSceneButtonRef} <sts-button
@open-media-library=${() => { ${ref(this.#addToSceneButtonRef)}
this.#mediaLibraryRef.value?.open(); icon="plus-large"
}} class="add-to-scene-dropdown"
></sts-add-to-scene-menu> >
<sts-dialog </sts-button>
${ref(this.#mediaLibraryRef)} <sts-transform-toolbar></sts-transform-toolbar>
tag="sts-media-library-dialog" </div>
nonBlocking
></sts-dialog>
<sts-transform-toolbar class="v-start h-center"></sts-transform-toolbar>
${this.showInspector ? html`
<section <section
${ref(this.#inspectorRef)} ${ref(this.#inspectorRef)}
class="inspector h-end v-start v-end" class="sidebar"
style=${styleMap({ style=${styleMap({
width: `${this.inspectorWidth}px`, width: `${this.inspectorWidth}px`,
})} })}
aria-labelledby="inspector-heading" aria-labelledby="sidebar-heading"
> >
<header class="inspector-header"> <div class="sidebar-header">
<h3 class="inspector-header__label" ${this.sidebars.map(({title, name}) => {
id="inspector-heading" return html`<sts-button class=${classMap({"current-tab": name === this.currentSidebar, "sidebar-tab": true})}>${title}</sts-button>`
> })}
Inspector </div>
</h3>
<button class="inspector-header__minimize"
aria-label="Minimize inspector"
title="Minimize inspector"
@click=${this.minimizeInspector}
>
<sts-icon icon="minus"></sts-icon>
</button>
</header>
<sts-scene-hierarchy ${this.renderSidebar()}
${ref(this.#hierarchyRef)}
class="inspector__hierarchy"
style=${styleMap({
flex: this.hierarchyHeight != null
? `0 0 ${this.hierarchyHeight}px`
: null,
})}
></sts-scene-hierarchy>
<div <div
class="resize-handle resize-handle--v-center" class="resize-handle"
${drag({ ${drag({
start: this.onPanelResizeStart, start: this.onPanelResizeStart,
end: this.onPanelResizeEnd, end: this.onPanelResizeEnd,
move: this.resizeHierarchyHeight, move: this.resizeInspectorWidth,
})} })}
></div>
<sts-entity-inspector class="inspector__entity"></sts-entity-inspector>
<div
class="resize-handle resize-handle--h-start"
${drag({
start: this.onPanelResizeStart,
end: this.onPanelResizeEnd,
move: this.resizeInspectorWidth,
})}
></div> ></div>
</section> </section>
` : html`
<button
class="inspector-open h-end v-start"
aria-label="Open scene inspector"
title="Open scene inspector"
@click=${this.openInspector}
>
<sts-icon icon="bars"></sts-icon>
</button>
`}
${this.#enableAnimTimeline ? html` <div class="entity-inspector">
<sts-entity-inspector></sts-entity-inspector>
</div>
<sts-anim-editor <sts-anim-editor
class="timeline v-end h-start" class="timeline"
style=${styleMap({ style=${styleMap({
right: `${this.inspectorWidth + 32}px` right: `${this.inspectorWidth + 32}px`
})} })}
></sts-anim-editor> ></sts-anim-editor>
` : nothing}
` : nothing} ` : nothing}
<!-- Dialogs and modals --> <!-- Dialogs and modals -->
${this.showHelp ? html` ${this.showHelp ? html`
<sts-help class="help v-after-toolbar h-start"></sts-help> <div class="help">
<sts-help></sts-help>
</div>
` : nothing} ` : nothing}
</div>
${this.loading ? html` ${this.loading ? html`
<sts-loading></sts-loading> <sts-loading></sts-loading>
+13
View File
@@ -7,4 +7,17 @@ html, body {
height: 100%; height: 100%;
margin: 0; margin: 0;
overflow: hidden; overflow: hidden;
--color-panel-background: #1e1e2c;
--color-toolbar-background: #2c2c3b;
--color-toolbar-background-light: #595970;
--color-panel-item-name-background: #39394d;
--color-panel-item-background: #55556b;
--color-accent: #fc6b68;
--color-text: #dbdbdf;
--color-text-bright: #ffffff;
--color-text-disabled: #7c7c85;
--corner-radius: 0.75rem;
--padding: 0.4rem;
} }
+1 -1
View File
@@ -4,7 +4,7 @@
} }
::slotted(*) { ::slotted(*) {
border-radius: 0; border-radius: var(--corner-radius);
} }
::slotted(:first-child) { ::slotted(:first-child) {
@@ -3,10 +3,11 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 1.5rem; gap: var(--padding);
padding: 1rem;
overflow: auto; overflow: auto;
--background-color: var(--color-panel-background);
color: #FFFC; color: #FFFC;
&::-webkit-scrollbar { &::-webkit-scrollbar {
@@ -31,3 +32,13 @@
*, *::before, *::after { *, *::before, *::after {
box-sizing: border-box; box-sizing: border-box;
} }
.container {
background-color: var(--background-color);
border-radius: var(--corner-radius);
display: flex;
flex-direction: column;
justify-content: flex-end;
padding: var(--padding);
gap: var(--padding);
}
@@ -34,63 +34,71 @@ export class EntityInspectorElement extends LitElement {
this.requestUpdate(); this.requestUpdate();
} }
protected override render = () => html` protected override render () {
${this._sceneData let components = this._sceneData
?.selectionComponents ?.selectionComponents
.slice() .slice()
.sort((a, b) => { .sort((a, b) => {
const ordA = COMPONENT_SORT_ORDER.indexOf(a.name); const ordA = COMPONENT_SORT_ORDER.indexOf(a.name);
const ordB = COMPONENT_SORT_ORDER.indexOf(b.name); const ordB = COMPONENT_SORT_ORDER.indexOf(b.name);
if (ordA === -1) return Infinity; if (ordA === -1) return Infinity;
if (ordB === -1) return -Infinity; if (ordB === -1) return -Infinity;
return ordA - ordB; return ordA - ordB;
}) })
.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": {
return html`
<sts-transform-inspector
.translation=${value.translation}
.rotation=${value.rotation}
.scale=${value.scale}
></sts-transform-inspector>
`;
}
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
.baseColor=${baseColor}
.colorSpace=${colorSpace}
.roughness=${value.perceptual_roughness}
.metallic=${value.metallic}
.reflectance=${value.reflectance}
></sts-material-inspector>
`;
}
default: {
return nothing;
// return html`
// <h3>Unknown Component: ${name}</h3>
// <pre>${JSON.stringify({ name, value }, null, " ")}</pre>
// `;
}
} }
}) case "bevy_transform::components::transform::Transform": {
return html`
<sts-transform-inspector
.translation=${value.translation}
.rotation=${value.rotation}
.scale=${value.scale}
></sts-transform-inspector>
`;
}
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
.baseColor=${baseColor}
.colorSpace=${colorSpace}
.roughness=${value.perceptual_roughness}
.metallic=${value.metallic}
.reflectance=${value.reflectance}
></sts-material-inspector>
`;
}
default: {
return nothing;
// return html`
// <h3>Unknown Component: ${name}</h3>
// <pre>${JSON.stringify({ name, value }, null, " ")}</pre>
// `;
}
}
}).filter((value) => value !== nothing) || [];
if (components?.length === 0) {
return nothing;
} }
return html`
<div class="container">
${components
}
</div>
`; `;
}
} }
const COMPONENT_SORT_ORDER: Array<InspectorComponent["name"]> = [ const COMPONENT_SORT_ORDER: Array<InspectorComponent["name"]> = [
@@ -8,7 +8,7 @@
pointer-events: auto; pointer-events: auto;
// TODO: Copypasta // TODO: Copypasta
background: #0006; background: var(--color-toolbar-background);
color: #FFFC; color: #FFFC;
backdrop-filter: blur(16px); backdrop-filter: blur(16px);
transition: { transition: {
@@ -16,6 +16,7 @@
duration: 0.1s; duration: 0.1s;
timing-function: linear; timing-function: linear;
} }
border-radius: var(--corner-radius);
&:hover { &:hover {
background: #0008; background: #0008;
@@ -28,7 +29,9 @@
} }
&:has(:checked) { &:has(:checked) {
background: #7A2C34E3; border-color: var(--color-accent);
border-width: 2px;
border-style: solid;
color: #FFF; color: #FFF;
&:hover { &:hover {