diff --git a/css/apple2.css b/css/apple2.css
index 866a263..4bf9f06 100644
--- a/css/apple2.css
+++ b/css/apple2.css
@@ -593,16 +593,15 @@ button:focus {
list-style-type: none;
}
-#printer-modal .feed {
- width: 640px;
- height: 480px;
- overflow: auto;
-}
-
#printer-modal .paper {
- min-width: 640px;
- min-height: 480px;
+ width: 70em;
+ height: 90ch;
+ overflow: auto;
background-color: white;
color: black;
- font-family: monospace;
+}
+
+#printer-modal .line {
+ font-family: monospace;
+ white-space: pre;
}
diff --git a/js/.gitignore b/js/.gitignore
deleted file mode 100644
index e83f384..0000000
--- a/js/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-all*.js
diff --git a/js/apple2.js b/js/apple2.js
new file mode 100644
index 0000000..346869e
--- /dev/null
+++ b/js/apple2.js
@@ -0,0 +1,177 @@
+import Apple2IO from './apple2io';
+import { HiresPage, LoresPage, VideoModes } from './canvas';
+import CPU6502 from './cpu6502';
+import MMU from './mmu';
+import RAM from './ram';
+import { debug } from './util';
+
+import SYMBOLS from './symbols';
+
+export function Apple2(options) {
+ var stats = {
+ renderedFrames: 0
+ };
+
+ var kHz = 1023;
+
+ var paused = false;
+
+ var DEBUG = false;
+ var TRACE = false;
+ var MAX_TRACE = 256;
+ var trace = [];
+
+ var runTimer = null;
+ var cpu = new CPU6502({ '65C02': options.enhanced });
+
+ var gr = new LoresPage(1, options.characterRom, options.e, options.screen[0]);
+ var gr2 = new LoresPage(2, options.characterRom, options.e, options.screen[1]);
+ var hgr = new HiresPage(1, options.screen[2]);
+ var hgr2 = new HiresPage(2, options.screen[3]);
+
+ var vm = new VideoModes(gr, hgr, gr2, hgr2, options.e);
+ vm.multiScreen(options.multiScreen);
+
+ var io = new Apple2IO(cpu, vm);
+
+ if (options.e) {
+ var mmu = new MMU(cpu, vm, gr, gr2, hgr, hgr2, io, options.rom);
+ cpu.addPageHandler(mmu);
+ } else {
+ var ram1 = new RAM(0x00, 0x03),
+ ram2 = new RAM(0x0C, 0x1F),
+ ram3 = new RAM(0x60, 0xBF);
+
+ cpu.addPageHandler(ram1);
+ cpu.addPageHandler(gr);
+ cpu.addPageHandler(gr2);
+ cpu.addPageHandler(ram2);
+ cpu.addPageHandler(hgr);
+ cpu.addPageHandler(hgr2);
+ cpu.addPageHandler(ram3);
+ cpu.addPageHandler(io);
+ cpu.addPageHandler(options.rom);
+ }
+
+ var _requestAnimationFrame =
+ window.requestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.msRequestAnimationFrame;
+
+ function run() {
+ if (runTimer) {
+ clearInterval(runTimer);
+ }
+
+ var interval = 30;
+
+ var now, last = Date.now();
+ var runFn = function() {
+ now = Date.now();
+
+ var step = (now - last) * kHz, stepMax = kHz * interval;
+ last = now;
+ if (step > stepMax) {
+ step = stepMax;
+ }
+
+ if (DEBUG) {
+ cpu.stepCyclesDebug(TRACE ? 1 : step, function() {
+ var line = cpu.dumpRegisters() + ' ' +
+ cpu.dumpPC(undefined, SYMBOLS);
+ if (TRACE) {
+ debug(line);
+ } else {
+ trace.push(line);
+ if (trace.length > MAX_TRACE) {
+ trace.shift();
+ }
+ }
+ });
+ } else {
+ cpu.stepCycles(step);
+ }
+ if (io.annunciator(0)) {
+ if (options.multiScreen) {
+ vm.blit();
+ }
+ if (io.blit()) {
+ stats.renderedFrames++;
+ }
+ } else {
+ if (vm.blit()) {
+ stats.renderedFrames++;
+ }
+ }
+ io.tick();
+ options.tick();
+
+ if (!paused && _requestAnimationFrame) {
+ _requestAnimationFrame(runFn);
+ }
+ };
+ if (_requestAnimationFrame) {
+ _requestAnimationFrame(runFn);
+ } else {
+ runTimer = setInterval(runFn, interval);
+ }
+ }
+
+ function stop() {
+ if (runTimer) {
+ clearInterval(runTimer);
+ }
+ runTimer = null;
+ }
+
+ function saveState() {
+ var state = {
+ cpu: cpu.getState(),
+ };
+
+ return state;
+ }
+
+ function restoreState(state) {
+ cpu.setState(state.cpu);
+ }
+
+ return {
+ reset: function () {
+ cpu.reset();
+ },
+
+ run: function () {
+ run();
+ },
+
+ stop: function () {
+ stop();
+ },
+
+ saveState: function () {
+ saveState();
+ },
+
+ restoreState: function () {
+ restoreState();
+ },
+
+ getStats: function () {
+ return stats;
+ },
+
+ getCPU: function () {
+ return cpu;
+ },
+
+ getIO: function () {
+ return io;
+ },
+
+ getVideoModes: function () {
+ return vm;
+ }
+ };
+}
diff --git a/js/apple2io.js b/js/apple2io.js
index a6cb45f..67bbb2b 100644
--- a/js/apple2io.js
+++ b/js/apple2io.js
@@ -338,6 +338,14 @@ export default function Apple2IO(cpu, callbacks)
callbacks.reset();
},
+ blit: function apple2io_blit() {
+ var card = _slot[3];
+ if (card && card.blit) {
+ return card.blit();
+ }
+ return false;
+ },
+
read: function apple2io_read(page, off) {
var result = 0;
var slot;
diff --git a/js/applesoft/compiler.js b/js/applesoft/compiler.js
index dbf0d4c..098c578 100644
--- a/js/applesoft/compiler.js
+++ b/js/applesoft/compiler.js
@@ -201,7 +201,7 @@ export default function ApplesoftCompiler(mem)
if (lookAhead === 'O') {
result.push(lookAhead.charCodeAt(0));
foundToken = 'TO';
- tokenIdx++; foundToken = 'TO';
+ tokenIdx++;
}
}
foundToken = possibleToken;
diff --git a/js/applesoft/decompiler.js b/js/applesoft/decompiler.js
index 89d5a42..f33e4e2 100644
--- a/js/applesoft/decompiler.js
+++ b/js/applesoft/decompiler.js
@@ -154,7 +154,7 @@ export default function ApplesoftDump(mem)
return str;
val = readByte(addr++);
- if (val > 0x80) {
+ if (val >= 0x80) {
line += ' ';
line += TOKENS[val];
line += ' ';
diff --git a/js/cards/disk2.js b/js/cards/disk2.js
index b3067e9..6cc5fef 100644
--- a/js/cards/disk2.js
+++ b/js/cards/disk2.js
@@ -32,9 +32,8 @@ export const DISK_TYPES = [
'woz'
];
-export default function DiskII(io, slot, callbacks, sectors = 16)
+export default function DiskII(io, callbacks, sectors = 16)
{
- slot = slot || 6;
var _drives = [
{ // Drive 1
format: 'dsk',
@@ -134,7 +133,7 @@ export default function DiskII(io, slot, callbacks, sectors = 16)
}
function _init() {
- debug('Disk ][ in slot', slot);
+ debug('Disk ][');
}
var _clock = 0;
diff --git a/js/cards/langcard.js b/js/cards/langcard.js
index 4beb95c..bef178f 100644
--- a/js/cards/langcard.js
+++ b/js/cards/langcard.js
@@ -12,7 +12,7 @@
import RAM from '../ram';
import { debug } from '../util';
-export default function LanguageCard(io, slot, rom) {
+export default function LanguageCard(io, rom) {
var _rom = rom;
var _bank1 = null;
var _bank2 = null;
@@ -30,7 +30,7 @@ export default function LanguageCard(io, slot, rom) {
var _write2 = null;
function _init() {
- debug('Language card in slot', slot);
+ debug('Language card');
_bank1 = new RAM(0xd0, 0xdf);
_bank2 = new RAM(0xd0, 0xdf);
diff --git a/js/cards/parallel.js b/js/cards/parallel.js
index bbf2a67..c2d6064 100644
--- a/js/cards/parallel.js
+++ b/js/cards/parallel.js
@@ -12,10 +12,9 @@
import { debug } from '../util';
import { rom } from '../roms/cards/parallel';
-export default function Parallel(io, slot, cbs) {
- slot = slot || 1;
+export default function Parallel(io, cbs) {
- debug('Parallel card in slot', slot);
+ debug('Parallel card');
var LOC = {
IOREG: 0x80
diff --git a/js/cards/ramfactor.js b/js/cards/ramfactor.js
index 1389f66..122cc97 100644
--- a/js/cards/ramfactor.js
+++ b/js/cards/ramfactor.js
@@ -13,7 +13,7 @@ import { base64_decode, base64_encode } from '../base64';
import { allocMem, debug } from '../util';
import { rom } from '../roms/cards/ramfactor';
-export default function RAMFactor(io, slot, size) {
+export default function RAMFactor(io, size) {
var mem = [];
var _firmware = 0;
@@ -37,7 +37,7 @@ export default function RAMFactor(io, slot, size) {
};
function _init() {
- debug('RAMFactor card in slot', slot);
+ debug('RAMFactor card');
mem = allocMem(size);
for (var off = 0; off < size; off++) {
@@ -130,13 +130,7 @@ export default function RAMFactor(io, slot, size) {
return _access(off, val);
},
read: function ramfactor_read(page, off) {
- var result;
- if (page == 0xc0 + slot) {
- result = rom[slot << 8 | off];
- } else {
- result = rom[_firmware << 12 | (page - 0xC0) << 8 | off];
- }
- return result;
+ return rom[_firmware << 12 | (page - 0xC0) << 8 | off];
},
write: function ramfactor_write() {},
reset: function ramfactor_reset() {
diff --git a/js/cards/smartport.js b/js/cards/smartport.js
index 66f13f1..ffc2d19 100644
--- a/js/cards/smartport.js
+++ b/js/cards/smartport.js
@@ -12,7 +12,7 @@
import { base64_decode } from '../base64';
import { debug, toHex } from '../util';
-export default function SmartPort(io, slot, cpu) {
+export default function SmartPort(io, cpu) {
/*
$Cn01=$20
diff --git a/js/cards/thunderclock.js b/js/cards/thunderclock.js
index ab99be0..fb08bb7 100644
--- a/js/cards/thunderclock.js
+++ b/js/cards/thunderclock.js
@@ -12,7 +12,7 @@
import { debug, toHex } from '../util';
import { rom } from '../roms/cards/thunderclock';
-export default function Thunderclock(io, slot)
+export default function Thunderclock()
{
var LOC = {
CONTROL: 0x80,
@@ -33,7 +33,7 @@ export default function Thunderclock(io, slot)
};
function _init() {
- debug('Thunderclock card in slot', slot);
+ debug('Thunderclock');
}
var _clock = false;
diff --git a/js/cards/videoterm.js b/js/cards/videoterm.js
index b5503d2..3d2e70e 100644
--- a/js/cards/videoterm.js
+++ b/js/cards/videoterm.js
@@ -12,10 +12,8 @@
import { allocMemPages, debug } from '../util';
import { ROM, VIDEO_ROM } from '../roms/cards/videoterm';
-export default function Videoterm(io, slot, context) {
- slot = slot || 3;
-
- debug('Videx Videoterm card in slot', slot);
+export default function Videoterm(io, context) {
+ debug('Videx Videoterm');
var LOC = {
IOREG: 0x80,
diff --git a/js/main2.js b/js/main2.js
index bdfcf7a..7e19726 100644
--- a/js/main2.js
+++ b/js/main2.js
@@ -1,26 +1,14 @@
-import MicroModal from 'micromodal';
-
-import Apple2IO from './apple2io';
-import ApplesoftDump from './applesoft/decompiler';
-import { HiresPage, LoresPage, VideoModes } from './canvas';
-import CPU6502 from './cpu6502';
import Prefs from './prefs';
-import RAM from './ram';
-import { debug, gup, hup } from './util';
-import Audio from './ui/audio';
-import DriveLights from './ui/drive_lights';
-import { gamepad, configGamepad, initGamepad, processGamepad } from './ui/gamepad';
-import KeyBoard from './ui/keyboard';
+import { driveLights, initUI, updateUI } from './ui/apple2';
import Printer from './ui/printer';
-import Tape, { TAPE_TYPES } from './ui/tape';
-import DiskII, { DISK_TYPES } from './cards/disk2';
+import DiskII from './cards/disk2';
import LanguageCard from './cards/langcard';
import Parallel from './cards/parallel';
import RAMFactor from './cards/ramfactor';
import Thunderclock from './cards/thunderclock';
-import Videoterm from './cards/videoterm';
+import VideoTerm from './cards/videoterm';
import apple2_charset from './roms/apple2_char';
import apple2j_charset from './roms/apple2j_char';
@@ -32,253 +20,15 @@ import Apple2jROM from './roms/apple2j_char';
import IntBASIC from './roms/intbasic';
import OriginalROM from './roms/original';
-import SYMBOLS from './symbols';
+import { Apple2 } from './apple2';
-var kHz = 1023;
-
-var focused = false;
-var startTime = Date.now();
-var lastCycles = 0;
-var renderedFrames = 0, lastFrames = 0;
-var paused = false;
-
-var hashtag;
-
-var DEBUG = false;
-var TRACE = false;
-var MAX_TRACE = 256;
-var trace = [];
-
-var disk_categories = {'Local Saves': []};
-var disk_sets = {};
-var disk_cur_name = [];
-var disk_cur_cat = [];
-
-var tape;
-var disk2;
-var driveLights;
-var _currentDrive = 1;
-
-export function openLoad(drive, event)
-{
- _currentDrive = parseInt(drive, 10);
- if (event.metaKey) {
- openLoadHTTP(drive);
- } else {
- if (disk_cur_cat[drive]) {
- document.querySelector('#category_select').value = disk_cur_cat[drive];
- selectCategory();
- }
- MicroModal.show('load-modal');
- }
-}
-
-export function openSave(drive, event)
-{
- _currentDrive = parseInt(drive, 10);
-
- var mimetype = 'application/octet-stream';
- var data = disk2.getBinary(drive);
- var a = document.querySelector('#local_save_link');
-
- var blob = new Blob([data], { 'type': mimetype });
- a.href = window.URL.createObjectURL(blob);
- a.download = driveLights.label(drive) + '.dsk';
-
- if (event.metaKey) {
- dumpDisk(drive);
- } else {
- document.querySelector('#save_name').value = driveLights.label(drive);
- MicroModal.show('save-modal');
- }
-}
-
-export function handleDragOver(drive, event) {
- event.preventDefault();
- event.dataTransfer.dropEffect = 'copy';
-}
-
-export function handleDragEnd(drive, event) {
- var dt = event.dataTransfer;
- if (dt.items) {
- for (var i = 0; i < dt.items.length; i++) {
- dt.items.remove(i);
- }
- } else {
- event.dataTransfer.clearData();
- }
-}
-
-export function handleDrop(drive, event) {
- event.preventDefault();
- event.stopPropagation();
-
- if (drive < 1) {
- if (!disk2.getMetadata(1)) {
- drive = 1;
- } else if (!disk2.getMetadata(2)) {
- drive = 2;
- } else {
- drive = 1;
- }
- }
- var dt = event.dataTransfer;
- if (dt.files.length == 1) {
- doLoadLocal(drive, dt.files[0]);
- } else if (dt.files.length == 2) {
- doLoadLocal(1, dt.files[0]);
- doLoadLocal(2, dt.files[1]);
- } else {
- for (var idx = 0; idx < dt.items.length; idx++) {
- if (dt.items[idx].type === 'text/uri-list') {
- dt.items[idx].getAsString(function(url) {
- var parts = document.location.hash.split('|');
- parts[drive - 1] = url;
- document.location.hash = parts.join('|');
- });
- }
- }
- }
-}
-
-var loading = false;
-
-function loadAjax(drive, url) {
- loading = true;
- MicroModal.show('loading-modal');
-
- fetch(url).then(function(response) {
- return response.json();
- }).then(function(data) {
- if (data.type == 'binary') {
- loadBinary(drive, data);
- } else if (DISK_TYPES.indexOf(data.type) > -1) {
- loadDisk(drive, data);
- }
- initGamepad(data.gamepad);
- MicroModal.close('loading-modal');
- loading = false;
- }).catch(function(error) {
- window.alert(error || status);
- MicroModal.close('loading-modal');
- loading = false;
- });
-}
-
-export function doLoad() {
- MicroModal.close('load-modal');
- var urls = document.querySelector('#disk_select').value, url;
- if (urls && urls.length) {
- if (typeof(urls) == 'string') {
- url = urls;
- } else {
- url = urls[0];
- }
- }
-
- var files = document.querySelector('#local_file').files;
- if (files.length == 1) {
- doLoadLocal(_currentDrive, files[0]);
- } else if (url) {
- var filename;
- if (url.substr(0,6) == 'local:') {
- filename = url.substr(6);
- if (filename == '__manage') {
- openManage();
- } else {
- loadLocalStorage(_currentDrive, filename);
- }
- } else {
- var r1 = /json\/disks\/(.*).json$/.exec(url);
- if (r1) {
- filename = r1[1];
- } else {
- filename = url;
- }
- var parts = document.location.hash.split('|');
- parts[_currentDrive - 1] = filename;
- document.location.hash = parts.join('|');
- }
- }
-}
-
-export function doSave() {
- var name = document.querySelector('#save_name').value;
- saveLocalStorage(_currentDrive, name);
- MicroModal.close('save-modal');
-}
-
-export function doDelete(name) {
- if (window.confirm('Delete ' + name + '?')) {
- deleteLocalStorage(name);
- }
-}
-
-function doLoadLocal(drive, file) {
- var parts = file.name.split('.');
- var ext = parts[parts.length - 1].toLowerCase();
- if (DISK_TYPES.indexOf(ext) > -1) {
- doLoadLocalDisk(drive, file);
- } else if (TAPE_TYPES.indexOf(ext) > -1) {
- tape.doLoadLocalTape(file);
- } else {
- window.alert('Unknown file type: ' + ext);
- }
-}
-
-function doLoadLocalDisk(drive, file) {
- var fileReader = new FileReader();
- fileReader.onload = function() {
- var parts = file.name.split('.');
- var ext = parts.pop().toLowerCase();
- var name = parts.join('.');
- if (disk2.setBinary(drive, name, ext, this.result)) {
- driveLights.label(drive, name);
- initGamepad();
- }
- };
- fileReader.readAsArrayBuffer(file);
-}
-
-function doLoadHTTP(drive, _url) {
- var url = _url || document.querySelector('#http_url').value;
- if (url) {
- var req = new XMLHttpRequest();
- req.open('GET', url, true);
- req.responseType = 'arraybuffer';
-
- req.onload = function() {
- var urlParts = url.split('/');
- var file = urlParts.pop();
- var fileParts = file.split('.');
- var ext = fileParts.pop().toLowerCase();
- var name = decodeURIComponent(fileParts.join('.'));
- if (disk2.setBinary(drive, name, ext, req.response)) {
- driveLights.label(drive, name);
- if (!_url) {
- MicroModal.close('http-modal');
- }
- initGamepad();
- }
- };
- req.send(null);
- }
-}
-
-function openLoadHTTP(drive) {
- _currentDrive = parseInt(drive, 10);
- MicroModal.show('http-modal');
-}
-
-function openManage() {
- MicroModal.show('manage-modal');
-}
+export * from './ui/apple2';
var prefs = new Prefs();
var romVersion = prefs.readPref('computer_type2');
-var multiScreen = false;
var rom;
-var char_rom = apple2_charset;
+var characterRom = apple2_charset;
+
switch (romVersion) {
case 'apple2':
rom = new IntBASIC();
@@ -288,735 +38,73 @@ case 'original':
break;
case 'apple2jplus':
rom = new Apple2jROM();
- char_rom = apple2j_charset;
+ characterRom = apple2j_charset;
break;
case 'apple2pig':
rom = new Apple2ROM();
- char_rom = pigfont_charset;
+ characterRom = pigfont_charset;
break;
case 'apple2lc':
rom = new Apple2ROM();
- char_rom = apple2lc_charset;
+ characterRom = apple2lc_charset;
break;
default:
rom = new Apple2ROM();
}
-var runTimer = null;
-var cpu = new CPU6502();
-
-var context1, context2, context3, context4;
+var options = {
+ screen: [],
+ multiScreen: false,
+ rom: rom,
+ characterRom: characterRom,
+ e: false,
+ enhanced: false,
+ cards: [],
+ tick: updateUI
+};
var canvas1 = document.getElementById('screen');
var canvas2 = document.getElementById('screen2');
var canvas3 = document.getElementById('screen3');
var canvas4 = document.getElementById('screen4');
-context1 = canvas1.getContext('2d');
+options.screen[0] = canvas1.getContext('2d');
if (canvas4) {
- multiScreen = true;
- context2 = canvas2.getContext('2d');
- context3 = canvas3.getContext('2d');
- context4 = canvas4.getContext('2d');
+ options.multiScreen = true;
+ options.screen[1] = canvas2.getContext('2d');
+ options.screen[2] = canvas3.getContext('2d');
+ options.screen[3] = canvas4.getContext('2d');
} else if (canvas2) {
- multiScreen = true;
- context2 = context1;
- context3 = canvas2.getContext('2d');
- context4 = context3;
+ options.multiScreen = true;
+ options.screen[1] = options.screen[0];
+ options.screen[2] = canvas2.getContext('2d');
+ options.screen[3] = options.screen[2];
} else {
- context2 = context1;
- context3 = context1;
- context4 = context1;
+ options.screen[1] = options.screen[0];
+ options.screen[2] = options.screen[0];
+ options.screen[3] = options.screen[0];
}
-var gr = new LoresPage(1, char_rom, false, context1);
-var gr2 = new LoresPage(2, char_rom, false, context2);
-var hgr = new HiresPage(1, context3);
-var hgr2 = new HiresPage(2, context4);
+var apple2 = new Apple2(options);
+var cpu = apple2.getCPU();
+var io = apple2.getIO();
-var ram1 = new RAM(0x00, 0x03),
- ram2 = new RAM(0x0C, 0x1F),
- ram3 = new RAM(0x60, 0xBF);
-
-
-var vm = new VideoModes(gr, hgr, gr2, hgr2, false);
-vm.multiScreen(multiScreen);
-var dumper = new ApplesoftDump(cpu);
-
-driveLights = new DriveLights();
-export var io = new Apple2IO(cpu, vm);
-var keyboard = new KeyBoard(cpu, io);
-var audio = new Audio(io);
-tape = new Tape(io);
var printer = new Printer('#printer-modal .paper');
-var lc = new LanguageCard(io, 0, rom);
-var parallel = new Parallel(io, 1, printer);
-var slinky = new RAMFactor(io, 2, 1024 * 1024);
-var videoterm = new Videoterm(io, 3, context1);
-disk2 = new DiskII(io, 6, driveLights);
-var clock = new Thunderclock(io, 7);
+var lc = new LanguageCard(io, rom);
+var parallel = new Parallel(io, printer);
+var videoTerm = new VideoTerm(io, options.screen[0]);
+var slinky = new RAMFactor(io, 1024 * 1024);
+var disk2 = new DiskII(io, driveLights);
+var clock = new Thunderclock(io);
-cpu.addPageHandler(ram1);
-cpu.addPageHandler(gr);
-cpu.addPageHandler(gr2);
-cpu.addPageHandler(ram2);
-cpu.addPageHandler(hgr);
-cpu.addPageHandler(hgr2);
-cpu.addPageHandler(ram3);
-cpu.addPageHandler(io);
-cpu.addPageHandler(lc);
+initUI(apple2, disk2, false);
io.setSlot(0, lc);
io.setSlot(1, parallel);
io.setSlot(2, slinky);
-io.setSlot(3, videoterm);
+io.setSlot(4, clock);
+io.setSlot(3, videoTerm);
io.setSlot(6, disk2);
-io.setSlot(7, clock);
-var showFPS = false;
-
-function updateKHz() {
- var now = Date.now();
- var ms = now - startTime;
- var cycles = cpu.cycles();
- var delta;
-
- if (showFPS) {
- delta = renderedFrames - lastFrames;
- var fps = parseInt(delta/(ms/1000), 10);
- document.querySelector('#khz').innerText = fps + 'fps';
- } else {
- delta = cycles - lastCycles;
- var khz = parseInt(delta/ms);
- document.querySelector('#khz').innerText = khz + 'KHz';
- }
-
- startTime = now;
- lastCycles = cycles;
- lastFrames = renderedFrames;
-}
-
-export function toggleShowFPS() {
- showFPS = !showFPS;
-}
-
-export function updateSound() {
- var on = document.querySelector('#enable_sound').checked;
- var label = document.querySelector('#toggle-sound i');
- audio.enable(on);
- if (on) {
- label.classList.remove('fa-volume-off');
- label.classList.add('fa-volume-up');
- } else {
- label.classList.remove('fa-volume-up');
- label.classList.add('fa-volume-off');
- }
-}
-
-function dumpDisk(drive) {
- var wind = window.open('', '_blank');
- wind.document.title = driveLights.label(drive);
- wind.document.write('
');
- wind.document.write(disk2.getJSON(drive, true));
- wind.document.write('
');
- wind.document.close();
-}
-
-export function dumpProgram() {
- var wind = window.open('', '_blank');
- wind.document.title = 'Program Listing';
- wind.document.write('');
- wind.document.write(dumper.toString());
- wind.document.write('
');
- wind.document.close();
-}
-
-export function step()
-{
- if (runTimer) {
- clearInterval(runTimer);
- }
- runTimer = null;
-
- cpu.step(function() {
- debug(cpu.dumpRegisters());
- debug(cpu.dumpPC());
- });
-}
-
-var accelerated = false;
-
-export function updateCPU()
-{
- accelerated = document.querySelector('#accelerator_toggle').checked;
- kHz = accelerated ? 4092 : 1023;
- io.updateHz(kHz * 1000);
- if (runTimer) {
- run();
- }
-}
-
-var _requestAnimationFrame =
- window.requestAnimationFrame ||
- window.mozRequestAnimationFrame ||
- window.webkitRequestAnimationFrame ||
- window.msRequestAnimationFrame;
-
-function run(pc) {
- if (runTimer) {
- clearInterval(runTimer);
- }
-
- if (pc) {
- cpu.setPC(pc);
- }
-
- var ival = 30;
-
- var now, last = Date.now();
- var runFn = function() {
- now = Date.now();
-
- var step = (now - last) * kHz, stepMax = kHz * ival;
- last = now;
- if (step > stepMax) {
- step = stepMax;
- }
-
- if (document.location.hash != hashtag) {
- hashtag = document.location.hash;
- var hash = hup();
- if (hash) {
- processHash(hash);
- }
- }
- if (!loading) {
- if (DEBUG) {
- cpu.stepCyclesDebug(TRACE ? 1 : step, function() {
- var line = cpu.dumpRegisters() + ' ' +
- cpu.dumpPC(undefined, SYMBOLS);
- if (TRACE) {
- debug(line);
- } else {
- trace.push(line);
- if (trace.length > MAX_TRACE) {
- trace.shift();
- }
- }
- });
- } else {
- cpu.stepCycles(step);
- }
- if (io.annunciator(0)) {
- if (multiScreen) {
- vm.blit();
- }
- if (videoterm.blit()) {
- renderedFrames++;
- }
- } else {
- if (vm.blit()) {
- renderedFrames++;
- }
- }
- io.tick();
- }
-
- processGamepad(io);
-
- if (!paused && _requestAnimationFrame) {
- _requestAnimationFrame(runFn);
- }
- };
- if (_requestAnimationFrame) {
- _requestAnimationFrame(runFn);
- } else {
- runTimer = setInterval(runFn, ival);
- }
-}
-
-function stop() {
- if (runTimer) {
- clearInterval(runTimer);
- }
- runTimer = null;
-}
-
-export function reset()
-{
- cpu.reset();
-}
-
-var state = null;
-
-function storeStateLocal() {
- window.localStorage['apple2.state'] = JSON.stringify(state);
-}
-
-function restoreStateLocal() {
- var data = window.localStorage['apple2.state'];
- if (data) {
- state = JSON.parse(data);
- }
-}
-
-function saveState() {
- if (state && !window.confirm('Overwrite Saved State?')) {
- return;
- }
-
- state = {
- cpu: cpu.getState(),
- ram1: ram1.getState(),
- ram2: ram2.getState(),
- ram3: ram3.getState(),
- io: io.getState(),
- lc: lc.getState(),
- vm: vm.getState(),
- disk2: disk2.getState(),
- driveLights: driveLights.getState()
- };
- if (slinky) {
- state.slinky = slinky.getState();
- }
-
- if (window.localStorage) {
- storeStateLocal();
- }
-}
-
-function restoreState() {
- if (window.localStorage) {
- restoreStateLocal();
- }
- if (!state) {
- return;
- }
- cpu.setState(state.cpu);
- ram1.setState(state.ram1);
- ram2.setState(state.ram2);
- ram3.setState(state.ram3);
- io.setState(state.io);
- lc.setState(state.lc);
- vm.setState(state.vm);
- disk2.setState(state.disk2);
- driveLights.setState(state.driveLights);
- if (slinky && state.slinky) {
- slinky.setState(state.slinky);
- }
-}
-
-function loadBinary(bin) {
- stop();
- for (var idx = 0; idx < bin.length; idx++) {
- var pos = bin.start + idx;
- cpu.write(pos >> 8, pos & 0xff, bin.data[idx]);
- }
- run(bin.start);
-}
-
-export function selectCategory() {
- document.querySelector('#disk_select').innerHTML = '';
- var cat = disk_categories[document.querySelector('#category_select').value];
- if (cat) {
- for (var idx = 0; idx < cat.length; idx++) {
- var file = cat[idx], name = file.name;
- if (file.disk) {
- name += ' - ' + file.disk;
- }
- var option = document.createElement('option');
- option.value = file.filename;
- option.innerText = name;
- document.querySelector('#disk_select').append(option);
- if (disk_cur_name[_currentDrive] == name) {
- option.selected = true;
- }
- }
- }
-}
-
-export function selectDisk() {
- document.querySelector('#local_file').value = '';
-}
-
-export function clickDisk() {
- doLoad();
-}
-
-function loadDisk(drive, disk) {
- var name = disk.name;
- var category = disk.category;
-
- if (disk.disk) {
- name += ' - ' + disk.disk;
- }
-
- disk_cur_cat[drive] = category;
- disk_cur_name[drive] = name;
-
- driveLights.label(drive, name);
- disk2.setDisk(drive, disk);
- initGamepad(disk.gamepad);
-}
-
-/*
- * LocalStorage Disk Storage
- */
-
-function updateLocalStorage() {
- var diskIndex = JSON.parse(window.localStorage.diskIndex || '{}');
- var names = [], name, cat;
-
- for (name in diskIndex) {
- if (diskIndex.hasOwnProperty(name)) {
- names.push(name);
- }
- }
-
- cat = disk_categories['Local Saves'] = [];
- document.querySelector('#manage-modal-content').innerHTML = '';
-
- names.forEach(function(name) {
- cat.push({
- 'category': 'Local Saves',
- 'name': name,
- 'filename': 'local:' + name
- });
- document.querySelector('#manage-modal-content').innerHTML =
- '' +
- name +
- ' Delete
';
- });
- cat.push({
- 'category': 'Local Saves',
- 'name': 'Manage Saves...',
- 'filename': 'local:__manage'
- });
-}
-
-function saveLocalStorage(drive, name) {
- var diskIndex = JSON.parse(window.localStorage.diskIndex || '{}');
-
- var json = disk2.getJSON(drive);
- diskIndex[name] = json;
-
- window.localStorage.diskIndex = JSON.stringify(diskIndex);
-
- window.alert('Saved');
-
- driveLights.label(drive, name);
- driveLights.dirty(drive, false);
- updateLocalStorage();
-}
-
-function deleteLocalStorage(name) {
- var diskIndex = JSON.parse(window.localStorage.diskIndex || '{}');
- if (diskIndex[name]) {
- delete diskIndex[name];
- window.alert('Deleted');
- }
- window.localStorage.diskIndex = JSON.stringify(diskIndex);
- updateLocalStorage();
-}
-
-function loadLocalStorage(drive, name) {
- var diskIndex = JSON.parse(window.localStorage.diskIndex || '{}');
- if (diskIndex[name]) {
- disk2.setJSON(drive, diskIndex[name]);
- driveLights.label(drive, name);
- driveLights.dirty(drive, false);
- }
-}
-
-function processHash(hash) {
- var files = hash.split('|');
- for (var idx = 0; idx < files.length; idx++) {
- var file = files[idx];
- if (file.indexOf('://') > 0) {
- var parts = file.split('.');
- var ext = parts[parts.length - 1].toLowerCase();
- if (ext == 'json') {
- loadAjax(idx + 1, file);
- } else {
- doLoadHTTP(idx + 1, file);
- }
- } else {
- loadAjax(idx + 1, 'json/disks/' + file + '.json');
- }
- }
-}
-
-/*
- * Keyboard/Gamepad routines
- */
-
-function _keydown(evt) {
- if (!focused && (!evt.metaKey || evt.ctrlKey)) {
- evt.preventDefault();
-
- var key = keyboard.mapKeyEvent(evt);
- if (key != 0xff) {
- io.keyDown(key);
- }
- }
- if (evt.keyCode === 112) { // F1 - Reset
- cpu.reset();
- evt.preventDefault(); // prevent launching help
- } else if (evt.keyCode === 113) { // F2 - Full Screen
- var elem = document.getElementById('screen');
- if (evt.shiftKey) { // Full window, but not full screen
- document.querySelector('#display').classList.toggle('zoomwindow');
- document.querySelector('#display > div').classList.toggle('overscan');
- document.querySelector('#display > div').classList.toggle('flexbox-centering');
- document.querySelector('#screen').classList.toggle('maxhw');
- document.querySelector('#header').classList.toggle('hidden');
- document.querySelectorAll('.inset').forEach(function(el) { el.classList.toggle('hidden'); });
- } else if (document.webkitCancelFullScreen) {
- if (document.webkitIsFullScreen) {
- document.webkitCancelFullScreen();
- } else {
- if (Element.ALLOW_KEYBOARD_INPUT) {
- elem.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
- } else {
- elem.webkitRequestFullScreen();
- }
- }
- } else if (document.mozCancelFullScreen) {
- if (document.mozIsFullScreen) {
- document.mozCancelFullScreen();
- } else {
- elem.mozRequestFullScreen();
- }
- }
- } else if (evt.keyCode === 114) { // F3
- io.keyDown(0x1b);
- } else if (evt.keyCode === 117) { // F6 Quick Save
- saveState();
- } else if (evt.keyCode === 120) { // F9 Quick Restore
- restoreState();
- } else if (evt.keyCode == 16) { // Shift
- keyboard.shiftKey(true);
- } else if (evt.keyCode == 17) { // Control
- keyboard.controlKey(true);
- }
-}
-
-function _keyup(evt) {
- if (!focused)
- io.keyUp();
-
- if (evt.keyCode == 16) { // Shift
- keyboard.shiftKey(false);
- } else if (evt.keyCode == 17) { // Control
- keyboard.controlKey(false);
- }
-}
-
-export function updateScreen() {
- var green = document.querySelector('#green_screen').checked;
- var scanlines = document.querySelector('#show_scanlines').checked;
-
- vm.green(green);
- vm.scanlines(scanlines);
-}
-
-var disableMouseJoystick = false;
-var flipX = false;
-var flipY = false;
-var swapXY = false;
-
-export function updateJoystick() {
- disableMouseJoystick = document.querySelector('#disable_mouse').checked;
- flipX = document.querySelector('#flip_x').checked;
- flipY = document.querySelector('#flip_y').checked;
- swapXY = document.querySelector('#swap_x_y').checked;
- configGamepad(flipX, flipY);
-
- if (disableMouseJoystick) {
- io.paddle(0, 0.5);
- io.paddle(1, 0.5);
- return;
- }
-}
-
-function _mousemove(evt) {
- if (gamepad || disableMouseJoystick) {
- return;
- }
-
- var s = document.querySelector('#screen');
- var offset = s.getBoundingClientRect();
- var x = (evt.pageX - offset.left) / s.clientWidth,
- y = (evt.pageY - offset.top) / s.clientHeight,
- z = x;
-
- if (swapXY) {
- x = y;
- y = z;
- }
-
- io.paddle(0, flipX ? 1 - x : x);
- io.paddle(1, flipY ? 1 - y : y);
-}
-
-export function pauseRun() {
- var label = document.querySelector('#pause-run i');
- if (paused) {
- run();
- label.classList.remove('fa-play');
- label.classList.add('fa-pause');
- } else {
- stop();
- label.classList.remove('fa-pause');
- label.classList.add('fa-play');
- }
- paused = !paused;
-}
-
-export function toggleSound() {
- var enableSound = document.querySelector('#enable_sound');
- enableSound.checked = !enableSound.checked;
- updateSound();
-}
-
-export function openOptions() {
- MicroModal.show('options-modal');
-}
-
-export function openPrinterModal() {
- MicroModal.show('printer-modal');
-}
-
-MicroModal.init();
-
-document.addEventListener('DOMContentLoaded', function() {
- hashtag = document.location.hash;
-
- /*
- * Input Handling
- */
-
- window.addEventListener('keydown', _keydown);
- window.addEventListener('keyup', _keyup);
- window.addEventListener('mousedown', function() { audio.autoStart(); });
-
- document.querySelectorAll('canvas').forEach(function(canvas) {
- canvas.addEventListener('mousedown', function(evt) {
- if (!gamepad) {
- io.buttonDown(evt.which == 1 ? 0 : 1);
- }
- evt.preventDefault();
- });
- canvas.addEventListener('mouseup', function(evt) {
- if (!gamepad) {
- io.buttonUp(evt.which == 1 ? 0 : 1);
- }
- });
- });
-
- document.body.addEventListener('mousemove', _mousemove);
-
- document.querySelectorAll('input,textarea').forEach(function(input) {
- input.addEventListener('focus', function() {
- focused = true;
- });
- input.addEventListener('blur', function() {
- focused = false;
- });
- });
-
- keyboard.create('#keyboard');
-
- if (prefs.havePrefs()) {
- document.querySelectorAll('#options-modal input[type=checkbox]').forEach(function(el) {
- var val = prefs.readPref(el.id);
- if (val) {
- el.checked = JSON.parse(val);
- }
- el.addEventListener('change', function() {
- prefs.writePref(el.id, JSON.stringify(el.checked));
- });
- });
- document.querySelectorAll('#options-modal select').forEach(function(el) {
- var val = prefs.readPref(el.id);
- if (val) {
- el.value = val;
- }
- el.addEventListener('change', function() {
- prefs.writePref(el.id, el.value);
- });
- });
- }
-
- cpu.reset();
- setInterval(updateKHz, 1000);
- updateSound();
- updateScreen();
- updateCPU();
-
- if (window.localStorage !== undefined) {
- document.querySelectorAll('.disksave').forEach(function (el) { el.style.display = 'inline-block';});
- }
-
- var oldcat = '';
- var option;
- for (var idx = 0; idx < window.disk_index.length; idx++) {
- var file = window.disk_index[idx];
- var cat = file.category;
- var name = file.name, disk = file.disk;
- if (file.e) {
- continue;
- }
- if (cat != oldcat) {
- option = document.createElement('option');
- option.value = cat;
- option.innerText = cat;
- document.querySelector('#category_select').append(option);
-
- disk_categories[cat] = [];
- oldcat = cat;
- }
- disk_categories[cat].push(file);
- if (disk) {
- if (!disk_sets[name]) {
- disk_sets[name] = [];
- }
- disk_sets[name].push(file);
- }
- }
- option = document.createElement('option');
- option.innerText = 'Local Saves';
- document.querySelector('#category_select').append(option);
-
- updateLocalStorage();
- initGamepad();
-
- // Check for disks in hashtag
-
- var hash = gup('disk') || hup();
- if (hash) {
- processHash(hash);
- }
-
- if (navigator.standalone) {
- document.body.classList.add('standalone');
- }
-
- var reptKey = document.querySelector('.key-REPT');
- reptKey.addEventListener('click', function() {
- document.querySelector('#keyboard').style.display = 'none';
- document.querySelector('#textarea').style.display = 'block';
- document.querySelector('#textarea').focus();
- });
- document.querySelector('#text_input').addEventListener('keydown', function() {
- focused = document.querySelector('#buffering').checked;
- });
- document.querySelector('#text_input').addEventListener('keyup', function() {
- focused = document.querySelector('#buffering').checked;
- });
-
- run();
-});
+cpu.addPageHandler(lc);
diff --git a/js/main2e.js b/js/main2e.js
index dbd679a..465e3e7 100644
--- a/js/main2e.js
+++ b/js/main2e.js
@@ -1,22 +1,9 @@
-import MicroModal from 'micromodal';
-
-import Apple2IO from './apple2io';
-import ApplesoftDump from './applesoft/decompiler';
-import ApplesoftCompiler from './applesoft/compiler';
-import { HiresPage, LoresPage, VideoModes } from './canvas';
-import CPU6502 from './cpu6502';
-import MMU from './mmu';
import Prefs from './prefs';
-import { debug, gup, hup } from './util';
-import Audio from './ui/audio';
-import DriveLights from './ui/drive_lights';
-import { gamepad, configGamepad, initGamepad, processGamepad } from './ui/gamepad';
-import KeyBoard from './ui/keyboard';
+import { driveLights, initUI, updateUI } from './ui/apple2';
import Printer from './ui/printer';
-import Tape, { TAPE_TYPES } from './ui/tape';
-import DiskII, { DISK_TYPES } from './cards/disk2';
+import DiskII from './cards/disk2';
import Parallel from './cards/parallel';
import RAMFactor from './cards/ramfactor';
import Thunderclock from './cards/thunderclock';
@@ -28,966 +15,79 @@ import rmfont_charset from './roms/rmfont_char';
import Apple2eROM from './roms/apple2e';
import Apple2eEnhancedROM from './roms/apple2enh';
-import SYMBOLS from './symbols';
+import { Apple2 } from './apple2';
-var kHz = 1023;
-
-var focused = false;
-var startTime = Date.now();
-var lastCycles = 0;
-var renderedFrames = 0, lastFrames = 0;
-var paused = false;
-
-var hashtag;
-
-var DEBUG = true;
-var TRACE = false;
-var MAX_TRACE = 256;
-var trace = [];
-
-var disk_categories = {'Local Saves': []};
-var disk_sets = {};
-var disk_cur_name = [];
-var disk_cur_cat = [];
-
-var tape;
-var disk2;
-var driveLights;
-var _currentDrive = 1;
-
-export function setTrace(debug, trace) {
- DEBUG = debug;
- TRACE = trace;
-}
-
-export function getTrace() {
- return trace;
-}
-
-export function openLoad(drive, event)
-{
- _currentDrive = parseInt(drive, 10);
- if (event.metaKey) {
- openLoadHTTP(drive);
- } else {
- if (disk_cur_cat[drive]) {
- document.querySelector('#category_select').value = disk_cur_cat[drive];
- selectCategory();
- }
- MicroModal.show('load-modal');
- }
-}
-
-export function openSave(drive, event)
-{
- _currentDrive = parseInt(drive, 10);
-
- var mimetype = 'application/octet-stream';
- var data = disk2.getBinary(drive);
- var a = document.querySelector('#local_save_link');
-
- var blob = new Blob([data], { 'type': mimetype });
- a.href = window.URL.createObjectURL(blob);
- a.download = driveLights.label(drive) + '.dsk';
-
- if (event.metaKey) {
- dumpDisk(drive);
- } else {
- document.querySelector('#save_name').value = driveLights.label(drive);
- MicroModal.show('save-modal');
- }
-}
-
-export function handleDragOver(drive, event) {
- event.preventDefault();
- event.dataTransfer.dropEffect = 'copy';
-}
-
-export function handleDragEnd(drive, event) {
- var dt = event.dataTransfer;
- if (dt.items) {
- for (var i = 0; i < dt.items.length; i++) {
- dt.items.remove(i);
- }
- } else {
- event.dataTransfer.clearData();
- }
-}
-
-export function handleDrop(drive, event) {
- event.preventDefault();
- event.stopPropagation();
-
- if (drive < 1) {
- if (!disk2.getMetadata(1)) {
- drive = 1;
- } else if (!disk2.getMetadata(2)) {
- drive = 2;
- } else {
- drive = 1;
- }
- }
- var dt = event.dataTransfer;
- if (dt.files.length == 1) {
- doLoadLocal(drive, dt.files[0]);
- } else if (dt.files.length == 2) {
- doLoadLocal(1, dt.files[0]);
- doLoadLocal(2, dt.files[1]);
- } else {
- for (var idx = 0; idx < dt.items.length; idx++) {
- if (dt.items[idx].type === 'text/uri-list') {
- dt.items[idx].getAsString(function(url) {
- var parts = document.location.hash.split('|');
- parts[drive - 1] = url;
- document.location.hash = parts.join('|');
- });
- }
- }
- }
-}
-
-var loading = false;
-
-function loadAjax(drive, url) {
- loading = true;
- MicroModal.show('loading-modal');
-
- fetch(url).then(function(response) {
- return response.json();
- }).then(function(data) {
- if (data.type == 'binary') {
- loadBinary(drive, data);
- } else if (DISK_TYPES.indexOf(data.type) > -1) {
- loadDisk(drive, data);
- }
- initGamepad(data.gamepad);
- MicroModal.close('loading-modal');
- loading = false;
- }).catch(function(error) {
- window.alert(error || status);
- MicroModal.close('loading-modal');
- loading = false;
- });
-}
-
-export function doLoad() {
- MicroModal.close('load-modal');
- var urls = document.querySelector('#disk_select').value, url;
- if (urls && urls.length) {
- if (typeof(urls) == 'string') {
- url = urls;
- } else {
- url = urls[0];
- }
- }
-
- var files = document.querySelector('#local_file').files;
- if (files.length == 1) {
- doLoadLocal(_currentDrive, files[0]);
- } else if (url) {
- var filename;
- MicroModal.close('load-modal');
- if (url.substr(0,6) == 'local:') {
- filename = url.substr(6);
- if (filename == '__manage') {
- openManage();
- } else {
- loadLocalStorage(_currentDrive, filename);
- }
- } else {
- var r1 = /json\/disks\/(.*).json$/.exec(url);
- if (r1) {
- filename = r1[1];
- } else {
- filename = url;
- }
- var parts = document.location.hash.split('|');
- parts[_currentDrive - 1] = filename;
- document.location.hash = parts.join('|');
- }
- }
-}
-
-export function doSave() {
- var name = document.querySelector('#save_name').value;
- saveLocalStorage(_currentDrive, name);
- MicroModal.close('save-modal');
-}
-
-export function doDelete(name) {
- if (window.confirm('Delete ' + name + '?')) {
- deleteLocalStorage(name);
- }
-}
-
-function doLoadLocal(drive, file) {
- var parts = file.name.split('.');
- var ext = parts[parts.length - 1].toLowerCase();
- if (DISK_TYPES.indexOf(ext) > -1) {
- doLoadLocalDisk(drive, file);
- } else if (TAPE_TYPES.indexOf(ext) > -1) {
- tape.doLoadLocalTape(file);
- } else {
- window.alert('Unknown file type: ' + ext);
- }
-}
-
-function doLoadLocalDisk(drive, file) {
- var fileReader = new FileReader();
- fileReader.onload = function() {
- var parts = file.name.split('.');
- var ext = parts.pop().toLowerCase();
- var name = parts.join('.');
- if (disk2.setBinary(drive, name, ext, this.result)) {
- driveLights.label(drive, name);
- initGamepad();
- }
- };
- fileReader.readAsArrayBuffer(file);
-}
-
-function doLoadHTTP(drive, _url) {
- var url = _url || document.querySelector('#http_url').value;
- if (url) {
- var req = new XMLHttpRequest();
- req.open('GET', url, true);
- req.responseType = 'arraybuffer';
-
- req.onload = function() {
- if (req.status !== 200) {
- return window.alert('Unable to load "' + url + '"');
- }
- var urlParts = url.split('/');
- var file = urlParts.pop();
- var fileParts = file.split('.');
- var ext = fileParts.pop().toLowerCase();
- var name = decodeURIComponent(fileParts.join('.'));
- if (disk2.setBinary(drive, name, ext, req.response)) {
- driveLights.label(drive, name);
- if (!_url) {
- MicroModal.close('http-modal');
- }
- initGamepad();
- }
- };
- req.send(null);
- }
-}
-
-function openLoadHTTP(drive) {
- _currentDrive = parseInt(drive, 10);
- MicroModal.show('http-modal');
-}
-
-function openManage() {
- MicroModal.show('manage-modal');
-}
+export * from './ui/apple2';
var prefs = new Prefs();
var romVersion = prefs.readPref('computer_type2e');
var enhanced = false;
-var multiScreen = false;
var rom;
-var char_rom = apple2e_charset;
+var characterRom = apple2e_charset;
+
switch (romVersion) {
case 'apple2e':
rom = new Apple2eROM();
break;
case 'apple2rm':
rom = new Apple2eEnhancedROM();
- char_rom = rmfont_charset;
+ characterRom = rmfont_charset;
enhanced = true;
break;
default:
rom = new Apple2eEnhancedROM();
- char_rom =apple2enh_charset;
+ characterRom = apple2enh_charset;
enhanced = true;
}
-var runTimer = null;
-export var cpu = new CPU6502({'65C02': enhanced});
-
-var context1, context2, context3, context4;
+var options = {
+ screen: [],
+ multiScreen: false,
+ rom: rom,
+ characterRom: characterRom,
+ e: true,
+ enhanced: enhanced,
+ cards: [],
+ tick: updateUI
+};
var canvas1 = document.getElementById('screen');
var canvas2 = document.getElementById('screen2');
var canvas3 = document.getElementById('screen3');
var canvas4 = document.getElementById('screen4');
-context1 = canvas1.getContext('2d');
+options.screen[0] = canvas1.getContext('2d');
if (canvas4) {
- multiScreen = true;
- context2 = canvas2.getContext('2d');
- context3 = canvas3.getContext('2d');
- context4 = canvas4.getContext('2d');
+ options.multiScreen = true;
+ options.screen[1] = canvas2.getContext('2d');
+ options.screen[2] = canvas3.getContext('2d');
+ options.screen[3] = canvas4.getContext('2d');
} else if (canvas2) {
- multiScreen = true;
- context2 = context1;
- context3 = canvas2.getContext('2d');
- context4 = context3;
+ options.multiScreen = true;
+ options.screen[1] = options.screen[0];
+ options.screen[2] = canvas2.getContext('2d');
+ options.screen[3] = options.screen[2];
} else {
- context2 = context1;
- context3 = context1;
- context4 = context1;
+ options.screen[1] = options.screen[0];
+ options.screen[2] = options.screen[0];
+ options.screen[3] = options.screen[0];
}
-var gr = new LoresPage(1, char_rom, true, context1);
-var gr2 = new LoresPage(2, char_rom, true, context2);
-var hgr = new HiresPage(1, context3);
-var hgr2 = new HiresPage(2, context4);
+var apple2 = new Apple2(options);
+var io = apple2.getIO();
-var vm = new VideoModes(gr, hgr, gr2, hgr2, true);
-vm.enhanced(enhanced);
-vm.multiScreen(multiScreen);
-var dumper = new ApplesoftDump(cpu);
-var compiler = new ApplesoftCompiler(cpu);
-
-driveLights = new DriveLights();
-var io = new Apple2IO(cpu, vm);
-var keyboard = new KeyBoard(cpu, io, true);
-var audio = new Audio(io);
-tape = new Tape(io);
var printer = new Printer('#printer-modal .paper');
-var mmu = new MMU(cpu, vm, gr, gr2, hgr, hgr2, io, rom);
+var parallel = new Parallel(io, printer);
+var slinky = new RAMFactor(io, 1024 * 1024);
+var disk2 = new DiskII(io, driveLights);
+var clock = new Thunderclock(io);
-cpu.addPageHandler(mmu);
-
-var parallel = new Parallel(io, 1, printer);
-var slinky = new RAMFactor(io, 2, 1024 * 1024);
-disk2 = new DiskII(io, 6, driveLights);
-var clock = new Thunderclock(io, 7);
+initUI(apple2, disk2, enhanced);
io.setSlot(1, parallel);
io.setSlot(2, slinky);
+io.setSlot(4, clock);
io.setSlot(6, disk2);
-io.setSlot(7, clock);
-var showFPS = false;
-function updateKHz() {
- var now = Date.now();
- var ms = now - startTime;
- var cycles = cpu.cycles();
- var delta;
-
- if (showFPS) {
- delta = renderedFrames - lastFrames;
- var fps = parseInt(delta/(ms/1000), 10);
- document.querySelector('#khz').innerText = fps + 'fps';
- } else {
- delta = cycles - lastCycles;
- var khz = parseInt(delta/ms);
- document.querySelector('#khz').innerText = khz + 'KHz';
- }
-
- startTime = now;
- lastCycles = cycles;
- lastFrames = renderedFrames;
-}
-
-export function toggleShowFPS() {
- showFPS = !showFPS;
-}
-
-export function updateSound() {
- var on = document.querySelector('#enable_sound').checked;
- var label = document.querySelector('#toggle-sound i');
- audio.enable(on);
- if (on) {
- label.classList.remove('fa-volume-off');
- label.classList.add('fa-volume-up');
- } else {
- label.classList.remove('fa-volume-up');
- label.classList.add('fa-volume-off');
- }
-}
-
-function dumpDisk(drive) {
- var wind = window.open('', '_blank');
- wind.document.title = driveLights.label(drive);
- wind.document.write('');
- wind.document.write(disk2.getJSON(drive, true));
- wind.document.write('
');
- wind.document.close();
-}
-
-export function dumpProgram() {
- debug(dumper.toString());
-}
-
-export function compileProgram(program) {
- compiler.compile(program);
-}
-
-export function step()
-{
- if (runTimer) {
- clearInterval(runTimer);
- }
- runTimer = null;
-
- cpu.step(function() {
- debug(cpu.dumpRegisters());
- debug(cpu.dumpPC());
- });
-}
-
-var accelerated = false;
-
-export function updateCPU()
-{
- accelerated = document.querySelector('#accelerator_toggle').checked;
- kHz = accelerated ? 4092 : 1023;
- io.updateHz(kHz * 1000);
- if (runTimer) {
- run();
- }
-}
-
-var _requestAnimationFrame =
- window.requestAnimationFrame ||
- window.mozRequestAnimationFrame ||
- window.webkitRequestAnimationFrame ||
- window.msRequestAnimationFrame;
-
-function run(pc) {
- if (runTimer) {
- clearInterval(runTimer);
- }
-
- if (pc) {
- cpu.setPC(pc);
- }
-
- var ival = 30;
-
- var now, last = Date.now();
- var runFn = function() {
- now = Date.now();
-
- var step = (now - last) * kHz, stepMax = kHz * ival;
- last = now;
- if (step > stepMax) {
- step = stepMax;
- }
-
- if (document.location.hash != hashtag) {
- hashtag = document.location.hash;
- var hash = hup();
- if (hash) {
- processHash(hash);
- }
- }
- if (!loading) {
- mmu.resetVB();
- if (DEBUG) {
- cpu.stepCyclesDebug(TRACE ? 1 : step, function() {
- var line = cpu.dumpRegisters() + ' ' +
- cpu.dumpPC(undefined, SYMBOLS);
- if (TRACE) {
- debug(line);
- } else {
- trace.push(line);
- if (trace.length > MAX_TRACE) {
- trace.shift();
- }
- }
- });
- } else {
- cpu.stepCycles(step);
- }
- if (vm.blit()) {
- renderedFrames++;
- }
- io.tick();
- }
-
- processGamepad(io);
-
- if (!paused && _requestAnimationFrame) {
- _requestAnimationFrame(runFn);
- }
- };
- if (_requestAnimationFrame) {
- _requestAnimationFrame(runFn);
- } else {
- runTimer = setInterval(runFn, ival);
- }
-}
-
-function stop() {
- if (runTimer) {
- clearInterval(runTimer);
- }
- runTimer = null;
-}
-
-export function reset()
-{
- cpu.reset();
-}
-
-var state = null;
-
-function storeStateLocal() {
- window.localStorage['apple2.state'] = JSON.stringify(state);
-}
-
-function restoreStateLocal() {
- var data = window.localStorage['apple2.state'];
- if (data) {
- state = JSON.parse(data);
- }
-}
-
-function saveState() {
- if (state && !window.confirm('Overwrite Saved State?')) {
- return;
- }
-
- state = {
- cpu: cpu.getState(),
- io: io.getState(),
- mmu: mmu.getState(),
- vm: vm.getState(),
- disk2: disk2.getState(),
- driveLights: driveLights.getState()
- };
- if (slinky) {
- state.slinky = slinky.getState();
- }
-
- if (window.localStorage) {
- storeStateLocal();
- }
-}
-
-function restoreState() {
- if (window.localStorage) {
- restoreStateLocal();
- }
- if (!state) {
- return;
- }
- cpu.setState(state.cpu);
- io.setState(state.io);
- mmu.setState(state.mmu);
- vm.setState(state.vm);
- disk2.setState(state.disk2);
- driveLights.setState(state.driveLights);
- if (slinky && state.slinky) {
- slinky.setState(state.slinky);
- }
-}
-
-function loadBinary(bin) {
- stop();
- for (var idx = 0; idx < bin.length; idx++) {
- var pos = bin.start + idx;
- cpu.write(pos >> 8, pos & 0xff, bin.data[idx]);
- }
- run(bin.start);
-}
-
-export function selectCategory() {
- document.querySelector('#disk_select').innerHTML = '';
- var cat = disk_categories[document.querySelector('#category_select').value];
- if (cat) {
- for (var idx = 0; idx < cat.length; idx++) {
- var file = cat[idx], name = file.name;
- if (file.disk) {
- name += ' - ' + file.disk;
- }
- var option = document.createElement('option');
- option.value = file.filename;
- option.innerText = name;
- document.querySelector('#disk_select').append(option);
- if (disk_cur_name[_currentDrive] == name) {
- option.selected = true;
- }
- }
- }
-}
-
-export function selectDisk() {
- document.querySelector('#local_file').value = '';
-}
-
-export function clickDisk() {
- doLoad();
-}
-
-function loadDisk(drive, disk) {
- var name = disk.name;
- var category = disk.category;
-
- if (disk.disk) {
- name += ' - ' + disk.disk;
- }
-
- disk_cur_cat[drive] = category;
- disk_cur_name[drive] = name;
-
- driveLights.label(drive, name);
- disk2.setDisk(drive, disk);
- initGamepad(disk.gamepad);
-}
-
-/*
- * LocalStorage Disk Storage
- */
-
-function updateLocalStorage() {
- var diskIndex = JSON.parse(window.localStorage.diskIndex || '{}');
- var names = [], name, cat;
-
- for (name in diskIndex) {
- if (diskIndex.hasOwnProperty(name)) {
- names.push(name);
- }
- }
-
- cat = disk_categories['Local Saves'] = [];
- document.querySelector('#manage-modal-content').innerHTML = '';
-
- names.forEach(function(name) {
- cat.push({
- 'category': 'Local Saves',
- 'name': name,
- 'filename': 'local:' + name
- });
- document.querySelector('#manage-modal-content').innerHTML =
- '' +
- name +
- ' Delete
';
- });
- cat.push({
- 'category': 'Local Saves',
- 'name': 'Manage Saves...',
- 'filename': 'local:__manage'
- });
-}
-
-function saveLocalStorage(drive, name) {
- var diskIndex = JSON.parse(window.localStorage.diskIndex || '{}');
-
- var json = disk2.getJSON(drive);
- diskIndex[name] = json;
-
- window.localStorage.diskIndex = JSON.stringify(diskIndex);
-
- window.alert('Saved');
-
- driveLights.label(drive, name);
- driveLights.dirty(drive, false);
- updateLocalStorage();
-}
-
-function deleteLocalStorage(name) {
- var diskIndex = JSON.parse(window.localStorage.diskIndex || '{}');
- if (diskIndex[name]) {
- delete diskIndex[name];
- window.alert('Deleted');
- }
- window.localStorage.diskIndex = JSON.stringify(diskIndex);
- updateLocalStorage();
-}
-
-function loadLocalStorage(drive, name) {
- var diskIndex = JSON.parse(window.localStorage.diskIndex || '{}');
- if (diskIndex[name]) {
- disk2.setJSON(drive, diskIndex[name]);
- driveLights.label(drive, name);
- driveLights.dirty(drive, false);
- }
-}
-
-function processHash(hash) {
- var files = hash.split('|');
- for (var idx = 0; idx < files.length; idx++) {
- var file = files[idx];
- if (file.indexOf('://') > 0) {
- var parts = file.split('.');
- var ext = parts[parts.length - 1].toLowerCase();
- if (ext == 'json') {
- loadAjax(idx + 1, file);
- } else {
- doLoadHTTP(idx + 1, file);
- }
- } else {
- loadAjax(idx + 1, 'json/disks/' + file + '.json');
- }
- }
-}
-
-/*
- * Keyboard/Gamepad routines
- */
-
-function _keydown(evt) {
- if (!focused && (!evt.metaKey || evt.ctrlKey)) {
- evt.preventDefault();
-
- var key = keyboard.mapKeyEvent(evt);
- if (key != 0xff) {
- io.keyDown(key);
- }
- }
- if (evt.keyCode === 112) { // F1 - Reset
- cpu.reset();
- evt.preventDefault(); // prevent launching help
- } else if (evt.keyCode === 113) { // F2 - Full Screen
- var elem = document.getElementById('screen');
- if (evt.shiftKey) { // Full window, but not full screen
- document.querySelector('#display').classList.toggle('zoomwindow');
- document.querySelector('#display > div').classList.toggle('overscan');
- document.querySelector('#display > div').classList.toggle('flexbox-centering');
- document.querySelector('#screen').classList.toggle('maxhw');
- document.querySelector('#header').classList.toggle('hidden');
- document.querySelectorAll('.inset').forEach(function(el) { el.classList.toggle('hidden'); });
- document.querySelector('#reset').classList.toggle('hidden');
- } else if (document.webkitCancelFullScreen) {
- if (document.webkitIsFullScreen) {
- document.webkitCancelFullScreen();
- } else {
- if (Element.ALLOW_KEYBOARD_INPUT) {
- elem.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
- } else {
- elem.webkitRequestFullScreen();
- }
- }
- } else if (document.mozCancelFullScreen) {
- if (document.mozIsFullScreen) {
- document.mozCancelFullScreen();
- } else {
- elem.mozRequestFullScreen();
- }
- }
- } else if (evt.keyCode === 114) { // F3
- io.keyDown(0x1b);
- } else if (evt.keyCode === 117) { // F6 Quick Save
- saveState();
- } else if (evt.keyCode === 120) { // F9 Quick Restore
- restoreState();
- } else if (evt.keyCode == 16) { // Shift
- keyboard.shiftKey(true);
- } else if (evt.keyCode == 17) { // Control
- keyboard.controlKey(true);
- } else if (evt.keyCode == 91 || evt.keyCode == 93) { // Command
- keyboard.commandKey(true);
- } else if (evt.keyCode == 18) { // Alt
- if (evt.location == 1) {
- keyboard.commandKey(true);
- } else {
- keyboard.optionKey(true);
- }
- }
-}
-
-function _keyup(evt) {
- if (!focused)
- io.keyUp();
-
- if (evt.keyCode == 16) { // Shift
- keyboard.shiftKey(false);
- } else if (evt.keyCode == 17) { // Control
- keyboard.controlKey(false);
- } else if (evt.keyCode == 91 || evt.keyCode == 93) { // Command
- keyboard.commandKey(false);
- } else if (evt.keyCode == 18) { // Alt
- if (evt.location == 1) {
- keyboard.commandKey(false);
- } else {
- keyboard.optionKey(false);
- }
- }
-}
-
-export function updateScreen() {
- var green = document.querySelector('#green_screen').checked;
- var scanlines = document.querySelector('#show_scanlines').checked;
-
- vm.green(green);
- vm.scanlines(scanlines);
-}
-
-var disableMouseJoystick = false;
-var flipX = false;
-var flipY = false;
-var swapXY = false;
-
-export function updateJoystick() {
- disableMouseJoystick = document.querySelector('#disable_mouse').checked;
- flipX = document.querySelector('#flip_x').checked;
- flipY = document.querySelector('#flip_y').checked;
- swapXY = document.querySelector('#swap_x_y').checked;
- configGamepad(flipX, flipY);
-
- if (disableMouseJoystick) {
- io.paddle(0, 0.5);
- io.paddle(1, 0.5);
- return;
- }
-}
-
-function _mousemove(evt) {
- if (gamepad || disableMouseJoystick) {
- return;
- }
-
- var s = document.querySelector('#screen');
- var offset = s.getBoundingClientRect();
- var x = (evt.pageX - offset.left) / s.clientWidth,
- y = (evt.pageY - offset.top) / s.clientHeight,
- z = x;
-
- if (swapXY) {
- x = y;
- y = z;
- }
-
- io.paddle(0, flipX ? 1 - x : x);
- io.paddle(1, flipY ? 1 - y : y);
-}
-
-export function pauseRun() {
- var label = document.querySelector('#pause-run i');
- if (paused) {
- run();
- label.classList.remove('fa-play');
- label.classList.add('fa-pause');
- } else {
- stop();
- label.classList.remove('fa-pause');
- label.classList.add('fa-play');
- }
- paused = !paused;
-}
-
-export function toggleSound() {
- var enableSound = document.querySelector('#enable_sound');
- enableSound.checked = !enableSound.checked;
- updateSound();
-}
-
-export function openOptions() {
- MicroModal.show('options-modal');
-}
-
-export function openPrinterModal() {
- MicroModal.show('printer-modal');
-}
-
-MicroModal.init();
-
-document.addEventListener('DOMContentLoaded', function() {
- hashtag = document.location.hash;
-
- /*
- * Input Handling
- */
-
- window.addEventListener('keydown', _keydown);
- window.addEventListener('keyup', _keyup);
- window.addEventListener('mousedown', function() { audio.autoStart(); });
-
- document.querySelectorAll('canvas').forEach(function(canvas) {
- canvas.addEventListener('mousedown', function(evt) {
- if (!gamepad) {
- io.buttonDown(evt.which == 1 ? 0 : 1);
- }
- evt.preventDefault();
- });
- canvas.addEventListener('mouseup', function(evt) {
- if (!gamepad) {
- io.buttonUp(evt.which == 1 ? 0 : 1);
- }
- });
- });
-
- document.body.addEventListener('mousemove', _mousemove);
-
- document.querySelectorAll('input,textarea').forEach(function(input) {
- input.addEventListener('input', function() { focused = true; });
- input.addEventListener('blur', function() { focused = false; });
- });
-
- keyboard.create('#keyboard');
-
- if (prefs.havePrefs()) {
- document.querySelectorAll('#options-modal input[type=checkbox]').forEach(function(el) {
- var val = prefs.readPref(el.id);
- if (val) {
- el.checked = JSON.parse(val);
- }
- el.addEventListener('change', function() {
- prefs.writePref(el.id, JSON.stringify(el.checked));
- });
- });
- document.querySelectorAll('#options-modal select').forEach(function(el) {
- var val = prefs.readPref(el.id);
- if (val) {
- el.value = val;
- }
- el.addEventListener('change', function() {
- prefs.writePref(el.id, el.value);
- });
- });
- }
-
- cpu.reset();
- setInterval(updateKHz, 1000);
- updateSound();
- updateScreen();
- updateCPU();
-
- if (window.localStorage !== undefined) {
- document.querySelectorAll('.disksave').forEach(function (el) { el.style.display = 'inline-block';});
- }
-
- var oldcat = '';
- var option;
- for (var idx = 0; idx < window.disk_index.length; idx++) {
- var file = window.disk_index[idx];
- var cat = file.category;
- var name = file.name, disk = file.disk;
- if (cat != oldcat) {
- option = document.createElement('option');
- option.value = cat;
- option.innerText = cat;
- document.querySelector('#category_select').append(option);
-
- disk_categories[cat] = [];
- oldcat = cat;
- }
- disk_categories[cat].push(file);
- if (disk) {
- if (!disk_sets[name]) {
- disk_sets[name] = [];
- }
- disk_sets[name].push(file);
- }
- }
- option = document.createElement('option');
- option.innerText = 'Local Saves';
- document.querySelector('#category_select').append(option);
-
- updateLocalStorage();
- initGamepad();
-
- // Check for disks in hashtag
-
- var hash = gup('disk') || hup();
- if (hash) {
- processHash(hash);
- }
-
- if (navigator.standalone) {
- document.body.classList.add('standalone');
- }
-
- run();
-});
diff --git a/js/ui/.gitignore b/js/ui/.gitignore
deleted file mode 100644
index b27bc7c..0000000
--- a/js/ui/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-ui-all*.js
diff --git a/js/ui/apple2.js b/js/ui/apple2.js
new file mode 100644
index 0000000..5dfec69
--- /dev/null
+++ b/js/ui/apple2.js
@@ -0,0 +1,763 @@
+import MicroModal from 'micromodal';
+
+import Audio from './audio';
+import DriveLights from './drive_lights';
+import { DISK_TYPES } from '../cards/disk2';
+import { gamepad, configGamepad, initGamepad } from './gamepad';
+import KeyBoard from './keyboard';
+import Tape, { TAPE_TYPES } from './tape';
+
+import ApplesoftDump from '../applesoft/decompiler';
+import ApplesoftCompiler from '../applesoft/compiler';
+
+import { debug, gup, hup } from '../util';
+import Prefs from '../prefs';
+
+var kHz = 1023;
+
+var focused = false;
+var startTime = Date.now();
+var lastCycles = 0;
+var lastFrames = 0;
+
+var hashtag;
+
+var disk_categories = {'Local Saves': []};
+var disk_sets = {};
+var disk_cur_name = [];
+var disk_cur_cat = [];
+
+var _apple2;
+var cpu;
+var stats;
+var vm;
+var tape;
+var _disk2;
+var audio;
+var keyboard;
+var io;
+var _currentDrive = 1;
+
+export const driveLights = new DriveLights();
+
+export function dumpAppleSoftProgram() {
+ var dumper = new ApplesoftDump(cpu);
+ debug(dumper.toString());
+}
+
+export function compileAppleSoftProgram(program) {
+ var compiler = new ApplesoftCompiler(cpu);
+ compiler.compile(program);
+}
+
+export function openLoad(drive, event) {
+ _currentDrive = parseInt(drive, 10);
+ if (event.metaKey) {
+ openLoadHTTP(drive);
+ } else {
+ if (disk_cur_cat[drive]) {
+ document.querySelector('#category_select').value = disk_cur_cat[drive];
+ selectCategory();
+ }
+ MicroModal.show('load-modal');
+ }
+}
+
+export function openSave(drive, event) {
+ _currentDrive = parseInt(drive, 10);
+
+ var mimeType = 'application/octet-stream';
+ var data = _disk2.getBinary(drive);
+ var a = document.querySelector('#local_save_link');
+
+ var blob = new Blob([data], { 'type': mimeType });
+ a.href = window.URL.createObjectURL(blob);
+ a.download = driveLights.label(drive) + '.dsk';
+
+ if (event.metaKey) {
+ dumpDisk(drive);
+ } else {
+ document.querySelector('#save_name').value = driveLights.label(drive);
+ MicroModal.show('save-modal');
+ }
+}
+
+export function handleDragOver(drive, event) {
+ event.preventDefault();
+ event.dataTransfer.dropEffect = 'copy';
+}
+
+export function handleDragEnd(drive, event) {
+ var dt = event.dataTransfer;
+ if (dt.items) {
+ for (var i = 0; i < dt.items.length; i++) {
+ dt.items.remove(i);
+ }
+ } else {
+ event.dataTransfer.clearData();
+ }
+}
+
+export function handleDrop(drive, event) {
+ event.preventDefault();
+ event.stopPropagation();
+
+ if (drive < 1) {
+ if (!_disk2.getMetadata(1)) {
+ drive = 1;
+ } else if (!_disk2.getMetadata(2)) {
+ drive = 2;
+ } else {
+ drive = 1;
+ }
+ }
+ var dt = event.dataTransfer;
+ if (dt.files.length == 1) {
+
+ doLoadLocal(drive, dt.files[0]);
+ } else if (dt.files.length == 2) {
+ doLoadLocal(1, dt.files[0]);
+ doLoadLocal(2, dt.files[1]);
+ } else {
+ for (var idx = 0; idx < dt.items.length; idx++) {
+ if (dt.items[idx].type === 'text/uri-list') {
+ dt.items[idx].getAsString(function(url) {
+ var parts = document.location.hash.split('|');
+ parts[drive - 1] = url;
+ document.location.hash = parts.join('|');
+ });
+ }
+ }
+ }
+}
+
+export function loadAjax(drive, url) {
+ MicroModal.show('loading-modal');
+
+ fetch(url).then(function(response) {
+ if (response.ok) {
+ return response.json();
+ } else {
+ throw new Error('Error loading: ' + response.statusText);
+ }
+ }).then(function(data) {
+ if (data.type == 'binary') {
+ loadBinary(drive, data);
+ } else if (DISK_TYPES.indexOf(data.type) > -1) {
+ loadDisk(drive, data);
+ }
+ initGamepad(data.gamepad);
+ MicroModal.close('loading-modal');
+ }).catch(function(error) {
+ window.alert(error.message);
+ MicroModal.close('loading-modal');
+ });
+}
+
+export function doLoad() {
+ MicroModal.close('load-modal');
+ var urls = document.querySelector('#disk_select').value, url;
+ if (urls && urls.length) {
+ if (typeof(urls) == 'string') {
+ url = urls;
+ } else {
+ url = urls[0];
+ }
+ }
+
+ var files = document.querySelector('#local_file').files;
+ if (files.length == 1) {
+ doLoadLocal(_currentDrive, files[0]);
+ } else if (url) {
+ var filename;
+ MicroModal.close('load-modal');
+ if (url.substr(0,6) == 'local:') {
+ filename = url.substr(6);
+ if (filename == '__manage') {
+ openManage();
+ } else {
+ loadLocalStorage(_currentDrive, filename);
+ }
+ } else {
+ var r1 = /json\/disks\/(.*).json$/.exec(url);
+ if (r1) {
+ filename = r1[1];
+ } else {
+ filename = url;
+ }
+ var parts = document.location.hash.split('|');
+ parts[_currentDrive - 1] = filename;
+ document.location.hash = parts.join('|');
+ }
+ }
+}
+
+export function doSave() {
+ var name = document.querySelector('#save_name').value;
+ saveLocalStorage(_currentDrive, name);
+ MicroModal.close('save-modal');
+}
+
+export function doDelete(name) {
+ if (window.confirm('Delete ' + name + '?')) {
+ deleteLocalStorage(name);
+ }
+}
+
+function doLoadLocal(drive, file) {
+ var parts = file.name.split('.');
+ var ext = parts[parts.length - 1].toLowerCase();
+ if (DISK_TYPES.indexOf(ext) > -1) {
+ doLoadLocalDisk(drive, file);
+ } else if (TAPE_TYPES.indexOf(ext) > -1) {
+ tape.doLoadLocalTape(file);
+ } else {
+ window.alert('Unknown file type: ' + ext);
+ }
+}
+
+function doLoadLocalDisk(drive, file) {
+ MicroModal.show('loading-modal');
+ var fileReader = new FileReader();
+ fileReader.onload = function() {
+ var parts = file.name.split('.');
+ var ext = parts.pop().toLowerCase();
+ var name = parts.join('.');
+ if (_disk2.setBinary(drive, name, ext, this.result)) {
+ driveLights.label(drive, name);
+ MicroModal.close('loading-modal');
+ initGamepad();
+ }
+ };
+ fileReader.readAsArrayBuffer(file);
+}
+
+export function doLoadHTTP(drive, _url) {
+ var url = _url || document.querySelector('#http_url').value;
+ if (url) {
+ fetch(url).then(function(response) {
+ if (response.ok) {
+ return response.arrayBuffer();
+ } else {
+ throw new Error('Error loading: ' + response.statusText);
+ }
+ }).then(function(data) {
+ var urlParts = url.split('/');
+ var file = urlParts.pop();
+ var fileParts = file.split('.');
+ var ext = fileParts.pop().toLowerCase();
+ var name = decodeURIComponent(fileParts.join('.'));
+ if (_disk2.setBinary(drive, name, ext, data)) {
+ driveLights.label(drive, name);
+ initGamepad();
+ }
+ if (!_url) { MicroModal.close('http-modal'); }
+ }).catch(function(error) {
+ window.alert(error.message);
+ if (!_url) { MicroModal.close('http-modal'); }
+ });
+ }
+}
+
+function openLoadHTTP(drive) {
+ _currentDrive = parseInt(drive, 10);
+ MicroModal.show('http-modal');
+}
+
+function openManage() {
+ MicroModal.show('manage-modal');
+}
+
+var prefs = new Prefs();
+var showFPS = false;
+
+export function updateKHz() {
+ var now = Date.now();
+ var ms = now - startTime;
+ var cycles = cpu.cycles();
+ var delta;
+
+ if (showFPS) {
+ delta = stats.renderedFrames - lastFrames;
+ var fps = parseInt(delta/(ms/1000), 10);
+ document.querySelector('#khz').innerText = fps + 'fps';
+ } else {
+ delta = cycles - lastCycles;
+ var khz = parseInt(delta/ms);
+ document.querySelector('#khz').innerText = khz + 'KHz';
+ }
+
+ startTime = now;
+ lastCycles = cycles;
+ lastFrames = stats.renderedFrames;
+}
+
+export function toggleShowFPS() {
+ showFPS = !showFPS;
+}
+
+export function updateSound() {
+ var on = document.querySelector('#enable_sound').checked;
+ var label = document.querySelector('#toggle-sound i');
+ audio.enable(on);
+ if (on) {
+ label.classList.remove('fa-volume-off');
+ label.classList.add('fa-volume-up');
+ } else {
+ label.classList.remove('fa-volume-up');
+ label.classList.add('fa-volume-off');
+ }
+}
+
+function dumpDisk(drive) {
+ var wind = window.open('', '_blank');
+ wind.document.title = driveLights.label(drive);
+ wind.document.write('');
+ wind.document.write(_disk2.getJSON(drive, true));
+ wind.document.write('
');
+ wind.document.close();
+}
+
+export function reset() {
+ _apple2.reset();
+}
+
+function loadBinary(bin) {
+ for (var idx = 0; idx < bin.length; idx++) {
+ var pos = bin.start + idx;
+ cpu.write(pos >> 8, pos & 0xff, bin.data[idx]);
+ }
+ cpu.reset();
+ cpu.setPC(bin.start);
+}
+
+export function selectCategory() {
+ document.querySelector('#disk_select').innerHTML = '';
+ var cat = disk_categories[document.querySelector('#category_select').value];
+ if (cat) {
+ for (var idx = 0; idx < cat.length; idx++) {
+ var file = cat[idx], name = file.name;
+ if (file.disk) {
+ name += ' - ' + file.disk;
+ }
+ var option = document.createElement('option');
+ option.value = file.filename;
+ option.innerText = name;
+ document.querySelector('#disk_select').append(option);
+ if (disk_cur_name[_currentDrive] == name) {
+ option.selected = true;
+ }
+ }
+ }
+}
+
+export function selectDisk() {
+ document.querySelector('#local_file').value = '';
+}
+
+export function clickDisk() {
+ doLoad();
+}
+
+function loadDisk(drive, disk) {
+ var name = disk.name;
+ var category = disk.category;
+
+ if (disk.disk) {
+ name += ' - ' + disk.disk;
+ }
+
+ disk_cur_cat[drive] = category;
+ disk_cur_name[drive] = name;
+
+ driveLights.label(drive, name);
+ _disk2.setDisk(drive, disk);
+ initGamepad(disk.gamepad);
+}
+
+/*
+ * LocalStorage Disk Storage
+ */
+
+function updateLocalStorage() {
+ var diskIndex = JSON.parse(window.localStorage.diskIndex || '{}');
+ var names = [], name, cat;
+
+ for (name in diskIndex) {
+ if (diskIndex.hasOwnProperty(name)) {
+ names.push(name);
+ }
+ }
+
+ cat = disk_categories['Local Saves'] = [];
+ document.querySelector('#manage-modal-content').innerHTML = '';
+
+ names.forEach(function(name) {
+ cat.push({
+ 'category': 'Local Saves',
+ 'name': name,
+ 'filename': 'local:' + name
+ });
+ document.querySelector('#manage-modal-content').innerHTML =
+ '' +
+ name +
+ ' Delete
';
+ });
+ cat.push({
+ 'category': 'Local Saves',
+ 'name': 'Manage Saves...',
+ 'filename': 'local:__manage'
+ });
+}
+
+function saveLocalStorage(drive, name) {
+ var diskIndex = JSON.parse(window.localStorage.diskIndex || '{}');
+
+ var json = _disk2.getJSON(drive);
+ diskIndex[name] = json;
+
+ window.localStorage.diskIndex = JSON.stringify(diskIndex);
+
+ window.alert('Saved');
+
+ driveLights.label(drive, name);
+ driveLights.dirty(drive, false);
+ updateLocalStorage();
+}
+
+function deleteLocalStorage(name) {
+ var diskIndex = JSON.parse(window.localStorage.diskIndex || '{}');
+ if (diskIndex[name]) {
+ delete diskIndex[name];
+ window.alert('Deleted');
+ }
+ window.localStorage.diskIndex = JSON.stringify(diskIndex);
+ updateLocalStorage();
+}
+
+function loadLocalStorage(drive, name) {
+ var diskIndex = JSON.parse(window.localStorage.diskIndex || '{}');
+ if (diskIndex[name]) {
+ _disk2.setJSON(drive, diskIndex[name]);
+ driveLights.label(drive, name);
+ driveLights.dirty(drive, false);
+ }
+}
+
+if (window.localStorage !== undefined) {
+ document.querySelectorAll('.disksave').forEach(function (el) { el.style.display = 'inline-block';});
+}
+
+var oldcat = '';
+var option;
+for (var idx = 0; idx < window.disk_index.length; idx++) {
+ var file = window.disk_index[idx];
+ var cat = file.category;
+ var name = file.name, disk = file.disk;
+ if (file.e) {
+ continue;
+ }
+ if (cat != oldcat) {
+ option = document.createElement('option');
+ option.value = cat;
+ option.innerText = cat;
+ document.querySelector('#category_select').append(option);
+
+ disk_categories[cat] = [];
+ oldcat = cat;
+ }
+ disk_categories[cat].push(file);
+ if (disk) {
+ if (!disk_sets[name]) {
+ disk_sets[name] = [];
+ }
+ disk_sets[name].push(file);
+ }
+}
+option = document.createElement('option');
+option.innerText = 'Local Saves';
+document.querySelector('#category_select').append(option);
+
+updateLocalStorage();
+
+function processHash(hash) {
+ var files = hash.split('|');
+ for (var idx = 0; idx < files.length; idx++) {
+ var file = files[idx];
+ if (file.indexOf('://') > 0) {
+ var parts = file.split('.');
+ var ext = parts[parts.length - 1].toLowerCase();
+ if (ext == 'json') {
+ loadAjax(idx + 1, file);
+ } else {
+ doLoadHTTP(idx + 1, file);
+ }
+ } else {
+ loadAjax(idx + 1, 'json/disks/' + file + '.json');
+ }
+ }
+}
+
+/*
+ * Keyboard/Gamepad routines
+ */
+
+function _keydown(evt) {
+ if (!focused && (!evt.metaKey || evt.ctrlKey)) {
+ evt.preventDefault();
+
+ var key = keyboard.mapKeyEvent(evt);
+ if (key != 0xff) {
+ io.keyDown(key);
+ }
+ }
+ if (evt.keyCode === 112) { // F1 - Reset
+ cpu.reset();
+ evt.preventDefault(); // prevent launching help
+ } else if (evt.keyCode === 113) { // F2 - Full Screen
+ var elem = document.getElementById('screen');
+ if (evt.shiftKey) { // Full window, but not full screen
+ document.querySelector('#display').classList.toggle('zoomwindow');
+ document.querySelector('#display > div').classList.toggle('overscan');
+ document.querySelector('#display > div').classList.toggle('flexbox-centering');
+ document.querySelector('#screen').classList.toggle('maxhw');
+ document.querySelector('#header').classList.toggle('hidden');
+ document.querySelectorAll('.inset').forEach(function(el) { el.classList.toggle('hidden'); });
+ document.querySelector('#reset').classList.toggle('hidden');
+ } else if (document.webkitCancelFullScreen) {
+ if (document.webkitIsFullScreen) {
+ document.webkitCancelFullScreen();
+ } else {
+ if (Element.ALLOW_KEYBOARD_INPUT) {
+ elem.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
+ } else {
+ elem.webkitRequestFullScreen();
+ }
+ }
+ } else if (document.mozCancelFullScreen) {
+ if (document.mozIsFullScreen) {
+ document.mozCancelFullScreen();
+ } else {
+ elem.mozRequestFullScreen();
+ }
+ }
+ } else if (evt.keyCode === 114) { // F3
+ io.keyDown(0x1b);
+ } else if (evt.keyCode === 117) { // F6 Quick Save
+ _apple2.saveState();
+ } else if (evt.keyCode === 120) { // F9 Quick Restore
+ _apple2.restoreState();
+ } else if (evt.keyCode == 16) { // Shift
+ keyboard.shiftKey(true);
+ } else if (evt.keyCode == 17) { // Control
+ keyboard.controlKey(true);
+ } else if (evt.keyCode == 91 || evt.keyCode == 93) { // Command
+ keyboard.commandKey(true);
+ } else if (evt.keyCode == 18) { // Alt
+ if (evt.location == 1) {
+ keyboard.commandKey(true);
+ } else {
+ keyboard.optionKey(true);
+ }
+ }
+}
+
+function _keyup(evt) {
+ if (!focused)
+ io.keyUp();
+
+ if (evt.keyCode == 16) { // Shift
+ keyboard.shiftKey(false);
+ } else if (evt.keyCode == 17) { // Control
+ keyboard.controlKey(false);
+ } else if (evt.keyCode == 91 || evt.keyCode == 93) { // Command
+ keyboard.commandKey(false);
+ } else if (evt.keyCode == 18) { // Alt
+ if (evt.location == 1) {
+ keyboard.commandKey(false);
+ } else {
+ keyboard.optionKey(false);
+ }
+ }
+}
+
+export function updateScreen() {
+ var green = document.querySelector('#green_screen').checked;
+ var scanlines = document.querySelector('#show_scanlines').checked;
+
+ vm.green(green);
+ vm.scanlines(scanlines);
+}
+
+export function updateCPU() {
+ var accelerated = document.querySelector('#accelerator_toggle').checked;
+ kHz = accelerated ? 4092 : 1023;
+ io.updateHz(kHz * 1000);
+}
+
+export function updateUI() {
+ if (document.location.hash != hashtag) {
+ hashtag = document.location.hash;
+ var hash = hup();
+ if (hash) {
+ processHash(hash);
+ }
+ }
+}
+
+var disableMouseJoystick = false;
+var flipX = false;
+var flipY = false;
+var swapXY = false;
+
+export function updateJoystick() {
+ disableMouseJoystick = document.querySelector('#disable_mouse').checked;
+ flipX = document.querySelector('#flip_x').checked;
+ flipY = document.querySelector('#flip_y').checked;
+ swapXY = document.querySelector('#swap_x_y').checked;
+ configGamepad(flipX, flipY);
+
+ if (disableMouseJoystick) {
+ io.paddle(0, 0.5);
+ io.paddle(1, 0.5);
+ return;
+ }
+}
+
+function _mousemove(evt) {
+ if (gamepad || disableMouseJoystick) {
+ return;
+ }
+
+ var s = document.querySelector('#screen');
+ var offset = s.getBoundingClientRect();
+ var x = (evt.pageX - offset.left) / s.clientWidth,
+ y = (evt.pageY - offset.top) / s.clientHeight,
+ z = x;
+
+ if (swapXY) {
+ x = y;
+ y = z;
+ }
+
+ io.paddle(0, flipX ? 1 - x : x);
+ io.paddle(1, flipY ? 1 - y : y);
+}
+
+var paused = false;
+
+export function pauseRun() {
+ var label = document.querySelector('#pause-run i');
+ if (paused) {
+ cpu.run();
+ label.classList.remove('fa-play');
+ label.classList.add('fa-pause');
+ } else {
+ cpu.stop();
+ label.classList.remove('fa-pause');
+ label.classList.add('fa-play');
+ }
+ paused = !paused;
+}
+
+export function toggleSound() {
+ var enableSound = document.querySelector('#enable_sound');
+ enableSound.checked = !enableSound.checked;
+ updateSound();
+}
+
+export function openOptions() {
+ MicroModal.show('options-modal');
+}
+
+export function openPrinterModal() {
+ MicroModal.show('printer-modal');
+}
+
+export function initUI(apple2, disk2, e) {
+ _apple2 = apple2;
+ cpu = _apple2.getCPU();
+ io = _apple2.getIO();
+ stats = apple2.getStats();
+ vm = apple2.getVideoModes();
+ tape = new Tape(io);
+ _disk2 = disk2;
+
+ keyboard = new KeyBoard(cpu, io, e);
+ keyboard.create('#keyboard');
+ audio = new Audio(io);
+
+ MicroModal.init();
+
+ /*
+ * Input Handling
+ */
+
+ window.addEventListener('keydown', _keydown);
+ window.addEventListener('keyup', _keyup);
+ window.addEventListener('mousedown', function() { audio.autoStart(); });
+
+ document.querySelectorAll('canvas').forEach(function(canvas) {
+ canvas.addEventListener('mousedown', function(evt) {
+ if (!gamepad) {
+ io.buttonDown(evt.which == 1 ? 0 : 1);
+ }
+ evt.preventDefault();
+ });
+ canvas.addEventListener('mouseup', function(evt) {
+ if (!gamepad) {
+ io.buttonUp(evt.which == 1 ? 0 : 1);
+ }
+ });
+ });
+
+ document.body.addEventListener('mousemove', _mousemove);
+
+ document.querySelectorAll('input,textarea').forEach(function(input) {
+ input.addEventListener('input', function() { focused = true; });
+ input.addEventListener('blur', function() { focused = false; });
+ });
+
+ if (prefs.havePrefs()) {
+ document.querySelectorAll('#options-modal input[type=checkbox]').forEach(function(el) {
+ var val = prefs.readPref(el.id);
+ if (val) {
+ el.checked = JSON.parse(val);
+ }
+ el.addEventListener('change', function() {
+ prefs.writePref(el.id, JSON.stringify(el.checked));
+ });
+ });
+ document.querySelectorAll('#options-modal select').forEach(function(el) {
+ var val = prefs.readPref(el.id);
+ if (val) {
+ el.value = val;
+ }
+ el.addEventListener('change', function() {
+ prefs.writePref(el.id, el.value);
+ });
+ });
+ }
+
+ cpu.reset();
+ setInterval(updateKHz, 1000);
+ updateSound();
+ updateScreen();
+ updateCPU();
+ initGamepad();
+
+ // Check for disks in hashtag
+
+ var hash = gup('disk') || hup();
+ if (hash) {
+ processHash(hash);
+ }
+
+ if (navigator.standalone) {
+ document.body.classList.add('standalone');
+ }
+
+ _apple2.run();
+}
diff --git a/js/ui/printer.js b/js/ui/printer.js
index 908d81b..2dc2023 100644
--- a/js/ui/printer.js
+++ b/js/ui/printer.js
@@ -30,18 +30,16 @@ export default function Printer(el) {
var visible = val >= 0x20;
var c = String.fromCharCode(ascii);
- if (c == '\r') {
+ if (c === '\r') {
newLine();
- } else if (c == '\n') {
+ } else if (c === '\n') {
// eat for now
- } else if (c == '\t') {
+ } else if (c === '\t') {
_lineBuffer += ' ';
- } else if (c == 0x04) {
+ } else if (ascii === 0x04) {
_lineBuffer = _lineBuffer.slice(0, -1);
- } else {
- if (visible) {
- _lineBuffer += c;
- }
+ } else if (visible) {
+ _lineBuffer += c;
}
_line.innerText = _lineBuffer;
},