diff --git a/css/ui.css b/css/ui.css index 69b8f39f..5b0a7634 100644 --- a/css/ui.css +++ b/css/ui.css @@ -575,6 +575,13 @@ div.asset_toolbar { user-select: text; 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 { line-height: 1.5; min-height: 1em; diff --git a/src/platform/zmachine.ts b/src/platform/zmachine.ts index bdfe977a..2602f68c 100644 --- a/src/platform/zmachine.ts +++ b/src/platform/zmachine.ts @@ -31,82 +31,129 @@ declare var ZVM; // https://inform-fiction.org/zmachine/standards/z1point0/sect15.html#read_char // https://manpages.debian.org/testing/inform6-compiler/inform6.1.en.html -class GlkWindow { - //area: HTMLElement; - // TODO -} - interface IFZVM { start(); run(); - version : number; - pc : number; - ram : DataView; - stack : DataView; - read_data : {buffer?}; - handle_line_input(len:number); - handle_char_input(charcode:number); + version: number; + pc: number; + ram: DataView; + stack: DataView; + read_data: { buffer?}; + handle_line_input(len: number); + handle_char_input(charcode: number); } -class GlkImpl { - vm : IFZVM; +class GlkWindow { page: HTMLElement; - input: HTMLInputElement; + stream: number; + curline: HTMLElement; curstyle: number; reverse: boolean; - waitingfor: "line" | "char" | null; - focused = false; - exited = false; + col: number; + row: number; - constructor(page: HTMLElement, input: HTMLInputElement) { + constructor(page: HTMLElement, stream: number) { this.page = page; - this.input = input; - this.reset(); - } - reset() { - this.exited = false; + this.stream = stream; this.clear(); } + clear() { this.curline = null; this.curstyle = 0; this.reverse = false; - // keep from losing input handlers - this.hideinput(); + this.col = 0; + this.row = -1; $(this.page).empty(); } + ensureline() { + if (this.curline == null) { + this.curline = $('
')[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 = $("").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')[0]; - this.page.appendChild(this.curline); - } + this.curwnd.ensureline(); } flushline() { - this.curline = null; + this.curwnd.flushline(); } - addtext(line: string, style: number) { - this.ensureline(); - if (line.length) { - var span = $("").text(line).appendTo(this.curline); - for (var i=0; i<8; i++) { - if (style & (1< 0) this.flushline(); - this.addtext(lines[i], this.curstyle); - } + //console.log('glk_put_jstring', arguments); + this.curwnd.put_jstring(val); } + 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) { console.log('glk_put_char', arguments); } - glk_put_char_stream(str, ch) { - console.log('glk_put_char_stream', arguments); - } glk_put_string(val) { console.log('glk_put_string', arguments); } @@ -193,9 +255,6 @@ class GlkImpl { glk_put_buffer_stream(str, arr) { console.log('glk_put_buffer_stream', arguments); } - glk_set_style(val) { - this.curstyle = val; - } glk_set_style_stream(str, val) { console.log('glk_set_style_stream', arguments); } @@ -208,6 +267,7 @@ class GlkImpl { glk_get_buffer_stream(str, buf) { console.log('glk_get_buffer_stream', arguments); } + */ glk_char_to_lower(val) { if (val >= 0x41 && val <= 0x5A) return val + 0x20; @@ -223,10 +283,10 @@ class GlkImpl { return val; } 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) { - console.log('glk_stylehint_clear', arguments); + //console.log('glk_stylehint_clear', arguments); } glk_style_distinguish(win, styl1, styl2) { return 0; @@ -241,40 +301,58 @@ class GlkImpl { } glk_window_open(splitwin, method, size, wintype, rock) { console.log('glk_window_open', arguments); - if (splitwin) console.log("split windows are not supported"); - return splitwin ? 0 : 1; // 0 = no window, 1 = main window + if (splitwin) { + // 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) { console.log('glk_window_close', arguments); - this.windows.pop(); // TODO + if (win == 2) $(this.windows[win].page).hide(); } glk_window_get_parent(win) { 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); + // TODO? } glk_window_get_stream(win) { console.log('glk_window_get_stream', arguments); + return this.windows[win].stream; } - */ glk_set_window(win) { console.log('glk_set_window', arguments); - //if (!win) gli_currentstr = null; - //else gli_currentstr = win.str; + this.curwnd = this.windows[win]; + 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); + // 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) { - 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) { return this.glk_gestalt_ext(sel, val, null); } glk_gestalt_ext(sel, val, arr) { - console.log('glk_gestalt_ext', arguments); + //console.log('glk_gestalt_ext', arguments); switch (sel) { case 0: // gestalt_Version @@ -403,52 +481,57 @@ class GlkImpl { 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, or will never return at all. This is used by glk_exit(), glk_select(), and glk_fileref_create_by_prompt(). */ 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 Const = { @@ -600,7 +683,7 @@ const Const = { class ZmachinePlatform implements Platform { mainElement: HTMLElement; - zfile : Uint8Array; + zfile: Uint8Array; zvm; glk; focused = false; @@ -612,16 +695,14 @@ class ZmachinePlatform implements Platform { async start() { 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 var parent = this.mainElement; var gameport = $('
').appendTo(parent); + var upperwnd = $('
').insertBefore(parent).hide(); var windowport = $('
').appendTo(gameport); var inputline = $('').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) => { this.glk.sendkey(e); }); @@ -643,9 +724,6 @@ class ZmachinePlatform implements Platform { reset(): void { if (this.zfile == null) return; - //this.glk = Glk; - //this.glk = new Object(); - //Object.setPrototypeOf(this.glk, Glk); this.zvm = new ZVM(); this.zvm.prepare(this.zfile.slice(0), { Glk: this.glk, @@ -669,6 +747,7 @@ class ZmachinePlatform implements Platform { getPC() { return this.zvm.pc; } + /* loadState(state): void { throw new Error("Method not implemented."); @@ -683,7 +762,7 @@ class ZmachinePlatform implements Platform { } } */ - + isRunning(): boolean { return this.zvm != null && !this.glk.exited; } @@ -699,7 +778,7 @@ class ZmachinePlatform implements Platform { getDefaultExtension(): string { return ".inf"; } - showHelp(tool:string, ident?:string) { + showHelp(tool: string, ident?: string) { switch (tool) { 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!! - inspect(ident:string) { + inspect(ident: string) { return inspectSymbol(this, ident); } @@ -736,7 +815,7 @@ class ZmachinePlatform implements Platform { var tree = {}; // TODO: better way? try { - for (let child=0; child<65536; child++) { + for (let child = 0; child < 65536; child++) { if (this.zvm.get_parent(child) == 0) { this.addObjectToTree(tree, child); } @@ -766,28 +845,28 @@ class ZmachinePlatform implements Platform { */ return tree; } - getFlagList(obj:number) { + getFlagList(obj: number) { var attrlookup = this.getDebugLookup('attribute'); var set_attrs = []; - for (var i=0; i<32; i++) { + for (var i = 0; i < 32; i++) { if (this.zvm.test_attr(obj, i)) { - set_attrs.push(attrlookup[i] || "#"+i); + set_attrs.push(attrlookup[i] || "#" + i); } } return set_attrs; } - getPropList(obj:number) { + getPropList(obj: number) { var proplookup = this.getDebugLookup('property'); var set_props = []; 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); if (addr == 0) break; - set_props.push(proplookup[addr] || "%"+addr); + set_props.push(proplookup[addr] || "%" + addr); } 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; return (debugsym && debugsym.debuginfo && debugsym.debuginfo[key]) || {}; }