A particle is a function of its age, not a thing that is stepped (#73)
game-maker / suite (push) Failing after 4m22s
game-maker / suite (push) Failing after 4m22s
The asset type `particles` is placeable and carries a graph at the same time — `behavior` was the blueprint. Because a continuous emitter gives birth to particle n at time n/rate, the whole field is a function of the clock rather than of the frames: no ring-buffer bookkeeping, exact time jumps, reproducibility as a measurable property. compile.ts follows compileShader's frame but emits JS closures into a Float64Array instead of GLSL, and without eval; a node with two consumers is evaluated once. Drawing is one Pixi ParticleContainer per source, so one draw call. Collision, wind and braking in flight are out for good — that is the price of the formula model, not an omission. The tab count goes from nine to ten, which is exactly the case #84 was built for one day earlier: at 1280 px the row no longer holds every tab, so guard 14b now asks for hidden.length > 0 instead of === 0.
This commit is contained in:
1 parent
f83c568999
commit
0f9e1611d1
39 files changed
+2386
-32
No files matched your search
@@ -56,6 +56,7 @@ export const Theme = {
|
||||
categoryAction: 0x2f6fd6,
|
||||
categoryState: 0x8e5ad6,
|
||||
categoryShader: 0x1f8f8f,
|
||||
categoryParticles: 0xc26a1f,
|
||||
portExec: 0xffffff,
|
||||
portNumber: 0x6fcf97,
|
||||
portBoolean: 0xe57373,
|
||||
|
||||
@@ -10,6 +10,7 @@ export { Button, type ButtonOptions } from './widgets/Button';
|
||||
export { Dropdown } from './widgets/Dropdown';
|
||||
export { Checkbox } from './widgets/Checkbox';
|
||||
export { Panel } from './widgets/Panel';
|
||||
export { Slider } from './widgets/Slider';
|
||||
export { Splitter } from './widgets/Splitter';
|
||||
export { ScrollContainer } from './widgets/ScrollContainer';
|
||||
export { type DropSpot, ListView, type ListItem, type PickModifiers } from './widgets/ListView';
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import { type FederatedPointerEvent, Graphics } from 'pixi.js';
|
||||
import { clamp, centreY } from '../math';
|
||||
import { Theme } from '../Theme';
|
||||
import { Widget } from '../Widget';
|
||||
|
||||
const TRACK_HEIGHT = 4;
|
||||
const KNOB_RADIUS = 6;
|
||||
|
||||
/** A value picked by dragging along a track. Reports every step of the drag, not just the end. */
|
||||
export class Slider extends Widget {
|
||||
private readonly bar = new Graphics();
|
||||
private dragging = false;
|
||||
onChange?: (value: number) => void;
|
||||
|
||||
constructor(
|
||||
private value = 0,
|
||||
private min = 0,
|
||||
private max = 1,
|
||||
width = 160,
|
||||
) {
|
||||
super();
|
||||
this.focusable = true;
|
||||
this.cursor = Theme.hoverCursor;
|
||||
this.minWidth = width;
|
||||
this.minHeight = Theme.controlHeight;
|
||||
this.addChild(this.bar);
|
||||
this.trackHover();
|
||||
this.on('pointerdown', (e: FederatedPointerEvent) => {
|
||||
this.dragging = true;
|
||||
this.pick(e);
|
||||
e.stopPropagation();
|
||||
});
|
||||
this.on('globalpointermove', (e: FederatedPointerEvent) => {
|
||||
if (this.dragging) this.pick(e);
|
||||
});
|
||||
this.onPointerRelease(() => {
|
||||
this.dragging = false;
|
||||
this.invalidate();
|
||||
});
|
||||
}
|
||||
|
||||
/** Does not fire `onChange`, unlike a drag. */
|
||||
setValue(value: number): void {
|
||||
const next = clamp(value, this.min, this.max);
|
||||
if (next === this.value) return;
|
||||
this.value = next;
|
||||
this.invalidate();
|
||||
}
|
||||
|
||||
setRange(min: number, max: number): void {
|
||||
this.min = min;
|
||||
this.max = max > min ? max : min + 1;
|
||||
this.setValue(this.value);
|
||||
this.invalidate();
|
||||
}
|
||||
|
||||
get currentValue(): number {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
private pick(e: FederatedPointerEvent): void {
|
||||
const span = Math.max(1, this.w - KNOB_RADIUS * 2);
|
||||
const at = clamp((e.global.x - this.getGlobalPosition().x - KNOB_RADIUS) / span, 0, 1);
|
||||
const next = this.min + at * (this.max - this.min);
|
||||
if (next === this.value) return;
|
||||
this.value = next;
|
||||
this.invalidate();
|
||||
this.onChange?.(next);
|
||||
}
|
||||
|
||||
override redraw(): void {
|
||||
const c = Theme.colors;
|
||||
const y = centreY(this.h, TRACK_HEIGHT);
|
||||
const span = Math.max(1, this.w - KNOB_RADIUS * 2);
|
||||
const at = KNOB_RADIUS + ((this.value - this.min) / (this.max - this.min)) * span;
|
||||
this.bar.clear();
|
||||
this.bar.rect(KNOB_RADIUS, y, span, TRACK_HEIGHT).fill(c.control);
|
||||
this.bar.rect(KNOB_RADIUS, y, at - KNOB_RADIUS, TRACK_HEIGHT).fill(c.accent);
|
||||
this.bar
|
||||
.circle(at, this.h / 2, KNOB_RADIUS)
|
||||
.fill(this.hovered || this.dragging ? c.controlHover : c.control)
|
||||
.stroke({ width: 1, color: this.focused ? c.accent : c.border });
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user