/* * 6502 Based Virtual Computer * Copyright (C) 2015 Scott C. Duensing * * 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 textDisplayCanvas() { // 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. // Font texture is 144x256 pixels. // Characters are 9x16 // --- 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 atlas size. var ATLAS_WIDTH = 16; // Character cell size. var CELL_WIDTH = 9; var CELL_HEIGHT = 16; // Default display size. var WIDTH = 80; var HEIGHT = 25; // Is this a color display? var IS_COLOR = true; // 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 COLOR_HEX = ["#000000", "#8B0000", "#006400", "#8B8B00", "#00008B", "#8B008B", "#008B8B", "#808080", "#696969", "#FF0000", "#00FF00", "#FFFF00", "#0000FF", "#FF00FF", "#00FFFF", "#FFFFFF"]; var COLOR_RGB = new Array(); // Cursor position, blink state, and ASCII symbol. var CURSOR_X = 0; var CURSOR_Y = 0; var CURSOR_B = false; var CURSOR_C = 177; // Current color attribute. Boring white on black. (LSN=FG, MSN=BG) var COLOR_VALUE = 7; // The actual DOM canvases. var CANVAS = null; var CANVAS_CONTEXT = null; var FONT = null; var FONT_CONTEXT = null; // --- Private methods. var blinkCursor = function() { // Change cursor state first so it is correct for the rest of the code. CURSOR_B = !CURSOR_B; if (CURSOR_B) { renderCell(CURSOR_X, CURSOR_Y, CURSOR_C, 15, 0, true); } else { self.refresh(CURSOR_X, CURSOR_Y); } }; // Clear the screen to the current color attributes. var clearDisplay = function() { var byte = MEMORY_START; for (var y=0; y> 4; CANVAS_CONTEXT.fillStyle = COLOR_HEX[MSN]; CANVAS_CONTEXT.fillRect(0, 0, CANVAS.width, CANVAS.height); }; // Move the cursor to a new location. var moveCursor = function(x, y) { // Be sure old character cell has been restored if the cursor is currently visible. if (CURSOR_B) self.refresh(CURSOR_X, CURSOR_Y); // Move it. CURSOR_X = x; CURSOR_Y = y; }; // 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++; } } for (var i=0; i> 4; CANVAS_CONTEXT.fillStyle = COLOR_HEX[MSN]; CANVAS_CONTEXT.fillRect(0, CANVAS.height - CELL_HEIGHT, CANVAS.width, CELL_HEIGHT); } // 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); }; // Finish attaching this display to the VM. var finishAttaching = function(newMemory, newStartPosition, divId) { MEMORY = newMemory; MEMORY_START = newStartPosition; // Pre-convert our hex colors to RGB values. for (c=0; c= 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 / WIDTH); var x = offset - y * WIDTH; self.refresh(x, y); } MEMORY.callbacksEnabled(true); }); }; var renderCell = function(x, y, c, f, b, t) { // Find pixel offset in display canvas. var cx = x * CELL_WIDTH; var cy = y * CELL_HEIGHT; // Locate this character in the font atlas. var ax = (c % ATLAS_WIDTH) * CELL_WIDTH; var ay = Math.floor(c / ATLAS_WIDTH) * CELL_HEIGHT; // Render character. var cell = CANVAS_CONTEXT.getImageData(cx, cy, CELL_WIDTH, CELL_HEIGHT); var pixelColor = null; for (rx=0; rx