1
0
mirror of https://github.com/sehugg/8bitworkshop.git synced 2024-06-12 03:29:31 +00:00

zmachine: working on status bar window

This commit is contained in:
Steven Hugg 2020-07-10 11:50:09 -05:00
parent 0f19983654
commit caabcb8196
2 changed files with 233 additions and 147 deletions

View File

@ -575,6 +575,13 @@ div.asset_toolbar {
user-select: text; user-select: text;
font-family: Verdana, Geneva, sans-serif; font-family: Verdana, Geneva, sans-serif;
} }
.transcript-split {
padding: 0.5em;
background: #eee;
box-shadow: 0 8px 6px -6px RGBA(0,0,0,0.3);
z-index: 1;
flex-shrink: 0;
}
.transcript-line { .transcript-line {
line-height: 1.5; line-height: 1.5;
min-height: 1em; min-height: 1em;

View File

@ -31,82 +31,129 @@ declare var ZVM;
// https://inform-fiction.org/zmachine/standards/z1point0/sect15.html#read_char // https://inform-fiction.org/zmachine/standards/z1point0/sect15.html#read_char
// https://manpages.debian.org/testing/inform6-compiler/inform6.1.en.html // https://manpages.debian.org/testing/inform6-compiler/inform6.1.en.html
class GlkWindow {
//area: HTMLElement;
// TODO
}
interface IFZVM { interface IFZVM {
start(); start();
run(); run();
version : number; version: number;
pc : number; pc: number;
ram : DataView; ram: DataView;
stack : DataView; stack: DataView;
read_data : {buffer?}; read_data: { buffer?};
handle_line_input(len:number); handle_line_input(len: number);
handle_char_input(charcode:number); handle_char_input(charcode: number);
} }
class GlkImpl { class GlkWindow {
vm : IFZVM;
page: HTMLElement; page: HTMLElement;
input: HTMLInputElement; stream: number;
curline: HTMLElement; curline: HTMLElement;
curstyle: number; curstyle: number;
reverse: boolean; reverse: boolean;
waitingfor: "line" | "char" | null; col: number;
focused = false; row: number;
exited = false;
constructor(page: HTMLElement, input: HTMLInputElement) { constructor(page: HTMLElement, stream: number) {
this.page = page; this.page = page;
this.input = input; this.stream = stream;
this.reset();
}
reset() {
this.exited = false;
this.clear(); this.clear();
} }
clear() { clear() {
this.curline = null; this.curline = null;
this.curstyle = 0; this.curstyle = 0;
this.reverse = false; this.reverse = false;
// keep from losing input handlers this.col = 0;
this.hideinput(); this.row = -1;
$(this.page).empty(); $(this.page).empty();
} }
ensureline() {
if (this.curline == null) {
this.curline = $('<div class="transcript-line"/>')[0];
this.page.appendChild(this.curline);
this.row++;
this.col = 0;
}
}
flushline() {
this.curline = null;
}
// TODO: support fixed-width window (use CSS grid?)
addtext(line: string, style: number) {
this.ensureline();
if (line.length) {
var span = $("<span/>").text(line).appendTo(this.curline);
for (var i = 0; i < 8; i++) {
if (style & (1 << i))
span.addClass("transcript-style-" + (1 << i));
}
if (this.reverse) span.addClass("transcript-reverse");
//span.data('vmip', this.vm.pc);
this.col += line.length;
}
}
put_jstring(val: string) {
var lines = val.split("\n");
for (var i = 0; i < lines.length; i++) {
if (i > 0) this.flushline();
this.addtext(lines[i], this.curstyle);
}
}
move_cursor(col, row) {
// TODO
if (row == this.row && col > this.col) {
for (var i=this.col; i<col; i++)
this.addtext(' ', this.curstyle);
}
this.col = col;
this.row = row;
if (row == 0 && col == 0)
this.clear();
}
}
class GlkImpl {
vm: IFZVM;
input: HTMLInputElement;
curwnd: GlkWindow;
windows: { [win: number]: GlkWindow };
windowcount: number;
waitingfor: "line" | "char" | null;
focused = false;
exited = false;
constructor(page: HTMLElement, input: HTMLInputElement, upper: HTMLElement) {
this.windows = {
1: new GlkWindow(page, 1),
2: new GlkWindow(upper, 2),
3: new GlkWindow(null, 3), // fake window for resizing
};
this.input = input;
this.reset();
}
reset() {
this.windowcount = 0;
this.exited = false;
this.curwnd = this.windows[1];
this.clear();
}
clear() {
this.curwnd.clear();
}
init(options) { init(options) {
this.vm = options.vm; this.vm = options.vm;
this.vm.start(); this.vm.start();
} }
fatal_error(s:string) { fatal_error(s: string) {
throw new EmuHalt(s); throw new EmuHalt(s);
} }
update() { update() {
// TODO // TODO
} }
glk_exit() {
this.exited = true;
this.flushline();
this.addtext("** Game exited **", 1);
}
glk_window_clear(win) {
console.log('glk_window_clear', arguments);
this.clear();
}
glk_request_line_event_uni(win, buf, initlen) {
this.waitingfor = 'line';
this.focusinput();
}
glk_request_char_event_uni(win, buf, initlen) {
this.waitingfor = 'char';
this.focusinput();
}
focusinput() { focusinput() {
this.ensureline(); this.ensureline();
// don't steal focus while editing // don't steal focus while editing
$(this.input).appendTo(this.curline).show()[0].scrollIntoView(); $(this.input).appendTo(this.curwnd.curline).show()[0].scrollIntoView();
if (this.focused) { if (this.focused) {
$(this.input).focus(); $(this.input).focus();
} }
@ -117,7 +164,7 @@ class GlkImpl {
$(this.input).removeClass('transcript-input-char') $(this.input).removeClass('transcript-input-char')
} }
hideinput() { hideinput() {
$(this.input).appendTo($(this.page).parent()).hide(); $(this.input).appendTo($(this.curwnd.page).parent()).hide();
} }
clearinput() { clearinput() {
this.input.value = ''; this.input.value = '';
@ -129,13 +176,12 @@ class GlkImpl {
this.sendinput(this.input.value.toString()); this.sendinput(this.input.value.toString());
} }
} else if (this.waitingfor == 'char') { } else if (this.waitingfor == 'char') {
this.vm.handle_char_input(e.keyCode); this.sendchar(e.keyCode);
this.vm.run();
e.preventDefault(); e.preventDefault();
} }
} }
sendinput(s: string) { sendinput(s: string) {
this.addtext(s, Const.style_Input); this.curwnd.addtext(s, Const.style_Input);
this.flushline(); this.flushline();
if (this.vm.read_data.buffer) { if (this.vm.read_data.buffer) {
for (var i = 0; i < s.length; i++) { for (var i = 0; i < s.length; i++) {
@ -144,43 +190,59 @@ class GlkImpl {
this.vm.handle_line_input(s.length); this.vm.handle_line_input(s.length);
} }
this.clearinput(); this.clearinput();
this.hideinput(); // keep from losing input handlers
this.vm.run();
}
sendchar(code: number) {
this.vm.handle_char_input(code);
this.hideinput(); // keep from losing input handlers
this.vm.run(); this.vm.run();
} }
ensureline() { ensureline() {
$(this.input).hide(); $(this.input).hide();
if (this.curline == null) { this.curwnd.ensureline();
this.curline = $('<div class="transcript-line"/>')[0];
this.page.appendChild(this.curline);
}
} }
flushline() { flushline() {
this.curline = null; this.curwnd.flushline();
} }
addtext(line: string, style: number) {
this.ensureline(); glk_exit() {
if (line.length) { this.exited = true;
var span = $("<span/>").text(line).appendTo(this.curline); this.flushline();
for (var i=0; i<8; i++) { this.windows[1].addtext("** Game exited **", 1);
if (style & (1<<i))
span.addClass("transcript-style-" + (1<<i));
}
if (this.reverse) span.addClass("transcript-reverse");
span.data('vmip', this.vm.pc);
}
} }
glk_window_clear(win) {
console.log('glk_window_clear', arguments);
this.windows[win].clear();
}
glk_request_line_event_uni(win, buf, initlen) {
this.waitingfor = 'line';
this.focusinput();
}
glk_request_char_event_uni(win, buf, initlen) {
this.waitingfor = 'char';
this.focusinput();
}
glk_put_jstring(val: string, allbytes) { glk_put_jstring(val: string, allbytes) {
var lines = val.split("\n"); //console.log('glk_put_jstring', arguments);
for (var i = 0; i < lines.length; i++) { this.curwnd.put_jstring(val);
if (i > 0) this.flushline();
this.addtext(lines[i], this.curstyle);
}
} }
glk_put_jstring_stream(stream: number, val: string) {
//console.log('glk_put_jstring_stream', arguments);
this.windows[stream].put_jstring(val);
}
glk_put_char_stream_uni(stream: number, ch: number) {
//console.log('glk_put_char_stream_uni', arguments);
this.windows[stream].put_jstring(String.fromCharCode(ch));
}
glk_set_style(val) {
this.curwnd.curstyle = val;
}
/*
glk_put_char(ch) { glk_put_char(ch) {
console.log('glk_put_char', arguments); console.log('glk_put_char', arguments);
} }
glk_put_char_stream(str, ch) {
console.log('glk_put_char_stream', arguments);
}
glk_put_string(val) { glk_put_string(val) {
console.log('glk_put_string', arguments); console.log('glk_put_string', arguments);
} }
@ -193,9 +255,6 @@ class GlkImpl {
glk_put_buffer_stream(str, arr) { glk_put_buffer_stream(str, arr) {
console.log('glk_put_buffer_stream', arguments); console.log('glk_put_buffer_stream', arguments);
} }
glk_set_style(val) {
this.curstyle = val;
}
glk_set_style_stream(str, val) { glk_set_style_stream(str, val) {
console.log('glk_set_style_stream', arguments); console.log('glk_set_style_stream', arguments);
} }
@ -208,6 +267,7 @@ class GlkImpl {
glk_get_buffer_stream(str, buf) { glk_get_buffer_stream(str, buf) {
console.log('glk_get_buffer_stream', arguments); console.log('glk_get_buffer_stream', arguments);
} }
*/
glk_char_to_lower(val) { glk_char_to_lower(val) {
if (val >= 0x41 && val <= 0x5A) if (val >= 0x41 && val <= 0x5A)
return val + 0x20; return val + 0x20;
@ -223,10 +283,10 @@ class GlkImpl {
return val; return val;
} }
glk_stylehint_set(wintype, styl, hint, value) { glk_stylehint_set(wintype, styl, hint, value) {
console.log('glk_stylehint_set', arguments); //console.log('glk_stylehint_set', arguments);
} }
glk_stylehint_clear(wintype, styl, hint) { glk_stylehint_clear(wintype, styl, hint) {
console.log('glk_stylehint_clear', arguments); //console.log('glk_stylehint_clear', arguments);
} }
glk_style_distinguish(win, styl1, styl2) { glk_style_distinguish(win, styl1, styl2) {
return 0; return 0;
@ -241,40 +301,58 @@ class GlkImpl {
} }
glk_window_open(splitwin, method, size, wintype, rock) { glk_window_open(splitwin, method, size, wintype, rock) {
console.log('glk_window_open', arguments); console.log('glk_window_open', arguments);
if (splitwin) console.log("split windows are not supported"); if (splitwin) {
return splitwin ? 0 : 1; // 0 = no window, 1 = main window // only support status lines for now
if (method != 0x12 || wintype != 4 || size != 1) return 0;
$(this.windows[2].page).show();
}
return ++this.windowcount;
} }
/*
glk_window_close(win) { glk_window_close(win) {
console.log('glk_window_close', arguments); console.log('glk_window_close', arguments);
this.windows.pop(); // TODO if (win == 2) $(this.windows[win].page).hide();
} }
glk_window_get_parent(win) { glk_window_get_parent(win) {
console.log('glk_window_get_parent', arguments); console.log('glk_window_get_parent', arguments);
if (win == 1) return 0;
else return 1;
} }
glk_window_set_arrangement(win) { glk_window_move_cursor(win, col, row) {
console.log('glk_window_move_cursor', arguments);
this.windows[win].move_cursor(col, row);
}
glk_window_set_arrangement(win, method, size, unknown) {
console.log('glk_window_set_arrangement', arguments); console.log('glk_window_set_arrangement', arguments);
// TODO?
} }
glk_window_get_stream(win) { glk_window_get_stream(win) {
console.log('glk_window_get_stream', arguments); console.log('glk_window_get_stream', arguments);
return this.windows[win].stream;
} }
*/
glk_set_window(win) { glk_set_window(win) {
console.log('glk_set_window', arguments); console.log('glk_set_window', arguments);
//if (!win) gli_currentstr = null; this.curwnd = this.windows[win];
//else gli_currentstr = win.str; if (this.curwnd == null) this.fatal_error("no window " + win);
} }
glk_window_get_size(win, widthref, heightref) { glk_window_get_size(win, widthref: RefBox, heightref: RefBox) {
console.log('glk_window_get_size', arguments); console.log('glk_window_get_size', arguments);
// TODO: made up sizes, only status line supported
if (widthref) widthref.set_value(40);
if (heightref) heightref.set_value(win == 1 ? 25 : 1);
} }
garglk_set_reversevideo(val) { garglk_set_reversevideo(val) {
this.reverse = !!val; console.log('garglk_set_reversevideo', arguments);
this.curwnd.reverse = !!val;
}
garglk_set_reversevideo_stream(win, val) {
console.log('garglk_set_reversevideo_stream', arguments);
this.windows[win].reverse = !!val; // TODO: per window
} }
glk_gestalt(sel, val) { glk_gestalt(sel, val) {
return this.glk_gestalt_ext(sel, val, null); return this.glk_gestalt_ext(sel, val, null);
} }
glk_gestalt_ext(sel, val, arr) { glk_gestalt_ext(sel, val, arr) {
console.log('glk_gestalt_ext', arguments); //console.log('glk_gestalt_ext', arguments);
switch (sel) { switch (sel) {
case 0: // gestalt_Version case 0: // gestalt_Version
@ -403,52 +481,57 @@ class GlkImpl {
return 0; return 0;
} }
/* RefBox: Simple class used for "call-by-reference" Glk arguments. The object
is just a box containing a single value, which can be written and read.
*/
RefBox = function () {
this.value = undefined;
this.set_value = function (val) {
this.value = val;
}
this.get_value = function () {
return this.value;
}
}
/* RefStruct: Used for struct-type Glk arguments. After creating the
object, you should call push_field() the appropriate number of times,
to set the initial field values. Then set_field() can be used to
change them, and get_fields() retrieves the list of all fields.
(The usage here is loose, since Javascript is forgiving about arrays.
Really the caller could call set_field() instead of push_field() --
or skip that step entirely, as long as the Glk function later calls
set_field() for each field. Which it should.)
*/
RefStruct = function (numels) {
this.fields = [];
this.push_field = function (val) {
this.fields.push(val);
}
this.set_field = function (pos, val) {
this.fields[pos] = val;
}
this.get_field = function (pos) {
return this.fields[pos];
}
this.get_fields = function () {
return this.fields;
}
}
/* Dummy return value, which means that the Glk call is still in progress, /* Dummy return value, which means that the Glk call is still in progress,
or will never return at all. This is used by glk_exit(), glk_select(), or will never return at all. This is used by glk_exit(), glk_select(),
and glk_fileref_create_by_prompt(). and glk_fileref_create_by_prompt().
*/ */
DidNotReturn = { dummy: 'Glk call has not yet returned' }; DidNotReturn = { dummy: 'Glk call has not yet returned' };
RefBox = RefBox;
RefStruct = RefStruct;
} }
/* RefBox: Simple class used for "call-by-reference" Glk arguments. The object
is just a box containing a single value, which can be written and read.
*/
class RefBox {
value;
set_value(val) {
this.value = val;
}
get_value() {
return this.value;
}
}
/* RefStruct: Used for struct-type Glk arguments. After creating the
object, you should call push_field() the appropriate number of times,
to set the initial field values. Then set_field() can be used to
change them, and get_fields() retrieves the list of all fields.
(The usage here is loose, since Javascript is forgiving about arrays.
Really the caller could call set_field() instead of push_field() --
or skip that step entirely, as long as the Glk function later calls
set_field() for each field. Which it should.)
*/
class RefStruct {
constructor(numels) {
}
fields = [];
push_field(val) {
this.fields.push(val);
}
set_field(pos, val) {
this.fields[pos] = val;
}
get_field(pos) {
return this.fields[pos];
}
get_fields() {
return this.fields;
}
}
const has_canvas = typeof window === 'object'; const has_canvas = typeof window === 'object';
const Const = { const Const = {
@ -600,7 +683,7 @@ const Const = {
class ZmachinePlatform implements Platform { class ZmachinePlatform implements Platform {
mainElement: HTMLElement; mainElement: HTMLElement;
zfile : Uint8Array; zfile: Uint8Array;
zvm; zvm;
glk; glk;
focused = false; focused = false;
@ -612,16 +695,14 @@ class ZmachinePlatform implements Platform {
async start() { async start() {
await loadScript('./lib/zvm/ifvms.min.js'); await loadScript('./lib/zvm/ifvms.min.js');
//await loadScript('./lib/zvm/glkote.min.js');
//await loadScript('./lib/zvm/glkapi.js');
//await loadScript('./lib/zvm/parchment.debug.js');
// create divs // create divs
var parent = this.mainElement; var parent = this.mainElement;
var gameport = $('<div id="gameport"/>').appendTo(parent); var gameport = $('<div id="gameport"/>').appendTo(parent);
var upperwnd = $('<div id="upperport" class="transcript transcript-split transcript-style-2"/>').insertBefore(parent).hide();
var windowport = $('<div id="windowport" class="transcript"/>').appendTo(gameport); var windowport = $('<div id="windowport" class="transcript"/>').appendTo(gameport);
var inputline = $('<input class="transcript-input" type="text"/>').appendTo(gameport).hide(); var inputline = $('<input class="transcript-input" type="text"/>').appendTo(gameport).hide();
this.glk = new GlkImpl(windowport[0], inputline[0] as HTMLInputElement); this.glk = new GlkImpl(windowport[0], inputline[0] as HTMLInputElement, upperwnd[0]);
inputline.on('keypress', (e) => { inputline.on('keypress', (e) => {
this.glk.sendkey(e); this.glk.sendkey(e);
}); });
@ -643,9 +724,6 @@ class ZmachinePlatform implements Platform {
reset(): void { reset(): void {
if (this.zfile == null) return; if (this.zfile == null) return;
//this.glk = Glk;
//this.glk = new Object();
//Object.setPrototypeOf(this.glk, Glk);
this.zvm = new ZVM(); this.zvm = new ZVM();
this.zvm.prepare(this.zfile.slice(0), { this.zvm.prepare(this.zfile.slice(0), {
Glk: this.glk, Glk: this.glk,
@ -669,6 +747,7 @@ class ZmachinePlatform implements Platform {
getPC() { getPC() {
return this.zvm.pc; return this.zvm.pc;
} }
/* /*
loadState(state): void { loadState(state): void {
throw new Error("Method not implemented."); throw new Error("Method not implemented.");
@ -683,7 +762,7 @@ class ZmachinePlatform implements Platform {
} }
} }
*/ */
isRunning(): boolean { isRunning(): boolean {
return this.zvm != null && !this.glk.exited; return this.zvm != null && !this.glk.exited;
} }
@ -699,7 +778,7 @@ class ZmachinePlatform implements Platform {
getDefaultExtension(): string { getDefaultExtension(): string {
return ".inf"; return ".inf";
} }
showHelp(tool:string, ident?:string) { showHelp(tool: string, ident?: string) {
switch (tool) { switch (tool) {
case 'inform6': window.open("https://www.inform-fiction.org/manual/html/contents.html"); break; case 'inform6': window.open("https://www.inform-fiction.org/manual/html/contents.html"); break;
} }
@ -709,7 +788,7 @@ class ZmachinePlatform implements Platform {
} }
// TODO: Z machine is big endian!! // TODO: Z machine is big endian!!
inspect(ident:string) { inspect(ident: string) {
return inspectSymbol(this, ident); return inspectSymbol(this, ident);
} }
@ -736,7 +815,7 @@ class ZmachinePlatform implements Platform {
var tree = {}; var tree = {};
// TODO: better way? // TODO: better way?
try { try {
for (let child=0; child<65536; child++) { for (let child = 0; child < 65536; child++) {
if (this.zvm.get_parent(child) == 0) { if (this.zvm.get_parent(child) == 0) {
this.addObjectToTree(tree, child); this.addObjectToTree(tree, child);
} }
@ -766,28 +845,28 @@ class ZmachinePlatform implements Platform {
*/ */
return tree; return tree;
} }
getFlagList(obj:number) { getFlagList(obj: number) {
var attrlookup = this.getDebugLookup('attribute'); var attrlookup = this.getDebugLookup('attribute');
var set_attrs = []; var set_attrs = [];
for (var i=0; i<32; i++) { for (var i = 0; i < 32; i++) {
if (this.zvm.test_attr(obj, i)) { if (this.zvm.test_attr(obj, i)) {
set_attrs.push(attrlookup[i] || "#"+i); set_attrs.push(attrlookup[i] || "#" + i);
} }
} }
return set_attrs; return set_attrs;
} }
getPropList(obj:number) { getPropList(obj: number) {
var proplookup = this.getDebugLookup('property'); var proplookup = this.getDebugLookup('property');
var set_props = []; var set_props = [];
var addr = 0; var addr = 0;
for (var i=0; i<50; i++) { for (var i = 0; i < 50; i++) {
addr = this.zvm.find_prop(obj, 0, addr); addr = this.zvm.find_prop(obj, 0, addr);
if (addr == 0) break; if (addr == 0) break;
set_props.push(proplookup[addr] || "%"+addr); set_props.push(proplookup[addr] || "%" + addr);
} }
return set_props; return set_props;
} }
getDebugLookup(key : 'object'|'property'|'attribute'|'constant'|'global-variable') : {} { getDebugLookup(key: 'object' | 'property' | 'attribute' | 'constant' | 'global-variable'): {} {
var debugsym = (this as Platform).debugSymbols; var debugsym = (this as Platform).debugSymbols;
return (debugsym && debugsym.debuginfo && debugsym.debuginfo[key]) || {}; return (debugsym && debugsym.debuginfo && debugsym.debuginfo[key]) || {};
} }