Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
240 changes: 205 additions & 35 deletions core/field_angle.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

/**
Expand All @@ -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 <DOCS LINK HERE> for more information about these properties. */
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs a link.


/**
* 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.
Expand Down Expand Up @@ -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;
}

Expand All @@ -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');
Expand All @@ -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) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RoboErikG thoughts on returning itself for method chaining? It's a reasonable pattern, but 1) we don't use it anywhere else and 2) we probably want to be consistent.

Copy link
Contributor

@RoboErikG RoboErikG Jul 26, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's nice for convenience, but it's pretty non-standard for instances. I think I'd rather start using an opt_config parameter on the constructor with a map of the configuration options. The keys should be the same as the JSON config which will also let you use the same code for both.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want me to go ahead and update it? or are you waiting for feedback from Rachel?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to Erik. Go ahead and update it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went to go update this, and then I realized I had more questions and wasn't sure how far reaching the change should be. I created an issue to make discussion easier.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're happy with the clarification on #2722, then just remove the method chaining and this will be good to go.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was planning on getting the abstract field changes in first (just to make sure how I'm thinking about it in my head is actually going to work). But don't worry I haven't forgotten about this little guy!

Also atm #2722 has this guy down as no hooks. This is b/c, for example, changing the WRAP value at runtime means the field would have to do some extra work recalculating it's value. I think I could get it to work though.

Did you want hooks for the angle field or no?

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);
Loading