import type { Graphics } from 'pixi.js'; import { crisp, hairline } from './pixelGrid'; import { Theme } from './Theme'; export interface ControlBoxOptions { fill: number; border: number; radius?: number; x?: number; y?: number; } /** * The shared look of every control. The stroke is a hairline inset by half its width, so it lands * on whole device pixels and stays crisp instead of blurring over two. Only for a `g` that holds * nothing but boxes – see `crisp`. */ export function drawControlBox(g: Graphics, w: number, h: number, options: ControlBoxOptions): void { const { fill, border, radius = Theme.radius, x = 0, y = 0 } = options; const line = hairline(); crisp(g).roundRect(x, y, w, h, radius).fill(fill); g.roundRect(x + line / 2, y + line / 2, w - line, h - line, radius).stroke({ width: line, color: border }); } /** A bar across the top of something – the toolbar, a tab's header, the breadcrumb: header colour, a line below. */ export function drawHeaderBar(g: Graphics, w: number, h: number): void { const line = hairline(); crisp(g).clear().rect(0, 0, w, h).fill(Theme.colors.panelHeader); g.rect(0, h - line, w, line).fill(Theme.colors.border); } export interface GridOptions { /** Where the origin of the drawn space sits on screen, in the same pixels as `w`/`h`. */ originX: number; originY: number; /** Spacing in screen pixels (world/graph step times zoom). */ step: number; w: number; h: number; alpha: number; /** Alpha of the two lines through the origin; omitted = no axes. */ axisAlpha?: number; } /** * White at a low alpha rather than a fixed colour, so the grid reads on whatever background the * scene sets. `g` is cleared by the caller. */ export function drawGrid(g: Graphics, options: GridOptions): void { const { originX, originY, step, w, h, alpha, axisAlpha } = options; const startX = ((originX % step) + step) % step; const startY = ((originY % step) + step) % step; for (let x = startX; x < w; x += step) g.moveTo(x, 0).lineTo(x, h); for (let y = startY; y < h; y += step) g.moveTo(0, y).lineTo(w, y); g.stroke({ width: 1, color: GRID_COLOR, alpha }); if (axisAlpha === undefined) return; const axis = { width: 1, color: GRID_COLOR, alpha: axisAlpha }; if (originX >= 0 && originX <= w) g.moveTo(originX, 0).lineTo(originX, h).stroke(axis); if (originY >= 0 && originY <= h) g.moveTo(0, originY).lineTo(w, originY).stroke(axis); } const GRID_COLOR = 0xffffff; /** A rubber-band selection between two corners, in whatever space `g` draws. */ export function drawBand(g: Graphics, a: { x: number; y: number }, b: { x: number; y: number }): void { g.rect(Math.min(a.x, b.x), Math.min(a.y, b.y), Math.abs(b.x - a.x), Math.abs(b.y - a.y)) .fill({ color: Theme.colors.marquee, alpha: 0.12 }) .stroke({ width: 1, color: Theme.colors.marquee }); }