Add motor slider control
This commit is contained in:
parent
d7f46c0fb5
commit
cbe68b3199
@ -73,8 +73,17 @@ namespace pxsim {
|
|||||||
this.started = true;
|
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) {
|
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);
|
const interval = Math.min(20, elapsed);
|
||||||
let t = 0;
|
let t = 0;
|
||||||
while(t < elapsed) {
|
while(t < elapsed) {
|
||||||
|
@ -44,7 +44,7 @@ namespace pxsim.visuals {
|
|||||||
}
|
}
|
||||||
.sim-text.number {
|
.sim-text.number {
|
||||||
font-family: Courier, Lato, Work Sans, PT Serif, Source Serif Pro;
|
font-family: Courier, Lato, Work Sans, PT Serif, Source Serif Pro;
|
||||||
font-weight: bold;
|
/*font-weight: bold;*/
|
||||||
}
|
}
|
||||||
.sim-text.inverted {
|
.sim-text.inverted {
|
||||||
fill:#5A5A5A;
|
fill:#5A5A5A;
|
||||||
@ -62,6 +62,14 @@ namespace pxsim.visuals {
|
|||||||
fill: gray !important;
|
fill: gray !important;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Motor slider */
|
||||||
|
.sim-motor-btn {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.sim-motor-btn:hover {
|
||||||
|
fill: gray !important;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const EV3_WIDTH = 99.984346;
|
const EV3_WIDTH = 99.984346;
|
||||||
@ -213,9 +221,12 @@ namespace pxsim.visuals {
|
|||||||
}
|
}
|
||||||
case NodeType.MediumMotor:
|
case NodeType.MediumMotor:
|
||||||
case NodeType.LargeMotor: {
|
case NodeType.LargeMotor: {
|
||||||
// TODO: figure out if the motor is in "input" or "output" mode
|
|
||||||
const state = ev3board().getMotors()[port];
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,8 +27,8 @@ namespace pxsim.visuals {
|
|||||||
this.group = svg.elt("g") as SVGGElement;
|
this.group = svg.elt("g") as SVGGElement;
|
||||||
|
|
||||||
const reporterGroup = pxsim.svg.child(this.group, "g");
|
const reporterGroup = pxsim.svg.child(this.group, "g");
|
||||||
reporterGroup.setAttribute("transform", `translate(31, 42)`);
|
reporterGroup.setAttribute("transform", `translate(${this.getWidth() / 2}, 42)`);
|
||||||
this.reporter = pxsim.svg.child(reporterGroup, "text", { 'x': 0, 'y': '0', 'class': 'sim-text number large inverted' }) as SVGTextElement;
|
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");
|
const sliderGroup = pxsim.svg.child(this.group, "g");
|
||||||
sliderGroup.setAttribute("transform", `translate(${this.getWidth() / 2 - this.getSliderWidth() / 2}, ${this.getReporterHeight()})`)
|
sliderGroup.setAttribute("transform", `translate(${this.getWidth() / 2 - this.getSliderWidth() / 2}, ${this.getReporterHeight()})`)
|
||||||
@ -60,12 +60,15 @@ namespace pxsim.visuals {
|
|||||||
}, ev => {
|
}, ev => {
|
||||||
captured = true;
|
captured = true;
|
||||||
if ((ev as MouseEvent).clientY != undefined) {
|
if ((ev as MouseEvent).clientY != undefined) {
|
||||||
|
dragSurface.setAttribute('cursor', '-webkit-grabbing');
|
||||||
this.updateSliderValue(pt, parent, ev as MouseEvent);
|
this.updateSliderValue(pt, parent, ev as MouseEvent);
|
||||||
}
|
}
|
||||||
}, () => {
|
}, () => {
|
||||||
captured = false;
|
captured = false;
|
||||||
|
dragSurface.setAttribute('cursor', '-webkit-grab');
|
||||||
}, () => {
|
}, () => {
|
||||||
captured = false;
|
captured = false;
|
||||||
|
dragSurface.setAttribute('cursor', '-webkit-grab');
|
||||||
})
|
})
|
||||||
|
|
||||||
return this.group;
|
return this.group;
|
||||||
|
199
sim/visuals/controls/motorSlider.ts
Normal file
199
sim/visuals/controls/motorSlider.ts
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
|
||||||
|
|
||||||
|
namespace pxsim.visuals {
|
||||||
|
|
||||||
|
export class MotorSliderControl extends ControlView<MotorNode> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user