diff --git a/src/strands/strands_api.js b/src/strands/strands_api.js index 84fa5597a9..fab1714b1c 100644 --- a/src/strands/strands_api.js +++ b/src/strands/strands_api.js @@ -103,15 +103,78 @@ function installBuiltinGlobalAccessors(strandsContext) { for (const name of Object.keys(BUILTIN_GLOBAL_SPECS)) { const spec = BUILTIN_GLOBAL_SPECS[name] - Object.defineProperty(window, name, { - get: () => { + const sym = Symbol(`_strands_${name}`) + + // Define on window for global mode only + const inst = getRuntimeP5Instance() + if (inst?._isGlobal) { + Object.defineProperty(window, name, { + get: () => { + if (strandsContext.active) { + return getBuiltinGlobalNode(strandsContext, name); + } + const inst = getRuntimeP5Instance() + return spec.get(inst); + }, + configurable: true, + }) + } + + // Define on p5.prototype for instance mode + const originalProtoDesc = Object.getOwnPropertyDescriptor(strandsContext.p5.prototype, name); + + // For data properties (like mouseX), save the initial value into Symbol-keyed store + if (originalProtoDesc && 'value' in originalProtoDesc) { + strandsContext.p5.prototype[sym] = originalProtoDesc.value; + } + + Object.defineProperty(strandsContext.p5.prototype, name, { + get: function() { if (strandsContext.active) { return getBuiltinGlobalNode(strandsContext, name); } - const inst = getRuntimeP5Instance() - return spec.get(inst); + // If our setter stored a value on this instance, return it + if (Object.prototype.hasOwnProperty.call(this, sym)) { + return this[sym]; + } + // Delegate to original getter (e.g. width -> this._renderer?.width) + if (originalProtoDesc?.get) { + return originalProtoDesc.get.call(this); + } + // Fall back to Symbol on prototype chain (for data properties like mouseX) + return this[sym]; }, + set: function(val) { + this[sym] = val; + }, + configurable: true, }) + + // Define on p5.Graphics.prototype for graphics mode + const GraphicsProto = strandsContext.p5?.Graphics?.prototype; + if (GraphicsProto) { + const originalDesc = Object.getOwnPropertyDescriptor(GraphicsProto, name); + Object.defineProperty(GraphicsProto, name, { + get: function() { + if (strandsContext.active) { + return getBuiltinGlobalNode(strandsContext, name); + } + // Delegate to original getter if it exists (class-level getters like width, deltaTime) + if (originalDesc?.get) { + return originalDesc.get.call(this); + } + return this[sym]; + }, + set: function(val) { + if (originalDesc?.set) { + originalDesc.set.call(this, val); + } else { + this[sym] = val; + } + }, + configurable: true, + }) + } } strandsContext._builtinGlobalsAccessorsInstalled = true } diff --git a/test/unit/visual/cases/webgl.js b/test/unit/visual/cases/webgl.js index 850b0b4a97..5d6b19e609 100644 --- a/test/unit/visual/cases/webgl.js +++ b/test/unit/visual/cases/webgl.js @@ -1172,16 +1172,14 @@ visualTest('randomGaussian() in a fragment loop averages to the mean', (p5, scre }); visualTest('uses width/height in getFinalColor', (p5, screenshot) => { - let firstShader; - function firstShaderCallback() { - getFinalColor((color) => { - color = [width / 60, height / 60, 0, 1]; - return color; - }); - } p5.createCanvas(60, 60, p5.WEBGL); p5.pixelDensity(1); - firstShader = p5.baseColorShader().modify(firstShaderCallback); + const firstShader = p5.baseColorShader().modify(() => { + p5.getFinalColor((color) => { + color = [p5.width / 60, p5.height / 60, 0, 1]; + return color; + }); + }, { p5 }); p5.background(0); p5.shader(firstShader); p5.noStroke(); diff --git a/test/unit/webgl/p5.Shader.js b/test/unit/webgl/p5.Shader.js index 499dab8da8..0317cf2d24 100644 --- a/test/unit/webgl/p5.Shader.js +++ b/test/unit/webgl/p5.Shader.js @@ -516,8 +516,8 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca myp5.createCanvas(5, 5, myp5.WEBGL); myp5.baseMaterialShader().modify(() => { myp5.getPixelInputs(inputs => { - const mxInHook = window.mouseX; - const wInHook = window.width; + const mxInHook = myp5.mouseX; + const wInHook = myp5.width; assert.isTrue(mxInHook.isStrandsNode); assert.isTrue(wInHook.isStrandsNode); inputs.color = [1, 0, 0, 1]; @@ -525,14 +525,13 @@ test('returns numbers for builtin globals outside hooks and a strandNode when ca }); }, { myp5 }); - const mx = window.mouseX; - const w = window.width; + const mx = myp5.mouseX; + const w = myp5.width; assert.isNumber(mx); assert.isNumber(w); assert.strictEqual(w, myp5.width); }); - test('map() works inside a strands modify callback', () => { myp5.createCanvas(50, 50, myp5.WEBGL); const testShader = myp5.baseMaterialShader().modify(() => {