enigma-bbs/core/view.js

420 lines
8.8 KiB
JavaScript
Raw Normal View History

2014-10-17 04:03:32 +00:00
/* jslint node: true */
'use strict';
var util = require('util');
var ansi = require('./ansi_term.js');
2014-10-20 03:06:39 +00:00
var miscUtil = require('./misc_util.js');
var strUtil = require('./string_util.js');
var assert = require('assert');
var events = require('events');
var logger = require('./logger.js');
2014-10-17 04:03:32 +00:00
exports.View = View;
exports.LabelView = LabelView;
2014-10-20 03:06:39 +00:00
exports.TextEditView = TextEditView;
exports.ViewsController = ViewsController;
2014-10-17 04:03:32 +00:00
function View(client) {
2014-10-20 03:06:39 +00:00
events.EventEmitter.call(this);
2014-10-17 04:03:32 +00:00
var self = this;
2014-10-20 03:06:39 +00:00
this.client = client;
this.acceptsFocus = false;
this.acceptsKeys = false;
}
2014-10-17 04:03:32 +00:00
2014-10-20 03:06:39 +00:00
util.inherits(View, events.EventEmitter);
2014-10-17 04:03:32 +00:00
2014-10-20 03:06:39 +00:00
View.prototype.place = function(pos) {
//
// We allow [x, y], { x : x, y : y }, or (x, y)
//
if(util.isArray(pos)) {
this.x = pos[0];
this.y = pos[1];
} else if(pos.x && pos.y) {
this.x = pos.x;
this.y = pos.y;
} else if(2 === arguments.length) {
var x = parseInt(arguments[0], 10);
var y = parseInt(arguments[1], 10);
if(!isNaN(x) && !isNaN(y)) {
this.x = x;
this.y = y;
}
}
assert(this.x > 0 && this.x < this.client.term.termHeight);
assert(this.y > 0 && this.y < this.client.term.termWidth);
this.client.term.write(ansi.goto(this.x, this.y));
};
2014-10-17 04:03:32 +00:00
2014-10-20 03:06:39 +00:00
View.prototype.setFocus = function(focused) {
assert(this.x);
assert(this.y);
2014-10-17 04:03:32 +00:00
};
2014-10-20 03:06:39 +00:00
View.prototype.getColor = function() {
return this.options.color;
};
View.prototype.getFocusColor = function() {
return this.options.focusColor || this.getColor();
};
function LabelView(client, text, options) {
2014-10-17 04:03:32 +00:00
View.call(this, client);
var self = this;
2014-10-20 03:06:39 +00:00
if(options) {
if(options.maxWidth) {
text = text.substr(0, options.maxWidth);
}
2014-10-17 04:03:32 +00:00
2014-10-20 03:06:39 +00:00
text = strUtil.stylizeString(text, options.style);
}
this.value = text;
this.height = 1;
this.width = this.value.length;
2014-10-17 04:03:32 +00:00
}
util.inherits(LabelView, View);
2014-10-20 03:06:39 +00:00
LabelView.prototype.place = function(pos) {
LabelView.super_.prototype.place.apply(this, arguments);
this.client.term.write(this.value);
};
///////////////////////////////////////////////////////////////////////////////
var INTERACTIVE_VIEW_DEFAULT_SPECIAL_KEYSET = {
enter : [ 'enter' ],
exit : [ 'esc' ],
backspace : [ 'backspace', 'del' ],
next : [ 'tab' ],
};
function InteractiveView(client, options) {
View.call(this, client);
this.acceptsFocus = true;
this.acceptsKeys = true;
if(options) {
this.options = options;
} else {
this.options = {
};
}
this.options.specialKeySet = miscUtil.valueWithDefault(
options.specialKeySet, INTERACTIVE_VIEW_DEFAULT_SPECIAL_KEYSET
);
this.isSpecialKeyFor = function(checkFor, specialKey) {
return this.options.specialKeySet[checkFor].indexOf(specialKey) > -1;
};
this.backspace = function() {
this.client.term.write('\b \b');
};
}
util.inherits(InteractiveView, View);
InteractiveView.prototype.setFocus = function(focused) {
InteractiveView.super_.prototype.setFocus.call(this, focused);
this.hasFocus = focused;
};
InteractiveView.prototype.setNextView = function(id) {
this.nextId = id;
};
var TEXT_EDIT_INPUT_TYPES = [
2014-10-20 05:30:44 +00:00
'normal', 'N',
'password', 'P',
'upper', 'U',
'lower', 'l',
2014-10-20 03:06:39 +00:00
];
function TextEditView(client, options) {
InteractiveView.call(this, client, options);
if(!options) {
this.options.multiLine = false;
}
2014-10-20 05:30:44 +00:00
this.options.inputType = miscUtil.valueWithDefault(this.options.inputType, 'normal');
2014-10-20 03:06:39 +00:00
assert(TEXT_EDIT_INPUT_TYPES.indexOf(this.options.inputType) > -1);
2014-10-20 05:30:44 +00:00
if('password' === this.options.inputType || 'P' === this.options.inputType) {
2014-10-20 03:06:39 +00:00
this.options.inputMaskChar = miscUtil.valueWithDefault(this.options.inputMaskChar, '*').substr(0,1);
}
this.value = miscUtil.valueWithDefault(options.defaultValue, '');
2014-10-20 05:30:44 +00:00
2014-10-20 03:06:39 +00:00
// :TODO: hilight, text, etc., should come from options or default for theme if not provided
// focus=fg + bg
// standard=fg +bg
}
util.inherits(TextEditView, InteractiveView);
TextEditView.prototype.place = function(pos) {
TextEditView.super_.prototype.place.apply(this, arguments);
if(!this.options.maxWidth) {
this.options.maxWidth = this.client.term.termWidth - this.x;
}
this.width = this.options.maxWidth;
};
TextEditView.prototype.setFocus = function(focused) {
TextEditView.super_.prototype.setFocus.call(this, focused);
this.client.term.write(ansi.goto(this.x, this.y));
this.redraw();
this.client.term.write(ansi.goto(this.x, this.y + this.value.length));
};
TextEditView.prototype.redraw = function() {
var color = this.hasFocus ? this.getFocusColor() : this.getColor();
this.client.term.write(ansi.sgr(color.flags, color.fg, color.bg));
this.client.term.write(strUtil.pad(this.value, this.width));
};
TextEditView.prototype.onKeyPressed = function(k, isSpecial) {
assert(this.hasFocus);
if(isSpecial) {
return; // handled via onSpecialKeyPressed()
}
if(this.value.length < this.options.maxWidth) {
k = strUtil.stylizeString(k, this.options.inputType);
2014-10-17 04:03:32 +00:00
2014-10-20 03:06:39 +00:00
this.value += k;
2014-10-20 05:30:44 +00:00
if('P' === this.options.inputType.charAt(0).toUpperCase()) {
2014-10-20 03:06:39 +00:00
this.client.term.write(this.options.inputMaskChar);
} else {
this.client.term.write(k);
}
}
};
TextEditView.prototype.onSpecialKeyPressed = function(keyName) {
assert(this.hasFocus);
console.log(keyName);
if(this.isSpecialKeyFor('backspace', keyName)) {
if(this.value.length > 0) {
this.value = this.value.substr(0, this.value.length - 1);
this.backspace();
}
} else if(this.isSpecialKeyFor('enter', keyName)) {
if(this.options.multiLine) {
} else {
this.emit('action', 'accepted');
}
} else if(this.isSpecialKeyFor('next', keyName)) {
this.emit('action', 'next');
}
2014-10-17 04:03:32 +00:00
};
function MenuView(options) {
}
function VerticalMenuView(options) {
2014-10-20 03:06:39 +00:00
}
///////////////////////////////////////////////////////
// :TODO: Move to view_controller.js
function ViewsController(client) {
events.EventEmitter.call(this);
var self = this;
this.views = {};
this.client = client;
client.on('key press', function onKeyPress(k, isSpecial) {
if(self.focusedView && self.focusedView.acceptsKeys) {
self.focusedView.onKeyPressed(k, isSpecial);
}
});
client.on('special key', function onSpecialKey(keyName) {
if(self.focusedView && self.focusedView.acceptsKeys) {
self.focusedView.onSpecialKeyPressed(keyName);
}
});
this.onViewAction = function(action) {
2014-10-20 05:30:44 +00:00
console.log(action + ' @ ' + this.id);
if(self.submitViewId == this.id) {
self.emit('action', { view : this, action : 'submit' });
} else {
self.emit('action', { view : this, action : action });
if('accepted' === action || 'next' === action) {
self.nextFocus();
}
2014-10-20 03:06:39 +00:00
}
};
}
util.inherits(ViewsController, events.EventEmitter);
ViewsController.prototype.addView = function(viewInfo) {
viewInfo.view.id = viewInfo.id;
this.views[viewInfo.id] = {
view : viewInfo.view,
pos : viewInfo.pos
};
viewInfo.view.place(viewInfo.pos);
};
ViewsController.prototype.viewExists = function(id) {
return id in this.views;
};
ViewsController.prototype.getView = function(id) {
return this.views[id].view;
};
ViewsController.prototype.switchFocus = function(id) {
var view = this.getView(id);
if(!view) {
logger.log.warn('Invalid view', { id : id });
return false;
}
if(!view.acceptsFocus) {
logger.log.warn('View does not accept focus', { id : id });
return false;
}
this.focusedView = view;
view.setFocus(true);
};
ViewsController.prototype.nextFocus = function() {
var nextId = this.focusedView.nextId;
this.focusedView.setFocus(false);
if(nextId > 0) {
this.switchFocus(nextId);
} else {
this.switchFocus(this.firstId);
}
};
2014-10-20 05:30:44 +00:00
ViewsController.prototype.setSubmitView = function(id) {
this.submitViewId = id;
};
2014-10-20 03:06:39 +00:00
ViewsController.prototype.loadFromMCIMap = function(mciMap) {
var factory = new MCIViewFactory(this.client);
var view;
var mci;
for(var entry in mciMap) {
mci = mciMap[entry];
view = factory.createFromMCI(mci);
if(view) {
this.addView({
id : mci.id,
view : view,
pos : mci.position
});
view.on('action', this.onViewAction);
}
}
};
ViewsController.prototype.setViewOrder = function(order) {
var idOrder = [];
if(order) {
// :TODO:
} else {
for(var id in this.views) {
idOrder.push(id);
}
// :TODO: simply sort
console.log(idOrder);
this.firstId = idOrder[0];
}
var view;
for(var i = 0; i < idOrder.length - 1; ++i) {
view = this.getView(idOrder[i]);
if(view) {
view.setNextView(idOrder[i + 1]);
}
}
};
///////////////////////////////////////////////////
function MCIViewFactory(client, mci) {
this.client = client;
}
MCIViewFactory.prototype.createFromMCI = function(mci) {
assert(mci.code);
assert(mci.id > 0);
var view;
var options = {};
switch(mci.code) {
case 'EV' :
if(mci.args.length > 0) {
options.maxWidth = mci.args[0];
}
if(mci.args.length > 1) {
2014-10-20 05:30:44 +00:00
options.inputType = mci.args[1];
2014-10-20 03:06:39 +00:00
}
options.color = mci.color;
options.focusColor = mci.focusColor;
view = new TextEditView(this.client, options);
break;
}
return view;
};