From cbe68b3199618d49be4831eb2ad0a76f5e682766 Mon Sep 17 00:00:00 2001 From: Sam El-Husseini Date: Thu, 28 Dec 2017 13:23:30 -0800 Subject: [PATCH 1/6] Add motor slider control --- sim/state/motornode.ts | 11 +- sim/visuals/board.ts | 17 ++- sim/visuals/controls/distanceSlider.ts | 7 +- sim/visuals/controls/motorSlider.ts | 199 +++++++++++++++++++++++++ 4 files changed, 228 insertions(+), 6 deletions(-) create mode 100644 sim/visuals/controls/motorSlider.ts diff --git a/sim/state/motornode.ts b/sim/state/motornode.ts index 5676eae6..9a35d31f 100644 --- a/sim/state/motornode.ts +++ b/sim/state/motornode.ts @@ -73,8 +73,17 @@ namespace pxsim { this.started = true; } + isInput() { + // TODO: figure out if a motor is in an input or output state + return true; + } + + setSpeedAsInput(speed: number) { + this.speed = speed; + } + updateState(elapsed: number) { - console.log(`motor: ${elapsed}ms - ${this.speed}% - ${this.angle}> - ${this.tacho}|`) + //console.log(`motor: ${elapsed}ms - ${this.speed}% - ${this.angle}> - ${this.tacho}|`) const interval = Math.min(20, elapsed); let t = 0; while(t < elapsed) { diff --git a/sim/visuals/board.ts b/sim/visuals/board.ts index e598839e..86931cf7 100644 --- a/sim/visuals/board.ts +++ b/sim/visuals/board.ts @@ -44,7 +44,7 @@ namespace pxsim.visuals { } .sim-text.number { font-family: Courier, Lato, Work Sans, PT Serif, Source Serif Pro; - font-weight: bold; + /*font-weight: bold;*/ } .sim-text.inverted { fill:#5A5A5A; @@ -62,6 +62,14 @@ namespace pxsim.visuals { fill: gray !important; cursor: pointer; } + + /* Motor slider */ + .sim-motor-btn { + cursor: pointer; + } + .sim-motor-btn:hover { + fill: gray !important; + } `; const EV3_WIDTH = 99.984346; @@ -213,9 +221,12 @@ namespace pxsim.visuals { } case NodeType.MediumMotor: case NodeType.LargeMotor: { - // TODO: figure out if the motor is in "input" or "output" mode const state = ev3board().getMotors()[port]; - view = new MotorReporterControl(this.element, this.defs, state, port); + if (state.isInput()) { + view = new MotorSliderControl(this.element, this.defs, state, port); + } else { + view = new MotorReporterControl(this.element, this.defs, state, port); + } break; } } diff --git a/sim/visuals/controls/distanceSlider.ts b/sim/visuals/controls/distanceSlider.ts index a4542ab6..7c5ee3f1 100644 --- a/sim/visuals/controls/distanceSlider.ts +++ b/sim/visuals/controls/distanceSlider.ts @@ -27,8 +27,8 @@ namespace pxsim.visuals { this.group = svg.elt("g") as SVGGElement; const reporterGroup = pxsim.svg.child(this.group, "g"); - reporterGroup.setAttribute("transform", `translate(31, 42)`); - this.reporter = pxsim.svg.child(reporterGroup, "text", { 'x': 0, 'y': '0', 'class': 'sim-text number large inverted' }) as SVGTextElement; + reporterGroup.setAttribute("transform", `translate(${this.getWidth() / 2}, 42)`); + this.reporter = pxsim.svg.child(reporterGroup, "text", { 'text-anchor': 'middle', 'x': 0, 'y': '0', 'class': 'sim-text number large inverted' }) as SVGTextElement; const sliderGroup = pxsim.svg.child(this.group, "g"); sliderGroup.setAttribute("transform", `translate(${this.getWidth() / 2 - this.getSliderWidth() / 2}, ${this.getReporterHeight()})`) @@ -60,12 +60,15 @@ namespace pxsim.visuals { }, ev => { captured = true; if ((ev as MouseEvent).clientY != undefined) { + dragSurface.setAttribute('cursor', '-webkit-grabbing'); this.updateSliderValue(pt, parent, ev as MouseEvent); } }, () => { captured = false; + dragSurface.setAttribute('cursor', '-webkit-grab'); }, () => { captured = false; + dragSurface.setAttribute('cursor', '-webkit-grab'); }) return this.group; diff --git a/sim/visuals/controls/motorSlider.ts b/sim/visuals/controls/motorSlider.ts new file mode 100644 index 00000000..3633bad0 --- /dev/null +++ b/sim/visuals/controls/motorSlider.ts @@ -0,0 +1,199 @@ + + +namespace pxsim.visuals { + + export class MotorSliderControl extends ControlView { + private group: SVGGElement; + private gradient: SVGLinearGradientElement; + private slider: SVGGElement; + + private reporter: SVGTextElement; + + private circleBar: SVGCircleElement; + private dial: SVGGElement; + + private static SLIDER_RADIUS = 100; + + private internalSpeed: number = 0; + + getInnerView(parent: SVGSVGElement, globalDefs: SVGDefsElement) { + this.group = svg.elt("g") as SVGGElement; + const sliderHeight = 250; + + const slider = pxsim.svg.child(this.group, 'g', { 'transform': 'translate(25,25)' }) + const outerCircle = pxsim.svg.child(slider, "circle", { + 'stroke-dasharray': '565.48', 'stroke-dashoffset': '0', + 'cx': 100, 'cy': 100, 'r': '90', 'style': `fill:transparent;`, + 'stroke': '#a8aaa8', 'stroke-width': '1rem' + }) as SVGCircleElement; + this.circleBar = pxsim.svg.child(slider, "circle", { + 'stroke-dasharray': '565.48', 'stroke-dashoffset': '0', + 'cx': 100, 'cy': 100, 'r': '90', 'style': `fill:transparent;`, + 'stroke': '#f12a21', 'stroke-width': '1rem', 'transform': 'rotate(-90 100 100)' + }) as SVGCircleElement; + + this.reporter = pxsim.svg.child(this.group, "text", { + 'x': this.getInnerWidth() / 2, 'y': sliderHeight / 2, + 'text-anchor': 'middle', 'alignment-baseline': 'middle', + 'style': 'font-size: 50px', + 'class': 'sim-text inverted number' + }) as SVGTextElement; + + this.dial = pxsim.svg.child(slider, "g", { 'cursor': '-webkit-grab' }) as SVGGElement; + const handleInner = pxsim.svg.child(this.dial, "g"); + pxsim.svg.child(handleInner, "circle", { 'cx': 0, 'cy': 0, 'r': 30, 'style': 'fill: #f12a21;' }); + pxsim.svg.child(handleInner, "circle", { 'cx': 0, 'cy': 0, 'r': 29.5, 'style': 'fill: none;stroke: #b32e29' }); + + // Add move buttons + const moveBtnRadius = 40; + const leftMoveG = pxsim.svg.child(this.group, 'g'); + const leftMove = pxsim.svg.child(leftMoveG, 'circle', { + 'cx': moveBtnRadius, 'cy': sliderHeight + moveBtnRadius, + 'r': moveBtnRadius, 'style': 'fill: #a8aaa8', 'class': 'sim-motor-btn' + }); + let leftMoveFrame: number; + touchEvents(leftMove, ev => { + // move + }, ev => { + if (leftMoveFrame) cancelAnimationFrame(leftMoveFrame); + let setSpeed = () => { + leftMoveFrame = requestAnimationFrame(() => { + this.state.setSpeedAsInput(-1 * this.internalSpeed); + setSpeed(); + }) + } + setSpeed(); + }, () => { + if (leftMoveFrame) cancelAnimationFrame(leftMoveFrame); + }, () => { + if (leftMoveFrame) cancelAnimationFrame(leftMoveFrame); + }) + + const rightMoveG = pxsim.svg.child(this.group, 'g'); + const rightMove = pxsim.svg.child(rightMoveG, 'circle', { + 'cx': this.getWidth() - moveBtnRadius, 'cy': sliderHeight + moveBtnRadius, + 'r': moveBtnRadius, 'style': 'fill: #a8aaa8', 'class': 'sim-motor-btn' + }); + let rightMoveFrame: number; + touchEvents(rightMove, ev => { + // move + }, ev => { + if (rightMoveFrame) cancelAnimationFrame(rightMoveFrame); + let setSpeed = () => { + rightMoveFrame = requestAnimationFrame(() => { + this.state.setSpeedAsInput(this.internalSpeed); + setSpeed(); + }) + } + setSpeed(); + }, () => { + if (rightMoveFrame) cancelAnimationFrame(rightMoveFrame); + }, () => { + if (rightMoveFrame) cancelAnimationFrame(rightMoveFrame); + }) + + this.updateInternalSpeed(); + + let pt = parent.createSVGPoint(); + let captured = false; + + const dragSurface = svg.child(this.group, "rect", { + x: 0, + y: 0, + width: this.getInnerWidth(), + height: sliderHeight, + opacity: 0, + cursor: '-webkit-grab' + }) + + touchEvents(dragSurface, ev => { + if (captured && (ev as MouseEvent).clientY != undefined) { + ev.preventDefault(); + this.updateSliderValue(pt, parent, ev as MouseEvent); + } + }, ev => { + captured = true; + if ((ev as MouseEvent).clientY != undefined) { + this.dial.setAttribute('cursor', '-webkit-grabbing'); + this.updateSliderValue(pt, parent, ev as MouseEvent); + } + }, () => { + captured = false; + this.dial.setAttribute('cursor', '-webkit-grab'); + }, () => { + captured = false; + this.dial.setAttribute('cursor', '-webkit-grab'); + }) + + return this.group; + } + + getInnerWidth() { + return 250; + } + + getInnerHeight() { + return 330; + } + + private lastPosition: number; + private prevVal: number; + private updateSliderValue(pt: SVGPoint, parent: SVGSVGElement, ev: MouseEvent) { + let cur = svg.cursorPoint(pt, parent, ev); + const coords = { + x: cur.x / this.scaleFactor - this.left / this.scaleFactor, + y: cur.y / this.scaleFactor - this.top / this.scaleFactor + }; + const radius = MotorSliderControl.SLIDER_RADIUS / 2; + const dx = coords.x - radius; + const dy = coords.y - radius; + const atan = Math.atan(-dy / dx); + let deg = Math.ceil(atan * (180 / Math.PI)); + + if (dx < 0) { + deg -= 270; + } else if (dy > 0) { + deg -= 450; + } else if (dx >= 0 && dy <= 0) { + deg = 90 - deg; + } + const value = Math.abs(Math.ceil((deg % 360) / 360 * this.getMax())); + + this.internalSpeed = value; + this.updateInternalSpeed(); + + this.prevVal = deg; + this.lastPosition = cur.x; + } + + private updateInternalSpeed() { + let speed = this.internalSpeed; + + // Update speed on circle bar + let c = Math.PI * (90 * 2); + speed = Math.abs(speed); + let pct = ((100 - speed) / 100) * c; + this.circleBar.setAttribute('stroke-dashoffset', `${pct}`); + + // Update reporter text + this.reporter.textContent = `${speed}`; + + // Update dial position + const deg = speed / this.getMax() * 360; // degrees + const radius = MotorSliderControl.SLIDER_RADIUS; + const dialRadius = 5; + const x = Math.ceil((radius - dialRadius) * Math.sin(deg * Math.PI / 180)) + radius; + const y = Math.ceil((radius - dialRadius) * -Math.cos(deg * Math.PI / 180)) + radius; + this.dial.setAttribute('transform', `translate(${x}, ${y})`); + } + + private getMin() { + return 0; + } + + private getMax() { + return 100; + } + } + +} \ No newline at end of file From 16b9a5027dfd81c765ddb13101253f7f4962d54d Mon Sep 17 00:00:00 2001 From: Sam El-Husseini Date: Fri, 29 Dec 2017 11:39:06 -0800 Subject: [PATCH 2/6] Add rotate icons --- sim/visuals/board.ts | 5 +++-- sim/visuals/controls/motorSlider.ts | 23 ++++++++++++++++------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/sim/visuals/board.ts b/sim/visuals/board.ts index 86931cf7..340a1b6c 100644 --- a/sim/visuals/board.ts +++ b/sim/visuals/board.ts @@ -67,8 +67,9 @@ namespace pxsim.visuals { .sim-motor-btn { cursor: pointer; } - .sim-motor-btn:hover { - fill: gray !important; + .sim-motor-btn:hover .btn { + stroke-width: 2px; + stroke: black !important; } `; diff --git a/sim/visuals/controls/motorSlider.ts b/sim/visuals/controls/motorSlider.ts index 3633bad0..f0d74f6a 100644 --- a/sim/visuals/controls/motorSlider.ts +++ b/sim/visuals/controls/motorSlider.ts @@ -45,12 +45,16 @@ namespace pxsim.visuals { pxsim.svg.child(handleInner, "circle", { 'cx': 0, 'cy': 0, 'r': 29.5, 'style': 'fill: none;stroke: #b32e29' }); // Add move buttons - const moveBtnRadius = 40; - const leftMoveG = pxsim.svg.child(this.group, 'g'); + const leftMoveG = pxsim.svg.child(this.group, 'g', {'class': 'sim-motor-btn', 'transform': `translate(${1}, ${sliderHeight - 2}) scale(2.5)`}); const leftMove = pxsim.svg.child(leftMoveG, 'circle', { - 'cx': moveBtnRadius, 'cy': sliderHeight + moveBtnRadius, - 'r': moveBtnRadius, 'style': 'fill: #a8aaa8', 'class': 'sim-motor-btn' + 'cx': 16, 'cy': 16, 'r': 16, 'style': 'fill: #a8aaa8', 'class': 'btn' }); + const semiCircleLeft = pxsim.svg.child(leftMoveG, 'g'); + pxsim.svg.child(semiCircleLeft, 'circle', { 'cx': 16, 'cy': 16, 'r': 9, 'style': 'fill: none'}); + pxsim.svg.child(semiCircleLeft, 'circle', { 'cx': 16, 'cy': 16, 'r': 8, 'style': 'fill: none;stroke: #fff;stroke-width: 2px'}); + pxsim.svg.child(leftMoveG, 'path', {'d': 'M501,382.33l-6.62-2.28-2.28,6.62,6.62,2.28Z', 'transform': 'translate(-472 -368)', 'fill': '#a8aaa8'}); + pxsim.svg.child(leftMoveG, 'path', {'d': 'M497.93,377.62c-.57,2.09-1.14,4.11-1.71,6.18l-6.06-2Z', 'transform': 'translate(-472 -368)', 'fill': '#fff'}); + let leftMoveFrame: number; touchEvents(leftMove, ev => { // move @@ -69,11 +73,16 @@ namespace pxsim.visuals { if (leftMoveFrame) cancelAnimationFrame(leftMoveFrame); }) - const rightMoveG = pxsim.svg.child(this.group, 'g'); + const rightMoveG = pxsim.svg.child(this.group, 'g', {'class': 'sim-motor-btn', 'transform': `translate(${42}, ${sliderHeight - 2}) scale(2.5)`}); const rightMove = pxsim.svg.child(rightMoveG, 'circle', { - 'cx': this.getWidth() - moveBtnRadius, 'cy': sliderHeight + moveBtnRadius, - 'r': moveBtnRadius, 'style': 'fill: #a8aaa8', 'class': 'sim-motor-btn' + 'cx': 67, 'cy': 16, 'r': 16, 'style': 'fill: #a8aaa8', 'class': 'btn' }); + const semiCircleRight = pxsim.svg.child(rightMoveG, 'g'); + pxsim.svg.child(semiCircleRight, 'circle', { 'cx': 67, 'cy': 17, 'r': 9, 'style': 'fill: none'}); + pxsim.svg.child(semiCircleRight, 'circle', { 'cx': 67, 'cy': 17, 'r': 8, 'style': 'fill: none;stroke: #fff;stroke-width: 2px'}); + pxsim.svg.child(rightMoveG, 'rect', {'x': 527, 'y': 380, 'width': 7, 'height': 7, 'transform': 'translate(-567.95 -174.39) rotate(-19)', 'style': 'fill: #a8aaa8'}); + pxsim.svg.child(rightMoveG, 'path', {'d': 'M529.08,376.63c.57,2.09,1.14,4.11,1.7,6.18l6.06-2Z', 'transform': 'translate(-472 -368)', 'fill': '#fff'}); + let rightMoveFrame: number; touchEvents(rightMove, ev => { // move From aa8635c4e71f074f1b46d346ad21a1cbe2aaaf7c Mon Sep 17 00:00:00 2001 From: Sam El-Husseini Date: Wed, 10 Jan 2018 10:00:48 -0800 Subject: [PATCH 3/6] Always use the motor slider control --- sim/state/motornode.ts | 5 ----- sim/visuals/board.ts | 6 +----- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/sim/state/motornode.ts b/sim/state/motornode.ts index 9ac4298f..46693274 100644 --- a/sim/state/motornode.ts +++ b/sim/state/motornode.ts @@ -96,11 +96,6 @@ namespace pxsim { this.started = true; } - isInput() { - // TODO: figure out if a motor is in an input or output state - return true; - } - setSpeedAsInput(speed: number) { this.speed = speed; } diff --git a/sim/visuals/board.ts b/sim/visuals/board.ts index 9decc9c1..d3be5c8a 100644 --- a/sim/visuals/board.ts +++ b/sim/visuals/board.ts @@ -232,11 +232,7 @@ namespace pxsim.visuals { case NodeType.MediumMotor: case NodeType.LargeMotor: { const state = ev3board().getMotors()[port]; - if (state.isInput()) { - view = new MotorSliderControl(this.element, this.defs, state, port); - } else { - view = new MotorReporterControl(this.element, this.defs, state, port); - } + view = new MotorSliderControl(this.element, this.defs, state, port); break; } } From 8357372fb56f3c84a1ab94d4adcb8cb3326d49f2 Mon Sep 17 00:00:00 2001 From: Sam El-Husseini Date: Wed, 10 Jan 2018 13:47:39 -0800 Subject: [PATCH 4/6] Update to make it more like a crank --- sim/state/motornode.ts | 12 ++- sim/visuals/controls/motorSlider.ts | 124 ++++++++++------------------ 2 files changed, 52 insertions(+), 84 deletions(-) diff --git a/sim/state/motornode.ts b/sim/state/motornode.ts index 46693274..1da46892 100644 --- a/sim/state/motornode.ts +++ b/sim/state/motornode.ts @@ -96,8 +96,16 @@ namespace pxsim { this.started = true; } - setSpeedAsInput(speed: number) { - this.speed = speed; + manualMotorDown() { + + } + + manualMotorMove(angle: number) { + + } + + manualMotorUp() { + } updateState(elapsed: number) { diff --git a/sim/visuals/controls/motorSlider.ts b/sim/visuals/controls/motorSlider.ts index f0d74f6a..c168a26d 100644 --- a/sim/visuals/controls/motorSlider.ts +++ b/sim/visuals/controls/motorSlider.ts @@ -9,7 +9,7 @@ namespace pxsim.visuals { private reporter: SVGTextElement; - private circleBar: SVGCircleElement; + // private circleBar: SVGCircleElement; private dial: SVGGElement; private static SLIDER_RADIUS = 100; @@ -18,7 +18,6 @@ namespace pxsim.visuals { getInnerView(parent: SVGSVGElement, globalDefs: SVGDefsElement) { this.group = svg.elt("g") as SVGGElement; - const sliderHeight = 250; const slider = pxsim.svg.child(this.group, 'g', { 'transform': 'translate(25,25)' }) const outerCircle = pxsim.svg.child(slider, "circle", { @@ -26,14 +25,9 @@ namespace pxsim.visuals { 'cx': 100, 'cy': 100, 'r': '90', 'style': `fill:transparent;`, 'stroke': '#a8aaa8', 'stroke-width': '1rem' }) as SVGCircleElement; - this.circleBar = pxsim.svg.child(slider, "circle", { - 'stroke-dasharray': '565.48', 'stroke-dashoffset': '0', - 'cx': 100, 'cy': 100, 'r': '90', 'style': `fill:transparent;`, - 'stroke': '#f12a21', 'stroke-width': '1rem', 'transform': 'rotate(-90 100 100)' - }) as SVGCircleElement; this.reporter = pxsim.svg.child(this.group, "text", { - 'x': this.getInnerWidth() / 2, 'y': sliderHeight / 2, + 'x': this.getInnerWidth() / 2, 'y': this.getInnerHeight() / 2, 'text-anchor': 'middle', 'alignment-baseline': 'middle', 'style': 'font-size: 50px', 'class': 'sim-text inverted number' @@ -44,64 +38,7 @@ namespace pxsim.visuals { pxsim.svg.child(handleInner, "circle", { 'cx': 0, 'cy': 0, 'r': 30, 'style': 'fill: #f12a21;' }); pxsim.svg.child(handleInner, "circle", { 'cx': 0, 'cy': 0, 'r': 29.5, 'style': 'fill: none;stroke: #b32e29' }); - // Add move buttons - const leftMoveG = pxsim.svg.child(this.group, 'g', {'class': 'sim-motor-btn', 'transform': `translate(${1}, ${sliderHeight - 2}) scale(2.5)`}); - const leftMove = pxsim.svg.child(leftMoveG, 'circle', { - 'cx': 16, 'cy': 16, 'r': 16, 'style': 'fill: #a8aaa8', 'class': 'btn' - }); - const semiCircleLeft = pxsim.svg.child(leftMoveG, 'g'); - pxsim.svg.child(semiCircleLeft, 'circle', { 'cx': 16, 'cy': 16, 'r': 9, 'style': 'fill: none'}); - pxsim.svg.child(semiCircleLeft, 'circle', { 'cx': 16, 'cy': 16, 'r': 8, 'style': 'fill: none;stroke: #fff;stroke-width: 2px'}); - pxsim.svg.child(leftMoveG, 'path', {'d': 'M501,382.33l-6.62-2.28-2.28,6.62,6.62,2.28Z', 'transform': 'translate(-472 -368)', 'fill': '#a8aaa8'}); - pxsim.svg.child(leftMoveG, 'path', {'d': 'M497.93,377.62c-.57,2.09-1.14,4.11-1.71,6.18l-6.06-2Z', 'transform': 'translate(-472 -368)', 'fill': '#fff'}); - - let leftMoveFrame: number; - touchEvents(leftMove, ev => { - // move - }, ev => { - if (leftMoveFrame) cancelAnimationFrame(leftMoveFrame); - let setSpeed = () => { - leftMoveFrame = requestAnimationFrame(() => { - this.state.setSpeedAsInput(-1 * this.internalSpeed); - setSpeed(); - }) - } - setSpeed(); - }, () => { - if (leftMoveFrame) cancelAnimationFrame(leftMoveFrame); - }, () => { - if (leftMoveFrame) cancelAnimationFrame(leftMoveFrame); - }) - - const rightMoveG = pxsim.svg.child(this.group, 'g', {'class': 'sim-motor-btn', 'transform': `translate(${42}, ${sliderHeight - 2}) scale(2.5)`}); - const rightMove = pxsim.svg.child(rightMoveG, 'circle', { - 'cx': 67, 'cy': 16, 'r': 16, 'style': 'fill: #a8aaa8', 'class': 'btn' - }); - const semiCircleRight = pxsim.svg.child(rightMoveG, 'g'); - pxsim.svg.child(semiCircleRight, 'circle', { 'cx': 67, 'cy': 17, 'r': 9, 'style': 'fill: none'}); - pxsim.svg.child(semiCircleRight, 'circle', { 'cx': 67, 'cy': 17, 'r': 8, 'style': 'fill: none;stroke: #fff;stroke-width: 2px'}); - pxsim.svg.child(rightMoveG, 'rect', {'x': 527, 'y': 380, 'width': 7, 'height': 7, 'transform': 'translate(-567.95 -174.39) rotate(-19)', 'style': 'fill: #a8aaa8'}); - pxsim.svg.child(rightMoveG, 'path', {'d': 'M529.08,376.63c.57,2.09,1.14,4.11,1.7,6.18l6.06-2Z', 'transform': 'translate(-472 -368)', 'fill': '#fff'}); - - let rightMoveFrame: number; - touchEvents(rightMove, ev => { - // move - }, ev => { - if (rightMoveFrame) cancelAnimationFrame(rightMoveFrame); - let setSpeed = () => { - rightMoveFrame = requestAnimationFrame(() => { - this.state.setSpeedAsInput(this.internalSpeed); - setSpeed(); - }) - } - setSpeed(); - }, () => { - if (rightMoveFrame) cancelAnimationFrame(rightMoveFrame); - }, () => { - if (rightMoveFrame) cancelAnimationFrame(rightMoveFrame); - }) - - this.updateInternalSpeed(); + this.updateDial(); let pt = parent.createSVGPoint(); let captured = false; @@ -110,7 +47,7 @@ namespace pxsim.visuals { x: 0, y: 0, width: this.getInnerWidth(), - height: sliderHeight, + height: this.getInnerHeight(), opacity: 0, cursor: '-webkit-grab' }) @@ -119,19 +56,20 @@ namespace pxsim.visuals { if (captured && (ev as MouseEvent).clientY != undefined) { ev.preventDefault(); this.updateSliderValue(pt, parent, ev as MouseEvent); + this.handleSliderDown(); } }, ev => { captured = true; if ((ev as MouseEvent).clientY != undefined) { - this.dial.setAttribute('cursor', '-webkit-grabbing'); this.updateSliderValue(pt, parent, ev as MouseEvent); + this.handleSliderMove(); } }, () => { captured = false; - this.dial.setAttribute('cursor', '-webkit-grab'); + this.handleSliderUp(); }, () => { captured = false; - this.dial.setAttribute('cursor', '-webkit-grab'); + this.handleSliderUp(); }) return this.group; @@ -142,7 +80,7 @@ namespace pxsim.visuals { } getInnerHeight() { - return 330; + return 250; } private lastPosition: number; @@ -169,24 +107,35 @@ namespace pxsim.visuals { const value = Math.abs(Math.ceil((deg % 360) / 360 * this.getMax())); this.internalSpeed = value; - this.updateInternalSpeed(); + this.updateDial(); this.prevVal = deg; this.lastPosition = cur.x; } - private updateInternalSpeed() { + private handleSliderDown() { + const state = this.state; + state.manualMotorDown(); + } + + private handleSliderMove() { + this.dial.setAttribute('cursor', '-webkit-grabbing'); + const state = this.state; + state.manualMotorMove(this.internalSpeed); + } + + private handleSliderUp() { + this.dial.setAttribute('cursor', '-webkit-grab'); + const state = this.state; + state.manualMotorUp(); + + this.internalSpeed = 0; + this.updateDial(); + } + + private updateDial() { let speed = this.internalSpeed; - // Update speed on circle bar - let c = Math.PI * (90 * 2); - speed = Math.abs(speed); - let pct = ((100 - speed) / 100) * c; - this.circleBar.setAttribute('stroke-dashoffset', `${pct}`); - - // Update reporter text - this.reporter.textContent = `${speed}`; - // Update dial position const deg = speed / this.getMax() * 360; // degrees const radius = MotorSliderControl.SLIDER_RADIUS; @@ -196,6 +145,17 @@ namespace pxsim.visuals { this.dial.setAttribute('transform', `translate(${x}, ${y})`); } + updateState() { + if (!this.visible) { + return; + } + const node = this.state; + const speed = node.getSpeed(); + + // Update reporter + this.reporter.textContent = `${speed}`; + } + private getMin() { return 0; } From e93e659e8a83a6e21906bf2e06e2f2424a8415d0 Mon Sep 17 00:00:00 2001 From: Sam El-Husseini Date: Wed, 10 Jan 2018 13:51:35 -0800 Subject: [PATCH 5/6] nit: remove unnecessary comment --- sim/visuals/controls/motorSlider.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/sim/visuals/controls/motorSlider.ts b/sim/visuals/controls/motorSlider.ts index c168a26d..2e30731e 100644 --- a/sim/visuals/controls/motorSlider.ts +++ b/sim/visuals/controls/motorSlider.ts @@ -9,7 +9,6 @@ namespace pxsim.visuals { private reporter: SVGTextElement; - // private circleBar: SVGCircleElement; private dial: SVGGElement; private static SLIDER_RADIUS = 100; From 0dd5ab9bde21581943ef00bc84bd8d0e4c1321a3 Mon Sep 17 00:00:00 2001 From: Peli de Halleux Date: Wed, 10 Jan 2018 14:08:50 -0800 Subject: [PATCH 6/6] appliying manual speed --- sim/state/motornode.ts | 150 +++++++++++++++++++++-------------------- 1 file changed, 78 insertions(+), 72 deletions(-) diff --git a/sim/state/motornode.ts b/sim/state/motornode.ts index 1da46892..1abab33b 100644 --- a/sim/state/motornode.ts +++ b/sim/state/motornode.ts @@ -17,6 +17,8 @@ namespace pxsim { private speedCmdTime: number; private _synchedMotor: MotorNode; // non-null if synchronized + private manualSpeed: number = 0; + constructor(port: number, large: boolean) { super(port); this.setLarge(large); @@ -42,7 +44,7 @@ namespace pxsim { setSpeedCmd(cmd: DAL, values: number[]) { if (this.speedCmd != cmd || JSON.stringify(this.speedCmdValues) != JSON.stringify(values)) - this.setChangedState(); + this.setChangedState(); // new command TODO: values this.speedCmd = cmd; this.speedCmdValues = values; @@ -97,15 +99,14 @@ namespace pxsim { } manualMotorDown() { - } - manualMotorMove(angle: number) { - + manualMotorMove(speed: number) { + this.manualSpeed = speed; } manualMotorUp() { - + this.manualSpeed = undefined; } updateState(elapsed: number) { @@ -121,79 +122,84 @@ namespace pxsim { } private updateStateStep(elapsed: number) { - // compute new speed - switch (this.speedCmd) { - case DAL.opOutputSpeed: - case DAL.opOutputPower: - // assume power == speed - // TODO: PID - this.speed = this.speedCmdValues[0]; - break; - case DAL.opOutputTimeSpeed: - case DAL.opOutputTimePower: - case DAL.opOutputStepPower: - case DAL.opOutputStepSpeed: { - // ramp up, run, ramp down, using time - const speed = this.speedCmdValues[0]; - const step1 = this.speedCmdValues[1]; - const step2 = this.speedCmdValues[2]; - const step3 = this.speedCmdValues[3]; - const brake = this.speedCmdValues[4]; - const dstep = (this.speedCmd == DAL.opOutputTimePower || this.speedCmd == DAL.opOutputTimeSpeed) - ? pxsim.U.now() - this.speedCmdTime - : this.tacho - this.speedCmdTacho; - if (dstep < step1) // rampup - this.speed = speed * dstep / step1; - else if (dstep < step1 + step2) // run - this.speed = speed; - else if (dstep < step1 + step2 + step3) - this.speed = speed * (step1 + step2 + step3 - dstep) / (step1 + step2 + step3); - else { - if (brake) this.speed = 0; - this.clearSpeedCmd(); - } - break; - } - case DAL.opOutputStepSync: - case DAL.opOutputTimeSync: { - const otherMotor = this._synchedMotor; - if (otherMotor.port < this.port) // handled in other motor code + if (!this.manualSpeed) { + // compute new speed + switch (this.speedCmd) { + case DAL.opOutputSpeed: + case DAL.opOutputPower: + // assume power == speed + // TODO: PID + this.speed = this.speedCmdValues[0]; + break; + case DAL.opOutputTimeSpeed: + case DAL.opOutputTimePower: + case DAL.opOutputStepPower: + case DAL.opOutputStepSpeed: { + // ramp up, run, ramp down, using time + const speed = this.speedCmdValues[0]; + const step1 = this.speedCmdValues[1]; + const step2 = this.speedCmdValues[2]; + const step3 = this.speedCmdValues[3]; + const brake = this.speedCmdValues[4]; + const dstep = (this.speedCmd == DAL.opOutputTimePower || this.speedCmd == DAL.opOutputTimeSpeed) + ? pxsim.U.now() - this.speedCmdTime + : this.tacho - this.speedCmdTacho; + if (dstep < step1) // rampup + this.speed = speed * dstep / step1; + else if (dstep < step1 + step2) // run + this.speed = speed; + else if (dstep < step1 + step2 + step3) + this.speed = speed * (step1 + step2 + step3 - dstep) / (step1 + step2 + step3); + else { + if (brake) this.speed = 0; + this.clearSpeedCmd(); + } break; - - const speed = this.speedCmdValues[0]; - const turnRatio = this.speedCmdValues[1]; - const stepsOrTime = this.speedCmdValues[2]; - const brake = this.speedCmdValues[3]; - const dstep = this.speedCmd == DAL.opOutputTimeSync - ? pxsim.U.now() - this.speedCmdTime - : this.tacho - this.speedCmdTacho; - // 0 is special case, run infinite - if (!stepsOrTime || dstep < stepsOrTime) - this.speed = speed; - else { - if (brake) this.speed = 0; - this.clearSpeedCmd(); } + case DAL.opOutputStepSync: + case DAL.opOutputTimeSync: { + const otherMotor = this._synchedMotor; + if (otherMotor.port < this.port) // handled in other motor code + break; - // turn ratio is a bit weird to interpret - // see https://communities.theiet.org/blogs/698/1706 - if (turnRatio < 0) { - otherMotor.speed = speed; - this.speed *= (100 + turnRatio) / 100; - } else { - otherMotor.speed = this.speed * (100 - turnRatio) / 100; + const speed = this.speedCmdValues[0]; + const turnRatio = this.speedCmdValues[1]; + const stepsOrTime = this.speedCmdValues[2]; + const brake = this.speedCmdValues[3]; + const dstep = this.speedCmd == DAL.opOutputTimeSync + ? pxsim.U.now() - this.speedCmdTime + : this.tacho - this.speedCmdTacho; + // 0 is special case, run infinite + if (!stepsOrTime || dstep < stepsOrTime) + this.speed = speed; + else { + if (brake) this.speed = 0; + this.clearSpeedCmd(); + } + + // turn ratio is a bit weird to interpret + // see https://communities.theiet.org/blogs/698/1706 + if (turnRatio < 0) { + otherMotor.speed = speed; + this.speed *= (100 + turnRatio) / 100; + } else { + otherMotor.speed = this.speed * (100 - turnRatio) / 100; + } + + // clamp + this.speed = Math.max(-100, Math.min(100, this.speed >> 0)); + otherMotor.speed = Math.max(-100, Math.min(100, otherMotor.speed >> 0));; + + // stop other motor if needed + if (!this._synchedMotor) + otherMotor.clearSpeedCmd(); + break; } - - // clamp - this.speed = Math.max(-100, Math.min(100, this.speed >> 0)); - otherMotor.speed = Math.max(-100, Math.min(100, otherMotor.speed >> 0));; - - // stop other motor if needed - if (!this._synchedMotor) - otherMotor.clearSpeedCmd(); - break; } } + else { + this.speed = this.manualSpeed; + } this.speed = Math.round(this.speed); // integer only // compute delta angle