pxt-ev3/sim/visuals/controls/motorSlider.ts
Sam El-Husseini b69af383a6
Better mouse event handling (#552)
* Use learnings from touch events in Blockly and apply to the Ev3 sim. Register move and up events on the document rather than the individual element allowing users greater flexibility where they move their mouse once it's down.

* Add mouse leave events
2018-05-02 13:54:06 -07:00

166 lines
5.5 KiB
TypeScript

namespace pxsim.visuals {
export class MotorSliderControl extends ControlView<MotorNode> {
private group: SVGGElement;
private gradient: SVGLinearGradientElement;
private slider: SVGGElement;
private reporter: SVGTextElement;
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 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.reporter = pxsim.svg.child(this.group, "text", {
'x': this.getInnerWidth() / 2, 'y': this.getInnerHeight() / 2,
'text-anchor': 'middle', 'dominant-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' });
this.updateDial();
let pt = parent.createSVGPoint();
let captured = false;
const dragSurface = svg.child(this.group, "rect", {
x: 0,
y: 0,
width: this.getInnerWidth(),
height: this.getInnerHeight(),
opacity: 0,
cursor: '-webkit-grab'
})
touchEvents(dragSurface, ev => {
if (captured && (ev as MouseEvent).clientY != undefined) {
ev.preventDefault();
this.updateSliderValue(pt, parent, ev as MouseEvent);
this.handleSliderMove();
}
}, ev => {
captured = true;
if ((ev as MouseEvent).clientY != undefined) {
this.updateSliderValue(pt, parent, ev as MouseEvent);
this.handleSliderDown();
}
}, () => {
captured = false;
this.handleSliderUp();
})
return this.group;
}
getInnerWidth() {
return 250;
}
getInnerHeight() {
return 250;
}
private lastPosition: number;
private prevVal: number;
private updateSliderValue(pt: SVGPoint, parent: SVGSVGElement, ev: MouseEvent) {
let cur = svg.cursorPoint(pt, parent, ev);
let bBox = this.content.getBoundingClientRect();
const coords = {
x: cur.x / this.scaleFactor - bBox.left / this.scaleFactor,
y: cur.y / this.scaleFactor - bBox.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.updateDial();
this.prevVal = deg;
this.lastPosition = cur.x;
}
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 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})`);
}
updateState() {
if (!this.visible) {
return;
}
const node = this.state;
const speed = node.getSpeed();
// Update reporter
this.reporter.textContent = `${speed}`;
}
private getMin() {
return 0;
}
private getMax() {
return 100;
}
}
}