mirror of
https://github.com/google/blockly.git
synced 2026-01-09 01:50:11 +01:00
committed by
Sam El-Husseini
parent
6516363469
commit
1c3db256fa
@@ -42,12 +42,49 @@ goog.require('Blockly.utils.userAgent');
|
||||
* @param {Function=} opt_validator A function that is called to validate
|
||||
* changes to the field's value. Takes in a number & returns a
|
||||
* validated number, or null to abort the change.
|
||||
* @param {Object=} opt_config A map of options used to configure the field.
|
||||
* See the [field creation documentation]{@link https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/angle#creation}
|
||||
* for a list of properties this parameter supports.
|
||||
* @extends {Blockly.FieldTextInput}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.FieldAngle = function(opt_value, opt_validator) {
|
||||
Blockly.FieldAngle = function(opt_value, opt_validator, opt_config) {
|
||||
|
||||
/**
|
||||
* Should the angle increase as the angle picker is moved clockwise (true)
|
||||
* or counterclockwise (false)
|
||||
* @see Blockly.FieldAngle.CLOCKWISE
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.clockwise_ = Blockly.FieldAngle.CLOCKWISE;
|
||||
|
||||
/**
|
||||
* The offset of zero degrees (and all other angles).
|
||||
* @see Blockly.FieldAngle.OFFSET
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.offset_ = Blockly.FieldAngle.OFFSET;
|
||||
|
||||
/**
|
||||
* The maximum angle to allow before wrapping.
|
||||
* @see Blockly.FieldAngle.WRAP
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.wrap_ = Blockly.FieldAngle.WRAP;
|
||||
|
||||
/**
|
||||
* The amount to round angles to when using a mouse or keyboard nav input.
|
||||
* @see Blockly.FieldAngle.ROUND
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.round_ = Blockly.FieldAngle.ROUND;
|
||||
|
||||
Blockly.FieldAngle.superClass_.constructor.call(
|
||||
this, opt_value || 0, opt_validator);
|
||||
this, opt_value || 0, opt_validator, opt_config);
|
||||
};
|
||||
Blockly.utils.object.inherits(Blockly.FieldAngle, Blockly.FieldTextInput);
|
||||
|
||||
@@ -59,7 +96,7 @@ Blockly.utils.object.inherits(Blockly.FieldAngle, Blockly.FieldTextInput);
|
||||
* @nocollapse
|
||||
*/
|
||||
Blockly.FieldAngle.fromJson = function(options) {
|
||||
return new Blockly.FieldAngle(options['angle']);
|
||||
return new Blockly.FieldAngle(options['angle'], null, options);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -71,51 +108,87 @@ 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.
|
||||
* The default amount to round angles to when using a mouse or keyboard nav
|
||||
* input. Must be a positive integer to support keyboard navigation.
|
||||
* @const {number}
|
||||
*/
|
||||
Blockly.FieldAngle.ROUND = 15;
|
||||
|
||||
/**
|
||||
* Half the width of protractor image.
|
||||
* @const {number}
|
||||
*/
|
||||
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;
|
||||
*/
|
||||
|
||||
/**
|
||||
* Angle increases clockwise (true) or counterclockwise (false).
|
||||
* Default property describing which direction makes an angle field's value
|
||||
* increase. Angle increases clockwise (true) or counterclockwise (false).
|
||||
* @const {boolean}
|
||||
*/
|
||||
Blockly.FieldAngle.CLOCKWISE = false;
|
||||
|
||||
/**
|
||||
* Offset the location of 0 degrees (and all angles) by a constant.
|
||||
* The default offset of 0 degrees (and all angles). Always offsets in the
|
||||
* counterclockwise direction, regardless of the field's clockwise property.
|
||||
* Usually either 0 (0 = right) or 90 (0 = up).
|
||||
* @const {number}
|
||||
*/
|
||||
Blockly.FieldAngle.OFFSET = 0;
|
||||
|
||||
/**
|
||||
* Maximum allowed angle before wrapping.
|
||||
* The default maximum angle to allow before wrapping.
|
||||
* Usually either 360 (for 0 to 359.9) or 180 (for -179.9 to 180).
|
||||
* @const {number}
|
||||
*/
|
||||
Blockly.FieldAngle.WRAP = 360;
|
||||
|
||||
/**
|
||||
* Radius of protractor circle. Slightly smaller than protractor size since
|
||||
* otherwise SVG crops off half the border at the edges.
|
||||
* @const {number}
|
||||
*/
|
||||
Blockly.FieldAngle.RADIUS = Blockly.FieldAngle.HALF - 1;
|
||||
|
||||
/**
|
||||
* Configure the field based on the given map of options.
|
||||
* @param {!Object} config A map of options to configure the field based on.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldAngle.prototype.configure_ = function(config) {
|
||||
Blockly.FieldAngle.superClass_.configure_.call(this, config);
|
||||
|
||||
switch (config['mode']) {
|
||||
case 'compass':
|
||||
this.clockwise_ = true;
|
||||
this.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.
|
||||
this.clockwise_ = false;
|
||||
this.offset_ = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// Allow individual settings to override the mode setting.
|
||||
var clockwise = config['clockwise'];
|
||||
if (typeof clockwise == 'boolean') {
|
||||
this.clockwise_ = clockwise;
|
||||
}
|
||||
var offset = Number(config['offset']);
|
||||
if (!isNaN(offset)) {
|
||||
this.offset_ = offset;
|
||||
}
|
||||
var wrap = Number(config['wrap']);
|
||||
if (!isNaN(wrap)) {
|
||||
this.wrap_ = wrap;
|
||||
}
|
||||
var round = Number(config['round']);
|
||||
if (!isNaN(round)) {
|
||||
this.round_ = round;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the block UI for this field.
|
||||
* @package
|
||||
@@ -261,37 +334,27 @@ Blockly.FieldAngle.prototype.onMouseMove = function(e) {
|
||||
}
|
||||
|
||||
// Do offsetting.
|
||||
if (Blockly.FieldAngle.CLOCKWISE) {
|
||||
angle = Blockly.FieldAngle.OFFSET + 360 - angle;
|
||||
if (this.clockwise_) {
|
||||
angle = this.offset_ + 360 - angle;
|
||||
} else {
|
||||
angle = 360 - (Blockly.FieldAngle.OFFSET - angle);
|
||||
}
|
||||
if (angle > 360) {
|
||||
angle -= 360;
|
||||
angle = 360 - (this.offset_ - angle);
|
||||
}
|
||||
|
||||
this.setAngle(angle);
|
||||
this.displayMouseOrKeyboardValue_(angle);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the angle value and update the graph.
|
||||
* @param {number} angle New angle
|
||||
* Handles and displays values that are input via mouse or arrow key input.
|
||||
* These values need to be rounded and wrapped before being displayed so
|
||||
* that the text input's value is appropriate.
|
||||
* @param {number} angle New angle.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldAngle.prototype.setAngle = function(angle) {
|
||||
// Do rounding.
|
||||
if (Blockly.FieldAngle.ROUND) {
|
||||
angle = Math.round(angle / Blockly.FieldAngle.ROUND) *
|
||||
Blockly.FieldAngle.ROUND;
|
||||
Blockly.FieldAngle.prototype.displayMouseOrKeyboardValue_ = function(angle) {
|
||||
if (this.round_) {
|
||||
angle = Math.round(angle / this.round_) * this.round_;
|
||||
}
|
||||
|
||||
// Do wrapping.
|
||||
if (angle > Blockly.FieldAngle.WRAP) {
|
||||
angle -= 360;
|
||||
} else if (angle < 0) {
|
||||
angle += 360;
|
||||
}
|
||||
|
||||
// Update value.
|
||||
angle = this.wrapValue_(angle);
|
||||
if (angle != this.value_) {
|
||||
this.setEditorValue_(angle);
|
||||
}
|
||||
@@ -306,30 +369,30 @@ Blockly.FieldAngle.prototype.updateGraph_ = function() {
|
||||
return;
|
||||
}
|
||||
// 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()) + this.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 clockwiseFlag = Number(this.clockwise_);
|
||||
var angle1 = Blockly.utils.math.toRadians(this.offset_);
|
||||
var x1 = Math.cos(angle1) * Blockly.FieldAngle.RADIUS;
|
||||
var y1 = Math.sin(angle1) * -Blockly.FieldAngle.RADIUS;
|
||||
if (Blockly.FieldAngle.CLOCKWISE) {
|
||||
if (clockwiseFlag) {
|
||||
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 (clockwiseFlag) {
|
||||
largeFlag = 1 - largeFlag;
|
||||
}
|
||||
var sweepFlag = Number(Blockly.FieldAngle.CLOCKWISE);
|
||||
path.push(' l ', x1, ',', y1,
|
||||
' A ', Blockly.FieldAngle.RADIUS, ',', Blockly.FieldAngle.RADIUS,
|
||||
' 0 ', largeFlag, ' ', sweepFlag, ' ', x2, ',', y2, ' z');
|
||||
' 0 ', largeFlag, ' ', clockwiseFlag, ' ', x2, ',', y2, ' z');
|
||||
}
|
||||
this.gauge_.setAttribute('d', path.join(''));
|
||||
this.line_.setAttribute('x2', x2);
|
||||
@@ -360,8 +423,8 @@ Blockly.FieldAngle.prototype.onHtmlInputKeyDown_ = function(e) {
|
||||
multiplier = 1;
|
||||
}
|
||||
if (multiplier) {
|
||||
this.setAngle(Number(this.getValue()) +
|
||||
(multiplier * Blockly.FieldAngle.ROUND));
|
||||
this.displayMouseOrKeyboardValue_(
|
||||
this.getValue() + (multiplier * this.round_));
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
@@ -375,17 +438,28 @@ Blockly.FieldAngle.prototype.onHtmlInputKeyDown_ = function(e) {
|
||||
* @override
|
||||
*/
|
||||
Blockly.FieldAngle.prototype.doClassValidation_ = function(opt_newValue) {
|
||||
var n = Number(opt_newValue) % 360;
|
||||
if (isNaN(n)) {
|
||||
var value = Number(opt_newValue);
|
||||
if (isNaN(value) || !isFinite(value)) {
|
||||
return null;
|
||||
}
|
||||
if (n < 0) {
|
||||
n += 360;
|
||||
return this.wrapValue_(value);
|
||||
};
|
||||
|
||||
/**
|
||||
* Wraps the value so that it is in the range (-360 + wrap, wrap).
|
||||
* @param {number} value The value to wrap.
|
||||
* @return {number} The wrapped value.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldAngle.prototype.wrapValue_ = function(value) {
|
||||
value %= 360;
|
||||
if (value < 0) {
|
||||
value += 360;
|
||||
}
|
||||
if (n > Blockly.FieldAngle.WRAP) {
|
||||
n -= 360;
|
||||
if (value > this.wrap_) {
|
||||
value -= 360;
|
||||
}
|
||||
return n;
|
||||
return value;
|
||||
};
|
||||
|
||||
Blockly.fieldRegistry.register('field_angle', Blockly.FieldAngle);
|
||||
|
||||
@@ -467,6 +467,104 @@ Blockly.defineBlocksWithJsonArray([ // BEGIN JSON EXTRACT
|
||||
"output": "Note",
|
||||
"tooltip": "A midi note."
|
||||
},
|
||||
{
|
||||
"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_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_images_datauri",
|
||||
"message0": "Image data: URI %1",
|
||||
|
||||
@@ -262,4 +262,134 @@ suite('Angle Fields', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
suite('Customizations', function() {
|
||||
suite('Clockwise', function() {
|
||||
test('JS Configuration', function() {
|
||||
var field = new Blockly.FieldAngle(0, null, {
|
||||
clockwise: true
|
||||
});
|
||||
chai.assert.isTrue(field.clockwise_);
|
||||
});
|
||||
test('JSON Definition', function() {
|
||||
var field = Blockly.FieldAngle.fromJson({
|
||||
value: 0,
|
||||
clockwise: true
|
||||
});
|
||||
chai.assert.isTrue(field.clockwise_);
|
||||
});
|
||||
test('Constant', function() {
|
||||
// Note: Generally constants should be set at compile time, not
|
||||
// runtime (since they are constants) but for testing purposes we
|
||||
// can do this.
|
||||
Blockly.FieldAngle.CLOCKWISE = true;
|
||||
var field = new Blockly.FieldAngle();
|
||||
chai.assert.isTrue(field.clockwise_);
|
||||
});
|
||||
});
|
||||
suite('Offset', function() {
|
||||
test('JS Configuration', function() {
|
||||
var field = new Blockly.FieldAngle(0, null, {
|
||||
offset: 90
|
||||
});
|
||||
chai.assert.equal(field.offset_, 90);
|
||||
});
|
||||
test('JSON Definition', function() {
|
||||
var field = Blockly.FieldAngle.fromJson({
|
||||
value: 0,
|
||||
offset: 90
|
||||
});
|
||||
chai.assert.equal(field.offset_, 90);
|
||||
});
|
||||
test('Constant', function() {
|
||||
// Note: Generally constants should be set at compile time, not
|
||||
// runtime (since they are constants) but for testing purposes we
|
||||
// can do this.
|
||||
Blockly.FieldAngle.OFFSET = 90;
|
||||
var field = new Blockly.FieldAngle();
|
||||
chai.assert.equal(field.offset_, 90);
|
||||
});
|
||||
});
|
||||
suite('Wrap', function() {
|
||||
test('JS Configuration', function() {
|
||||
var field = new Blockly.FieldAngle(0, null, {
|
||||
wrap: 180
|
||||
});
|
||||
chai.assert.equal(field.wrap_, 180);
|
||||
});
|
||||
test('JSON Definition', function() {
|
||||
var field = Blockly.FieldAngle.fromJson({
|
||||
value: 0,
|
||||
wrap: 180
|
||||
});
|
||||
chai.assert.equal(field.wrap_, 180);
|
||||
});
|
||||
test('Constant', function() {
|
||||
// Note: Generally constants should be set at compile time, not
|
||||
// runtime (since they are constants) but for testing purposes we
|
||||
// can do this.
|
||||
Blockly.FieldAngle.WRAP = 180;
|
||||
var field = new Blockly.FieldAngle();
|
||||
chai.assert.equal(field.wrap_, 180);
|
||||
});
|
||||
});
|
||||
suite('Round', function() {
|
||||
test('JS Configuration', function() {
|
||||
var field = new Blockly.FieldAngle(0, null, {
|
||||
round: 30
|
||||
});
|
||||
chai.assert.equal(field.round_, 30);
|
||||
});
|
||||
test('JSON Definition', function() {
|
||||
var field = Blockly.FieldAngle.fromJson({
|
||||
value: 0,
|
||||
round: 30
|
||||
});
|
||||
chai.assert.equal(field.round_, 30);
|
||||
});
|
||||
test('Constant', function() {
|
||||
// Note: Generally constants should be set at compile time, not
|
||||
// runtime (since they are constants) but for testing purposes we
|
||||
// can do this.
|
||||
Blockly.FieldAngle.ROUND = 30;
|
||||
var field = new Blockly.FieldAngle();
|
||||
chai.assert.equal(field.round_, 30);
|
||||
});
|
||||
});
|
||||
suite('Mode', function() {
|
||||
suite('Compass', function() {
|
||||
test('JS Configuration', function() {
|
||||
var field = new Blockly.FieldAngle(0, null, {
|
||||
mode: 'compass'
|
||||
});
|
||||
chai.assert.equal(field.offset_, 90);
|
||||
chai.assert.isTrue(field.clockwise_);
|
||||
});
|
||||
test('JS Configuration', function() {
|
||||
var field = Blockly.FieldAngle.fromJson({
|
||||
value: 0,
|
||||
mode: 'compass'
|
||||
});
|
||||
chai.assert.equal(field.offset_, 90);
|
||||
chai.assert.isTrue(field.clockwise_);
|
||||
});
|
||||
});
|
||||
suite('Protractor', function() {
|
||||
test('JS Configuration', function() {
|
||||
var field = new Blockly.FieldAngle(0, null, {
|
||||
mode: 'protractor'
|
||||
});
|
||||
chai.assert.equal(field.offset_, 0);
|
||||
chai.assert.isFalse(field.clockwise_);
|
||||
});
|
||||
test('JS Configuration', function() {
|
||||
var field = Blockly.FieldAngle.fromJson({
|
||||
value: 0,
|
||||
mode: 'protractor'
|
||||
});
|
||||
chai.assert.equal(field.offset_, 0);
|
||||
chai.assert.isFalse(field.clockwise_);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1464,6 +1464,15 @@ var spaghettiXml = [
|
||||
<field name="NOTE">60</field>
|
||||
</block>
|
||||
</category>
|
||||
<category name="Angles">
|
||||
<block type="test_angles_clockwise"></block>
|
||||
<block type="test_angles_offset"></block>
|
||||
<block type="test_angles_wrap"></block>
|
||||
<block type="test_angles_round_30"></block>
|
||||
<block type="test_angles_round_0"></block>
|
||||
<block type="test_angles_protractor"></block>
|
||||
<block type="test_angles_compass"></block>
|
||||
</category>
|
||||
<category name="Drop-downs">
|
||||
<label text="Dynamic"></label>
|
||||
<block type="test_dropdowns_dynamic"></block>
|
||||
|
||||
Reference in New Issue
Block a user