The Variables and Properties nodes listed their picked rows in the inspector, but unlike Start, End and struct rows they could not be reordered. The drag dots also sat right on the edge of the row and touched the kind mark. - buildPickedPortSection gathers each row with its warning line as one block and hands the blocks to sortBlocks. A move reorders the ports on the node and is one undo step. Properties uses the same section and gains the same reordering. - Grip draws the dots 3 px in from the left edge and moves the kind mark 3 px clear of them (DOTS_PAD). The column keeps its width, so the name fields do not shift. Rows without dots are unchanged. Tests: - block-drag, reorder, variable-choices: 3 files, 13 tests, all green. block-drag now expects the mark 11 px in with dots, 0 without. - e2e 23 on melektaus-atx: ALL PASSED, 32 checks (+3 for dragging vy before vx and undoing it). It ran before the padding change, which touches no checks there. - The gate did not run before this commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
72 lines
2.4 KiB
TypeScript
72 lines
2.4 KiB
TypeScript
import { Graphics } from 'pixi.js';
|
|
import { crisp } from '../pixelGrid';
|
|
import { Theme } from '../Theme';
|
|
import { type Size, Widget } from '../Widget';
|
|
|
|
const DOT = 2;
|
|
const DOT_GAP = 1;
|
|
const IDLE_ALPHA = 0.5;
|
|
/** The air on either side of the dots, so they do not touch the edge of the lit block or the mark. */
|
|
const DOTS_PAD = 3;
|
|
/** Where the mark starts once the dots are drawn: their two columns and the air around them. */
|
|
const DOTS_INSET = DOTS_PAD + 2 * DOT + DOT_GAP + DOTS_PAD;
|
|
|
|
/**
|
|
* What a row is taken hold of by to drag it to another place (#310): its mark, the same size. Once
|
|
* `sortBlocks` makes it grabbable it carries a column of dots at its left edge, lit while the
|
|
* pointer is on it.
|
|
*/
|
|
export class Grip extends Widget {
|
|
private readonly dots = crisp(new Graphics());
|
|
private grabbable = false;
|
|
private lit = false;
|
|
|
|
constructor(readonly content: Widget) {
|
|
super();
|
|
// One target for over/out: moving onto the mark must not read as leaving the grip.
|
|
this.interactiveChildren = false;
|
|
this.addChild(this.dots, content);
|
|
}
|
|
|
|
override measure(): Size {
|
|
const size = this.content.measure();
|
|
return { w: Math.max(this.minWidth, size.w), h: Math.max(this.minHeight, size.h) };
|
|
}
|
|
|
|
override layout(): void {
|
|
// The dots take the left edge, the mark the rest: it moves over instead of sitting on them.
|
|
const inset = this.grabbable ? DOTS_INSET : 0;
|
|
this.content.position.x = inset;
|
|
this.content.resize(Math.max(0, this.w - inset), this.h);
|
|
}
|
|
|
|
setGrabbable(on: boolean): void {
|
|
if (this.grabbable === on) return;
|
|
this.grabbable = on;
|
|
this.layout();
|
|
this.invalidate();
|
|
}
|
|
|
|
setLit(on: boolean): void {
|
|
if (this.lit === on) return;
|
|
this.lit = on;
|
|
this.invalidate();
|
|
}
|
|
|
|
/** Whether the dots are drawn, and lit (test hook). */
|
|
get dotsShown(): 'none' | 'idle' | 'lit' {
|
|
return this.grabbable ? (this.lit ? 'lit' : 'idle') : 'none';
|
|
}
|
|
|
|
override redraw(): void {
|
|
this.dots.clear();
|
|
if (!this.grabbable) return;
|
|
const top = Math.round(this.h / 2 - (3 * DOT + 2 * DOT_GAP) / 2);
|
|
for (let col = 0; col < 2; col++)
|
|
for (let row = 0; row < 3; row++)
|
|
this.dots.rect(DOTS_PAD + col * (DOT + DOT_GAP), top + row * (DOT + DOT_GAP), DOT, DOT);
|
|
this.dots.fill(this.lit ? Theme.colors.text : Theme.colors.textMuted);
|
|
this.dots.alpha = this.lit ? 1 : IDLE_ALPHA;
|
|
}
|
|
}
|