1
0
mirror of https://github.com/sehugg/8bitworkshop.git synced 2024-06-12 18:42:14 +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;
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;

View File

@ -31,11 +31,6 @@ 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();
@ -48,34 +43,103 @@ interface IFZVM {
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 = $('<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) {
this.vm = options.vm;
this.vm.start();
@ -86,27 +150,10 @@ class GlkImpl {
update() {
// 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() {
this.ensureline();
// 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) {
$(this.input).focus();
}
@ -117,7 +164,7 @@ class GlkImpl {
$(this.input).removeClass('transcript-input-char')
}
hideinput() {
$(this.input).appendTo($(this.page).parent()).hide();
$(this.input).appendTo($(this.curwnd.page).parent()).hide();
}
clearinput() {
this.input.value = '';
@ -129,13 +176,12 @@ class GlkImpl {
this.sendinput(this.input.value.toString());
}
} else if (this.waitingfor == 'char') {
this.vm.handle_char_input(e.keyCode);
this.vm.run();
this.sendchar(e.keyCode);
e.preventDefault();
}
}
sendinput(s: string) {
this.addtext(s, Const.style_Input);
this.curwnd.addtext(s, Const.style_Input);
this.flushline();
if (this.vm.read_data.buffer) {
for (var i = 0; i < s.length; i++) {
@ -144,43 +190,59 @@ class GlkImpl {
this.vm.handle_line_input(s.length);
}
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();
}
ensureline() {
$(this.input).hide();
if (this.curline == null) {
this.curline = $('<div class="transcript-line"/>')[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 = $("<span/>").text(line).appendTo(this.curline);
for (var i=0; i<8; i++) {
if (style & (1<<i))
span.addClass("transcript-style-" + (1<<i));
glk_exit() {
this.exited = true;
this.flushline();
this.windows[1].addtext("** Game exited **", 1);
}
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) {
var lines = val.split("\n");
for (var i = 0; i < lines.length; i++) {
if (i > 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,15 +481,24 @@ class GlkImpl {
return 0;
}
/* 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.
*/
RefBox = function () {
this.value = undefined;
this.set_value = function (val) {
class RefBox {
value;
set_value(val) {
this.value = val;
}
this.get_value = function () {
get_value() {
return this.value;
}
}
@ -426,28 +513,24 @@ class GlkImpl {
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) {
class RefStruct {
constructor(numels) {
}
fields = [];
push_field(val) {
this.fields.push(val);
}
this.set_field = function (pos, val) {
set_field(pos, val) {
this.fields[pos] = val;
}
this.get_field = function (pos) {
get_field(pos) {
return this.fields[pos];
}
this.get_fields = function () {
get_fields() {
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' };
}
const has_canvas = typeof window === 'object';
@ -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 = $('<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 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) => {
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.");