UNPKG

cirsim

Version:

Cirsim Circuit Simulator

591 lines (496 loc) 16.2 kB
import {Component} from '../Component'; import {Button} from '../Graphics/Button'; import {CanvasHelper} from '../Graphics/CanvasHelper'; import {Value} from '../Value'; import {ComponentPropertiesDlg} from '../Dlg/ComponentPropertiesDlg'; import {Clock} from "./Clock"; import {DFF} from "./DFF"; /** * Component: Pad * Configurable keypad for button presses * @constructor */ export const Pad = function() { Component.call(this); this.height = 150; this.width = 150; this.value = new Value(); this.bus = true; this.bits = 4; this.clockDelay = 0; // The type of pad, null until we define it this.pad = null; this.setType(16); this.setAsInteger(0); this.setPressedOut(false); }; Pad.prototype = Object.create(Component.prototype); Pad.prototype.constructor = Pad; Pad.prototype.prefix = "P"; Pad.prototype.nameRequired = true; Pad.type = "Pad"; ///< Name to use in files Pad.label = "Pad"; ///< Label for the palette Pad.desc = "Configurable Keypad"; ///< Description for the palette Pad.img = 'pad.png'; Pad.description = `<h2>Configurable Keypad</h2> <p>Presents a keypad with from 4 to 16 buttons. If configured as a bus output, there are two outputs. The top output is the bus output and is the most recently pressed button value. If configured for single bit output, there will be 2-4 output bits the represent the most recently pressed button. The bottom output is the clock and is true when the button is pressed.</p>`; Pad.order = 500; Pad.help = 'pad'; Pad.prototype.setType = function(pad) { if(this.pad === pad) { this.ensureIO(); return; } const size = 30; const gap = 5; let set16 = () => { this.pad = 16; const a = - size * 1.5 - gap * 1.5; const b = - size/2 - gap/2; const c = size/2 + gap/2; const d = size * 1.5 + gap * 1.5; const up = -8; this.buttons = [ new Button('0', 0, a, a + up, size), new Button('1', 1, b, a + up, size), new Button('2', 2, c, a + up, size), new Button('3', 3, d, a + up, size), new Button('4', 4, a, b + up, size), new Button('5', 5, b, b + up, size), new Button('6', 6, c, b + up, size), new Button('7', 7, d, b + up, size), new Button('8', 8, a, c + up, size), new Button('9', 9, b, c + up, size), new Button('A', 10, c, c + up, size), new Button('B', 11, d, c + up, size), new Button('C', 12, a, d + up, size), new Button('D', 13, b, d + up, size), new Button('E', 14, c, d + up, size), new Button('F', 15, d, d + up, size), ]; this.width = (size + gap) * 4 + gap * 2 + 4; this.height = this.width + 13 + 4; this.bits = 4; } let set10 = () => { this.pad = 10; // Columns const x = -size - gap; const y = 0; const z = size + gap; // Rows const a = - size * 1.5 - gap * 1.5; const b = - size/2 - gap/2; const c = size/2 + gap/2; const d = size * 1.5 + gap * 1.5; const up = -8; this.buttons = [ new Button('1', 1, x, a + up, size), new Button('2', 2, y, a + up, size), new Button('3', 3, z, a + up, size), new Button('4', 4, x, b + up, size), new Button('5', 5, y, b + up, size), new Button('6', 6, z, b + up, size), new Button('7', 7, x, c + up, size), new Button('8', 8, y, c + up, size), new Button('9', 9, z, c + up, size), new Button('0', 0, y, d + up, size), ]; this.width = (size + gap) * 3 + gap * 2 + 4; this.height = (size + gap) * 4 + gap * 2 + 4 + 13 + 4; this.bits = 4; } let set12 = () => { this.pad = 12; // Columns const x = -size - gap; const y = 0; const z = size + gap; // Rows const a = - size * 1.5 - gap * 1.5; const b = - size/2 - gap/2; const c = size/2 + gap/2; const d = size * 1.5 + gap * 1.5; const up = -8; this.buttons = [ new Button('1', 1, x, a + up, size), new Button('2', 2, y, a + up, size), new Button('3', 3, z, a + up, size), new Button('4', 4, x, b + up, size), new Button('5', 5, y, b + up, size), new Button('6', 6, z, b + up, size), new Button('7', 7, x, c + up, size), new Button('8', 8, y, c + up, size), new Button('9', 9, z, c + up, size), new Button('*', 10, x, d + up, size), new Button('0', 0, y, d + up, size), new Button('#', 11, z, d + up, size), ]; this.width = (size + gap) * 3 + gap * 2 + 4; this.height = (size + gap) * 4 + gap * 2 + 4 + 13 + 4; this.bits = 4; } let set8 = () => { this.pad = 8; const a = - size * 1.5 - gap * 1.5; const b = - size/2 - gap/2; const c = size/2 + gap/2; const d = size * 1.5 + gap * 1.5; const up = -8; this.buttons = [ new Button('0', 0, a, b + up, size), new Button('1', 1, b, b + up, size), new Button('2', 2, c, b + up, size), new Button('3', 3, d, b + up, size), new Button('4', 4, a, c + up, size), new Button('5', 5, b, c + up, size), new Button('6', 6, c, c + up, size), new Button('7', 7, d, c + up, size), ]; this.width = (size + gap) * 4 + gap * 2 + 4; this.height = (size + gap) * 2 + gap * 2 + 4 + 13 + 4; this.bits = 3; } let set4 = () => { this.pad = 4; const b = - size/2 - gap/2; const c = size/2 + gap/2; const up = -8; this.buttons = [ new Button('0', 0, b, b + up, size), new Button('1', 1, c, b + up, size), new Button('2', 2, b, c + up, size), new Button('3', 3, c, c + up, size), ]; this.width = (size + gap) * 2 + gap * 2 + 4; this.height = (size + gap) * 2 + gap * 2 + 4 + 13 + 4; this.bits = 2; } switch(pad) { case 16: case '16': default: set16(); break; case 10: case '10': set10(); break; case 12: case '12': set12(); break; case 8: case '8': set8(); break; case 4: case '4': set4(); break; } this.ensureIO(); } /** * Ensure the actual number of outputs matches the * defined bus size. */ Pad.prototype.ensureIO = function() { let spacing = 16; let i=0; if(this.outs.length > 0) { // // Test if we switched output types or size // If so, disconnect everything // let clear = false; if(this.outs[0].bus !== this.bus) { // Changed bus status clear = true; } if(!this.bus && this.outs.length !== this.bits + 1) { // Changed number of bits required clear = true; } if(!clear) { // Ensure positions are correct... for(let out of this.outs) { out.x = this.width/2; } return; // We're good! } } // Clear all outputs for(i=0; i<this.outs.length; i++) { this.outs[i].clear(); } this.outs = []; let startY = 0; const len = 11; if(this.bus) { this.addOut(this.width/2, -16, len).bus = true; } else { for(i=0; i<this.bits; i++) { let pinY = startY - i * spacing; // Add any new pins this.addOut(this.width / 2, pinY, len); } } this.addOut(this.width/2, 32, len); this.setOuts(); this.setPressedOut(false); } /** * Clone this component object. * @returns {Pad} */ Pad.prototype.clone = function() { const copy = new Pad(); copy.value = this.value.clone(); copy.bus = this.bus; copy.setType(this.pad); copy.copyFrom(this); return copy; }; /** * Draw component object. * @param context Display context * @param view View object */ Pad.prototype.draw = function(context, view) { const leftX = this.x - this.width/2 - 0.5; const topY = this.y - this.height/2 - 0.5; // Select the style const saveUS = this.unselectedStyle; this.unselectedStyle = '#f2f2f2'; this.selectStyle(context, view); const saveFillStyle = context.fillStyle; const saveStrokeStyle = context.strokeStyle; const radius = 6; // // Background // context.fillStyle = "#111111"; CanvasHelper.roundedRect(context, leftX, topY, this.width, this.height, radius); context.fill(); // Border const indent = 3; context.fillStyle = saveFillStyle; context.lineWidth = 2; CanvasHelper.roundedRect(context, leftX+indent, topY+indent, this.width-indent*2, this.height-indent*2, radius); context.stroke(); context.lineWidth = 1; this.buttons.forEach((button) => { button.draw(context, this.x, this.y); }); this.drawName(context, 0, this.height/2 - 8); // Restore context.lineWidth = 1; context.fillStyle = saveFillStyle; context.strokeStyle = saveStrokeStyle; this.unselectedStyle = saveUS; this.drawIO(context, view); }; Pad.prototype.touch = function(x, y) { const size = this.size; if(x < this.x - size/2 || x > this.x + size/2 || y < this.y - size/2 || y > this.y - size/2) { return; } for(let i in this.buttons) { const button = this.buttons[i]; if(button.touch(x - this.x, y - this.y)) { this.setAsInteger(button.value, 4); this.setPressedOut(true); break; } } return Component.prototype.touch.call(this, x, y); }; /** * Set the value for the input * @param value to set */ Pad.prototype.set = function(value) { this.value.set(value); this.setOuts(); this.updateUI(); }; Pad.prototype.setAsInteger = function(value) { this.value.setAsInteger(value, this.bits); this.setOuts(); this.updateUI(); } Pad.prototype.updateUI = function() { const p = this.value.getAsInteger(); let pressed = this.getPressedOut(); if(pressed === undefined) { pressed = false; } this.buttons.forEach((button) => { if(button.value !== p) { button.setState('off'); } else if(pressed) { button.setState('pressed'); } else { button.setState('on'); } }); } /** * This function sets the pad state based on an input string. * * This accepts a simple value. It also accepts these strings: * * release - Releases any currently pressed button * press:X - Presses button X * * @param value * @param parseonly */ Pad.prototype.setAsString = function(value, parseonly) { if(value === 'release') { this.mouseUp(); } else if(value.substr(0, 6) === 'press:') { this.value.setAsHex(value.substr(6)); this.setPressedOut(true); this.updateUI(); } else { this.value.setAsString(value, parseonly); this.updateUI(); } if (!parseonly) { this.setOuts(); } } Pad.prototype.setOuts = function() { if(this.bus) { this.outs[0].set(this.value.get()); } else { for(let b=0; b<this.bits; b++) { this.outs[b].set(this.value.getBit(b)); } } } /** * Advance the animation for this component by delta seconds * @param delta Time to advance in seconds * @returns true if animation needs to be redrawn */ Pad.prototype.advance = function(delta) { // if(this.clockDelay > 0) { // this.clockDelay -= delta; // if(this.clockDelay < 0) { // this.clockDelay = 0; // No pending clock // if(this.bus) { // this.outs[1].set(true); // } else { // this.outs[this.bits].set(true); // } // this.updateUI(); // return true; // } // } return false; }; /** * Compute the gate result * @param state */ Pad.prototype.compute = function(state) { const value = state[0] === true; if(this.bus) { this.outs[1].set(value); } else { this.outs[this.bits].set(value); } this.updateUI(); }; /** * Set the button pressed output to a given value. * @param value Value to set */ Pad.prototype.setPressedOut = function(value) { const simulation = this.getSimulation(); if(simulation !== null) { simulation.queue(this, 50, [value]); } // if(value) { // // The clock rising edge output is delayed by 50ns // this.clockDelay = 0.00000004; // 0.00000005; // // this.getSimulation().queue(this, 50, [1]); // } else { // // Setting to zero // this.clockDelay = 0; // No pending clock // if(this.bus) { // this.outs[1].set(value); // } else { // this.outs[this.bits].set(value); // } // this.updateUI(); // } } Pad.prototype.getPressedOut = function() { if(this.bus) { return this.outs[1].get(); } else { return this.outs[this.bits].get(); } } Pad.prototype.mouseUp = function() { this.buttons.forEach((button) => { button.untouch(); }); this.setPressedOut(false); }; /** * Create a save object suitable for conversion to JSON for export. * @returns {*} */ Pad.prototype.save = function() { const obj = Component.prototype.save.call(this); this.value.save(obj); obj.pad = this.pad; obj.bus = this.bus; return obj; }; /** * Load this object from an object converted from JSON * @param obj Object from JSON */ Pad.prototype.load = function(obj) { this.value.load(obj); this.bus = obj.bus !== false; this.setType(obj.pad); Component.prototype.load.call(this, obj); }; Pad.prototype.properties = function(main) { const dlg = new ComponentPropertiesDlg(this, main); const id = dlg.uniqueId(); let html = '<div class="control center"><div class="choosers">'; html += ` <label><input type="radio" name="${id}" value="16" ${this.pad === 16 ? 'checked' : ''}> 16 Buttons (Hex)</label> <label><input type="radio" name="${id}" value="10" ${this.pad === 10 ? 'checked' : ''}> 10 Buttons </label> <label><input type="radio" name="${id}" value="8" ${this.pad === 8 ? 'checked' : ''}> 8 Buttons </label> <label><input type="radio" name="${id}" value="4" ${this.pad === 4 ? 'checked' : ''}> 4 Buttons </label> <label><input type="radio" name="${id}" value="12" ${this.pad === 12 ? 'checked' : ''}> Phone (12 buttons)</label> `; html += '<br>'; const busId = dlg.uniqueId(); html += ` <label><input type="radio" name="${busId}" ${this.bus ? 'checked' : ''} value="1"> Bus Output</label> <label><input type="radio" name="${busId}" ${!this.bus ? 'checked' : ''} value="0"> Single Bit Outputs</label>`; html += '</div></div>'; dlg.extra(html, () => { return null; }, () => { this.bus = document.querySelector(`input[name=${busId}]:checked`).value === '1'; this.setType(document.querySelector(`input[name=${id}]:checked`).value); }); dlg.open(); };