diff --git a/editor/extension.ts b/editor/extension.ts index 9e4c6c53..347b3ab1 100644 --- a/editor/extension.ts +++ b/editor/extension.ts @@ -4,10 +4,11 @@ import { deployCoreAsync, initAsync } from "./deploy"; import { FieldPorts } from "./field_ports"; import { FieldImages } from "./field_images"; -import {FieldSpeed} from "./field_speed"; +import { FieldSpeed } from "./field_speed"; import { FieldBrickButtons } from "./field_brickbuttons"; +import { FieldTurnRatio } from "./field_turnratio"; -pxt.editor.initExtensionsAsync = function (opts: pxt.editor.ExtensionOptions): Promise { +pxt.editor.initExtensionsAsync = function(opts: pxt.editor.ExtensionOptions): Promise { pxt.debug('loading pxt-ev3 target extensions...') updateBlocklyShape(); const res: pxt.editor.ExtensionResult = { @@ -23,6 +24,9 @@ pxt.editor.initExtensionsAsync = function (opts: pxt.editor.ExtensionOptions): P }, { selector: "brickbuttons", editor: FieldBrickButtons + }, { + selector: "turnratio", + editor: FieldTurnRatio }], deployCoreAsync }; diff --git a/editor/field_turnratio.ts b/editor/field_turnratio.ts new file mode 100644 index 00000000..f98c2156 --- /dev/null +++ b/editor/field_turnratio.ts @@ -0,0 +1,108 @@ +/// +/// + +export interface FieldTurnRatioOptions extends Blockly.FieldCustomOptions { +} + +export class FieldTurnRatio extends Blockly.FieldSlider implements Blockly.FieldCustom { + public isFieldCustom_ = true; + + private params: any; + + private path_: SVGPathElement; + private reporter_: SVGTextElement; + + /** + * Class for a color wheel field. + * @param {number|string} value 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 either the accepted text, a replacement + * text, or null to abort the change. + * @extends {Blockly.FieldNumber} + * @constructor + */ + constructor(value_: any, params: FieldTurnRatioOptions, opt_validator?: Function) { + super(String(value_), '-100', '100', null, '10', 'TurnRatio', opt_validator); + this.params = params; + (this as any).sliderColor_ = '#a8aaa8'; + } + + static HALF = 80; + static HANDLE_RADIUS = 30; + static RADIUS = FieldTurnRatio.HALF - FieldTurnRatio.HANDLE_RADIUS - 1; + + createLabelDom_(labelText: string) { + let labelContainer = document.createElement('div'); + let svg = Blockly.utils.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': (FieldTurnRatio.HALF + FieldTurnRatio.HANDLE_RADIUS + 10) + 'px', + 'width': (FieldTurnRatio.HALF * 2) + 'px' + }, labelContainer); + let defs = Blockly.utils.createSvgElement('defs', {}, svg); + let marker = Blockly.utils.createSvgElement('marker', { + 'id': 'head', + 'orient': "auto", + 'markerWidth': '2', + 'markerHeight': '4', + 'refX': '0.1', 'refY': '1.5' + }, defs); + let markerPath = Blockly.utils.createSvgElement('path', { + 'd': 'M0,0 V3 L1.5,1.5 Z', + 'fill': '#f12a21' + }, marker); + this.reporter_ = pxsim.svg.child(svg, "text", { + 'x': FieldTurnRatio.HALF, 'y': 96, + 'text-anchor': 'middle', 'alignment-baseline': 'middle', + 'style': 'font-size: 50px', + 'class': 'sim-text inverted number' + }) as SVGTextElement; + this.path_ = Blockly.utils.createSvgElement('path', { + 'x1': FieldTurnRatio.HALF, + 'y1': FieldTurnRatio.HALF, + 'marker-end': 'url(#head)', + 'style': 'fill: none; stroke: #f12a21; stroke-width: 10' + }, svg); + this.updateGraph_(); + let readout = document.createElement('span'); + readout.setAttribute('class', 'blocklyFieldSliderReadout'); + return [labelContainer, readout]; + }; + + updateGraph_() { + if (!this.path_) { + return; + } + let v = goog.math.clamp(parseFloat(this.getText()), -100, 100); + if (isNaN(v)) { + v = 0; + } + + const x = goog.math.clamp(parseFloat(this.getText()), -100, 100) / 100; + const theta = x * Math.PI / 2; + const cx = FieldTurnRatio.HALF; + const cy = FieldTurnRatio.HALF - 14; + const gamma = Math.PI - 2 * theta; + const r = FieldTurnRatio.RADIUS; + const alpha = 0.2 + Math.abs(x) * 0.5; + const x1 = 0; + const y1 = r * alpha; + const y2 = r * Math.sin(Math.PI / 2 - theta); + const x2 = r * Math.cos(Math.PI / 2 - theta); + const y3 = y2 - r * alpha * Math.cos(2 * theta); + const x3 = x2 - r * alpha * Math.sin(2 * theta); + + + const d = `M ${cx} ${cy} C ${cx} ${cy - y1} ${cx + x3} ${cy - y3} ${cx + x2} ${cy - y2}`; + this.path_.setAttribute('d', d); + + this.reporter_.textContent = `${v}`; + } + + setReadout_(readout: Element, value: string) { + this.updateGraph_(); + } +} \ No newline at end of file diff --git a/libs/core/_locales/core-strings.json b/libs/core/_locales/core-strings.json index c2a8fc3a..a0861b81 100644 --- a/libs/core/_locales/core-strings.json +++ b/libs/core/_locales/core-strings.json @@ -59,7 +59,7 @@ "motors.MotorBase.setReversed|block": "set %motor|reversed %reversed", "motors.MotorBase.setSpeed|block": "set %motor|speed to %speed=motorSpeedPicker|%", "motors.MotorBase.stop|block": "%motors|stop", - "motors.SynchedMotorPair.steer|block": "steer %chassis|turn ratio %turnRatio|speed %speed=motorSpeedPicker|%", + "motors.SynchedMotorPair.steer|block": "steer %chassis|turn ratio %turnRatio=motorTurnRatioPicker|speed %speed=motorSpeedPicker|%", "motors.SynchedMotorPair.tank|block": "tank %motors|%speedLeft=motorSpeedPicker|%|%speedRight=motorSpeedPicker|%", "motors.largeAB|block": "large A+B", "motors.largeAD|block": "large A+D", diff --git a/libs/core/ns.ts b/libs/core/ns.ts index 9b083c78..d658ca3a 100644 --- a/libs/core/ns.ts +++ b/libs/core/ns.ts @@ -11,4 +11,15 @@ namespace motors { export function __speedPicker(speed: number): number { return speed; } + + /** + * A turn ratio picker + * @param turnratio the turn ratio, eg: 0 + */ + //% blockId=motorTurnRatioPicker block="%turnratio" shim=TD_ID + //% turnratio.fieldEditor="turnratio" colorSecondary="#FFFFFF" + //% weight=0 blockHidden=1 turnRatio.fieldOptions.decompileLiterals=1 + export function __turnRatioPicker(turnratio: number): number { + return turnratio; + } } \ No newline at end of file diff --git a/libs/core/output.ts b/libs/core/output.ts index b62106a4..3be020a4 100644 --- a/libs/core/output.ts +++ b/libs/core/output.ts @@ -476,7 +476,7 @@ namespace motors { * @param value (optional) move duration or rotation * @param unit (optional) unit of the value */ - //% blockId=motorPairSteer block="steer %chassis|turn ratio %turnRatio|speed %speed=motorSpeedPicker|%" + //% blockId=motorPairSteer block="steer %chassis|turn ratio %turnRatio=motorTurnRatioPicker|speed %speed=motorSpeedPicker|%" //% weight=95 //% turnRatio.min=-200 turnRatio=200 //% inlineInputMode=inline diff --git a/package.json b/package.json index 0a0ea328..3c547787 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ }, "dependencies": { "pxt-common-packages": "0.15.4", - "pxt-core": "3.0.5" + "pxt-core": "3.0.7" }, "scripts": { "test": "node node_modules/pxt-core/built/pxt.js travis" diff --git a/sim/state/motornode.ts b/sim/state/motornode.ts index 2334c358..be502448 100644 --- a/sim/state/motornode.ts +++ b/sim/state/motornode.ts @@ -40,6 +40,10 @@ namespace pxsim { } setSpeedCmd(cmd: DAL, values: number[]) { + if (this.speedCmd != cmd || + JSON.stringify(this.speedCmdValues) != JSON.stringify(values)) + this.setChangedState(); + // new command TODO: values this.speedCmd = cmd; this.speedCmdValues = values; this.speedCmdTacho = this.angle;