├── .gitignore ├── .project ├── blank.html ├── simcir-basicset.css ├── README.txt ├── LICENSE ├── get_and_set.html ├── misc ├── simcir-altfulladder.js ├── simcir.d.ts ├── simcir-delay.js ├── simcir-transmitter.js ├── simcir-num.js └── simcir-dso.js ├── simcir.css ├── sample.html ├── simcir-library.js ├── simcir-basicset.js └── simcir.js /.gitignore: -------------------------------------------------------------------------------- 1 | /.settings 2 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | simcirjs 4 | 5 | 6 | 7 | 8 | 9 | 10 | org.eclipse.wst.jsdt.core.jsNature 11 | 12 | 13 | -------------------------------------------------------------------------------- /blank.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /simcir-basicset.css: -------------------------------------------------------------------------------- 1 | /* 2 | * SimcirJS - basicset 3 | * 4 | * Copyright (c) 2014 Kazuhiko Arase 5 | * 6 | * URL: http://www.d-project.com/ 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/mit-license.php 10 | */ 11 | 12 | .simcir-basicset-dc { 13 | fill: #ffcccc; 14 | } 15 | 16 | .simcir-basicset-switch { 17 | fill: #ccccff; 18 | } 19 | 20 | .simcir-basicset-switch-button-pressed { 21 | fill: #9999cc; 22 | } 23 | 24 | .simcir-basicset-symbol { 25 | stroke: #000000; 26 | stroke-width: 1; 27 | stroke-linecap: round; 28 | stroke-linejoin: round; 29 | fill: none; 30 | } 31 | 32 | .simcir-basicset-osc { 33 | fill: #ffcccc; 34 | } 35 | 36 | .simcir-basicset-knob { 37 | stroke: none; 38 | fill: #333333; 39 | } 40 | 41 | .simcir-basicset-knob-mark { 42 | stroke: #ffffff; 43 | stroke-width: 3; 44 | } 45 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | SimcirJS 2 | 3 | Copyright (c) 2014 Kazuhiko Arase 4 | 5 | URL: http://www.d-project.com/ 6 | 7 | Licensed under the MIT license: 8 | http://www.opensource.org/licenses/mit-license.php 9 | 10 | 11 | -- System Requirements 12 | 13 | Web browser that supports HTML5 14 | 15 | -- Contents 16 | 17 | README.txt -- this file 18 | simcir.js -- Simcir core javascript (required) 19 | simcir.css -- Simcir core stylesheet (required) 20 | simcir-basicset.js -- Simcir basicset javascript (optional) 21 | simcir-basicset.css -- Simcir basicset stylesheet (optional) 22 | simcir-library.js -- Simcir library javascript 23 | (optional, requires basicset) 24 | sample.html -- sample of live circuit 25 | blank.html -- blank template 26 | get_and_set.html -- sample that get and set a circuit directly 27 | with jQuery 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2014 Kazuhiko Arase 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /get_and_set.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | 12 | 13 | 14 | 41 | 42 | 43 | 44 |
45 |
46 | 47 | 48 |
49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /misc/simcir-altfulladder.js: -------------------------------------------------------------------------------- 1 | // 2 | // SimcirJS - altfulladder 3 | // 4 | // Copyright (c) 2017 Kazuhiko Arase 5 | // 6 | // URL: http://www.d-project.com/ 7 | // 8 | // Licensed under the MIT license: 9 | // http://www.opensource.org/licenses/mit-license.php 10 | // 11 | // This file describes how to customize the layout of library. 12 | // 13 | 14 | // includes following device types: 15 | // AltFullAdder 16 | 17 | simcir.registerDevice('AltFullAdder', 18 | { 19 | "width":440, 20 | "height":200, 21 | "showToolbox":false, 22 | "toolbox":[ 23 | ], 24 | "layout":{"rows":8,"cols":8,"hideLabelOnWorkspace":true, 25 | "nodes":{"A":"T2","B":"T6","S":"B4","Cin":"R4","Cout":"L4"}}, 26 | "devices":[ 27 | {"type":"In","id":"dev0","x":120,"y":32,"label":"Cin"}, 28 | {"type":"In","id":"dev1","x":120,"y":80,"label":"A"}, 29 | {"type":"In","id":"dev2","x":120,"y":128,"label":"B"}, 30 | {"type":"Toggle","id":"dev3","x":72,"y":32,"label":"Toggle"}, 31 | {"type":"Toggle","id":"dev4","x":72,"y":80,"label":"Toggle"}, 32 | {"type":"Toggle","id":"dev5","x":72,"y":128,"label":"Toggle"}, 33 | {"type":"DC","id":"dev6","x":24,"y":80,"label":"DC"}, 34 | {"type":"HalfAdder","id":"dev7","x":168,"y":104,"label":"HalfAdder"}, 35 | {"type":"HalfAdder","id":"dev8","x":248,"y":56,"label":"HalfAdder"}, 36 | {"type":"OR","id":"dev9","x":328,"y":104,"label":"OR"}, 37 | {"type":"Out","id":"dev10","x":376,"y":104,"label":"Cout"}, 38 | {"type":"Out","id":"dev11","x":376,"y":48,"label":"S"} 39 | ], 40 | "connectors":[ 41 | {"from":"dev0.in0","to":"dev3.out0"}, 42 | {"from":"dev1.in0","to":"dev4.out0"}, 43 | {"from":"dev2.in0","to":"dev5.out0"}, 44 | {"from":"dev3.in0","to":"dev6.out0"}, 45 | {"from":"dev4.in0","to":"dev6.out0"}, 46 | {"from":"dev5.in0","to":"dev6.out0"}, 47 | {"from":"dev7.in0","to":"dev1.out0"}, 48 | {"from":"dev7.in1","to":"dev2.out0"}, 49 | {"from":"dev8.in0","to":"dev0.out0"}, 50 | {"from":"dev8.in1","to":"dev7.out0"}, 51 | {"from":"dev9.in0","to":"dev8.out1"}, 52 | {"from":"dev9.in1","to":"dev7.out1"}, 53 | {"from":"dev10.in0","to":"dev9.out0"}, 54 | {"from":"dev11.in0","to":"dev8.out0"} 55 | ] 56 | } 57 | ); 58 | -------------------------------------------------------------------------------- /misc/simcir.d.ts: -------------------------------------------------------------------------------- 1 | // 2 | // SimcirJS - TypeScript Declaration File 3 | // 4 | // Copyright (c) 2016 Kazuhiko Arase 5 | // 6 | // URL: http://www.d-project.com/ 7 | // 8 | // Licensed under the MIT license: 9 | // http://www.opensource.org/licenses/mit-license.php 10 | // 11 | 12 | interface SimcirPoint { x: number, y: number } 13 | interface SimcirSize { width: number, height: number } 14 | interface SimcirRect extends SimcirPoint, SimcirSize {} 15 | 16 | interface SimcirEvent { target: JQuery, type: string } 17 | 18 | interface SimcirGraphics { 19 | attr: { [name : string] : (string|number) }; 20 | moveTo(x: number, y: number) : void; 21 | lineTo(x: number, y: number) : void; 22 | curveTo(x1: number, y1: number, x: number, y: number) : void; 23 | closePath(close?: boolean) : void; 24 | drawRect(x: number, y: number, width: number, height: number) : void; 25 | drawCircle(x: number, y: number, r: number) : void; 26 | } 27 | 28 | interface SimcirNode { 29 | type: string; 30 | label: string; 31 | description: string; 32 | $ui: JQuery; 33 | headless: boolean; 34 | getValue() : any; 35 | setValue(value : any, force? : boolean) : void; 36 | } 37 | 38 | interface SimcirInputNode extends SimcirNode { 39 | setOutput(outNode : SimcirNode) : void; 40 | getOutput() : SimcirOutputNode; 41 | } 42 | 43 | interface SimcirOutputNode extends SimcirNode { 44 | getInputs() : SimcirInputNode[]; 45 | connectTo(inNode : SimcirInputNode) : void; 46 | disconnectFrom(inNode : SimcirInputNode) : void; 47 | } 48 | 49 | interface SimcirDeviceDefBase { [id : string] : string|number } 50 | 51 | interface SimcirDeviceDef extends SimcirDeviceDefBase { 52 | type: string; 53 | label?: string; 54 | } 55 | 56 | interface SimcirDeviceInstance extends SimcirDeviceDef { 57 | id: string; 58 | x: number; 59 | y: number; 60 | } 61 | 62 | interface SimcirDocument { params: SimcirParamDescription[]; code: string; } 63 | 64 | interface SimcirParamDescription { 65 | name: string; 66 | type: string; 67 | defaultValue: any; 68 | description: string; 69 | } 70 | 71 | interface SimcirDevice { 72 | $ui: JQuery; 73 | doc: SimcirDocument; 74 | halfPitch: boolean; 75 | headless: boolean; 76 | scope: any; 77 | deviceDef: Def; 78 | addInput(label? : string, description? : string) : SimcirInputNode; 79 | getInputs() : SimcirInputNode[]; 80 | addOutput(label? : string, description? : string) : SimcirOutputNode; 81 | getOutputs() : SimcirOutputNode[]; 82 | getLabel() : string; 83 | getSize() : SimcirSize; 84 | createUI() : void; 85 | getState() : any; 86 | } 87 | 88 | interface SimcirConnectorDef { from: string; to: string; } 89 | 90 | interface SimcirData { 91 | width?: number; 92 | height?: number; 93 | toolbox?: SimcirDeviceDef[]; 94 | showToolbox?: boolean; 95 | editable?: boolean; 96 | layout? : SimcirCustomLayout; 97 | devices?: SimcirDeviceInstance[]; 98 | connectors?: SimcirConnectorDef[]; 99 | } 100 | 101 | interface SimcirCustomLayout { 102 | rows: number; 103 | cols: number; 104 | hideLabelOnWorkspace?: boolean; 105 | nodes: { [label : string] : string }; 106 | } 107 | 108 | type SimcirTypeFactory = (device : Def) => void; 109 | 110 | interface Simcir { 111 | unit: number; 112 | createSVGElement(tagName: string) : JQuery; 113 | graphics($target: JQuery) : SimcirGraphics; 114 | offset($o: JQuery) : SimcirPoint; 115 | transform($o: JQuery, x: number, y: number, rotate: number) : void; 116 | transform($o: JQuery) : { x: number; y: number; rotate: number; }; 117 | enableEvents($o: JQuery, enable: boolean) : void; 118 | controller($ui: JQuery, controller: any) : void; 119 | controller($ui: JQuery) : any; 120 | registerDevice(type: string, factory: SimcirTypeFactory) : void; 121 | registerDevice(type: string, data: SimcirData) : void; 122 | clearSimcir($placeHolder: JQuery) : JQuery; 123 | setupSimcir($placeHolder: JQuery, data: SimcirData) : JQuery; 124 | createWorkspace(data: SimcirData) : JQuery; 125 | } 126 | 127 | declare var simcir : Simcir; 128 | -------------------------------------------------------------------------------- /simcir.css: -------------------------------------------------------------------------------- 1 | /* 2 | * SimcirJS 3 | * 4 | * Copyright (c) 2014 Kazuhiko Arase 5 | * 6 | * URL: http://www.d-project.com/ 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/mit-license.php 10 | */ 11 | 12 | .simcir-body { 13 | display: inline-block; 14 | } 15 | 16 | .simcir-workspace { 17 | cursor: default; 18 | overflow: hidden; 19 | border-width: 1px; 20 | border-style: solid; 21 | border-color: #666666; 22 | } 23 | 24 | .simcir-pin-hole { 25 | fill: #cccccc; 26 | stroke: none; 27 | } 28 | 29 | .simcir-toolbox { 30 | fill: #eeeeee; 31 | } 32 | 33 | .simcir-scrollbar { 34 | fill: #cccccc; 35 | } 36 | 37 | .simcir-scrollbar-bar { 38 | fill: #aaaaaa; 39 | } 40 | 41 | .simcir-scrollbar-bar:hover { 42 | fill: #999999; 43 | } 44 | 45 | .simcir-connector { 46 | stroke-width: 1; 47 | stroke: #0000ff; 48 | stroke-linecap: round; 49 | } 50 | 51 | .simcir-connector-hot { 52 | /* stroke: #ff0000; */ 53 | } 54 | 55 | .simcir-joint-point { 56 | stroke-width: 3; 57 | } 58 | 59 | .simcir-device { 60 | fill: #cccccc; 61 | stroke-width: 2; 62 | stroke: #666666; 63 | } 64 | 65 | .simcir-device-selected { 66 | stroke: #0000ff; 67 | } 68 | 69 | .simcir-device-label { 70 | fill: #000000; 71 | stroke-width: 0; 72 | stroke: none; 73 | } 74 | 75 | .simcir-selection-rect { 76 | fill: none; 77 | stroke-width: 1; 78 | stroke: #0000ff; 79 | } 80 | 81 | .simcir-node-type-in { 82 | fill: #ffcc00; 83 | } 84 | 85 | .simcir-node-type-out { 86 | fill: #ffffff; 87 | } 88 | 89 | .simcir-node { 90 | stroke-width: 1; 91 | stroke: #000000; 92 | } 93 | 94 | .simcir-node-hot { 95 | stroke: #ff0000; 96 | } 97 | 98 | .simcir-node-hover { 99 | stroke: #ffff00; 100 | } 101 | 102 | .simcir-node-label { 103 | fill: #000000; 104 | stroke: none; 105 | } 106 | 107 | .simcir-port { 108 | stroke-width: 1; 109 | stroke: #000000; 110 | } 111 | 112 | .simcir-port-hole { 113 | stroke: none; 114 | fill: #000000; 115 | } 116 | 117 | .simcir-json-data-area { 118 | padding: 0px; 119 | outline: none; 120 | border-width: 1px; 121 | border-style: solid; 122 | border-color: #000000; 123 | } 124 | 125 | .simcir-dialog { 126 | padding: 4px; 127 | border-width: 2px; 128 | border-style: solid; 129 | border-color: #666666; 130 | background-color: #ffffff; 131 | -webkit-box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.5); 132 | -moz-box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.5); 133 | box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.5); 134 | } 135 | 136 | .simcir-dialog-title { 137 | margin-right: 4px; 138 | } 139 | 140 | .simcir-dialog-close-button { 141 | fill: #666666; 142 | stroke: none; 143 | } 144 | 145 | .simcir-dialog-close-button:hover { 146 | fill: #888888; 147 | stroke: none; 148 | } 149 | 150 | .simcir-dialog-close-button-symbol { 151 | fill: none; 152 | stroke: #ffffff; 153 | stroke-width: 2; 154 | } 155 | 156 | .simcir-label-editor { 157 | width: 100px; 158 | border-width: 1px; 159 | border-style: solid; 160 | border-color: #cccccc; 161 | background-color: #f0f0f0; 162 | outline: none; 163 | } 164 | 165 | .simcir-label-editor::-ms-clear { 166 | display: none; 167 | width: 0; 168 | height: 0; 169 | } 170 | 171 | /* simcir doc */ 172 | 173 | TABLE.simcir-doc-table { 174 | border-collapse: collapse; 175 | } 176 | 177 | TABLE.simcir-doc-table, 178 | TABLE.simcir-doc-table TH, 179 | TABLE.simcir-doc-table TD { 180 | border-width: 1px; 181 | border-style: solid; 182 | border-color: #666666; 183 | } 184 | 185 | TABLE.simcir-doc-table TH, 186 | TABLE.simcir-doc-table TD { 187 | padding: 16px; 188 | } 189 | 190 | TABLE.simcir-doc-params-table { 191 | border-collapse: collapse; 192 | } 193 | 194 | TABLE.simcir-doc-params-table, 195 | TABLE.simcir-doc-params-table TH, 196 | TABLE.simcir-doc-params-table TD { 197 | border-width: 1px; 198 | border-style: solid; 199 | border-color: #666666; 200 | } 201 | 202 | TABLE.simcir-doc-params-table TH, 203 | TABLE.simcir-doc-params-table TD { 204 | padding: 2px; 205 | } 206 | 207 | .simcir-doc-title { 208 | font-weight: bold; 209 | margin: 16px 0px 0px 0px; 210 | } 211 | 212 | .simcir-doc-code { 213 | padding: 8px; 214 | -webkit-border-radius: 4px; 215 | -moz-border-radius: 4px; 216 | border-radius: 4px; 217 | background-color: #f0f0f0; 218 | } 219 | -------------------------------------------------------------------------------- /misc/simcir-delay.js: -------------------------------------------------------------------------------- 1 | // 2 | // SimcirJS - Delay 3 | // 4 | // Copyright (c) 2017 Kazuhiko Arase 5 | // 6 | // URL: http://www.d-project.com/ 7 | // 8 | // Licensed under the MIT license: 9 | // http://www.opensource.org/licenses/mit-license.php 10 | // 11 | 12 | // includes following device types: 13 | // Delay 14 | 15 | !function($s) { 16 | 17 | 'use strict'; 18 | 19 | var $ = $s.$; 20 | 21 | // unit size 22 | var unit = $s.unit; 23 | 24 | var connectNode = function(in1, out1, delay) { 25 | // set input value to output without inputValueChange event. 26 | var in1_super_setValue = in1.setValue; 27 | in1.setValue = function(value, force) { 28 | var changed = in1.getValue() !== value; 29 | in1_super_setValue(value, force); 30 | if (changed || force) { 31 | var value = in1.getValue(); 32 | window.setTimeout(function() { out1.setValue(value); }, delay); 33 | } 34 | }; 35 | }; 36 | 37 | var createDelayFactory = function() { 38 | 39 | var maxFadeCount = 16; 40 | var fadeTimeout = 100; 41 | 42 | var defaultDelay = 50; 43 | var defaultDelayColor = '#ff0000'; 44 | 45 | var Direction = { WE : 0, NS : 1, EW : 2, SN : 3 }; 46 | 47 | return function(device) { 48 | 49 | var delay = Math.max(0, device.deviceDef.delay || defaultDelay); 50 | var color = device.deviceDef.color || defaultDelayColor; 51 | 52 | var in1 = device.addInput(); 53 | var out1 = device.addOutput(); 54 | connectNode(in1, out1, delay); 55 | 56 | var state = device.deviceDef.state || { direction : Direction.WE }; 57 | device.getState = function() { 58 | return state; 59 | }; 60 | 61 | device.getSize = function() { 62 | return { width : unit, height : unit }; 63 | }; 64 | 65 | var super_createUI = device.createUI; 66 | device.createUI = function() { 67 | super_createUI(); 68 | 69 | var $label = device.$ui.children('.simcir-device-label'); 70 | $label.attr('y', $label.attr('y') - unit / 4); 71 | 72 | var $point = $s.createSVGElement('circle'). 73 | css('pointer-events', 'none').css('opacity', 0).attr('r', 2). 74 | addClass('simcir-connector').addClass('simcir-joint-point'); 75 | device.$ui.append($point); 76 | 77 | var $path = $s.createSVGElement('path'). 78 | css('pointer-events', 'none').css('opacity', 0). 79 | addClass('simcir-connector').css('stroke', color); 80 | device.$ui.append($path); 81 | 82 | var $title = $s.createSVGElement('title'). 83 | text('Double-Click to change a direction.'); 84 | 85 | var updatePoint = function() { 86 | $point.css('display', out1.getInputs().length > 1? '' : 'none'); 87 | }; 88 | 89 | updatePoint(); 90 | 91 | var super_connectTo = out1.connectTo; 92 | out1.connectTo = function(inNode) { 93 | super_connectTo(inNode); 94 | updatePoint(); 95 | }; 96 | var super_disconnectFrom = out1.disconnectFrom; 97 | out1.disconnectFrom = function(inNode) { 98 | super_disconnectFrom(inNode); 99 | updatePoint(); 100 | }; 101 | 102 | var updateUI = function() { 103 | var x0, y0, x1, y1; 104 | x0 = y0 = x1 = y1 = unit / 2; 105 | var d = unit / 2; 106 | var direction = state.direction; 107 | if (direction == Direction.WE) { 108 | x0 -= d; 109 | x1 += d; 110 | } else if (direction == Direction.NS) { 111 | y0 -= d; 112 | y1 += d; 113 | } else if (direction == Direction.EW) { 114 | x0 += d; 115 | x1 -= d; 116 | } else if (direction == Direction.SN) { 117 | y0 += d; 118 | y1 -= d; 119 | } 120 | $path.attr('d', 'M' + x0 + ' ' + y0 + 'L' + x1 + ' ' + y1); 121 | $s.transform(in1.$ui, x0, y0); 122 | $s.transform(out1.$ui, x1, y1); 123 | $point.attr({cx : x1, cy : y1}); 124 | if (direction == Direction.EW || direction == Direction.WE) { 125 | device.$ui.children('.simcir-device-body'). 126 | attr({x: 0, y: unit / 4, width: unit, height: unit / 2}); 127 | } else { 128 | device.$ui.children('.simcir-device-body'). 129 | attr({x: unit / 4, y: 0, width: unit / 2, height: unit}); 130 | } 131 | }; 132 | 133 | updateUI(); 134 | 135 | // fadeout a body. 136 | var fadeCount = 0; 137 | var setOpacity = function(opacity) { 138 | device.$ui.children('.simcir-device-body,.simcir-node'). 139 | css('opacity', opacity); 140 | $path.css('opacity', 1 - opacity); 141 | $point.css('opacity', 1 - opacity); 142 | }; 143 | var fadeout = function() { 144 | window.setTimeout(function() { 145 | if (fadeCount > 0) { 146 | fadeCount -= 1; 147 | setOpacity(fadeCount / maxFadeCount); 148 | fadeout(); 149 | } 150 | }, fadeTimeout); 151 | }; 152 | 153 | var isEditable = function($dev) { 154 | var $workspace = $dev.closest('.simcir-workspace'); 155 | return !!$s.controller($workspace).data().editable; 156 | }; 157 | var device_mouseoutHandler = function(event) { 158 | if (!isEditable($(event.target) ) ) { 159 | return; 160 | } 161 | if (!device.isSelected() ) { 162 | fadeCount = maxFadeCount; 163 | fadeout(); 164 | } 165 | }; 166 | var device_dblclickHandler = function(event) { 167 | if (!isEditable($(event.target) ) ) { 168 | return; 169 | } 170 | state.direction = (state.direction + 1) % 4; 171 | updateUI(); 172 | // update connectors. 173 | $(this).trigger('mousedown').trigger('mouseup'); 174 | }; 175 | 176 | device.$ui.on('mouseover', function(event) { 177 | if (!isEditable($(event.target) ) ) { 178 | $title.text(''); 179 | return; 180 | } 181 | setOpacity(1); 182 | fadeCount = 0; 183 | }).on('deviceAdd', function() { 184 | if ($(this).closest('BODY').length == 0) { 185 | setOpacity(0); 186 | } 187 | $(this).append($title).on('mouseout', device_mouseoutHandler). 188 | on('dblclick', device_dblclickHandler); 189 | // hide a label 190 | $label.css('display', 'none'); 191 | }).on('deviceRemove', function() { 192 | $(this).off('mouseout', device_mouseoutHandler). 193 | off('dblclick', device_dblclickHandler); 194 | $title.remove(); 195 | // show a label 196 | $label.css('display', ''); 197 | }).on('deviceSelect', function() { 198 | if (device.isSelected() ) { 199 | setOpacity(1); 200 | fadeCount = 0; 201 | } else { 202 | if (fadeCount == 0) { 203 | setOpacity(0); 204 | } 205 | } 206 | }); 207 | device.doc = { 208 | labelless: true, 209 | params: [ 210 | {name: 'delay', type: 'number', 211 | defaultValue: defaultDelay, 212 | description: 'time delay in milli-seconds.'}, 213 | {name: 'color', type: 'string', 214 | defaultValue: defaultDelayColor, 215 | description: 'color in hexadecimal.'}], 216 | code: '{"type":"' + device.deviceDef.type + '","delay":50}' 217 | }; 218 | }; 219 | }; 220 | }; 221 | 222 | $s.registerDevice('Delay', createDelayFactory() ); 223 | 224 | }(simcir); 225 | -------------------------------------------------------------------------------- /misc/simcir-transmitter.js: -------------------------------------------------------------------------------- 1 | // 2 | // SimcirJS - Transmitter 3 | // 4 | // Copyright (c) 2016 Kazuhiko Arase 5 | // 6 | // URL: http://www.d-project.com/ 7 | // 8 | // Licensed under the MIT license: 9 | // http://www.opensource.org/licenses/mit-license.php 10 | // 11 | 12 | // includes following device types: 13 | // Transmitter 14 | 15 | !function($s) { 16 | 17 | 'use strict'; 18 | 19 | var $ = $s.$; 20 | 21 | // unit size 22 | var unit = $s.unit; 23 | 24 | var createTransmitterFactory = function() { 25 | 26 | var emptyGroup = { 27 | getValue : function() { return null; }, 28 | setValue : function(value, force) {}, 29 | getInput : function() { return null; }, 30 | setInput : function(device) {} 31 | }; 32 | 33 | var createGroup = function(devices) { 34 | var input = function() { 35 | for (var i = 0; i < devices.length; i += 1) { 36 | var inNode = devices[i].getInputs()[0]; 37 | var outNode = inNode.getOutput(); 38 | if (outNode != null) { 39 | return devices[i]; 40 | } 41 | } 42 | return null; 43 | }(); 44 | return { 45 | getValue : function() { 46 | return input != null? input.getInputs()[0].getValue() : null; 47 | }, 48 | setValue : function(value, force) { 49 | for (var i = 0; i < devices.length; i += 1) { 50 | devices[i].getOutputs()[0].setValue(value, force); 51 | } 52 | }, 53 | getInput : function() { 54 | return input; 55 | }, 56 | setInput : function(device) { 57 | input = device; 58 | for (var i = 0; i < devices.length; i += 1) { 59 | if (devices[i] != device) { 60 | var inNode = devices[i].getInputs()[0]; 61 | var outNode = inNode.getOutput(); 62 | if (outNode != null) { 63 | outNode.disconnectFrom(inNode); 64 | } 65 | } 66 | } 67 | } 68 | }; 69 | }; 70 | 71 | var createGroupByLabel = function(devices) { 72 | var devicesByLabel = {}; 73 | for (var id in devices) { 74 | var device = devices[id]; 75 | var label = device.getLabel(); 76 | if (!devicesByLabel[label]) { 77 | devicesByLabel[label] = []; 78 | } 79 | devicesByLabel[label].push(device); 80 | } 81 | var groupByLabel = {}; 82 | for (var label in devicesByLabel) { 83 | groupByLabel[label] = createGroup(devicesByLabel[label]); 84 | } 85 | return groupByLabel; 86 | }; 87 | 88 | var createGroupManager = function() { 89 | 90 | var devices = {}; 91 | var idCount = 0; 92 | var groupCache = null; 93 | 94 | var register = function(device) { 95 | var id = 'id' + idCount++; 96 | if (device.headless) { 97 | devices[id] = device; 98 | reset(); 99 | } else { 100 | device.$ui.on('deviceAdd', function() { 101 | devices[id] = device; 102 | reset(); 103 | }).on('deviceRemove', function() { 104 | delete devices[id]; 105 | reset(); 106 | }); 107 | } 108 | }; 109 | 110 | var reset = function() { 111 | groupCache = null; 112 | }; 113 | 114 | var getGroupByLabel = function(label) { 115 | if (!groupCache) { 116 | groupCache = createGroupByLabel(devices); 117 | } 118 | return groupCache[label] || emptyGroup; 119 | }; 120 | 121 | return { 122 | register : register, 123 | reset : reset, 124 | getGroupByLabel : getGroupByLabel 125 | }; 126 | }; 127 | 128 | var maxFadeCount = 16; 129 | var fadeTimeout = 100; 130 | var getEmptyGroupByLabel = function(label) { return emptyGroup; }; 131 | 132 | return function(device) { 133 | 134 | var getGroupByLabel = getEmptyGroupByLabel; 135 | if (device.scope) { 136 | var groupManager = device.scope.transmitterGroupManager; 137 | if (!groupManager) { 138 | groupManager = createGroupManager(); 139 | device.scope.transmitterGroupManager = groupManager; 140 | } 141 | groupManager.register(device); 142 | getGroupByLabel = function(label) { 143 | return groupManager.getGroupByLabel(label); 144 | }; 145 | } 146 | 147 | var in1 = device.addInput(); 148 | var out1 = device.addOutput(); 149 | var lastLabel = device.getLabel(); 150 | 151 | var in1_super_setValue = in1.setValue; 152 | in1.setValue = function(value, force) { 153 | var changed = in1.getValue() !== value; 154 | in1_super_setValue(value, force); 155 | if (changed || force) { 156 | getGroupByLabel(device.getLabel() ).setValue(in1.getValue(), force); 157 | } 158 | }; 159 | 160 | var in1_super_setOutput = in1.setOutput; 161 | in1.setOutput = function(outNode) { 162 | in1_super_setOutput(outNode); 163 | if (outNode != null) { 164 | getGroupByLabel(device.getLabel() ).setInput(device); 165 | } 166 | }; 167 | 168 | device.getSize = function() { 169 | return { width : unit, height : unit }; 170 | }; 171 | 172 | var super_createUI = device.createUI; 173 | device.createUI = function() { 174 | super_createUI(); 175 | 176 | device.$ui.children('.simcir-device-body'). 177 | attr({x: 0, y: unit / 4, width: unit, height: unit / 2}); 178 | 179 | var $label = device.$ui.children('.simcir-device-label'); 180 | var defaultLabelX = +$label.attr('x'); 181 | var defaultLabelY = +$label.attr('y'); 182 | 183 | var $point = $s.createSVGElement('circle'). 184 | css('pointer-events', 'none').css('opacity', 0). 185 | attr({cx: unit / 2, cy: unit / 2, r: 2}). 186 | addClass('simcir-connector').addClass('simcir-joint-point'); 187 | device.$ui.append($point); 188 | 189 | var $path = $s.createSVGElement('path'). 190 | css('pointer-events', 'none').css('opacity', 0). 191 | addClass('simcir-connector'); 192 | device.$ui.append($path); 193 | 194 | var updateUI = function() { 195 | 196 | var isInSet = in1.getOutput() != null; 197 | var isOutSet = out1.getInputs().length > 0; 198 | 199 | var x0, y0, x1, y1, cx, cy; 200 | x0 = y0 = x1 = y1 = cx = cy = unit / 2; 201 | var d = unit / 2; 202 | x0 -= d; 203 | x1 += d; 204 | if (isInSet && isOutSet) { 205 | } else if (isInSet) { 206 | cx += d; 207 | } else if (isOutSet) { 208 | cx -= d; 209 | } 210 | $point.attr('cx', cx).attr('cy', cy); 211 | $path.attr('d', 'M' + x0 + ' ' + y0 + 'L' + x1 + ' ' + y1); 212 | 213 | var labelX = defaultLabelX; 214 | var labelY = defaultLabelY; 215 | var anchor = 'middle'; 216 | if (isInSet && isOutSet) { 217 | labelY -= unit / 4; 218 | } else if (!isInSet && !isOutSet) { 219 | labelY -= unit / 4; 220 | } else if (isInSet) { 221 | labelX += unit; 222 | labelY -= unit; 223 | anchor = 'start'; 224 | } else if (isOutSet) { 225 | labelX -= unit; 226 | labelY -= unit; 227 | anchor = 'end'; 228 | } 229 | $label.attr('x', labelX).attr('y', labelY). 230 | attr('text-anchor', anchor); 231 | }; 232 | 233 | updateUI(); 234 | 235 | var in1_super_setOutput = in1.setOutput; 236 | in1.setOutput = function(outNode) { 237 | in1_super_setOutput(outNode); 238 | updateUI(); 239 | }; 240 | var super_connectTo = out1.connectTo; 241 | out1.connectTo = function(inNode) { 242 | super_connectTo(inNode); 243 | updateUI(); 244 | }; 245 | var super_disconnectFrom = out1.disconnectFrom; 246 | out1.disconnectFrom = function(inNode) { 247 | super_disconnectFrom(inNode); 248 | updateUI(); 249 | }; 250 | 251 | // fadeout a body. 252 | var fadeCount = 0; 253 | var setOpacity = function(opacity) { 254 | device.$ui.children('.simcir-device-body,.simcir-node'). 255 | css('opacity', opacity); 256 | $path.css('opacity', 1 - opacity); 257 | $point.css('opacity', 1 - opacity); 258 | }; 259 | var fadeout = function() { 260 | window.setTimeout(function() { 261 | if (fadeCount > 0) { 262 | fadeCount -= 1; 263 | setOpacity(fadeCount / maxFadeCount); 264 | fadeout(); 265 | } 266 | }, fadeTimeout); 267 | }; 268 | 269 | var device_mouseoutHandler = function(event) { 270 | if (!device.isSelected() ) { 271 | fadeCount = maxFadeCount; 272 | fadeout(); 273 | } 274 | }; 275 | 276 | device.$ui.on('mouseover', function(event) { 277 | setOpacity(1); 278 | fadeCount = 0; 279 | }).on('deviceAdd', function() { 280 | out1.setValue(getGroupByLabel(device.getLabel() ).getValue() ); 281 | if ($(this).closest('BODY').length == 0) { 282 | setOpacity(0); 283 | } 284 | $(this).on('mouseout', device_mouseoutHandler); 285 | }).on('deviceRemove', function() { 286 | $(this).off('mouseout', device_mouseoutHandler); 287 | }).on('deviceLabelChange', function() { 288 | 289 | groupManager.reset(); 290 | 291 | var lastGrp = getGroupByLabel(lastLabel); 292 | lastGrp.setValue(lastGrp.getValue() ); 293 | 294 | var newLabel = device.getLabel(); 295 | var newGrp = getGroupByLabel(newLabel); 296 | if (in1.getOutput() != null) { 297 | newGrp.setInput(device); 298 | } 299 | newGrp.setValue(newGrp.getValue() ); 300 | 301 | lastLabel = newLabel; 302 | 303 | }).on('deviceSelect', function() { 304 | if (device.isSelected() ) { 305 | setOpacity(1); 306 | fadeCount = 0; 307 | } else { 308 | if (fadeCount == 0) { 309 | setOpacity(0); 310 | } 311 | } 312 | }); 313 | device.doc = { 314 | description: 315 | 'Transmit a signal to another trasmitter that has same label.', 316 | params: [], 317 | code: '{"type":"' + device.deviceDef.type + '"}' 318 | }; 319 | }; 320 | }; 321 | }; 322 | 323 | $s.registerDevice('Transmitter', createTransmitterFactory() ); 324 | 325 | }(simcir); 326 | -------------------------------------------------------------------------------- /misc/simcir-num.js: -------------------------------------------------------------------------------- 1 | // 2 | // SimcirJS - Num 3 | // 4 | // Copyright (c) 2017 Kazuhiko Arase 5 | // 6 | // URL: http://www.d-project.com/ 7 | // 8 | // Licensed under the MIT license: 9 | // http://www.opensource.org/licenses/mit-license.php 10 | // 11 | 12 | // includes following device types: 13 | // NumSrc 14 | // NumDsp 15 | 16 | !function($s) { 17 | 18 | 'use strict'; 19 | 20 | var $ = $s.$; 21 | 22 | // unit size 23 | var unit = $s.unit; 24 | 25 | var createNumLabel = function(size) { 26 | var $label = $s.createSVGElement('g'). 27 | css('pointer-events', 'none'). 28 | attr('fill', 'none'). 29 | attr('stroke-width', '2'); 30 | $s.transform($label, size.width / 2, size.height / 2); 31 | var lsize = Math.max(size.width, size.height); 32 | var ratio = 0.65; 33 | $s.controller($label, { 34 | setOn : function(on) { 35 | $label.children().remove(); 36 | if (on) { 37 | var w = lsize / 2 * ratio * 0.5; 38 | var x = w * 0.2; 39 | $label.append($s.createSVGElement('path'). 40 | attr('d', 41 | 'M' + x + ' ' + (lsize / 2 * ratio) + 42 | 'L ' + x + ' ' + -lsize / 2 * ratio + 43 | 'Q' + (x - lsize / 2 * ratio * 0.125) + 44 | ' ' + (-lsize / 2 * ratio * 0.6) + 45 | ' ' + (x - w) + 46 | ' ' + (-lsize / 2 * ratio * 0.5) ). 47 | attr('stroke-linecap' , 'square'). 48 | attr('stroke-linejoin' , 'round') ); 49 | } else { 50 | $label.append($s.createSVGElement('ellipse'). 51 | attr({ cx : 0, cy : 0, 52 | rx : lsize / 2 * ratio * 0.6, ry : lsize / 2 * ratio}). 53 | attr('fill', 'none') ); 54 | } 55 | }, 56 | setColor : function(color) { 57 | $label.attr('stroke', color); 58 | } 59 | }); 60 | return $label; 61 | }; 62 | 63 | var createNumFactory = function(type) { 64 | 65 | var maxFadeCount = 16; 66 | var fadeTimeout = 100; 67 | 68 | var Direction = { WE : 0, NS : 1, EW : 2, SN : 3 }; 69 | 70 | return function(device) { 71 | 72 | var in1 = type == 'dsp'? device.addInput() : null; 73 | var out1 = type == 'src'? device.addOutput() : null; 74 | 75 | var on = false; 76 | var updateOutput = null; 77 | 78 | var direction = null; 79 | if (device.deviceDef.state) { 80 | direction = device.deviceDef.state.direction; 81 | } 82 | if (typeof direction != 'number') { 83 | direction = type == 'src'? Direction.WE : Direction.EW; 84 | } 85 | 86 | if (type == 'src') { 87 | if (device.deviceDef.state) { 88 | on = device.deviceDef.state.on; 89 | } 90 | device.getState = function() { 91 | return { direction : direction, on : on }; 92 | }; 93 | device.$ui.on('inputValueChange', function() { 94 | if (on) { 95 | out1.setValue(in1.getValue() ); 96 | } 97 | }); 98 | updateOutput = function() { 99 | out1.setValue(on? 1 : null); 100 | }; 101 | updateOutput(); 102 | } else if (type == 'dsp') { 103 | device.getState = function() { 104 | return { direction : direction }; 105 | }; 106 | } 107 | 108 | device.getSize = function() { 109 | return { width : unit, height : unit }; 110 | }; 111 | 112 | var super_createUI = device.createUI; 113 | device.createUI = function() { 114 | super_createUI(); 115 | 116 | var $label = device.$ui.children('.simcir-device-label'); 117 | var size = device.getSize(); 118 | 119 | device.$ui.css('fill', '#eeeeee'); 120 | 121 | var $button = null; 122 | if (type == 'src') { 123 | $button = $s.createSVGElement('rect'). 124 | attr({x: 1, y: 1, width: size.width - 2, height: size.height - 2, 125 | rx: 2, ry: 2, stroke: 'none', fill: '#cccccc'}). 126 | append($s.createSVGElement('title') ); 127 | device.$ui.append($button); 128 | var button_mouseDownHandler = function(event) { 129 | event.preventDefault(); 130 | event.stopPropagation(); 131 | on = !on; 132 | updateOutput(); 133 | $(document).on('mouseup', button_mouseUpHandler); 134 | $(document).on('touchend', button_mouseUpHandler); 135 | }; 136 | var button_dblClickHandler = function(event) { 137 | event.preventDefault(); 138 | event.stopPropagation(); 139 | }; 140 | var button_mouseUpHandler = function(event) { 141 | updateOutput(); 142 | $(document).off('mouseup', button_mouseUpHandler); 143 | $(document).off('touchend', button_mouseUpHandler); 144 | }; 145 | device.$ui.on('deviceAdd', function() { 146 | $s.enableEvents($button, true); 147 | $button.on('mousedown', button_mouseDownHandler); 148 | $button.on('touchstart', button_mouseDownHandler); 149 | $button.on('dblclick', button_dblClickHandler); 150 | }); 151 | device.$ui.on('deviceRemove', function() { 152 | $s.enableEvents($button, false); 153 | $button.off('mousedown', button_mouseDownHandler); 154 | $button.off('touchstart', button_mouseDownHandler); 155 | $button.off('dblclick', button_dblClickHandler); 156 | }); 157 | var out1_setValue = out1.setValue; 158 | out1.setValue = function(value) { 159 | out1_setValue(value); 160 | $s.controller($numLabel).setOn(out1.getValue() != null); 161 | }; 162 | } 163 | 164 | var $numLabel = createNumLabel(size); 165 | $s.controller($numLabel).setColor('#000000'); 166 | device.$ui.append($numLabel); 167 | 168 | if (type == 'src') { 169 | $s.controller($numLabel).setOn(out1.getValue() != null); 170 | } else if (type == 'dsp') { 171 | $s.controller($numLabel).setOn(false); 172 | device.$ui.on('inputValueChange', function() { 173 | $s.controller($numLabel).setOn(in1.getValue() != null); 174 | }); 175 | } 176 | 177 | var $path = $s.createSVGElement('path'). 178 | css('pointer-events', 'none').css('opacity', 0). 179 | addClass('simcir-connector'); 180 | device.$ui.append($path); 181 | 182 | var $title = $s.createSVGElement('title'). 183 | text('Double-Click to change a direction.'); 184 | 185 | if (type == 'src') { 186 | 187 | var $point = $s.createSVGElement('circle'). 188 | css('pointer-events', 'none').css('opacity', 0). 189 | attr('cx', size.width).attr('cy', size.height / 2).attr('r', 2). 190 | addClass('simcir-connector').addClass('simcir-joint-point'); 191 | device.$ui.append($point); 192 | 193 | var updatePoint = function() { 194 | $point.css('display', out1.getInputs().length > 1? '' : 'none'); 195 | }; 196 | 197 | updatePoint(); 198 | 199 | var super_connectTo = out1.connectTo; 200 | out1.connectTo = function(inNode) { 201 | super_connectTo(inNode); 202 | updatePoint(); 203 | }; 204 | var super_disconnectFrom = out1.disconnectFrom; 205 | out1.disconnectFrom = function(inNode) { 206 | super_disconnectFrom(inNode); 207 | updatePoint(); 208 | }; 209 | } 210 | 211 | var updateUI = function() { 212 | var x0, y0, x1, y1; 213 | x0 = y0 = x1 = y1 = unit / 2; 214 | var d = unit / 2; 215 | if (direction == Direction.WE) { 216 | x0 += d; 217 | x1 += unit; 218 | } else if (direction == Direction.NS) { 219 | y0 += d * 1.25; 220 | y1 += unit; 221 | } else if (direction == Direction.EW) { 222 | x0 -= d; 223 | x1 -= unit; 224 | } else if (direction == Direction.SN) { 225 | y0 -= d * 1.25; 226 | y1 -= unit; 227 | } 228 | $path.attr('d', 'M' + x0 + ' ' + y0 + 'L' + x1 + ' ' + y1); 229 | if (type == 'src') { 230 | $s.transform(out1.$ui, x1, y1); 231 | $point.attr({cx : x1, cy : y1}); 232 | } else if (type == 'dsp') { 233 | $s.transform(in1.$ui, x1, y1); 234 | } 235 | if (direction == Direction.EW || direction == Direction.WE) { 236 | device.$ui.children('.simcir-device-body'). 237 | attr({x: -unit / 2, y: 0, width: unit * 2, height: unit}); 238 | } else { 239 | device.$ui.children('.simcir-device-body'). 240 | attr({x: 0, y: -unit / 2, width: unit, height: unit * 2}); 241 | } 242 | }; 243 | 244 | updateUI(); 245 | 246 | // fadeout a body. 247 | var fadeCount = 0; 248 | var setOpacity = function(opacity) { 249 | device.$ui.children('.simcir-device-body,.simcir-node'). 250 | css('opacity', opacity); 251 | $path.css('opacity', 1 - opacity); 252 | if (type == 'src') { 253 | $button.css('opacity', opacity); 254 | $point.css('opacity', 1 - opacity); 255 | } 256 | }; 257 | var fadeout = function() { 258 | window.setTimeout(function() { 259 | if (fadeCount > 0) { 260 | fadeCount -= 1; 261 | setOpacity(fadeCount / maxFadeCount); 262 | fadeout(); 263 | } 264 | }, fadeTimeout); 265 | }; 266 | 267 | var isEditable = function($dev) { 268 | var $workspace = $dev.closest('.simcir-workspace'); 269 | return !!$s.controller($workspace).data().editable; 270 | }; 271 | var device_mouseoutHandler = function(event) { 272 | if (!isEditable($(event.target) ) ) { 273 | return; 274 | } 275 | if (!device.isSelected() ) { 276 | fadeCount = maxFadeCount; 277 | fadeout(); 278 | } 279 | }; 280 | var device_dblclickHandler = function(event) { 281 | if (!isEditable($(event.target) ) ) { 282 | return; 283 | } 284 | direction = (direction + 1) % 4; 285 | updateUI(); 286 | // update connectors. 287 | $(this).trigger('mousedown').trigger('mouseup'); 288 | }; 289 | 290 | device.$ui.on('mouseover', function(event) { 291 | if (!isEditable($(event.target) ) ) { 292 | $title.text(''); 293 | return; 294 | } 295 | setOpacity(1); 296 | fadeCount = 0; 297 | }).on('deviceAdd', function() { 298 | if ($(this).closest('BODY').length == 0) { 299 | setOpacity(0); 300 | } 301 | $(this).append($title).on('mouseout', device_mouseoutHandler). 302 | on('dblclick', device_dblclickHandler); 303 | // hide a label 304 | $label.css('display', 'none'); 305 | }).on('deviceRemove', function() { 306 | $(this).off('mouseout', device_mouseoutHandler). 307 | off('dblclick', device_dblclickHandler); 308 | $title.remove(); 309 | // show a label 310 | $label.css('display', ''); 311 | }).on('deviceSelect', function() { 312 | if (device.isSelected() ) { 313 | setOpacity(1); 314 | fadeCount = 0; 315 | } else { 316 | if (fadeCount == 0) { 317 | setOpacity(0); 318 | } 319 | } 320 | }); 321 | }; 322 | }; 323 | }; 324 | 325 | $s.registerDevice('NumSrc', createNumFactory('src') ); 326 | $s.registerDevice('NumDsp', createNumFactory('dsp') ); 327 | 328 | }(simcir); 329 | -------------------------------------------------------------------------------- /sample.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 29 | SimcirJS 30 | 31 | 32 | 33 |

SimcirJS

34 | 35 |
36 | SimcirJS(a.k.a. Simcir) is a circuit simulator in HTML5 and JavaScript. 37 |
38 | Let's try your circuit! 39 |
40 | 41 |

Usage

42 | 51 | 52 | 56 |
57 | { 58 | "width":800, 59 | "height":400 60 | } 61 |
62 | 63 |

Embed your circuit

64 |

65 | Ctrl+Click(Mac:command+Click) on your circuit and copy a circuit data. 66 |
67 | Then paste it into template below. 68 |

69 | 70 |
<!doctype html> 72 | <html> 73 | <head> 74 | <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" /> 75 | <script type="text/javascript" src="simcir.js"></script> 76 | <link rel="stylesheet" type="text/css" href="simcir.css" /> 77 | <script type="text/javascript" src="simcir-basicset.js"></script> 78 | <link rel="stylesheet" type="text/css" href="simcir-basicset.css" /> 79 | <script type="text/javascript" src="simcir-library.js"></script> 80 | <title></title> 81 | </head> 82 | <body> 83 | <div class="simcir"> 84 | <!-- paste here your circuit data --> 85 | </div> 86 | </body> 87 | </html> 88 |
89 | 90 |

Reuse your circuit

91 |

92 | To reuse your circuit as library, use device 'In' and 'Out'. 93 |

94 |
95 | { 96 | "width":500, 97 | "height":200, 98 | "showToolbox":true, 99 | "toolbox":[ 100 | {"type":"In"}, 101 | {"type":"Out"}, 102 | {"type":"DC"}, 103 | {"type":"PushOff"}, 104 | {"type":"PushOn"}, 105 | {"type":"Toggle"}, 106 | {"type":"NAND"} 107 | ], 108 | "devices":[ 109 | {"type":"DC","id":"dev0","x":64,"y":80,"label":"DC"}, 110 | {"type":"PushOff","id":"dev1","x":120,"y":48,"label":"PushOff"}, 111 | {"type":"PushOff","id":"dev2","x":120,"y":112,"label":"PushOff"}, 112 | {"type":"In","id":"dev3","x":176,"y":48,"label":"~S"}, 113 | {"type":"In","id":"dev4","x":176,"y":112,"label":"~R"}, 114 | {"type":"NAND","id":"dev5","x":232,"y":56,"label":"NAND"}, 115 | {"type":"NAND","id":"dev6","x":232,"y":104,"label":"NAND"}, 116 | {"type":"Out","id":"dev7","x":288,"y":56,"label":"Q"}, 117 | {"type":"Out","id":"dev8","x":288,"y":104,"label":"~Q"} 118 | ], 119 | "connectors":[ 120 | {"from":"dev1.in0","to":"dev0.out0"}, 121 | {"from":"dev2.in0","to":"dev0.out0"}, 122 | {"from":"dev3.in0","to":"dev1.out0"}, 123 | {"from":"dev4.in0","to":"dev2.out0"}, 124 | {"from":"dev5.in0","to":"dev3.out0"}, 125 | {"from":"dev5.in1","to":"dev6.out0"}, 126 | {"from":"dev6.in0","to":"dev5.out0"}, 127 | {"from":"dev6.in1","to":"dev4.out0"}, 128 | {"from":"dev7.in0","to":"dev5.out0"}, 129 | {"from":"dev8.in0","to":"dev6.out0"} 130 | ] 131 | } 132 |
133 |

134 | Then register it in JavaScript and add to the toolbox in HTML. 135 |

136 |
simcir.registerDevice('MyDevice', 138 | // paste here your circuit data 139 | ); 140 |
141 |
<div class="simcir"> 143 | ⋮ 144 | "toolbox":[ 145 | {"type":"DC"}, 146 | {"type":"PushOff"}, 147 | ⋮ 148 | {"type":"MyDevice"} 149 | ], 150 | ⋮ 151 | </div> 152 |
153 |

154 | In this case, a new device 'RS-FF' is added. 155 | Try to Double-Click the 'RS-FF' :) 156 |
157 | Remember that all the connectors on 158 | an input of 'In' and an output of 'Out' 159 | are disconnected internally when the device is reused. 160 |

161 |
162 | { 163 | "width":500, 164 | "height":200, 165 | "showToolbox":true, 166 | "toolbox":[ 167 | {"type":"DC"}, 168 | {"type":"PushOff"}, 169 | {"type":"RS-FF"} 170 | ], 171 | "devices":[ 172 | {"type":"DC","id":"dev0","x":104,"y":80,"label":"DC"}, 173 | {"type":"PushOff","id":"dev1","x":160,"y":48,"label":"PushOff"}, 174 | {"type":"PushOff","id":"dev2","x":160,"y":112,"label":"PushOff"}, 175 | {"type":"RS-FF","id":"dev3","x":216,"y":80,"label":"RS-FF"} 176 | ], 177 | "connectors":[ 178 | {"from":"dev1.in0","to":"dev0.out0"}, 179 | {"from":"dev2.in0","to":"dev0.out0"}, 180 | {"from":"dev3.in0","to":"dev1.out0"}, 181 | {"from":"dev3.in1","to":"dev2.out0"} 182 | ] 183 | } 184 |
185 |

186 | To customize the layout of library, 187 | add the "layout" property to your device with a text editor. 188 |
189 | "rows" and "cols" define the size. 190 | nodes property contains 'label - position' pairs of nodes. 191 |

192 |
simcir.registerDevice('AltFullAdder', 194 | { 195 | ⋮ 196 | "layout":{"rows":8,"cols":8,"hideLabelOnWorkspace":true, 197 | "nodes":{"A":"T2","B":"T6","S":"B4","Cin":"R4","Cout":"L4"}}, 198 | "devices":[ 199 | ⋮ 200 |
201 | 202 | 205 | 207 | 208 | 209 | L1 210 | L0 211 | L2 212 | L3 213 | L4 214 | T0 215 | T1 216 | T2 217 | T3 218 | T4 219 | R0 220 | R1 221 | R2 222 | R3 223 | R4 224 | B0 225 | B1 226 | B2 227 | B3 228 | B4 229 | 230 | 231 | 232 | 233 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | Left 250 | Right 251 | Bottom 252 | Top 253 | 254 | rows 255 | 256 | 257 | 258 | 259 | 261 | 262 | 264 | 266 | 267 | 268 | 269 | cols 270 | 271 | 272 | 273 | 274 | 276 | 277 | 279 | 281 | 282 | 283 | 284 | 285 | 286 | 287 |

Customize a device

288 |

289 | label, color, number of inputs, freq ... 290 |

291 |
292 | { 293 | "width":500, 294 | "height":400, 295 | "showToolbox":true, 296 | "toolbox":[ 297 | {"type":"DC"}, 298 | {"type":"Toggle"}, 299 | {"type":"LED","color":"#00ff00","label":"LED(G)"}, 300 | {"type":"NAND","numInputs":"3","label":"NAND(3in)"}, 301 | {"type":"OSC","freq":1,"label":"OSC(1Hz)"}, 302 | {"type":"7seg","color":"#000000","bgColor":"#889988"} 303 | ], 304 | "devices":[ 305 | {"type":"NAND","numInputs":"3","label":"NAND(3in)","id":"dev0","x":208,"y":128}, 306 | {"type":"Toggle","id":"dev1","x":152,"y":80,"label":"Toggle"}, 307 | {"type":"Toggle","id":"dev2","x":152,"y":128,"label":"Toggle"}, 308 | {"type":"Toggle","id":"dev3","x":152,"y":176,"label":"Toggle"}, 309 | {"type":"DC","id":"dev4","x":96,"y":128,"label":"DC"}, 310 | {"type":"LED","color":"#00ff00","label":"LED(G)","id":"dev5","x":264,"y":128}, 311 | {"type":"OSC","freq":1,"label":"OSC(1Hz)","id":"dev6","x":96,"y":232}, 312 | {"type":"NAND","numInputs":"3","label":"NAND(3in)","id":"dev7","x":152,"y":272}, 313 | {"type":"7seg","color":"#000000","bgColor":"#889988","id":"dev8","x":208,"y":232,"label":"7seg"} 314 | ], 315 | "connectors":[ 316 | {"from":"dev0.in0","to":"dev1.out0"}, 317 | {"from":"dev0.in1","to":"dev2.out0"}, 318 | {"from":"dev0.in2","to":"dev3.out0"}, 319 | {"from":"dev1.in0","to":"dev4.out0"}, 320 | {"from":"dev2.in0","to":"dev4.out0"}, 321 | {"from":"dev3.in0","to":"dev4.out0"}, 322 | {"from":"dev5.in0","to":"dev0.out0"}, 323 | {"from":"dev7.in0","to":"dev6.out0"}, 324 | {"from":"dev7.in1","to":"dev6.out0"}, 325 | {"from":"dev7.in2","to":"dev6.out0"}, 326 | {"from":"dev8.in0","to":"dev6.out0"}, 327 | {"from":"dev8.in1","to":"dev7.out0"}, 328 | {"from":"dev8.in2","to":"dev7.out0"}, 329 | {"from":"dev8.in3","to":"dev6.out0"}, 330 | {"from":"dev8.in4","to":"dev7.out0"}, 331 | {"from":"dev8.in5","to":"dev7.out0"}, 332 | {"from":"dev8.in6","to":"dev6.out0"} 333 | ] 334 | } 335 |
336 | 337 |
338 | 339 |
340 |
341 | Contents Copyright © Kazuhiko Arase 342 |
343 | 344 | 345 | 346 | -------------------------------------------------------------------------------- /misc/simcir-dso.js: -------------------------------------------------------------------------------- 1 | // 2 | // SimcirJS - DSO 3 | // 4 | // Copyright (c) 2016 Kazuhiko Arase 5 | // 6 | // URL: http://www.d-project.com/ 7 | // 8 | // Licensed under the MIT license: 9 | // http://www.opensource.org/licenses/mit-license.php 10 | // 11 | 12 | // includes following device types: 13 | // DSO 14 | 15 | !function($s) { 16 | 17 | 'use strict'; 18 | 19 | var $ = $s.$; 20 | 21 | // unit size 22 | var unit = $s.unit; 23 | 24 | var createDSOFactory = function() { 25 | 26 | var colors = [ 27 | '#ff00cc', 28 | '#ffcc00', 29 | '#ccff00', 30 | '#00ffcc', 31 | '#00ccff', 32 | '#cc00ff' 33 | ]; 34 | var timeRanges = [10000, 5000, 2000, 1000]; 35 | var maxTimeRange = timeRanges[0]; 36 | 37 | var createProbe = function(color) { 38 | 39 | var samples = []; 40 | 41 | var model = { 42 | valueRange : 1, 43 | timeRange : maxTimeRange 44 | }; 45 | 46 | var $path = $s.createSVGElement('path'). 47 | css('fill', 'none'). 48 | css('stroke-width', 1). 49 | css('stroke-linejoin', 'bevel'). 50 | css('stroke', color); 51 | 52 | var setValueRange = function(valueRange) { 53 | model.valueRange = valueRange; 54 | }; 55 | 56 | var setTimeRange = function(timeRange) { 57 | model.timeRange = timeRange; 58 | }; 59 | 60 | var update = function(ts, x, y, width, height) { 61 | var d = ''; 62 | for (var i = samples.length - 1; i >= 0; i -= 1) { 63 | var last = i - 1 >= 0 && ts - samples[i - 1].ts > model.timeRange; 64 | var val = samples[i].value; 65 | if (!last && i > 0 && i + 1 < samples.length && 66 | samples[i - 1].value === val && 67 | samples[i + 1].value === val) { 68 | continue; 69 | } 70 | if (typeof val != 'number') { 71 | val = 0; 72 | } 73 | var sx = x + width - (ts - samples[i].ts) / model.timeRange * width; 74 | var sy = y + height - val / model.valueRange * height; 75 | d += d == ''? 'M' : 'L'; 76 | d += sx + ' ' + sy; 77 | if (last) { 78 | break; 79 | } 80 | } 81 | $path.attr('d', d); 82 | }; 83 | 84 | var sample = function(ts, value) { 85 | samples.push({ts: ts, value: value}); 86 | while (ts - samples[0].ts > maxTimeRange) { 87 | samples.shift(); 88 | } 89 | }; 90 | 91 | return { 92 | $ui : $path, 93 | setValueRange : setValueRange, 94 | setTimeRange : setTimeRange, 95 | update : update, 96 | sample : sample 97 | }; 98 | }; 99 | 100 | var createPanel = function() { 101 | 102 | var $lcd = $s.createSVGElement('path'). 103 | css('stroke', 'none').css('fill', '#ffcc00'); 104 | var setLCDText = function(text) { 105 | $lcd.attr('d', createFontPath(text, 4, 4, 1) ); 106 | }; 107 | var $lcdPanel = $s.createSVGElement('g'). 108 | append($s.createSVGElement('rect'). 109 | css('stroke', 'none'). 110 | css('fill', '#000000'). 111 | attr({x : 0, y : 0, width: unit * 7, height : unit}) ). 112 | append($lcd). 113 | on('mousedown', function(event) { 114 | event.preventDefault(); 115 | event.stopPropagation(); 116 | $panel.trigger('timeRangeDown'); 117 | }); 118 | $s.transform($lcdPanel, unit * 1.5, 0); 119 | 120 | var $playing = $s.createSVGElement('path'). 121 | attr('d', 'M' + unit / 4 + ' ' + unit / 4 + 122 | 'L' + unit / 4 * 3 + ' ' + unit / 2 + 123 | 'L' + unit / 4 + ' ' + unit / 4 * 3 + 'Z'). 124 | css('stroke-width', 1); 125 | var btnAttr = {x : 0, y : 0, width : unit, height : unit, 126 | rx : 1, ry : 1}; 127 | var $btnRect = $s.createSVGElement('rect'). 128 | attr(btnAttr). 129 | css('stroke', 'none'). 130 | css('fill', '#999999'). 131 | css('opacity', 0); 132 | var $btn = $s.createSVGElement('g'). 133 | append($btnRect). 134 | append($s.createSVGElement('rect'). 135 | attr(btnAttr). 136 | css('stroke-width', 1). 137 | css('stroke', '#666666'). 138 | css('fill', 'none') ). 139 | append($playing). 140 | on('mousedown', function(event) { 141 | event.preventDefault(); 142 | event.stopPropagation(); 143 | $panel.trigger('playDown'); 144 | }); 145 | 146 | var $panel = $s.createSVGElement('g'). 147 | append($btn).append($lcdPanel); 148 | 149 | return { 150 | $ui : $panel, 151 | setPlaying : function(playing) { 152 | $playing.css('fill', playing? '#00ff00' : '#006600'). 153 | css('stroke', playing? '#00cc00' : '#003300'); 154 | }, 155 | setTimeRange : function(timeRange) { 156 | var unit = 'ms'; 157 | if (timeRange > 5000) { 158 | unit = 's'; 159 | timeRange /= 1000; 160 | } 161 | setLCDText('TimeRange:' + timeRange + unit); 162 | } 163 | }; 164 | }; 165 | 166 | return function(device) { 167 | 168 | var numInputs = Math.max(1, 169 | device.deviceDef.numInputs || 4); 170 | var scale = 1; 171 | var gap = 2; 172 | 173 | for (var i = 0; i < numInputs; i += 1) { 174 | device.addInput(); 175 | } 176 | 177 | var state = device.deviceDef.state || 178 | { playing : true, rangeIndex : 0 }; 179 | device.getState = function() { 180 | return state; 181 | }; 182 | 183 | device.getSize = function() { 184 | return { width : unit * 4, 185 | height : unit * (numInputs * scale + 2) }; 186 | }; 187 | 188 | var super_createUI = device.createUI; 189 | device.createUI = function() { 190 | super_createUI(); 191 | 192 | var $display = $s.createSVGElement('g'); 193 | device.$ui.append($display); 194 | $s.transform($display, unit / 2, unit / 2); 195 | 196 | var $rect = $s.createSVGElement('rect'). 197 | css('stroke', 'none').css('fill', '#000000'). 198 | attr({x: 0, y: 0, width: unit * 3, 199 | height: unit * numInputs * scale }); 200 | $display.append($rect); 201 | 202 | var probes = []; 203 | for (var i = 0; i < device.getInputs().length; i += 1) { 204 | var inNode = device.getInputs()[i]; 205 | $s.transform(inNode.$ui, 0, unit * 206 | (0.5 + 0.5 * scale + i * scale) ); 207 | var probe = createProbe(colors[i % colors.length]); 208 | probes.push(probe); 209 | $display.append(probe.$ui); 210 | } 211 | 212 | var setTimeRange = function(timeRange) { 213 | panel.setTimeRange(timeRange); 214 | for (var i = 0; i < probes.length; i += 1) { 215 | probes[i].setTimeRange(timeRange); 216 | } 217 | }; 218 | 219 | var panel = createPanel(); 220 | panel.$ui.on('playDown', function(event){ 221 | state.playing = !state.playing; 222 | panel.setPlaying(state.playing); 223 | }).on('timeRangeDown', function(event) { 224 | state.rangeIndex = (state.rangeIndex + 1) % timeRanges.length; 225 | setTimeRange(timeRanges[state.rangeIndex]); 226 | }); 227 | device.$ui.append(panel.$ui.css('display', 'none') ); 228 | $s.transform(panel.$ui, unit / 2, 229 | unit * numInputs * scale + unit / 4 * 3); 230 | 231 | panel.setPlaying(state.playing); 232 | setTimeRange(timeRanges[state.rangeIndex] || timeRanges[0]); 233 | 234 | var alive = false; 235 | var render = function(ts) { 236 | for (var i = 0; i < device.getInputs().length; i += 1) { 237 | probes[i].sample(ts, device.getInputs()[i].getValue() ); 238 | if (state.playing) { 239 | probes[i].update(ts, 0, unit * i * scale + gap, 240 | unit * 15, unit * scale - gap * 2); 241 | } 242 | } 243 | if (alive) { 244 | window.requestAnimationFrame(render); 245 | } 246 | }; 247 | 248 | device.$ui.on('deviceAdd', function() { 249 | 250 | device.$ui.children('.simcir-device-body'). 251 | attr('width', unit * 16); 252 | device.$ui.children('.simcir-device-label'). 253 | attr('x', unit * 8); 254 | $rect.attr('width', unit * 15); 255 | panel.$ui.css('display', ''); 256 | 257 | alive = true; 258 | window.requestAnimationFrame(render); 259 | 260 | }).on('deviceRemove', function() { 261 | alive = false; 262 | }); 263 | 264 | device.doc = { 265 | params: [ 266 | {name: 'numInputs', type: 'number', 267 | defaultValue: 4, 268 | description: 'number of inputs.'} 269 | ], 270 | code: '{"type":"' + device.deviceDef.type + '","numInputs":4}' 271 | }; 272 | }; 273 | }; 274 | }; 275 | 276 | var createFontPath = function() { 277 | var data = { 278 | "\u0020":[0,0,0,0,0,0,0], 279 | "!":[4,4,4,4,0,0,4], 280 | "\"":[10,10,10,0,0,0,0], 281 | "#":[10,10,31,10,31,10,10], 282 | "$":[4,30,5,14,20,15,4], 283 | "%":[3,19,8,4,2,25,24], 284 | "&":[6,9,5,2,21,9,22], 285 | "'":[6,4,2,0,0,0,0], 286 | "(":[8,4,2,2,2,4,8], 287 | ")":[2,4,8,8,8,4,2], 288 | "*":[0,4,21,14,21,4,0], 289 | "+":[0,4,4,31,4,4,0], 290 | ",":[0,0,0,0,6,4,2], 291 | "-":[0,0,0,31,0,0,0], 292 | ".":[0,0,0,0,0,6,6], 293 | "/":[0,16,8,4,2,1,0], 294 | "0":[14,17,25,21,19,17,14], 295 | "1":[4,6,4,4,4,4,14], 296 | "2":[14,17,16,8,4,2,31], 297 | "3":[31,8,4,8,16,17,14], 298 | "4":[8,12,10,9,31,8,8], 299 | "5":[31,1,15,16,16,17,14], 300 | "6":[12,2,1,15,17,17,14], 301 | "7":[31,16,8,4,2,2,2], 302 | "8":[14,17,17,14,17,17,14], 303 | "9":[14,17,17,30,16,8,6], 304 | ":":[0,6,6,0,6,6,0], 305 | ";":[0,6,6,0,6,4,2], 306 | "<":[8,4,2,1,2,4,8], 307 | "=":[0,0,31,0,31,0,0], 308 | ">":[2,4,8,16,8,4,2], 309 | "?":[14,17,16,8,4,0,4], 310 | "@":[14,17,16,18,21,21,14], 311 | "A":[14,17,17,17,31,17,17], 312 | "B":[15,17,17,15,17,17,15], 313 | "C":[14,17,1,1,1,17,14], 314 | "D":[7,9,17,17,17,9,7], 315 | "E":[31,1,1,15,1,1,31], 316 | "F":[31,1,1,15,1,1,1], 317 | "G":[14,17,1,29,17,17,14], 318 | "H":[17,17,17,31,17,17,17], 319 | "I":[14,4,4,4,4,4,14], 320 | "J":[28,8,8,8,8,9,6], 321 | "K":[17,9,5,3,5,9,17], 322 | "L":[1,1,1,1,1,1,31], 323 | "M":[17,27,21,21,17,17,17], 324 | "N":[17,17,19,21,25,17,17], 325 | "O":[14,17,17,17,17,17,14], 326 | "P":[15,17,17,15,1,1,1], 327 | "Q":[14,17,17,17,21,9,22], 328 | "R":[15,17,17,15,5,9,17], 329 | "S":[30,1,1,14,16,16,15], 330 | "T":[31,4,4,4,4,4,4], 331 | "U":[17,17,17,17,17,17,14], 332 | "V":[17,17,17,17,17,10,4], 333 | "W":[17,17,17,21,21,21,10], 334 | "X":[17,17,10,4,10,17,17], 335 | "Y":[17,17,17,10,4,4,4], 336 | "Z":[31,16,8,4,2,1,31], 337 | "[":[14,2,2,2,2,2,14], 338 | "\\":[0,1,2,4,8,16,0], 339 | "]":[14,8,8,8,8,8,14], 340 | "^":[4,10,17,0,0,0,0], 341 | "_":[0,0,0,0,0,0,31], 342 | "`":[2,4,8,0,0,0,0], 343 | "a":[0,0,14,16,30,17,30], 344 | "b":[1,1,1,15,17,17,15], 345 | "c":[0,0,30,1,1,1,30], 346 | "d":[16,16,16,30,17,17,30], 347 | "e":[0,0,14,17,31,1,30], 348 | "f":[8,20,4,14,4,4,4], 349 | "g":[0,0,30,17,30,16,15], 350 | "h":[1,1,1,15,17,17,17], 351 | "i":[0,4,0,4,4,4,4], 352 | "j":[8,0,8,8,8,9,6], 353 | "k":[2,2,18,10,6,10,18], 354 | "l":[6,4,4,4,4,4,14], 355 | "m":[0,0,27,21,21,21,17], 356 | "n":[0,0,13,19,17,17,17], 357 | "o":[0,0,14,17,17,17,14], 358 | "p":[0,0,15,17,15,1,1], 359 | "q":[0,0,30,17,30,16,16], 360 | "r":[0,0,13,19,1,1,1], 361 | "s":[0,0,30,1,14,16,15], 362 | "t":[4,4,31,4,4,20,8], 363 | "u":[0,0,17,17,17,17,14], 364 | "v":[0,0,17,17,17,10,4], 365 | "w":[0,0,17,17,21,21,10], 366 | "x":[0,0,17,10,4,10,17], 367 | "y":[0,0,17,10,4,4,2], 368 | "z":[0,0,31,8,4,2,31], 369 | "{":[12,2,2,1,2,2,12], 370 | "|":[4,4,4,4,4,4,4], 371 | "}":[6,8,8,16,8,8,6], 372 | "~":[16,14,1,0,0,0,0] 373 | }; 374 | var getCharPath = function(c, x, y, s) { 375 | var d = ''; 376 | var cdata = data[c] || data['?']; 377 | for (var cy = 0; cy < cdata.length; cy += 1) { 378 | for (var cx = 0; cx < 5; cx += 1) { 379 | if ( (cdata[cy] >> cx) & 1) { 380 | d += 'M' + (x + cx) * s + ' ' + (y + cy) * s; 381 | d += 'L' + (x + cx + 1) * s + ' ' + (y + cy) * s; 382 | d += 'L' + (x + cx + 1) * s + ' ' + (y + cy + 1) * s; 383 | d += 'L' + (x + cx) * s + ' ' + (y + cy + 1) * s; 384 | d += 'Z'; 385 | } 386 | } 387 | } 388 | return d; 389 | }; 390 | return function(s, x, y, scale) { 391 | scale = scale || 1; 392 | var d = ''; 393 | for (var i = 0; i < s.length; i += 1) { 394 | d += getCharPath(s.charAt(i), x + i * 6, y, scale); 395 | } 396 | return d; 397 | }; 398 | }(); 399 | 400 | $s.registerDevice('DSO', createDSOFactory() ); 401 | 402 | }(simcir); 403 | -------------------------------------------------------------------------------- /simcir-library.js: -------------------------------------------------------------------------------- 1 | // 2 | // SimcirJS - library 3 | // 4 | // Copyright (c) 2014 Kazuhiko Arase 5 | // 6 | // URL: http://www.d-project.com/ 7 | // 8 | // Licensed under the MIT license: 9 | // http://www.opensource.org/licenses/mit-license.php 10 | // 11 | 12 | // includes following device types: 13 | // RS-FF 14 | // JK-FF 15 | // T-FF 16 | // D-FF 17 | // 8bitCounter 18 | // HalfAdder 19 | // FullAdder 20 | // 4bitAdder 21 | // 2to4BinaryDecoder 22 | // 3to8BinaryDecoder 23 | // 4to16BinaryDecoder 24 | 25 | simcir.registerDevice('RS-FF', 26 | { 27 | "width":320, 28 | "height":160, 29 | "showToolbox":false, 30 | "toolbox":[ 31 | ], 32 | "devices":[ 33 | {"type":"NAND","id":"dev0","x":184,"y":32,"label":"NAND"}, 34 | {"type":"NAND","id":"dev1","x":184,"y":80,"label":"NAND"}, 35 | {"type":"In","id":"dev2","x":136,"y":24,"label":"~S"}, 36 | {"type":"In","id":"dev3","x":136,"y":88,"label":"~R"}, 37 | {"type":"Out","id":"dev4","x":232,"y":32,"label":"Q"}, 38 | {"type":"Out","id":"dev5","x":232,"y":80,"label":"~Q"}, 39 | {"type":"PushOff","id":"dev6","x":88,"y":24,"label":"PushOff"}, 40 | {"type":"PushOff","id":"dev7","x":88,"y":88,"label":"PushOff"}, 41 | {"type":"DC","id":"dev8","x":40,"y":56,"label":"DC"} 42 | ], 43 | "connectors":[ 44 | {"from":"dev0.in0","to":"dev2.out0"}, 45 | {"from":"dev0.in1","to":"dev1.out0"}, 46 | {"from":"dev1.in0","to":"dev0.out0"}, 47 | {"from":"dev1.in1","to":"dev3.out0"}, 48 | {"from":"dev2.in0","to":"dev6.out0"}, 49 | {"from":"dev3.in0","to":"dev7.out0"}, 50 | {"from":"dev4.in0","to":"dev0.out0"}, 51 | {"from":"dev5.in0","to":"dev1.out0"}, 52 | {"from":"dev6.in0","to":"dev8.out0"}, 53 | {"from":"dev7.in0","to":"dev8.out0"} 54 | ] 55 | } 56 | ); 57 | 58 | simcir.registerDevice('JK-FF', 59 | { 60 | "width":480, 61 | "height":240, 62 | "showToolbox":false, 63 | "toolbox":[ 64 | ], 65 | "devices":[ 66 | {"type":"RS-FF","id":"dev0","x":216,"y":112,"label":"RS-FF"}, 67 | {"type":"RS-FF","id":"dev1","x":344,"y":112,"label":"RS-FF"}, 68 | {"type":"NAND","numInputs":3,"id":"dev2","x":168,"y":80,"label":"NAND"}, 69 | {"type":"NAND","numInputs":3,"id":"dev3","x":168,"y":144,"label":"NAND"}, 70 | {"type":"NAND","id":"dev4","x":296,"y":80,"label":"NAND"}, 71 | {"type":"NAND","id":"dev5","x":296,"y":144,"label":"NAND"}, 72 | {"type":"NOT","id":"dev6","x":168,"y":24,"label":"NOT"}, 73 | {"type":"In","id":"dev7","x":120,"y":64,"label":"J"}, 74 | {"type":"In","id":"dev8","x":120,"y":112,"label":"CLK"}, 75 | {"type":"In","id":"dev9","x":120,"y":160,"label":"K"}, 76 | {"type":"Out","id":"dev10","x":424,"y":80,"label":"Q"}, 77 | {"type":"Out","id":"dev11","x":424,"y":144,"label":"~Q"}, 78 | {"type":"Toggle","id":"dev12","x":72,"y":64,"label":"Toggle"}, 79 | {"type":"PushOn","id":"dev13","x":72,"y":112,"label":"PushOn"}, 80 | {"type":"Toggle","id":"dev14","x":72,"y":160,"label":"Toggle"}, 81 | {"type":"DC","id":"dev15","x":24,"y":112,"label":"DC"} 82 | ], 83 | "connectors":[ 84 | {"from":"dev0.in0","to":"dev2.out0"}, 85 | {"from":"dev0.in1","to":"dev3.out0"}, 86 | {"from":"dev1.in0","to":"dev4.out0"}, 87 | {"from":"dev1.in1","to":"dev5.out0"}, 88 | {"from":"dev2.in0","to":"dev1.out1"}, 89 | {"from":"dev2.in1","to":"dev7.out0"}, 90 | {"from":"dev2.in2","to":"dev8.out0"}, 91 | {"from":"dev3.in0","to":"dev8.out0"}, 92 | {"from":"dev3.in1","to":"dev9.out0"}, 93 | {"from":"dev3.in2","to":"dev1.out0"}, 94 | {"from":"dev4.in0","to":"dev6.out0"}, 95 | {"from":"dev4.in1","to":"dev0.out0"}, 96 | {"from":"dev5.in0","to":"dev0.out1"}, 97 | {"from":"dev5.in1","to":"dev6.out0"}, 98 | {"from":"dev6.in0","to":"dev8.out0"}, 99 | {"from":"dev7.in0","to":"dev12.out0"}, 100 | {"from":"dev8.in0","to":"dev13.out0"}, 101 | {"from":"dev9.in0","to":"dev14.out0"}, 102 | {"from":"dev10.in0","to":"dev1.out0"}, 103 | {"from":"dev11.in0","to":"dev1.out1"}, 104 | {"from":"dev12.in0","to":"dev15.out0"}, 105 | {"from":"dev13.in0","to":"dev15.out0"}, 106 | {"from":"dev14.in0","to":"dev15.out0"} 107 | ] 108 | } 109 | ); 110 | 111 | simcir.registerDevice('T-FF', 112 | { 113 | "width":320, 114 | "height":160, 115 | "showToolbox":false, 116 | "toolbox":[ 117 | ], 118 | "devices":[ 119 | {"type":"JK-FF","id":"dev0","x":168,"y":48,"label":"JK-FF"}, 120 | {"type":"In","id":"dev1","x":120,"y":32,"label":"T"}, 121 | {"type":"In","id":"dev2","x":120,"y":80,"label":"CLK"}, 122 | {"type":"Out","id":"dev3","x":248,"y":32,"label":"Q"}, 123 | {"type":"Out","id":"dev4","x":248,"y":80,"label":"~Q"}, 124 | {"type":"Toggle","id":"dev5","x":72,"y":32,"label":"Toggle"}, 125 | {"type":"PushOn","id":"dev6","x":72,"y":80,"label":"PushOn"}, 126 | {"type":"DC","id":"dev7","x":24,"y":56,"label":"DC"} 127 | ], 128 | "connectors":[ 129 | {"from":"dev0.in0","to":"dev1.out0"}, 130 | {"from":"dev0.in1","to":"dev2.out0"}, 131 | {"from":"dev0.in2","to":"dev1.out0"}, 132 | {"from":"dev1.in0","to":"dev5.out0"}, 133 | {"from":"dev2.in0","to":"dev6.out0"}, 134 | {"from":"dev3.in0","to":"dev0.out0"}, 135 | {"from":"dev4.in0","to":"dev0.out1"}, 136 | {"from":"dev5.in0","to":"dev7.out0"}, 137 | {"from":"dev6.in0","to":"dev7.out0"} 138 | ] 139 | } 140 | ); 141 | 142 | simcir.registerDevice('D-FF', 143 | { 144 | "width":540, 145 | "height":200, 146 | "showToolbox":false, 147 | "toolbox":[ 148 | ], 149 | "devices":[ 150 | {"type":"In","id":"dev0","x":128,"y":24,"label":"D"}, 151 | {"type":"In","id":"dev1","x":168,"y":128,"label":"CLK"}, 152 | {"type":"NOT","id":"dev2","x":176,"y":64,"label":"NOT"}, 153 | {"type":"NAND","id":"dev3","x":224,"y":32,"label":"NAND"}, 154 | {"type":"NAND","id":"dev4","x":224,"y":96,"label":"NAND"}, 155 | {"type":"RS-FF","id":"dev5","x":272,"y":64,"label":"RS-FF"}, 156 | {"type":"NOT","id":"dev6","x":296,"y":128,"label":"NOT"}, 157 | {"type":"NAND","id":"dev7","x":352,"y":32,"label":"NAND"}, 158 | {"type":"NAND","id":"dev8","x":352,"y":96,"label":"NAND"}, 159 | {"type":"RS-FF","id":"dev9","x":400,"y":64,"label":"RS-FF"}, 160 | {"type":"Out","id":"dev10","x":480,"y":32,"label":"Q"}, 161 | {"type":"Out","id":"dev11","x":480,"y":96,"label":"~Q"}, 162 | {"type":"Toggle","id":"dev12","x":80,"y":24,"label":"Toggle"}, 163 | {"type":"PushOn","id":"dev13","x":80,"y":128,"label":"PushOn"}, 164 | {"type":"DC","id":"dev14","x":32,"y":72,"label":"DC"} 165 | ], 166 | "connectors":[ 167 | {"from":"dev0.in0","to":"dev12.out0"}, 168 | {"from":"dev1.in0","to":"dev13.out0"}, 169 | {"from":"dev2.in0","to":"dev0.out0"}, 170 | {"from":"dev3.in0","to":"dev0.out0"}, 171 | {"from":"dev3.in1","to":"dev1.out0"}, 172 | {"from":"dev4.in0","to":"dev1.out0"}, 173 | {"from":"dev4.in1","to":"dev2.out0"}, 174 | {"from":"dev5.in0","to":"dev3.out0"}, 175 | {"from":"dev5.in1","to":"dev4.out0"}, 176 | {"from":"dev6.in0","to":"dev1.out0"}, 177 | {"from":"dev7.in0","to":"dev5.out0"}, 178 | {"from":"dev7.in1","to":"dev6.out0"}, 179 | {"from":"dev8.in0","to":"dev6.out0"}, 180 | {"from":"dev8.in1","to":"dev5.out1"}, 181 | {"from":"dev9.in0","to":"dev7.out0"}, 182 | {"from":"dev9.in1","to":"dev8.out0"}, 183 | {"from":"dev10.in0","to":"dev9.out0"}, 184 | {"from":"dev11.in0","to":"dev9.out1"}, 185 | {"from":"dev12.in0","to":"dev14.out0"}, 186 | {"from":"dev13.in0","to":"dev14.out0"} 187 | ] 188 | } 189 | ); 190 | 191 | simcir.registerDevice('8bitCounter', 192 | { 193 | "width":320, 194 | "height":420, 195 | "showToolbox":false, 196 | "toolbox":[ 197 | ], 198 | "devices":[ 199 | {"type":"T-FF","id":"dev0","x":184,"y":16,"label":"T-FF"}, 200 | {"type":"T-FF","id":"dev1","x":184,"y":64,"label":"T-FF"}, 201 | {"type":"T-FF","id":"dev2","x":184,"y":112,"label":"T-FF"}, 202 | {"type":"T-FF","id":"dev3","x":184,"y":160,"label":"T-FF"}, 203 | {"type":"T-FF","id":"dev4","x":184,"y":208,"label":"T-FF"}, 204 | {"type":"T-FF","id":"dev5","x":184,"y":256,"label":"T-FF"}, 205 | {"type":"T-FF","id":"dev6","x":184,"y":304,"label":"T-FF"}, 206 | {"type":"T-FF","id":"dev7","x":184,"y":352,"label":"T-FF"}, 207 | {"type":"Out","id":"dev8","x":264,"y":16,"label":"D0"}, 208 | {"type":"Out","id":"dev9","x":264,"y":64,"label":"D1"}, 209 | {"type":"Out","id":"dev10","x":264,"y":112,"label":"D2"}, 210 | {"type":"Out","id":"dev11","x":264,"y":160,"label":"D3"}, 211 | {"type":"Out","id":"dev12","x":264,"y":208,"label":"D4"}, 212 | {"type":"Out","id":"dev13","x":264,"y":256,"label":"D5"}, 213 | {"type":"Out","id":"dev14","x":264,"y":304,"label":"D6"}, 214 | {"type":"Out","id":"dev15","x":264,"y":352,"label":"D7"}, 215 | {"type":"In","id":"dev16","x":120,"y":16,"label":"T"}, 216 | {"type":"In","id":"dev17","x":120,"y":112,"label":"CLK"}, 217 | {"type":"PushOn","id":"dev18","x":72,"y":112,"label":"PushOn"}, 218 | {"type":"DC","id":"dev19","x":24,"y":16,"label":"DC"}, 219 | {"type":"Toggle","id":"dev20","x":72,"y":16,"label":"Toggle"} 220 | ], 221 | "connectors":[ 222 | {"from":"dev0.in0","to":"dev16.out0"}, 223 | {"from":"dev0.in1","to":"dev17.out0"}, 224 | {"from":"dev1.in0","to":"dev16.out0"}, 225 | {"from":"dev1.in1","to":"dev0.out0"}, 226 | {"from":"dev2.in0","to":"dev16.out0"}, 227 | {"from":"dev2.in1","to":"dev1.out0"}, 228 | {"from":"dev3.in0","to":"dev16.out0"}, 229 | {"from":"dev3.in1","to":"dev2.out0"}, 230 | {"from":"dev4.in0","to":"dev16.out0"}, 231 | {"from":"dev4.in1","to":"dev3.out0"}, 232 | {"from":"dev5.in0","to":"dev16.out0"}, 233 | {"from":"dev5.in1","to":"dev4.out0"}, 234 | {"from":"dev6.in0","to":"dev16.out0"}, 235 | {"from":"dev6.in1","to":"dev5.out0"}, 236 | {"from":"dev7.in0","to":"dev16.out0"}, 237 | {"from":"dev7.in1","to":"dev6.out0"}, 238 | {"from":"dev8.in0","to":"dev0.out0"}, 239 | {"from":"dev9.in0","to":"dev1.out0"}, 240 | {"from":"dev10.in0","to":"dev2.out0"}, 241 | {"from":"dev11.in0","to":"dev3.out0"}, 242 | {"from":"dev12.in0","to":"dev4.out0"}, 243 | {"from":"dev13.in0","to":"dev5.out0"}, 244 | {"from":"dev14.in0","to":"dev6.out0"}, 245 | {"from":"dev15.in0","to":"dev7.out0"}, 246 | {"from":"dev16.in0","to":"dev20.out0"}, 247 | {"from":"dev17.in0","to":"dev18.out0"}, 248 | {"from":"dev18.in0","to":"dev19.out0"}, 249 | {"from":"dev20.in0","to":"dev19.out0"} 250 | ] 251 | } 252 | ); 253 | 254 | simcir.registerDevice('HalfAdder', 255 | { 256 | "width":320, 257 | "height":160, 258 | "showToolbox":false, 259 | "toolbox":[ 260 | ], 261 | "devices":[ 262 | {"type":"Toggle","id":"dev0","x":96,"y":80,"label":"Toggle"}, 263 | {"type":"DC","id":"dev1","x":48,"y":56,"label":"DC"}, 264 | {"type":"AND","id":"dev2","x":192,"y":80,"label":"AND"}, 265 | {"type":"XOR","id":"dev3","x":192,"y":32,"label":"XOR"}, 266 | {"type":"In","id":"dev4","x":144,"y":32,"label":"A"}, 267 | {"type":"In","id":"dev5","x":144,"y":80,"label":"B"}, 268 | {"type":"Out","id":"dev6","x":240,"y":32,"label":"S"}, 269 | {"type":"Out","id":"dev7","x":240,"y":80,"label":"C"}, 270 | {"type":"Toggle","id":"dev8","x":96,"y":32,"label":"Toggle"} 271 | ], 272 | "connectors":[ 273 | {"from":"dev0.in0","to":"dev1.out0"}, 274 | {"from":"dev2.in0","to":"dev4.out0"}, 275 | {"from":"dev2.in1","to":"dev5.out0"}, 276 | {"from":"dev3.in0","to":"dev4.out0"}, 277 | {"from":"dev3.in1","to":"dev5.out0"}, 278 | {"from":"dev4.in0","to":"dev8.out0"}, 279 | {"from":"dev5.in0","to":"dev0.out0"}, 280 | {"from":"dev6.in0","to":"dev3.out0"}, 281 | {"from":"dev7.in0","to":"dev2.out0"}, 282 | {"from":"dev8.in0","to":"dev1.out0"} 283 | ] 284 | } 285 | ); 286 | 287 | simcir.registerDevice('FullAdder', 288 | { 289 | "width":440, 290 | "height":200, 291 | "showToolbox":false, 292 | "toolbox":[ 293 | ], 294 | "devices":[ 295 | {"type":"In","id":"dev0","x":120,"y":32,"label":"Cin"}, 296 | {"type":"In","id":"dev1","x":120,"y":80,"label":"A"}, 297 | {"type":"In","id":"dev2","x":120,"y":128,"label":"B"}, 298 | {"type":"Toggle","id":"dev3","x":72,"y":32,"label":"Toggle"}, 299 | {"type":"Toggle","id":"dev4","x":72,"y":80,"label":"Toggle"}, 300 | {"type":"Toggle","id":"dev5","x":72,"y":128,"label":"Toggle"}, 301 | {"type":"DC","id":"dev6","x":24,"y":80,"label":"DC"}, 302 | {"type":"HalfAdder","id":"dev7","x":168,"y":104,"label":"HalfAdder"}, 303 | {"type":"HalfAdder","id":"dev8","x":248,"y":56,"label":"HalfAdder"}, 304 | {"type":"OR","id":"dev9","x":328,"y":104,"label":"OR"}, 305 | {"type":"Out","id":"dev10","x":376,"y":104,"label":"Cout"}, 306 | {"type":"Out","id":"dev11","x":376,"y":48,"label":"S"} 307 | ], 308 | "connectors":[ 309 | {"from":"dev0.in0","to":"dev3.out0"}, 310 | {"from":"dev1.in0","to":"dev4.out0"}, 311 | {"from":"dev2.in0","to":"dev5.out0"}, 312 | {"from":"dev3.in0","to":"dev6.out0"}, 313 | {"from":"dev4.in0","to":"dev6.out0"}, 314 | {"from":"dev5.in0","to":"dev6.out0"}, 315 | {"from":"dev7.in0","to":"dev1.out0"}, 316 | {"from":"dev7.in1","to":"dev2.out0"}, 317 | {"from":"dev8.in0","to":"dev0.out0"}, 318 | {"from":"dev8.in1","to":"dev7.out0"}, 319 | {"from":"dev9.in0","to":"dev8.out1"}, 320 | {"from":"dev9.in1","to":"dev7.out1"}, 321 | {"from":"dev10.in0","to":"dev9.out0"}, 322 | {"from":"dev11.in0","to":"dev8.out0"} 323 | ] 324 | } 325 | ); 326 | 327 | simcir.registerDevice('4bitAdder', 328 | { 329 | "width":280, 330 | "height":480, 331 | "showToolbox":false, 332 | "toolbox":[ 333 | ], 334 | "devices":[ 335 | {"type":"FullAdder","id":"dev0","x":120,"y":72,"label":"FullAdder"}, 336 | {"type":"FullAdder","id":"dev1","x":120,"y":136,"label":"FullAdder"}, 337 | {"type":"FullAdder","id":"dev2","x":120,"y":200,"label":"FullAdder"}, 338 | {"type":"FullAdder","id":"dev3","x":120,"y":264,"label":"FullAdder"}, 339 | {"type":"In","id":"dev4","x":40,"y":80,"label":"A0"}, 340 | {"type":"In","id":"dev5","x":40,"y":128,"label":"A1"}, 341 | {"type":"In","id":"dev6","x":40,"y":176,"label":"A2"}, 342 | {"type":"In","id":"dev7","x":40,"y":224,"label":"A3"}, 343 | {"type":"In","id":"dev8","x":40,"y":272,"label":"B0"}, 344 | {"type":"In","id":"dev9","x":40,"y":320,"label":"B1"}, 345 | {"type":"In","id":"dev10","x":40,"y":368,"label":"B2"}, 346 | {"type":"In","id":"dev11","x":40,"y":416,"label":"B3"}, 347 | {"type":"Out","id":"dev12","x":200,"y":72,"label":"S0"}, 348 | {"type":"Out","id":"dev13","x":200,"y":120,"label":"S1"}, 349 | {"type":"Out","id":"dev14","x":200,"y":168,"label":"S2"}, 350 | {"type":"Out","id":"dev15","x":200,"y":216,"label":"S3"}, 351 | {"type":"Out","id":"dev16","x":200,"y":280,"label":"Cout"}, 352 | {"type":"In","id":"dev17","x":40,"y":24,"label":"Cin"} 353 | ], 354 | "connectors":[ 355 | {"from":"dev0.in0","to":"dev17.out0"}, 356 | {"from":"dev0.in1","to":"dev4.out0"}, 357 | {"from":"dev0.in2","to":"dev8.out0"}, 358 | {"from":"dev1.in0","to":"dev0.out1"}, 359 | {"from":"dev1.in1","to":"dev5.out0"}, 360 | {"from":"dev1.in2","to":"dev9.out0"}, 361 | {"from":"dev2.in0","to":"dev1.out1"}, 362 | {"from":"dev2.in1","to":"dev6.out0"}, 363 | {"from":"dev2.in2","to":"dev10.out0"}, 364 | {"from":"dev3.in0","to":"dev2.out1"}, 365 | {"from":"dev3.in1","to":"dev7.out0"}, 366 | {"from":"dev3.in2","to":"dev11.out0"}, 367 | {"from":"dev12.in0","to":"dev0.out0"}, 368 | {"from":"dev13.in0","to":"dev1.out0"}, 369 | {"from":"dev14.in0","to":"dev2.out0"}, 370 | {"from":"dev15.in0","to":"dev3.out0"}, 371 | {"from":"dev16.in0","to":"dev3.out1"} 372 | ] 373 | } 374 | ); 375 | 376 | simcir.registerDevice('2to4BinaryDecoder', 377 | { 378 | "width":400, 379 | "height":240, 380 | "showToolbox":false, 381 | "toolbox":[ 382 | ], 383 | "devices":[ 384 | {"type":"AND","numInputs":3,"id":"dev0","x":280,"y":24,"label":"AND"}, 385 | {"type":"AND","numInputs":3,"id":"dev1","x":280,"y":72,"label":"AND"}, 386 | {"type":"AND","numInputs":3,"id":"dev2","x":280,"y":120,"label":"AND"}, 387 | {"type":"NOT","id":"dev3","x":192,"y":48,"label":"NOT"}, 388 | {"type":"AND","numInputs":3,"id":"dev4","x":280,"y":168,"label":"AND"}, 389 | {"type":"NOT","id":"dev5","x":192,"y":96,"label":"NOT"}, 390 | {"type":"In","id":"dev6","x":192,"y":176,"label":"OE"}, 391 | {"type":"In","id":"dev7","x":128,"y":48,"label":"D0"}, 392 | {"type":"In","id":"dev8","x":128,"y":96,"label":"D1"}, 393 | {"type":"Toggle","id":"dev9","x":80,"y":48,"label":"Toggle"}, 394 | {"type":"Toggle","id":"dev10","x":80,"y":96,"label":"Toggle"}, 395 | {"type":"DC","id":"dev11","x":32,"y":96,"label":"DC"}, 396 | {"type":"Out","id":"dev12","x":328,"y":24,"label":"A0"}, 397 | {"type":"Out","id":"dev13","x":328,"y":72,"label":"A1"}, 398 | {"type":"Out","id":"dev14","x":328,"y":120,"label":"A2"}, 399 | {"type":"Out","id":"dev15","x":328,"y":168,"label":"A3"}, 400 | {"type":"Toggle","id":"dev16","x":80,"y":144,"label":"Toggle"} 401 | ], 402 | "connectors":[ 403 | {"from":"dev0.in0","to":"dev3.out0"}, 404 | {"from":"dev0.in1","to":"dev5.out0"}, 405 | {"from":"dev0.in2","to":"dev6.out0"}, 406 | {"from":"dev1.in0","to":"dev7.out0"}, 407 | {"from":"dev1.in1","to":"dev5.out0"}, 408 | {"from":"dev1.in2","to":"dev6.out0"}, 409 | {"from":"dev2.in0","to":"dev3.out0"}, 410 | {"from":"dev2.in1","to":"dev8.out0"}, 411 | {"from":"dev2.in2","to":"dev6.out0"}, 412 | {"from":"dev3.in0","to":"dev7.out0"}, 413 | {"from":"dev4.in0","to":"dev7.out0"}, 414 | {"from":"dev4.in1","to":"dev8.out0"}, 415 | {"from":"dev4.in2","to":"dev6.out0"}, 416 | {"from":"dev5.in0","to":"dev8.out0"}, 417 | {"from":"dev6.in0","to":"dev16.out0"}, 418 | {"from":"dev7.in0","to":"dev9.out0"}, 419 | {"from":"dev8.in0","to":"dev10.out0"}, 420 | {"from":"dev9.in0","to":"dev11.out0"}, 421 | {"from":"dev10.in0","to":"dev11.out0"}, 422 | {"from":"dev12.in0","to":"dev0.out0"}, 423 | {"from":"dev13.in0","to":"dev1.out0"}, 424 | {"from":"dev14.in0","to":"dev2.out0"}, 425 | {"from":"dev15.in0","to":"dev4.out0"}, 426 | {"from":"dev16.in0","to":"dev11.out0"} 427 | ] 428 | } 429 | ); 430 | 431 | simcir.registerDevice('3to8BinaryDecoder', 432 | { 433 | "width":360, 434 | "height":440, 435 | "showToolbox":false, 436 | "toolbox":[ 437 | ], 438 | "devices":[ 439 | {"type":"In","id":"dev0","x":24,"y":144,"label":"D0"}, 440 | {"type":"In","id":"dev1","x":24,"y":192,"label":"D1"}, 441 | {"type":"In","id":"dev2","x":24,"y":240,"label":"D2"}, 442 | {"type":"In","id":"dev3","x":24,"y":304,"label":"OE"}, 443 | {"type":"NOT","id":"dev4","x":72,"y":240,"label":"NOT"}, 444 | {"type":"AND","id":"dev5","x":120,"y":248,"label":"AND"}, 445 | {"type":"AND","id":"dev6","x":120,"y":296,"label":"AND"}, 446 | {"type":"2to4BinaryDecoder","id":"dev7","x":184,"y":144,"label":"2to4BinaryDecoder"}, 447 | {"type":"2to4BinaryDecoder","id":"dev8","x":184,"y":224,"label":"2to4BinaryDecoder"}, 448 | {"type":"Out","id":"dev9","x":296,"y":32,"label":"A0"}, 449 | {"type":"Out","id":"dev10","x":296,"y":80,"label":"A1"}, 450 | {"type":"Out","id":"dev11","x":296,"y":128,"label":"A2"}, 451 | {"type":"Out","id":"dev12","x":296,"y":176,"label":"A3"}, 452 | {"type":"Out","id":"dev13","x":296,"y":224,"label":"A4"}, 453 | {"type":"Out","id":"dev14","x":296,"y":272,"label":"A5"}, 454 | {"type":"Out","id":"dev15","x":296,"y":320,"label":"A6"}, 455 | {"type":"Out","id":"dev16","x":296,"y":368,"label":"A7"} 456 | ], 457 | "connectors":[ 458 | {"from":"dev4.in0","to":"dev2.out0"}, 459 | {"from":"dev5.in0","to":"dev4.out0"}, 460 | {"from":"dev5.in1","to":"dev3.out0"}, 461 | {"from":"dev6.in0","to":"dev2.out0"}, 462 | {"from":"dev6.in1","to":"dev3.out0"}, 463 | {"from":"dev7.in0","to":"dev0.out0"}, 464 | {"from":"dev7.in1","to":"dev1.out0"}, 465 | {"from":"dev7.in2","to":"dev5.out0"}, 466 | {"from":"dev8.in0","to":"dev0.out0"}, 467 | {"from":"dev8.in1","to":"dev1.out0"}, 468 | {"from":"dev8.in2","to":"dev6.out0"}, 469 | {"from":"dev9.in0","to":"dev7.out0"}, 470 | {"from":"dev10.in0","to":"dev7.out1"}, 471 | {"from":"dev11.in0","to":"dev7.out2"}, 472 | {"from":"dev12.in0","to":"dev7.out3"}, 473 | {"from":"dev13.in0","to":"dev8.out0"}, 474 | {"from":"dev14.in0","to":"dev8.out1"}, 475 | {"from":"dev15.in0","to":"dev8.out2"}, 476 | {"from":"dev16.in0","to":"dev8.out3"} 477 | ] 478 | } 479 | ); 480 | 481 | simcir.registerDevice('4to16BinaryDecoder', 482 | { 483 | "width":440, 484 | "height":360, 485 | "showToolbox":false, 486 | "toolbox":[ 487 | ], 488 | "devices":[ 489 | {"type":"In","id":"dev0","x":32,"y":56,"label":"D0"}, 490 | {"type":"In","id":"dev1","x":32,"y":104,"label":"D1"}, 491 | {"type":"In","id":"dev2","x":32,"y":152,"label":"D2"}, 492 | {"type":"In","id":"dev3","x":32,"y":200,"label":"D3"}, 493 | {"type":"In","id":"dev4","x":32,"y":264,"label":"OE"}, 494 | {"type":"NOT","id":"dev5","x":80,"y":200,"label":"NOT"}, 495 | {"type":"AND","id":"dev6","x":136,"y":208,"label":"AND"}, 496 | {"type":"AND","id":"dev7","x":136,"y":256,"label":"AND"}, 497 | {"type":"3to8BinaryDecoder","id":"dev8","x":208,"y":32,"label":"3to8BinaryDecoder"}, 498 | {"type":"3to8BinaryDecoder","id":"dev9","x":208,"y":184,"label":"3to8BinaryDecoder"}, 499 | {"type":"BusOut","id":"dev10","x":320,"y":88,"label":"BusOut"}, 500 | {"type":"BusOut","id":"dev11","x":320,"y":184,"label":"BusOut"}, 501 | {"type":"Out","id":"dev12","x":376,"y":128,"label":"A0"}, 502 | {"type":"Out","id":"dev13","x":376,"y":184,"label":"A1"} 503 | ], 504 | "connectors":[ 505 | {"from":"dev5.in0","to":"dev3.out0"}, 506 | {"from":"dev6.in0","to":"dev5.out0"}, 507 | {"from":"dev6.in1","to":"dev4.out0"}, 508 | {"from":"dev7.in0","to":"dev3.out0"}, 509 | {"from":"dev7.in1","to":"dev4.out0"}, 510 | {"from":"dev8.in0","to":"dev0.out0"}, 511 | {"from":"dev8.in1","to":"dev1.out0"}, 512 | {"from":"dev8.in2","to":"dev2.out0"}, 513 | {"from":"dev8.in3","to":"dev6.out0"}, 514 | {"from":"dev9.in0","to":"dev0.out0"}, 515 | {"from":"dev9.in1","to":"dev1.out0"}, 516 | {"from":"dev9.in2","to":"dev2.out0"}, 517 | {"from":"dev9.in3","to":"dev7.out0"}, 518 | {"from":"dev10.in0","to":"dev8.out0"}, 519 | {"from":"dev10.in1","to":"dev8.out1"}, 520 | {"from":"dev10.in2","to":"dev8.out2"}, 521 | {"from":"dev10.in3","to":"dev8.out3"}, 522 | {"from":"dev10.in4","to":"dev8.out4"}, 523 | {"from":"dev10.in5","to":"dev8.out5"}, 524 | {"from":"dev10.in6","to":"dev8.out6"}, 525 | {"from":"dev10.in7","to":"dev8.out7"}, 526 | {"from":"dev11.in0","to":"dev9.out0"}, 527 | {"from":"dev11.in1","to":"dev9.out1"}, 528 | {"from":"dev11.in2","to":"dev9.out2"}, 529 | {"from":"dev11.in3","to":"dev9.out3"}, 530 | {"from":"dev11.in4","to":"dev9.out4"}, 531 | {"from":"dev11.in5","to":"dev9.out5"}, 532 | {"from":"dev11.in6","to":"dev9.out6"}, 533 | {"from":"dev11.in7","to":"dev9.out7"}, 534 | {"from":"dev12.in0","to":"dev10.out0"}, 535 | {"from":"dev13.in0","to":"dev11.out0"} 536 | ] 537 | } 538 | ); 539 | -------------------------------------------------------------------------------- /simcir-basicset.js: -------------------------------------------------------------------------------- 1 | // 2 | // SimcirJS - basicset 3 | // 4 | // Copyright (c) 2014 Kazuhiko Arase 5 | // 6 | // URL: http://www.d-project.com/ 7 | // 8 | // Licensed under the MIT license: 9 | // http://www.opensource.org/licenses/mit-license.php 10 | // 11 | 12 | // includes following device types: 13 | // DC 14 | // LED 15 | // PushOff 16 | // PushOn 17 | // Toggle 18 | // BUF 19 | // NOT 20 | // AND 21 | // NAND 22 | // OR 23 | // NOR 24 | // EOR 25 | // ENOR 26 | // OSC 27 | // 7seg 28 | // 16seg 29 | // 4bit7seg 30 | // RotaryEncoder 31 | // BusIn 32 | // BusOut 33 | 34 | !function($s) { 35 | 36 | 'use strict'; 37 | 38 | var $ = $s.$; 39 | 40 | // unit size 41 | var unit = $s.unit; 42 | 43 | // red/black 44 | var defaultLEDColor = '#ff0000'; 45 | var defaultLEDBgColor = '#000000'; 46 | 47 | var multiplyColor = function() { 48 | var HEX = '0123456789abcdef'; 49 | var toIColor = function(sColor) { 50 | if (!sColor) { 51 | return 0; 52 | } 53 | sColor = sColor.toLowerCase(); 54 | if (sColor.match(/^#[0-9a-f]{3}$/i) ) { 55 | var iColor = 0; 56 | for (var i = 0; i < 6; i += 1) { 57 | iColor = (iColor << 4) | HEX.indexOf(sColor.charAt( (i >> 1) + 1) ); 58 | } 59 | return iColor; 60 | } else if (sColor.match(/^#[0-9a-f]{6}$/i) ) { 61 | var iColor = 0; 62 | for (var i = 0; i < 6; i += 1) { 63 | iColor = (iColor << 4) | HEX.indexOf(sColor.charAt(i + 1) ); 64 | } 65 | return iColor; 66 | } 67 | return 0; 68 | }; 69 | var toSColor = function(iColor) { 70 | var sColor = '#'; 71 | for (var i = 0; i < 6; i += 1) { 72 | sColor += HEX.charAt( (iColor >>> (5 - i) * 4) & 0x0f); 73 | } 74 | return sColor; 75 | }; 76 | var toRGB = function(iColor) { 77 | return { 78 | r: (iColor >>> 16) & 0xff, 79 | g: (iColor >>> 8) & 0xff, 80 | b: iColor & 0xff}; 81 | }; 82 | var multiplyColor = function(iColor1, iColor2, ratio) { 83 | var c1 = toRGB(iColor1); 84 | var c2 = toRGB(iColor2); 85 | var mc = function(v1, v2, ratio) { 86 | return ~~Math.max(0, Math.min( (v1 - v2) * ratio + v2, 255) ); 87 | }; 88 | return (mc(c1.r, c2.r, ratio) << 16) | 89 | (mc(c1.g, c2.g, ratio) << 8) | mc(c1.b, c2.b, ratio); 90 | }; 91 | return function(color1, color2, ratio) { 92 | return toSColor(multiplyColor( 93 | toIColor(color1), toIColor(color2), ratio) ); 94 | }; 95 | }(); 96 | 97 | // symbol draw functions 98 | var drawBUF = function(g, x, y, width, height) { 99 | g.moveTo(x, y); 100 | g.lineTo(x + width, y + height / 2); 101 | g.lineTo(x, y + height); 102 | g.lineTo(x, y); 103 | g.closePath(true); 104 | }; 105 | var drawAND = function(g, x, y, width, height) { 106 | g.moveTo(x, y); 107 | g.curveTo(x + width, y, x + width, y + height / 2); 108 | g.curveTo(x + width, y + height, x, y + height); 109 | g.lineTo(x, y); 110 | g.closePath(true); 111 | }; 112 | var drawOR = function(g, x, y, width, height) { 113 | var depth = width * 0.2; 114 | g.moveTo(x, y); 115 | g.curveTo(x + width - depth, y, x + width, y + height / 2); 116 | g.curveTo(x + width - depth, y + height, x, y + height); 117 | g.curveTo(x + depth, y + height, x + depth, y + height / 2); 118 | g.curveTo(x + depth, y, x, y); 119 | g.closePath(true); 120 | }; 121 | var drawEOR = function(g, x, y, width, height) { 122 | drawOR(g, x + 3, y, width - 3, height); 123 | var depth = (width - 3) * 0.2; 124 | g.moveTo(x, y + height); 125 | g.curveTo(x + depth, y + height, x + depth, y + height / 2); 126 | g.curveTo(x + depth, y, x, y); 127 | g.closePath(); 128 | }; 129 | var drawNOT = function(g, x, y, width, height) { 130 | drawBUF(g, x - 1, y, width - 2, height); 131 | g.drawCircle(x + width - 1, y + height / 2, 2); 132 | }; 133 | var drawNAND = function(g, x, y, width, height) { 134 | drawAND(g, x - 1, y, width - 2, height); 135 | g.drawCircle(x + width - 1, y + height / 2, 2); 136 | }; 137 | var drawNOR = function(g, x, y, width, height) { 138 | drawOR(g, x - 1, y, width - 2, height); 139 | g.drawCircle(x + width - 1, y + height / 2, 2); 140 | }; 141 | var drawENOR = function(g, x, y, width, height) { 142 | drawEOR(g, x - 1, y, width - 2, height); 143 | g.drawCircle(x + width - 1, y + height / 2, 2); 144 | }; 145 | // logical functions 146 | var AND = function(a, b) { return a & b; }; 147 | var OR = function(a, b) { return a | b; }; 148 | var EOR = function(a, b) { return a ^ b; }; 149 | var BUF = function(a) { return (a == 1)? 1 : 0; }; 150 | var NOT = function(a) { return (a == 1)? 0 : 1; }; 151 | 152 | var onValue = 1; 153 | var offValue = null; 154 | var isHot = function(v) { return v != null; }; 155 | var intValue = function(v) { return isHot(v)? 1 : 0; }; 156 | 157 | var createSwitchFactory = function(type) { 158 | return function(device) { 159 | var in1 = device.addInput(); 160 | var out1 = device.addOutput(); 161 | var on = (type == 'PushOff'); 162 | 163 | if (type == 'Toggle' && device.deviceDef.state) { 164 | on = device.deviceDef.state.on; 165 | } 166 | device.getState = function() { 167 | return type == 'Toggle'? { on : on } : null; 168 | }; 169 | 170 | device.$ui.on('inputValueChange', function() { 171 | if (on) { 172 | out1.setValue(in1.getValue() ); 173 | } 174 | }); 175 | var updateOutput = function() { 176 | out1.setValue(on? in1.getValue() : null); 177 | }; 178 | updateOutput(); 179 | 180 | var super_createUI = device.createUI; 181 | device.createUI = function() { 182 | super_createUI(); 183 | var size = device.getSize(); 184 | var $button = $s.createSVGElement('rect'). 185 | attr({x: size.width / 4, y: size.height / 4, 186 | width: size.width / 2, height: size.height / 2, 187 | rx: 2, ry: 2}); 188 | $button.addClass('simcir-basicset-switch-button'); 189 | if (type == 'Toggle' && on) { 190 | $button.addClass('simcir-basicset-switch-button-pressed'); 191 | } 192 | device.$ui.append($button); 193 | var button_mouseDownHandler = function(event) { 194 | event.preventDefault(); 195 | event.stopPropagation(); 196 | if (type == 'PushOn') { 197 | on = true; 198 | $button.addClass('simcir-basicset-switch-button-pressed'); 199 | } else if (type == 'PushOff') { 200 | on = false; 201 | $button.addClass('simcir-basicset-switch-button-pressed'); 202 | } else if (type == 'Toggle') { 203 | on = !on; 204 | $button.addClass('simcir-basicset-switch-button-pressed'); 205 | } 206 | updateOutput(); 207 | $(document).on('mouseup', button_mouseUpHandler); 208 | $(document).on('touchend', button_mouseUpHandler); 209 | }; 210 | var button_mouseUpHandler = function(event) { 211 | if (type == 'PushOn') { 212 | on = false; 213 | $button.removeClass('simcir-basicset-switch-button-pressed'); 214 | } else if (type == 'PushOff') { 215 | on = true; 216 | $button.removeClass('simcir-basicset-switch-button-pressed'); 217 | } else if (type == 'Toggle') { 218 | // keep state 219 | if (!on) { 220 | $button.removeClass('simcir-basicset-switch-button-pressed'); 221 | } 222 | } 223 | updateOutput(); 224 | $(document).off('mouseup', button_mouseUpHandler); 225 | $(document).off('touchend', button_mouseUpHandler); 226 | }; 227 | device.$ui.on('deviceAdd', function() { 228 | $s.enableEvents($button, true); 229 | $button.on('mousedown', button_mouseDownHandler); 230 | $button.on('touchstart', button_mouseDownHandler); 231 | }); 232 | device.$ui.on('deviceRemove', function() { 233 | $s.enableEvents($button, false); 234 | $button.off('mousedown', button_mouseDownHandler); 235 | $button.off('touchstart', button_mouseDownHandler); 236 | }); 237 | device.$ui.addClass('simcir-basicset-switch'); 238 | }; 239 | }; 240 | }; 241 | 242 | var createLogicGateFactory = function(op, out, draw) { 243 | return function(device) { 244 | var numInputs = (op == null)? 1 : 245 | Math.max(2, device.deviceDef.numInputs || 2); 246 | device.halfPitch = numInputs > 2; 247 | for (var i = 0; i < numInputs; i += 1) { 248 | device.addInput(); 249 | } 250 | device.addOutput(); 251 | var inputs = device.getInputs(); 252 | var outputs = device.getOutputs(); 253 | device.$ui.on('inputValueChange', function() { 254 | var b = intValue(inputs[0].getValue() ); 255 | if (op != null) { 256 | for (var i = 1; i < inputs.length; i += 1) { 257 | b = op(b, intValue(inputs[i].getValue() ) ); 258 | } 259 | } 260 | b = out(b); 261 | outputs[0].setValue( (b == 1)? 1 : null); 262 | }); 263 | var super_createUI = device.createUI; 264 | device.createUI = function() { 265 | super_createUI(); 266 | var size = device.getSize(); 267 | var g = $s.graphics(device.$ui); 268 | g.attr['class'] = 'simcir-basicset-symbol'; 269 | draw(g, 270 | (size.width - unit) / 2, 271 | (size.height - unit) / 2, 272 | unit, unit); 273 | if (op != null) { 274 | device.doc = { 275 | params: [ 276 | {name: 'numInputs', type: 'number', 277 | defaultValue: 2, 278 | description: 'number of inputs.'} 279 | ], 280 | code: '{"type":"' + device.deviceDef.type + '","numInputs":2}' 281 | }; 282 | } 283 | }; 284 | }; 285 | }; 286 | 287 | /* 288 | var segBase = function() { 289 | return { 290 | width: 0, 291 | height: 0, 292 | allSegments: '', 293 | drawSegment: function(g, segment, color) {}, 294 | drawPoint: function(g, color) {} 295 | }; 296 | }; 297 | */ 298 | 299 | var _7Seg = function() { 300 | var _SEGMENT_DATA = { 301 | a: [575, 138, 494, 211, 249, 211, 194, 137, 213, 120, 559, 120], 302 | b: [595, 160, 544, 452, 493, 500, 459, 456, 500, 220, 582, 146], 303 | c: [525, 560, 476, 842, 465, 852, 401, 792, 441, 562, 491, 516], 304 | d: [457, 860, 421, 892, 94, 892, 69, 864, 144, 801, 394, 801], 305 | e: [181, 560, 141, 789, 61, 856, 48, 841, 96, 566, 148, 516], 306 | f: [241, 218, 200, 453, 150, 500, 115, 454, 166, 162, 185, 145], 307 | g: [485, 507, 433, 555, 190, 555, 156, 509, 204, 464, 451, 464] 308 | }; 309 | return { 310 | width: 636, 311 | height: 1000, 312 | allSegments: 'abcdefg', 313 | drawSegment: function(g, segment, color) { 314 | if (!color) { 315 | return; 316 | } 317 | var data = _SEGMENT_DATA[segment]; 318 | var numPoints = data.length / 2; 319 | g.attr['fill'] = color; 320 | for (var i = 0; i < numPoints; i += 1) { 321 | var x = data[i * 2]; 322 | var y = data[i * 2 + 1]; 323 | if (i == 0) { 324 | g.moveTo(x, y); 325 | } else { 326 | g.lineTo(x, y); 327 | } 328 | } 329 | g.closePath(true); 330 | }, 331 | drawPoint: function(g, color) { 332 | if (!color) { 333 | return; 334 | } 335 | g.attr['fill'] = color; 336 | g.drawCircle(542, 840, 46); 337 | } 338 | }; 339 | }(); 340 | 341 | var _16Seg = function() { 342 | var _SEGMENT_DATA = { 343 | a: [255, 184, 356, 184, 407, 142, 373, 102, 187, 102], 344 | b: [418, 144, 451, 184, 552, 184, 651, 102, 468, 102], 345 | c: [557, 190, 507, 455, 540, 495, 590, 454, 656, 108], 346 | d: [487, 550, 438, 816, 506, 898, 573, 547, 539, 507], 347 | e: [281, 863, 315, 903, 500, 903, 432, 821, 331, 821], 348 | f: [35, 903, 220, 903, 270, 861, 236, 821, 135, 821], 349 | g: [97, 548, 30, 897, 129, 815, 180, 547, 147, 507], 350 | h: [114, 455, 148, 495, 198, 454, 248, 189, 181, 107], 351 | i: [233, 315, 280, 452, 341, 493, 326, 331, 255, 200], 352 | j: [361, 190, 334, 331, 349, 485, 422, 312, 445, 189, 412, 149], 353 | k: [430, 316, 354, 492, 432, 452, 522, 334, 547, 200], 354 | l: [354, 502, 408, 542, 484, 542, 534, 500, 501, 460, 434, 460], 355 | m: [361, 674, 432, 805, 454, 691, 405, 550, 351, 509], 356 | n: [265, 693, 242, 816, 276, 856, 326, 815, 353, 676, 343, 518], 357 | o: [255, 546, 165, 671, 139, 805, 258, 689, 338, 510], 358 | p: [153, 502, 187, 542, 254, 542, 338, 500, 278, 460, 203, 460] 359 | }; 360 | return { 361 | width: 690, 362 | height: 1000, 363 | allSegments: 'abcdefghijklmnop', 364 | drawSegment: function(g, segment, color) { 365 | if (!color) { 366 | return; 367 | } 368 | var data = _SEGMENT_DATA[segment]; 369 | var numPoints = data.length / 2; 370 | g.attr['fill'] = color; 371 | for (var i = 0; i < numPoints; i += 1) { 372 | var x = data[i * 2]; 373 | var y = data[i * 2 + 1]; 374 | if (i == 0) { 375 | g.moveTo(x, y); 376 | } else { 377 | g.lineTo(x, y); 378 | } 379 | } 380 | g.closePath(true); 381 | }, 382 | drawPoint: function(g, color) { 383 | if (!color) { 384 | return; 385 | } 386 | g.attr['fill'] = color; 387 | g.drawCircle(610, 900, 30); 388 | } 389 | }; 390 | }(); 391 | 392 | var drawSeg = function(seg, g, pattern, hiColor, loColor, bgColor) { 393 | g.attr['stroke'] = 'none'; 394 | if (bgColor) { 395 | g.attr['fill'] = bgColor; 396 | g.drawRect(0, 0, seg.width, seg.height); 397 | } 398 | var on; 399 | for (var i = 0; i < seg.allSegments.length; i += 1) { 400 | var c = seg.allSegments.charAt(i); 401 | on = (pattern != null && pattern.indexOf(c) != -1); 402 | seg.drawSegment(g, c, on? hiColor : loColor); 403 | } 404 | on = (pattern != null && pattern.indexOf('.') != -1); 405 | seg.drawPoint(g, on? hiColor : loColor); 406 | }; 407 | 408 | var createSegUI = function(device, seg) { 409 | var size = device.getSize(); 410 | var sw = seg.width; 411 | var sh = seg.height; 412 | var dw = size.width - unit; 413 | var dh = size.height - unit; 414 | var scale = (sw / sh > dw / dh)? dw / sw : dh / sh; 415 | var tx = (size.width - seg.width * scale) / 2; 416 | var ty = (size.height - seg.height * scale) / 2; 417 | return $s.createSVGElement('g'). 418 | attr('transform', 'translate(' + tx + ' ' + ty + ')' + 419 | ' scale(' + scale + ') '); 420 | }; 421 | 422 | var createLEDSegFactory = function(seg) { 423 | return function(device) { 424 | var hiColor = device.deviceDef.color || defaultLEDColor; 425 | var bgColor = device.deviceDef.bgColor || defaultLEDBgColor; 426 | var loColor = multiplyColor(hiColor, bgColor, 0.25); 427 | var allSegs = seg.allSegments + '.'; 428 | device.halfPitch = true; 429 | for (var i = 0; i < allSegs.length; i += 1) { 430 | device.addInput(); 431 | } 432 | 433 | var super_getSize = device.getSize; 434 | device.getSize = function() { 435 | var size = super_getSize(); 436 | return {width: unit * 4, height: size.height}; 437 | }; 438 | 439 | var super_createUI = device.createUI; 440 | device.createUI = function() { 441 | super_createUI(); 442 | 443 | var $seg = createSegUI(device, seg); 444 | device.$ui.append($seg); 445 | 446 | var update = function() { 447 | var segs = ''; 448 | for (var i = 0; i < allSegs.length; i += 1) { 449 | if (isHot(device.getInputs()[i].getValue() ) ) { 450 | segs += allSegs.charAt(i); 451 | } 452 | } 453 | $seg.children().remove(); 454 | drawSeg(seg, $s.graphics($seg), segs, 455 | hiColor, loColor, bgColor); 456 | }; 457 | device.$ui.on('inputValueChange', update); 458 | update(); 459 | device.doc = { 460 | params: [ 461 | {name: 'color', type: 'string', 462 | defaultValue: defaultLEDColor, 463 | description: 'color in hexadecimal.'}, 464 | {name: 'bgColor', type: 'string', 465 | defaultValue: defaultLEDBgColor, 466 | description: 'background color in hexadecimal.'} 467 | ], 468 | code: '{"type":"' + device.deviceDef.type + 469 | '","color":"' + defaultLEDColor + '"}' 470 | }; 471 | }; 472 | }; 473 | }; 474 | 475 | var createLED4bitFactory = function() { 476 | 477 | var _PATTERNS = { 478 | 0: 'abcdef', 479 | 1: 'bc', 480 | 2: 'abdeg', 481 | 3: 'abcdg', 482 | 4: 'bcfg', 483 | 5: 'acdfg', 484 | 6: 'acdefg', 485 | 7: 'abc', 486 | 8: 'abcdefg', 487 | 9: 'abcdfg', 488 | a: 'abcefg', 489 | b: 'cdefg', 490 | c: 'adef', 491 | d: 'bcdeg', 492 | e: 'adefg', 493 | f: 'aefg' 494 | }; 495 | 496 | var getPattern = function(value) { 497 | return _PATTERNS['0123456789abcdef'.charAt(value)]; 498 | }; 499 | 500 | var seg = _7Seg; 501 | 502 | return function(device) { 503 | var hiColor = device.deviceDef.color || defaultLEDColor; 504 | var bgColor = device.deviceDef.bgColor || defaultLEDBgColor; 505 | var loColor = multiplyColor(hiColor, bgColor, 0.25); 506 | for (var i = 0; i < 4; i += 1) { 507 | device.addInput(); 508 | } 509 | 510 | var super_getSize = device.getSize; 511 | device.getSize = function() { 512 | var size = super_getSize(); 513 | return {width: unit * 4, height: size.height}; 514 | }; 515 | 516 | var super_createUI = device.createUI; 517 | device.createUI = function() { 518 | super_createUI(); 519 | 520 | var $seg = createSegUI(device, seg); 521 | device.$ui.append($seg); 522 | 523 | var update = function() { 524 | var value = 0; 525 | for (var i = 0; i < 4; i += 1) { 526 | if (isHot(device.getInputs()[i].getValue() ) ) { 527 | value += (1 << i); 528 | } 529 | } 530 | $seg.children().remove(); 531 | drawSeg(seg, $s.graphics($seg), getPattern(value), 532 | hiColor, loColor, bgColor); 533 | }; 534 | device.$ui.on('inputValueChange', update); 535 | update(); 536 | device.doc = { 537 | params: [ 538 | {name: 'color', type: 'string', 539 | defaultValue: defaultLEDColor, 540 | description: 'color in hexadecimal.'}, 541 | {name: 'bgColor', type: 'string', 542 | defaultValue: defaultLEDBgColor, 543 | description: 'background color in hexadecimal.'} 544 | ], 545 | code: '{"type":"' + device.deviceDef.type + 546 | '","color":"' + defaultLEDColor + '"}' 547 | }; 548 | }; 549 | }; 550 | }; 551 | 552 | var createRotaryEncoderFactory = function() { 553 | var _MIN_ANGLE = 45; 554 | var _MAX_ANGLE = 315; 555 | var thetaToAngle = function(theta) { 556 | var angle = (theta - Math.PI / 2) / Math.PI * 180; 557 | while (angle < 0) { 558 | angle += 360; 559 | } 560 | while (angle > 360) { 561 | angle -= 360; 562 | } 563 | return angle; 564 | }; 565 | return function(device) { 566 | var numOutputs = Math.max(2, device.deviceDef.numOutputs || 4); 567 | device.halfPitch = numOutputs > 4; 568 | device.addInput(); 569 | for (var i = 0; i < numOutputs; i += 1) { 570 | device.addOutput(); 571 | } 572 | 573 | var super_getSize = device.getSize; 574 | device.getSize = function() { 575 | var size = super_getSize(); 576 | return {width: unit * 4, height: size.height}; 577 | }; 578 | 579 | var super_createUI = device.createUI; 580 | device.createUI = function() { 581 | super_createUI(); 582 | var size = device.getSize(); 583 | 584 | var $knob = $s.createSVGElement('g'). 585 | attr('class', 'simcir-basicset-knob'). 586 | append($s.createSVGElement('rect'). 587 | attr({x:-10,y:-10,width:20,height:20})); 588 | var r = Math.min(size.width, size.height) / 4 * 1.5; 589 | var g = $s.graphics($knob); 590 | g.drawCircle(0, 0, r); 591 | g.attr['class'] = 'simcir-basicset-knob-mark'; 592 | g.moveTo(0, 0); 593 | g.lineTo(r, 0); 594 | g.closePath(); 595 | device.$ui.append($knob); 596 | 597 | var _angle = _MIN_ANGLE; 598 | var setAngle = function(angle) { 599 | _angle = Math.max(_MIN_ANGLE, Math.min(angle, _MAX_ANGLE) ); 600 | update(); 601 | }; 602 | 603 | var dragPoint = null; 604 | var knob_mouseDownHandler = function(event) { 605 | event.preventDefault(); 606 | event.stopPropagation(); 607 | dragPoint = {x: event.pageX, y: event.pageY}; 608 | $(document).on('mousemove', knob_mouseMoveHandler); 609 | $(document).on('mouseup', knob_mouseUpHandler); 610 | }; 611 | var knob_mouseMoveHandler = function(event) { 612 | var off = $knob.parent('svg').offset(); 613 | var pos = $s.offset($knob); 614 | var cx = off.left + pos.x; 615 | var cy = off.top + pos.y; 616 | var dx = event.pageX - cx; 617 | var dy = event.pageY - cy; 618 | if (dx == 0 && dy == 0) return; 619 | setAngle(thetaToAngle(Math.atan2(dy, dx) ) ); 620 | }; 621 | var knob_mouseUpHandler = function(event) { 622 | $(document).off('mousemove', knob_mouseMoveHandler); 623 | $(document).off('mouseup', knob_mouseUpHandler); 624 | }; 625 | device.$ui.on('deviceAdd', function() { 626 | $s.enableEvents($knob, true); 627 | $knob.on('mousedown', knob_mouseDownHandler); 628 | }); 629 | device.$ui.on('deviceRemove', function() { 630 | $s.enableEvents($knob, false); 631 | $knob.off('mousedown', knob_mouseDownHandler); 632 | }); 633 | 634 | var update = function() { 635 | $s.transform($knob, size.width / 2, 636 | size.height / 2, _angle + 90); 637 | var max = 1 << numOutputs; 638 | var value = Math.min( ( (_angle - _MIN_ANGLE) / 639 | (_MAX_ANGLE - _MIN_ANGLE) * max), max - 1); 640 | for (var i = 0; i < numOutputs; i += 1) { 641 | device.getOutputs()[i].setValue( (value & (1 << i) )? 642 | device.getInputs()[0].getValue() : null); 643 | } 644 | }; 645 | device.$ui.on('inputValueChange', update); 646 | update(); 647 | device.doc = { 648 | params: [ 649 | {name: 'numOutputs', type: 'number', defaultValue: 4, 650 | description: 'number of outputs.'} 651 | ], 652 | code: '{"type":"' + device.deviceDef.type + '","numOutputs":4}' 653 | }; 654 | }; 655 | }; 656 | }; 657 | 658 | // register direct current source 659 | $s.registerDevice('DC', function(device) { 660 | device.addOutput(); 661 | var super_createUI = device.createUI; 662 | device.createUI = function() { 663 | super_createUI(); 664 | device.$ui.addClass('simcir-basicset-dc'); 665 | }; 666 | device.$ui.on('deviceAdd', function() { 667 | device.getOutputs()[0].setValue(onValue); 668 | }); 669 | device.$ui.on('deviceRemove', function() { 670 | device.getOutputs()[0].setValue(null); 671 | }); 672 | }); 673 | 674 | // register simple LED 675 | $s.registerDevice('LED', function(device) { 676 | var in1 = device.addInput(); 677 | var super_createUI = device.createUI; 678 | device.createUI = function() { 679 | super_createUI(); 680 | var hiColor = device.deviceDef.color || defaultLEDColor; 681 | var bgColor = device.deviceDef.bgColor || defaultLEDBgColor; 682 | var loColor = multiplyColor(hiColor, bgColor, 0.25); 683 | var bLoColor = multiplyColor(hiColor, bgColor, 0.2); 684 | var bHiColor = multiplyColor(hiColor, bgColor, 0.8); 685 | var size = device.getSize(); 686 | var $ledbase = $s.createSVGElement('circle'). 687 | attr({cx: size.width / 2, cy: size.height / 2, r: size.width / 4}). 688 | attr('stroke', 'none'). 689 | attr('fill', bLoColor); 690 | device.$ui.append($ledbase); 691 | var $led = $s.createSVGElement('circle'). 692 | attr({cx: size.width / 2, cy: size.height / 2, r: size.width / 4 * 0.8}). 693 | attr('stroke', 'none'). 694 | attr('fill', loColor); 695 | device.$ui.append($led); 696 | device.$ui.on('inputValueChange', function() { 697 | $ledbase.attr('fill', isHot(in1.getValue() )? bHiColor : bLoColor); 698 | $led.attr('fill', isHot(in1.getValue() )? hiColor : loColor); 699 | }); 700 | device.doc = { 701 | params: [ 702 | {name: 'color', type: 'string', 703 | defaultValue: defaultLEDColor, 704 | description: 'color in hexadecimal.'}, 705 | {name: 'bgColor', type: 'string', 706 | defaultValue: defaultLEDBgColor, 707 | description: 'background color in hexadecimal.'} 708 | ], 709 | code: '{"type":"' + device.deviceDef.type + 710 | '","color":"' + defaultLEDColor + '"}' 711 | }; 712 | }; 713 | }); 714 | 715 | // register switches 716 | $s.registerDevice('PushOff', createSwitchFactory('PushOff') ); 717 | $s.registerDevice('PushOn', createSwitchFactory('PushOn') ); 718 | $s.registerDevice('Toggle', createSwitchFactory('Toggle') ); 719 | 720 | // register logic gates 721 | $s.registerDevice('BUF', createLogicGateFactory(null, BUF, drawBUF) ); 722 | $s.registerDevice('NOT', createLogicGateFactory(null, NOT, drawNOT) ); 723 | $s.registerDevice('AND', createLogicGateFactory(AND, BUF, drawAND) ); 724 | $s.registerDevice('NAND', createLogicGateFactory(AND, NOT, drawNAND) ); 725 | $s.registerDevice('OR', createLogicGateFactory(OR, BUF, drawOR) ); 726 | $s.registerDevice('NOR', createLogicGateFactory(OR, NOT, drawNOR) ); 727 | $s.registerDevice('XOR', createLogicGateFactory(EOR, BUF, drawEOR) ); 728 | $s.registerDevice('XNOR', createLogicGateFactory(EOR, NOT, drawENOR) ); 729 | // deprecated. not displayed in the default toolbox. 730 | $s.registerDevice('EOR', createLogicGateFactory(EOR, BUF, drawEOR), true); 731 | $s.registerDevice('ENOR', createLogicGateFactory(EOR, NOT, drawENOR), true); 732 | 733 | // register Oscillator 734 | $s.registerDevice('OSC', function(device) { 735 | var freq = device.deviceDef.freq || 10; 736 | var delay = ~~(500 / freq); 737 | var out1 = device.addOutput(); 738 | var timerId = null; 739 | var on = false; 740 | device.$ui.on('deviceAdd', function() { 741 | timerId = window.setInterval(function() { 742 | out1.setValue(on? onValue : offValue); 743 | on = !on; 744 | }, delay); 745 | }); 746 | device.$ui.on('deviceRemove', function() { 747 | if (timerId != null) { 748 | window.clearInterval(timerId); 749 | timerId = null; 750 | } 751 | }); 752 | var super_createUI = device.createUI; 753 | device.createUI = function() { 754 | super_createUI(); 755 | device.$ui.addClass('simcir-basicset-osc'); 756 | device.doc = { 757 | params: [ 758 | {name: 'freq', type: 'number', defaultValue: '10', 759 | description: 'frequency of an oscillator.'} 760 | ], 761 | code: '{"type":"' + device.deviceDef.type + '","freq":10}' 762 | }; 763 | }; 764 | }); 765 | 766 | // register LED seg 767 | $s.registerDevice('7seg', createLEDSegFactory(_7Seg) ); 768 | $s.registerDevice('16seg', createLEDSegFactory(_16Seg) ); 769 | $s.registerDevice('4bit7seg', createLED4bitFactory() ); 770 | 771 | // register Rotary Encoder 772 | $s.registerDevice('RotaryEncoder', createRotaryEncoderFactory() ); 773 | 774 | $s.registerDevice('BusIn', function(device) { 775 | var numOutputs = Math.max(2, device.deviceDef.numOutputs || 8); 776 | device.halfPitch = true; 777 | device.addInput('', 'x' + numOutputs); 778 | for (var i = 0; i < numOutputs; i += 1) { 779 | device.addOutput(); 780 | } 781 | var extractValue = function(busValue, i) { 782 | return (busValue != null && typeof busValue == 'object' && 783 | typeof busValue[i] != 'undefined')? busValue[i] : null; 784 | }; 785 | device.$ui.on('inputValueChange', function() { 786 | var busValue = device.getInputs()[0].getValue(); 787 | for (var i = 0; i < numOutputs; i += 1) { 788 | device.getOutputs()[i].setValue(extractValue(busValue, i) ); 789 | } 790 | }); 791 | var super_createUI = device.createUI; 792 | device.createUI = function() { 793 | super_createUI(); 794 | device.doc = { 795 | params: [ 796 | {name: 'numOutputs', type: 'number', defaultValue: 8, 797 | description: 'number of outputs.'} 798 | ], 799 | code: '{"type":"' + device.deviceDef.type + '","numOutputs":8}' 800 | }; 801 | }; 802 | }); 803 | 804 | $s.registerDevice('BusOut', function(device) { 805 | var numInputs = Math.max(2, device.deviceDef.numInputs || 8); 806 | device.halfPitch = true; 807 | for (var i = 0; i < numInputs; i += 1) { 808 | device.addInput(); 809 | } 810 | device.addOutput('', 'x' + numInputs); 811 | device.$ui.on('inputValueChange', function() { 812 | var busValue = []; 813 | var hotCount = 0; 814 | for (var i = 0; i < numInputs; i += 1) { 815 | var value = device.getInputs()[i].getValue(); 816 | if (isHot(value) ) { 817 | hotCount += 1; 818 | } 819 | busValue.push(value); 820 | } 821 | device.getOutputs()[0].setValue( 822 | (hotCount > 0)? busValue : null); 823 | }); 824 | var super_createUI = device.createUI; 825 | device.createUI = function() { 826 | super_createUI(); 827 | device.doc = { 828 | params: [ 829 | {name: 'numInputs', type: 'number', defaultValue: 8, 830 | description: 'number of inputs.'} 831 | ], 832 | code: '{"type":"' + device.deviceDef.type + '","numInputs":8}' 833 | }; 834 | }; 835 | }); 836 | 837 | }(simcir); 838 | -------------------------------------------------------------------------------- /simcir.js: -------------------------------------------------------------------------------- 1 | // 2 | // SimcirJS 3 | // 4 | // Copyright (c) 2014 Kazuhiko Arase 5 | // 6 | // URL: http://www.d-project.com/ 7 | // 8 | // Licensed under the MIT license: 9 | // http://www.opensource.org/licenses/mit-license.php 10 | // 11 | 12 | // includes following device types: 13 | // In 14 | // Out 15 | // Joint 16 | 17 | 'use strict'; 18 | 19 | var simcir = {}; 20 | 21 | // 22 | // https://github.com/kazuhikoarase/lessQuery 23 | // 24 | simcir.$ = function() { 25 | 26 | var debug = location.hash == '#debug'; 27 | 28 | var cacheIdKey = '.lessqCacheId'; 29 | var cacheIdSeq = 0; 30 | var cache = {}; 31 | 32 | var getCache = function(elm) { 33 | var cacheId = elm[cacheIdKey]; 34 | if (typeof cacheId == 'undefined') { 35 | elm[cacheIdKey] = cacheId = cacheIdSeq++; 36 | cache[cacheId] = debug? { e : elm } : {}; 37 | } 38 | return cache[cacheId]; 39 | }; 40 | 41 | var hasCache = function(elm) { 42 | return typeof elm[cacheIdKey] != 'undefined'; 43 | }; 44 | 45 | if (debug) { 46 | var lastKeys = {}; 47 | var showCacheCount = function() { 48 | var cnt = 0; 49 | var keys = {}; 50 | for (var k in cache) { 51 | cnt += 1; 52 | if (!lastKeys[k]) { 53 | console.log(cache[k]); 54 | } 55 | keys[k] = true; 56 | } 57 | lastKeys = keys; 58 | console.log('cacheCount:' + cnt); 59 | window.setTimeout(showCacheCount, 5000); 60 | }; 61 | showCacheCount(); 62 | } 63 | 64 | var removeCache = function(elm) { 65 | 66 | if (typeof elm[cacheIdKey] != 'undefined') { 67 | 68 | // remove all listeners 69 | var cacheId = elm[cacheIdKey]; 70 | var listenerMap = cache[cacheId].listenerMap; 71 | for (var type in listenerMap) { 72 | var listeners = listenerMap[type]; 73 | for (var i = 0; i < listeners.length; i += 1) { 74 | elm.removeEventListener(type, listeners[i]); 75 | } 76 | } 77 | 78 | // delete refs 79 | delete elm[cacheIdKey]; 80 | delete cache[cacheId]; 81 | } 82 | 83 | while (elm.firstChild) { 84 | removeCache(elm.firstChild); 85 | elm.removeChild(elm.firstChild); 86 | } 87 | }; 88 | 89 | var getData = function(elm) { 90 | if (!getCache(elm).data) { getCache(elm).data = {}; } 91 | return getCache(elm).data; 92 | }; 93 | 94 | var getListeners = function(elm, type) { 95 | if (!getCache(elm).listenerMap) { 96 | getCache(elm).listenerMap = {}; } 97 | if (!getCache(elm).listenerMap[type]) { 98 | getCache(elm).listenerMap[type] = []; } 99 | return getCache(elm).listenerMap[type]; 100 | }; 101 | 102 | // add / remove event listener. 103 | var addEventListener = function(elm, type, listener, add) { 104 | var listeners = getListeners(elm, type); 105 | var newListeners = []; 106 | for (var i = 0; i < listeners.length; i += 1) { 107 | if (listeners[i] != listener) { 108 | newListeners.push(listeners[i]); 109 | } 110 | } 111 | if (add) { newListeners.push(listener); } 112 | getCache(elm).listenerMap[type] = newListeners; 113 | return true; 114 | }; 115 | 116 | var CustomEvent = { 117 | preventDefault : function() { this._pD = true; }, 118 | stopPropagation : function() { this._sP = true; }, 119 | stopImmediatePropagation : function() { this._sIp = true; } 120 | }; 121 | 122 | var trigger = function(elm, type, data) { 123 | var event = { type : type, target : elm, currentTarget : null, 124 | _pD : false, _sP : false, _sIp : false, __proto__ : CustomEvent }; 125 | for (var e = elm; e != null; e = e.parentNode) { 126 | if (!hasCache(e) ) { continue; } 127 | if (!getCache(e).listenerMap) { continue; } 128 | if (!getCache(e).listenerMap[type]) { continue; } 129 | event.currentTarget = e; 130 | var listeners = getCache(e).listenerMap[type]; 131 | for (var i = 0; i < listeners.length; i += 1) { 132 | listeners[i].call(e, event, data); 133 | if (event._sIp) { return; } 134 | } 135 | if (event._sP) { return; } 136 | } 137 | }; 138 | 139 | var data = function(elm, kv) { 140 | if (arguments.length == 2) { 141 | if (typeof kv == 'string') return getData(elm)[kv]; 142 | for (var k in kv) { getData(elm)[k] = kv[k]; } 143 | } else if (arguments.length == 3) { 144 | getData(elm)[kv] = arguments[2]; 145 | } 146 | return elm; 147 | }; 148 | 149 | var extend = function(o1, o2) { 150 | for (var k in o2) { o1[k] = o2[k]; } return o1; 151 | }; 152 | 153 | var each = function(it, callback) { 154 | if (typeof it.splice == 'function') { 155 | for (var i = 0; i < it.length; i += 1) { callback(i, it[i]); } 156 | } else { 157 | for (var k in it) { callback(k, it[k]); } 158 | } 159 | }; 160 | 161 | var grep = function(list, accept) { 162 | var newList = []; 163 | for (var i = 0; i < list.length; i += 1) { 164 | var item = list[i]; 165 | if (accept(item) ) { 166 | newList.push(item); 167 | } 168 | } 169 | return newList; 170 | }; 171 | 172 | var addClass = function(elm, className, add) { 173 | var classes = (elm.getAttribute('class') || '').split(/\s+/g); 174 | var newClasses = ''; 175 | for (var i = 0; i < classes.length; i+= 1) { 176 | if (classes[i] == className) { continue; } 177 | newClasses += ' ' + classes[i]; 178 | } 179 | if (add) { newClasses += ' ' + className; } 180 | elm.setAttribute('class', newClasses); 181 | }; 182 | 183 | var hasClass = function(elm, className) { 184 | var classes = (elm.getAttribute('class') || '').split(/\s+/g); 185 | for (var i = 0; i < classes.length; i+= 1) { 186 | if (classes[i] == className) { return true; } 187 | } 188 | return false; 189 | }; 190 | 191 | var matches = function(elm, selector) { 192 | if (elm.nodeType != 1) { 193 | return false; 194 | } else if (!selector) { 195 | return true; 196 | } 197 | var sels = selector.split(/[,\s]+/g); 198 | for (var i = 0; i < sels.length; i += 1) { 199 | var sel = sels[i]; 200 | if (sel.substring(0, 1) == '#') { 201 | throw 'not supported:' + sel; 202 | } else if (sel.substring(0, 1) == '.') { 203 | if (hasClass(elm, sel.substring(1) ) ) { 204 | return true; 205 | } 206 | } else { 207 | if (elm.tagName.toUpperCase() == sel.toUpperCase() ) { 208 | return true; 209 | } 210 | } 211 | } 212 | return false; 213 | }; 214 | 215 | var parser = new window.DOMParser(); 216 | 217 | var html = function(html) { 218 | var doc = parser.parseFromString( 219 | '
' + html + '
', 220 | 'text/xml').firstChild; 221 | var elms = []; 222 | while (doc.firstChild) { 223 | elms.push(doc.firstChild); 224 | doc.removeChild(doc.firstChild); 225 | } 226 | elms.__proto__ = fn; 227 | return elms; 228 | }; 229 | 230 | var pxToNum = function(px) { 231 | if (typeof px != 'string' || px.length <= 2 || 232 | px.charAt(px.length - 2) != 'p' || 233 | px.charAt(px.length - 1) != 'x') { 234 | throw 'illegal px:' + px; 235 | } 236 | return +px.substring(0, px.length - 2); 237 | }; 238 | 239 | var buildQuery = function(data) { 240 | var query = ''; 241 | for (var k in data) { 242 | if (query.length > 0) { 243 | query += '&'; 244 | } 245 | query += window.encodeURIComponent(k); 246 | query += '='; 247 | query += window.encodeURIComponent(data[k]); 248 | } 249 | return query; 250 | }; 251 | 252 | var parseResponse = function() { 253 | 254 | var contentType = this.getResponseHeader('content-type'); 255 | if (contentType != null) { 256 | contentType = contentType.replace(/\s*;.+$/, '').toLowerCase(); 257 | } 258 | 259 | if (contentType == 'text/xml' || 260 | contentType == 'application/xml') { 261 | return parser.parseFromString(this.responseText, 'text/xml'); 262 | } else if (contentType == 'text/json' || 263 | contentType == 'application/json') { 264 | return JSON.parse(this.responseText); 265 | } else { 266 | return this.response; 267 | } 268 | }; 269 | 270 | var ajax = function(params) { 271 | 272 | params = extend({ 273 | url: '', 274 | method : 'GET', 275 | contentType : 'application/x-www-form-urlencoded;charset=UTF-8', 276 | cache: true, 277 | processData: true, 278 | async : true 279 | }, params); 280 | 281 | if (!params.async) { 282 | // force async. 283 | throw 'not supported.'; 284 | } 285 | 286 | var method = params.method.toUpperCase(); 287 | var data = null; 288 | var contentType = params.contentType; 289 | if (method == 'POST' || method == 'PUT') { 290 | data = (typeof params.data == 'object' && params.processData)? 291 | buildQuery(params.data) : params.data; 292 | } else { 293 | contentType = false; 294 | } 295 | 296 | var xhr = params.xhr? params.xhr() : new window.XMLHttpRequest(); 297 | xhr.open(method, params.url, params.async); 298 | if (contentType !== false) { 299 | xhr.setRequestHeader('Content-Type', contentType); 300 | } 301 | xhr.onreadystatechange = function() { 302 | if(xhr.readyState == window.XMLHttpRequest.DONE) { 303 | try { 304 | if (xhr.status == 200) { 305 | done.call(xhr, parseResponse.call(this) ); 306 | } else { 307 | fail.call(xhr); 308 | } 309 | } finally { 310 | always.call(xhr); 311 | } 312 | } 313 | }; 314 | 315 | // call later 316 | window.setTimeout(function() { xhr.send(data); }, 0); 317 | 318 | // callbacks 319 | var done = function(data) {}; 320 | var fail = function() {}; 321 | var always = function() {}; 322 | 323 | var $ = { 324 | done : function(callback) { done = callback; return $; }, 325 | fail : function(callback) { fail = callback; return $; }, 326 | always : function(callback) { always = callback; return $; }, 327 | abort : function() { xhr.abort(); return $; } 328 | }; 329 | return $; 330 | }; 331 | 332 | // 1. for single element 333 | var fn = { 334 | attr : function(kv) { 335 | if (arguments.length == 1) { 336 | if (typeof kv == 'string') return this.getAttribute(kv); 337 | for (var k in kv) { this.setAttribute(k, kv[k]); } 338 | } else if (arguments.length == 2) { 339 | this.setAttribute(kv, arguments[1]); 340 | } 341 | return this; 342 | }, 343 | prop : function(kv) { 344 | if (arguments.length == 1) { 345 | if (typeof kv == 'string') return this[kv]; 346 | for (var k in kv) { this[k] = kv[k]; } 347 | } else if (arguments.length == 2) { 348 | this[kv] = arguments[1]; 349 | } 350 | return this; 351 | }, 352 | css : function(kv) { 353 | if (arguments.length == 1) { 354 | if (typeof kv == 'string') return this.style[kv]; 355 | for (var k in kv) { this.style[k] = kv[k]; } 356 | } else if (arguments.length == 2) { 357 | this.style[kv] = arguments[1]; 358 | } 359 | return this; 360 | }, 361 | data : function(kv) { 362 | var args = [ this ]; 363 | for (var i = 0; i < arguments.length; i += 1) { 364 | args.push(arguments[i]); 365 | }; 366 | return data.apply(null, args); 367 | }, 368 | val : function() { 369 | if (arguments.length == 0) { 370 | return this.value || ''; 371 | } else if (arguments.length == 1) { 372 | this.value = arguments[0]; 373 | } 374 | return this; 375 | }, 376 | on : function(type, listener) { 377 | var types = type.split(/\s+/g); 378 | for (var i = 0; i < types.length; i += 1) { 379 | this.addEventListener(types[i], listener); 380 | addEventListener(this, types[i], listener, true); 381 | } 382 | return this; 383 | }, 384 | off : function(type, listener) { 385 | var types = type.split(/\s+/g); 386 | for (var i = 0; i < types.length; i += 1) { 387 | this.removeEventListener(types[i], listener); 388 | addEventListener(this, types[i], listener, false); 389 | } 390 | return this; 391 | }, 392 | trigger : function(type, data) { 393 | trigger(this, type, data); 394 | return this; 395 | }, 396 | offset : function() { 397 | var off = { left : 0, top : 0 }; 398 | var base = null; 399 | for (var e = this; e.parentNode != null; e = e.parentNode) { 400 | if (e.offsetParent != null) { 401 | base = e; 402 | break; 403 | } 404 | } 405 | if (base != null) { 406 | for (var e = base; e.offsetParent != null; e = e.offsetParent) { 407 | off.left += e.offsetLeft; 408 | off.top += e.offsetTop; 409 | } 410 | } 411 | for (var e = this; e.parentNode != null && 412 | e != document.body; e = e.parentNode) { 413 | off.left -= e.scrollLeft; 414 | off.top -= e.scrollTop; 415 | } 416 | return off; 417 | }, 418 | append : function(elms) { 419 | if (typeof elms == 'string') { 420 | elms = html(elms); 421 | } 422 | for (var i = 0; i < elms.length; i += 1) { 423 | this.appendChild(elms[i]); 424 | } 425 | return this; 426 | }, 427 | prepend : function(elms) { 428 | if (typeof elms == 'string') { 429 | elms = html(elms); 430 | } 431 | for (var i = 0; i < elms.length; i += 1) { 432 | if (this.firstChild) { 433 | this.insertBefore(elms[i], this.firstChild); 434 | } else { 435 | this.appendChild(elms[i]); 436 | } 437 | } 438 | return this; 439 | }, 440 | insertBefore : function(elms) { 441 | var elm = elms[0]; 442 | elm.parentNode.insertBefore(this, elm); 443 | return this; 444 | }, 445 | insertAfter : function(elms) { 446 | var elm = elms[0]; 447 | if (elm.nextSibling) { 448 | elm.parentNode.insertBefore(this, elm.nextSibling); 449 | } else { 450 | elm.parentNode.appendChild(this); 451 | } 452 | return this; 453 | }, 454 | remove : function() { 455 | if (this.parentNode) { this.parentNode.removeChild(this); } 456 | removeCache(this); 457 | return this; 458 | }, 459 | detach : function() { 460 | if (this.parentNode) { this.parentNode.removeChild(this); } 461 | return this; 462 | }, 463 | parent : function() { 464 | return $(this.parentNode); 465 | }, 466 | closest : function(selector) { 467 | for (var e = this; e != null; e = e.parentNode) { 468 | if (matches(e, selector) ) { 469 | return $(e); 470 | } 471 | } 472 | return $(); 473 | }, 474 | find : function(selector) { 475 | var elms = []; 476 | var childNodes = this.querySelectorAll(selector); 477 | for (var i = 0; i < childNodes.length; i += 1) { 478 | elms.push(childNodes.item(i) ); 479 | } 480 | elms.__proto__ = fn; 481 | return elms; 482 | }, 483 | children : function(selector) { 484 | var elms = []; 485 | var childNodes = this.childNodes; 486 | for (var i = 0; i < childNodes.length; i += 1) { 487 | if (matches(childNodes.item(i), selector) ) { 488 | elms.push(childNodes.item(i) ); 489 | } 490 | } 491 | elms.__proto__ = fn; 492 | return elms; 493 | }, 494 | index : function(selector) { 495 | return Array.prototype.indexOf.call( 496 | $(this).parent().children(selector), this); 497 | }, 498 | clone : function() { return $(this.cloneNode(true) ); }, 499 | focus : function() { this.focus(); return this; }, 500 | select : function() { this.select(); return this; }, 501 | submit : function() { this.submit(); return this; }, 502 | scrollLeft : function() { 503 | if (arguments.length == 0) return this.scrollLeft; 504 | this.scrollLeft = arguments[0]; return this; 505 | }, 506 | scrollTop : function() { 507 | if (arguments.length == 0) return this.scrollTop; 508 | this.scrollTop = arguments[0]; return this; 509 | }, 510 | html : function() { 511 | if (arguments.length == 0) return this.innerHTML; 512 | this.innerHTML = arguments[0]; return this; 513 | }, 514 | text : function() { 515 | if (typeof this.textContent != 'undefined') { 516 | if (arguments.length == 0) return this.textContent; 517 | this.textContent = arguments[0]; return this; 518 | } else { 519 | if (arguments.length == 0) return this.innerText; 520 | this.innerText = arguments[0]; return this; 521 | } 522 | }, 523 | outerWidth : function(margin) { 524 | var w = this.offsetWidth; 525 | if (margin) { 526 | var cs = window.getComputedStyle(this, null); 527 | return w + pxToNum(cs.marginLeft) + pxToNum(cs.marginRight); 528 | } 529 | return w; 530 | }, 531 | innerWidth : function() { 532 | var cs = window.getComputedStyle(this, null); 533 | return this.offsetWidth - 534 | pxToNum(cs.borderLeftWidth) - pxToNum(cs.borderRightWidth); 535 | }, 536 | width : function() { 537 | if (this == window) return this.innerWidth; 538 | var cs = window.getComputedStyle(this, null); 539 | return this.offsetWidth - 540 | pxToNum(cs.borderLeftWidth) - pxToNum(cs.borderRightWidth) - 541 | pxToNum(cs.paddingLeft) - pxToNum(cs.paddingRight); 542 | }, 543 | outerHeight : function(margin) { 544 | var h = this.offsetHeight; 545 | if (margin) { 546 | var cs = window.getComputedStyle(this, null); 547 | return h + pxToNum(cs.marginTop) + pxToNum(cs.marginBottom); 548 | } 549 | return h; 550 | }, 551 | innerHeight : function() { 552 | var cs = window.getComputedStyle(this, null); 553 | return this.offsetHeight - 554 | pxToNum(cs.borderTopWidth) - pxToNum(cs.borderBottomWidth); 555 | }, 556 | height : function() { 557 | if (this == window) return this.innerHeight; 558 | var cs = window.getComputedStyle(this, null); 559 | return this.offsetHeight - 560 | pxToNum(cs.borderTopWidth) - pxToNum(cs.borderBottomWidth) - 561 | pxToNum(cs.paddingTop) - pxToNum(cs.paddingBottom); 562 | }, 563 | addClass : function(className) { 564 | addClass(this, className, true); return this; 565 | }, 566 | removeClass : function(className) { 567 | addClass(this, className, false); return this; 568 | }, 569 | hasClass : function(className) { 570 | return hasClass(this, className); 571 | } 572 | }; 573 | 574 | // 2. to array 575 | each(fn, function(name, func) { 576 | fn[name] = function() { 577 | var newRet = null; 578 | for (var i = 0; i < this.length; i += 1) { 579 | var elm = this[i]; 580 | var ret = func.apply(elm, arguments); 581 | if (elm !== ret) { 582 | if (ret != null && ret.__proto__ == fn) { 583 | if (newRet == null) { newRet = []; } 584 | newRet = newRet.concat(ret); 585 | } else { 586 | return ret; 587 | } 588 | } 589 | } 590 | if (newRet != null) { 591 | newRet.__proto__ = fn; 592 | return newRet; 593 | } 594 | return this; 595 | }; 596 | }); 597 | 598 | // 3. for array 599 | fn = extend(fn, { 600 | each : function(callback) { 601 | for (var i = 0; i < this.length; i += 1) { 602 | callback.call(this[i], i); 603 | } 604 | return this; 605 | }, 606 | first : function() { 607 | return $(this.length > 0? this[0] : null); 608 | }, 609 | last : function() { 610 | return $(this.length > 0? this[this.length - 1] : null); 611 | } 612 | }); 613 | 614 | var $ = function(target) { 615 | 616 | if (typeof target == 'function') { 617 | 618 | // ready 619 | return $(document).on('DOMContentLoaded', target); 620 | 621 | } else if (typeof target == 'string') { 622 | 623 | if (target.charAt(0) == '<') { 624 | 625 | // dom creation 626 | return html(target); 627 | 628 | } else { 629 | 630 | // query 631 | var childNodes = document.querySelectorAll(target); 632 | var elms = []; 633 | for (var i = 0; i < childNodes.length; i += 1) { 634 | elms.push(childNodes.item(i) ); 635 | } 636 | elms.__proto__ = fn; 637 | return elms; 638 | } 639 | 640 | } else if (typeof target == 'object' && target != null) { 641 | 642 | if (target.__proto__ == fn) { 643 | return target; 644 | } else { 645 | var elms = []; 646 | elms.push(target); 647 | elms.__proto__ = fn; 648 | return elms; 649 | } 650 | 651 | } else { 652 | 653 | var elms = []; 654 | elms.__proto__ = fn; 655 | return elms; 656 | } 657 | }; 658 | 659 | return extend($, { 660 | fn : fn, extend : extend, each : each, grep : grep, 661 | data : data, ajax : ajax }); 662 | }(); 663 | 664 | !function($s) { 665 | 666 | var $ = $s.$; 667 | 668 | var createSVGElement = function(tagName) { 669 | return $(document.createElementNS( 670 | 'http://www.w3.org/2000/svg', tagName) ); 671 | }; 672 | 673 | var createSVG = function(w, h) { 674 | return createSVGElement('svg').attr({ 675 | version: '1.1', 676 | width: w, height: h, 677 | viewBox: '0 0 ' + w + ' ' + h 678 | }); 679 | }; 680 | 681 | var graphics = function($target) { 682 | var attr = {}; 683 | var buf = ''; 684 | var moveTo = function(x, y) { 685 | buf += ' M ' + x + ' ' + y; 686 | }; 687 | var lineTo = function(x, y) { 688 | buf += ' L ' + x + ' ' + y; 689 | }; 690 | var curveTo = function(x1, y1, x, y) { 691 | buf += ' Q ' + x1 + ' ' + y1 + ' ' + x + ' ' + y; 692 | }; 693 | var closePath = function(close) { 694 | if (close) { 695 | // really close path. 696 | buf += ' Z'; 697 | } 698 | $target.append(createSVGElement('path'). 699 | attr('d', buf).attr(attr) ); 700 | buf = ''; 701 | }; 702 | var drawRect = function(x, y, width, height) { 703 | $target.append(createSVGElement('rect'). 704 | attr({x: x, y: y, width: width, height: height}).attr(attr) ); 705 | }; 706 | var drawCircle = function(x, y, r) { 707 | $target.append(createSVGElement('circle'). 708 | attr({cx: x, cy: y, r: r}).attr(attr) ); 709 | }; 710 | return { 711 | attr: attr, 712 | moveTo: moveTo, 713 | lineTo: lineTo, 714 | curveTo: curveTo, 715 | closePath: closePath, 716 | drawRect: drawRect, 717 | drawCircle: drawCircle 718 | }; 719 | }; 720 | 721 | var transform = function() { 722 | var attrX = 'simcir-transform-x'; 723 | var attrY = 'simcir-transform-y'; 724 | var attrRotate = 'simcir-transform-rotate'; 725 | var num = function($o, k) { 726 | var v = $o.attr(k); 727 | return v? +v : 0; 728 | }; 729 | return function($o, x, y, rotate) { 730 | if (arguments.length >= 3) { 731 | var transform = 'translate(' + x + ' ' + y + ')'; 732 | if (rotate) { 733 | transform += ' rotate(' + rotate + ')'; 734 | } 735 | $o.attr('transform', transform); 736 | $o.attr(attrX, x); 737 | $o.attr(attrY, y); 738 | $o.attr(attrRotate, rotate); 739 | } else if (arguments.length == 1) { 740 | return {x: num($o, attrX), y: num($o, attrY), 741 | rotate: num($o, attrRotate)}; 742 | } 743 | }; 744 | }(); 745 | 746 | var offset = function($o) { 747 | var x = 0; 748 | var y = 0; 749 | while ($o[0].nodeName != 'svg') { 750 | var pos = transform($o); 751 | x += pos.x; 752 | y += pos.y; 753 | $o = $o.parent(); 754 | } 755 | return {x: x, y: y}; 756 | }; 757 | 758 | var enableEvents = function($o, enable) { 759 | $o.css('pointer-events', enable? 'visiblePainted' : 'none'); 760 | }; 761 | 762 | var disableSelection = function($o) { 763 | $o.each(function() { 764 | this.onselectstart = function() { return false; }; 765 | }).css('-webkit-user-select', 'none'); 766 | }; 767 | 768 | var controller = function() { 769 | var id = 'controller'; 770 | return function($ui, controller) { 771 | if (arguments.length == 1) { 772 | return $.data($ui[0], id); 773 | } else if (arguments.length == 2) { 774 | $.data($ui[0], id, controller); 775 | } 776 | }; 777 | }(); 778 | 779 | var eventQueue = function() { 780 | var delay = 50; // ms 781 | var limit = 40; // ms 782 | var _queue = null; 783 | var postEvent = function(event) { 784 | if (_queue == null) { 785 | _queue = []; 786 | } 787 | _queue.push(event); 788 | }; 789 | var dispatchEvent = function() { 790 | var queue = _queue; 791 | _queue = null; 792 | while (queue.length > 0) { 793 | var e = queue.shift(); 794 | e.target.trigger(e.type); 795 | } 796 | }; 797 | var getTime = function() { 798 | return new Date().getTime(); 799 | }; 800 | var timerHandler = function() { 801 | var start = getTime(); 802 | while (_queue != null && getTime() - start < limit) { 803 | dispatchEvent(); 804 | } 805 | window.setTimeout(timerHandler, 806 | Math.max(delay - limit, delay - (getTime() - start) ) ); 807 | }; 808 | timerHandler(); 809 | return { 810 | postEvent: postEvent 811 | }; 812 | }(); 813 | 814 | var unit = 16; 815 | var fontSize = 12; 816 | 817 | var createLabel = function(text) { 818 | return createSVGElement('text'). 819 | text(text). 820 | css('font-size', fontSize + 'px'); 821 | }; 822 | 823 | var createNode = function(type, label, description, headless) { 824 | var $node = createSVGElement('g'). 825 | attr('simcir-node-type', type); 826 | if (!headless) { 827 | $node.attr('class', 'simcir-node'); 828 | } 829 | var node = createNodeController({ 830 | $ui: $node, type: type, label: label, 831 | description: description, headless: headless}); 832 | if (type == 'in') { 833 | controller($node, createInputNodeController(node) ); 834 | } else if (type == 'out') { 835 | controller($node, createOutputNodeController(node) ); 836 | } else { 837 | throw 'unknown type:' + type; 838 | } 839 | return $node; 840 | }; 841 | 842 | var isActiveNode = function($o) { 843 | return $o.closest('.simcir-node').length == 1 && 844 | $o.closest('.simcir-toolbox').length == 0; 845 | }; 846 | 847 | var createNodeController = function(node) { 848 | var _value = null; 849 | var setValue = function(value, force) { 850 | if (_value === value && !force) { 851 | return; 852 | } 853 | _value = value; 854 | eventQueue.postEvent({target: node.$ui, type: 'nodeValueChange'}); 855 | }; 856 | var getValue = function() { 857 | return _value; 858 | }; 859 | 860 | if (!node.headless) { 861 | 862 | node.$ui.attr('class', 'simcir-node simcir-node-type-' + node.type); 863 | 864 | var $circle = createSVGElement('circle'). 865 | attr({cx: 0, cy: 0, r: 4}); 866 | node.$ui.on('mouseover', function(event) { 867 | if (isActiveNode(node.$ui) ) { 868 | node.$ui.addClass('simcir-node-hover'); 869 | } 870 | }); 871 | node.$ui.on('mouseout', function(event) { 872 | if (isActiveNode(node.$ui) ) { 873 | node.$ui.removeClass('simcir-node-hover'); 874 | } 875 | }); 876 | node.$ui.append($circle); 877 | var appendLabel = function(text, align) { 878 | var $label = createLabel(text). 879 | attr('class', 'simcir-node-label'); 880 | enableEvents($label, false); 881 | if (align == 'right') { 882 | $label.attr('text-anchor', 'start'). 883 | attr('x', 6). 884 | attr('y', fontSize / 2); 885 | } else if (align == 'left') { 886 | $label.attr('text-anchor', 'end'). 887 | attr('x', -6). 888 | attr('y', fontSize / 2); 889 | } 890 | node.$ui.append($label); 891 | }; 892 | if (node.label) { 893 | if (node.type == 'in') { 894 | appendLabel(node.label, 'right'); 895 | } else if (node.type == 'out') { 896 | appendLabel(node.label, 'left'); 897 | } 898 | } 899 | if (node.description) { 900 | if (node.type == 'in') { 901 | appendLabel(node.description, 'left'); 902 | } else if (node.type == 'out') { 903 | appendLabel(node.description, 'right'); 904 | } 905 | } 906 | node.$ui.on('nodeValueChange', function(event) { 907 | if (_value != null) { 908 | node.$ui.addClass('simcir-node-hot'); 909 | } else { 910 | node.$ui.removeClass('simcir-node-hot'); 911 | } 912 | }); 913 | } 914 | 915 | return $.extend(node, { 916 | setValue: setValue, 917 | getValue: getValue 918 | }); 919 | }; 920 | 921 | var createInputNodeController = function(node) { 922 | var output = null; 923 | var setOutput = function(outNode) { 924 | output = outNode; 925 | }; 926 | var getOutput = function() { 927 | return output; 928 | }; 929 | return $.extend(node, { 930 | setOutput: setOutput, 931 | getOutput: getOutput 932 | }); 933 | }; 934 | 935 | var createOutputNodeController = function(node) { 936 | var inputs = []; 937 | var super_setValue = node.setValue; 938 | var setValue = function(value) { 939 | super_setValue(value); 940 | for (var i = 0; i < inputs.length; i += 1) { 941 | inputs[i].setValue(value); 942 | } 943 | }; 944 | var connectTo = function(inNode) { 945 | if (inNode.getOutput() != null) { 946 | inNode.getOutput().disconnectFrom(inNode); 947 | } 948 | inNode.setOutput(node); 949 | inputs.push(inNode); 950 | inNode.setValue(node.getValue(), true); 951 | }; 952 | var disconnectFrom = function(inNode) { 953 | if (inNode.getOutput() != node) { 954 | throw 'not connected.'; 955 | } 956 | inNode.setOutput(null); 957 | inNode.setValue(null, true); 958 | inputs = $.grep(inputs, function(v) { 959 | return v != inNode; 960 | }); 961 | }; 962 | var getInputs = function() { 963 | return inputs; 964 | }; 965 | return $.extend(node, { 966 | setValue: setValue, 967 | getInputs: getInputs, 968 | connectTo: connectTo, 969 | disconnectFrom: disconnectFrom 970 | }); 971 | }; 972 | 973 | var createDevice = function(deviceDef, headless, scope) { 974 | headless = headless || false; 975 | scope = scope || null; 976 | var $dev = createSVGElement('g'); 977 | if (!headless) { 978 | $dev.attr('class', 'simcir-device'); 979 | } 980 | controller($dev, createDeviceController( 981 | {$ui: $dev, deviceDef: deviceDef, 982 | headless: headless, scope: scope, doc: null}) ); 983 | var factory = factories[deviceDef.type]; 984 | if (factory) { 985 | factory(controller($dev) ); 986 | } 987 | if (!headless) { 988 | controller($dev).createUI(); 989 | } 990 | return $dev; 991 | }; 992 | 993 | var createDeviceController = function(device) { 994 | var inputs = []; 995 | var outputs = []; 996 | var addInput = function(label, description) { 997 | var $node = createNode('in', label, description, device.headless); 998 | $node.on('nodeValueChange', function(event) { 999 | device.$ui.trigger('inputValueChange'); 1000 | }); 1001 | if (!device.headless) { 1002 | device.$ui.append($node); 1003 | } 1004 | var node = controller($node); 1005 | inputs.push(node); 1006 | return node; 1007 | }; 1008 | var addOutput = function(label, description) { 1009 | var $node = createNode('out', label, description, device.headless); 1010 | if (!device.headless) { 1011 | device.$ui.append($node); 1012 | } 1013 | var node = controller($node); 1014 | outputs.push(node); 1015 | return node; 1016 | }; 1017 | var getInputs = function() { 1018 | return inputs; 1019 | }; 1020 | var getOutputs = function() { 1021 | return outputs; 1022 | }; 1023 | var disconnectAll = function() { 1024 | $.each(getInputs(), function(i, inNode) { 1025 | var outNode = inNode.getOutput(); 1026 | if (outNode != null) { 1027 | outNode.disconnectFrom(inNode); 1028 | } 1029 | }); 1030 | $.each(getOutputs(), function(i, outNode) { 1031 | $.each(outNode.getInputs(), function(i, inNode) { 1032 | outNode.disconnectFrom(inNode); 1033 | }); 1034 | }); 1035 | }; 1036 | device.$ui.on('dispose', function() { 1037 | $.each(getInputs(), function(i, inNode) { 1038 | inNode.$ui.remove(); 1039 | }); 1040 | $.each(getOutputs(), function(i, outNode) { 1041 | outNode.$ui.remove(); 1042 | }); 1043 | device.$ui.remove(); 1044 | } ); 1045 | 1046 | var selected = false; 1047 | var setSelected = function(value) { 1048 | selected = value; 1049 | device.$ui.trigger('deviceSelect'); 1050 | }; 1051 | var isSelected = function() { 1052 | return selected; 1053 | }; 1054 | 1055 | var label = device.deviceDef.label; 1056 | var defaultLabel = device.deviceDef.type; 1057 | if (typeof label == 'undefined') { 1058 | label = defaultLabel; 1059 | } 1060 | var setLabel = function(value) { 1061 | value = value.replace(/^\s+|\s+$/g, ''); 1062 | label = value || defaultLabel; 1063 | device.$ui.trigger('deviceLabelChange'); 1064 | }; 1065 | var getLabel = function() { 1066 | return label; 1067 | }; 1068 | 1069 | var getSize = function() { 1070 | var nodes = Math.max(device.getInputs().length, 1071 | device.getOutputs().length); 1072 | return { width: unit * 2, 1073 | height: unit * Math.max(2, device.halfPitch? 1074 | (nodes + 1) / 2 : nodes)}; 1075 | }; 1076 | 1077 | var layoutUI = function() { 1078 | 1079 | var size = device.getSize(); 1080 | var w = size.width; 1081 | var h = size.height; 1082 | 1083 | device.$ui.children('.simcir-device-body'). 1084 | attr({x: 0, y: 0, width: w, height: h}); 1085 | 1086 | var pitch = device.halfPitch? unit / 2 : unit; 1087 | var layoutNodes = function(nodes, x) { 1088 | var offset = (h - pitch * (nodes.length - 1) ) / 2; 1089 | $.each(nodes, function(i, node) { 1090 | transform(node.$ui, x, pitch * i + offset); 1091 | }); 1092 | }; 1093 | layoutNodes(getInputs(), 0); 1094 | layoutNodes(getOutputs(), w); 1095 | 1096 | device.$ui.children('.simcir-device-label'). 1097 | attr({x: w / 2, y: h + fontSize}); 1098 | }; 1099 | 1100 | var createUI = function() { 1101 | 1102 | device.$ui.attr('class', 'simcir-device'); 1103 | device.$ui.on('deviceSelect', function() { 1104 | if (selected) { 1105 | $(this).addClass('simcir-device-selected'); 1106 | } else { 1107 | $(this).removeClass('simcir-device-selected'); 1108 | } 1109 | }); 1110 | 1111 | var $body = createSVGElement('rect'). 1112 | attr('class', 'simcir-device-body'). 1113 | attr('rx', 2).attr('ry', 2); 1114 | device.$ui.prepend($body); 1115 | 1116 | var $label = createLabel(label). 1117 | attr('class', 'simcir-device-label'). 1118 | attr('text-anchor', 'middle'); 1119 | device.$ui.on('deviceLabelChange', function() { 1120 | $label.text(getLabel() ); 1121 | }); 1122 | 1123 | var label_dblClickHandler = function(event) { 1124 | event.preventDefault(); 1125 | event.stopPropagation(); 1126 | var $workspace = $(event.target).closest('.simcir-workspace'); 1127 | if (!controller($workspace).data().editable) { 1128 | return; 1129 | } 1130 | var title = 'Enter device name '; 1131 | var $labelEditor = $(''). 1132 | addClass('simcir-label-editor'). 1133 | val($label.text() ). 1134 | on('keydown', function(event) { 1135 | if (event.keyCode == 13) { 1136 | // ENTER 1137 | setLabel($(this).val() ); 1138 | $dlg.remove(); 1139 | } else if (event.keyCode == 27) { 1140 | // ESC 1141 | $dlg.remove(); 1142 | } 1143 | } ); 1144 | var $placeHolder = $('
'). 1145 | append($labelEditor); 1146 | var $dlg = showDialog(title, $placeHolder); 1147 | $labelEditor.focus(); 1148 | }; 1149 | device.$ui.on('deviceAdd', function() { 1150 | $label.on('dblclick', label_dblClickHandler); 1151 | } ); 1152 | device.$ui.on('deviceRemove', function() { 1153 | $label.off('dblclick', label_dblClickHandler); 1154 | } ); 1155 | device.$ui.append($label); 1156 | 1157 | layoutUI(); 1158 | 1159 | }; 1160 | 1161 | var getState = function() { return null; }; 1162 | 1163 | return $.extend(device, { 1164 | addInput: addInput, 1165 | addOutput: addOutput, 1166 | getInputs: getInputs, 1167 | getOutputs: getOutputs, 1168 | disconnectAll: disconnectAll, 1169 | setSelected: setSelected, 1170 | isSelected: isSelected, 1171 | getLabel: getLabel, 1172 | halfPitch: false, 1173 | getSize: getSize, 1174 | createUI: createUI, 1175 | layoutUI: layoutUI, 1176 | getState: getState 1177 | }); 1178 | }; 1179 | 1180 | var createConnector = function(x1, y1, x2, y2) { 1181 | return createSVGElement('path'). 1182 | attr('d', 'M ' + x1 + ' ' + y1 + ' L ' + x2 + ' ' + y2). 1183 | attr('class', 'simcir-connector'); 1184 | }; 1185 | 1186 | var connect = function($node1, $node2) { 1187 | var type1 = $node1.attr('simcir-node-type'); 1188 | var type2 = $node2.attr('simcir-node-type'); 1189 | if (type1 == 'in' && type2 == 'out') { 1190 | controller($node2).connectTo(controller($node1) ); 1191 | } else if (type1 == 'out' && type2 == 'in') { 1192 | controller($node1).connectTo(controller($node2) ); 1193 | } 1194 | }; 1195 | 1196 | var buildCircuit = function(data, headless, scope) { 1197 | var $devices = []; 1198 | var $devMap = {}; 1199 | var getNode = function(path) { 1200 | if (!path.match(/^(\w+)\.(in|out)([0-9]+)$/g) ) { 1201 | throw 'unknown path:' + path; 1202 | } 1203 | var devId = RegExp.$1; 1204 | var type = RegExp.$2; 1205 | var index = +RegExp.$3; 1206 | return (type == 'in')? 1207 | controller($devMap[devId]).getInputs()[index] : 1208 | controller($devMap[devId]).getOutputs()[index]; 1209 | }; 1210 | $.each(data.devices, function(i, deviceDef) { 1211 | var $dev = createDevice(deviceDef, headless, scope); 1212 | transform($dev, deviceDef.x, deviceDef.y); 1213 | $devices.push($dev); 1214 | $devMap[deviceDef.id] = $dev; 1215 | }); 1216 | $.each(data.connectors, function(i, conn) { 1217 | var nodeFrom = getNode(conn.from); 1218 | var nodeTo = getNode(conn.to); 1219 | if (nodeFrom && nodeTo) { 1220 | connect(nodeFrom.$ui, nodeTo.$ui); 1221 | } 1222 | }); 1223 | return $devices; 1224 | }; 1225 | 1226 | var dialogManager = function() { 1227 | var dialogs = []; 1228 | var updateDialogs = function($dlg, remove) { 1229 | var newDialogs = []; 1230 | $.each(dialogs, function(i) { 1231 | if (dialogs[i] != $dlg) { 1232 | newDialogs.push(dialogs[i]); 1233 | } 1234 | }); 1235 | if (!remove) { 1236 | newDialogs.push($dlg); 1237 | } 1238 | // renumber z-index 1239 | $.each(newDialogs, function(i) { 1240 | newDialogs[i].css('z-index', '' + (i + 1) ); 1241 | }); 1242 | dialogs = newDialogs; 1243 | }; 1244 | return { 1245 | add : function($dlg) { 1246 | updateDialogs($dlg); 1247 | }, 1248 | remove : function($dlg) { 1249 | updateDialogs($dlg, true); 1250 | }, 1251 | toFront : function($dlg) { 1252 | updateDialogs($dlg); 1253 | } 1254 | }; 1255 | }(); 1256 | 1257 | var showDialog = function(title, $content) { 1258 | var $closeButton = function() { 1259 | var r = 16; 1260 | var pad = 4; 1261 | var $btn = createSVG(r, r). 1262 | attr('class', 'simcir-dialog-close-button'); 1263 | var g = graphics($btn); 1264 | g.drawRect(0, 0, r, r); 1265 | g.attr['class'] = 'simcir-dialog-close-button-symbol'; 1266 | g.moveTo(pad, pad); 1267 | g.lineTo(r - pad, r - pad); 1268 | g.closePath(); 1269 | g.moveTo(r - pad, pad); 1270 | g.lineTo(pad, r - pad); 1271 | g.closePath(); 1272 | return $btn; 1273 | }(); 1274 | var $title = $('
'). 1275 | addClass('simcir-dialog-title'). 1276 | text(title). 1277 | css('cursor', 'default'). 1278 | on('mousedown', function(event) { 1279 | event.preventDefault(); 1280 | }); 1281 | var $dlg = $('
'). 1282 | addClass('simcir-dialog'). 1283 | css({position:'absolute'}). 1284 | append($title.css('float', 'left') ). 1285 | append($closeButton.css('float', 'right') ). 1286 | append($('
').css('clear', 'both') ). 1287 | append($content); 1288 | $('BODY').append($dlg); 1289 | dialogManager.add($dlg); 1290 | var dragPoint = null; 1291 | var dlg_mouseDownHandler = function(event) { 1292 | if (!$(event.target).hasClass('simcir-dialog') && 1293 | !$(event.target).hasClass('simcir-dialog-title') ) { 1294 | return; 1295 | } 1296 | event.preventDefault(); 1297 | dialogManager.toFront($dlg); 1298 | var off = $dlg.offset(); 1299 | dragPoint = { 1300 | x: event.pageX - off.left, 1301 | y: event.pageY - off.top}; 1302 | $(document).on('mousemove', dlg_mouseMoveHandler); 1303 | $(document).on('mouseup', dlg_mouseUpHandler); 1304 | }; 1305 | var dlg_mouseMoveHandler = function(event) { 1306 | moveTo( 1307 | event.pageX - dragPoint.x, 1308 | event.pageY - dragPoint.y); 1309 | }; 1310 | var dlg_mouseUpHandler = function(event) { 1311 | $(document).off('mousemove', dlg_mouseMoveHandler); 1312 | $(document).off('mouseup', dlg_mouseUpHandler); 1313 | }; 1314 | $dlg.on('mousedown', dlg_mouseDownHandler); 1315 | $closeButton.on('mousedown', function() { 1316 | $dlg.trigger('close'); 1317 | $dlg.remove(); 1318 | dialogManager.remove($dlg); 1319 | }); 1320 | var w = $dlg.width(); 1321 | var h = $dlg.height(); 1322 | var cw = $(window).width(); 1323 | var ch = $(window).height(); 1324 | var getProp = function(id) { 1325 | return $('HTML')[id]() || $('BODY')[id](); 1326 | }; 1327 | var x = (cw - w) / 2 + getProp('scrollLeft'); 1328 | var y = (ch - h) / 2 + getProp('scrollTop'); 1329 | var moveTo = function(x, y) { 1330 | $dlg.css({left: x + 'px', top: y + 'px'}); 1331 | }; 1332 | moveTo(x, y); 1333 | return $dlg; 1334 | }; 1335 | 1336 | var createDeviceRefFactory = function(data) { 1337 | return function(device) { 1338 | var $devs = buildCircuit(data, true, {}); 1339 | var $ports = []; 1340 | $.each($devs, function(i, $dev) { 1341 | var deviceDef = controller($dev).deviceDef; 1342 | if (deviceDef.type == 'In' || deviceDef.type == 'Out') { 1343 | $ports.push($dev); 1344 | } 1345 | }); 1346 | $ports.sort(function($p1, $p2) { 1347 | var x1 = controller($p1).deviceDef.x; 1348 | var y1 = controller($p1).deviceDef.y; 1349 | var x2 = controller($p2).deviceDef.x; 1350 | var y2 = controller($p2).deviceDef.y; 1351 | if (x1 == x2) { 1352 | return (y1 < y2)? -1 : 1; 1353 | } 1354 | return (x1 < x2)? -1 : 1; 1355 | }); 1356 | var getDesc = function(port) { 1357 | return port? port.description : ''; 1358 | }; 1359 | $.each($ports, function(i, $port) { 1360 | var port = controller($port); 1361 | var portDef = port.deviceDef; 1362 | var inPort; 1363 | var outPort; 1364 | if (portDef.type == 'In') { 1365 | outPort = port.getOutputs()[0]; 1366 | inPort = device.addInput(portDef.label, 1367 | getDesc(outPort.getInputs()[0]) ); 1368 | // force disconnect test devices that connected to In-port 1369 | var inNode = port.getInputs()[0]; 1370 | if (inNode.getOutput() != null) { 1371 | inNode.getOutput().disconnectFrom(inNode); 1372 | } 1373 | } else if (portDef.type == 'Out') { 1374 | inPort = port.getInputs()[0]; 1375 | outPort = device.addOutput(portDef.label, 1376 | getDesc(inPort.getOutput() ) ); 1377 | // force disconnect test devices that connected to Out-port 1378 | var outNode = port.getOutputs()[0]; 1379 | $.each(outNode.getInputs(), function(i, inNode) { 1380 | if (inNode.getOutput() != null) { 1381 | inNode.getOutput().disconnectFrom(inNode); 1382 | } 1383 | } ); 1384 | } 1385 | inPort.$ui.on('nodeValueChange', function() { 1386 | outPort.setValue(inPort.getValue() ); 1387 | }); 1388 | }); 1389 | var super_getSize = device.getSize; 1390 | device.getSize = function() { 1391 | var size = super_getSize(); 1392 | return {width: unit * 4, height: size.height}; 1393 | }; 1394 | device.$ui.on('dispose', function() { 1395 | $.each($devs, function(i, $dev) { 1396 | $dev.trigger('dispose'); 1397 | }); 1398 | } ); 1399 | device.$ui.on('dblclick', function(event) { 1400 | // open library, 1401 | event.preventDefault(); 1402 | event.stopPropagation(); 1403 | showDialog(device.deviceDef.label || device.deviceDef.type, 1404 | setupSimcir($('
'), data) ).on('close', function() { 1405 | $(this).find('.simcir-workspace').trigger('dispose'); 1406 | }); 1407 | }); 1408 | }; 1409 | }; 1410 | 1411 | var createCustomLayoutDeviceRefFactory = function(data) { 1412 | return function(device) { 1413 | var $devs = buildCircuit(data, true, {}); 1414 | var $ports = []; 1415 | var intfs = []; 1416 | $.each($devs, function(i, $dev) { 1417 | var deviceDef = controller($dev).deviceDef; 1418 | if (deviceDef.type == 'In' || deviceDef.type == 'Out') { 1419 | $ports.push($dev); 1420 | } 1421 | }); 1422 | var getDesc = function(port) { 1423 | return port? port.description : ''; 1424 | }; 1425 | $.each($ports, function(i, $port) { 1426 | var port = controller($port); 1427 | var portDef = port.deviceDef; 1428 | var inPort; 1429 | var outPort; 1430 | if (portDef.type == 'In') { 1431 | outPort = port.getOutputs()[0]; 1432 | inPort = device.addInput(); 1433 | intfs.push({ node : inPort, label : portDef.label, 1434 | desc : getDesc(outPort.getInputs()[0]) }); 1435 | // force disconnect test devices that connected to In-port 1436 | var inNode = port.getInputs()[0]; 1437 | if (inNode.getOutput() != null) { 1438 | inNode.getOutput().disconnectFrom(inNode); 1439 | } 1440 | } else if (portDef.type == 'Out') { 1441 | inPort = port.getInputs()[0]; 1442 | outPort = device.addOutput(); 1443 | intfs.push({ node : outPort, label : portDef.label, 1444 | desc : getDesc(inPort.getOutput() ) }); 1445 | // force disconnect test devices that connected to Out-port 1446 | var outNode = port.getOutputs()[0]; 1447 | $.each(outNode.getInputs(), function(i, inNode) { 1448 | if (inNode.getOutput() != null) { 1449 | inNode.getOutput().disconnectFrom(inNode); 1450 | } 1451 | } ); 1452 | } 1453 | inPort.$ui.on('nodeValueChange', function() { 1454 | outPort.setValue(inPort.getValue() ); 1455 | }); 1456 | }); 1457 | var layout = data.layout; 1458 | var cols = layout.cols; 1459 | var rows = layout.rows; 1460 | rows = ~~( (Math.max(1, rows) + 1) / 2) * 2; 1461 | cols = ~~( (Math.max(1, cols) + 1) / 2) * 2; 1462 | var updateIntf = function(intf, x, y, align) { 1463 | transform(intf.node.$ui, x, y); 1464 | if (!intf.$label) { 1465 | intf.$label = createLabel(intf.label). 1466 | attr('class', 'simcir-node-label'); 1467 | enableEvents(intf.$label, false); 1468 | intf.node.$ui.append(intf.$label); 1469 | } 1470 | if (align == 'right') { 1471 | intf.$label.attr('text-anchor', 'start'). 1472 | attr('x', 6). 1473 | attr('y', fontSize / 2); 1474 | } else if (align == 'left') { 1475 | intf.$label.attr('text-anchor', 'end'). 1476 | attr('x', -6). 1477 | attr('y', fontSize / 2); 1478 | } else if (align == 'top') { 1479 | intf.$label.attr('text-anchor', 'middle'). 1480 | attr('x', 0). 1481 | attr('y', -6); 1482 | } else if (align == 'bottom') { 1483 | intf.$label.attr('text-anchor', 'middle'). 1484 | attr('x', 0). 1485 | attr('y', fontSize + 6); 1486 | } 1487 | }; 1488 | var doLayout = function() { 1489 | var x = 0; 1490 | var y = 0; 1491 | var w = unit * cols / 2; 1492 | var h = unit * rows / 2; 1493 | device.$ui.children('.simcir-device-label'). 1494 | attr({y : y + h + fontSize}); 1495 | device.$ui.children('.simcir-device-body'). 1496 | attr({x: x, y: y, width: w, height: h}); 1497 | $.each(intfs, function(i, intf) { 1498 | if (layout.nodes[intf.label] && 1499 | layout.nodes[intf.label].match(/^([TBLR])([0-9]+)$/) ) { 1500 | var off = +RegExp.$2 * unit / 2; 1501 | switch(RegExp.$1) { 1502 | case 'T' : updateIntf(intf, x + off, y, 'bottom'); break; 1503 | case 'B' : updateIntf(intf, x + off, y + h, 'top'); break; 1504 | case 'L' : updateIntf(intf, x, y + off, 'right'); break; 1505 | case 'R' : updateIntf(intf, x + w, y + off, 'left'); break; 1506 | } 1507 | } else { 1508 | transform(intf.node.$ui, 0, 0); 1509 | } 1510 | }); 1511 | }; 1512 | device.getSize = function() { 1513 | return {width: unit * cols / 2, height: unit * rows / 2}; 1514 | }; 1515 | device.$ui.on('dispose', function() { 1516 | $.each($devs, function(i, $dev) { 1517 | $dev.trigger('dispose'); 1518 | }); 1519 | } ); 1520 | if (data.layout.hideLabelOnWorkspace) { 1521 | device.$ui.on('deviceAdd', function() { 1522 | device.$ui.children('.simcir-device-label').css('display', 'none'); 1523 | }).on('deviceRemove', function() { 1524 | device.$ui.children('.simcir-device-label').css('display', ''); 1525 | }); 1526 | } 1527 | device.$ui.on('dblclick', function(event) { 1528 | // open library, 1529 | event.preventDefault(); 1530 | event.stopPropagation(); 1531 | showDialog(device.deviceDef.label || device.deviceDef.type, 1532 | setupSimcir($('
'), data) ).on('close', function() { 1533 | $(this).find('.simcir-workspace').trigger('dispose'); 1534 | }); 1535 | }); 1536 | var super_createUI = device.createUI; 1537 | device.createUI = function() { 1538 | super_createUI(); 1539 | doLayout(); 1540 | }; 1541 | }; 1542 | }; 1543 | 1544 | var factories = {}; 1545 | var defaultToolbox = []; 1546 | var registerDevice = function(type, factory, deprecated) { 1547 | if (typeof factory == 'object') { 1548 | if (typeof factory.layout == 'object') { 1549 | factory = createCustomLayoutDeviceRefFactory(factory); 1550 | } else { 1551 | factory = createDeviceRefFactory(factory); 1552 | } 1553 | } 1554 | factories[type] = factory; 1555 | if (!deprecated) { 1556 | defaultToolbox.push({type: type}); 1557 | } 1558 | }; 1559 | 1560 | var createScrollbar = function() { 1561 | 1562 | // vertical only. 1563 | var _value = 0; 1564 | var _min = 0; 1565 | var _max = 0; 1566 | var _barSize = 0; 1567 | var _width = 0; 1568 | var _height = 0; 1569 | 1570 | var $body = createSVGElement('rect'); 1571 | var $bar = createSVGElement('g'). 1572 | append(createSVGElement('rect') ). 1573 | attr('class', 'simcir-scrollbar-bar'); 1574 | var $scrollbar = createSVGElement('g'). 1575 | attr('class', 'simcir-scrollbar'). 1576 | append($body).append($bar). 1577 | on('unitup', function(event) { 1578 | setValue(_value - unit * 2); 1579 | }).on('unitdown', function(event) { 1580 | setValue(_value + unit * 2); 1581 | }).on('rollup', function(event) { 1582 | setValue(_value - _barSize); 1583 | }).on('rolldown', function(event) { 1584 | setValue(_value + _barSize); 1585 | }); 1586 | 1587 | var dragPoint = null; 1588 | var bar_mouseDownHandler = function(event) { 1589 | event.preventDefault(); 1590 | event.stopPropagation(); 1591 | var pos = transform($bar); 1592 | dragPoint = { 1593 | x: event.pageX - pos.x, 1594 | y: event.pageY - pos.y}; 1595 | $(document).on('mousemove', bar_mouseMoveHandler); 1596 | $(document).on('mouseup', bar_mouseUpHandler); 1597 | }; 1598 | var bar_mouseMoveHandler = function(event) { 1599 | calc(function(unitSize) { 1600 | setValue( (event.pageY - dragPoint.y) / unitSize); 1601 | }); 1602 | }; 1603 | var bar_mouseUpHandler = function(event) { 1604 | $(document).off('mousemove', bar_mouseMoveHandler); 1605 | $(document).off('mouseup', bar_mouseUpHandler); 1606 | }; 1607 | $bar.on('mousedown', bar_mouseDownHandler); 1608 | var body_mouseDownHandler = function(event) { 1609 | event.preventDefault(); 1610 | event.stopPropagation(); 1611 | var off = $scrollbar.parent('svg').offset(); 1612 | var pos = transform($scrollbar); 1613 | var y = event.pageY - off.top - pos.y; 1614 | var barPos = transform($bar); 1615 | if (y < barPos.y) { 1616 | $scrollbar.trigger('rollup'); 1617 | } else { 1618 | $scrollbar.trigger('rolldown'); 1619 | } 1620 | }; 1621 | $body.on('mousedown', body_mouseDownHandler); 1622 | 1623 | var setSize = function(width, height) { 1624 | _width = width; 1625 | _height = height; 1626 | layout(); 1627 | }; 1628 | var layout = function() { 1629 | 1630 | $body.attr({x: 0, y: 0, width: _width, height: _height}); 1631 | 1632 | var visible = _max - _min > _barSize; 1633 | $bar.css('display', visible? 'inline' : 'none'); 1634 | if (!visible) { 1635 | return; 1636 | } 1637 | calc(function(unitSize) { 1638 | $bar.children('rect'). 1639 | attr({x: 0, y: 0, width: _width, height: _barSize * unitSize}); 1640 | transform($bar, 0, _value * unitSize); 1641 | }); 1642 | }; 1643 | var calc = function(f) { 1644 | f(_height / (_max - _min) ); 1645 | }; 1646 | var setValue = function(value) { 1647 | setValues(value, _min, _max, _barSize); 1648 | }; 1649 | var setValues = function(value, min, max, barSize) { 1650 | value = Math.max(min, Math.min(value, max - barSize) ); 1651 | var changed = (value != _value); 1652 | _value = value; 1653 | _min = min; 1654 | _max = max; 1655 | _barSize = barSize; 1656 | layout(); 1657 | if (changed) { 1658 | $scrollbar.trigger('scrollValueChange'); 1659 | } 1660 | }; 1661 | var getValue = function() { 1662 | return _value; 1663 | }; 1664 | controller($scrollbar, { 1665 | setSize: setSize, 1666 | setValues: setValues, 1667 | getValue: getValue 1668 | }); 1669 | return $scrollbar; 1670 | }; 1671 | 1672 | var getUniqueId = function() { 1673 | var uniqueIdCount = 0; 1674 | return function() { 1675 | return 'simcir-id' + uniqueIdCount++; 1676 | }; 1677 | }(); 1678 | 1679 | var createWorkspace = function(data) { 1680 | 1681 | data = $.extend({ 1682 | width: 400, 1683 | height: 200, 1684 | showToolbox: true, 1685 | editable: true, 1686 | toolbox: defaultToolbox, 1687 | devices: [], 1688 | connectors: [], 1689 | }, data); 1690 | 1691 | var scope = {}; 1692 | 1693 | var workspaceWidth = data.width; 1694 | var workspaceHeight = data.height; 1695 | var barWidth = unit; 1696 | var toolboxWidth = data.showToolbox? unit * 6 + barWidth : 0; 1697 | 1698 | var connectorsValid = true; 1699 | var connectorsValidator = function() { 1700 | if (!connectorsValid) { 1701 | updateConnectors(); 1702 | connectorsValid = true; 1703 | } 1704 | }; 1705 | 1706 | var $workspace = createSVG( 1707 | workspaceWidth, workspaceHeight). 1708 | attr('class', 'simcir-workspace'). 1709 | on('nodeValueChange', function(event) { 1710 | connectorsValid = false; 1711 | window.setTimeout(connectorsValidator, 0); 1712 | }). 1713 | on('dispose', function() { 1714 | $(this).find('.simcir-device').trigger('dispose'); 1715 | $toolboxPane.remove(); 1716 | $workspace.remove(); 1717 | }); 1718 | 1719 | disableSelection($workspace); 1720 | 1721 | var $defs = createSVGElement('defs'); 1722 | $workspace.append($defs); 1723 | 1724 | !function() { 1725 | 1726 | // fill with pin hole pattern. 1727 | var patId = getUniqueId(); 1728 | var pitch = unit / 2; 1729 | var w = workspaceWidth - toolboxWidth; 1730 | var h = workspaceHeight; 1731 | 1732 | $defs.append(createSVGElement('pattern'). 1733 | attr({id: patId, x: 0, y: 0, 1734 | width: pitch / w, height: pitch / h}).append( 1735 | createSVGElement('rect').attr('class', 'simcir-pin-hole'). 1736 | attr({x: 0, y: 0, width: 1, height: 1}) ) ); 1737 | 1738 | $workspace.append(createSVGElement('rect'). 1739 | attr({x: toolboxWidth, y: 0, width: w, height: h}). 1740 | css({fill: 'url(#' + patId + ')'}) ); 1741 | }(); 1742 | 1743 | var $toolboxDevicePane = createSVGElement('g'); 1744 | var $scrollbar = createScrollbar(); 1745 | $scrollbar.on('scrollValueChange', function(event) { 1746 | transform($toolboxDevicePane, 0, 1747 | -controller($scrollbar).getValue() ); 1748 | }); 1749 | controller($scrollbar).setSize(barWidth, workspaceHeight); 1750 | transform($scrollbar, toolboxWidth - barWidth, 0); 1751 | var $toolboxPane = createSVGElement('g'). 1752 | attr('class', 'simcir-toolbox'). 1753 | append(createSVGElement('rect'). 1754 | attr({x: 0, y: 0, 1755 | width: toolboxWidth, 1756 | height: workspaceHeight}) ). 1757 | append($toolboxDevicePane). 1758 | append($scrollbar).on('wheel', function(event) { 1759 | event.preventDefault(); 1760 | var oe = event.originalEvent || event; 1761 | if (oe.deltaY < 0) { 1762 | $scrollbar.trigger('unitup'); 1763 | } else if (oe.deltaY > 0) { 1764 | $scrollbar.trigger('unitdown'); 1765 | } 1766 | }); 1767 | 1768 | var $devicePane = createSVGElement('g'); 1769 | transform($devicePane, toolboxWidth, 0); 1770 | var $connectorPane = createSVGElement('g'); 1771 | var $temporaryPane = createSVGElement('g'); 1772 | 1773 | enableEvents($connectorPane, false); 1774 | enableEvents($temporaryPane, false); 1775 | 1776 | if (data.showToolbox) { 1777 | $workspace.append($toolboxPane); 1778 | } 1779 | $workspace.append($devicePane); 1780 | $workspace.append($connectorPane); 1781 | $workspace.append($temporaryPane); 1782 | 1783 | var addDevice = function($dev) { 1784 | $devicePane.append($dev); 1785 | $dev.trigger('deviceAdd'); 1786 | }; 1787 | 1788 | var removeDevice = function($dev) { 1789 | $dev.trigger('deviceRemove'); 1790 | // before remove, disconnect all 1791 | controller($dev).disconnectAll(); 1792 | $dev.trigger('dispose'); 1793 | updateConnectors(); 1794 | }; 1795 | 1796 | var disconnect = function($inNode) { 1797 | var inNode = controller($inNode); 1798 | if (inNode.getOutput() != null) { 1799 | inNode.getOutput().disconnectFrom(inNode); 1800 | } 1801 | updateConnectors(); 1802 | }; 1803 | 1804 | var updateConnectors = function() { 1805 | $connectorPane.children().remove(); 1806 | $devicePane.children('.simcir-device').each(function() { 1807 | var device = controller($(this) ); 1808 | $.each(device.getInputs(), function(i, inNode) { 1809 | if (inNode.getOutput() != null) { 1810 | var p1 = offset(inNode.$ui); 1811 | var p2 = offset(inNode.getOutput().$ui); 1812 | var $conn = createConnector(p1.x, p1.y, p2.x, p2.y); 1813 | if (inNode.getOutput().getValue() != null) { 1814 | $conn.addClass('simcir-connector-hot'); 1815 | } 1816 | $connectorPane.append($conn); 1817 | } 1818 | }); 1819 | }); 1820 | }; 1821 | 1822 | var loadToolbox = function(data) { 1823 | var vgap = 8; 1824 | var y = vgap; 1825 | $.each(data.toolbox, function(i, deviceDef) { 1826 | var $dev = createDevice(deviceDef); 1827 | $toolboxDevicePane.append($dev); 1828 | var size = controller($dev).getSize(); 1829 | transform($dev, (toolboxWidth - barWidth - size.width) / 2, y); 1830 | y += (size.height + fontSize + vgap); 1831 | }); 1832 | controller($scrollbar).setValues(0, 0, y, workspaceHeight); 1833 | }; 1834 | 1835 | var getData = function() { 1836 | 1837 | // renumber all id 1838 | var devIdCount = 0; 1839 | $devicePane.children('.simcir-device').each(function() { 1840 | var $dev = $(this); 1841 | var device = controller($dev); 1842 | var devId = 'dev' + devIdCount++; 1843 | device.id = devId; 1844 | $.each(device.getInputs(), function(i, node) { 1845 | node.id = devId + '.in' + i; 1846 | }); 1847 | $.each(device.getOutputs(), function(i, node) { 1848 | node.id = devId + '.out' + i; 1849 | }); 1850 | }); 1851 | 1852 | var toolbox = []; 1853 | var devices = []; 1854 | var connectors = []; 1855 | var clone = function(obj) { 1856 | return JSON.parse(JSON.stringify(obj) ); 1857 | }; 1858 | $toolboxDevicePane.children('.simcir-device').each(function() { 1859 | var $dev = $(this); 1860 | var device = controller($dev); 1861 | toolbox.push(device.deviceDef); 1862 | }); 1863 | $devicePane.children('.simcir-device').each(function() { 1864 | var $dev = $(this); 1865 | var device = controller($dev); 1866 | $.each(device.getInputs(), function(i, inNode) { 1867 | if (inNode.getOutput() != null) { 1868 | connectors.push({from:inNode.id, to:inNode.getOutput().id}); 1869 | } 1870 | }); 1871 | var pos = transform($dev); 1872 | var deviceDef = clone(device.deviceDef); 1873 | deviceDef.id = device.id; 1874 | deviceDef.x = pos.x; 1875 | deviceDef.y = pos.y; 1876 | deviceDef.label = device.getLabel(); 1877 | var state = device.getState(); 1878 | if (state != null) { 1879 | deviceDef.state = state; 1880 | } 1881 | devices.push(deviceDef); 1882 | }); 1883 | return { 1884 | width: data.width, 1885 | height: data.height, 1886 | showToolbox: data.showToolbox, 1887 | editable: data.editable, 1888 | toolbox: toolbox, 1889 | devices: devices, 1890 | connectors: connectors 1891 | }; 1892 | }; 1893 | var getText = function() { 1894 | 1895 | var data = getData(); 1896 | 1897 | var buf = ''; 1898 | var print = function(s) { 1899 | buf += s; 1900 | }; 1901 | var println = function(s) { 1902 | print(s); 1903 | buf += '\r\n'; 1904 | }; 1905 | var printArray = function(array) { 1906 | $.each(array, function(i, item) { 1907 | println(' ' + JSON.stringify(item). 1908 | replace(//g, '\\u003e') + 1909 | (i + 1 < array.length? ',' : '') ); 1910 | }); 1911 | }; 1912 | println('{'); 1913 | println(' "width":' + data.width + ','); 1914 | println(' "height":' + data.height + ','); 1915 | println(' "showToolbox":' + data.showToolbox + ','); 1916 | println(' "toolbox":['); 1917 | printArray(data.toolbox); 1918 | println(' ],'); 1919 | println(' "devices":['); 1920 | printArray(data.devices); 1921 | println(' ],'); 1922 | println(' "connectors":['); 1923 | printArray(data.connectors); 1924 | println(' ]'); 1925 | print('}'); 1926 | return buf; 1927 | }; 1928 | 1929 | //------------------------------------------- 1930 | // mouse operations 1931 | 1932 | var dragMoveHandler = null; 1933 | var dragCompleteHandler = null; 1934 | 1935 | var adjustDevice = function($dev) { 1936 | var pitch = unit / 2; 1937 | var adjust = function(v) { return Math.round(v / pitch) * pitch; }; 1938 | var pos = transform($dev); 1939 | var size = controller($dev).getSize(); 1940 | var x = Math.max(0, Math.min(pos.x, 1941 | workspaceWidth - toolboxWidth - size.width) ); 1942 | var y = Math.max(0, Math.min(pos.y, 1943 | workspaceHeight - size.height) ); 1944 | transform($dev, adjust(x), adjust(y) ); 1945 | }; 1946 | 1947 | var beginConnect = function(event, $target) { 1948 | var $srcNode = $target.closest('.simcir-node'); 1949 | var off = $workspace.offset(); 1950 | var pos = offset($srcNode); 1951 | if ($srcNode.attr('simcir-node-type') == 'in') { 1952 | disconnect($srcNode); 1953 | } 1954 | dragMoveHandler = function(event) { 1955 | var x = event.pageX - off.left; 1956 | var y = event.pageY - off.top; 1957 | $temporaryPane.children().remove(); 1958 | $temporaryPane.append(createConnector(pos.x, pos.y, x, y) ); 1959 | }; 1960 | dragCompleteHandler = function(event) { 1961 | $temporaryPane.children().remove(); 1962 | var $dst = $(event.target); 1963 | if (isActiveNode($dst) ) { 1964 | var $dstNode = $dst.closest('.simcir-node'); 1965 | connect($srcNode, $dstNode); 1966 | updateConnectors(); 1967 | } 1968 | }; 1969 | }; 1970 | 1971 | var beginNewDevice = function(event, $target) { 1972 | var $dev = $target.closest('.simcir-device'); 1973 | var pos = offset($dev); 1974 | $dev = createDevice(controller($dev).deviceDef, false, scope); 1975 | transform($dev, pos.x, pos.y); 1976 | $temporaryPane.append($dev); 1977 | var dragPoint = { 1978 | x: event.pageX - pos.x, 1979 | y: event.pageY - pos.y}; 1980 | dragMoveHandler = function(event) { 1981 | transform($dev, 1982 | event.pageX - dragPoint.x, 1983 | event.pageY - dragPoint.y); 1984 | }; 1985 | dragCompleteHandler = function(event) { 1986 | var $target = $(event.target); 1987 | if ($target.closest('.simcir-toolbox').length == 0) { 1988 | $dev.detach(); 1989 | var pos = transform($dev); 1990 | transform($dev, pos.x - toolboxWidth, pos.y); 1991 | adjustDevice($dev); 1992 | addDevice($dev); 1993 | } else { 1994 | $dev.trigger('dispose'); 1995 | } 1996 | }; 1997 | }; 1998 | 1999 | var $selectedDevices = []; 2000 | var addSelected = function($dev) { 2001 | controller($dev).setSelected(true); 2002 | $selectedDevices.push($dev); 2003 | }; 2004 | var deselectAll = function() { 2005 | $devicePane.children('.simcir-device').each(function() { 2006 | controller($(this) ).setSelected(false); 2007 | }); 2008 | $selectedDevices = []; 2009 | }; 2010 | 2011 | var beginMoveDevice = function(event, $target) { 2012 | var $dev = $target.closest('.simcir-device'); 2013 | var pos = transform($dev); 2014 | if (!controller($dev).isSelected() ) { 2015 | deselectAll(); 2016 | addSelected($dev); 2017 | // to front. 2018 | $dev.parent().append($dev.detach() ); 2019 | } 2020 | 2021 | var dragPoint = { 2022 | x: event.pageX - pos.x, 2023 | y: event.pageY - pos.y}; 2024 | dragMoveHandler = function(event) { 2025 | // disable events while dragging. 2026 | enableEvents($dev, false); 2027 | var curPos = transform($dev); 2028 | var deltaPos = { 2029 | x: event.pageX - dragPoint.x - curPos.x, 2030 | y: event.pageY - dragPoint.y - curPos.y}; 2031 | $.each($selectedDevices, function(i, $dev) { 2032 | var curPos = transform($dev); 2033 | transform($dev, 2034 | curPos.x + deltaPos.x, 2035 | curPos.y + deltaPos.y); 2036 | }); 2037 | updateConnectors(); 2038 | }; 2039 | dragCompleteHandler = function(event) { 2040 | var $target = $(event.target); 2041 | enableEvents($dev, true); 2042 | $.each($selectedDevices, function(i, $dev) { 2043 | if ($target.closest('.simcir-toolbox').length == 0) { 2044 | adjustDevice($dev); 2045 | updateConnectors(); 2046 | } else { 2047 | removeDevice($dev); 2048 | } 2049 | }); 2050 | }; 2051 | }; 2052 | 2053 | var beginSelectDevice = function(event, $target) { 2054 | var intersect = function(rect1, rect2) { 2055 | return !( 2056 | rect1.x > rect2.x + rect2.width || 2057 | rect1.y > rect2.y + rect2.height || 2058 | rect1.x + rect1.width < rect2.x || 2059 | rect1.y + rect1.height < rect2.y); 2060 | }; 2061 | var pointToRect = function(p1, p2) { 2062 | return { 2063 | x: Math.min(p1.x, p2.x), 2064 | y: Math.min(p1.y, p2.y), 2065 | width: Math.abs(p1.x - p2.x), 2066 | height: Math.abs(p1.y - p2.y)}; 2067 | }; 2068 | deselectAll(); 2069 | var off = $workspace.offset(); 2070 | var pos = offset($devicePane); 2071 | var p1 = {x: event.pageX - off.left, y: event.pageY - off.top}; 2072 | dragMoveHandler = function(event) { 2073 | deselectAll(); 2074 | var p2 = {x: event.pageX - off.left, y: event.pageY - off.top}; 2075 | var selRect = pointToRect(p1, p2); 2076 | $devicePane.children('.simcir-device').each(function() { 2077 | var $dev = $(this); 2078 | var devPos = transform($dev); 2079 | var devSize = controller($dev).getSize(); 2080 | var devRect = { 2081 | x: devPos.x + pos.x, 2082 | y: devPos.y + pos.y, 2083 | width: devSize.width, 2084 | height: devSize.height}; 2085 | if (intersect(selRect, devRect) ) { 2086 | addSelected($dev); 2087 | } 2088 | }); 2089 | $temporaryPane.children().remove(); 2090 | $temporaryPane.append(createSVGElement('rect'). 2091 | attr(selRect). 2092 | attr('class', 'simcir-selection-rect') ); 2093 | }; 2094 | }; 2095 | 2096 | var mouseDownHandler = function(event) { 2097 | event.preventDefault(); 2098 | event.stopPropagation(); 2099 | var $target = $(event.target); 2100 | if (!data.editable) { 2101 | return; 2102 | } 2103 | if (isActiveNode($target) ) { 2104 | beginConnect(event, $target); 2105 | } else if ($target.closest('.simcir-device').length == 1) { 2106 | if ($target.closest('.simcir-toolbox').length == 1) { 2107 | beginNewDevice(event, $target); 2108 | } else { 2109 | beginMoveDevice(event, $target); 2110 | } 2111 | } else { 2112 | beginSelectDevice(event, $target); 2113 | } 2114 | $(document).on('mousemove', mouseMoveHandler); 2115 | $(document).on('mouseup', mouseUpHandler); 2116 | }; 2117 | var mouseMoveHandler = function(event) { 2118 | if (dragMoveHandler != null) { 2119 | dragMoveHandler(event); 2120 | } 2121 | }; 2122 | var mouseUpHandler = function(event) { 2123 | if (dragCompleteHandler != null) { 2124 | dragCompleteHandler(event); 2125 | } 2126 | dragMoveHandler = null; 2127 | dragCompleteHandler = null; 2128 | $devicePane.children('.simcir-device').each(function() { 2129 | enableEvents($(this), true); 2130 | }); 2131 | $temporaryPane.children().remove(); 2132 | $(document).off('mousemove', mouseMoveHandler); 2133 | $(document).off('mouseup', mouseUpHandler); 2134 | }; 2135 | $workspace.on('mousedown', mouseDownHandler); 2136 | 2137 | //------------------------------------------- 2138 | // 2139 | 2140 | loadToolbox(data); 2141 | $.each(buildCircuit(data, false, scope), function(i, $dev) { 2142 | addDevice($dev); 2143 | }); 2144 | updateConnectors(); 2145 | 2146 | controller($workspace, { 2147 | data: getData, 2148 | text: getText 2149 | }); 2150 | 2151 | return $workspace; 2152 | }; 2153 | 2154 | var clearSimcir = function($placeHolder) { 2155 | $placeHolder = $($placeHolder[0]); 2156 | $placeHolder.find('.simcir-workspace').trigger('dispose'); 2157 | $placeHolder.children().remove(); 2158 | return $placeHolder; 2159 | }; 2160 | 2161 | var setupSimcir = function($placeHolder, data) { 2162 | 2163 | $placeHolder = clearSimcir($placeHolder); 2164 | 2165 | var $workspace = simcir.createWorkspace(data); 2166 | var $dataArea = $(''). 2167 | addClass('simcir-json-data-area'). 2168 | attr('readonly', 'readonly'). 2169 | css('width', $workspace.attr('width') + 'px'). 2170 | css('height', $workspace.attr('height') + 'px'); 2171 | var showData = false; 2172 | var toggle = function() { 2173 | $workspace.css('display', !showData? 'inline' : 'none'); 2174 | $dataArea.css('display', showData? 'inline' : 'none'); 2175 | if (showData) { 2176 | $dataArea.val(controller($workspace).text() ).focus(); 2177 | } 2178 | showData = !showData; 2179 | }; 2180 | $placeHolder.text(''); 2181 | $placeHolder.append($('
'). 2182 | addClass('simcir-body'). 2183 | append($workspace). 2184 | append($dataArea). 2185 | on('click', function(event) { 2186 | if (event.ctrlKey || event.metaKey) { 2187 | toggle(); 2188 | } 2189 | })); 2190 | toggle(); 2191 | return $placeHolder; 2192 | }; 2193 | 2194 | var setupSimcirDoc = function($placeHolder) { 2195 | var $table = $('
'). 2196 | addClass('simcir-doc-table'); 2197 | $.each(defaultToolbox, function(i, deviceDef) { 2198 | var $dev = createDevice(deviceDef); 2199 | var device = controller($dev); 2200 | if (!device.doc) { 2201 | return; 2202 | } 2203 | var doc = $.extend({description: '', params: []},device.doc); 2204 | var size = device.getSize(); 2205 | 2206 | var $tr = $(''); 2207 | var hgap = 32; 2208 | var vgap = 8; 2209 | var $view = createSVG(size.width + hgap * 2, 2210 | size.height + vgap * 2 + fontSize); 2211 | var $dev = createDevice(deviceDef); 2212 | transform($dev, hgap, vgap); 2213 | 2214 | $view.append($dev); 2215 | $tr.append($('').css('text-align', 'center').append($view) ); 2216 | var $desc = $(''); 2217 | $tr.append($desc); 2218 | 2219 | if (doc.description) { 2220 | $desc.append($(''). 2221 | text(doc.description) ); 2222 | } 2223 | 2224 | $desc.append($('
Params
').addClass('simcir-doc-title') ); 2225 | var $paramsTable = $('
'). 2226 | addClass('simcir-doc-params-table'); 2227 | $paramsTable.children('tbody').append($(''). 2228 | append($('Name') ). 2229 | append($('Type') ). 2230 | append($('Default') ). 2231 | append($('Description') ) ); 2232 | $paramsTable.children('tbody').append($(''). 2233 | append($('type') ). 2234 | append($('string') ). 2235 | append($('-'). 2236 | css('text-align', 'center') ). 2237 | append($('"' + deviceDef.type + '"') ) ); 2238 | if (!doc.labelless) { 2239 | $paramsTable.children('tbody').append($(''). 2240 | append($('label') ). 2241 | append($('string') ). 2242 | append($('same with type').css('text-align', 'center') ). 2243 | append($('label for a device.') ) ); 2244 | } 2245 | if (doc.params) { 2246 | $.each(doc.params, function(i, param) { 2247 | $paramsTable.children('tbody').append($(''). 2248 | append($('').text(param.name) ). 2249 | append($('').text(param.type) ). 2250 | append($('').css('text-align', 'center'). 2251 | text(param.defaultValue) ). 2252 | append($('').text(param.description) ) ); 2253 | }); 2254 | } 2255 | $desc.append($paramsTable); 2256 | 2257 | if (doc.code) { 2258 | $desc.append($('
Code
').addClass('simcir-doc-title') ); 2259 | $desc.append($('
'). 2260 | addClass('simcir-doc-code').text(doc.code) ); 2261 | } 2262 | 2263 | $table.children('tbody').append($tr); 2264 | }); 2265 | 2266 | $placeHolder.append($table); 2267 | }; 2268 | 2269 | $(function() { 2270 | $('.simcir').each(function() { 2271 | var $placeHolder = $(this); 2272 | var text = $placeHolder.text().replace(/^\s+|\s+$/g, ''); 2273 | setupSimcir($placeHolder, JSON.parse(text || '{}') ); 2274 | }); 2275 | }); 2276 | 2277 | $(function() { 2278 | $('.simcir-doc').each(function() { 2279 | setupSimcirDoc($(this) ); 2280 | }); 2281 | }); 2282 | 2283 | $.extend($s, { 2284 | registerDevice: registerDevice, 2285 | clearSimcir: clearSimcir, 2286 | setupSimcir: setupSimcir, 2287 | createWorkspace: createWorkspace, 2288 | createSVGElement: createSVGElement, 2289 | offset: offset, 2290 | transform: transform, 2291 | enableEvents: enableEvents, 2292 | graphics: graphics, 2293 | controller: controller, 2294 | unit: unit 2295 | }); 2296 | }(simcir); 2297 | 2298 | // 2299 | // built-in devices 2300 | // 2301 | !function($s) { 2302 | 2303 | 'use strict'; 2304 | 2305 | var $ = $s.$; 2306 | 2307 | // unit size 2308 | var unit = $s.unit; 2309 | 2310 | var connectNode = function(in1, out1) { 2311 | // set input value to output without inputValueChange event. 2312 | var in1_super_setValue = in1.setValue; 2313 | in1.setValue = function(value, force) { 2314 | var changed = in1.getValue() !== value; 2315 | in1_super_setValue(value, force); 2316 | if (changed || force) { 2317 | out1.setValue(in1.getValue() ); 2318 | } 2319 | }; 2320 | }; 2321 | 2322 | var createPortFactory = function(type) { 2323 | return function(device) { 2324 | var in1 = device.addInput(); 2325 | var out1 = device.addOutput(); 2326 | connectNode(in1, out1); 2327 | var super_createUI = device.createUI; 2328 | device.createUI = function() { 2329 | super_createUI(); 2330 | var size = device.getSize(); 2331 | var cx = size.width / 2; 2332 | var cy = size.height / 2; 2333 | device.$ui.append($s.createSVGElement('circle'). 2334 | attr({cx: cx, cy: cy, r: unit / 2}). 2335 | attr('class', 'simcir-port simcir-node-type-' + type) ); 2336 | device.$ui.append($s.createSVGElement('circle'). 2337 | attr({cx: cx, cy: cy, r: unit / 4}). 2338 | attr('class', 'simcir-port-hole') ); 2339 | }; 2340 | }; 2341 | }; 2342 | 2343 | var createJointFactory = function() { 2344 | 2345 | var maxFadeCount = 16; 2346 | var fadeTimeout = 100; 2347 | 2348 | var Direction = { WE : 0, NS : 1, EW : 2, SN : 3 }; 2349 | 2350 | return function(device) { 2351 | 2352 | var in1 = device.addInput(); 2353 | var out1 = device.addOutput(); 2354 | connectNode(in1, out1); 2355 | 2356 | var state = device.deviceDef.state || { direction : Direction.WE }; 2357 | device.getState = function() { 2358 | return state; 2359 | }; 2360 | 2361 | device.getSize = function() { 2362 | return { width : unit, height : unit }; 2363 | }; 2364 | 2365 | var super_createUI = device.createUI; 2366 | device.createUI = function() { 2367 | super_createUI(); 2368 | 2369 | var $label = device.$ui.children('.simcir-device-label'); 2370 | $label.attr('y', $label.attr('y') - unit / 4); 2371 | 2372 | var $point = $s.createSVGElement('circle'). 2373 | css('pointer-events', 'none').css('opacity', 0).attr('r', 2). 2374 | addClass('simcir-connector').addClass('simcir-joint-point'); 2375 | device.$ui.append($point); 2376 | 2377 | var $path = $s.createSVGElement('path'). 2378 | css('pointer-events', 'none').css('opacity', 0). 2379 | addClass('simcir-connector'); 2380 | device.$ui.append($path); 2381 | 2382 | var $title = $s.createSVGElement('title'). 2383 | text('Double-Click to change a direction.'); 2384 | 2385 | var updatePoint = function() { 2386 | $point.css('display', out1.getInputs().length > 1? '' : 'none'); 2387 | }; 2388 | 2389 | updatePoint(); 2390 | 2391 | var super_connectTo = out1.connectTo; 2392 | out1.connectTo = function(inNode) { 2393 | super_connectTo(inNode); 2394 | updatePoint(); 2395 | }; 2396 | var super_disconnectFrom = out1.disconnectFrom; 2397 | out1.disconnectFrom = function(inNode) { 2398 | super_disconnectFrom(inNode); 2399 | updatePoint(); 2400 | }; 2401 | 2402 | var updateUI = function() { 2403 | var x0, y0, x1, y1; 2404 | x0 = y0 = x1 = y1 = unit / 2; 2405 | var d = unit / 2; 2406 | var direction = state.direction; 2407 | if (direction == Direction.WE) { 2408 | x0 -= d; 2409 | x1 += d; 2410 | } else if (direction == Direction.NS) { 2411 | y0 -= d; 2412 | y1 += d; 2413 | } else if (direction == Direction.EW) { 2414 | x0 += d; 2415 | x1 -= d; 2416 | } else if (direction == Direction.SN) { 2417 | y0 += d; 2418 | y1 -= d; 2419 | } 2420 | $path.attr('d', 'M' + x0 + ' ' + y0 + 'L' + x1 + ' ' + y1); 2421 | $s.transform(in1.$ui, x0, y0); 2422 | $s.transform(out1.$ui, x1, y1); 2423 | $point.attr({cx : x1, cy : y1}); 2424 | if (direction == Direction.EW || direction == Direction.WE) { 2425 | device.$ui.children('.simcir-device-body'). 2426 | attr({x: 0, y: unit / 4, width: unit, height: unit / 2}); 2427 | } else { 2428 | device.$ui.children('.simcir-device-body'). 2429 | attr({x: unit / 4, y: 0, width: unit / 2, height: unit}); 2430 | } 2431 | }; 2432 | 2433 | updateUI(); 2434 | 2435 | // fadeout a body. 2436 | var fadeCount = 0; 2437 | var setOpacity = function(opacity) { 2438 | device.$ui.children('.simcir-device-body,.simcir-node'). 2439 | css('opacity', opacity); 2440 | $path.css('opacity', 1 - opacity); 2441 | $point.css('opacity', 1 - opacity); 2442 | }; 2443 | var fadeout = function() { 2444 | window.setTimeout(function() { 2445 | if (fadeCount > 0) { 2446 | fadeCount -= 1; 2447 | setOpacity(fadeCount / maxFadeCount); 2448 | fadeout(); 2449 | } 2450 | }, fadeTimeout); 2451 | }; 2452 | 2453 | var isEditable = function($dev) { 2454 | var $workspace = $dev.closest('.simcir-workspace'); 2455 | return !!$s.controller($workspace).data().editable; 2456 | }; 2457 | var device_mouseoutHandler = function(event) { 2458 | if (!isEditable($(event.target) ) ) { 2459 | return; 2460 | } 2461 | if (!device.isSelected() ) { 2462 | fadeCount = maxFadeCount; 2463 | fadeout(); 2464 | } 2465 | }; 2466 | var device_dblclickHandler = function(event) { 2467 | if (!isEditable($(event.target) ) ) { 2468 | return; 2469 | } 2470 | state.direction = (state.direction + 1) % 4; 2471 | updateUI(); 2472 | // update connectors. 2473 | $(this).trigger('mousedown').trigger('mouseup'); 2474 | }; 2475 | 2476 | device.$ui.on('mouseover', function(event) { 2477 | if (!isEditable($(event.target) ) ) { 2478 | $title.text(''); 2479 | return; 2480 | } 2481 | setOpacity(1); 2482 | fadeCount = 0; 2483 | }).on('deviceAdd', function() { 2484 | if ($(this).closest('BODY').length == 0) { 2485 | setOpacity(0); 2486 | } 2487 | $(this).append($title).on('mouseout', device_mouseoutHandler). 2488 | on('dblclick', device_dblclickHandler); 2489 | // hide a label 2490 | $label.css('display', 'none'); 2491 | }).on('deviceRemove', function() { 2492 | $(this).off('mouseout', device_mouseoutHandler). 2493 | off('dblclick', device_dblclickHandler); 2494 | $title.remove(); 2495 | // show a label 2496 | $label.css('display', ''); 2497 | }).on('deviceSelect', function() { 2498 | if (device.isSelected() ) { 2499 | setOpacity(1); 2500 | fadeCount = 0; 2501 | } else { 2502 | if (fadeCount == 0) { 2503 | setOpacity(0); 2504 | } 2505 | } 2506 | }); 2507 | }; 2508 | }; 2509 | }; 2510 | 2511 | // register built-in devices 2512 | $s.registerDevice('In', createPortFactory('in') ); 2513 | $s.registerDevice('Out', createPortFactory('out') ); 2514 | $s.registerDevice('Joint', createJointFactory() ); 2515 | 2516 | }(simcir); 2517 | --------------------------------------------------------------------------------