view web-zui.js @ 67:41c698849ec9

Minor stylistic changes.
author Atul Varma <varmaa@toolness.com>
date Fri, 16 May 2008 18:28:59 -0700
parents 3df72ae19981
children 5716cb072c43
line wrap: on
line source

var BACKSPACE_KEYCODE = 8;
var RETURN_KEYCODE = 13;

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

  if (logfunc) {
    this._log = logfunc;
  } else {
    this._log = function() {};
  }

  var self = this;

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

    _scrollBottomWindow: function() {
      window.scroll(0, document.body.scrollHeight);
    },

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

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

    _unbindEventHandlers: function() {
      $(window).unbind("keypress", self._windowKeypress)
               .unbind("resize", self._windowResize)
               .unbind("keyup", self._windowKeyup)
               .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;
            self._removeBufferedWindows();
            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/>')
          );
          self._removeBufferedWindows();
          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;
      $(".buffered-window").css({left: contentLeft});
    },

    _removeBufferedWindows: function() {
      var windows = $(".buffered-window");
      windows.fadeOut("slow", function() { windows.remove(); });
      // A more conservative alternative to the above is:
      // $("#buffered-windows").empty();
    },

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

    setVersion: function(version) {
      self._version = version;
    },

    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) {
        if (self._console) {
          self._console.close();
          self._console = null;
        }
      } else {
        if (!self._console || self._version == 3) {
          self._console = new Console(self._size[0],
                                      numlines,
                                      $("#top-window").get(0),
                                      self);
        } else if (self._console.height != numlines) {
          // Z-Machine games are peculiar in regards to the way they
          // sometimes overlay quotations on top of the current text;
          // we basically want to preserve any text that is already in
          // the top window "below" the layer of the top window, so
          // that anything it doesn't write over remains visible, at
          // least (and this is an arbitrary decision on our part)
          // until the user has entered some input.

          var newDiv = document.createElement("div");
          var html = $("#top-window").get(0).innerHTML;
          newDiv.className = "buffered-window";
          newDiv.innerHTML = html;
          $("#buffered-windows").append(newDiv);

          // Pretend the window was just resized, which will position
          // the new buffered window properly on the x-axis.
          self._windowResize();

          self._console.resize(numlines);
        }
      }
    },

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

      self._log("print wind: " + self._activeWindow + " output: " +
                output.quote() + " style: " + styles);

      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/>");
        }

        self._scrollBottomWindow();
      } 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(logfunc);
  var runner = new EngineRunner(engine, zui, logfunc);

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

$(document).ready(_webZuiStartup);