322 lines
8.9 KiB
JavaScript
322 lines
8.9 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 = 12;
|
|
|
|
// 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 + "'> </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 / monitor.getWidth());
|
|
var x = offset - y * monitor.getWidth();
|
|
monitor.refresh(x, y);
|
|
}
|
|
MEMORY.callbacksEnabled(true);
|
|
});
|
|
};
|
|
|
|
// 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;
|
|
};
|
|
|
|
// 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;
|
|
};
|
|
}
|