123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300 |
- /**
- * @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 Blockly.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';
- // The angle picker is different from other fields in that it updates on
- // mousemove even if it's not in the middle of a drag. In future we may
- // change this behavior. For now, using bindEvent_ instead of
- // bindEventWithChecks_ allows it to work without a mousedown/touchstart.
- 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);
- };
|