sbbs/load/windows.js

1071 lines
31 KiB
JavaScript

/**
* Windows are elements of a Page object
*
* @param x - (int) starting x of it's parent [1..]
* @param y - (int) starting y of it's parent [1..]
* @param width - (int) full width of the window (text content will be smaller if there are scroll bars/boarder)
* @param height - (int) full height of the window (text content will be smaller if there are scroll bars/boarder)
* @param name - (string) internal name for the window (useful for debugging)
* @param parent - (object) parent of this window
* @param debug - (int) debug mode, which fills the window with debug content
* @constructor
*
* Pages have the following attributes:
* - bx/by - (int) right/bottom most boundary of the window representing the start + width/height of the window
* - child - (array) children in this window
* - height - (int) Window's height
* - name - (string) Windows name (useful for internal debugging)
* - parent - (object) Parent that this window belongs to
* - x/y - (int) start position of the window
* - visible - (bool) whether this window is visible
* - width - (int) Window's width
* - z - (int) Window's depth indicator
*
* Windows have the following public functions
* - build - Compile the frame for rendering
* - debug - Useful for debugging with properties of this Window
* - draw - Draw a part of this Window
* - drawline - Draw a y line for this Window
* - visibleChildren - Children that will be included when this window is rendered
*/
function Window(x,y,width,height,name,parent,debug) {
this.__properties__ = {
x: undefined, // X offset of parent that the canvas starts [1..width]
y: undefined, // Y offset of parent that the canvas starts [1..height]
z: 0, // Window top-bottom position, higher z is shown [0..]
ox: 0, // When canvas width > width, this is the offset we display [0..]
oy: 0, // When canvas height > height, this is the offset we display [0..]
width: undefined, // Display Width + (1 char if scrollbars = true)
height: undefined, // Display Height
canvaswidth: undefined, // Width of Canvas (default display width)
canvasheight: undefined, // Height of Canvas (default display height)
content: [], // Window content - starting at 0,0 = 1,1
visible: true, // Is this window visible
};
/*
this.__settings__ = {
checkbounds: true, // Can this frame move outside of the parent
v_scroll: true, // Can the content scroll vertically (takes up 1 line) [AUTO DETERMINE IF canvas > width]
h_scroll: false, // Can the content scroll horizontally (takes up 1 char) [AUTO DETERMINE IF canvas > height]
delay: 0, // Delay while rendering
word_wrap: false, // Word wrap content
pageable: false, // Overflowed content is rendered with the next page
};
*/
this.__relations__ = {
parent: undefined,
child: [],
};
/*
this.__position__ = {
cursor: undefined,
};
*/
/*
Validation to implement:
+ X BOUNDARY
- x cannot be < parent.x if checkbounds is true [when moving window]
- x+width(-1 if h_scroll is true) cannot be greater than parent.width if checkbounds is true
- v_scroll must be true for canvaswidth > width
- when scrolling ox cannot be > width-x
- when layout.pageable is true, next page will only have windows included that have a y in the range
ie: if height is 44 (window is 22), next page is 23-44 and will only include children where y=23-44
+ Y BOUNDARY
- y cannot be < parent.y if checkbounds is true [when moving window]
- y+height(-1 if v_scroll is true) cannot be greater than parent.height if checkbounds is true
- h_scroll must be true for canvasheight > height
- when scrolling oy cannot be > height-y
- when layout.pageable is true, children height cannot be greater than parent.height - y.
*/
function init(x,y,width,height,name,parent,debug) {
if (parent instanceof Window) {
this.z = parent.__relations__.child.length+1;
this.parent = parent;
parent.child = this;
// Check that our height/widths is not outside of our parent
if ((this.x < 1) || (width > this.parent.width))
throw new Error('Window: '+name+' width ['+width+'] is beyond our parent\'s width ['+this.parent.width+'].');
if ((x > this.parent.bx) || (x+width-1 > this.parent.bx))
throw new Error('Window: '+name+' start x ['+x+'] and width ['+width+'] is beyond our parent\'s end x ['+this.parent.bx+'].');
if ((this.y < 1) || (height > this.parent.height))
throw new Error('Window: '+name+' height ['+height+'] is beyond our parent\'s height ['+this.parent.height+'].');
if ((y > this.parent.by) || (y+height-1 > this.parent.by))
throw new Error('Window: '+name+' start y ['+y+'] and height ['+height+'] is beyond our parent\'s end y ['+this.parent.by+'].');
} else if (parent instanceof Page) {
this.parent = parent;
} else {
throw new Error('INVALID Parent Type: '+parent);
}
this.name = name;
this.x = x;
this.y = y;
this.width = this.canvaswidth = width;
this.height = this.canvasheight = height;
if (debug) {
this.canvaswidth = width*2;
this.canvasheight = height*2;
}
// Fill with data
for(var y=1;y<=this.canvasheight;y++) {
for(var x=1;x<=this.canvaswidth;x++) {
if (this.content[y] === undefined)
this.content[y] = [];
this.content[y][x] = debug
? new Char((x > this.width) || (y > this.height) ? this.name[0].toUpperCase() : this.name[0].toLowerCase(),undefined)
: new Char(undefined,undefined);
}
}
}
// Windows boundary (right most width) of parent [1..]
Window.prototype.__defineGetter__('bx',function() {
return this.__properties__.x+this.__properties__.width-1;
});
// Windows boundary (bottom most height) of parent [1..]
Window.prototype.__defineGetter__('by',function() {
return this.__properties__.y+this.__properties__.height-1;
});
/*
// Can this window be moved outside of the parents visible area
Window.prototype.__defineGetter__('checkbounds',function() {
return this.__settings__.checkbounds;
});
Window.prototype.__defineSetter__('checkbounds',function(bool) {
this.__settings__.checkbounds = bool;
});
*/
// Management of children objects, in highest z order
Window.prototype.__defineGetter__('child',function() {
// Return the children sorted in z order lowest to highest
return this.__relations__.child.sort(function(a,b) { return (a.__properties__.z < b.__properties__.z) ? -1 : ((b.__properties__.z < a.__properties__.z) ? 1 : 0); });
});
Window.prototype.__defineSetter__('child',function(obj) {
if(obj instanceof Window) {
this.__relations__.child.push(obj);
} else
throw new Error('child not an instance of Window()');
});
// Window content
Window.prototype.__defineGetter__('content',function() {
return this.__properties__.content;
});
Window.prototype.__defineSetter__('content',function(string) {
if (this.__properties__.content)
throw new Error('content already DEFINED');
return this.__properties__.content = string;
});
// Window canvas height
Window.prototype.__defineGetter__('canvasheight',function() {
return this.__properties__.canvasheight;
});
Window.prototype.__defineSetter__('canvasheight',function(int) {
if (this.__properties__.canvasheight)
throw new Error('canvasheight already DEFINED');
return this.__properties__.canvasheight = int;
});
// Window canvas width
Window.prototype.__defineGetter__('canvaswidth',function() {
return this.__properties__.canvaswidth;
});
Window.prototype.__defineSetter__('canvaswidth',function(int) {
if (this.__properties__.canvaswidth)
throw new Error('canvaswidth already DEFINED');
return this.__properties__.canvaswidth = int;
});
// Window name
Window.prototype.__defineGetter__('name',function() {
return this.__properties__.name;
});
Window.prototype.__defineSetter__('name',function(string) {
if (this.__properties__.name)
throw new Error('name already DEFINED');
return this.__properties__.name = string;
});
Window.prototype.__defineGetter__('ox',function() {
return this.__properties__.ox;
});
Window.prototype.__defineSetter__('ox',function(int) {
if (this.__properties__.ox)
throw new Error('ox already DEFINED');
this.__properties__.ox = int;
});
Window.prototype.__defineGetter__('oy',function() {
return this.__properties__.oy;
});
Window.prototype.__defineSetter__('oy',function(int) {
if (this.__properties__.oy)
throw new Error('oy already DEFINED');
this.__properties__.oy = int;
});
// Parent window object
Window.prototype.__defineGetter__('parent',function() {
return this.__relations__.parent;
});
Window.prototype.__defineSetter__('parent',function(obj) {
if (this.__relations__.parent)
throw new Error('parent already DEFINED');
return this.__relations__.parent = obj;
});
// Window's display height
Window.prototype.__defineGetter__('height',function() {
return this.__properties__.height;
});
Window.prototype.__defineSetter__('height',function(int) {
if (this.__properties__.height)
throw new Error('height already DEFINED');
this.__properties__.height = int;
});
// Window's display width
Window.prototype.__defineGetter__('width',function() {
return this.__properties__.width;
});
Window.prototype.__defineSetter__('width',function(int) {
if (this.__properties__.width)
throw new Error('width already DEFINED');
this.__properties__.width = int;
});
// Window's start position on it's parent (width)
Window.prototype.__defineGetter__('x',function() {
return this.__properties__.x;
});
Window.prototype.__defineSetter__('x',function(int) {
if (this.__properties__.x)
throw new Error('x already DEFINED');
this.__properties__.x = int;
});
// Window's start position on it's parent (height)
Window.prototype.__defineGetter__('y',function() {
return this.__properties__.y;
});
Window.prototype.__defineSetter__('y',function(int) {
if (this.__properties__.y)
throw new Error('y already DEFINED');
this.__properties__.y = int;
});
// Is the current window visible
Window.prototype.__defineGetter__('visible',function() {
return this.__properties__.visible;
});
Window.prototype.__defineSetter__('visible',function(bool) {
if (typeof bool !== 'boolean')
throw new Error('visible expected a true/false');
this.__properties__.visible = bool;
});
// What position is this window (highest is visible)
Window.prototype.__defineSetter__('z',function(int) {
if (this.__properties__.z)
throw new Error('z already DEFINED');
this.__properties__.z = int;
});
Window.prototype.__defineGetter__('z',function() {
return this.__properties__.z;
});
/**
* Build this window, returning an array of Char that will be rendered by Page
*
* @param xoffset - (int) This windows x position for its parent
* @param yoffset - (int) This windows y position for its parent
* @param debug - (int) debug mode, which fills the window with debug content
* @returns {*[]}
*/
Window.prototype.build = function(xoffset,yoffset,debug) {
var display = [];
if (debug) {
writeln('********* ['+this.name+'] *********');
writeln('name :'+this.name);
writeln('xoff :'+xoffset);
writeln('yoff :'+yoffset);
writeln('x :'+this.x);
writeln('bx :'+this.bx)
writeln('ox :'+this.ox);
writeln('y :'+this.y);
writeln('by :'+this.by)
writeln('oy :'+this.oy);
writeln('lines :'+this.content.length);
writeln('content:'+JSON.stringify(Object.keys(this.content).join(',')));
}
if (debug)
writeln('-------------');
for (y=1;y<=this.height;y++) {
if (debug)
write(padright(y,2,0)+':');
var sy = this.y-1+y+yoffset-1;
for (x=1;x<=this.width;x++) {
if (debug)
writeln('- Checking :'+this.name+', y:'+(y+this.oy)+', x:'+(x+this.ox));
var sx = this.x-1+x+xoffset-1;
if (display[sy] === undefined)
display[sy] = [];
if ((this.content[y+this.oy] !== undefined) && (this.content[y+this.oy][x+this.ox] !== undefined)) {
display[sy][sx] = this.content[y+this.oy][x+this.ox];
if (debug)
writeln('- storing in y:'+(sy)+', x:'+(sx)+', ch:'+display[sy][sx].ch);
} else {
//display[sy][sx] = new Char(null,BG_BLACK|LIGHTGRAY);
display[sy][sx] = new Char();
if (debug)
writeln('- nothing for y:'+(sy)+', x:'+(sx)+', ch:'+display[sy][sx].ch);
}
}
if (debug)
writeln();
}
if (debug) {
writeln('Window:'+this.name+', has ['+this.child.filter(function(child) { return child.visible; }).length+'] children');
this.child.forEach(function(child) {
writeln(' - child:'+child.name+', visible:'+child.visible);
})
}
// Fill the array with our values
var that = this;
this.child.filter(function(child) { return child.visible; }).forEach(function(child) {
if (debug) {
writeln('=========== ['+child.name+'] =============');
writeln('xoff :'+xoffset);
writeln('yoff :'+yoffset);
writeln('this.x :'+that.x);
writeln('this.y :'+that.y);
}
draw = child.build(that.x+xoffset-1,that.y+yoffset-1,debug);
if (debug) {
writeln('draw y:'+JSON.stringify(Object.keys(draw).join(',')));
writeln('draw 1:'+JSON.stringify(Object.keys(draw[1]).join(',')));
}
for (var y in draw)
for (var x in draw[y]) {
if (display[y] === undefined)
display[y] = [];
display[y][x] = draw[y][x];
}
if (debug) {
writeln('draw 1:'+JSON.stringify(Object.keys(draw[1]).join(',')));
writeln('=========== END ['+child.name+'] =============')
}
})
if (debug) {
writeln('this.name:'+this.name);
writeln('this.y:'+this.y);
writeln('display now:'+Object.keys(display[this.y]).join(','));
writeln('********* END ['+this.name+'] *********');
}
return display;
}
Window.prototype.debug = function(text) {
return '- '+text+': '+this.name+'('+this.x+'->'+(this.bx)+') width:'+this.width+' ['+this.y+'=>'+this.by+'] with z:'+this.z;
};
/**
* Render this window
*
* @param start - (int) Starting x position
* @param end - (int) Ending x position
* @param y - (int) Line to render
* @param color - (bool) Whether to include color
* @returns {{x: number, content: string}}
*/
Window.prototype.draw = function(start,end,y,color) {
var content = '';
for (x=start;x<=end;x++) {
var rx = this.ox+x;
var ry = this.oy+y;
// Check if we have an attribute to draw
if (! (ry in this.content) || ! (rx in this.content[ry])) {
content += ' ';
continue;
}
if (color === undefined || color === true) {
// Only write a new attribute if it has changed
if ((this.last === undefined) || (this.last !== this.content[ry][rx].attr)) {
this.last = this.content[ry][rx].attr;
content += (this.last === null ? BG_BLACK|LIGHTGRAY : this.last);
}
}
try {
content += (this.content[ry][rx].ch !== null ? this.content[ry][rx].ch : ' ');
} catch (e) {
writeln(e);
writeln('---');
writeln('x:'+(x-this.x));
writeln('y:'+(y-this.y));
writeln('ox:'+this.ox);
writeln('oy:'+this.oy);
writeln('rx:'+rx);
writeln('ry:'+ry);
exit();
}
}
return { content: content, x: end - start + 1 };
}
/**
* DRAW a line for this Window
*
* @param startx - the start position to render this window [1..]
* @param endx - the stop position to stop rendinering this window [1..]
* @param y - the current windows line number [1..]
* @param color - output color
* @param debug - turn on debugging
*
* Other Attributes:
* @param x - Our current X position
*
* When rendering, for each children on a y axis, the one with a character in x and the highest z wins.
*
* = Thus, if there is only 1 child on a y line, we render that child.width, with optional padding on the left/right.
*
* = If there are more children:
* + build an array of each childs x starting position sorted by z (asc).
* + if we need, we pad until the first child to rendered
* + after the child is rendered, we pad on the right if it didnt render to window.width
*
* + we render the a.x -> a.bx, unless there are other children with a higher z and it's x < a.bx
* + "x" keeps track of where we are
* + repeat until all children are processed, repeating from the begining again if necessary where children with a lower z have a wider width
*/
Window.prototype.drawline = function(startx,endx,y,color,debug) {
// Some sanity checking
if (startx < 1)
throw new Error('Nope, startx < 1:'+startx);
if (endx < startx)
throw new Error('Nope, endx:'+endx+' < startx:'+startx);
if (y < 1)
throw new Error('Nope, y < 1:'+y);
// Advance x if required
var x = startx;
// Get any of our children
var children = this.visibleChildren();
// Find children with something on this line.
var that = this;
var kids = children.filter(function(child) {
return (y-that.y+1 >= child.y) && (y-that.y+1 <= child.by) && (child.x < endx) && (child.bx > startx);
});
var draw;
var content = '';
if (debug !== false && (y > debug)) {
writeln()
writeln('********* drawline for ['+this.name+'] ***********');
writeln('* chars :'+startx+'->'+endx);
writeln('* line :'+y);
writeln('* parents start line :'+(this.parent.y !== undefined ? this.parent.y : '-no parent-'));
writeln(this.debug('drawline'));
writeln('* we have children :'+children.length);
children.forEach(function(child) {
if (y > debug) writeln(child.debug('CHILD'));
})
writeln('* relevant :'+kids.length);
kids.forEach(function(child) {
writeln(child.debug('KID'));
})
writeln('*************************************'+'*'.repeat(this.name.length));
}
var child = null;
// Only 1 child, so we render the line with it
if (kids.length === 1) {
child = kids.pop();
if (debug !== false && (y > debug)) {
writeln(': Only 1 child :'+child.name);
}
if (child.x > x) {
if (debug !== false && (y > debug)) {
writeln(': Padding until :'+x+'->'+(endx < child.x-1 ? endx : child.x-1)+' on:'+(y-this.y+1)+' with:'+this.name);
writeln(': Padded :'+((endx < child.x-1 ? endx : child.x-1)-x));
}
draw = this.draw(x,endx < child.x-1 ? endx : child.x-1,y-this.y+1,color);
content += draw.content;
x += draw.x;
if (debug !== false && (y > debug)) writeln();
}
if (debug !== false && (y > debug)) {
writeln(child.debug('THIS'));
writeln(': x :'+x);
writeln(': endx :'+endx);
writeln(': size of child :'+(child.bx-child.x+1));
writeln(': draw :'+(x-child.x+1)+'->'+((endx-startx+1 < child.width ? endx : child.bx)-child.x+1)+' on:'+(y-this.y+1))
}
if (x < endx) {
draw = child.drawline(x-child.x+1,(endx < child.bx ? endx : child.bx)-child.x+1,y-this.y+1,color,(debug !== false ? debug-this.y+1 : debug));
content += draw.content;
x += draw.x;
}
if (debug !== false && (y > debug)) writeln();
} else if (kids.length !== 0) {
if (debug !== false && (y > debug)) writeln('| multiple children :'+kids.length);
// Sort the kids in x start order
kids = kids.sort(function(a,b) {
return (a.x < b.x) ? -1 : ((b.x < a.x) ? 1 : 0);
});
if (debug !== false)
kids.forEach(function(child) {
if (y > debug) writeln(child.debug('SORT'));
})
var c = 0; // Our kids index
var C = null; // If we need to come back and reprocess the list
while (c < kids.length) {
child = kids[c++];
var drawendx = endx;
if (debug !== false && (y > debug)) {
writeln('| -------------- ['+child.name+'] -----------');
writeln('| x :'+x);
writeln('| child.x :'+child.x);
writeln('| child.bx:'+child.bx);
writeln('| startx :'+startx);
writeln(child.debug('CHILD'));
}
// If this child cannot be rendered, skip it (because it is underneath another window)
if (x > child.bx) {
if (debug !== false && (y > debug)) writeln('| skipping:'+child.name);
continue;
}
// Pad the beginning of this child
if (child.x > x) {
if (debug !== false && (y > debug)) {
writeln('| Padding :'+x+'->'+(child.x-1)+' for: '+this.name);
}
draw = this.draw(x,child.x-1,y-this.y+1,color);
content += draw.content;
x += draw.x;
if (debug !== false && (y > debug)) writeln();
}
/*
// If this child cannot be rendered, skip it
if (x < child.x) {
if (debug !== false && (y > debug)) writeln('| skipping:'+child.name);
continue;
}
*/
if (kids[c] !== undefined) {
if (debug !== false && (y > debug)) writeln(kids[c].debug('AFTER'));
// If the next child has a higher z and the same x skip this one
if ((kids[c].x === x) && (kids[c].z > child.z)) {
if (debug !== false && (y > debug)) writeln('|- Skipping:'+child.name);
C = kids[c].x;
continue;
}
if (debug !== false && (y > debug)) writeln('| FILTERING: x le:'+x+' bx lt:'+(child.bx)+' and z gt:'+child.z);
var nextkid = kids.filter(function(kid) {
if (debug !== false && (y > debug)) writeln('| - EVAL: '+kid.name+': x:'+(kid.x)+' bx:'+(kid.bx)+' and z:'+kid.z);
return ((kid.bx > x+startx-1)) && (kid.x < child.bx) && (kid.z > child.z);
});
if (debug !== false && (y > debug)) writeln('| Got next children: '+nextkid.length);
if (debug !== false)
nextkid.forEach(function(child) {
if (y > debug) writeln(child.debug('NEXT'));
})
// If a next child who starts before we finish and has a higher z, we'll stop at that next child
if (nextkid.length) {
// If nextkid should already be showing, we'll skip to it
if (x > nextkid[0].x) {
C = nextkid[0].bx;
if (debug !== false && (y > debug)) writeln('| NEXTKID should have started, skipping to it:'+nextkid[0].x+' (C:'+C+')');
continue;
}
drawendx = (endx-child.x+1 < nextkid[0].x-1 ? endx : nextkid[0].x-1);
if (debug !== false && (y > debug)) {
writeln('| x :'+x);
writeln('| startx :'+startx);
writeln('| childx :'+child.x);
}
if (debug !== false && (y > debug)) {
writeln(nextkid[0].debug('NEXTKID'));
writeln('| draw partial width of me:'+child.name+', drawendx:'+drawendx+' because nextkid starts:'+nextkid[0].name+' at:'+nextkid[0].x);
}
// If I need to continue after next kid, we'll come back
if ((! C) && (nextkid[0].bx < child.bx)) {
C = nextkid[0].bx;
if (debug !== false && (y > debug)) writeln('| coming back to:'+child.name+' at:'+C);
}
// If C is set, we need to push it out.
if (C)
C = drawendx;
// No next children
} else {
if (debug !== false && (y > debug)) {
writeln('| x :'+x);
writeln('| startx :'+startx);
writeln('| endx :'+endx);
writeln('| child :'+child.name);
writeln('| childx :'+child.x);
writeln('| childbx:'+child.bx);
writeln('| calcsta:'+(endx-child.x+1));
}
drawendx = (endx-child.x+1 < child.bx ? x+child.width : child.bx);
if (debug !== false && (y > debug)) writeln('| draw full width of me:'+child.name+', from:'+(x-child.x+1)+' until:'+drawendx);
}
// No other children
} else {
if (debug !== false && (y > debug)) {
writeln('| No other child');
writeln(child.debug('DRAW'));
}
/*
// If there is a gap, we'll need to pad it.
if (x < child.x) {
write(':'.repeat(xx=child.x-x));
x += xx;
}
*/
drawendx = (endx-child.x+1 < child.width ? endx : child.bx);
if (debug !== false && (y > debug)) {
writeln('| will render:'+child.name+', from:'+(x-child.x+1)+'->'+(drawendx-child.x+1)+' on:'+(y-this.y+1));
}
}
if (x < endx) {
draw = child.drawline(x-child.x+1,(x < endx ? drawendx : child.bx)-child.x+1,y-this.y+1,color,(debug !== false ? debug-this.y+1 : debug));
content += draw.content;
x += draw.x;
}
if (debug !== false && (y > debug)) {
writeln();
writeln('| C :'+C);
writeln('| x :'+x);
}
if (C && (x > C)) {
c = 0;
C = null;
if (debug !== false && (y > debug)) writeln('! Resetting c back');
}
}
}
if (debug !== false && (y > debug)) {
writeln('= x :'+x);
writeln('= startx :'+startx);
}
// Pad the beginning of this child
if (x < startx) {
if (debug !== false && (y > debug)) writeln('! Padding until:'+this.x);
write(' '.repeat(xx=this.x-startx));
x += xx;
}
// Render our item
if (debug !== false && (y > debug)) writeln('= drawing:'+this.name+' ('+x+'->'+endx+')');
draw = this.draw(x,endx,y-this.y+1,color);
content += draw.content;
x += draw.x;
if (debug !== false && (y > debug)) {
writeln();
writeln('- DONE, x now :'+x+' (endx:'+endx+') for:'+this.name);
writeln('- DREW :'+(x-startx));
writeln('************** end for ['+this.name+'] ***********');
}
if (debug !== false && y > debug+1) exit();
return { content: content, x: endx-startx+1 };
}
Window.prototype.scroll = function(x,y) {
this.__properties__.ox += x;
if (this.__properties__.ox < 0)
this.__properties__.ox = 0;
this.__properties__.oy += y;
if (this.__properties__.oy < 0)
this.__properties__.oy = 0;
}
// Return the visible children (child should have sort by z)
Window.prototype.visibleChildren = function() {
return this.child.filter(function(child) {
return child.visible;
});
}
init.apply(this,arguments);
}
/**
* Each character in a window
*
* @todo Need to add a Viewdata implementation
* @param ch
* @param attr
* @param ext - tex = ANSItex, vtx = ViewDasta
* @constructor
*/
function Char(ch,attr) {
this.__properties__ = {
attr: attr, // - Attributes for the character (ie: color)
ch: ch, // - Character to be shown
//changed: false, // - Has this ch or attr been changed?
};
/**
* Return the color codes required to draw the current character
*
* @todo Implement Viewdata
* @param last - last rendered char
* @param ext - service we are rendering for
* @param debug - debug mode
* @returns {string|undefined}
*/
Char.prototype.attribute = function(last,ext,debug) {
// If our attr is undefined, we'll return
if (this.attr === undefined)
return;
// @todo This condition appears to fail?
if (this.attr === null)
throw new Error('Attributes shouldnt be null');
var ansi = [];
var c;
var l;
var r = '';
if (debug) {
writeln();
writeln('- last:'+last+', this:'+this.attr);
}
switch (ext) {
case 'tex':
if (debug) {
writeln(' - this BG_BLACK:'+(this.attr & BG_BLACK));
writeln(' - last BG_BLACK:'+(last & BG_BLACK));
writeln(' - this HIGH:'+(this.attr & HIGH));
writeln(' - last HIGH:'+(last & HIGH));
writeln(' - this BLINK:'+(this.attr & BLINK));
writeln(' - last BLINK:'+(last & BLINK));
}
if (
(((this.attr & BG_BLACK) !== (last & BG_BLACK))
|| ((this.attr & HIGH) !== (last & HIGH))
|| ((this.attr & BLINK) !== (last & BLINK))))
{
ansi.push('0');
last = BG_BLACK|LIGHTGRAY;
}
if ((this.attr & HIGH) && ((this.attr & HIGH) !== (last & HIGH))) {
ansi.push('1');
}
if ((this.attr & BLINK) && ((this.attr & BLINK) !== (last & BLINK))) {
ansi.push('5');
}
c = (this.attr & 0x07);
l = (last & 0x07);
// Foreground
switch (c) {
case BLACK:
r = 30;
break;
case RED:
r = 31;
break;
case GREEN:
r = 32;
break;
case BROWN:
r = 33;
break;
case BLUE:
r = 34;
break;
case MAGENTA:
r = 35;
break;
case CYAN:
r = 36;
break;
case LIGHTGRAY:
r = 37;
break;
}
//writeln('r:'+r+', l:'+l+', c:'+c);
if (r && (c !== l))
ansi.push(r);
// Background
if (this.attr & 0x70) {
c = (this.attr & 0x70);
l = (last & 0x70);
switch (this.attr & 0x70) {
case BG_BLACK:
r = 40;
break;
case BG_RED:
r = 41;
break;
case BG_GREEN:
r = 42;
break;
case BG_BROWN:
r = 43;
break;
case BG_BLUE:
r = 44;
break;
case BG_MAGENTA:
r = 45;
break;
case BG_CYAN:
r = 46;
break;
case BG_LIGHTGRAY:
r = 47
break;
}
if (r && (c !== l))
ansi.push(r);
}
if (debug)
writeln(' - ansi:'+ansi);
return ansi.length ? (debug ? '': '\x1b')+'['+ansi.join(';')+'m' : undefined;
case 'vtx':
if (debug)
log(LOG_DEBUG,'+ last:'+last+', attr ('+this.attr+')');
switch (this.attr) {
// \x08
case BLINK:
r = VIEWDATA_BLINK;
break;
// \x09
case STEADY:
r = VIEWDATA_STEADY;
break;
// \x0c
case NORMAL:
r = VIEWDATA_NORMAL;
break;
// \x0d
case DOUBLE:
r = VIEWDATA_DOUBLE;
break;
// \x18
case CONCEAL:
r = VIEWDATA_CONCEAL;
break;
// \x19
case BLOCKS:
r = VIEWDATA_BLOCKS;
break;
// \x1a
case SEPARATED:
r = VIEWDATA_SEPARATED;
break;
// \x1c
case BLACKBACK:
r = VIEWDATA_BLACKBACK;
break;
// \x1d
case NEWBACK:
r = VIEWDATA_NEWBACK;
break;
// \x1e
case HOLD:
r = VIEWDATA_HOLD;
break;
// \x1f
case RELEASE:
r = VIEWDATA_REVEAL;
break;
// Not handled
// \x0a-b,\x0e-f,\x1b
case 0xff00:
return '?';
default:
var mosiac = (this.attr & MOSIAC);
c = (this.attr & 0x07);
// Color control \x00-\x07, \x10-\x17
switch (c) {
case BLACK:
r = VIEWDATA_BLACKBACK;
break;
case RED:
r = mosiac ? VIEWDATA_MOSIAC_RED : VIEWDATA_RED;
break;
case GREEN:
r = mosiac ? VIEWDATA_MOSIAC_GREEN : VIEWDATA_GREEN;
break;
case BROWN:
r = mosiac ? VIEWDATA_MOSIAC_YELLOW : VIEWDATA_YELLOW;
break;
case BLUE:
r = mosiac ? VIEWDATA_MOSIAC_BLUE : VIEWDATA_BLUE;
break;
case MAGENTA:
r = mosiac ? VIEWDATA_MOSIAC_MAGENTA : VIEWDATA_MAGENTA;
break;
case CYAN:
r = mosiac ? VIEWDATA_MOSIAC_CYAN : VIEWDATA_CYAN;
break;
case LIGHTGRAY:
r = mosiac ? VIEWDATA_MOSIAC_WHITE : VIEWDATA_WHITE;
break;
default:
log(LOG_DEBUG,'Not a color?:'+c);
return '?';
}
}
if (debug)
log(LOG_DEBUG,'= result:'+r.charCodeAt(0)+', ('+r+')');
return ESC+r;
default:
throw new Error(ext+': has not been implemented');
}
};
Char.prototype.__defineGetter__('attr',function() {
return this.__properties__.attr;
});
Char.prototype.__defineGetter__('ch',function() {
return this.__properties__.ch;
});
Char.prototype.__defineSetter__('ch',function(char) {
if (typeof char !== 'string')
throw new Error('ch is not a string')
if (char.length !== 1)
throw new Error('ch can only be 1 character');
this.__properties__.ch = char;
})
}