From bb82603909135af81e000f5f26e374f8b97d28ea Mon Sep 17 00:00:00 2001 From: prompt.ac/@jeffrey Date: Tue, 28 Jul 2026 04:07:52 +0000 Subject: [PATCH] Make the No Paint canvas a giant button --- system/public/aesthetic.computer/disks/nopaint.mjs | 38 ++++++++++++++++++++++++++++++++++++-- tests/browser/nopaint-journey.test.mjs | 6 ++++++ 2 file(s) changed, 42 insertion(s)(+), 2 deletion(s)(-) diff --git a/system/public/aesthetic.computer/disks/nopaint.mjs b/system/public/aesthetic.computer/disks/nopaint.mjs --- a/system/public/aesthetic.computer/disks/nopaint.mjs +++ b/system/public/aesthetic.computer/disks/nopaint.mjs @@ -34,6 +34,7 @@ let paintingResolution = null; let finishMode = false; let doneCount = 0; let paintingDragPaused = false; +let paintingPressed = false; let hoveredDecision = null; let cursorSheet = null; let cursorPoint = null; @@ -49,6 +50,7 @@ "paint-down": "generic - paint button pressed (psst).webm", paint: "generic - paint button released (cha).webm", rollover: "generic - button rollover.webm", "button-down": "generic - button press.webm", + "button-up": "generic - button release.webm", back: "generic - pause release.webm", "done-down": "generic - save button pressed.webm", done: "generic - save button released.webm", @@ -535,6 +537,11 @@ visible: Boolean(cursorPoint), animation: hoveredDecision ? "Over Button" : "Normal", frame: cursorFrame, }, + paintingButton: layout ? { + ...layout.stage, + down: paintingPressed, + over: hoveredDecision === "painting", + } : null, lastDownload, ready: Boolean(proposal && testApi?.system?.nopaint?.buffer), controls: finishMode @@ -612,6 +619,7 @@ saveCount = 0; finishMode = false; doneCount = 0; paintingDragPaused = false; + paintingPressed = false; hoveredDecision = null; cursorSheet = null; cursorPoint = null; @@ -859,6 +867,9 @@ // viewport may fit/letterbox responsively, but its pixels never reflow. $.wipe(18); $.paste($.system.painting, stage.x, stage.y, scale); $.paste($.system.nopaint.buffer, stage.x, stage.y, scale); + if (paintingPressed || hoveredDecision === "painting") { + $.ink(255, 255, 255, paintingPressed ? 235 : 145).box(stage, "outline"); + } const definition = proposalDefinition(proposal.kind); $.ink(18).box(bar, "fill"); $.ink(255, 180).write( @@ -958,6 +969,16 @@ ) leaveFinishMode(); return; } + const overPainting = e.x >= stage.x && e.x <= stage.x + stage.w && + e.y >= stage.y && e.y <= stage.y + stage.h; + if (e.is("touch:1") && overPainting) { + paintingPressed = true; + playCue($, "button-down"); + setDecisionHeld(true); + $.needsPaint(); + publishTestState(); + } + if ( loopState === "proposing" && e.is("draw:1") && @@ -966,6 +987,8 @@ e.drag.x >= stage.x && e.drag.x <= stage.x + stage.w && e.drag.y >= stage.y && e.drag.y <= stage.y + stage.h ) { paintingDragPaused = true; + paintingPressed = false; + setDecisionHeld(false); playCue($, "pause-down"); togglePaused($); return; @@ -995,13 +1018,17 @@ push: () => commitProposal($), }); if ( e.is("lift:1") && - e.x >= stage.x && e.x <= stage.x + stage.w && - e.y >= stage.y && e.y <= stage.y + stage.h + overPainting ) { if (paintingDragPaused) { paintingDragPaused = false; + paintingPressed = false; return; } + if (!paintingPressed) return; + paintingPressed = false; + setDecisionHeld(false); + playCue($, "button-up"); stopBrushCue(); finishMode = true; stateBeforePause = loopState; @@ -1009,6 +1036,13 @@ transition("paused"); $.needsPaint(); publishTestState(); return; + } + if (e.is("lift:1") && paintingPressed) { + paintingPressed = false; + setDecisionHeld(false); + playCue($, "back"); + $.needsPaint(); + publishTestState(); } if (isAny(e, [ "keyboard:down:n", diff --git a/tests/browser/nopaint-journey.test.mjs b/tests/browser/nopaint-journey.test.mjs --- a/tests/browser/nopaint-journey.test.mjs +++ b/tests/browser/nopaint-journey.test.mjs @@ -328,6 +328,9 @@ }); await scenario("The painting is the gateway to the canonical Done command", async (expect) => { const before = await ac.nopaintState(); + expect(before?.paintingButton?.w === before?.layout?.paintingViewport?.w && + before?.paintingButton?.h === before?.layout?.paintingViewport?.h, + "the entire visible painting is one giant button"); const rect = await ac.page.evaluate(() => Array.from(document.querySelectorAll("canvas")) .map((canvas) => { const box = canvas.getBoundingClientRect(); @@ -348,6 +351,9 @@ await ac.wait(250); let finishing = await ac.nopaintState(); await receipt("05-done-ready"); expect(finishing?.finishMode === true, "tapping the painting enters completion mode"); + expect(finishing?.audio?.events?.some(({ name }) => name === "button-down") && + finishing?.audio?.events?.some(({ name }) => name === "button-up"), + "painting tap emits button press and release cues"); expect( Object.keys(finishing?.controls || {}).join(",") === "back,done", "Back and Done replace the painting decisions", -- tangled.sh