/*********************************************************************** * retro-220/webUI B220PanelUtil.js ************************************************************************ * Copyright (c) 2017, Paul Kimpel. * Licensed under the MIT License, see * http://www.opensource.org/licenses/mit-license.php ************************************************************************ * JavaScript object definition for the Burroughs 220 Emulator Control * Panel utility constructors: * NeonLamp * NeonLampBox * ColoredLamp * ToggleSwitch * ThreeWaySwitch * OrganSwitch * BlackControlKnob * PanelRegister ************************************************************************ * 2017-01-01 P.Kimpel * Original version, from retro-205 D205PanelUtil.js. ***********************************************************************/ /*********************************************************************** * Panel Neon Lamp * ***********************************************************************/ function NeonLamp(parent, x, y, id) { /* Constructor for the neon lamp objects used within panels. x & y are the coordinates of the lamp within its containing element; id is the DOM id */ this.state = 0; // current lamp state, 0=off this.topCaptionDiv = null; // optional top caption element this.bottomCaptionDiv = null; // optional bottom caption element // visible DOM element this.element = document.createElement("div"); this.element.id = id; this.element.className = NeonLamp.lampClass; if (x !== null) { this.element.style.left = x.toString() + "px"; } if (y !== null) { this.element.style.top = y.toString() + "px"; } if (parent) { parent.appendChild(this.element); } } /**************************************/ NeonLamp.topCaptionClass = "neonLampTopCaption"; NeonLamp.bottomCaptionClass = "neonLampBottomCaption"; NeonLamp.lampClass = "neonLamp"; NeonLamp.litClass = "neonLamp neonLit"; NeonLamp.lampLevels = 6; NeonLamp.levelClass = [ // css class names for the lamp levels NeonLamp.lampClass, NeonLamp.litClass + "1", NeonLamp.litClass + "2", NeonLamp.litClass + "3", NeonLamp.litClass + "4", NeonLamp.litClass + "5", NeonLamp.litClass]; /**************************************/ NeonLamp.prototype.addEventListener = function addEventListener(eventName, handler, useCapture) { /* Sets an event handler whenever the image element is clicked */ this.element.addEventListener(eventName, handler, useCapture); }; /**************************************/ NeonLamp.prototype.set = function set(state) { /* Changes the visible state of the lamp according to the value of "state", 0-1 */ var newState = Math.max(Math.min(Math.round(state*NeonLamp.lampLevels + 0.4999), NeonLamp.lampLevels), 0); if (this.state ^ newState) { // the state has changed this.state = newState; this.element.className = NeonLamp.levelClass[newState]; } }; /**************************************/ NeonLamp.prototype.flip = function flip() { /* Complements the visible state of the lamp */ this.set(1.0 - this.state/NeonLamp.lampLevels); }; /**************************************/ NeonLamp.prototype.setCaption = function setCaption(caption, atBottom) { /* Establishes an optional caption at the top or bottom of a single lamp. Returns the caption element */ var e = (atBottom ? this.bottomCaptionDiv : this.topCaptionDiv); if (e) { e.textContent = caption; } else { e = document.createElement("div"); if (atBottom) { this.bottomCaptionDiv = e; e.className = NeonLamp.bottomCaptionClass; } else { this.topCaptionDiv = e; e.className = NeonLamp.topCaptionClass; } e.appendChild(document.createTextNode(caption)); this.element.appendChild(e); } return e; }; /*********************************************************************** * Panel Neon Lamp Box * ***********************************************************************/ function NeonLampBox(parent, x, y, id, caption) { /* Constructor for the neon lamp-in-a-box objects used within panels. x & y are the coordinates of the box within its containing element; id is the DOM id */ // visible DOM element this.element = document.createElement("div"); this.element.id = id; this.element.className = NeonLampBox.lampBoxClass; if (x !== null) { this.element.style.left = x.toString() + "px"; } if (y !== null) { this.element.style.top = y.toString() + "px"; } this.lamp = new NeonLamp(this.element, 3, 3, id + "_Lamp"); this.button = document.createElement("div"); this.button.id = id + "_LampBtn"; this.button.className = NeonLampBox.lampButtonClass; this.button.textContent = caption; this.element.appendChild(this.button); if (parent) { parent.appendChild(this.element); } } /**************************************/ NeonLampBox.lampBoxClass = "lampBox"; NeonLampBox.lampButtonClass = "lampButton"; /**************************************/ NeonLampBox.prototype.addEventListener = function addEventListener(eventName, handler, useCapture) { /* Sets an event handler whenever the image element is clicked */ this.element.addEventListener(eventName, handler, useCapture); }; /**************************************/ NeonLampBox.prototype.set = function set(state) { /* Changes the visible state of the lamp according to the value of "state", 0-1 */ this.lamp.set(state); }; /**************************************/ NeonLampBox.prototype.flip = function flip() { /* Complements the visible state of the lamp */ this.lamp.flip(); }; /**************************************/ NeonLampBox.prototype.setCaption = function setCaption(caption) { /* Establishes an optional caption on the button for a single lamp */ this.button.textContent = caption; }; /*********************************************************************** * Panel Colored Lamp * ***********************************************************************/ function ColoredLamp(parent, x, y, id, offClass, onClass) { /* Constructor for the colored lamp objects used within panels. x & y are the coordinates of the lamp within its containing element; id is the DOM id */ this.state = 0; // current lamp state, 0=off this.topCaptionDiv = null; // optional top caption element this.bottomCaptionDiv = null; // optional bottom caption element this.lampClass = offClass; // css styling for an "off" lamp this.litClass = // css styling for an "on" lamp offClass + " " + onClass; this.levelClass = [ // css class names for the lamp levels offClass, this.litClass + "1", this.litClass + "2", this.litClass + "3", this.litClass + "4", this.litClass + "5", this.litClass]; // visible DOM element this.element = document.createElement("div"); this.element.id = id; this.element.className = offClass; if (x !== null) { this.element.style.left = x.toString() + "px"; } if (y !== null) { this.element.style.top = y.toString() + "px"; } if (parent) { parent.appendChild(this.element); } } /**************************************/ ColoredLamp.lampLevels = 6; ColoredLamp.topCaptionClass = "coloredLampTopCaption"; ColoredLamp.bottomCaptionClass = "coloredLampBottomCaption"; /**************************************/ ColoredLamp.prototype.addEventListener = function addEventListener(eventName, handler, useCapture) { /* Sets an event handler whenever the image element is clicked */ this.element.addEventListener(eventName, handler, useCapture); }; /**************************************/ ColoredLamp.prototype.set = function set(state) { /* Changes the visible state of the lamp according to the value of "state", 0-1 */ var newState = Math.max(Math.min(Math.round(state*ColoredLamp.lampLevels + 0.4999), ColoredLamp.lampLevels), 0); if (this.state != newState) { // the state has changed this.state = newState; this.element.className = this.levelClass[newState]; } }; /**************************************/ ColoredLamp.prototype.flip = function flip() { /* Complements the visible state of the lamp */ this.set(ColoredLamp.lampLevels - this.state); }; /**************************************/ ColoredLamp.prototype.setCaption = function setCaption(caption, atBottom) { /* Establishes an optional caption at the top or bottom of a single lamp. Returns the caption element */ var e = (atBottom ? this.bottomCaptionDiv : this.topCaptionDiv); if (e) { e.textContent = caption; } else { e = document.createElement("div"); if (atBottom) { this.bottomCaptionDiv = e; e.className = ColoredLamp.bottomCaptionClass; } else { this.topCaptionDiv = e; e.className = ColoredLamp.topCaptionClass; } e.appendChild(document.createTextNode(caption)); this.element.appendChild(e); } return e; }; /*********************************************************************** * Panel Toggle Switch * ***********************************************************************/ function ToggleSwitch(parent, x, y, id, offImage, onImage) { /* Constructor for the toggle switch objects used within panels. x & y are the coordinates of the switch within its containing element; id is the DOM id */ this.state = 0; // current switch state, 0=off this.topCaptionDiv = null; // optional top caption element this.bottomCaptionDiv = null; // optional bottom caption element this.offImage = offImage; // image used for the off state this.onImage = onImage; // image used for the on state // visible DOM element this.element = document.createElement("img"); this.element.id = id; this.element.src = offImage; if (x !== null) { this.element.style.left = x.toString() + "px"; } if (y !== null) { this.element.style.top = y.toString() + "px"; } if (parent) { parent.appendChild(this.element); } } /**************************************/ ToggleSwitch.topCaptionClass = "toggleSwitchTopCaption"; ToggleSwitch.bottomCaptionClass = "toggleSwitchBottomCaption"; /**************************************/ ToggleSwitch.prototype.addEventListener = function addEventListener(eventName, handler, useCapture) { /* Sets an event handler whenever the image element is clicked */ this.element.addEventListener(eventName, handler, useCapture); }; /**************************************/ ToggleSwitch.prototype.set = function set(state) { /* Changes the visible state of the switch according to the low-order bit of "state" */ var newState = state & 1; if (this.state ^ newState) { // the state has changed this.state = newState; this.element.src = (newState ? this.onImage : this.offImage); } }; /**************************************/ ToggleSwitch.prototype.flip = function flip() { /* Complements the visible state of the switch */ var newState = this.state ^ 1; this.state = newState; this.element.src = (newState ? this.onImage : this.offImage); }; /**************************************/ ToggleSwitch.prototype.setCaption = function setCaption(caption, atBottom) { /* Establishes an optional caption at the top or bottom of a single switch. Returns the caption element */ var e = (atBottom ? this.bottomCaptionDiv : this.topCaptionDiv); if (e) { e.textContent = caption; } else { e = document.createElement("div"); if (atBottom) { this.bottomCaptionDiv = e; e.className = ToggleSwitch.bottomCaptionClass; } else { this.topCaptionDiv = e; e.className = ToggleSwitch.topCaptionClass; } e.appendChild(document.createTextNode(caption)); this.element.appendChild(e); } return e; }; /*********************************************************************** * Panel Three-way Toggle Switch * ***********************************************************************/ function ThreeWaySwitch(parent, x, y, id, offImage, onImage1, onImage2) { /* Constructor for the three-way toggle switch objects used within panels. x & y are the coordinates of the switch within its containing element; id is the DOM id */ this.state = 0; // current switch state, 0=off, 1=down, 2=up this.topCaptionDiv = null; // optional top caption element this.bottomCaptionDiv = null; // optional bottom caption element this.offImage = offImage; // image used for the off state this.onImage1 = onImage1; // image used for the lower on state this.onImage2 = onImage2; // image used for the upper on state // visible DOM element this.element = document.createElement("img"); this.element.id = id; this.element.src = offImage; if (x !== null) { this.element.style.left = x.toString() + "px"; } if (y !== null) { this.element.style.top = y.toString() + "px"; } if (parent) { parent.appendChild(this.element); } } /**************************************/ ThreeWaySwitch.topCaptionClass = "ToggleSwitchTopCaption"; ThreeWaySwitch.bottomCaptionClass = "ToggleSwitchBottomCaption"; /**************************************/ ThreeWaySwitch.prototype.addEventListener = function addEventListener(eventName, handler, useCapture) { /* Sets an event handler whenever the image element is clicked */ this.element.addEventListener(eventName, handler, useCapture); }; /**************************************/ ThreeWaySwitch.prototype.set = function set(state) { /* Changes the visible state of the switch according to the value of "state" */ if (this.state != state) { // the state has changed switch (state) { case 1: // down position this.state = 1; this.element.src = this.onImage1; break; case 2: // up position this.state = 2; this.element.src = this.onImage2; break; default: // middle position this.state = 0; this.element.src = this.offImage; break; } // switch state } }; /**************************************/ ThreeWaySwitch.prototype.flip = function flip() { /* Increments the visible state of the switch */ this.set(this.state+1); }; /**************************************/ ThreeWaySwitch.prototype.setCaption = function setCaption(caption, atBottom) { /* Establishes an optional caption at the top or bottom of a single switch. Returns the caption element */ var e = (atBottom ? this.bottomCaptionDiv : this.topCaptionDiv); if (e) { e.textContent = caption; } else { e = document.createElement("div"); if (atBottom) { this.bottomCaptionDiv = e; e.className = ThreeWaySwitch.bottomCaptionClass; } else { this.topCaptionDiv = e; e.className = ThreeWaySwitch.topCaptionClass; } e.appendChild(document.createTextNode(caption)); this.element.appendChild(e); } return e; }; /*********************************************************************** * Panel Organ Switch * ***********************************************************************/ function OrganSwitch(parent, x, y, id, offImage, onImage, momentary) { /* Constructor for the organ switch objects used within panels. x & y are the coordinates of the switch within its containing element; id is the DOM id */ this.state = 0; // current switch state, 0=off this.topCaptionDiv = null; // optional top caption element this.bottomCaptionDiv = null; // optional bottom caption element this.offImage = offImage; // image used for the off state this.onImage = onImage; // image used for the on state this.momentary = momentary || false;// true if the switch is only momentary-on // visible DOM element this.element = document.createElement("img"); this.element.id = id; this.element.src = offImage; if (x !== null) { this.element.style.left = x.toString() + "px"; } if (y !== null) { this.element.style.top = y.toString() + "px"; } if (parent) { parent.appendChild(this.element); } } /**************************************/ OrganSwitch.topCaptionClass = "OrganSwitchTopCaption"; OrganSwitch.bottomCaptionClass = "OrganSwitchBottomCaption"; OrganSwitch.momentaryPeriod = 200; // time for momentary switch to bounce back, ms /**************************************/ OrganSwitch.prototype.addEventListener = function addEventListener(eventName, handler, useCapture) { /* Sets an event handler whenever the image element is clicked */ this.element.addEventListener(eventName, handler, useCapture); }; /**************************************/ OrganSwitch.prototype.set = function set(state) { /* Changes the visible state of the switch according to the low-order bit of "state" */ var newState = state & 1; if (this.state ^ newState) { // the state has changed this.state = newState; this.element.src = (newState ? this.onImage : this.offImage); if (this.momentary && newState) { setCallback(null, this, OrganSwitch.momentaryPeriod, this.set, 0); } } }; /**************************************/ OrganSwitch.prototype.flip = function flip() { /* Complements the visible state of the switch */ this.set(this.state ^ 1); }; /**************************************/ OrganSwitch.prototype.setCaption = function setCaption(caption, atBottom) { /* Establishes an optional caption at the top or bottom of a single switch. Returns the caption element */ var e = (atBottom ? this.bottomCaptionDiv : this.topCaptionDiv); if (e) { e.textContent = caption; } else { e = document.createElement("div"); if (atBottom) { this.bottomCaptionDiv = e; e.className = OrganSwitch.bottomCaptionClass; } else { this.topCaptionDiv = e; e.className = OrganSwitch.topCaptionClass; } e.appendChild(document.createTextNode(caption)); this.element.appendChild(e); } return e; }; /*********************************************************************** * Black Control Knob * ***********************************************************************/ function BlackControlKnob(parent, x, y, id, initial, positions) { /* Constructor for the black control knob objects used within panels. x & y are the coordinates of the knob within its containing element; id is the DOM id; initial is the 0-relative index indicating the default position of the switch; positions is an array indicating the angular position (in degrees, where 0 is straight up) of each of the knob's positions */ this.position = 0; // current knob position this.direction = 1; // rotate knob clockwise(1), counter-clockwise(-1) this.topCaptionDiv = null; // optional top caption element this.bottomCaptionDiv = null; // optional bottom caption element this.positions = positions; // array of knob position angles // visible DOM element this.element = document.createElement("canvas"); this.element.id = id; this.element.width = BlackControlKnob.size; this.element.height = BlackControlKnob.size; this.element.className = BlackControlKnob.className; if (x !== null) { this.element.style.left = x.toString() + "px"; } if (y !== null) { this.element.style.top = y.toString() + "px"; } if (parent) { parent.appendChild(this.element); } this.set(initial); // set to its initial position } /**************************************/ BlackControlKnob.topCaptionClass = "blackControlKnobTopCaption"; BlackControlKnob.bottomCaptionClass = "blackControlKnobBottomCaption"; BlackControlKnob.className = "blackControlKnob1"; BlackControlKnob.canvasColor = "transparent"; BlackControlKnob.size = 64; // width/height in pixels /**************************************/ BlackControlKnob.prototype.addEventListener = function addEventListener(eventName, handler, useCapture) { /* Sets an event handler whenever the canvas element is clicked */ this.element.addEventListener(eventName, handler, useCapture); }; /**************************************/ BlackControlKnob.prototype.set = function set(position) { /* Changes the visible state of the knob according to the position index */ var dc = this.element.getContext("2d"); var degrees = Math.PI/180; var fullCircle = 360*degrees; var halfSize = Math.floor(BlackControlKnob.size/2); var quarterSize = Math.floor(BlackControlKnob.size/4); var silverSkirt; if (position < 0) { this.position = 0; this.direction = 1; } else if (position < this.positions.length) { this.position = position; } else { this.position = this.positions.length-1; this.direction = -1; } dc.save(); dc.translate(halfSize+0.5, halfSize+0.5); // move origin to the center dc.fillStyle = BlackControlKnob.canvasColor;// fill in the panel background (aids antialiasing) dc.fillRect(-halfSize, -halfSize, BlackControlKnob.size, BlackControlKnob.size); silverSkirt = dc.createRadialGradient(0, 0, halfSize, 0, 0, quarterSize); silverSkirt.addColorStop(0.5, "#FFF"); silverSkirt.addColorStop(1, "#CCC"); dc.beginPath(); // draw the outer skirt of the knob dc.arc(0, 0, halfSize-1, 0, fullCircle, false); dc.fillStyle = silverSkirt; dc.fill(); dc.beginPath(); // draw the central knob dc.arc(0, 0, quarterSize, 0, fullCircle, false); dc.fillStyle = "#000"; dc.fill(); dc.beginPath(); // draw the inset on top of the knob dc.arc(0, 0, quarterSize-4, 0, fullCircle, false); dc.fillStyle = "#333"; dc.fill(); dc.save(); // draw the knob indicator dc.rotate(this.positions[this.position]*degrees); dc.beginPath(); dc.moveTo(0, 1-halfSize); dc.lineTo(-quarterSize/4, -halfSize+quarterSize/2); dc.lineTo(quarterSize/4, -halfSize+quarterSize/2); dc.closePath(); dc.fillStyle = "#000"; dc.fill(); dc.restore(); // undo the rotation dc.restore(); // undo the translation }; /**************************************/ BlackControlKnob.prototype.step = function step() { /* Steps the knob to its next position. If it is at the last position, steps it to the first position */ var position = this.position+this.direction; if (position < 0) { this.direction = 1; this.set(1); } else if (position < this.positions.length) { this.set(position); } else { this.direction = -1; this.set(this.positions.length-2); } }; /**************************************/ BlackControlKnob.prototype.setCaption = function setCaption(caption, atBottom) { /* Establishes an optional caption at the top or bottom of a single switch. Returns the caption element */ var e = (atBottom ? this.bottomCaptionDiv : this.topCaptionDiv); if (e) { e.textContent = caption; } else { e = document.createElement("div"); if (atBottom) { this.bottomCaptionDiv = e; e.className = BlackControlKnob.bottomCaptionClass; } else { this.topCaptionDiv = e; e.className = BlackControlKnob.topCaptionClass; } e.appendChild(document.createTextNode(caption)); this.element.appendChild(e); } return e; }; /*********************************************************************** * Panel Register * ***********************************************************************/ function PanelRegister(parent, bits, rows, idPrefix, caption) { /* Constructor for the register objects used within panels: parent:the DOM element (usually a