diff --git a/core/field_angle.js b/core/field_angle.js index 842eaf08198..a77fd01c7a3 100644 --- a/core/field_angle.js +++ b/core/field_angle.js @@ -61,7 +61,47 @@ goog.inherits(Blockly.FieldAngle, Blockly.FieldTextInput); * @nocollapse */ Blockly.FieldAngle.fromJson = function(options) { - return new Blockly.FieldAngle(options['angle']); + var field = new Blockly.FieldAngle(options['angle']); + + var clockwise = null; + var offset = null; + var wrap = null; + var round = null; + + var mode = options['mode']; + switch (mode) { + case 'compass': + clockwise = true; + offset = 90; + break; + case 'protractor': + // This is the default mode, so we could do nothing. But just to + // future-proof we'll set it anyway. + clockwise = false; + offset = 0; + break; + } + + // Allow individual settings to override the mode setting. + if (typeof options['clockwise'] == 'boolean') { + clockwise = options['clockwise']; + } + if (typeof options['offset'] == 'number') { + offset = options['offset']; + } + if (typeof options['wrap'] == 'number') { + wrap = options['wrap']; + } + if (typeof options['round'] == 'number') { + round = options['round']; + } + + field.setClockwise((clockwise)); + field.setOffset(offset); + field.setWrap(wrap); + field.setRound(round); + + return field; }; /** @@ -72,51 +112,83 @@ Blockly.FieldAngle.fromJson = function(options) { */ Blockly.FieldAngle.prototype.SERIALIZABLE = true; -/** - * Round angles to the nearest 15 degrees when using mouse. - * Set to 0 to disable rounding. - */ -Blockly.FieldAngle.ROUND = 15; - /** * Half the width of protractor image. + * @type {number} + * @const */ Blockly.FieldAngle.HALF = 100 / 2; -/* The following two settings work together to set the behaviour of the angle - * picker. While many combinations are possible, two modes are typical: - * Math mode. - * 0 deg is right, 90 is up. This is the style used by protractors. - * Blockly.FieldAngle.CLOCKWISE = false; - * Blockly.FieldAngle.OFFSET = 0; - * Compass mode. - * 0 deg is up, 90 is right. This is the style used by maps. - * Blockly.FieldAngle.CLOCKWISE = true; - * Blockly.FieldAngle.OFFSET = 90; +/** + * Radius of protractor circle. Slightly smaller than protractor size since + * otherwise SVG crops off half the border at the edges. + * @type {number} + * @const */ +Blockly.FieldAngle.RADIUS = Blockly.FieldAngle.HALF - 1; + +/* See for more information about these properties. */ /** * Angle increases clockwise (true) or counterclockwise (false). + * @type {boolean} + * @const */ Blockly.FieldAngle.CLOCKWISE = false; /** - * Offset the location of 0 degrees (and all angles) by a constant. + * Offset the location of 0 degrees (and all angles) by a constant number of + * degrees in the clockwise direction (independent of the CLOCKWISE property). * Usually either 0 (0 = right) or 90 (0 = up). + * @type {number} + * @const */ Blockly.FieldAngle.OFFSET = 0; /** - * Maximum allowed angle before wrapping. + * The wrap/range of the angle field. The range is equal to (-360 + WRAP, WRAP). * Usually either 360 (for 0 to 359.9) or 180 (for -179.9 to 180). + * @type {number} + * @const */ Blockly.FieldAngle.WRAP = 360; /** - * Radius of protractor circle. Slightly smaller than protractor size since - * otherwise SVG crops off half the border at the edges. + * Round angles input through the graphical editor (mouse) to the nearest + * multiple of this number. + * Set to 0 to disable rounding. + * @type {number} + * @const */ -Blockly.FieldAngle.RADIUS = Blockly.FieldAngle.HALF - 1; +Blockly.FieldAngle.ROUND = 15; + +/** + * The clockwise property used by this field. If null, use the global property. + * @type {?boolean} + * @private + */ +Blockly.FieldAngle.prototype.clockwise_ = null; + +/** + * The offset property used by this field. If null, use the global property. + * @type {?number} + * @private + */ +Blockly.FieldAngle.prototype.offset_ = null; + +/** + * The wrap property used by this field. If null, use the global property. + * @type {?number} + * @private + */ +Blockly.FieldAngle.prototype.wrap_ = null; + +/** + * The round property used by this field. If null, use the global property. + * @type {?number} + * @private + */ +Blockly.FieldAngle.prototype.round_ = null; /** * Create the block UI for this field. @@ -262,23 +334,24 @@ Blockly.FieldAngle.prototype.onMouseMove = function(e) { } // Do offsetting. - if (Blockly.FieldAngle.CLOCKWISE) { - angle = Blockly.FieldAngle.OFFSET + 360 - angle; + var offset = this.getOffset(); + if (this.getClockwise()) { + angle = offset + 360 - angle; } else { - angle = 360 - (Blockly.FieldAngle.OFFSET - angle); + angle = 360 - (offset - angle); } if (angle > 360) { angle -= 360; } // Do rounding. - if (Blockly.FieldAngle.ROUND) { - angle = Math.round(angle / Blockly.FieldAngle.ROUND) * - Blockly.FieldAngle.ROUND; + var round = this.getRound(); + if (round) { + angle = Math.round(angle / round) * round; } // Do wrapping. - if (angle > Blockly.FieldAngle.WRAP) { + if (angle > this.getWrap()) { angle -= 360; } @@ -301,28 +374,31 @@ Blockly.FieldAngle.prototype.updateGraph_ = function() { if (!this.gauge_) { return; } + var clockwise = this.getClockwise(); + var offset = this.getOffset(); + // Always display the input (i.e. getText) even if it is invalid. - var angleDegrees = Number(this.getText()) + Blockly.FieldAngle.OFFSET; + var angleDegrees = Number(this.getText()) + offset; angleDegrees %= 360; var angleRadians = Blockly.utils.math.toRadians(angleDegrees); var path = ['M ', Blockly.FieldAngle.HALF, ',', Blockly.FieldAngle.HALF]; var x2 = Blockly.FieldAngle.HALF; var y2 = Blockly.FieldAngle.HALF; if (!isNaN(angleRadians)) { - var angle1 = Blockly.utils.math.toRadians(Blockly.FieldAngle.OFFSET); + var angle1 = Blockly.utils.math.toRadians(offset); var x1 = Math.cos(angle1) * Blockly.FieldAngle.RADIUS; var y1 = Math.sin(angle1) * -Blockly.FieldAngle.RADIUS; - if (Blockly.FieldAngle.CLOCKWISE) { + if (clockwise) { angleRadians = 2 * angle1 - angleRadians; } x2 += Math.cos(angleRadians) * Blockly.FieldAngle.RADIUS; y2 -= Math.sin(angleRadians) * Blockly.FieldAngle.RADIUS; // Don't ask how the flag calculations work. They just do. var largeFlag = Math.abs(Math.floor((angleRadians - angle1) / Math.PI) % 2); - if (Blockly.FieldAngle.CLOCKWISE) { + if (clockwise) { largeFlag = 1 - largeFlag; } - var sweepFlag = Number(Blockly.FieldAngle.CLOCKWISE); + var sweepFlag = Number(clockwise); path.push(' l ', x1, ',', y1, ' A ', Blockly.FieldAngle.RADIUS, ',', Blockly.FieldAngle.RADIUS, ' 0 ', largeFlag, ' ', sweepFlag, ' ', x2, ',', y2, ' z'); @@ -348,10 +424,104 @@ Blockly.FieldAngle.prototype.doClassValidation_ = function(newValue) { if (n < 0) { n += 360; } - if (n > Blockly.FieldAngle.WRAP) { + if (n > this.getWrap()) { n -= 360; } return n; }; +/** + * Set which direction should make the graphical angle editor increase. + * @param {?boolean} clockwise Whether the graphical angle editor should + * increase as it is moved clockwise (true) or counterclockwise (false) + * or the global setting (null). + * @return {!Blockly.FieldAngle} Returns itself (for method chaining). + */ +Blockly.FieldAngle.prototype.setClockwise = function(clockwise) { + this.clockwise_ = clockwise; + return this; +}; + +/** + * Set the offset of zero degrees. Usually 0 (right) or 90 (up). + * @param {?number} offset The amount to offset the location of 0 degrees + * by. Always offsets in the clockwise direction independent of the + * Clockwise property. Pass null to use the + * global setting. + * @return {!Blockly.FieldAngle} Returns itself (for method chaining). + */ +Blockly.FieldAngle.prototype.setOffset = function(offset) { + this.offset_ = offset; + return this; +}; + +/** + * Set the wrap/range of the angle field. The range is equal to (-360 + + * WRAP, WRAP). + * Usually either 360 (for 0 to 359.9) or 180 (for -179.9 to 180). + * @param {?number} wrap The wrap value of the angle field. Pass null to use + * the global setting. + * @return {!Blockly.FieldAngle} Returns itself (for method chaining). + */ +Blockly.FieldAngle.prototype.setWrap = function(wrap) { + this.wrap_ = wrap; + return this; +}; + +/** + * Set the rounding of the angle field's graphical editor. + * @param {?number} round The value (when input through the graphical + * editor) will be rounded to the nearest multiple of this number. Pass 0 + * to disable rounding. Pass null to use the global setting. + * @return {!Blockly.FieldAngle} Returns itself (for method chaining). + */ +Blockly.FieldAngle.prototype.setRound = function(round) { + this.round_ = round; + return this; +}; + +/** + * Get the direction the angle field's value increases in. + * @return {boolean} Clockwise (true) or counterclockwise (false). + */ +Blockly.FieldAngle.prototype.getClockwise = function() { + if (this.clockwise_ == null) { + return Blockly.FieldAngle.CLOCKWISE; + } + return this.clockwise_; +}; + +/** + * Get the amount 0 degrees is offset by. + * @return {number} The amount of offset. + */ +Blockly.FieldAngle.prototype.getOffset = function() { + if (this.offset_ == null) { + return Blockly.FieldAngle.OFFSET; + } + return this.offset_; +}; + +/** + * Get the wrap value of the angle field. + * @return {number} The number the angle field gets wrapped at. + */ +Blockly.FieldAngle.prototype.getWrap = function() { + if (this.wrap_ == null) { + return Blockly.FieldAngle.WRAP; + } + return this.wrap_; +}; + +/** + * Get the number values input via the graphical editor are rounded to. + * @return {number} The round value. + */ +Blockly.FieldAngle.prototype.getRound = function() { + if (this.round_ == null) { + return Blockly.FieldAngle.ROUND; + } + return this.round_; +}; + Blockly.Field.register('field_angle', Blockly.FieldAngle); diff --git a/tests/blocks/test_blocks.js b/tests/blocks/test_blocks.js index 611453bac28..f6ac1a6cbcf 100644 --- a/tests/blocks/test_blocks.js +++ b/tests/blocks/test_blocks.js @@ -411,6 +411,104 @@ Blockly.defineBlocksWithJsonArray([ // BEGIN JSON EXTRACT "output": "Note", "tooltip": "A midi note." }, + { + "type": "test_angles_clockwise", + "message0": "clockwise %1", + "args0": [ + { + "type": "field_angle", + "name": "FIELDNAME", + "angle": 0, + "clockwise": true + } + ], + "style": "math_blocks", + "tooltip": "test tooltip" + }, + { + "type": "test_angles_offset", + "message0": "offset 90 %1", + "args0": [ + { + "type": "field_angle", + "name": "FIELDNAME", + "angle": 0, + "offset": 90 + } + ], + "style": "math_blocks", + "tooltip": "test tooltip" + }, + { + "type": "test_angles_wrap", + "message0": "wrap %1", + "args0": [ + { + "type": "field_angle", + "name": "FIELDNAME", + "angle": 0, + "wrap": 180 + } + ], + "style": "math_blocks", + "tooltip": "test tooltip" + }, + { + "type": "test_angles_round_30", + "message0": "round 30 %1", + "args0": [ + { + "type": "field_angle", + "name": "FIELDNAME", + "angle": 0, + "round": 30 + } + ], + "style": "math_blocks", + "tooltip": "test tooltip" + }, + { + "type": "test_angles_round_0", + "message0": "no round %1", + "args0": [ + { + "type": "field_angle", + "name": "FIELDNAME", + "angle": 0, + "round": 0 + } + ], + "style": "math_blocks", + "tooltip": "test tooltip" + }, + { + "type": "test_angles_protractor", + "message0": "protractor %1", + "args0": [ + { + "type": "field_angle", + "name": "FIELDNAME", + "angle": 0, + "mode": "protractor" + } + ], + "style": "math_blocks", + "tooltip": "test tooltip" + }, + { + "type": "test_angles_compass", + "message0": "compass %1", + "args0": [ + { + "type": "field_angle", + "name": "FIELDNAME", + "angle": 0, + "mode": "compass" + } + ], + "style": "math_blocks", + "tooltip": "test tooltip" + }, { "type": "test_images_datauri", "message0": "Image data: URI %1", diff --git a/tests/playground.html b/tests/playground.html index a84f31563bb..fa921c79921 100644 --- a/tests/playground.html +++ b/tests/playground.html @@ -1392,6 +1392,17 @@

Blockly Playground

60 + + + + + + + + + + +