1067 lines
31 KiB
JavaScript
1067 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 (false && debug)
|
|
writeln(' - ansi:'+ansi);
|
|
|
|
return ansi.length ? (debug ? '': '\x1b')+'['+ansi.join(';')+'m' : undefined;
|
|
|
|
case 'vtx':
|
|
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 '?';
|
|
}
|
|
}
|
|
|
|
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;
|
|
})
|
|
}
|