v6502/vm/computer/textDisplay.js

342 lines
9.2 KiB
JavaScript

/*
* 6502 Based Virtual Computer
* Copyright (C) 2011 Scott C. Duensing <scott@jaegertech.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
// Our display adapter object.
function textDisplay() {
// This display adapter can render text in 16 foreground colors
// on 16 background colors. A simple "BIOS" is available by
// writing command data into the byte directly following the
// display memory and then the command into the byte after that.
// You may also write directly to display RAM.
// --- Private variables.
var self = this;
// Memory Information.
var MEMORY = null;
var MEMORY_START = 0;
var DATA_BYTE = 0;
var DATA_LSB = 0;
var DATA_BYTE_ADDRESS = 0;
var COMMAND_BYTE_ADDRESS = 0;
// Font size.
var FONT_SIZE = 12;
// Default character cell size.
var CELL_WIDTH = 8;
var CELL_HEIGHT = 16;
// Default display size.
var WIDTH = 80;
var HEIGHT = 25;
// Is this a color display?
var IS_COLOR = true;
// Cursor position.
var CURSOR_X = 0;
var CURSOR_Y = 0;
// Current color attribute. Boring white on black. (LSN=FG, MSN=BG)
var COLOR_VALUE = 7;
// --- Private methods.
// Clear the screen to the current color attributes.
var clearDisplay = function() {
var byte = MEMORY_START;
for (var y=0; y<HEIGHT; y++) {
for (var x=0; x<WIDTH; x++) {
MEMORY.writeByte(byte++, 32); // [SPACE] in ASCII.
if (IS_COLOR) {
MEMORY.writeByte(byte++, COLOR_VALUE);
}
}
}
moveCursor(0, 0);
self.refreshAll();
};
// Move the cursor to a new location.
var moveCursor = function(x, y) {
var element = document.getElementById("cc" + CURSOR_X + "x" + CURSOR_Y);
var style = element.style;
style.border = "none";
CURSOR_X = x;
CURSOR_Y = y;
element = document.getElementById("cc" + CURSOR_X + "x" + CURSOR_Y);
style = element.style;
style.borderBottom = "2px solid white"; //***TODO*** Avoid using background color.
};
// Draw one character to the display & update cursor position.
var drawCharacter = function(c) {
var color = (IS_COLOR ? 2 : 1);
var byte = MEMORY_START + (CURSOR_Y * WIDTH + CURSOR_X) * color;
var x = CURSOR_X;
var y = CURSOR_Y;
if (c == 8) {
// Backspace
if (x > 0)
x--;
} else if (c == 9) {
// TAB
x = (x + 8) & ~7;
} else if (c == 10) {
// Line Feed
y++;
} else if (c == 13) {
// Carriage Return
x = 0;
} else {
// Other characters.
MEMORY.writeByte(byte++, c);
if (IS_COLOR) MEMORY.writeByte(byte++, COLOR_VALUE);
self.refresh(x, y);
x++;
}
// Line wrap?
if (x >= WIDTH - 1) {
x = 0;
y++;
}
// Scroll required?
var didScroll = false;
while (y >= HEIGHT) {
didScroll = true;
byte = MEMORY_START;
for (var i=0; i<(HEIGHT - 1) * WIDTH; i++) {
MEMORY.writeByte(byte, MEMORY.readByte(byte + WIDTH * color));
byte++;
if (IS_COLOR) {
MEMORY.writeByte(byte, MEMORY.readByte(byte + WIDTH * color));
byte++;
}
}
//byte = MEMORY_START + WIDTH * (HEIGHT - 1) * color;
for (var i=0; i<WIDTH; i++) {
MEMORY.writeByte(byte++, 32);
if (IS_COLOR) MEMORY.writeByte(byte++, COLOR_VALUE);
}
y--;
}
// Redraw entire display if we scrolled.
if (didScroll) self.refreshAll();
// Reposition cursor.
moveCursor(x, y);
};
// Draw a zero-terminated string to the display & update cursor position.
var drawString = function(address) {
var byte = address;
do {
var c = MEMORY.readByte(byte++);
if (c != 0) drawCharacter(c);
} while (c != 0);
};
// --- Public methods.
// Attach this display to the VM and build the required HTML in a DIV in the document.
this.attach = function(newMemory, newStartPosition, divId) {
MEMORY = newMemory;
MEMORY_START = newStartPosition;
var html = "<table border='0' cellPadding='0' cellSpacing='0'>";
var byte = MEMORY_START;
for (var h=0; h<HEIGHT; h++) {
html += "<tr vAlign='middle' height='" + CELL_HEIGHT + "px'>";
for (var w=0; w<WIDTH; w++) {
MEMORY.writeByte(byte++, 32); // [SPACE] in ASCII.
if (IS_COLOR) {
MEMORY.writeByte(byte++, COLOR_VALUE);
}
html += "<td align='center' width='" + CELL_WIDTH + "px' id='cc" + w + "x" + h + "'>&nbsp;</td>";
}
html += "</tr>";
}
html += "</table>";
document.getElementById(divId).innerHTML = html;
DATA_BYTE_ADDRESS = byte++;
COMMAND_BYTE_ADDRESS = byte++;
moveCursor(0, 0);
this.refreshAll();
// Set up memory mapped hardware ports.
MEMORY.addWriteCallback(MEMORY_START, MEMORY_START + this.getMemoryNeeded() - 1, null, function(address, value, data){
MEMORY.callbacksEnabled(false);
var offset = address - MEMORY_START;
// Is this a write to one of the command ports or direct to video memory?
if (address >= DATA_BYTE_ADDRESS) {
// Command byte?
if (address == COMMAND_BYTE_ADDRESS) {
if (value == 0) DATA_LSB = DATA_BYTE;
if (value == 1) clearDisplay();
if (value == 2) moveCursor(DATA_BYTE, CURSOR_Y);
if (value == 3) moveCursor(CURSOR_X, DATA_BYTE);
if (value == 4) MEMORY.writeByte(DATA_BYTE_ADDRESS, CURSOR_X);
if (value == 5) MEMORY.writeByte(DATA_BYTE_ADDRESS, CURSOR_Y);
if (value == 6) COLOR_VALUE = DATA_BYTE;
if (value == 7) drawCharacter(DATA_BYTE);
if (value == 8) drawString((DATA_BYTE << 8) + DATA_LSB);
} else {
// Data byte.
DATA_BYTE = value;
}
} else {
if (IS_COLOR) {
// Is this the character byte or the color byte?
if (offset % 2 == 0)
offset = offset / 2; // Character.
else
offset = ((offset + 1) / 2) - 1; // Color.
}
var y = Math.floor(offset / self.getWidth());
var x = offset - y * self.getWidth();
self.refresh(x, y);
}
MEMORY.callbacksEnabled(true);
});
};
// Deserialize display state.
this.deserialize = function(state) {
DDATA_LSB = state.D;
IS_COLOR = state.C;
COLOR_VALUE = state.A;
moveCursor(state.X, state.Y);
};
// Refresh a single character cell.
this.refresh = function(x, y) {
// These are good old PC DOS colors.
// black, dark red, dark green, brown, dark blue, dark magenta, dark cyan, gray
// dim gray, red, green, yellow, blue, magenta, cyan, white
var colors = ["#000000", "#8B0000", "#006400", "#8B8B00", "#00008B", "#8B008B", "#008B8B", "#808080",
"#696969", "#FF0000", "#00FF00", "#FFFF00", "#0000FF", "#FF00FF", "#00FFFF", "#FFFFFF"];
var byte = MEMORY_START + (y * WIDTH + x) * (IS_COLOR ? 2 : 1);
var foreground = colors[7];
var background = colors[0];
if (IS_COLOR) {
// LSN=FG, MSN=BG
var value = MEMORY.readByte(byte + 1);
var LSN = value & 0x0f;
var MSN = (value & 0xf0) >> 4;
foreground = colors[LSN];
background = colors[MSN];
}
var element = document.getElementById("cc" + x + "x" + y);
var style = element.style;
var html = "<span style='font-family:monospace; font-size:" + FONT_SIZE + "px; color:" + foreground + ";'>";
html += String.fromCharCode(MEMORY.readByte(byte));
html += "</span>";
element.innerHTML = html;
style.background = background;
};
// Refresh the entire display.
this.refreshAll = function() {
for (var h=0; h<HEIGHT; h++) {
for (var w=0; w<WIDTH; w++) {
this.refresh(w, h);
}
}
};
// Fetch the current cell height.
this.getCellHeight = function() {
return CELL_HEIGHT;
};
// Fetch the current cell width.
this.getCellWidth = function() {
return CELL_WIDTH;
};
// Fetch the current font size.
this.getFontSize = function() {
return FONT_SIZE;
};
// Fetch the current height.
this.getHeight = function() {
return HEIGHT;
};
// Fetch how much RAM the current settings will require.
this.getMemoryNeeded = function() {
return WIDTH * HEIGHT * (IS_COLOR ? 2 : 1) + 2;
};
// Fetch the current width.
this.getWidth = function() {
return WIDTH;
};
// Is this a color display?
this.isColor = function() {
return IS_COLOR;
};
// Serialize display state.
this.serialize = function() {
var state = {
X: CURSOR_X,
Y: CURSOR_Y,
C: IS_COLOR,
A: COLOR_VALUE,
D: DATA_LSB
};
return state;
};
// Set the current cell height.
this.setCellHeight = function(newCellHeight) {
CELL_HEIGHT = newCellHeight;
};
// Set the current cell width.
this.setCellWidth = function(newCellWidth) {
CELL_WIDTH = newCellWidth;
};
// Set if we're a color display or not.
this.setColor = function(newIsColor) {
IS_COLOR = newIsColor;
};
// Set the current font size.
this.setFontSize = function(newFontSize) {
FONT_SIZE = newFontSize;
};
// Set the current height.
this.setHeight = function(newHeight) {
HEIGHT = newHeight;
};
// Set the current width.
this.setWidth = function(newWidth) {
WIDTH = newWidth;
};
}