Merge pull request #125 from Microsoft/sim_refactor_resize
Refactor sim for better resizing and support for multiple controls to be open
This commit is contained in:
commit
88df2e14cb
@ -23,20 +23,6 @@ namespace pxsim {
|
||||
}
|
||||
|
||||
export class EV3Board extends CoreBoard {
|
||||
// state & update logic for component services
|
||||
// neopixelState: CommonNeoPixelState;
|
||||
buttonState: EV3ButtonState;
|
||||
slideSwitchState: SlideSwitchState;
|
||||
lightSensorState: AnalogSensorState;
|
||||
thermometerState: AnalogSensorState;
|
||||
thermometerUnitState: number;
|
||||
microphoneState: AnalogSensorState;
|
||||
edgeConnectorState: EdgeConnectorState;
|
||||
capacitiveSensorState: CapacitiveSensorState;
|
||||
accelerometerState: AccelerometerState;
|
||||
touchButtonState: TouchButtonState;
|
||||
irState: InfraredState;
|
||||
|
||||
view: SVGSVGElement;
|
||||
|
||||
outputState: EV3OutputState;
|
||||
@ -86,11 +72,6 @@ namespace pxsim {
|
||||
// TODO
|
||||
break;
|
||||
}
|
||||
case "irpacket": {
|
||||
let ev = <SimulatorInfraredPacketMessage>msg;
|
||||
this.irState.receive(new RefBuffer(ev.packet));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,6 +101,9 @@ namespace pxsim {
|
||||
document.body.innerHTML = ""; // clear children
|
||||
document.body.appendChild(this.view = viewHost.getView() as SVGSVGElement);
|
||||
|
||||
this.inputNodes = [];
|
||||
this.outputNodes = [];
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
@ -127,14 +111,6 @@ namespace pxsim {
|
||||
return svg.toDataUri(new XMLSerializer().serializeToString(this.view));
|
||||
}
|
||||
|
||||
//defaultNeopixelPin() {
|
||||
// return this.edgeConnectorState.getPin(CPlayPinName.D8);
|
||||
//}
|
||||
|
||||
getDefaultPitchPin() {
|
||||
return this.edgeConnectorState.getPin(CPlayPinName.D6);
|
||||
}
|
||||
|
||||
getBrickNode() {
|
||||
return this.brickNode;
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ namespace pxsim {
|
||||
export class ColorSensorNode extends UartSensorNode {
|
||||
id = NodeType.ColorSensor;
|
||||
|
||||
private color: number;
|
||||
private color: number = 50;
|
||||
|
||||
constructor(port: number) {
|
||||
super(port);
|
||||
@ -31,10 +31,8 @@ namespace pxsim {
|
||||
}
|
||||
|
||||
setColor(color: number) {
|
||||
if (this.color != color) {
|
||||
this.color = color;
|
||||
this.setChangedState();
|
||||
}
|
||||
this.color = color;
|
||||
this.setChangedState();
|
||||
}
|
||||
|
||||
getValue() {
|
||||
|
@ -48,6 +48,7 @@ namespace pxsim {
|
||||
|
||||
stop() {
|
||||
// TODO: implement
|
||||
this.setSpeed(0);
|
||||
}
|
||||
|
||||
start() {
|
||||
|
@ -88,7 +88,7 @@ namespace pxsim {
|
||||
const inputNodes = ev3board().getInputNodes();
|
||||
for (let port = 0; port < DAL.NUM_INPUTS; port++) {
|
||||
const node = inputNodes[port];
|
||||
if (node) {
|
||||
if (node && node.isUart()) {
|
||||
// Actual
|
||||
const index = 0; //UartOff.Actual + port * 2;
|
||||
util.map16Bit(data, UartOff.Raw + DAL.MAX_DEVICE_DATALENGTH * 300 * port + DAL.MAX_DEVICE_DATALENGTH * index, Math.floor(node.getValue()))
|
||||
|
@ -99,16 +99,9 @@ namespace pxsim.visuals {
|
||||
|
||||
private layoutView: LayoutView;
|
||||
|
||||
private controlGroup: ViewContainer;
|
||||
private selectedNode: NodeType;
|
||||
private selectedPort: number;
|
||||
private controlView: View;
|
||||
private cachedControlNodes: { [index: string]: View[] } = {};
|
||||
private cachedDisplayViews: { [index: string]: LayoutElement[] } = {};
|
||||
|
||||
private closeGroup: ViewContainer;
|
||||
private closeIconView: View;
|
||||
|
||||
private screenCanvas: HTMLCanvasElement;
|
||||
private screenCanvasCtx: CanvasRenderingContext2D;
|
||||
private screenCanvasData: ImageData;
|
||||
@ -143,7 +136,12 @@ namespace pxsim.visuals {
|
||||
|
||||
Runtime.messagePosted = (msg) => {
|
||||
switch (msg.type || "") {
|
||||
case "status": if ((msg as pxsim.SimulatorStateMessage).state == "killed") this.kill(); break;
|
||||
case "status": {
|
||||
const state = (msg as pxsim.SimulatorStateMessage).state;
|
||||
if (state == "killed") this.kill();
|
||||
if (state == "running") this.begin();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -177,36 +175,6 @@ namespace pxsim.visuals {
|
||||
this.layoutView.updateTheme(theme);
|
||||
}
|
||||
|
||||
public resize() {
|
||||
const bounds = this.element.getBoundingClientRect();
|
||||
this.width = bounds.width;
|
||||
this.height = bounds.height;
|
||||
this.layoutView.layout(bounds.width, bounds.height);
|
||||
|
||||
if (this.selectedNode) {
|
||||
const scale = this.width / this.closeIconView.getInnerWidth() / 10;
|
||||
// Translate close icon
|
||||
this.closeIconView.scale(Math.max(0, Math.min(1, scale)));
|
||||
const closeIconWidth = this.closeIconView.getWidth();
|
||||
const closeIconHeight = this.closeIconView.getHeight();
|
||||
const closeCoords = this.layoutView.getCloseIconCoords(closeIconWidth, closeIconHeight);
|
||||
this.closeIconView.translate(closeCoords.x, closeCoords.y);
|
||||
}
|
||||
|
||||
if (this.controlView) {
|
||||
const h = this.controlView.getInnerHeight();
|
||||
const w = this.controlView.getInnerWidth();
|
||||
const bh = this.layoutView.getModuleBounds().height - this.closeIconView.getHeight();
|
||||
const bw = this.layoutView.getModuleBounds().width - (this.width * MODULE_INNER_PADDING_RATIO * 2);
|
||||
this.controlView.scale(Math.min(bh / h, bw / w), false);
|
||||
|
||||
const controlCoords = this.layoutView.getSelectedCoords();
|
||||
this.controlView.translate(controlCoords.x, controlCoords.y);
|
||||
}
|
||||
|
||||
//this.updateScreen();
|
||||
}
|
||||
|
||||
private getControlForNode(id: NodeType, port: number) {
|
||||
if (this.cachedControlNodes[id] && this.cachedControlNodes[id][port]) {
|
||||
return this.cachedControlNodes[id][port];
|
||||
@ -285,6 +253,10 @@ namespace pxsim.visuals {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private getCloseIconView() {
|
||||
return new CloseIconControl(this.element, this.defs, new PortNode(-1), -1);
|
||||
}
|
||||
|
||||
private buildDom() {
|
||||
this.wrapper = document.createElement('div');
|
||||
this.wrapper.style.display = 'inline';
|
||||
@ -299,25 +271,10 @@ namespace pxsim.visuals {
|
||||
this.layoutView = new LayoutView();
|
||||
this.layoutView.inject(this.element);
|
||||
|
||||
this.controlGroup = new ViewContainer();
|
||||
this.controlGroup.inject(this.element);
|
||||
|
||||
this.closeGroup = new ViewContainer();
|
||||
this.closeGroup.inject(this.element);
|
||||
|
||||
// Add EV3 module element
|
||||
this.layoutView.setBrick(new BrickView(-1));
|
||||
|
||||
this.closeIconView = new CloseIconControl(this.element, this.defs, new PortNode(-1), -1);
|
||||
this.closeIconView.registerClick(() => {
|
||||
this.layoutView.clearSelected();
|
||||
this.updateState();
|
||||
})
|
||||
this.closeGroup.addView(this.closeIconView);
|
||||
this.closeIconView.setVisible(false);
|
||||
|
||||
this.resize();
|
||||
//this.updateState();
|
||||
|
||||
// Add Screen canvas to board
|
||||
this.buildScreenCanvas();
|
||||
@ -329,6 +286,22 @@ namespace pxsim.visuals {
|
||||
window.addEventListener("resize", e => {
|
||||
this.resize();
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
this.resize();
|
||||
}, 200);
|
||||
}
|
||||
|
||||
public resize() {
|
||||
if (!this.element) return;
|
||||
const bounds = this.element.getBoundingClientRect();
|
||||
this.width = bounds.width;
|
||||
this.height = bounds.height;
|
||||
this.layoutView.layout(bounds.width, bounds.height);
|
||||
|
||||
this.updateState();
|
||||
let state = ev3board().screenState;
|
||||
this.updateScreenStep(state);
|
||||
}
|
||||
|
||||
private buildScreenCanvas() {
|
||||
@ -357,12 +330,27 @@ namespace pxsim.visuals {
|
||||
}
|
||||
|
||||
private kill() {
|
||||
if (this.lastAnimationId) cancelAnimationFrame(this.lastAnimationId);
|
||||
this.running = false;
|
||||
if (this.lastAnimationIds.length > 0) {
|
||||
this.lastAnimationIds.forEach(animationId => {
|
||||
cancelAnimationFrame(animationId);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private lastAnimationId: number;
|
||||
private begin() {
|
||||
this.running = true;
|
||||
}
|
||||
|
||||
private running: boolean = false;
|
||||
private lastAnimationIds: number[] = [];
|
||||
public updateState() {
|
||||
if (this.lastAnimationId) cancelAnimationFrame(this.lastAnimationId);
|
||||
if (this.lastAnimationIds.length > 0) {
|
||||
this.lastAnimationIds.forEach(animationId => {
|
||||
cancelAnimationFrame(animationId);
|
||||
})
|
||||
}
|
||||
if (!this.running) return;
|
||||
const fps = GAME_LOOP_FPS;
|
||||
let now;
|
||||
let then = Date.now();
|
||||
@ -370,7 +358,8 @@ namespace pxsim.visuals {
|
||||
let delta;
|
||||
let that = this;
|
||||
function loop() {
|
||||
that.lastAnimationId = requestAnimationFrame(loop);
|
||||
const animationId = requestAnimationFrame(loop);
|
||||
that.lastAnimationIds.push(animationId);
|
||||
now = Date.now();
|
||||
delta = now - then;
|
||||
if (delta > interval) {
|
||||
@ -382,67 +371,46 @@ namespace pxsim.visuals {
|
||||
}
|
||||
|
||||
private updateStateStep(elapsed: number) {
|
||||
const selected = this.layoutView.getSelected();
|
||||
let selectedChanged = false;
|
||||
const inputNodes = ev3board().getInputNodes();
|
||||
inputNodes.forEach((node, index) => {
|
||||
node.updateState(elapsed);
|
||||
if (!node.didChange()) return;
|
||||
const view = this.getDisplayViewForNode(node.id, index);
|
||||
if (!node.didChange() && !view.didChange()) return;
|
||||
if (view) {
|
||||
this.layoutView.setInput(index, view);
|
||||
const control = view.getSelected() ? this.getControlForNode(node.id, index) : undefined;
|
||||
const closeIcon = control ? this.getCloseIconView() : undefined;
|
||||
this.layoutView.setInput(index, view, control, closeIcon);
|
||||
view.updateState();
|
||||
if (selected == view) selectedChanged = true;
|
||||
if (control) control.updateState();
|
||||
}
|
||||
});
|
||||
|
||||
const brickNode = ev3board().getBrickNode();
|
||||
if (brickNode.didChange()) {
|
||||
this.getDisplayViewForNode(ev3board().getBrickNode().id, -1).updateState();
|
||||
this.getDisplayViewForNode(brickNode.id, -1).updateState();
|
||||
}
|
||||
|
||||
const outputNodes = ev3board().getMotors();
|
||||
outputNodes.forEach((node, index) => {
|
||||
node.updateState(elapsed);
|
||||
if (!node.didChange()) return;
|
||||
const view = this.getDisplayViewForNode(node.id, index);
|
||||
if (!node.didChange() && !view.didChange()) return;
|
||||
if (view) {
|
||||
this.layoutView.setOutput(index, view);
|
||||
const control = view.getSelected() ? this.getControlForNode(node.id, index) : undefined;
|
||||
const closeIcon = control ? this.getCloseIconView() : undefined;
|
||||
this.layoutView.setOutput(index, view, control, closeIcon);
|
||||
view.updateState();
|
||||
if (selected == view) selectedChanged = true;
|
||||
if (control) control.updateState();
|
||||
}
|
||||
});
|
||||
|
||||
if (selected && (selected.getId() !== this.selectedNode || selected.getPort() !== this.selectedPort)) {
|
||||
this.selectedNode = selected.getId();
|
||||
this.selectedPort = selected.getPort();
|
||||
this.controlGroup.clear();
|
||||
const control = this.getControlForNode(this.selectedNode, selected.getPort());
|
||||
if (control) {
|
||||
this.controlView = control;
|
||||
this.controlGroup.addView(control);
|
||||
}
|
||||
this.closeIconView.setVisible(true);
|
||||
this.resize();
|
||||
} else if (!selected) {
|
||||
this.controlGroup.clear();
|
||||
this.controlView = undefined;
|
||||
this.selectedNode = undefined;
|
||||
this.selectedPort = undefined;
|
||||
this.closeIconView.setVisible(false);
|
||||
let state = ev3board().screenState;
|
||||
if (!state.didChange()) {
|
||||
this.updateScreenStep(state);
|
||||
}
|
||||
|
||||
if (selectedChanged && selected) {
|
||||
this.controlView.updateState();
|
||||
}
|
||||
|
||||
this.updateScreenStep();
|
||||
}
|
||||
|
||||
private updateScreenStep() {
|
||||
let state = ev3board().screenState;
|
||||
if (!state.didChange()) return;
|
||||
|
||||
private updateScreenStep(state: EV3ScreenState) {
|
||||
const bBox = this.layoutView.getBrick().getScreenBBox();
|
||||
if (!bBox || bBox.width == 0) return;
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
/// <reference path="./nodes/staticView.ts" />
|
||||
/// <reference path="./nodes/moduleView.ts" />
|
||||
|
||||
namespace pxsim.visuals {
|
||||
|
||||
@ -6,7 +6,7 @@ namespace pxsim.visuals {
|
||||
export const CONTROL_HEIGHT = 175;
|
||||
|
||||
export abstract class ControlView<T extends BaseNode> extends SimView<T> implements LayoutElement {
|
||||
private background: SVGSVGElement;
|
||||
protected content: SVGSVGElement;
|
||||
|
||||
abstract getInnerView(parent: SVGSVGElement, globalDefs: SVGDefsElement): SVGElement;
|
||||
|
||||
@ -34,18 +34,30 @@ namespace pxsim.visuals {
|
||||
return false;
|
||||
}
|
||||
|
||||
buildDom(width: number): SVGElement {
|
||||
this.background = svg.elt("svg", { height: "100%", width: "100%"}) as SVGSVGElement;
|
||||
this.background.appendChild(this.getInnerView(this.parent, this.globalDefs));
|
||||
return this.background;
|
||||
buildDom(): SVGElement {
|
||||
this.content = svg.elt("svg", { viewBox: `0 0 ${this.getInnerWidth()} ${this.getInnerHeight()}`}) as SVGSVGElement;
|
||||
this.content.appendChild(this.getInnerView(this.parent, this.globalDefs));
|
||||
return this.content;
|
||||
}
|
||||
|
||||
public resize(width: number, height: number) {
|
||||
super.resize(width, height);
|
||||
this.updateDimensions(width, height);
|
||||
}
|
||||
|
||||
private updateDimensions(width: number, height: number) {
|
||||
if (this.content) {
|
||||
const currentWidth = this.getInnerWidth();
|
||||
const currentHeight = this.getInnerHeight();
|
||||
const newHeight = currentHeight / currentWidth * width;
|
||||
const newWidth = currentWidth / currentHeight * height;
|
||||
this.content.setAttribute('width', `${width}`);
|
||||
this.content.setAttribute('height', `${newHeight}`);
|
||||
}
|
||||
}
|
||||
|
||||
onComponentVisible() {
|
||||
|
||||
}
|
||||
|
||||
getWeight() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -17,6 +17,17 @@ namespace pxsim.visuals {
|
||||
return this.closeGroup;
|
||||
}
|
||||
|
||||
|
||||
buildDom(): SVGElement {
|
||||
this.content = svg.elt("svg", { width: "100%", height: "100%"}) as SVGSVGElement;
|
||||
this.content.appendChild(this.getInnerView());
|
||||
return this.content;
|
||||
}
|
||||
|
||||
public resize(width: number, height: number) {
|
||||
super.resize(width, height);
|
||||
}
|
||||
|
||||
public getInnerHeight() {
|
||||
return 32;
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ namespace pxsim.visuals {
|
||||
|
||||
getInnerView() {
|
||||
this.group = svg.elt("g") as SVGGElement;
|
||||
this.group.setAttribute("transform", `translate(17, ${35 + this.getHeight() / 4}) scale(5)`)
|
||||
this.group.setAttribute("transform", `translate(17, ${20 + this.getHeight() / 4}) scale(5)`)
|
||||
|
||||
const colorIds = ['red', 'yellow', 'blue', 'green', 'black', 'grey'];
|
||||
const colors = ['#f12a21', '#ffd01b', '#006db3', '#00934b', '#000', '#6c2d00'];
|
||||
|
@ -10,7 +10,7 @@ namespace pxsim.visuals {
|
||||
getInnerView(parent: SVGSVGElement) {
|
||||
this.defs = <SVGDefsElement>svg.child(this.element, "defs", {});
|
||||
this.group = svg.elt("g") as SVGGElement;
|
||||
this.group.setAttribute("transform", `translate(12, ${this.getHeight() / 2 - 15}) scale(2.5)`)
|
||||
this.group.setAttribute("transform", `translate(12, ${this.getHeight() / 2 - 15}) scale(2)`)
|
||||
|
||||
let gc = "gradient-color";
|
||||
this.colorGradient = svg.linearGradient(this.defs, gc, true);
|
||||
|
@ -100,7 +100,8 @@ namespace pxsim.visuals {
|
||||
private updateSliderValue(pt: SVGPoint, parent: SVGSVGElement, ev: MouseEvent) {
|
||||
let cur = svg.cursorPoint(pt, parent, ev);
|
||||
const height = this.getContentHeight(); //DistanceSliderControl.SLIDER_HEIGHT;
|
||||
let t = Math.max(0, Math.min(1, (this.getTopPadding() + height + this.top / this.scaleFactor - cur.y / this.scaleFactor) / height))
|
||||
const bBox = this.content.getBoundingClientRect();
|
||||
let t = Math.max(0, Math.min(1, (this.getTopPadding() + height + bBox.top / this.scaleFactor - cur.y / this.scaleFactor) / height))
|
||||
|
||||
const state = this.state;
|
||||
state.setDistance((1 - t) * (this.getMax()));
|
||||
|
@ -83,7 +83,8 @@ namespace pxsim.visuals {
|
||||
private updateSliderValue(pt: SVGPoint, parent: SVGSVGElement, ev: MouseEvent) {
|
||||
let cur = svg.cursorPoint(pt, parent, ev);
|
||||
const width = CONTROL_WIDTH; //DistanceSliderControl.SLIDER_HEIGHT;
|
||||
let t = Math.max(0, Math.min(1, (width + this.left / this.scaleFactor - cur.x / this.scaleFactor) / width))
|
||||
const bBox = this.content.getBoundingClientRect();
|
||||
let t = Math.max(0, Math.min(1, (width + bBox.left / this.scaleFactor - cur.x / this.scaleFactor) / width))
|
||||
|
||||
const state = this.state;
|
||||
state.setAngle((1 - t) * (100));
|
||||
|
@ -1,5 +1,5 @@
|
||||
/// <reference path="./view.ts" />
|
||||
/// <reference path="./nodes/staticView.ts" />
|
||||
/// <reference path="./nodes/moduleView.ts" />
|
||||
/// <reference path="./nodes/portView.ts" />
|
||||
|
||||
namespace pxsim.visuals {
|
||||
@ -9,59 +9,51 @@ namespace pxsim.visuals {
|
||||
export const BRICK_HEIGHT_RATIO = 1 / 3;
|
||||
export const MODULE_AND_WIRING_HEIGHT_RATIO = 1 / 3; // For inputs and outputs
|
||||
|
||||
export const MODULE_HEIGHT_RATIO = MODULE_AND_WIRING_HEIGHT_RATIO * 3 / 4;
|
||||
export const WIRING_HEIGHT_RATIO = MODULE_AND_WIRING_HEIGHT_RATIO / 4;
|
||||
export const MODULE_HEIGHT_RATIO = MODULE_AND_WIRING_HEIGHT_RATIO * 4 / 5;
|
||||
export const WIRING_HEIGHT_RATIO = MODULE_AND_WIRING_HEIGHT_RATIO / 5;
|
||||
|
||||
export const MODULE_INNER_PADDING_RATIO = 1 / 35;
|
||||
|
||||
export const MAX_MODULE_WIDTH = 100;
|
||||
|
||||
export interface LayoutElement extends View {
|
||||
getId(): number;
|
||||
getPort(): number;
|
||||
getPaddingRatio(): number;
|
||||
getWiringRatio(): number;
|
||||
setSelected(selected: boolean): void;
|
||||
}
|
||||
|
||||
export class LayoutView extends ViewContainer {
|
||||
private inputs: LayoutElement[] = [];
|
||||
private outputs: LayoutElement[] = [];
|
||||
|
||||
private inputContainers: ViewContainer[] = [];
|
||||
private outputContainers: ViewContainer[] = [];
|
||||
|
||||
private inputControls: View[] = [];
|
||||
private outputControls: View[] = [];
|
||||
|
||||
private inputCloseIcons: View[] = [];
|
||||
private outputCloseIcons: View[] = [];
|
||||
|
||||
private inputWires: WireView[] = [];
|
||||
private outputWires: WireView[] = [];
|
||||
|
||||
private selected: number;
|
||||
private selectedIsInput: boolean;
|
||||
private brick: BrickView;
|
||||
private offsets: number[];
|
||||
private contentGroup: SVGGElement;
|
||||
private scrollGroup: SVGGElement;
|
||||
private renderedViews: Map<boolean> = {};
|
||||
|
||||
private childScaleFactor: number;
|
||||
|
||||
private totalLength: number;
|
||||
private height: number;
|
||||
private hasDimensions = false;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.outputs = [
|
||||
new PortView(0, 'A'),
|
||||
new PortView(1, 'B'),
|
||||
new PortView(2, 'C'),
|
||||
new PortView(3, 'D')
|
||||
];
|
||||
this.outputContainers = [new ViewContainer(), new ViewContainer, new ViewContainer(), new ViewContainer()];
|
||||
this.inputContainers = [new ViewContainer(), new ViewContainer, new ViewContainer(), new ViewContainer()];
|
||||
|
||||
this.brick = new BrickView(0);
|
||||
|
||||
this.inputs = [
|
||||
new PortView(0, '1'),
|
||||
new PortView(1, '2'),
|
||||
new PortView(2, '3'),
|
||||
new PortView(3, '4')
|
||||
];
|
||||
|
||||
for (let port = 0; port < DAL.NUM_OUTPUTS; port++) {
|
||||
this.outputWires[port] = new WireView(port);
|
||||
}
|
||||
@ -72,8 +64,7 @@ namespace pxsim.visuals {
|
||||
|
||||
public layout(width: number, height: number) {
|
||||
this.hasDimensions = true;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.resize(width, height);
|
||||
this.scrollGroup.setAttribute("width", width.toString());
|
||||
this.scrollGroup.setAttribute("height", height.toString());
|
||||
this.position();
|
||||
@ -81,6 +72,7 @@ namespace pxsim.visuals {
|
||||
|
||||
public setBrick(brick: BrickView) {
|
||||
this.brick = brick;
|
||||
this.brick.inject(this.scrollGroup);
|
||||
this.position();
|
||||
}
|
||||
|
||||
@ -88,55 +80,112 @@ namespace pxsim.visuals {
|
||||
return this.brick;
|
||||
}
|
||||
|
||||
public setInput(port: number, child: LayoutElement) {
|
||||
if (this.inputs[port]) {
|
||||
// Remove current input
|
||||
this.inputs[port].dispose();
|
||||
public setInput(port: number, view: LayoutElement, control?: View, closeIcon?: View) {
|
||||
if (this.inputs[port] != view || this.inputControls[port] != control) {
|
||||
if (this.inputs[port]) {
|
||||
// Remove current input
|
||||
this.inputs[port].dispose();
|
||||
}
|
||||
this.inputs[port] = view;
|
||||
if (this.inputControls[port]) {
|
||||
this.inputControls[port].dispose();
|
||||
}
|
||||
this.inputControls[port] = control;
|
||||
this.inputCloseIcons[port] = closeIcon;
|
||||
|
||||
this.inputContainers[port].clear();
|
||||
this.inputContainers[port].addView(view);
|
||||
|
||||
if (control) this.inputContainers[port].addView(control);
|
||||
|
||||
if (view.hasClick()) view.registerClick((ev: any) => {
|
||||
view.setSelected(true);
|
||||
runtime.queueDisplayUpdate();
|
||||
}, true);
|
||||
|
||||
if (control && closeIcon) {
|
||||
this.inputContainers[port].addView(closeIcon);
|
||||
closeIcon.registerClick(() => {
|
||||
// Clear selection
|
||||
view.setSelected(false);
|
||||
runtime.queueDisplayUpdate();
|
||||
})
|
||||
}
|
||||
}
|
||||
this.inputs[port] = child;
|
||||
|
||||
this.position();
|
||||
}
|
||||
|
||||
public setOutput(port: number, child: LayoutElement) {
|
||||
if (this.outputs[port]) {
|
||||
// Remove current input
|
||||
this.outputs[port].dispose();
|
||||
public setOutput(port: number, view: LayoutElement, control?: View, closeIcon?: View) {
|
||||
if (this.outputs[port] != view || this.outputControls[port] != control) {
|
||||
if (this.outputs[port]) {
|
||||
// Remove current output
|
||||
this.outputs[port].dispose();
|
||||
}
|
||||
this.outputs[port] = view;
|
||||
if (this.outputControls[port]) {
|
||||
this.outputControls[port].dispose();
|
||||
}
|
||||
this.outputControls[port] = control;
|
||||
this.outputCloseIcons[port] = closeIcon;
|
||||
|
||||
this.outputContainers[port].clear();
|
||||
this.outputContainers[port].addView(view);
|
||||
|
||||
if (control) this.outputContainers[port].addView(control);
|
||||
|
||||
if (view.hasClick()) view.registerClick((ev: any) => {
|
||||
view.setSelected(true);
|
||||
runtime.queueDisplayUpdate();
|
||||
}, true)
|
||||
|
||||
if (control && closeIcon) {
|
||||
this.outputContainers[port].addView(closeIcon);
|
||||
closeIcon.registerClick(() => {
|
||||
// Clear selection
|
||||
view.setSelected(false);
|
||||
runtime.queueDisplayUpdate();
|
||||
})
|
||||
}
|
||||
}
|
||||
this.outputs[port] = child;
|
||||
|
||||
this.position();
|
||||
}
|
||||
|
||||
public onClick(index: number, input: boolean, ev: any) {
|
||||
this.setSelected(index, input);
|
||||
}
|
||||
|
||||
public clearSelected() {
|
||||
this.selected = undefined;
|
||||
this.selectedIsInput = undefined;
|
||||
}
|
||||
|
||||
public setSelected(index: number, input?: boolean) {
|
||||
if (index !== this.selected || input !== this.selectedIsInput) {
|
||||
this.selected = index;
|
||||
this.selectedIsInput = input;
|
||||
const node = this.getSelected();
|
||||
if (node) node.setSelected(true);
|
||||
|
||||
//this.redoPositioning();
|
||||
runtime.queueDisplayUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public getSelected() {
|
||||
if (this.selected !== undefined) {
|
||||
return this.selectedIsInput ? this.inputs[this.selected] : this.outputs[this.selected];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
protected buildDom(width: number) {
|
||||
protected buildDom() {
|
||||
this.contentGroup = svg.elt("g") as SVGGElement;
|
||||
this.scrollGroup = svg.child(this.contentGroup, "g") as SVGGElement;
|
||||
|
||||
// Inject all view containers
|
||||
for (let i = 0; i < 4; i++) {
|
||||
this.inputContainers[i].inject(this.scrollGroup);
|
||||
this.outputContainers[i].inject(this.scrollGroup);
|
||||
}
|
||||
|
||||
this.inputs = [];
|
||||
this.outputs = [];
|
||||
this.inputControls = [];
|
||||
this.outputControls = [];
|
||||
|
||||
// Inject all wires
|
||||
for (let port = 0; port < DAL.NUM_OUTPUTS; port++) {
|
||||
this.outputWires[port].inject(this.scrollGroup);
|
||||
}
|
||||
for (let port = 0; port < DAL.NUM_INPUTS; port++) {
|
||||
this.inputWires[port].inject(this.scrollGroup);
|
||||
}
|
||||
|
||||
// Inject all ports
|
||||
this.setInput(0, new PortView(0, 'A'));
|
||||
this.setInput(1, new PortView(1, 'B'));
|
||||
this.setInput(2, new PortView(2, 'C'));
|
||||
this.setInput(3, new PortView(3, 'D'));
|
||||
|
||||
this.setOutput(0, new PortView(0, '1'));
|
||||
this.setOutput(1, new PortView(1, '2'));
|
||||
this.setOutput(2, new PortView(2, '3'));
|
||||
this.setOutput(3, new PortView(3, '4'));
|
||||
|
||||
return this.contentGroup;
|
||||
}
|
||||
|
||||
@ -171,34 +220,47 @@ namespace pxsim.visuals {
|
||||
|
||||
this.offsets = [];
|
||||
|
||||
const selectedNode = this.getSelected();
|
||||
|
||||
const contentWidth = this.width || DEFAULT_WIDTH;
|
||||
const contentHeight = this.height || DEFAULT_HEIGHT;
|
||||
|
||||
const moduleHeight = this.getModuleHeight();
|
||||
|
||||
const brickHeight = this.getBrickHeight();
|
||||
this.brick.inject(this.scrollGroup);
|
||||
const brickWidth = this.brick.getInnerWidth() / this.brick.getInnerHeight() * brickHeight;
|
||||
const brickPadding = (contentWidth - brickWidth) / 2;
|
||||
|
||||
const modulePadding = contentWidth / 35;
|
||||
const modulePadding = this.getModulePadding();
|
||||
const moduleSpacing = contentWidth / 4;
|
||||
const moduleWidth = moduleSpacing - (modulePadding * 2);
|
||||
let currentX = modulePadding;
|
||||
const moduleWidth = this.getInnerModuleWidth();
|
||||
let currentX = this.getModulePadding();
|
||||
let currentY = 0;
|
||||
this.outputs.forEach((n, i) => {
|
||||
const outputPadding = moduleWidth * n.getPaddingRatio();
|
||||
const outputWidth = moduleWidth - outputPadding * 2;
|
||||
n.inject(this.scrollGroup, outputWidth);
|
||||
n.resize(outputWidth);
|
||||
const nHeight = n.getHeight() / n.getWidth() * outputWidth;
|
||||
n.translate(currentX + outputPadding, currentY + moduleHeight - nHeight);
|
||||
n.setSelected(n == selectedNode);
|
||||
if (n.hasClick()) n.registerClick((ev: any) => {
|
||||
this.onClick(i, false, ev);
|
||||
})
|
||||
this.outputContainers[i].translate(currentX, currentY);
|
||||
if (this.outputs[i]) {
|
||||
const view = this.outputs[i];
|
||||
const outputPadding = this.getInnerModuleWidth() * view.getPaddingRatio();
|
||||
const desiredOutputWidth = this.getInnerModuleWidth() - outputPadding * 2;
|
||||
const outputWidth = Math.min(desiredOutputWidth, MAX_MODULE_WIDTH);
|
||||
const outputHeight = this.getModuleHeight();
|
||||
|
||||
// Translate and resize view
|
||||
view.resize(outputWidth, outputHeight);
|
||||
const viewHeight = view.getInnerHeight() / view.getInnerWidth() * outputWidth;
|
||||
view.translate(outputPadding + ((desiredOutputWidth - outputWidth) / 2), outputHeight - viewHeight, true);
|
||||
|
||||
// Resize control
|
||||
const control = this.outputControls[i];
|
||||
if (control) {
|
||||
control.resize(this.getInnerModuleWidth(), outputHeight);
|
||||
|
||||
// Translate close icon
|
||||
const closeIcon = this.outputCloseIcons[i];
|
||||
if (closeIcon) {
|
||||
const closeIconWidth = closeIcon.getWidth();
|
||||
closeIcon.translate(this.getInnerModuleWidth() / 2 - closeIconWidth / 2, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
currentX += moduleSpacing;
|
||||
})
|
||||
|
||||
@ -206,7 +268,7 @@ namespace pxsim.visuals {
|
||||
currentY = moduleHeight;
|
||||
|
||||
const wireBrickSpacing = brickWidth / 5;
|
||||
const wiringYPadding = 10;
|
||||
const wiringYPadding = 0;
|
||||
let wireStartX = 0;
|
||||
let wireEndX = brickPadding + wireBrickSpacing;
|
||||
let wireEndY = currentY + this.getWiringHeight() + wiringYPadding;
|
||||
@ -214,7 +276,6 @@ namespace pxsim.visuals {
|
||||
|
||||
// Draw output lines
|
||||
for (let port = 0; port < DAL.NUM_OUTPUTS; port++) {
|
||||
if (!this.outputWires[port].isRendered()) this.outputWires[port].inject(this.scrollGroup);
|
||||
this.outputWires[port].updateDimensions(wireStartX + moduleSpacing * this.outputs[port].getWiringRatio(), wireStartY, wireEndX, wireEndY);
|
||||
this.outputWires[port].setSelected(this.outputs[port].getId() == NodeType.Port);
|
||||
wireStartX += moduleSpacing;
|
||||
@ -225,22 +286,39 @@ namespace pxsim.visuals {
|
||||
currentY += this.getWiringHeight();
|
||||
|
||||
// Render the brick in the middle
|
||||
this.brick.resize(brickWidth);
|
||||
this.brick.resize(brickWidth, brickHeight);
|
||||
this.brick.translate(currentX, currentY);
|
||||
|
||||
currentX = modulePadding;
|
||||
currentY += brickHeight + this.getWiringHeight();
|
||||
|
||||
this.inputs.forEach((n, i) => {
|
||||
const inputPadding = moduleWidth * n.getPaddingRatio();
|
||||
const inputWidth = moduleWidth - inputPadding * 2;
|
||||
n.inject(this.scrollGroup, inputWidth);
|
||||
n.resize(inputWidth);
|
||||
n.translate(currentX + inputPadding, currentY);
|
||||
n.setSelected(n == selectedNode);
|
||||
if (n.hasClick()) n.registerClick((ev: any) => {
|
||||
this.onClick(i, true, ev);
|
||||
})
|
||||
this.inputContainers[i].translate(currentX, currentY);
|
||||
if (this.inputs[i]) {
|
||||
const view = this.inputs[i];
|
||||
const inputPadding = this.getInnerModuleWidth() * view.getPaddingRatio();
|
||||
const desiredInputWidth = this.getInnerModuleWidth() - inputPadding * 2;
|
||||
const inputWidth = Math.min(desiredInputWidth, MAX_MODULE_WIDTH);
|
||||
const inputHeight = this.getModuleHeight();
|
||||
|
||||
// Translate and resize view
|
||||
view.resize(inputWidth, inputHeight);
|
||||
view.translate(inputPadding + ((desiredInputWidth - inputWidth) / 2), 0, true);
|
||||
|
||||
// Resize control
|
||||
const control = this.inputControls[i];
|
||||
if (control) {
|
||||
control.resize(this.getInnerModuleWidth(), inputHeight);
|
||||
|
||||
// Translate and resize close icon
|
||||
const closeIcon = this.inputCloseIcons[i];
|
||||
if (closeIcon) {
|
||||
const closeIconWidth = closeIcon.getWidth();
|
||||
const closeIconHeight = closeIcon.getHeight();
|
||||
closeIcon.translate(this.getInnerModuleWidth() / 2 - closeIconWidth / 2, this.getModuleHeight() - closeIconHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
currentX += moduleSpacing;
|
||||
})
|
||||
|
||||
@ -251,7 +329,6 @@ namespace pxsim.visuals {
|
||||
|
||||
// Draw input lines
|
||||
for (let port = 0; port < DAL.NUM_INPUTS; port++) {
|
||||
if (!this.inputWires[port].isRendered()) this.inputWires[port].inject(this.scrollGroup);
|
||||
this.inputWires[port].updateDimensions(wireStartX, wireStartY, wireEndX, wireEndY);
|
||||
this.inputWires[port].setSelected(this.inputs[port].getId() == NodeType.Port);
|
||||
wireStartX += moduleSpacing;
|
||||
@ -259,27 +336,6 @@ namespace pxsim.visuals {
|
||||
}
|
||||
}
|
||||
|
||||
public getSelectedCoords() {
|
||||
const selected = this.getSelected();
|
||||
if (!selected) return undefined;
|
||||
const port = this.getSelected().getPort();
|
||||
return {
|
||||
x: this.getSelected().getPort() * this.width / 4 + this.width * MODULE_INNER_PADDING_RATIO,
|
||||
y: this.selectedIsInput ? this.getModuleHeight() + 2 * this.getWiringHeight() + this.getBrickHeight() : this.getModuleHeight() / 4
|
||||
}
|
||||
}
|
||||
|
||||
public getCloseIconCoords(closeIconWidth: number, closeIconHeight: number) {
|
||||
return {
|
||||
x: this.getSelected().getPort() * this.width / 4 + this.getModuleBounds().width / 2 - closeIconWidth / 2,
|
||||
y: this.selectedIsInput ? this.getModuleHeight() + 2 * this.getWiringHeight() + this.getBrickHeight() + this.getModuleHeight() - closeIconHeight : 0
|
||||
}
|
||||
}
|
||||
|
||||
public getModuleHeight() {
|
||||
return (this.height || DEFAULT_HEIGHT) * MODULE_HEIGHT_RATIO;
|
||||
}
|
||||
|
||||
public getBrickHeight() {
|
||||
return (this.height || DEFAULT_HEIGHT) * BRICK_HEIGHT_RATIO;
|
||||
}
|
||||
@ -290,9 +346,21 @@ namespace pxsim.visuals {
|
||||
|
||||
public getModuleBounds() {
|
||||
return {
|
||||
width: this.width / 4,
|
||||
width: (this.width || DEFAULT_WIDTH) / 4,
|
||||
height: this.getModuleHeight()
|
||||
}
|
||||
}
|
||||
|
||||
public getModulePadding() {
|
||||
return this.getModuleBounds().width / 35;
|
||||
}
|
||||
|
||||
public getInnerModuleWidth() {
|
||||
return this.getModuleBounds().width - (this.getModulePadding() * 2);
|
||||
}
|
||||
|
||||
public getModuleHeight() {
|
||||
return (this.height || DEFAULT_HEIGHT) * MODULE_HEIGHT_RATIO;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
/// <reference path="./staticView.ts" />
|
||||
/// <reference path="./moduleView.ts" />
|
||||
|
||||
namespace pxsim.visuals {
|
||||
|
||||
export class BrickView extends StaticModuleView implements LayoutElement {
|
||||
export class BrickView extends ModuleView implements LayoutElement {
|
||||
|
||||
private static EV3_SCREEN_ID = "ev3_screen";
|
||||
private static EV3_LIGHT_ID = "btn_color";
|
||||
@ -41,9 +41,9 @@ namespace pxsim.visuals {
|
||||
|
||||
public updateThemeCore() {
|
||||
let theme = this.theme;
|
||||
svg.fill(this.buttons[0], theme.buttonUps[0]);
|
||||
svg.fill(this.buttons[1], theme.buttonUps[1]);
|
||||
svg.fill(this.buttons[2], theme.buttonUps[2]);
|
||||
// svg.fill(this.buttons[0], theme.buttonUps[0]);
|
||||
// svg.fill(this.buttons[1], theme.buttonUps[1]);
|
||||
// svg.fill(this.buttons[2], theme.buttonUps[2]);
|
||||
}
|
||||
|
||||
private lastLightPattern: number = -1;
|
||||
|
@ -1,7 +1,7 @@
|
||||
/// <reference path="./staticView.ts" />
|
||||
/// <reference path="./moduleView.ts" />
|
||||
|
||||
namespace pxsim.visuals {
|
||||
export class ColorSensorView extends StaticModuleView implements LayoutElement {
|
||||
export class ColorSensorView extends ModuleView implements LayoutElement {
|
||||
|
||||
private control: ColorGridControl;
|
||||
|
||||
@ -10,7 +10,7 @@ namespace pxsim.visuals {
|
||||
}
|
||||
|
||||
public getPaddingRatio() {
|
||||
return 1 / 8;
|
||||
return 1 / 6;
|
||||
}
|
||||
|
||||
public updateState() {
|
||||
|
@ -1,7 +1,7 @@
|
||||
/// <reference path="./staticView.ts" />
|
||||
/// <reference path="./moduleView.ts" />
|
||||
|
||||
namespace pxsim.visuals {
|
||||
export class GyroSensorView extends StaticModuleView implements LayoutElement {
|
||||
export class GyroSensorView extends ModuleView implements LayoutElement {
|
||||
|
||||
constructor(port: number) {
|
||||
super(GYRO_SVG, "gyro", NodeType.GyroSensor, port);
|
||||
|
@ -1,7 +1,7 @@
|
||||
/// <reference path="./staticView.ts" />
|
||||
/// <reference path="./moduleView.ts" />
|
||||
|
||||
namespace pxsim.visuals {
|
||||
export class LargeMotorView extends StaticModuleView implements LayoutElement {
|
||||
export class LargeMotorView extends ModuleView implements LayoutElement {
|
||||
|
||||
private static ROTATING_ECLIPSE_ID = "1eb2ae58-2419-47d4-86bf-4f26a7f0cf61";
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
/// <reference path="./staticView.ts" />
|
||||
/// <reference path="./moduleView.ts" />
|
||||
|
||||
namespace pxsim.visuals {
|
||||
|
||||
export const MOTOR_ROTATION_FPS = 32;
|
||||
|
||||
export class MediumMotorView extends StaticModuleView implements LayoutElement {
|
||||
export class MediumMotorView extends ModuleView implements LayoutElement {
|
||||
|
||||
private static ROTATING_ECLIPSE_ID = "Hole";
|
||||
|
||||
@ -16,7 +16,7 @@ namespace pxsim.visuals {
|
||||
}
|
||||
|
||||
public getPaddingRatio() {
|
||||
return 1 / 10;
|
||||
return 1 / 5;
|
||||
}
|
||||
|
||||
updateState() {
|
||||
|
@ -1,6 +1,6 @@
|
||||
namespace pxsim.visuals {
|
||||
|
||||
export class StaticModuleView extends View implements LayoutElement {
|
||||
export class ModuleView extends View implements LayoutElement {
|
||||
protected content: SVGSVGElement;
|
||||
|
||||
protected controlShown: boolean;
|
||||
@ -45,9 +45,8 @@ namespace pxsim.visuals {
|
||||
return 0.5;
|
||||
}
|
||||
|
||||
protected buildDom(width: number): SVGElement {
|
||||
protected buildDom(): SVGElement {
|
||||
this.content = svg.parseString(this.xml);
|
||||
this.updateDimensions(width);
|
||||
this.buildDomCore();
|
||||
this.attachEvents();
|
||||
if (this.hasClick())
|
||||
@ -82,15 +81,17 @@ namespace pxsim.visuals {
|
||||
public attachEvents() {
|
||||
}
|
||||
|
||||
public resize(width: number) {
|
||||
this.updateDimensions(width);
|
||||
public resize(width: number, height: number) {
|
||||
super.resize(width, height);
|
||||
this.updateDimensions(width, height);
|
||||
}
|
||||
|
||||
private updateDimensions(width: number) {
|
||||
private updateDimensions(width: number, height: number) {
|
||||
if (this.content) {
|
||||
const currentWidth = this.getInnerWidth();
|
||||
const currentHeight = this.getInnerHeight();
|
||||
const newHeight = currentHeight / currentWidth * width;
|
||||
const newWidth = currentWidth / currentHeight * height;
|
||||
this.content.setAttribute('width', `${width}`);
|
||||
this.content.setAttribute('height', `${newHeight}`);
|
||||
}
|
||||
@ -101,13 +102,13 @@ namespace pxsim.visuals {
|
||||
}
|
||||
|
||||
public setSelected(selected: boolean) {
|
||||
this.selected = selected;
|
||||
super.setSelected(selected);
|
||||
this.updateOpacity();
|
||||
}
|
||||
|
||||
protected updateOpacity() {
|
||||
if (this.rendered) {
|
||||
const opacity = this.selected ? "0.5" : "1";
|
||||
const opacity = this.selected ? "0.2" : "1";
|
||||
if (this.hasClick()) {
|
||||
this.setOpacity(opacity);
|
||||
if (this.selected) this.content.style.cursor = "";
|
@ -1,8 +1,8 @@
|
||||
/// <reference path="./staticView.ts" />
|
||||
/// <reference path="./moduleView.ts" />
|
||||
|
||||
namespace pxsim.visuals {
|
||||
|
||||
export class PortView extends StaticModuleView implements LayoutElement {
|
||||
export class PortView extends ModuleView implements LayoutElement {
|
||||
|
||||
constructor(port: NodeType, private label: string) {
|
||||
super(PORT_SVG, "port", NodeType.Port, port);
|
||||
|
@ -1,7 +1,7 @@
|
||||
/// <reference path="./staticView.ts" />
|
||||
/// <reference path="./moduleView.ts" />
|
||||
|
||||
namespace pxsim.visuals {
|
||||
export class TouchSensorView extends StaticModuleView implements LayoutElement {
|
||||
export class TouchSensorView extends ModuleView implements LayoutElement {
|
||||
|
||||
private static RECT_ID = ["touch_gradient4", "touch_gradient3", "touch_gradient2", "touch_gradient1"];
|
||||
private static TOUCH_GRADIENT_UNPRESSED = ["linear-gradient-2", "linear-gradient-3", "linear-gradient-4", "linear-gradient-5"];
|
||||
|
@ -1,7 +1,7 @@
|
||||
/// <reference path="./staticView.ts" />
|
||||
/// <reference path="./moduleView.ts" />
|
||||
|
||||
namespace pxsim.visuals {
|
||||
export class UltrasonicSensorView extends StaticModuleView implements LayoutElement {
|
||||
export class UltrasonicSensorView extends ModuleView implements LayoutElement {
|
||||
|
||||
constructor(port: number) {
|
||||
super(ULTRASONIC_SVG, "ultrasonic", NodeType.UltrasonicSensor, port);
|
||||
|
@ -3,14 +3,19 @@ namespace pxsim.visuals {
|
||||
protected element: SVGGElement;
|
||||
protected rendered = false;
|
||||
protected visible = false;
|
||||
protected selected: boolean;
|
||||
protected width: number = 0;
|
||||
protected height: number = 0;
|
||||
protected left: number = 0;
|
||||
protected top: number = 0;
|
||||
protected scaleFactor: number = 1;
|
||||
|
||||
private changed: boolean;
|
||||
private hover: boolean = false;
|
||||
|
||||
protected theme: IBoardTheme;
|
||||
|
||||
protected abstract buildDom(width: number): SVGElement;
|
||||
protected abstract buildDom(): SVGElement;
|
||||
public abstract getInnerWidth(): number;
|
||||
public abstract getInnerHeight(): number;
|
||||
|
||||
@ -83,9 +88,26 @@ namespace pxsim.visuals {
|
||||
}
|
||||
|
||||
private onClickHandler: (ev: any) => void;
|
||||
public registerClick(handler: (ev: any) => void) {
|
||||
public registerClick(handler: (ev: any) => void, zoom?: boolean) {
|
||||
this.onClickHandler = handler;
|
||||
this.getView().addEventListener(pointerEvents.up, this.onClickHandler);
|
||||
if (zoom) {
|
||||
this.getView().addEventListener(pointerEvents.up, (ev: any) => {
|
||||
if (!this.getSelected()) {
|
||||
this.onClickHandler(ev);
|
||||
this.setHover(false);
|
||||
}
|
||||
});
|
||||
this.getView().addEventListener(pointerEvents.move, () => {
|
||||
if (!this.getSelected()) {
|
||||
this.setHover(true);
|
||||
}
|
||||
});
|
||||
this.getView().addEventListener(pointerEvents.leave, () => {
|
||||
this.setHover(false);
|
||||
});
|
||||
} else {
|
||||
this.getView().addEventListener(pointerEvents.up, this.onClickHandler);
|
||||
}
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
@ -98,7 +120,7 @@ namespace pxsim.visuals {
|
||||
this.element = svg.elt("g") as SVGGElement;
|
||||
View.track(this);
|
||||
|
||||
const content = this.buildDom(this.width);
|
||||
const content = this.buildDom();
|
||||
if (content) {
|
||||
this.element.appendChild(content);
|
||||
}
|
||||
@ -108,16 +130,30 @@ namespace pxsim.visuals {
|
||||
return this.element;
|
||||
}
|
||||
|
||||
public resize(width: number) {
|
||||
public resize(width: number, height: number) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
private updateTransform() {
|
||||
if (this.rendered) {
|
||||
let transform = `translate(${this.left} ${this.top})`;
|
||||
let left = this.left;
|
||||
let top = this.top;
|
||||
let scaleFactor = this.scaleFactor;
|
||||
if (this.hover) {
|
||||
const hoverScaleFactor = scaleFactor + 0.05;
|
||||
// Scale around center of module
|
||||
const centerX = this.getWidth() / 2;
|
||||
const centerY = this.getHeight() / 2;
|
||||
left = left - centerX * (hoverScaleFactor - 1);
|
||||
top = top - centerY * (hoverScaleFactor - 1);
|
||||
scaleFactor = hoverScaleFactor;
|
||||
}
|
||||
|
||||
if (this.scaleFactor !== 1) {
|
||||
transform += ` scale(${this.scaleFactor})`;
|
||||
let transform = `translate(${left} ${top})`;
|
||||
|
||||
if (scaleFactor !== 1) {
|
||||
transform += ` scale(${scaleFactor})`;
|
||||
}
|
||||
|
||||
this.element.setAttribute("transform", transform);
|
||||
@ -149,6 +185,41 @@ namespace pxsim.visuals {
|
||||
delete View.allViews[id];
|
||||
}
|
||||
}
|
||||
|
||||
///////// HOVERED STATE /////////////
|
||||
|
||||
public getHover() {
|
||||
return this.hover;
|
||||
}
|
||||
|
||||
public setHover(hover: boolean) {
|
||||
if (this.hover != hover) {
|
||||
this.hover = hover;
|
||||
this.updateTransform();
|
||||
}
|
||||
}
|
||||
|
||||
///////// SELECTED STATE /////////////
|
||||
|
||||
public getSelected() {
|
||||
return this.selected;
|
||||
}
|
||||
|
||||
public setSelected(selected: boolean) {
|
||||
this.selected = selected;
|
||||
this.setChangedState();
|
||||
}
|
||||
|
||||
protected setChangedState() {
|
||||
this.changed = true;
|
||||
}
|
||||
|
||||
public didChange() {
|
||||
const res = this.changed;
|
||||
this.changed = false;
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export abstract class SimView<T extends BaseNode> extends View implements LayoutElement {
|
||||
@ -222,7 +293,7 @@ namespace pxsim.visuals {
|
||||
});
|
||||
}
|
||||
|
||||
protected buildDom(width: number): SVGElement {
|
||||
protected buildDom(): SVGElement {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,10 @@
|
||||
/// <reference path="./nodes/staticView.ts" />
|
||||
/// <reference path="./nodes/moduleView.ts" />
|
||||
|
||||
namespace pxsim.visuals {
|
||||
|
||||
export class WireView extends View implements LayoutElement {
|
||||
private wire: SVGSVGElement;
|
||||
private path: SVGPathElement;
|
||||
private selected: boolean;
|
||||
private hasDimensions: boolean;
|
||||
|
||||
protected startX: number;
|
||||
@ -30,13 +29,13 @@ namespace pxsim.visuals {
|
||||
this.updatePath();
|
||||
}
|
||||
|
||||
buildDom(width: number): SVGElement {
|
||||
buildDom(): SVGElement {
|
||||
this.wire = svg.elt("svg", { height: "100%", width: "100%" }) as SVGSVGElement;
|
||||
this.path = pxsim.svg.child(this.wire, "path", {
|
||||
'd': '',
|
||||
'fill': 'transparent',
|
||||
'stroke': '#5A5A5A',
|
||||
'stroke-width': '3px'
|
||||
'stroke-width': '2px'
|
||||
}) as SVGPathElement;
|
||||
this.setSelected(true);
|
||||
return this.wire;
|
||||
@ -45,8 +44,8 @@ namespace pxsim.visuals {
|
||||
updatePath() {
|
||||
if (!this.hasDimensions) return;
|
||||
const height = this.endY - this.startY;
|
||||
const quarterHeight = height / 4;
|
||||
const middleHeight = this.port == 1 || this.port == 2 ? quarterHeight : quarterHeight * 2;
|
||||
const thirdHeight = height / 3;
|
||||
const middleHeight = this.port == 1 || this.port == 2 ? thirdHeight : thirdHeight * 2;
|
||||
let d = `M${this.startX} ${this.startY}`;
|
||||
d += ` L${this.startX} ${this.startY + middleHeight}`;
|
||||
d += ` L${this.endX} ${this.startY + middleHeight}`;
|
||||
@ -79,7 +78,7 @@ namespace pxsim.visuals {
|
||||
}
|
||||
|
||||
public setSelected(selected: boolean) {
|
||||
this.selected = selected;
|
||||
super.setSelected(selected);
|
||||
this.updateOpacity();
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user