Respond to clicks
Handle canvas clicks and build a grid.When a knob or a drag zone isn't the right control (a step grid, a note matrix, a pad
bank), declare a command region on a canvas. A click there dispatches onCommand,
which runs off the audio thread, so you can branch and allocate freely.
Note
Keep grid state in m.state with a BitGrid, not one param per cell. A 12x8
grid is 96 cells, not 96 knobs.
Name your commands
Put the ids in a commands.ts enum. Both index.ts and ui.ts import it, so the id
space is one source of truth.
export enum Cmd {
ToggleCell,
ClearAll,
}Add a command region in ui.ts
Overlay it on the canvas. x/y/w/h are canvas fractions (0..1); here it covers
the whole grid.
import { Cmd } from "./commands";
Canvas({
id: "grid",
inputs: [channel("grid"), Theme.colors.accent],
commands: [{ command: Cmd.ToggleCell, x: 0, y: 0, w: 1, h: 1 }],
});Handle it in index.ts
Decode the hit with event.col(cols) / event.row(rows), edit the grid, and
commitState to save it.
import { Cmd } from "./commands";
class Node extends Device {
private grid: BitGrid = new BitGrid(12, 8);
private viz: Float32Array = new Float32Array(12 * 8);
onCommand(id: i32, e: CommandEvent, ctx: ControlCtx): void {
if (id == Cmd.ClearAll) {
this.grid.clear();
ctx.commitState();
return;
}
if (id == Cmd.ToggleCell) {
this.grid.toggle(e.col(12), e.row(8));
ctx.commitState();
}
}
}To draw the grid, mirror it to the channel each block and let the canvas read it:
process(ctx: Ctx): void {
this.grid.toChannel(this.viz);
ctx.writeChannel(0, this.viz);
}
getState(): Uint8Array { return this.grid.bytes(); }
setState(b: Uint8Array): void { this.grid.load(b); }A click reflects within a block. For the full set of handlers, events, and BitGrid
methods, see the control surface
reference.