Simulator refactoring to support better resizing of modules and controls

This commit is contained in:
Sam El-Husseini 2017-12-22 14:00:23 -08:00
parent 300a2c1476
commit 180f32f25c
23 changed files with 408 additions and 301 deletions

View File

@ -23,20 +23,6 @@ namespace pxsim {
} }
export class EV3Board extends CoreBoard { 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; view: SVGSVGElement;
outputState: EV3OutputState; outputState: EV3OutputState;
@ -86,11 +72,6 @@ namespace pxsim {
// TODO // TODO
break; 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.innerHTML = ""; // clear children
document.body.appendChild(this.view = viewHost.getView() as SVGSVGElement); document.body.appendChild(this.view = viewHost.getView() as SVGSVGElement);
this.inputNodes = [];
this.outputNodes = [];
return Promise.resolve(); return Promise.resolve();
} }
@ -127,14 +111,6 @@ namespace pxsim {
return svg.toDataUri(new XMLSerializer().serializeToString(this.view)); return svg.toDataUri(new XMLSerializer().serializeToString(this.view));
} }
//defaultNeopixelPin() {
// return this.edgeConnectorState.getPin(CPlayPinName.D8);
//}
getDefaultPitchPin() {
return this.edgeConnectorState.getPin(CPlayPinName.D6);
}
getBrickNode() { getBrickNode() {
return this.brickNode; return this.brickNode;
} }

View File

