b69af383a6
* 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
166 lines
5.5 KiB
TypeScript
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;
|
|
}
|
|
}
|
|
|
|
} |