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; } }