@ -20,7 +20,7 @@ namespace pxsim {
export class ColorSensorNode extends UartSensorNode { export class ColorSensorNode extends UartSensorNode {
id = NodeType.ColorSensor; id = NodeType.ColorSensor;
private color: number; private color: number = 50;
constructor(port: number) { constructor(port: number) {
super(port); super(port);
@ -31,10 +31,8 @@ namespace pxsim {
} }
setColor(color: number) { setColor(color: number) {
if (this.color != color) { this.color = color;
this.color = color; this.setChangedState();
this.setChangedState();
}
} }
getValue() { getValue() {

View File

@ -48,6 +48,7 @@ namespace pxsim {
stop() { stop() {
// TODO: implement // TODO: implement
this.setSpeed(0);
} }
start() { start() {

View File

@ -88,7 +88,7 @@ namespace pxsim {
const inputNodes = ev3board().getInputNodes(); const inputNodes = ev3board().getInputNodes();
for (let port = 0; port < DAL.NUM_INPUTS; port++) { for (let port = 0; port < DAL.NUM_INPUTS; port++) {
const node = inputNodes[port]; const node = inputNodes[port];
if (node) { if (node && node.isUart()) {
// Actual // Actual
const index = 0; //UartOff.Actual + port * 2; 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())) util.map16Bit(data, UartOff.Raw + DAL.MAX_DEVICE_DATALENGTH * 300 * port + DAL.MAX_DEVICE_DATALENGTH * index, Math.floor(node.getValue()))

View File

@ -99,16 +99,9 @@ namespace pxsim.visuals {
private layoutView: LayoutView; private layoutView: LayoutView;
private controlGroup: ViewContainer;
private selectedNode: NodeType;
private selectedPort: number;
private controlView: View;
private cachedControlNodes: { [index: string]: View[] } = {}; private cachedControlNodes: { [index: string]: View[] } = {};
private cachedDisplayViews: { [index: string]: LayoutElement[] } = {}; private cachedDisplayViews: { [index: string]: LayoutElement[] } = {};
private closeGroup: ViewContainer;
private closeIconView: View;
private screenCanvas: HTMLCanvasElement; private screenCanvas: HTMLCanvasElement;
private screenCanvasCtx: CanvasRenderingContext2D; private screenCanvasCtx: CanvasRenderingContext2D;
private screenCanvasData: ImageData; private screenCanvasData: ImageData;
@ -143,7 +136,12 @@ namespace pxsim.visuals {
Runtime.messagePosted = (msg) => { Runtime.messagePosted = (msg) => {
switch (msg.type || "") { 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); 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) { private getControlForNode(id: NodeType, port: number) {
if (this.cachedControlNodes[id] && this.cachedControlNodes[id][port]) { if (this.cachedControlNodes[id] && this.cachedControlNodes[id][port]) {
return this.cachedControlNodes[id][port]; return this.cachedControlNodes[id][port];
@ -285,6 +253,10 @@ namespace pxsim.visuals {
return undefined; return undefined;
} }
private getCloseIconView() {
return new CloseIconControl(this.element, this.defs, new PortNode(-1), -1);
}
private buildDom() { private buildDom() {
this.wrapper = document.createElement('div'); this.wrapper = document.createElement('div');
this.wrapper.style.display = 'inline'; this.wrapper.style.display = 'inline';
@ -299,25 +271,10 @@ namespace pxsim.visuals {
this.layoutView = new LayoutView(); this.layoutView = new LayoutView();
this.layoutView.inject(this.element); 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 // Add EV3 module element
this.layoutView.setBrick(new BrickView(-1)); 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.resize();
//this.updateState();
// Add Screen canvas to board // Add Screen canvas to board
this.buildScreenCanvas(); this.buildScreenCanvas();
@ -329,6 +286,22 @@ namespace pxsim.visuals {
window.addEventListener("resize", e => { window.addEventListener("resize", e => {
this.resize(); 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() { private buildScreenCanvas() {
@ -357,12 +330,27 @@ namespace pxsim.visuals {
} }
private kill() { 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() { 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; const fps = GAME_LOOP_FPS;
let now; let now;
let then = Date.now(); let then = Date.now();
@ -370,7 +358,8 @@ namespace pxsim.visuals {
let delta; let delta;
let that = this; let that = this;
function loop() { function loop() {
that.lastAnimationId = requestAnimationFrame(loop); const animationId = requestAnimationFrame(loop);
that.lastAnimationIds.push(animationId);
now = Date.now(); now = Date.now();
delta = now - then; delta = now - then;
if (delta > interval) { if (delta > interval) {
@ -382,67 +371,46 @@ namespace pxsim.visuals {
} }
private updateStateStep(elapsed: number) { private updateStateStep(elapsed: number) {
const selected = this.layoutView.getSelected();
let selectedChanged = false;
const inputNodes = ev3board().getInputNodes(); const inputNodes = ev3board().getInputNodes();
inputNodes.forEach((node, index) => { inputNodes.forEach((node, index) => {
node.updateState(elapsed); node.updateState(elapsed);
if (!node.didChange()) return;
const view = this.getDisplayViewForNode(node.id, index); const view = this.getDisplayViewForNode(node.id, index);
if (!node.didChange() && !view.didChange()) return;
if (view) { 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(); view.updateState();
if (selected == view) selectedChanged = true; if (control) control.updateState();
} }
}); });
const brickNode = ev3board().getBrickNode(); const brickNode = ev3board().getBrickNode();
if (brickNode.didChange()) { if (brickNode.didChange()) {
this.getDisplayViewForNode(ev3board().getBrickNode().id, -1).updateState(); this.getDisplayViewForNode(brickNode.id, -1).updateState();
} }
const outputNodes = ev3board().getMotors(); const outputNodes = ev3board().getMotors();
outputNodes.forEach((node, index) => { outputNodes.forEach((node, index) => {
node.updateState(elapsed); node.updateState(elapsed);
if (!node.didChange()) return;
const view = this.getDisplayViewForNode(node.id, index); const view = this.getDisplayViewForNode(node.id, index);
if (!node.didChange() && !view.didChange()) return;
if (view) { 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(); view.updateState();
if (selected == view) selectedChanged = true; if (control) control.updateState();
} }
}); });
if (selected && (selected.getId() !== this.selectedNode || selected.getPort() !== this.selectedPort)) { let state = ev3board().screenState;
this.selectedNode = selected.getId(); if (!state.didChange()) {
this.selectedPort = selected.getPort(); this.updateScreenStep(state);
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);
} }
if (selectedChanged && selected) {
this.controlView.updateState();
}
this.updateScreenStep();
} }
private updateScreenStep() { private updateScreenStep(state: EV3ScreenState) {
let state = ev3board().screenState;
if (!state.didChange()) return;
const bBox = this.layoutView.getBrick().getScreenBBox(); const bBox = this.layoutView.getBrick().getScreenBBox();
if (!bBox || bBox.width == 0) return; if (!bBox || bBox.width == 0) return;

View File

@ -1,4 +1,4 @@
/// <reference path="./nodes/staticView.ts" /> /// <reference path="./nodes/moduleView.ts" />
namespace pxsim.visuals { namespace pxsim.visuals {
@ -6,7 +6,7 @@ namespace pxsim.visuals {
export const CONTROL_HEIGHT = 175; export const CONTROL_HEIGHT = 175;
export abstract class ControlView<T extends BaseNode> extends SimView<T> implements LayoutElement { 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; abstract getInnerView(parent: SVGSVGElement, globalDefs: SVGDefsElement): SVGElement;
@ -34,18 +34,30 @@ namespace pxsim.visuals {
return false; return false;
} }
buildDom(width: number): SVGElement { buildDom(): SVGElement {
this.background = svg.elt("svg", { height: "100%", width: "100%"}) as SVGSVGElement; this.content = svg.elt("svg", { viewBox: `0 0 ${this.getInnerWidth()} ${this.getInnerHeight()}`}) as SVGSVGElement;
this.background.appendChild(this.getInnerView(this.parent, this.globalDefs)); this.content.appendChild(this.getInnerView(this.parent, this.globalDefs));
return this.background; 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() { onComponentVisible() {
} }
getWeight() {
return 0;
}
} }
} }

View File

@ -17,6 +17,17 @@ namespace pxsim.visuals {
return this.closeGroup; 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() { public getInnerHeight() {
return 32; return 32;
} }

View File

@ -7,7 +7,7 @@ namespace pxsim.visuals {
getInnerView() { getInnerView() {
this.group = svg.elt("g") as SVGGElement; 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 colorIds = ['red', 'yellow', 'blue', 'green', 'black', 'grey'];
const colors = ['#f12a21', '#ffd01b', '#006db3', '#00934b', '#000', '#6c2d00']; const colors = ['#f12a21', '#ffd01b', '#006db3', '#00934b', '#000', '#6c2d00'];

View File

@ -10,7 +10,7 @@ namespace pxsim.visuals {
getInnerView(parent: SVGSVGElement) { getInnerView(parent: SVGSVGElement) {
this.defs = <SVGDefsElement>svg.child(this.element, "defs", {}); this.defs = <SVGDefsElement>svg.child(this.element, "defs", {});
this.group = svg.elt("g") as SVGGElement; 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"; let gc = "gradient-color";
this.colorGradient = svg.linearGradient(this.defs, gc, true); this.colorGradient = svg.linearGradient(this.defs, gc, true);

View File

@ -100,7 +100,8 @@ namespace pxsim.visuals {
private updateSliderValue(pt: SVGPoint, parent: SVGSVGElement, ev: MouseEvent) { private updateSliderValue(pt: SVGPoint, parent: SVGSVGElement, ev: MouseEvent) {
let cur = svg.cursorPoint(pt, parent, ev); let cur = svg.cursorPoint(pt, parent, ev);
const height = this.getContentHeight(); //DistanceSliderControl.SLIDER_HEIGHT; 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; const state = this.state;
state.setDistance((1 - t) * (this.getMax())); state.setDistance((1 - t) * (this.getMax()));

View File

@ -83,7 +83,8 @@ namespace pxsim.visuals {
private updateSliderValue(pt: SVGPoint, parent: SVGSVGElement, ev: MouseEvent) { private updateSliderValue(pt: SVGPoint, parent: SVGSVGElement, ev: MouseEvent) {
let cur = svg.cursorPoint(pt, parent, ev); let cur = svg.cursorPoint(pt, parent, ev);
const width = CONTROL_WIDTH; //DistanceSliderControl.SLIDER_HEIGHT; 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; const state = this.state;
state.setAngle((1 - t) * (100)); state.setAngle((1 - t) * (100));

View File

@ -1,5 +1,5 @@
/// <reference path="./view.ts" /> /// <reference path="./view.ts" />
/// <reference path="./nodes/staticView.ts" /> /// <reference path="./nodes/moduleView.ts" />
/// <reference path="./nodes/portView.ts" /> /// <reference path="./nodes/portView.ts" />
namespace pxsim.visuals { namespace pxsim.visuals {
@ -9,59 +9,51 @@ namespace pxsim.visuals {
export const BRICK_HEIGHT_RATIO = 1 / 3; export const BRICK_HEIGHT_RATIO = 1 / 3;
export const MODULE_AND_WIRING_HEIGHT_RATIO = 1 / 3; // For inputs and outputs 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 MODULE_HEIGHT_RATIO = MODULE_AND_WIRING_HEIGHT_RATIO * 4 / 5;
export const WIRING_HEIGHT_RATIO = MODULE_AND_WIRING_HEIGHT_RATIO / 4; export const WIRING_HEIGHT_RATIO = MODULE_AND_WIRING_HEIGHT_RATIO / 5;
export const MODULE_INNER_PADDING_RATIO = 1 / 35; export const MODULE_INNER_PADDING_RATIO = 1 / 35;
export const MAX_MODULE_WIDTH = 100;
export interface LayoutElement extends View { export interface LayoutElement extends View {
getId(): number; getId(): number;
getPort(): number; getPort(): number;
getPaddingRatio(): number; getPaddingRatio(): number;
getWiringRatio(): number; getWiringRatio(): number;
setSelected(selected: boolean): void;
} }
export class LayoutView extends ViewContainer { export class LayoutView extends ViewContainer {
private inputs: LayoutElement[] = []; private inputs: LayoutElement[] = [];
private outputs: 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 inputWires: WireView[] = [];
private outputWires: WireView[] = []; private outputWires: WireView[] = [];
private selected: number;
private selectedIsInput: boolean;
private brick: BrickView; private brick: BrickView;
private offsets: number[]; private offsets: number[];
private contentGroup: SVGGElement; private contentGroup: SVGGElement;
private scrollGroup: SVGGElement; private scrollGroup: SVGGElement;
private renderedViews: Map<boolean> = {}; private renderedViews: Map<boolean> = {};
private childScaleFactor: number;
private totalLength: number;
private height: number;
private hasDimensions = false; private hasDimensions = false;
constructor() { constructor() {
super(); super();
this.outputs = [ this.outputContainers = [new ViewContainer(), new ViewContainer, new ViewContainer(), new ViewContainer()];
new PortView(0, 'A'), this.inputContainers = [new ViewContainer(), new ViewContainer, new ViewContainer(), new ViewContainer()];
new PortView(1, 'B'),
new PortView(2, 'C'),
new PortView(3, 'D')
];
this.brick = new BrickView(0); 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++) { for (let port = 0; port < DAL.NUM_OUTPUTS; port++) {
this.outputWires[port] = new WireView(port); this.outputWires[port] = new WireView(port);
} }
@ -72,8 +64,7 @@ namespace pxsim.visuals {
public layout(width: number, height: number) { public layout(width: number, height: number) {
this.hasDimensions = true; this.hasDimensions = true;
this.width = width; this.resize(width, height);
this.height = height;
this.scrollGroup.setAttribute("width", width.toString()); this.scrollGroup.setAttribute("width", width.toString());
this.scrollGroup.setAttribute("height", height.toString()); this.scrollGroup.setAttribute("height", height.toString());
this.position(); this.position();
@ -81,6 +72,7 @@ namespace pxsim.visuals {
public setBrick(brick: BrickView) { public setBrick(brick: BrickView) {
this.brick = brick; this.brick = brick;
this.brick.inject(this.scrollGroup);
this.position(); this.position();
} }
@ -88,55 +80,112 @@ namespace pxsim.visuals {
return this.brick; return this.brick;
} }
public setInput(port: number, child: LayoutElement) { public setInput(port: number, view: LayoutElement, control?: View, closeIcon?: View) {
if (this.inputs[port]) { if (this.inputs[port] != view || this.inputControls[port] != control) {
// Remove current input if (this.inputs[port]) {
this.inputs[port].dispose(); // 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(); this.position();
} }
public setOutput(port: number, child: LayoutElement) { public setOutput(port: number, view: LayoutElement, control?: View, closeIcon?: View) {
if (this.outputs[port]) { if (this.outputs[port] != view || this.outputControls[port] != control) {
// Remove current input if (this.outputs[port]) {
this.outputs[port].dispose(); // 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(); this.position();
} }
public onClick(index: number, input: boolean, ev: any) { protected buildDom() {
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) {
this.contentGroup = svg.elt("g") as SVGGElement; this.contentGroup = svg.elt("g") as SVGGElement;
this.scrollGroup = svg.child(this.contentGroup, "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; return this.contentGroup;
} }
@ -171,34 +220,47 @@ namespace pxsim.visuals {
this.offsets = []; this.offsets = [];
const selectedNode = this.getSelected();
const contentWidth = this.width || DEFAULT_WIDTH; const contentWidth = this.width || DEFAULT_WIDTH;
const contentHeight = this.height || DEFAULT_HEIGHT; const contentHeight = this.height || DEFAULT_HEIGHT;
const moduleHeight = this.getModuleHeight(); const moduleHeight = this.getModuleHeight();
const brickHeight = this.getBrickHeight(); const brickHeight = this.getBrickHeight();
this.brick.inject(this.scrollGroup);
const brickWidth = this.brick.getInnerWidth() / this.brick.getInnerHeight() * brickHeight; const brickWidth = this.brick.getInnerWidth() / this.brick.getInnerHeight() * brickHeight;
const brickPadding = (contentWidth - brickWidth) / 2; const brickPadding = (contentWidth - brickWidth) / 2;
const modulePadding = contentWidth / 35; const modulePadding = this.getModulePadding();
const moduleSpacing = contentWidth / 4; const moduleSpacing = contentWidth / 4;
const moduleWidth = moduleSpacing - (modulePadding * 2); const moduleWidth = this.getInnerModuleWidth();
let currentX = modulePadding; let currentX = this.getModulePadding();
let currentY = 0; let currentY = 0;
this.outputs.forEach((n, i) => { this.outputs.forEach((n, i) => {
const outputPadding = moduleWidth * n.getPaddingRatio(); this.outputContainers[i].translate(currentX, currentY);
const outputWidth = moduleWidth - outputPadding * 2; if (this.outputs[i]) {
n.inject(this.scrollGroup, outputWidth); const view = this.outputs[i];
n.resize(outputWidth); const outputPadding = this.getInnerModuleWidth() * view.getPaddingRatio();
const nHeight = n.getHeight() / n.getWidth() * outputWidth; const desiredOutputWidth = this.getInnerModuleWidth() - outputPadding * 2;
n.translate(currentX + outputPadding, currentY + moduleHeight - nHeight); const outputWidth = Math.min(desiredOutputWidth, MAX_MODULE_WIDTH);
n.setSelected(n == selectedNode); const outputHeight = this.getModuleHeight();
if (n.hasClick()) n.registerClick((ev: any) => {
this.onClick(i, false, ev); // 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; currentX += moduleSpacing;
}) })
@ -206,7 +268,7 @@ namespace pxsim.visuals {
currentY = moduleHeight; currentY = moduleHeight;
const wireBrickSpacing = brickWidth / 5; const wireBrickSpacing = brickWidth / 5;
const wiringYPadding = 10; const wiringYPadding = 0;
let wireStartX = 0; let wireStartX = 0;
let wireEndX = brickPadding + wireBrickSpacing; let wireEndX = brickPadding + wireBrickSpacing;
let wireEndY = currentY + this.getWiringHeight() + wiringYPadding; let wireEndY = currentY + this.getWiringHeight() + wiringYPadding;
@ -214,7 +276,6 @@ namespace pxsim.visuals {
// Draw output lines // Draw output lines
for (let port = 0; port < DAL.NUM_OUTPUTS; port++) { 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].updateDimensions(wireStartX + moduleSpacing * this.outputs[port].getWiringRatio(), wireStartY, wireEndX, wireEndY);
this.outputWires[port].setSelected(this.outputs[port].getId() == NodeType.Port); this.outputWires[port].setSelected(this.outputs[port].getId() == NodeType.Port);
wireStartX += moduleSpacing; wireStartX += moduleSpacing;
@ -225,22 +286,39 @@ namespace pxsim.visuals {
currentY += this.getWiringHeight(); currentY += this.getWiringHeight();
// Render the brick in the middle // Render the brick in the middle
this.brick.resize(brickWidth); this.brick.resize(brickWidth, brickHeight);
this.brick.translate(currentX, currentY); this.brick.translate(currentX, currentY);
currentX = modulePadding; currentX = modulePadding;
currentY += brickHeight + this.getWiringHeight(); currentY += brickHeight + this.getWiringHeight();
this.inputs.forEach((n, i) => { this.inputs.forEach((n, i) => {
const inputPadding = moduleWidth * n.getPaddingRatio(); this.inputContainers[i].translate(currentX, currentY);
const inputWidth = moduleWidth - inputPadding * 2; if (this.inputs[i]) {
n.inject(this.scrollGroup, inputWidth); const view = this.inputs[i];
n.resize(inputWidth); const inputPadding = this.getInnerModuleWidth() * view.getPaddingRatio();
n.translate(currentX + inputPadding, currentY); const desiredInputWidth = this.getInnerModuleWidth() - inputPadding * 2;
n.setSelected(n == selectedNode); const inputWidth = Math.min(desiredInputWidth, MAX_MODULE_WIDTH);
if (n.hasClick()) n.registerClick((ev: any) => { const inputHeight = this.getModuleHeight();
this.onClick(i, true, ev);
}) // 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; currentX += moduleSpacing;
}) })
@ -251,7 +329,6 @@ namespace pxsim.visuals {
// Draw input lines // Draw input lines
for (let port = 0; port < DAL.NUM_INPUTS; port++) { 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].updateDimensions(wireStartX, wireStartY, wireEndX, wireEndY);
this.inputWires[port].setSelected(this.inputs[port].getId() == NodeType.Port); this.inputWires[port].setSelected(this.inputs[port].getId() == NodeType.Port);
wireStartX += moduleSpacing; 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() { public getBrickHeight() {
return (this.height || DEFAULT_HEIGHT) * BRICK_HEIGHT_RATIO; return (this.height || DEFAULT_HEIGHT) * BRICK_HEIGHT_RATIO;
} }
@ -290,9 +346,21 @@ namespace pxsim.visuals {
public getModuleBounds() { public getModuleBounds() {
return { return {
width: this.width / 4, width: (this.width || DEFAULT_WIDTH) / 4,
height: this.getModuleHeight() 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;
}
} }
} }

View File

@ -1,8 +1,8 @@
/// <reference path="./staticView.ts" /> /// <reference path="./moduleView.ts" />
namespace pxsim.visuals { 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_SCREEN_ID = "ev3_screen";
private static EV3_LIGHT_ID = "btn_color"; private static EV3_LIGHT_ID = "btn_color";
@ -41,9 +41,9 @@ namespace pxsim.visuals {
public updateThemeCore() { public updateThemeCore() {
let theme = this.theme; let theme = this.theme;
svg.fill(this.buttons[0], theme.buttonUps[0]); // svg.fill(this.buttons[0], theme.buttonUps[0]);
svg.fill(this.buttons[1], theme.buttonUps[1]); // svg.fill(this.buttons[1], theme.buttonUps[1]);
svg.fill(this.buttons[2], theme.buttonUps[2]); // svg.fill(this.buttons[2], theme.buttonUps[2]);
} }
private lastLightPattern: number = -1; private lastLightPattern: number = -1;

View File

@ -1,7 +1,7 @@
/// <reference path="./staticView.ts" /> /// <reference path="./moduleView.ts" />
namespace pxsim.visuals { namespace pxsim.visuals {
export class ColorSensorView extends StaticModuleView implements LayoutElement { export class ColorSensorView extends ModuleView implements LayoutElement {
private control: ColorGridControl; private control: ColorGridControl;
@ -10,7 +10,7 @@ namespace pxsim.visuals {
} }
public getPaddingRatio() { public getPaddingRatio() {
return 1 / 8; return 1 / 6;
} }
public updateState() { public updateState() {

View File

@ -1,7 +1,7 @@
/// <reference path="./staticView.ts" /> /// <reference path="./moduleView.ts" />
namespace pxsim.visuals { namespace pxsim.visuals {
export class GyroSensorView extends StaticModuleView implements LayoutElement { export class GyroSensorView extends ModuleView implements LayoutElement {
constructor(port: number) { constructor(port: number) {
super(GYRO_SVG, "gyro", NodeType.GyroSensor, port); super(GYRO_SVG, "gyro", NodeType.GyroSensor, port);

View File

@ -1,7 +1,7 @@
/// <reference path="./staticView.ts" /> /// <reference path="./moduleView.ts" />
namespace pxsim.visuals { 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"; private static ROTATING_ECLIPSE_ID = "1eb2ae58-2419-47d4-86bf-4f26a7f0cf61";

View File

@ -1,10 +1,10 @@
/// <reference path="./staticView.ts" /> /// <reference path="./moduleView.ts" />
namespace pxsim.visuals { namespace pxsim.visuals {
export const MOTOR_ROTATION_FPS = 32; 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"; private static ROTATING_ECLIPSE_ID = "Hole";
@ -16,7 +16,7 @@ namespace pxsim.visuals {
} }
public getPaddingRatio() { public getPaddingRatio() {
return 1 / 10; return 1 / 5;
} }
updateState() { updateState() {

View File

@ -1,6 +1,6 @@
namespace pxsim.visuals { namespace pxsim.visuals {
export class StaticModuleView extends View implements LayoutElement { export class ModuleView extends View implements LayoutElement {
protected content: SVGSVGElement; protected content: SVGSVGElement;
protected controlShown: boolean; protected controlShown: boolean;
@ -45,9 +45,8 @@ namespace pxsim.visuals {
return 0.5; return 0.5;
} }
protected buildDom(width: number): SVGElement { protected buildDom(): SVGElement {
this.content = svg.parseString(this.xml); this.content = svg.parseString(this.xml);
this.updateDimensions(width);
this.buildDomCore(); this.buildDomCore();
this.attachEvents(); this.attachEvents();
if (this.hasClick()) if (this.hasClick())
@ -82,15 +81,17 @@ namespace pxsim.visuals {
public attachEvents() { public attachEvents() {
} }
public resize(width: number) { public resize(width: number, height: number) {
this.updateDimensions(width); super.resize(width, height);
this.updateDimensions(width, height);
} }
private updateDimensions(width: number) { private updateDimensions(width: number, height: number) {
if (this.content) { if (this.content) {
const currentWidth = this.getInnerWidth(); const currentWidth = this.getInnerWidth();
const currentHeight = this.getInnerHeight(); const currentHeight = this.getInnerHeight();
const newHeight = currentHeight / currentWidth * width; const newHeight = currentHeight / currentWidth * width;
const newWidth = currentWidth / currentHeight * height;
this.content.setAttribute('width', `${width}`); this.content.setAttribute('width', `${width}`);
this.content.setAttribute('height', `${newHeight}`); this.content.setAttribute('height', `${newHeight}`);
} }
@ -101,13 +102,13 @@ namespace pxsim.visuals {
} }
public setSelected(selected: boolean) { public setSelected(selected: boolean) {
this.selected = selected; super.setSelected(selected);
this.updateOpacity(); this.updateOpacity();
} }
protected updateOpacity() { protected updateOpacity() {
if (this.rendered) { if (this.rendered) {
const opacity = this.selected ? "0.5" : "1"; const opacity = this.selected ? "0.2" : "1";
if (this.hasClick()) { if (this.hasClick()) {
this.setOpacity(opacity); this.setOpacity(opacity);
if (this.selected) this.content.style.cursor = ""; if (this.selected) this.content.style.cursor = "";

View File

@ -1,8 +1,8 @@
/// <reference path="./staticView.ts" /> /// <reference path="./moduleView.ts" />
namespace pxsim.visuals { namespace pxsim.visuals {
export class PortView extends StaticModuleView implements LayoutElement { export class PortView extends ModuleView implements LayoutElement {
constructor(port: NodeType, private label: string) { constructor(port: NodeType, private label: string) {
super(PORT_SVG, "port", NodeType.Port, port); super(PORT_SVG, "port", NodeType.Port, port);

View File

@ -1,7 +1,7 @@
/// <reference path="./staticView.ts" /> /// <reference path="./moduleView.ts" />
namespace pxsim.visuals { 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 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"]; private static TOUCH_GRADIENT_UNPRESSED = ["linear-gradient-2", "linear-gradient-3", "linear-gradient-4", "linear-gradient-5"];

View File

@ -1,7 +1,7 @@
/// <reference path="./staticView.ts" /> /// <reference path="./moduleView.ts" />
namespace pxsim.visuals { namespace pxsim.visuals {
export class UltrasonicSensorView extends StaticModuleView implements LayoutElement { export class UltrasonicSensorView extends ModuleView implements LayoutElement {
constructor(port: number) { constructor(port: number) {
super(ULTRASONIC_SVG, "ultrasonic", NodeType.UltrasonicSensor, port); super(ULTRASONIC_SVG, "ultrasonic", NodeType.UltrasonicSensor, port);

View File

@ -3,14 +3,19 @@ namespace pxsim.visuals {
protected element: SVGGElement; protected element: SVGGElement;
protected rendered = false; protected rendered = false;
protected visible = false; protected visible = false;
protected selected: boolean;
protected width: number = 0; protected width: number = 0;
protected height: number = 0;
protected left: number = 0; protected left: number = 0;
protected top: number = 0; protected top: number = 0;
protected scaleFactor: number = 1; protected scaleFactor: number = 1;
private changed: boolean;
private hover: boolean = false;
protected theme: IBoardTheme; protected theme: IBoardTheme;
protected abstract buildDom(width: number): SVGElement; protected abstract buildDom(): SVGElement;
public abstract getInnerWidth(): number; public abstract getInnerWidth(): number;
public abstract getInnerHeight(): number; public abstract getInnerHeight(): number;
@ -83,9 +88,26 @@ namespace pxsim.visuals {
} }
private onClickHandler: (ev: any) => void; private onClickHandler: (ev: any) => void;
public registerClick(handler: (ev: any) => void) { public registerClick(handler: (ev: any) => void, zoom?: boolean) {
this.onClickHandler = handler; 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() { public dispose() {
@ -98,7 +120,7 @@ namespace pxsim.visuals {
this.element = svg.elt("g") as SVGGElement; this.element = svg.elt("g") as SVGGElement;
View.track(this); View.track(this);
const content = this.buildDom(this.width); const content = this.buildDom();
if (content) { if (content) {
this.element.appendChild(content); this.element.appendChild(content);
} }
@ -108,16 +130,30 @@ namespace pxsim.visuals {
return this.element; return this.element;
} }
public resize(width: number) { public resize(width: number, height: number) {
this.width = width; this.width = width;
this.height = height;
} }
private updateTransform() { private updateTransform() {
if (this.rendered) { 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) { let transform = `translate(${left} ${top})`;
transform += ` scale(${this.scaleFactor})`;
if (scaleFactor !== 1) {
transform += ` scale(${scaleFactor})`;
} }
this.element.setAttribute("transform", transform); this.element.setAttribute("transform", transform);
@ -149,6 +185,41 @@ namespace pxsim.visuals {
delete View.allViews[id]; 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 { 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; return undefined;
} }
} }

View File

@ -1,11 +1,10 @@
/// <reference path="./nodes/staticView.ts" /> /// <reference path="./nodes/moduleView.ts" />
namespace pxsim.visuals { namespace pxsim.visuals {
export class WireView extends View implements LayoutElement { export class WireView extends View implements LayoutElement {
private wire: SVGSVGElement; private wire: SVGSVGElement;
private path: SVGPathElement; private path: SVGPathElement;
private selected: boolean;
private hasDimensions: boolean; private hasDimensions: boolean;
protected startX: number; protected startX: number;
@ -30,13 +29,13 @@ namespace pxsim.visuals {
this.updatePath(); this.updatePath();
} }
buildDom(width: number): SVGElement { buildDom(): SVGElement {
this.wire = svg.elt("svg", { height: "100%", width: "100%" }) as SVGSVGElement; this.wire = svg.elt("svg", { height: "100%", width: "100%" }) as SVGSVGElement;
this.path = pxsim.svg.child(this.wire, "path", { this.path = pxsim.svg.child(this.wire, "path", {
'd': '', 'd': '',
'fill': 'transparent', 'fill': 'transparent',
'stroke': '#5A5A5A', 'stroke': '#5A5A5A',
'stroke-width': '3px' 'stroke-width': '2px'
}) as SVGPathElement; }) as SVGPathElement;
this.setSelected(true); this.setSelected(true);
return this.wire; return this.wire;
@ -45,8 +44,8 @@ namespace pxsim.visuals {
updatePath() { updatePath() {
if (!this.hasDimensions) return; if (!this.hasDimensions) return;
const height = this.endY - this.startY; const height = this.endY - this.startY;
const quarterHeight = height / 4; const thirdHeight = height / 3;
const middleHeight = this.port == 1 || this.port == 2 ? quarterHeight : quarterHeight * 2; const middleHeight = this.port == 1 || this.port == 2 ? thirdHeight : thirdHeight * 2;
let d = `M${this.startX} ${this.startY}`; let d = `M${this.startX} ${this.startY}`;
d += ` L${this.startX} ${this.startY + middleHeight}`; d += ` L${this.startX} ${this.startY + middleHeight}`;
d += ` L${this.endX} ${this.startY + middleHeight}`; d += ` L${this.endX} ${this.startY + middleHeight}`;
@ -79,7 +78,7 @@ namespace pxsim.visuals {
} }
public setSelected(selected: boolean) { public setSelected(selected: boolean) {
this.selected = selected; super.setSelected(selected);
this.updateOpacity(); this.updateOpacity();
} }