view web-zui.js @ 54:7dc2c7bded08

Added implementation of the restart opcode.
author Atul Varma <varmaa@toolness.com>
date Fri, 16 May 2008 11:11:17 -0700
parents 0abb2dbc2dd1
children d9fa2605b044
line wrap: on
line source

var BACKSPACE_KEYCODE = 8;
var RETURN_KEYCODE = 13;

function WebZui() {
  this._size = [80, 255];
  this._console = null;
  this._activeWindow = 0;
  this._inputString = "";
  this._currentCallback = null;
  this._currStyles = ["z-roman"];
  var self = this;

  this.__proto__ = {
    onConsoleRender: function() {
      var height = $("#top-window").get(0).clientHeight;
      $("#content").get(0).style.padding = "" + height + "px 0 0 0";
    },

    _finalize: function() {
      if (self._console) {
        self._console.close();
        self._console = null;
      }
      $("#content").html("");
      self._unbindEventHandlers();
    },

    _bindEventHandlers: function() {
      $(window).keypress(self._windowKeypress);
      $(window).resize(self._windowResize);
      $(window).keyup(self._windowKeyup);
      $(window).keydown(self._windowKeydown);
    },

    _unbindEventHandlers: function() {
      $(window).unbind("keypress", self._windowKeypress);
      $(window).unbind("resize", self._windowResize);
      $(window).unbind("keyup", self._windowKeyup);
      $(window).unbind("keydown", self._windowKeydown);
    },

    // We want to make sure that all key events don't bubble up, so
    // that anything listening in--such as Firefox's "Search for text
    // when I start typing" feature--doesn't think that we're not
    // doing anything with the keypresses.  If we don't do this, such
    // listeners may think that they can intervene and capture
    // keystrokes before they get to us in the future.

    _windowKeyup: function(event) {
      return false;
    },

    _windowKeydown: function(event) {
      return false;
    },

    _windowKeypress: function(event) {
      if ($("#current-input").length == 0) {
        // We're not waiting for a line of input, but we may
        // be waiting for a character of input.

        // Note that we have to return a ZSCII keycode here.
        //
        // For more information, see:
        //
        //   http://www.gnelson.demon.co.uk/zspec/sect03.html
        if (self._currentCallback) {
          var keyCode = 0;
          if (event.charCode) {
            keyCode = event.charCode;
          } else {
            // TODO: Deal w/ arrow keys, etc.
            switch (event.keyCode) {
            case RETURN_KEYCODE:
              keyCode = event.keyCode;
              break;
            }
          }
          if (keyCode != 0) {
            var callback = self._currentCallback;

            self._currentCallback = null;
            callback(keyCode);
          }
        }
        return false;
      }

      var oldInputString = self._inputString;

      if (event.charCode) {
        var newChar = String.fromCharCode(event.charCode);
        var lastChar = self._inputString.slice(-1);
        if (!(newChar == " " && lastChar == " ")) {
          self._inputString += newChar;
        }
      } else {
        switch (event.keyCode) {
        case BACKSPACE_KEYCODE:
          if (self._inputString) {
            self._inputString = self._inputString.slice(0, -1);
          }
          break;
        case RETURN_KEYCODE:
          var finalInputString = self._inputString;
          var callback = self._currentCallback;

          self._inputString = "";
          self._currentCallback = null;
          finalInputString = finalInputString.entityify();
          $("#current-input").replaceWith(
            ('<span class="finished-input">' + finalInputString +
             '</span><br/>')
          );
          callback(finalInputString);
        }
      }
      if ($("#current-input") &&
          oldInputString != self._inputString) {
        $("#current-input").html(
          self._inputString.entityify() +
            '<span id="cursor">_</span>'
        );
      }
    },

    _windowResize: function() {
      var contentLeft = $("#content").get(0).offsetLeft + "px";
      $("#top-window").get(0).style.left = contentLeft;
    },

    _eraseBottomWindow: function() {
      $("#content").html("");
    },

    getSize: function() {
      return self._size;
    },

    onLineInput: function(callback) {
      self._currentCallback = callback;
      $("#content").append(
        '<span id="current-input"><span id="cursor">_</span></span>'
      );
    },

    onCharacterInput: function(callback) {
      self._currentCallback = callback;
    },

    onQuit: function() {
      self._finalize();
    },

    onRestart: function() {
      self._finalize();

      // TODO: It's not high-priority, but according to the Z-Machine
      // spec documentation for the restart opcode, we need to
      // preserve the "transcribing to printer" bit and the "use
      // fixed-pitch font" bit when restarting.

      window.setTimeout(_webZuiStartup, 0);
    },

    onSetStyle: function(textStyle, foreground, background) {
      switch (textStyle) {
      case 0:
        this._currStyles = ["z-roman"];
        break;
      case 1:
        this._currStyles.push("z-reverse-video");
        break;
      case 2:
        this._currStyles.push("z-bold");
        break;
      case 4:
        this._currStyles.push("z-italic");
        break;
      case 8:
        this._currStyles.push("z-fixed-pitch");
        break;
      default:
        throw new Error("Unknown style: " + textStyle);
      }
    },

    onSetWindow: function(window) {
      // From the Z-Spec, section 8.7.2.
      if (window == 1)
        self._console.moveTo(0, 0);

      self._activeWindow = window;
    },

    onEraseWindow: function(window) {
      if (window == -2) {
        self._console.clear();
        self._eraseBottomWindow();
      } else if (window == -1) {
        // From the Z-Spec, section 8.7.3.3.
        self.onSplitWindow(0);

        // TODO: Depending on the Z-Machine version, we want
        // to move the cursor to the bottom-left or top-left.
        self._eraseBottomWindow();
      } else if (window == 0) {
        self._eraseBottomWindow();
      } else if (window == 1) {
        self._console.clear();
      }
    },

    onSetCursor: function(x, y) {
      self._console.moveTo(x - 1, y - 1);
    },

    onSetBufferMode: function(flag) {
      // TODO: How to emulate non-word wrapping in HTML?
    },

    onSplitWindow: function(numlines) {
      if (numlines == 0) {
        self._console.close();
        self._console = null;
      } else
        self._console = new Console(self._size[0],
                                    numlines,
                                    $("#top-window").get(0),
                                    self);
    },

    onPrint: function(output) {
      var styles = self._currStyles.join(" ");

      if (self._activeWindow == 0) {
        var lines = output.split("\n");
        for (var i = 0; i < lines.length; i++) {
          var addNewline = false;

          if (lines[i]) {
            var chunk = lines[i].entityify();
            chunk = '<span class="' + styles + '">' + chunk + '</span>';
            $("#content").append(chunk);
            if (i < lines.length - 1)
              addNewline = true;
          } else
            addNewline = true;

          if (addNewline)
            $("#content").append("<br/>");
        }

        window.scroll(0, document.body.scrollHeight);
      } else {
        self._console.write(output, styles);
      }
    }
  };

  self._windowResize();
  self._bindEventHandlers();
  self._eraseBottomWindow();
}

function _webZuiStartup() {
  var logfunc = undefined;

  if (window.console) {
    logfunc = function(msg) { console.log(msg); };
  }

  var engine = new GnustoEngine();
  var zui = new WebZui();
  var runner = new EngineRunner(engine, zui, logfunc);

  engine.loadStory(troll_z5.slice());
  runner.run();
}

$(document).ready(_webZuiStartup);