(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 |
43 | Choose a device from the toolbox and move to right side.
44 | Connect them by drag operation.
45 | Click an input node to disconnect.
46 | Move a device back to the toolbox if you don't use.
47 | Ctrl+Click(Mac:command+Click) to toggle view (Live circuit or JSON data).
48 | Double-Click a label to edit device name.
49 | Double-Click a library to open the circuit inside.
50 |
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 |
--------------------------------------------------------------------------------