Created
July 29, 2017 18:49
-
-
Save anonymous/90e761fee54ac2624c1e4431551eb1c4 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Pixel { | |
constructor(options = {}) { | |
this.character = options.character || null; | |
this.color = options.color || null; | |
this.background = options.background || null; | |
} | |
merge(pixel) { | |
if (pixel.character !== null) { | |
this.character = pixel.character; | |
} | |
if (pixel.color !== null) { | |
this.color = pixel.color; | |
} | |
if (pixel.background !== null) { | |
this.background = pixel.background; | |
} | |
return this; | |
} | |
} | |
class Canvas { | |
constructor(options = {}) { | |
this.data = [ [ new Pixel() ] ]; | |
this.height = options.height || 20; | |
this.width = options.width || 80; | |
} | |
get height() { | |
return this.data.length; | |
} | |
set height(value) { | |
let oldValue = this.height; | |
if (value > oldValue) { | |
for (let i = oldValue; i < value; i++) { | |
this.data[i] = []; | |
for (let j = 0; j < this.width; j++) { | |
this.data[i][j] = new Pixel; | |
} | |
} | |
} else if (value < oldValue) { | |
this.data.splice(value); | |
} | |
} | |
get width() { | |
return this.data[0].length; | |
} | |
set width(value) { | |
let oldValue = this.data[0].length; | |
if (value > oldValue) { | |
for (let i = 0; i < this.height; i++) { | |
for (let j = oldValue; j < value; j++) { | |
this.data[i][j] = new Pixel(); | |
} | |
} | |
} else if (value < oldValue) { | |
for (let i = 0; i < this.height; i++) { | |
this.data[i].splice(value); | |
} | |
} | |
} | |
parseColor(color, background = false) { | |
switch(color) { | |
case null: | |
return "\x1b[0m"; | |
case "red": | |
return `\x1b[${background ? 4 : 3}1m`; | |
default: | |
return ""; | |
} | |
} | |
merge(canvas, yOffset = 0, xOffset = 0) { | |
for (let i = 0; i < (this.height < canvas.height ? this.height : canvas.height); i++) { | |
for (let j = 0; j < (this.width < canvas.width ? this.width : canvas.width); j++) { | |
let pixel = canvas.data[i][j]; | |
let thisPixel = this.data[i + yOffset][j + xOffset]; | |
thisPixel.merge(pixel); | |
} | |
} | |
return this; | |
} | |
fill(character, fillAll = false) { | |
for (let i = 0; i < this.height; i++) { | |
for (let j = 0; j < this.width; j++) { | |
let pixel = this.data[i][j]; | |
if (pixel.character === null || fillAll) { | |
pixel.character = character; | |
} | |
} | |
} | |
return this; | |
} | |
toString() { | |
let text = `\x1b[${this.height.toString(10) - 1}A\x1b[${this.width.toString(10 - 1)}D`; | |
for (let i = 0; i < this.height; i++) { | |
for (let j = 0; j < this.width; j++) { | |
let pixel = this.data[i][j]; | |
text += this.parseColor(pixel.color, false) + this.parseColor(pixel.background, true); // make this optimized | |
if (pixel.character === null) { | |
text += " "; // This could also be the right arrow asni key or enter if there's no more characters on the line | |
} else { | |
text += pixel.character; | |
} | |
} | |
text += "\n"; | |
} | |
text = text.substring(0, text.length - 1); | |
return text; | |
} | |
} | |
class Component { | |
constructor(options = {}) { | |
this.parent = null; | |
this.children = []; | |
this.root = false; | |
this.x = options.x || 0; | |
this.y = options.y || 0; | |
this.height = options.height || null; // null: takes all space, -x: takes all space minus x | |
this.width = options.width || null; | |
} | |
render(canvas) { | |
return canvas; | |
} | |
renderChildren(canvas) { | |
for (let i = 0; i < this.children.length; i++) { | |
let child = this.children[i]; | |
canvas.merge(child.render(new Canvas({ | |
height: child.absoluteHeight, | |
width: child.absoluteWidth | |
})), child.absoluteY, child.absoluteX); | |
} | |
return canvas; | |
} | |
getAbsoluteValueNull(value, fullValue) { | |
if (value === null) { | |
return fullValue; | |
} else if (value < 0) { | |
return fullValue + value; | |
} else { | |
return value; | |
} | |
} | |
appendChild(child) { | |
if (child.root === true) { | |
throw new Error("Cannot append a root element as a child"); | |
} | |
this.children.push(child); | |
child.parent = this; | |
return this; | |
} | |
appendParent(parent) { // TO-DO: Change this name to something else | |
if (this.root === true) { | |
throw new Error("Cannot append a parent of a root element"); | |
} | |
parent.children.push(this); | |
this.parent = parent; | |
return this; | |
} | |
removeChild(child) { | |
for (let i = 0; i < this.children.length; i++) { | |
if (this.children[i] === child) { | |
this.children.splice(i, 1); | |
child.parent = null; | |
return true; | |
} | |
} | |
return false; | |
} | |
get absoluteHeight() { | |
if (this.height >= 0 && this.height !== null) { | |
return this.height; | |
} else if (this.parent === null) { | |
throw new Error("Trying to use a full height negation value on a orphan"); | |
} | |
let parentHeight = this.parent.absoluteHeight; | |
if (this.height === null) { | |
return parentHeight; | |
} else if (this.height < 0) { | |
return parentHeight + this.height; | |
} | |
} | |
get absoluteWidth() { | |
if (this.width >= 0 && this.width !== null) { | |
return this.width; | |
} else if (this.parent === null) { | |
throw new Error("Trying to use a full width negation value on a orphan"); | |
} | |
let parentWidth = this.parent.absoluteWidth; | |
if (this.width === null) { | |
return parentWidth; | |
} else if (this.width < 0) { | |
return parentWidth + this.width; | |
} | |
} | |
get absoluteY() { | |
if (this.y >= 0 && this.y !== null) { | |
return this.y; | |
} else if (this.parent === null) { | |
throw new Error("Trying to use a full height negation value on a orphan"); | |
} | |
let parentHeight = this.parent.absoluteHeight; | |
if (this.y === null) { | |
return parentHeight; | |
} else if (this.y < 0) { | |
return parentHeight + this.y; | |
} | |
} | |
get absoluteX() { | |
if (this.x >= 0 && this.x !== null) { | |
return this.x; | |
} else if (this.parent === null) { | |
throw new Error("Trying to use a full width negation value on a orphan"); | |
} | |
let parentWidth = this.parent.absoluteWidth; | |
if (this.x === null) { | |
return parentWidth; | |
} else if (this.x < 0) { | |
return parentWidth + this.x; | |
} | |
} | |
trace() { | |
let trace = [this]; | |
let parent = this; | |
do { | |
trace.push(parent.parent); | |
parent = parent.parent; | |
} while (!parent.root && parent !== this) | |
return trace; | |
} | |
traceChild(child) { | |
let trace = child.trace(); | |
for (let i = 0; i < trace.length; i++) { | |
if (trace[i] === this) { | |
return trace.slice(0, i + 1).reverse(); | |
} | |
} | |
return null; | |
} | |
traceParent(parent) { | |
let trace = this.trace(); | |
for (let i = 0; i < trace.length; i++) { | |
if (trace[i] === parent) { | |
return trace.slice(0, i + 1); | |
} | |
} | |
return null; | |
} | |
getPositionChild(child, y = 0, x = 0) { | |
} | |
getPositionParent(parent, y = 0, x = 0) { | |
let trace = this.traceParent(parent); | |
if (trace === null) { | |
return null; | |
} | |
y = this.getAbsolutePosition | |
for (let i = 0; i < trace.length; i++) { | |
let currentParent = trace[i]; | |
y += currentParent.y; | |
} | |
} | |
} | |
class Root extends Component { | |
constructor(options = {}) { | |
super(options); | |
this.root = true; | |
} | |
getAbsolutePosition(position, child) { | |
let trace = child.trace(); | |
if (trace[trace.length] !== this) { | |
return false; | |
} | |
} | |
} | |
class Window extends Root { | |
constructor(options = {}) { | |
super(options); | |
this.stdin = options.stdin || process.stdin; | |
this.stdout = options.stdout || process.stdout; | |
if (this.stdin.isTTY !== true || this.stdout.isTTY !== true) { | |
throw new Error("Stream is not a TTY terminal"); | |
} | |
if (this.stdin.isRaw === false) { | |
this.stdin.setRawMode(true); | |
} | |
this.height = this.stdout.rows; // TO-DO: update this when changed, there's prob an event that fires or just use a getter | |
this.width = this.stdout.columns; | |
let that = this; | |
this.stdout.on("resize", function() { | |
that.height = that.stdout.rows; | |
that.width = that.stdout.columns; | |
}); | |
this.stdin.on("data", function(data) { | |
console.log(data); | |
}); | |
} | |
redraw() { | |
let canvas = new Canvas({height: this.height, width: this.width}); | |
canvas.merge(this.renderChildren(canvas)); | |
this.stdout.write(canvas.toString()); | |
} | |
} | |
class Text extends Component { | |
constructor(options = {}) { | |
super(options); | |
this.text = options.text || ""; | |
this.align = options.align || "left"; | |
this.color = options.color || null; | |
} | |
render(canvas) { | |
canvas.merge(this.renderChildren(canvas)); | |
let width = this.absoluteWidth; | |
let start = 0; | |
if (this.align === "right") { | |
start = width - this.text.length; | |
} else if (this.align.startsWith("center")) { | |
start = Math[this.align === "center-right" ? "ceil" : "floor"](width / 2 - this.text.length / 2); | |
} | |
for (let i = 0; i < this.text.length; i++) { | |
let pixel = canvas.data[0][i + start]; | |
pixel.character = this.text[i]; | |
pixel.color = this.color; | |
} | |
return canvas; | |
} | |
} | |
class Rectangle extends Component { | |
constructor(options = {}) { | |
super(options); | |
this.color = options.color || "white"; | |
} | |
render(canvas) { | |
canvas.merge(this.renderChildren(canvas)); | |
for (let i = 0; i < canvas.height; i++) { | |
for (let j = 0; j < canvas.width; j++) { | |
canvas.data[i][j].background = this.color; | |
} | |
} | |
return canvas; | |
} | |
} | |
class Input extends Component { | |
constructor(options = {}) { | |
super(options); | |
this.filler = options.filler || "_"; | |
this.value = options.value || ""; | |
this.position = options.position || 0; | |
} | |
render(canvas) { | |
} | |
} | |
module.exports = { | |
Pixel: Pixel, | |
Canvas: Canvas, | |
Component: Component, | |
Window: Window, | |
Text: Text, | |
Rectangle: Rectangle, | |
Input: Input | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "termapp", | |
"version": "0.1.0", | |
"description": "A package library backend for developing terminal apps.", | |
"main": "index.js", | |
"scripts": { | |
"test": "echo \"Error: no test specified\" && exit 1" | |
}, | |
"keywords": [ | |
"terminal", | |
"library" | |
], | |
"author": "Bit", | |
"license": "ISC" | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const termapp = require("./"); | |
let window = new termapp.Window(); | |
window.redraw(); | |
let title = new termapp.Rectangle({ | |
color: "red", | |
width: null, | |
height: 1 | |
}).appendParent(window); | |
let titleText = new termapp.Text({ | |
text: "Test app", | |
color: null, | |
align: "center-right" | |
}).appendParent(title); | |
window.redraw(); | |
let t = window.traceChild(titleText); | |
for (let i = 0; i < t.length; i++) { | |
let ct = t[i]; | |
console.log(`h:${ct.height} ah:${ct.absoluteHeight} w:${ct.width} aw:${ct.absoluteWidth} x:${ct.x} ax:${ct.absoluteX} y:${ct.y} ay:${ct.absoluteY}`); | |
} | |
setInterval(function() {process.exit()}, 20000) | |
/* | |
console.log(`window: h${window.height} ah${window.absoluteHeight}\ntitle: h${title.height} ah${title.absoluteHeight}\ntitleText: h${titleText.height} ah${titleText.absoluteHeight}\n`); | |
console.log(`window: w${window.width} aw${window.absoluteWidth}\ntitle: w${title.width} aw${title.absoluteWidth}\ntitleText: w${titleText.width} aw${titleText.absoluteWidth}\n`); | |
console.log(`window: x${window.x} ax${window.absoluteX}\ntitle: x${title.x} ax${title.absoluteX}\ntitleText: x${titleText.x} ax${titleText.absoluteX}\n`); | |
*/ | |
/*const readline = require("readline").createInterface({ | |
input: process.stdin, | |
output: process.stdout | |
}); | |
readline.on("line", function(line) { | |
try { | |
console.log(eval(line)); | |
} catch (error) { | |
console.log(error); | |
} | |
}); | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment