A held preview is rebased, not paused, and it wears the asset's own rows (#86)
game-maker / suite (push) Successful in 9m42s
game-maker / suite (push) Successful in 9m42s
Three reports plus one found on the way.
Hold only froze the *picture*: the preview computed a fake second while drawing,
the library clock kept counting underneath, so letting go jumped to "how much
would have passed by now". That is the flip side of the formula model — a thing
that is a function of the clock is not stopped, its origin is moved.
ParticleField.rebase() does that; hold(null) calls it with the second stood at.
The Hold label now mirrors the preview's state instead of its own click history,
so dragging the slider (which holds too) reads `Run`, and Restart puts a held
preview back to 0.00 s rather than leaving it on the second it stopped at.
previewProps only read defaultsFor('particles'), so the panel showed the type's
defaults while the inspector showed the asset's rows. AssetBase.props is a full
bag on a graph asset as well — asset.props now lie over the defaults, the
asset's texture is resolved instead of always drawing a white square, and the
slider's span follows its lifetime. The span is re-read in tick(), because the
rows are edited in the inspector and the tab hears nothing about it.
The preview moves from under the graph to beside it, past a draggable splitter:
a graph is read across, so width is what it can spare and height is not.
AssetTabEditor grows a body() hook for that — the base class still stacks header,
strip and canvas, a subclass may put something next to the canvas.
Found by the full run, not reported: the graph canvas's empty-state hint ran
38 px past its frame now that the preview takes 280 px away. It is cut with
truncate() like any other label.
816 checks (809 + 7).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4feb4c2cf6
commit
fbb2d157f0
8 files changed
+284
-66
No files matched your search
@@ -2,18 +2,22 @@ import { Container, Graphics, Texture } from 'pixi.js';
|
||||
import type { ParticlesAsset } from '../../core/model/Asset';
|
||||
import { translation } from '../../core/model/matrix';
|
||||
import type { PropertyBag } from '../../core/model/Properties';
|
||||
import { resolveTexture } from '../../core/model/texture';
|
||||
import { defaultsFor } from '../../core/properties/PropertySchema';
|
||||
import { SHEET_EDGE } from '../../core/shader/nodes';
|
||||
import { type Size, Theme, Widget } from '../../ui';
|
||||
import { ParticleField } from '../../viewport/particles';
|
||||
import { particleLibrary } from '../../viewport/ParticleLibrary';
|
||||
import { textureLibrary } from '../../viewport/TextureLibrary';
|
||||
import type { EditorState } from '../state';
|
||||
|
||||
const MIN_HEIGHT = 40;
|
||||
|
||||
/**
|
||||
* The asset's own particles, emitted from the middle of the strip by a source with the type's
|
||||
* default rows. It is the one place a formula graph can be judged: the numbers say nothing. The
|
||||
* picture is deliberately a plain square – which texture a source throws belongs to the placed
|
||||
* object, not to the asset the tab is showing.
|
||||
* The asset's own particles, emitted from the middle of the panel by a source that wears the asset's
|
||||
* own rows. It is the one place a formula graph can be judged: the numbers say nothing. What it is
|
||||
* not is an instance – a source placed in a scene may override every one of those rows, and the tab
|
||||
* has no way to know which of them is meant.
|
||||
*/
|
||||
export class ParticlePreview extends Widget {
|
||||
private readonly bg = new Graphics();
|
||||
@@ -24,7 +28,7 @@ export class ParticlePreview extends Widget {
|
||||
/** Null: the preview runs on the library clock. A number: it stands at that second. */
|
||||
private held: number | null = null;
|
||||
|
||||
constructor() {
|
||||
constructor(private readonly state: EditorState) {
|
||||
super();
|
||||
this.minHeight = MIN_HEIGHT;
|
||||
this.content.addChild(this.field.container);
|
||||
@@ -51,12 +55,17 @@ export class ParticlePreview extends Widget {
|
||||
}
|
||||
|
||||
hold(at: number | null): void {
|
||||
// Letting go has to move the origin, not just stop faking one: the library clock keeps counting
|
||||
// under a freeze, so the panel would otherwise resume at the second it would have reached.
|
||||
if (at === null && this.held !== null) this.field.rebase(particleLibrary.now(), this.held);
|
||||
this.held = at;
|
||||
this.invalidate();
|
||||
}
|
||||
|
||||
/** Back to second 0 – a held panel stands there too, rather than keeping the second it was on. */
|
||||
restart(): void {
|
||||
this.field.restart(particleLibrary.now());
|
||||
if (this.held !== null) this.held = 0;
|
||||
this.invalidate();
|
||||
}
|
||||
|
||||
@@ -76,11 +85,13 @@ export class ParticlePreview extends Widget {
|
||||
if (!this.asset) return;
|
||||
const build = particleLibrary.buildFor(this.asset);
|
||||
const rows = build.compiled?.params ?? [];
|
||||
const props = previewProps(rows);
|
||||
const props = previewProps(this.asset, rows);
|
||||
const params = rows.map((row) => Number(props[row.key] ?? row.default));
|
||||
// The source sits in the middle of the strip; a fountain then has room above and below it.
|
||||
const ref = resolveTexture(this.state.project, props);
|
||||
const texture = ref ? textureLibrary.get(ref, SHEET_EDGE) : null;
|
||||
// The source sits in the middle of the panel; a fountain then has room above and below it.
|
||||
const m = translation(this.w / 2, this.h / 2);
|
||||
this.field.sync(build.compiled, props, params, m, Texture.WHITE, this.clock());
|
||||
this.field.sync(build.compiled, props, params, m, texture ?? Texture.WHITE, this.clock());
|
||||
}
|
||||
|
||||
/** Holding at second t means handing the field a clock that reads exactly t since it started. */
|
||||
@@ -96,9 +107,15 @@ export class ParticlePreview extends Widget {
|
||||
}
|
||||
}
|
||||
|
||||
/** A source as the asset alone describes it: the type's defaults, the graph's parameters at theirs. */
|
||||
function previewProps(params: readonly { key: string; default: number }[]): PropertyBag {
|
||||
/**
|
||||
* A source as the asset alone describes it: the rows the asset carries, over the type's defaults for
|
||||
* the ones it does not, with the graph's own parameters at their declared defaults underneath.
|
||||
*/
|
||||
function previewProps(
|
||||
asset: ParticlesAsset,
|
||||
params: readonly { key: string; default: number }[],
|
||||
): PropertyBag {
|
||||
const props = defaultsFor('particles');
|
||||
for (const row of params) props[row.key] = row.default;
|
||||
return props;
|
||||
return { ...props, ...asset.props };
|
||||
}
|
||||
Reference in new issue
Block a user