mirror of
https://github.com/pkimpel/retro-220.git
synced 2026-01-13 15:18:24 +00:00
905 lines
33 KiB
JavaScript
905 lines
33 KiB
JavaScript
/***********************************************************************
|
|
* 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.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
|
|
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:
|
|
this.state = 1;
|
|
this.element.src = this.onImage1;
|
|
break;
|
|
case 2:
|
|
this.state = 2;
|
|
this.element.src = this.onImage2;
|
|
break;
|
|
default:
|
|
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 = 150; // 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.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 = "#246"; // 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, -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 <div>) within which the register will be built
|
|
bits: number of bits in register
|
|
rows: number of rows used to display the bit lamps
|
|
caption:optional caption displayed at the bottom of the register
|
|
*/
|
|
var cols = Math.floor((bits+rows-1)/rows);
|
|
var b;
|
|
var cx;
|
|
var cy;
|
|
var e;
|
|
|
|
this.element = parent; // containing element for the panel
|
|
this.bits = bits; // number of bits in the register
|
|
this.caption = caption || ""; // panel caption
|
|
this.lastValue = 0; // prior register value
|
|
this.lamps = new Array(bits); // bit lamps
|
|
|
|
cx = cols*PanelRegister.hSpacing + PanelRegister.hOffset;
|
|
for (b=0; b<bits; b++) {
|
|
if (b%rows == 0) {
|
|
cy = (rows-1)*PanelRegister.vSpacing + PanelRegister.vOffset;
|
|
cx -= PanelRegister.hSpacing;
|
|
} else {
|
|
cy -= PanelRegister.vSpacing;
|
|
}
|
|
|
|
this.lamps[b] = new NeonLampBox(parent, cx, cy, idPrefix + b.toString());
|
|
}
|
|
|
|
this.captionDiv = document.createElement("div");
|
|
this.captionDiv.className = PanelRegister.captionClass;
|
|
this.captionDiv.style.top = "0";
|
|
this.captionDiv.style.left = "0";
|
|
if (caption) {
|
|
e = document.createElement("span");
|
|
e.className = PanelRegister.captionSpanClass;
|
|
e.appendChild(document.createTextNode(caption.substring(0, 1)));
|
|
this.captionDiv.appendChild(e);
|
|
}
|
|
parent.appendChild(this.captionDiv);
|
|
|
|
this.leftClearBar = document.createElement("div");
|
|
this.leftClearBar.className = PanelRegister.regClearBarClass;
|
|
this.leftClearBar.style.left = "0";
|
|
parent.appendChild(this.leftClearBar);
|
|
|
|
this.rightClearBar = document.createElement("div");
|
|
this.rightClearBar.className = PanelRegister.regClearBarClass;
|
|
this.rightClearBar.style.right = "0";
|
|
parent.appendChild(this.rightClearBar);
|
|
|
|
e = document.createElement("div");
|
|
e.className = PanelRegister.regSpacerClass;
|
|
e.style.top = "0";
|
|
e.style.right = "0";
|
|
parent.appendChild(e);
|
|
|
|
e = document.createElement("div");
|
|
e.className = PanelRegister.regSpacerClass;
|
|
e.style.bottom = "0";
|
|
e.style.left = "0";
|
|
parent.appendChild(e);
|
|
|
|
e = document.createElement("div");
|
|
e.className = PanelRegister.regSpacerClass;
|
|
e.style.bottom = "0";
|
|
e.style.right = "0";
|
|
parent.appendChild(e);
|
|
}
|
|
|
|
/**************************************/
|
|
|
|
PanelRegister.hSpacing = 24; // horizontal lamp spacing, pixels
|
|
PanelRegister.hOffset = 24; // horizontal lamp offset within container
|
|
PanelRegister.vSpacing = 32; // vertical lamp spacing, pixels
|
|
PanelRegister.vOffset = 0; // vertical lamp offset within container
|
|
PanelRegister.lampDiameter = 20; // lamp outer diameter, pixels
|
|
PanelRegister.panelClass = "panelRegister";
|
|
PanelRegister.captionClass = "panelRegCaption";
|
|
PanelRegister.regSpacerClass = "panelRegSpacer";
|
|
PanelRegister.regClearBarClass = "panelRegClearBar";
|
|
PanelRegister.captionSpanClass = "panelRegSpan";
|
|
PanelRegister.boxCaptionClass = "boxCaption";
|
|
|
|
/**************************************/
|
|
PanelRegister.prototype.addEventListener = function addEventListener(eventName, handler, useCapture) {
|
|
/* Sets an event handler whenever the parent element is clicked */
|
|
|
|
this.element.addEventListener(eventName, handler, useCapture);
|
|
};
|
|
|
|
/**************************************/
|
|
PanelRegister.prototype.xCoord = function xCoord(col) {
|
|
/* Returns the horizontal lamp coordinate in pixels */
|
|
|
|
return ((col-1)*PanelRegister.hSpacing + PanelRegister.hOffset);
|
|
};
|
|
|
|
/**************************************/
|
|
PanelRegister.prototype.yCoord = function yCoord(row) {
|
|
/* Returns the vertical lamp coordinate in pixels */
|
|
|
|
return ((row-1)*PanelRegister.vSpacing + PanelRegister.vOffset);
|
|
};
|
|
|
|
/**************************************/
|
|
PanelRegister.prototype.panelWidth = function panelWidth(cols) {
|
|
/* Returns the width of a register panel in pixels */
|
|
|
|
return (cols-1)*PanelRegister.hSpacing + PanelRegister.hOffset*2 + PanelRegister.lampDiameter;
|
|
};
|
|
|
|
/**************************************/
|
|
PanelRegister.prototype.panelHeight = function panelHeight(rows) {
|
|
/* Returns the height of a register panel in pixels */
|
|
|
|
return (rows-1)*PanelRegister.vSpacing + PanelRegister.vOffset*2 + PanelRegister.lampDiameter;
|
|
};
|
|
|
|
/**************************************/
|
|
PanelRegister.prototype.drawBox = function drawBox(col, lamps, rows, leftStyle, rightStyle) {
|
|
/* Creates a box centered around a specified group of lamps in a register.
|
|
leftStyle and rightStyle specify the left and right borders of the box using
|
|
standard CSS border syntax. Returns the box element */
|
|
var box = document.createElement("div");
|
|
var rightBias = (rightStyle ? 1 : 0);
|
|
|
|
box.style.position = "absolute";
|
|
box.style.left = (this.xCoord(col) - (PanelRegister.hSpacing-PanelRegister.lampDiameter)/2).toString() + "px";
|
|
box.style.width = (PanelRegister.hSpacing*lamps - rightBias).toString() + "px";
|
|
box.style.top = this.yCoord(1).toString() + "px";
|
|
box.style.height = (this.yCoord(rows) - this.yCoord(1) + PanelRegister.lampDiameter).toString() + "px";
|
|
box.style.borderLeft = leftStyle;
|
|
box.style.borderRight = rightStyle;
|
|
box.appendChild(document.createTextNode("\xA0"));
|
|
this.element.appendChild(box);
|
|
return box;
|
|
};
|
|
|
|
/**************************************/
|
|
PanelRegister.prototype.setBoxCaption = function setBoxCaption(box, caption) {
|
|
/* Establishes an optional caption for register lamp box.
|
|
Returns the caption element */
|
|
var e = box.captionDiv;
|
|
|
|
if (e) {
|
|
e.textContent = caption;
|
|
} else {
|
|
box.captionDiv = e = document.createElement("div");
|
|
e.className = PanelRegister.boxCaptionClass;
|
|
e.appendChild(document.createTextNode(caption));
|
|
box.appendChild(e);
|
|
}
|
|
return e;
|
|
};
|
|
|
|
/**************************************/
|
|
PanelRegister.prototype.update = function update(value) {
|
|
/* Update the register lamps from the value of the parameter. This routine
|
|
compares the value of the register that was previously updated against the new
|
|
one in an attempt to minimize the number of lamp flips that need to be done */
|
|
var bitBase = 0;
|
|
var bitNr;
|
|
var lastMask;
|
|
var lastValue = this.lastValue;
|
|
var thisMask;
|
|
var thisValue = Math.floor(Math.abs(value)) % 0x100000000000;
|
|
|
|
if (thisValue != lastValue) {
|
|
this.lastValue = thisValue; // save it for next time
|
|
do {
|
|
// Loop through the masks 30 bits at a time so we can use Javascript bit ops
|
|
bitNr = bitBase;
|
|
lastMask = lastValue % 0x40000000; // get the low-order 30 bits
|
|
lastValue = (lastValue-lastMask)/0x40000000; // shift the value right 30 bits
|
|
thisMask = thisValue % 0x40000000; // ditto for the second value
|
|
thisValue = (thisValue-thisMask)/0x40000000;
|
|
|
|
lastMask ^= thisMask; // determine which bits have changed
|
|
while (lastMask) {
|
|
if (lastMask & 0x01) {
|
|
this.lamps[bitNr].set(thisMask & 0x01);
|
|
}
|
|
if (++bitNr <= this.bits) {
|
|
lastMask >>>= 1;
|
|
thisMask >>>= 1;
|
|
} else {
|
|
thisValue = thisMask = 0;
|
|
lastValue = lastMask = 0;
|
|
break; // out of inner while loop
|
|
}
|
|
}
|
|
|
|
bitBase += 30;
|
|
} while (thisValue || lastValue);
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
PanelRegister.prototype.updateGlow = function updateGlow(glow) {
|
|
/* Update the register lamps from the bitwise intensity values in "glow" */
|
|
var bitNr;
|
|
|
|
for (bitNr=this.bits-1; bitNr>=0; --bitNr) {
|
|
this.lamps[bitNr].set(glow[bitNr]);
|
|
}
|
|
};
|