Simulator refactoring to support better resizing of modules and controls
This commit is contained in:
		@@ -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();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user