diff --git a/docs/parameterData.json b/docs/parameterData.json index ab5f651595..543787aa80 100644 --- a/docs/parameterData.json +++ b/docs/parameterData.json @@ -162,6 +162,7 @@ }, "background": { "overloads": [ + [], [ "p5.Color" ], @@ -201,6 +202,7 @@ }, "colorMode": { "overloads": [ + [], [ "RGB|HSB|HSL|RGBHDR|HWB|LAB|LCH|OKLAB|OKLCH", "Number?" @@ -217,6 +219,7 @@ }, "fill": { "overloads": [ + [], [ "Number", "Number", @@ -250,6 +253,7 @@ }, "stroke": { "overloads": [ + [], [ "Number", "Number", @@ -286,6 +290,7 @@ }, "blendMode": { "overloads": [ + [], [ "BLEND|DARKEST|LIGHTEST|DIFFERENCE|MULTIPLY|EXCLUSION|SCREEN|REPLACE|OVERLAY|HARD_LIGHT|SOFT_LIGHT|DODGE|BURN|ADD|REMOVE|SUBTRACT" ] @@ -303,6 +308,7 @@ }, "cursor": { "overloads": [ + [], [ "ARROW|CROSS|HAND|MOVE|TEXT|WAIT|String", "Number?", @@ -528,28 +534,32 @@ [ "Number", "p5.Vector|Number[]?" - ] + ], + [] ] }, "rotateX": { "overloads": [ [ "Number" - ] + ], + [] ] }, "rotateY": { "overloads": [ [ "Number" - ] + ], + [] ] }, "rotateZ": { "overloads": [ [ "Number" - ] + ], + [] ] }, "scale": { @@ -561,21 +571,24 @@ ], [ "p5.Vector|Number[]" - ] + ], + [] ] }, "shearX": { "overloads": [ [ "Number" - ] + ], + [] ] }, "shearY": { "overloads": [ [ "Number" - ] + ], + [] ] }, "translate": { @@ -587,7 +600,8 @@ ], [ "p5.Vector" - ] + ], + [] ] }, "push": { @@ -1014,7 +1028,8 @@ ], [ "p5.Color" - ] + ], + [] ] }, "noTint": { @@ -1026,7 +1041,8 @@ "overloads": [ [ "CORNER|CORNERS|CENTER" - ] + ], + [] ] }, "blend": { @@ -1748,7 +1764,8 @@ "overloads": [ [ "CENTER|RADIUS|CORNER|CORNERS" - ] + ], + [] ] }, "noSmooth": { @@ -1758,6 +1775,7 @@ }, "rectMode": { "overloads": [ + [], [ "CENTER|RADIUS|CORNER|CORNERS" ] @@ -1772,21 +1790,24 @@ "overloads": [ [ "ROUND|SQUARE|PROJECT" - ] + ], + [] ] }, "strokeJoin": { "overloads": [ [ "MITER|BEVEL|ROUND" - ] + ], + [] ] }, "strokeWeight": { "overloads": [ [ "Number" - ] + ], + [] ] }, "bezier": { @@ -2108,7 +2129,8 @@ [ "LEFT|CENTER|RIGHT?", "TOP|BOTTOM|CENTER|BASELINE?" - ] + ], + [] ] }, "textAscent": { diff --git a/src/color/setting.js b/src/color/setting.js index e60af75c3f..9509f9dd3f 100644 --- a/src/color/setting.js +++ b/src/color/setting.js @@ -668,8 +668,7 @@ function setting(p5, fn){ * @chainable */ fn.background = function(...args) { - this._renderer.background(...args); - return this; + return this._renderer.background(...args); }; /** @@ -1470,8 +1469,7 @@ function setting(p5, fn){ * @chainable */ fn.fill = function(...args) { - this._renderer.fill(...args); - return this; + return this._renderer.fill(...args); }; /** @@ -1839,8 +1837,7 @@ function setting(p5, fn){ * @chainable */ fn.stroke = function(...args) { - this._renderer.stroke(...args); - return this; + return this._renderer.stroke(...args); }; /** @@ -2443,7 +2440,7 @@ function setting(p5, fn){ ); mode = constants.BLEND; } - this._renderer.blendMode(mode); + return this._renderer.blendMode(mode); }; } diff --git a/src/core/environment.js b/src/core/environment.js index a3226aa77b..ab4575d9bb 100644 --- a/src/core/environment.js +++ b/src/core/environment.js @@ -314,6 +314,10 @@ function environment(p5, fn, lifecycles){ fn.cursor = function(type, x, y) { let cursor = 'auto'; const canvas = this._curElement.elt; + if (typeof type === 'undefined') { + let curstr = canvas.style.cursor; + return curstr.length ? curstr : 'default'; + } if (standardCursors.includes(type)) { // Standard css cursor cursor = type; diff --git a/src/core/p5.Renderer.js b/src/core/p5.Renderer.js index 75d01bc04c..87c5241e75 100644 --- a/src/core/p5.Renderer.js +++ b/src/core/p5.Renderer.js @@ -35,6 +35,7 @@ class ClonableObject { class Renderer { static states = { + background: null, strokeColor: null, strokeSet: false, fillColor: null, @@ -45,6 +46,11 @@ class Renderer { rectMode: constants.CORNER, ellipseMode: constants.CENTER, strokeWeight: 1, + bezierOrder: 3, + splineProperties: new ClonableObject({ + ends: constants.INCLUDE, + tightness: 0 + }), textFont: { family: 'sans-serif' }, textLeading: 15, @@ -52,15 +58,8 @@ class Renderer { textSize: 12, textAlign: constants.LEFT, textBaseline: constants.BASELINE, - bezierOrder: 3, - splineProperties: new ClonableObject({ - ends: constants.INCLUDE, - tightness: 0 - }), textWrap: constants.WORD, - - // added v2.0 - fontStyle: constants.NORMAL, // v1: textStyle + fontStyle: constants.NORMAL, // v1: was textStyle fontStretch: constants.NORMAL, fontWeight: constants.NORMAL, lineHeight: constants.NORMAL, @@ -323,9 +322,12 @@ class Renderer { } fill(...args) { - this.states.setValue('fillSet', true); - this.states.setValue('fillColor', this._pInst.color(...args)); - this.updateShapeVertexProperties(); + if (args.length > 0) { + this.states.setValue('fillSet', true); + this.states.setValue('fillColor', this._pInst.color(...args)); + this.updateShapeVertexProperties(); + } + return this.states.fillColor; } noFill() { @@ -333,14 +335,16 @@ class Renderer { } strokeWeight(w) { - if (w === undefined) { + if (typeof w === 'undefined') { return this.states.strokeWeight; - } else { - this.states.setValue('strokeWeight', w); } + this.states.setValue('strokeWeight', w); } stroke(...args) { + if (args.length === 0) { + return this.states.strokeColor; + } this.states.setValue('strokeSet', true); this.states.setValue('strokeColor', this._pInst.color(...args)); this.updateShapeVertexProperties(); diff --git a/src/core/p5.Renderer2D.js b/src/core/p5.Renderer2D.js index b9a7e12d00..e75a108a7a 100644 --- a/src/core/p5.Renderer2D.js +++ b/src/core/p5.Renderer2D.js @@ -165,18 +165,20 @@ class Renderer2D extends Renderer { ////////////////////////////////////////////// background(...args) { + if (args.length === 0) { + return this.states.background; // getter (#8278) + } + let bgForState = null; this.push(); this.resetMatrix(); - if (args[0] instanceof Image) { + const img = args[0]; if (args[1] >= 0) { // set transparency of background - const img = args[0]; this.drawingContext.globalAlpha = args[1] / 255; - this._pInst.image(img, 0, 0, this.width, this.height); - } else { - this._pInst.image(args[0], 0, 0, this.width, this.height); } + this._pInst.image(img, 0, 0, this.width, this.height); + bgForState = img; // save for getter (#8278) } else { // create background rect const color = this._pInst.color(...args); @@ -199,8 +201,11 @@ class Renderer2D extends Renderer { if (this._isErasing) { this._pInst.erase(); } + bgForState = color; // save for getter (#8278) } this.pop(); + + this.states.setValue('background', bgForState); // set state (#8278) } clear() { @@ -213,6 +218,9 @@ class Renderer2D extends Renderer { fill(...args) { super.fill(...args); const color = this.states.fillColor; + if (args.length === 0) { + return color; // getter + } this._setFill(color.toString()); // Add accessible outputs if the method exists; on success, @@ -225,6 +233,9 @@ class Renderer2D extends Renderer { stroke(...args) { super.stroke(...args); const color = this.states.strokeColor; + if (args.length === 0) { + return color; // getter + } this._setStroke(color.toString()); // Add accessible outputs if the method exists; on success, @@ -475,6 +486,9 @@ class Renderer2D extends Renderer { ////////////////////////////////////////////// blendMode(mode) { + if (typeof mode === 'undefined') { // getter + return this._cachedBlendMode; + } if (mode === constants.SUBTRACT) { console.warn('blendMode(SUBTRACT) only works in WEBGL mode.'); } else if ( @@ -921,6 +935,9 @@ class Renderer2D extends Renderer { ////////////////////////////////////////////// strokeCap(cap) { + if (typeof cap === 'undefined') { // getter + return this.drawingContext.lineCap; + } if ( cap === constants.ROUND || cap === constants.SQUARE || @@ -932,6 +949,9 @@ class Renderer2D extends Renderer { } strokeJoin(join) { + if (typeof join === 'undefined') { // getter + return this.drawingContext.lineJoin; + } if ( join === constants.ROUND || join === constants.BEVEL || @@ -944,7 +964,10 @@ class Renderer2D extends Renderer { strokeWeight(w) { super.strokeWeight(w); - if (typeof w === 'undefined' || w === 0) { + if (typeof w === 'undefined') { + return this.states.strokeWeight; + } + if (w === 0) { // hack because lineWidth 0 doesn't work this.drawingContext.lineWidth = 0.0001; } else { @@ -1006,17 +1029,42 @@ class Renderer2D extends Renderer { } rotate(rad) { + if (typeof rad === 'undefined') { + const matrix = this.drawingContext.getTransform(); + let angle = this._pInst.decomposeMatrix(matrix).rotation; + if (angle < 0) { + angle += Math.PI * 2; // ensure a positive angle + } + if (this._pInst._angleMode === this._pInst.DEGREES) { + angle *= constants.RAD_TO_DEG; // to degrees + } + return Math.abs(angle); + } this.drawingContext.rotate(rad); + return this; } scale(x, y) { + if (typeof x === 'undefined' && typeof y === 'undefined') { + const matrix = this.drawingContext.getTransform(); + return this._pInst.decomposeMatrix(matrix).scale; + } + // support passing objects with x,y properties (including p5.Vector) + if (typeof x === 'object' && 'x' in x && 'y' in x) { + y = x.y; + x = x.x; + } this.drawingContext.scale(x, y); return this; } translate(x, y) { - // support passing a vector as the 1st parameter - if (x instanceof p5.Vector) { + if (typeof x === 'undefined' && typeof y === 'undefined') { + const matrix = this.drawingContext.getTransform(); + return this._pInst.decomposeMatrix(matrix).translation; + } + // support passing objects with x,y properties (including p5.Vector) + if (typeof x === 'object' && 'x' in x && 'y' in x) { y = x.y; x = x.x; } diff --git a/src/core/transform.js b/src/core/transform.js index 8832d3a4ee..133a9da9cc 100644 --- a/src/core/transform.js +++ b/src/core/transform.js @@ -462,8 +462,7 @@ function transform(p5, fn){ */ fn.rotate = function(angle, axis) { // p5._validateParameters('rotate', arguments); - this._renderer.rotate(this._toRadians(angle), axis); - return this; + return this._renderer.rotate(this._toRadians(angle), axis); }; /** @@ -598,8 +597,7 @@ function transform(p5, fn){ fn.rotateX = function(angle) { this._assert3d('rotateX'); // p5._validateParameters('rotateX', arguments); - this._renderer.rotateX(this._toRadians(angle)); - return this; + return this._renderer.rotateX(this._toRadians(angle)); }; /** @@ -734,8 +732,7 @@ function transform(p5, fn){ fn.rotateY = function(angle) { this._assert3d('rotateY'); // p5._validateParameters('rotateY', arguments); - this._renderer.rotateY(this._toRadians(angle)); - return this; + return this._renderer.rotateY(this._toRadians(angle)); }; /** @@ -870,8 +867,7 @@ function transform(p5, fn){ fn.rotateZ = function(angle) { this._assert3d('rotateZ'); // p5._validateParameters('rotateZ', arguments); - this._renderer.rotateZ(this._toRadians(angle)); - return this; + return this._renderer.rotateZ(this._toRadians(angle)); }; /** @@ -1060,9 +1056,7 @@ function transform(p5, fn){ z = 1; } - this._renderer.scale(x, y, z); - - return this; + return this._renderer.scale(x, y, z); }; /** @@ -1137,6 +1131,14 @@ function transform(p5, fn){ */ fn.shearX = function(angle) { // p5._validateParameters('shearX', arguments); + if (typeof angle === 'undefined') { + let matrix = this._renderer.drawingContext.getTransform(); + let rad = this.decomposeMatrix(matrix).shear.x; + if (fn._angleMode === fn.DEGREES) { + rad *= fn.RAD_TO_DEG; // to degrees + } + return rad; + } const rad = this._toRadians(angle); this._renderer.applyMatrix(1, 0, Math.tan(rad), 1, 0, 0); return this; @@ -1214,6 +1216,14 @@ function transform(p5, fn){ */ fn.shearY = function(angle) { // p5._validateParameters('shearY', arguments); + if (typeof angle === 'undefined') { + let matrix = this._renderer.drawingContext.getTransform(); + let rad = this.decomposeMatrix(matrix).shear.y; + if (fn._angleMode === fn.DEGREES) { + rad *= fn.RAD_TO_DEG; // to degrees + } + return rad; + } const rad = this._toRadians(angle); this._renderer.applyMatrix(1, Math.tan(rad), 0, 1, 0, 0); return this; @@ -1398,11 +1408,10 @@ function transform(p5, fn){ fn.translate = function(x, y, z) { // p5._validateParameters('translate', arguments); if (this._renderer.isP3D) { - this._renderer.translate(x, y, z); + return this._renderer.translate(x, y, z); } else { - this._renderer.translate(x, y); + return this._renderer.translate(x, y); } - return this; }; /** diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index 8487dde0a5..9617b2ce3c 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -1291,6 +1291,9 @@ function loadingDisplaying(p5, fn){ */ fn.tint = function(...args) { // p5._validateParameters('tint', args); + if (args.length === 0) { + return this.color(this._renderer.states.tint); // getter + } const c = this.color(...args); this._renderer.states.setValue('tint', c._getRGBA([255, 255, 255, 255])); }; @@ -1437,6 +1440,9 @@ function loadingDisplaying(p5, fn){ */ fn.imageMode = function(m) { // p5._validateParameters('imageMode', arguments); + if (typeof m === 'undefined') { // getter + return this._renderer.states.imageMode; + } if ( m === constants.CORNER || m === constants.CORNERS || diff --git a/src/math/trigonometry.js b/src/math/trigonometry.js index 608ac60743..28fe4fcf3a 100644 --- a/src/math/trigonometry.js +++ b/src/math/trigonometry.js @@ -226,6 +226,33 @@ function trigonometry(p5, fn){ return this._fromRadians(Math.asin(ratio)); }; + fn.decomposeMatrix = function(mat) { + // adapted from https://frederic-wang.fr/2013/12/01/decomposition-of-2d-transform-matrices/ + let { a, b, c, d, e, f } = mat; + let delta = a * d - b * c; + let result = { + translation: { x: e, y: f }, + scale: { x: 0, y: 0 }, + shear: { x: 0, y: 0 }, + rotation: 0 + }; + if (a !== 0 || b !== 0) { + let r = Math.sqrt(a * a + b * b); + result.rotation = b > 0 ? Math.acos(a / r) : -Math.acos(a / r); + result.scale = { x: r, y: delta / r }; + result.shear = { x: Math.atan((a * c + b * d) / (r * r)), y: 0 }; + } else if (c !== 0 || d !== 0) { + let s = Math.sqrt(c * c + d * d); + result.rotation = Math.PI / 2 - + (d > 0 ? Math.acos(-c / s) : -Math.acos(c / s)); + result.scale = { x: delta / s, y: s }; + result.shear = { x: 0, y: Math.atan((a * c + b * d) / (s * s)) }; + } else { + // a = b = c = d = 0 + } + return result; + }; + /** * Calculates the arc tangent of a number. * @@ -864,7 +891,8 @@ function trigonometry(p5, fn){ * @returns {Number} */ fn._toRadians = function(angle) { - if (this._angleMode === DEGREES) { + // returns undefined if no argument + if (typeof angle !== 'undefined' && this._angleMode === DEGREES) { return angle * constants.DEG_TO_RAD; } return angle; diff --git a/src/shape/attributes.js b/src/shape/attributes.js index 5669c5d32e..7ac2a8618e 100644 --- a/src/shape/attributes.js +++ b/src/shape/attributes.js @@ -86,6 +86,9 @@ function attributes(p5, fn){ */ fn.ellipseMode = function(m) { // p5._validateParameters('ellipseMode', arguments); + if (typeof m === 'undefined') { // getter + return this._renderer?.states.ellipseMode; + } if ( m === constants.CORNER || m === constants.CORNERS || @@ -286,6 +289,9 @@ function attributes(p5, fn){ */ fn.rectMode = function(m) { // p5._validateParameters('rectMode', arguments); + if (typeof m === 'undefined') { // getter + return this._renderer?.states.rectMode; + } if ( m === constants.CORNER || m === constants.CORNERS || @@ -424,6 +430,9 @@ function attributes(p5, fn){ */ fn.strokeCap = function(cap) { // p5._validateParameters('strokeCap', arguments); + if (typeof cap === 'undefined') { // getter + return this._renderer.strokeCap(); + } if ( cap === constants.ROUND || cap === constants.SQUARE || @@ -523,6 +532,9 @@ function attributes(p5, fn){ */ fn.strokeJoin = function(join) { // p5._validateParameters('strokeJoin', arguments); + if (typeof join === 'undefined') { // getter + return this._renderer.strokeJoin(); + } if ( join === constants.ROUND || join === constants.BEVEL || @@ -590,8 +602,7 @@ function attributes(p5, fn){ */ fn.strokeWeight = function(w) { // p5._validateParameters('strokeWeight', arguments); - this._renderer.strokeWeight(w); - return this; + return this._renderer.strokeWeight(w); }; } diff --git a/src/type/textCore.js b/src/type/textCore.js index 70c05cf927..4f6e555601 100644 --- a/src/type/textCore.js +++ b/src/type/textCore.js @@ -1611,7 +1611,14 @@ function textCore(p5, fn) { // the setter if (typeof h !== 'undefined') { + // accept what is returned from the getter + if (h.hasOwnProperty('horizontal')) { + h = h.horizontal; + } this.states.setValue('textAlign', h); + if (h.hasOwnProperty('vertical') && typeof v === 'undefined') { + v = h.vertical; + } if (typeof v !== 'undefined') { if (v === fn.CENTER) { v = textCoreConstants._CTX_MIDDLE; diff --git a/test/unit/core/properties.js b/test/unit/core/properties.js new file mode 100644 index 0000000000..10bcce9162 --- /dev/null +++ b/test/unit/core/properties.js @@ -0,0 +1,71 @@ +import p5 from '../../../src/app.js'; + +suite('Set/get properties', function() { + + let p = new p5(function (sketch) { + sketch.setup = function () { }; + sketch.draw = function () { }; + }); + + /*beforeEach(function () { + myp5 = new p5(function (p) { + p.setup = function () { }; + p.draw = function () { }; + }); + }); + afterEach(function () { + myp5.remove(); + });*/ + + let getters = { + background: new p5.Color([100, 100, 50]), + fill: new p5.Color([100, 200, 50]), + stroke: new p5.Color([200, 100, 50, 100]), + tint: new p5.Color([100, 140, 50]), + + rectMode: p.CENTER, + colorMode: p.HSB, + blendMode: 'source-over', + imageMode: p.CORNER, + ellipseMode: p.CORNER, + + strokeWeight: 6, + strokeCap: p.ROUND, + strokeJoin: p.MITER, + pixelDensity: 1, + cursor: 'pointer', + + rotate: p.PI, + translate: { x: 1, y: 2 }, + scale: { x: 1, y: 2 }, + bezierOrder: 2, + splineProperties: { ends: p.EXCLUDE, tightness: -5 }, + textAlign: { horizontal: p.CENTER, vertical: p.CENTER }, + textLeading: 18, + textFont: 'arial', + textSize: 1, + textStyle: 1, + textWrap: p.WORD, + textDirection: 1, + textWeight: 1 + }; + + Object.keys(getters).forEach(prop => { + let arg = getters[prop]; + test(`${prop}()`, function() { + + // setter + if (typeof arg === 'object' && !(arg instanceof p5.Color)) { + p[prop](...Object.values(arg)); // set with object + } + else if (Array.isArray(arg)) { + p[prop](...arg); // set with array + } + else { + p[prop](arg); // set with primitive + } + // getter + assert.strictEqual(p[prop]().toString(), arg.toString(), `${arg.toString()}`); + }); + }); +});