summaryrefslogtreecommitdiff
path: root/src/blockly/core/field_angle.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/blockly/core/field_angle.js')
-rw-r--r--src/blockly/core/field_angle.js294
1 files changed, 294 insertions, 0 deletions
diff --git a/src/blockly/core/field_angle.js b/src/blockly/core/field_angle.js
new file mode 100644
index 0000000..a294948
--- /dev/null
+++ b/src/blockly/core/field_angle.js
@@ -0,0 +1,294 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2013 Google Inc.
+ * https://developers.google.com/blockly/
+ *
+ * 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');
+goog.require('goog.math');
+goog.require('goog.userAgent');
+
+
+/**
+ * Class for an editable angle field.
+ * @param {string} text The initial content of the field.
+ * @param {Function=} opt_validator An optional function that is called
+ * 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.
+ * @extends {Blockly.FieldTextInput}
+ * @constructor
+ */
+Blockly.FieldAngle = function(text, opt_validator) {
+ // 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, opt_validator);
+};
+goog.inherits(Blockly.FieldAngle, Blockly.FieldTextInput);
+
+/**
+ * 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.
+ */
+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).
+ */
+Blockly.FieldAngle.CLOCKWISE = false;
+
+/**
+ * Offset the location of 0 degrees (and all angles) by a constant.
+ * Usually either 0 (0 = right) or 90 (0 = up).
+ */
+Blockly.FieldAngle.OFFSET = 0;
+
+/**
+ * Maximum allowed angle before wrapping.
+ * Usually either 360 (for 0 to 359.9) or 180 (for -179.9 to 180).
+ */
+Blockly.FieldAngle.WRAP = 360;
+
+
+/**
+ * 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() {
+ 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);
+ 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);
+ this.gauge_ = Blockly.createSvgElement('path',
+ {'class': 'blocklyAngleGauge'}, svg);
+ this.line_ = Blockly.createSvgElement('line',
+ {'x1': Blockly.FieldAngle.HALF,
+ 'y1': Blockly.FieldAngle.HALF,
+ 'class': 'blocklyAngleLine'}, svg);
+ // Draw markers around the edge.
+ for (var angle = 0; angle < 360; angle += 15) {
+ Blockly.createSvgElement('line', {
+ 'x1': Blockly.FieldAngle.HALF + Blockly.FieldAngle.RADIUS,
+ 'y1': Blockly.FieldAngle.HALF,
+ 'x2': Blockly.FieldAngle.HALF + Blockly.FieldAngle.RADIUS -
+ (angle % 45 == 0 ? 10 : 5),
+ 'y2': Blockly.FieldAngle.HALF,
+ 'class': 'blocklyAngleMarks',
+ 'transform': 'rotate(' + angle + ',' +
+ Blockly.FieldAngle.HALF + ',' + Blockly.FieldAngle.HALF + ')'
+ }, svg);
+ }
+ svg.style.marginLeft = (15 - Blockly.FieldAngle.RADIUS) + 'px';
+ 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);
+ this.updateGraph_();
+};
+
+/**
+ * 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;
+ }
+ angle = goog.math.toDegrees(angle);
+ // 0: East, 90: North, 180: West, 270: South.
+ if (dx < 0) {
+ angle += 180;
+ } else if (dy > 0) {
+ angle += 360;
+ }
+ if (Blockly.FieldAngle.CLOCKWISE) {
+ angle = Blockly.FieldAngle.OFFSET + 360 - angle;
+ } else {
+ angle -= Blockly.FieldAngle.OFFSET;
+ }
+ if (Blockly.FieldAngle.ROUND) {
+ angle = Math.round(angle / Blockly.FieldAngle.ROUND) *
+ Blockly.FieldAngle.ROUND;
+ }
+ angle = this.callValidator(angle);
+ Blockly.FieldTextInput.htmlInput_.value = angle;
+ this.setValue(angle);
+ this.validate_();
+ this.resizeEditor_();
+};
+
+/**
+ * Insert a degree symbol.
+ * @param {?string} text New text.
+ */
+Blockly.FieldAngle.prototype.setText = function(text) {
+ Blockly.FieldAngle.superClass_.setText.call(this, text);
+ if (!this.textElement_) {
+ // Not rendered yet.
+ return;
+ }
+ this.updateGraph_();
+ // Insert degree symbol.
+ if (this.sourceBlock_.RTL) {
+ this.textElement_.insertBefore(this.symbol_, this.textElement_.firstChild);
+ } else {
+ this.textElement_.appendChild(this.symbol_);
+ }
+ // Cached width is obsolete. Clear it.
+ this.size_.width = 0;
+};
+
+/**
+ * Redraw the graph with the current angle.
+ * @private
+ */
+Blockly.FieldAngle.prototype.updateGraph_ = function() {
+ if (!this.gauge_) {
+ return;
+ }
+ var angleDegrees = Number(this.getText()) + Blockly.FieldAngle.OFFSET;
+ var angleRadians = goog.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 = goog.math.toRadians(Blockly.FieldAngle.OFFSET);
+ var x1 = Math.cos(angle1) * Blockly.FieldAngle.RADIUS;
+ var y1 = Math.sin(angle1) * -Blockly.FieldAngle.RADIUS;
+ if (Blockly.FieldAngle.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) {
+ 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');
+ }
+ this.gauge_.setAttribute('d', path.join(''));
+ this.line_.setAttribute('x2', x2);
+ this.line_.setAttribute('y2', y2);
+};
+
+/**
+ * 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.prototype.classValidator = function(text) {
+ if (text === null) {
+ return null;
+ }
+ var n = parseFloat(text || 0);
+ if (isNaN(n)) {
+ return null;
+ }
+ n = n % 360;
+ if (n < 0) {
+ n += 360;
+ }
+ if (n > Blockly.FieldAngle.WRAP) {
+ n -= 360;
+ }
+ return String(n);
+};