diff --git a/src/gameobjects/shape/rectangle/Rectangle.js b/src/gameobjects/shape/rectangle/Rectangle.js index f82ef34683..5191d5cada 100644 --- a/src/gameobjects/shape/rectangle/Rectangle.js +++ b/src/gameobjects/shape/rectangle/Rectangle.js @@ -55,30 +55,52 @@ var Rectangle = new Class({ Shape.call(this, scene, 'Rectangle', new GeomRectangle(0, 0, width, height)); /** - * The radius of the rectangle if this is set to use rounded corners. + * The radius of the top-left corner of the rectangle. * * Do not modify this property. Instead, call the method `setRounded` to set the * radius of the rounded corners. * - * @name Phaser.GameObjects.Shape#radius + * @name Phaser.GameObjects.Rectangle#radiusTopLeft * @type {number} * @readonly - * @since 3.90.0 */ - this.radius = 20; + this.radiusTopLeft = 0; /** - * Does this Rectangle have rounded corners? + * The radius of the top-right corner of the rectangle. * * Do not modify this property. Instead, call the method `setRounded` to set the - * radius state of this rectangle. + * radius of the rounded corners. + * + * @name Phaser.GameObjects.Rectangle#radiusTopRight + * @type {number} + * @readonly + */ + this.radiusTopRight = 0; + + /** + * The radius of the bottom-left corner of the rectangle. + * + * Do not modify this property. Instead, call the method `setRounded` to set the + * radius of the rounded corners. + * + * @name Phaser.GameObjects.Rectangle#radiusBottomLeft + * @type {number} + * @readonly + */ + this.radiusBottomLeft = 0; + + /** + * The radius of the bottom-right corner of the rectangle. + * + * Do not modify this property. Instead, call the method `setRounded` to set the + * radius of the rounded corners. * - * @name Phaser.GameObjects.Shape#isRounded - * @type {boolean} + * @name Phaser.GameObjects.Rectangle#radiusBottomRight + * @type {number} * @readonly - * @since 3.90.0 */ - this.isRounded = false; + this.radiusBottomRight = 0; this.setPosition(x, y); this.setSize(width, height); @@ -92,26 +114,85 @@ var Rectangle = new Class({ this.updateData(); }, + /** + * The radius of all the corners of the rectangle if this is set to use rounded corners. + * Return `radiusTopLeft` when read this radius property. + * + * @name Phaser.GameObjects.Shape#radius + * @type {number} + * @since 3.90.0 + */ + radius: { + + get: function () + { + return this.radiusTopLeft; + }, + + set: function (value) + { + this.setRounded(value, value, value, value); + } + + }, + + /** + * Does this Rectangle have rounded corners? + * + * It checks to see if the 4 radius properties are set to 0. + * This indicates that a Rectangle isn't rounded. + * + * @name Phaser.GameObjects.Shape#isRounded + * @type {boolean} + * @readonly + * @since 3.90.0 + */ + isRounded: { + + get: function () + { + return ( + this.radiusTopLeft !== 0 || + this.radiusTopRight !== 0 || + this.radiusBottomLeft !== 0 || + this.radiusBottomRight !== 0 + ); + } + + }, + /** * Sets this rectangle to have rounded corners by specifying the radius of the corner. * * The radius of the rounded corners is limited by the smallest dimension of the rectangle. * - * To disable rounded corners, set the `radius` parameter to 0. + * To disable rounded corners, set the `topLeft` parameter to 0. * * @method Phaser.GameObjects.Rectangle#setRounded * @since 3.90.0 * - * @param {number} [radius=16] - The radius of all four rounded corners. + * @param {number} [topLeft=16] - The radius of the top-left corner. If no other values are given this value is applied evenly, setting all corners to this radius. + * @param {number} [topRight] - The radius of the top-right corner. + * @param {number} [bottomLeft] - The radius of the bottom-left corner. + * @param {number} [bottomRight] - The radius of the bottom-right corner. * * @return {this} This Game Object instance. */ - setRounded: function (radius) + setRounded: function (topLeft, topRight, bottomLeft, bottomRight) { - if (radius === undefined) { radius = 16; } + if (topLeft === undefined) { topLeft = 16; } + + if (topRight === undefined) + { + topRight = topLeft; + bottomLeft = topLeft; + bottomRight = topLeft; + } - this.radius = radius; - this.isRounded = radius > 0; + this.radiusTopLeft = topLeft; + this.radiusTopRight = topRight; + this.radiusBottomLeft = bottomLeft; + this.radiusBottomRight = bottomRight; return this.updateRoundedData(); }, @@ -210,36 +291,42 @@ var Rectangle = new Class({ // Limit max radius to half the smallest dimension var maxRadius = Math.min(halfWidth, halfHeight); - var radius = Math.min(this.radius, maxRadius); + var tl = Math.min(this.radiusTopLeft, maxRadius); + var tr = Math.min(this.radiusTopRight, maxRadius); + var bl = Math.min(this.radiusBottomLeft, maxRadius); + var br = Math.min(this.radiusBottomRight, maxRadius); var x = halfWidth; var y = halfHeight; - // The number of segments is based on radius (more segments = larger radius) - var segments = Math.max(1, Math.floor(radius / 5)); + // Ensure minimum smoothness for small radii while preventing excessive tessellation + var tlSegments = Math.max(4, Math.min(16, Math.ceil(tl / 2))); + var trSegments = Math.max(4, Math.min(16, Math.ceil(tr / 2))); + var blSegments = Math.max(4, Math.min(16, Math.ceil(bl / 2))); + var brSegments = Math.max(4, Math.min(16, Math.ceil(br / 2))); // Create points going clockwise from top-left // Top-left corner - this.arcTo(path, x - halfWidth + radius, y - halfHeight + radius, radius, Math.PI, Math.PI * 1.5, segments); + this.arcTo(path, x - halfWidth + tl, y - halfHeight + tl, tl, Math.PI, Math.PI * 1.5, tlSegments); // Top edge and top-right corner - path.push(x + halfWidth - radius, y - halfHeight); + path.push(x + halfWidth - tr, y - halfHeight); - this.arcTo(path, x + halfWidth - radius, y - halfHeight + radius, radius, Math.PI * 1.5, Math.PI * 2, segments); + this.arcTo(path, x + halfWidth - tr, y - halfHeight + tr, tr, Math.PI * 1.5, Math.PI * 2, trSegments); // Right edge and bottom-right corner - path.push(x + halfWidth, y + halfHeight - radius); + path.push(x + halfWidth, y + halfHeight - br); - this.arcTo(path, x + halfWidth - radius, y + halfHeight - radius, radius, 0, Math.PI * 0.5, segments); + this.arcTo(path, x + halfWidth - br, y + halfHeight - br, br, 0, Math.PI * 0.5, brSegments); // Bottom edge and bottom-left corner - path.push(x - halfWidth + radius, y + halfHeight); + path.push(x - halfWidth + bl, y + halfHeight); - this.arcTo(path, x - halfWidth + radius, y + halfHeight - radius, radius, Math.PI * 0.5, Math.PI, segments); + this.arcTo(path, x - halfWidth + bl, y + halfHeight - bl, bl, Math.PI * 0.5, Math.PI, blSegments); // Left edge (connects back to first point) - path.push(x - halfWidth, y - halfHeight + radius); + path.push(x - halfWidth, y - halfHeight + tl); this.pathIndexes = Earcut(path); this.pathData = path; diff --git a/src/gameobjects/shape/rectangle/RectangleCanvasRenderer.js b/src/gameobjects/shape/rectangle/RectangleCanvasRenderer.js index 6a1a6e919e..b7fe2d5ad0 100644 --- a/src/gameobjects/shape/rectangle/RectangleCanvasRenderer.js +++ b/src/gameobjects/shape/rectangle/RectangleCanvasRenderer.js @@ -8,38 +8,81 @@ var FillStyleCanvas = require('../FillStyleCanvas'); var LineStyleCanvas = require('../LineStyleCanvas'); var SetTransform = require('../../../renderer/canvas/utils/SetTransform'); -var DrawRoundedRect = function (ctx, x, y, width, height, radius) +var DrawRoundedRect = function (ctx, x, y, width, height, tlRadius, trRadius, blRadius, brRadius) { // Limit radius to half of the smaller dimension var maxRadius = Math.min(width / 2, height / 2); - var r = Math.min(radius, maxRadius); - - if (r === 0) + var tl = Math.min(tlRadius, maxRadius); + var tr = Math.min(trRadius, maxRadius); + var bl = Math.min(blRadius, maxRadius); + var br = Math.min(brRadius, maxRadius); + + if (tl === 0 && tr === 0 && bl === 0 && br === 0) { // Fall back to normal rectangle if radius is 0 ctx.rect(x, y, width, height); return; } - - // Start at top-left, after the corner - ctx.moveTo(x + r, y); - - // Top edge and top-right corner - ctx.lineTo(x + width - r, y); - ctx.arcTo(x + width, y, x + width, y + r, r); - - // Right edge and bottom-right corner - ctx.lineTo(x + width, y + height - r); - ctx.arcTo(x + width, y + height, x + width - r, y + height, r); - - // Bottom edge and bottom-left corner - ctx.lineTo(x + r, y + height); - ctx.arcTo(x, y + height, x, y + height - r, r); - - // Left edge and top-left corner - ctx.lineTo(x, y + r); - ctx.arcTo(x, y, x + r, y, r); - + + if (tl === 0) + { + // Start at top-left + ctx.moveTo(x, y); + } + else + { + // Start at top-left, after the corner + ctx.moveTo(x + tl, y); + } + + if (tr === 0) + { + // Top edge + ctx.lineTo(x + width, y); + } + else + { + // Top edge and top-right corner + ctx.lineTo(x + width - tr, y); + ctx.arcTo(x + width, y, x + width, y + tr, tr); + } + + if (br === 0) + { + // Right edge + ctx.lineTo(x + width, y + height); + } + else + { + // Right edge and bottom-right corner + ctx.lineTo(x + width, y + height - br); + ctx.arcTo(x + width, y + height, x + width - br, y + height, br); + } + + if (bl === 0) + { + // Bottom edge + ctx.lineTo(x, y + height); + } + else + { + // Bottom edge and bottom-left corner + ctx.lineTo(x + bl, y + height); + ctx.arcTo(x, y + height, x, y + height - bl, bl); + } + + if (tl === 0) + { + // Left edge + ctx.lineTo(x, y); + } + else + { + // Left edge and top-left corner + ctx.lineTo(x, y + tl); + ctx.arcTo(x, y, x + tl, y, tl); + } + ctx.closePath(); }; @@ -75,7 +118,17 @@ var RectangleCanvasRenderer = function (renderer, src, camera, parentMatrix) if (src.isRounded) { ctx.beginPath(); - DrawRoundedRect(ctx, -dx, -dy, src.width, src.height, src.radius); + DrawRoundedRect( + ctx, + -dx, + -dy, + src.width, + src.height, + src.radiusTopLeft, + src.radiusTopRight, + src.radiusBottomLeft, + src.radiusBottomRight + ); ctx.fill(); } else @@ -97,7 +150,17 @@ var RectangleCanvasRenderer = function (renderer, src, camera, parentMatrix) if (src.isRounded) { - DrawRoundedRect(ctx, -dx, -dy, src.width, src.height, src.radius); + DrawRoundedRect( + ctx, + -dx, + -dy, + src.width, + src.height, + src.radiusTopLeft, + src.radiusTopRight, + src.radiusBottomLeft, + src.radiusBottomRight + ); } else {