2013-10-30 14:46:03 -07:00
|
|
|
/**
|
2014-01-28 03:00:09 -08:00
|
|
|
* @license
|
2013-10-30 14:46:03 -07:00
|
|
|
* Visual Blocks Editor
|
|
|
|
*
|
|
|
|
* Copyright 2013 Google Inc.
|
2014-10-07 13:09:55 -07:00
|
|
|
* https://developers.google.com/blockly/
|
2013-10-30 14:46:03 -07:00
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @fileoverview Angle input field.
|
|
|
|
* @author fraser@google.com (Neil Fraser)
|
|
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
goog.provide('Blockly.FieldAngle');
|
|
|
|
|
|
|
|
goog.require('Blockly.FieldTextInput');
|
2014-11-24 15:08:19 -08:00
|
|
|
goog.require('goog.math');
|
2015-02-06 15:27:25 -08:00
|
|
|
goog.require('goog.userAgent');
|
2013-10-30 14:46:03 -07:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Class for an editable angle field.
|
|
|
|
* @param {string} text The initial content of the field.
|
2015-07-13 15:03:22 -07:00
|
|
|
* @param {Function=} opt_changeHandler An optional function that is called
|
2013-10-30 14:46:03 -07:00
|
|
|
* to validate any constraints on what the user entered. Takes the new
|
|
|
|
* text as an argument and returns the accepted text or null to abort
|
|
|
|
* the change.
|
2014-09-08 14:26:52 -07:00
|
|
|
* @extends {Blockly.FieldTextInput}
|
2013-10-30 14:46:03 -07:00
|
|
|
* @constructor
|
|
|
|
*/
|
|
|
|
Blockly.FieldAngle = function(text, opt_changeHandler) {
|
2015-06-08 15:54:18 -07:00
|
|
|
// Add degree symbol: "360°" (LTR) or "°360" (RTL)
|
|
|
|
this.symbol_ = Blockly.createSvgElement('tspan', {}, null);
|
|
|
|
this.symbol_.appendChild(document.createTextNode('\u00B0'));
|
|
|
|
|
|
|
|
Blockly.FieldAngle.superClass_.constructor.call(this, text, null);
|
|
|
|
this.setChangeHandler(opt_changeHandler);
|
|
|
|
};
|
|
|
|
goog.inherits(Blockly.FieldAngle, Blockly.FieldTextInput);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets a new change handler for angle field.
|
|
|
|
* @param {Function} handler New change handler, or null.
|
|
|
|
*/
|
|
|
|
Blockly.FieldAngle.prototype.setChangeHandler = function(handler) {
|
|
|
|
var wrappedHandler;
|
|
|
|
if (handler) {
|
2013-10-30 14:46:03 -07:00
|
|
|
// Wrap the user's change handler together with the angle validator.
|
2015-06-08 15:54:18 -07:00
|
|
|
wrappedHandler = function(value) {
|
2015-07-20 16:18:40 -07:00
|
|
|
var v1 = handler.call(this, value);
|
2015-06-08 15:54:18 -07:00
|
|
|
if (v1 === null) {
|
|
|
|
var v2 = v1;
|
|
|
|
} else {
|
|
|
|
if (v1 === undefined) {
|
|
|
|
v1 = value;
|
|
|
|
}
|
2015-07-20 16:18:40 -07:00
|
|
|
var v2 = Blockly.FieldAngle.angleValidator.call(this, v1);
|
2015-06-08 15:54:18 -07:00
|
|
|
if (v2 !== undefined) {
|
|
|
|
v2 = v1;
|
|
|
|
}
|
2013-10-30 14:46:03 -07:00
|
|
|
}
|
2015-06-08 15:54:18 -07:00
|
|
|
return v2 === value ? undefined : v2;
|
2013-10-30 14:46:03 -07:00
|
|
|
};
|
|
|
|
} else {
|
2015-06-08 15:54:18 -07:00
|
|
|
wrappedHandler = Blockly.FieldAngle.angleValidator;
|
2013-10-30 14:46:03 -07:00
|
|
|
}
|
2015-06-08 15:54:18 -07:00
|
|
|
Blockly.FieldAngle.superClass_.setChangeHandler(wrappedHandler);
|
2013-10-30 14:46:03 -07:00
|
|
|
};
|
|
|
|
|
2013-12-14 03:00:02 -08:00
|
|
|
/**
|
|
|
|
* Round angles to the nearest 15 degrees when using mouse.
|
|
|
|
* Set to 0 to disable rounding.
|
|
|
|
*/
|
|
|
|
Blockly.FieldAngle.ROUND = 15;
|
|
|
|
|
2013-10-30 14:46:03 -07:00
|
|
|
/**
|
|
|
|
* Half the width of protractor image.
|
|
|
|
*/
|
|
|
|
Blockly.FieldAngle.HALF = 100 / 2;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Radius of protractor circle. Slightly smaller than protractor size since
|
|
|
|
* otherwise SVG crops off half the border at the edges.
|
|
|
|
*/
|
|
|
|
Blockly.FieldAngle.RADIUS = Blockly.FieldAngle.HALF - 1;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Clean up this FieldAngle, as well as the inherited FieldTextInput.
|
|
|
|
* @return {!Function} Closure to call on destruction of the WidgetDiv.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
Blockly.FieldAngle.prototype.dispose_ = function() {
|
|
|
|
var thisField = this;
|
|
|
|
return function() {
|
|
|
|
Blockly.FieldAngle.superClass_.dispose_.call(thisField)();
|
|
|
|
thisField.gauge_ = null;
|
|
|
|
if (thisField.clickWrapper_) {
|
|
|
|
Blockly.unbindEvent_(thisField.clickWrapper_);
|
|
|
|
}
|
|
|
|
if (thisField.moveWrapper1_) {
|
|
|
|
Blockly.unbindEvent_(thisField.moveWrapper1_);
|
|
|
|
}
|
|
|
|
if (thisField.moveWrapper2_) {
|
|
|
|
Blockly.unbindEvent_(thisField.moveWrapper2_);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Show the inline free-text editor on top of the text.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
Blockly.FieldAngle.prototype.showEditor_ = function() {
|
2014-09-08 14:26:52 -07:00
|
|
|
var noFocus =
|
|
|
|
goog.userAgent.MOBILE || goog.userAgent.ANDROID || goog.userAgent.IPAD;
|
|
|
|
// Mobile browsers have issues with in-line textareas (focus & keyboards).
|
|
|
|
Blockly.FieldAngle.superClass_.showEditor_.call(this, noFocus);
|
2013-10-30 14:46:03 -07:00
|
|
|
var div = Blockly.WidgetDiv.DIV;
|
|
|
|
if (!div.firstChild) {
|
|
|
|
// Mobile interface uses window.prompt.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Build the SVG DOM.
|
|
|
|
var svg = Blockly.createSvgElement('svg', {
|
|
|
|
'xmlns': 'http://www.w3.org/2000/svg',
|
|
|
|
'xmlns:html': 'http://www.w3.org/1999/xhtml',
|
|
|
|
'xmlns:xlink': 'http://www.w3.org/1999/xlink',
|
|
|
|
'version': '1.1',
|
|
|
|
'height': (Blockly.FieldAngle.HALF * 2) + 'px',
|
|
|
|
'width': (Blockly.FieldAngle.HALF * 2) + 'px'
|
|
|
|
}, div);
|
|
|
|
var circle = Blockly.createSvgElement('circle', {
|
|
|
|
'cx': Blockly.FieldAngle.HALF, 'cy': Blockly.FieldAngle.HALF,
|
|
|
|
'r': Blockly.FieldAngle.RADIUS,
|
|
|
|
'class': 'blocklyAngleCircle'
|
|
|
|
}, svg);
|
2013-12-14 03:00:02 -08:00
|
|
|
this.gauge_ = Blockly.createSvgElement('path',
|
|
|
|
{'class': 'blocklyAngleGauge'}, svg);
|
|
|
|
this.line_ = Blockly.createSvgElement('line',
|
|
|
|
{'x1': Blockly.FieldAngle.HALF,
|
|
|
|
'y1': Blockly.FieldAngle.HALF,
|
|
|
|
'class': 'blocklyAngleLine'}, svg);
|
2013-10-30 14:46:03 -07:00
|
|
|
// Draw markers around the edge.
|
|
|
|
for (var a = 0; a < 360; a += 15) {
|
|
|
|
Blockly.createSvgElement('line', {
|
|
|
|
'x1': Blockly.FieldAngle.HALF + Blockly.FieldAngle.RADIUS,
|
|
|
|
'y1': Blockly.FieldAngle.HALF,
|
|
|
|
'x2': Blockly.FieldAngle.HALF + Blockly.FieldAngle.RADIUS -
|
|
|
|
(a % 45 == 0 ? 10 : 5),
|
|
|
|
'y2': Blockly.FieldAngle.HALF,
|
|
|
|
'class': 'blocklyAngleMarks',
|
|
|
|
'transform': 'rotate(' + a + ', ' +
|
|
|
|
Blockly.FieldAngle.HALF + ', ' + Blockly.FieldAngle.HALF + ')'
|
|
|
|
}, svg);
|
|
|
|
}
|
|
|
|
svg.style.marginLeft = '-35px';
|
|
|
|
this.clickWrapper_ =
|
|
|
|
Blockly.bindEvent_(svg, 'click', this, Blockly.WidgetDiv.hide);
|
|
|
|
this.moveWrapper1_ =
|
|
|
|
Blockly.bindEvent_(circle, 'mousemove', this, this.onMouseMove);
|
|
|
|
this.moveWrapper2_ =
|
|
|
|
Blockly.bindEvent_(this.gauge_, 'mousemove', this, this.onMouseMove);
|
2014-09-08 14:26:52 -07:00
|
|
|
this.updateGraph_();
|
2013-10-30 14:46:03 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the angle to match the mouse's position.
|
|
|
|
* @param {!Event} e Mouse move event.
|
|
|
|
*/
|
|
|
|
Blockly.FieldAngle.prototype.onMouseMove = function(e) {
|
|
|
|
var bBox = this.gauge_.ownerSVGElement.getBoundingClientRect();
|
|
|
|
var dx = e.clientX - bBox.left - Blockly.FieldAngle.HALF;
|
|
|
|
var dy = e.clientY - bBox.top - Blockly.FieldAngle.HALF;
|
|
|
|
var angle = Math.atan(-dy / dx);
|
|
|
|
if (isNaN(angle)) {
|
|
|
|
// This shouldn't happen, but let's not let this error propogate further.
|
|
|
|
return;
|
|
|
|
}
|
2014-11-24 15:08:19 -08:00
|
|
|
angle = goog.math.toDegrees(angle);
|
2013-10-30 14:46:03 -07:00
|
|
|
// 0: East, 90: North, 180: West, 270: South.
|
|
|
|
if (dx < 0) {
|
|
|
|
angle += 180;
|
|
|
|
} else if (dy > 0) {
|
|
|
|
angle += 360;
|
|
|
|
}
|
2013-12-14 03:00:02 -08:00
|
|
|
if (Blockly.FieldAngle.ROUND) {
|
|
|
|
angle = Math.round(angle / Blockly.FieldAngle.ROUND) *
|
|
|
|
Blockly.FieldAngle.ROUND;
|
|
|
|
}
|
|
|
|
if (angle >= 360) {
|
|
|
|
// Rounding may have rounded up to 360.
|
|
|
|
angle -= 360;
|
|
|
|
}
|
|
|
|
angle = String(angle);
|
2013-10-30 14:46:03 -07:00
|
|
|
Blockly.FieldTextInput.htmlInput_.value = angle;
|
|
|
|
this.setText(angle);
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Insert a degree symbol.
|
|
|
|
* @param {?string} text New text.
|
|
|
|
*/
|
|
|
|
Blockly.FieldAngle.prototype.setText = function(text) {
|
|
|
|
Blockly.FieldAngle.superClass_.setText.call(this, text);
|
2014-12-26 23:34:28 +00:00
|
|
|
if (!this.textElement_) {
|
|
|
|
// Not rendered yet.
|
|
|
|
return;
|
|
|
|
}
|
2014-09-08 14:26:52 -07:00
|
|
|
this.updateGraph_();
|
2013-10-30 14:46:03 -07:00
|
|
|
// Insert degree symbol.
|
2015-04-28 13:51:25 -07:00
|
|
|
if (this.sourceBlock_.RTL) {
|
2013-10-30 14:46:03 -07:00
|
|
|
this.textElement_.insertBefore(this.symbol_, this.textElement_.firstChild);
|
|
|
|
} else {
|
|
|
|
this.textElement_.appendChild(this.symbol_);
|
|
|
|
}
|
|
|
|
// Cached width is obsolete. Clear it.
|
|
|
|
this.size_.width = 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2014-09-08 14:26:52 -07:00
|
|
|
* Redraw the graph with the current angle.
|
|
|
|
* @private
|
2013-10-30 14:46:03 -07:00
|
|
|
*/
|
2014-09-08 14:26:52 -07:00
|
|
|
Blockly.FieldAngle.prototype.updateGraph_ = function() {
|
|
|
|
if (!this.gauge_) {
|
|
|
|
return;
|
|
|
|
}
|
2014-11-24 15:08:19 -08:00
|
|
|
var angleRadians = goog.math.toRadians(Number(this.getText()));
|
2014-09-08 14:26:52 -07:00
|
|
|
if (isNaN(angleRadians)) {
|
|
|
|
this.gauge_.setAttribute('d',
|
|
|
|
'M ' + Blockly.FieldAngle.HALF + ', ' + Blockly.FieldAngle.HALF);
|
|
|
|
this.line_.setAttribute('x2', Blockly.FieldAngle.HALF);
|
|
|
|
this.line_.setAttribute('y2', Blockly.FieldAngle.HALF);
|
|
|
|
} else {
|
|
|
|
var x = Blockly.FieldAngle.HALF + Math.cos(angleRadians) *
|
|
|
|
Blockly.FieldAngle.RADIUS;
|
|
|
|
var y = Blockly.FieldAngle.HALF + Math.sin(angleRadians) *
|
|
|
|
-Blockly.FieldAngle.RADIUS;
|
|
|
|
var largeFlag = (angleRadians > Math.PI) ? 1 : 0;
|
|
|
|
this.gauge_.setAttribute('d',
|
|
|
|
'M ' + Blockly.FieldAngle.HALF + ', ' + Blockly.FieldAngle.HALF +
|
|
|
|
' h ' + Blockly.FieldAngle.RADIUS +
|
|
|
|
' A ' + Blockly.FieldAngle.RADIUS + ',' + Blockly.FieldAngle.RADIUS +
|
|
|
|
' 0 ' + largeFlag + ' 0 ' + x + ',' + y + ' z');
|
|
|
|
this.line_.setAttribute('x2', x);
|
|
|
|
this.line_.setAttribute('y2', y);
|
2013-10-30 14:46:03 -07:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Ensure that only an angle may be entered.
|
|
|
|
* @param {string} text The user's text.
|
|
|
|
* @return {?string} A string representing a valid angle, or null if invalid.
|
|
|
|
*/
|
|
|
|
Blockly.FieldAngle.angleValidator = function(text) {
|
|
|
|
var n = Blockly.FieldTextInput.numberValidator(text);
|
|
|
|
if (n !== null) {
|
|
|
|
n = n % 360;
|
|
|
|
if (n < 0) {
|
|
|
|
n += 360;
|
|
|
|
}
|
|
|
|
n = String(n);
|
|
|
|
}
|
|
|
|
return n;
|
|
|
|
};
|