├── README.md ├── help.png ├── index.js ├── input.js ├── logo └── package.json /README.md: -------------------------------------------------------------------------------- 1 | # paint 2 | 3 | Paint is paint in your terminal! 4 | 5 | Its available through npm: 6 | 7 | npm install -g paint 8 | 9 | ## How do I use it? 10 | 11 | ![Paint help](https://raw.github.com/mafintosh/paint/master/help.png) 12 | 13 | To show the above help: 14 | 15 | $ paint --help # shows the help 16 | 17 | Happy drawing! -------------------------------------------------------------------------------- /help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafintosh/paint/1291d1391f62b257759e784b483909319a37b6a4/help.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var clivas = require('clivas'); 4 | var input = require('./input'); 5 | 6 | var opt = function(a,b) { 7 | return process.argv.indexOf('--'+a) > -1 || process.argv.indexOf('-'+b) > -1; 8 | }; 9 | 10 | var PIPING_IN = !process.stdin.isTTY; 11 | var PIPING_OUT = !process.stdout.isTTY; 12 | var PIPING_IN_TO_OUT = opt('echo', 'e'); 13 | 14 | var CANVAS_WIDTH = clivas.width ? clivas.width : 80; 15 | var CANVAS_HEIGHT = clivas.height ? clivas.height - 1 : 30; 16 | 17 | var lines = []; 18 | var stack = []; 19 | var shouldColor = false; 20 | var chosenColor = 0; 21 | var cursorX = 0; 22 | var cursorY = 0; 23 | 24 | var lastFlush = 0; 25 | var palette = 'green blue white grey cyan magenta red yellow'.split(' '); 26 | 27 | var draw = function() { 28 | if (Date.now() - lastFlush > 5000) { 29 | clivas.flush(); 30 | lastFlush = Date.now(); 31 | } 32 | clivas.clear(); 33 | for (var i = 0; i < CANVAS_HEIGHT; i++) { 34 | lines[i] = lines[i] || Array(CANVAS_WIDTH+1).join(' x').split('x'); 35 | 36 | if (cursorY === i && !PIPING_IN) { 37 | clivas.line(lines[i].slice(0,cursorX).join('')+'{inverse+'+palette[chosenColor]+':'+(shouldColor ? 'x' : 'o')+'}'+lines[i].slice(cursorX+1).join('')); 38 | } else { 39 | clivas.line(lines[i].join('')); 40 | } 41 | } 42 | }; 43 | 44 | input.on('key', function(key) { 45 | if (key === 'up') { 46 | cursorY--; 47 | } 48 | if (key === 'down') { 49 | cursorY++; 50 | } 51 | if (key === 'left') { 52 | cursorX--; 53 | } 54 | if (key === 'right') { 55 | cursorX++; 56 | } 57 | 58 | cursorX = Math.min(Math.max(cursorX, 0), CANVAS_WIDTH-1); 59 | cursorY = Math.min(Math.max(cursorY, 0), CANVAS_HEIGHT-1); 60 | 61 | if (key === 'space') { 62 | chosenColor = (chosenColor+1) % palette.length; 63 | } 64 | if (key === 'undo') { 65 | shouldColor = false; 66 | var lastAction = stack.pop(); 67 | if (lastAction) { 68 | lines[lastAction[0]][lastAction[1]] = lastAction[2]; 69 | } 70 | } 71 | if (key === 'enter') { 72 | shouldColor = !shouldColor; 73 | } 74 | if (shouldColor) { 75 | if (stack.length > 500) { 76 | stack.shift(); 77 | } 78 | stack.push([cursorY,cursorX,lines[cursorY][cursorX]]); 79 | lines[cursorY][cursorX] = '{'+palette[chosenColor]+'+inverse: }'; 80 | } 81 | 82 | draw(); 83 | }); 84 | 85 | if (opt('help', 'h')) { 86 | clivas.use(process.stderr); 87 | clivas.write(require('fs').readFileSync(require('path').join(__dirname,'logo'))); 88 | clivas.line('{green:you are using} {bold:paint} {green:version} {bold:'+require('./package.json').version+'} {green:by} @{bold:mafintosh}\n'); 89 | clivas.line(' {green:save drawing} {bold:paint > myimage}'); 90 | clivas.line(' {green:show drawing} {bold:cat myimage}'); 91 | clivas.line(' {green:save input} {bold:paint --echo > myinput}'); 92 | clivas.line(' {green:show animation} {bold:cat myinput | paint --delay}\n'); 93 | clivas.line(' {cyan:arrows} to move brush'); 94 | clivas.line(' {cyan:enter} to lower/lift brush'); 95 | clivas.line(' {cyan:space} to change color'); 96 | clivas.line(' {cyan:ctrl+z} to undo'); 97 | clivas.line(' {cyan:ctrl+c} to exit (and save)\n'); 98 | process.exit(0); 99 | } 100 | 101 | clivas.use(process.stderr).write('G1tIG1sySg==', 'base64'); 102 | clivas.flush(false); 103 | clivas.cursor(false); 104 | 105 | if (PIPING_OUT && !PIPING_IN_TO_OUT) { 106 | process.on('exit', function() { 107 | while (lines.length && !lines[lines.length-1].join('').trim()) { 108 | lines.pop(); 109 | } 110 | for (var i = 0; i < lines.length; i++) { 111 | lines[i] = lines[i].join('').replace(/\s+$/g, ''); 112 | } 113 | lines = lines.join('\n'); 114 | palette.forEach(function(color) { 115 | var pattern = new RegExp('\\{'+color+'\\+inverse:([^\\}]+)\\}(\\n?)\\{'+color+'\\+inverse:([^\\}]+)\\}'); 116 | while (pattern.test(lines)) { 117 | lines = lines.replace(pattern, function(_, a, newline, b) { 118 | return '{'+color+'+inverse:'+a+(newline || '')+b+'}'; 119 | }); 120 | } 121 | }); 122 | clivas.use(process.stdout); 123 | clivas.clear(); 124 | clivas.line(lines); 125 | process.stdout.write('\n'); 126 | }); 127 | } 128 | if (PIPING_OUT && PIPING_IN_TO_OUT) { 129 | input.on('data', function(data) { 130 | process.stdout.write(data); 131 | }); 132 | } 133 | if (PIPING_IN) { 134 | clivas.flush(true); 135 | } 136 | 137 | draw(); -------------------------------------------------------------------------------- /input.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | 3 | if (process.stdin.setRawMode) { 4 | process.stdin.setRawMode(true); 5 | } 6 | 7 | var DELAY = process.argv.indexOf('--delay') > -1 || process.argv.indexOf('-d') > -1; 8 | 9 | var ENTER_1 = '\r'; 10 | var ENTER_2 = '\n'; 11 | var UP = '\u001b[A'; 12 | var DOWN = '\u001b[B'; 13 | var RIGHT = '\u001b[C'; 14 | var LEFT = '\u001b[D'; 15 | var SPACE = ' '; 16 | var UNDO = '\u001a'; 17 | 18 | var that = new EventEmitter(); 19 | var buf = ''; 20 | 21 | var timeout; 22 | var stack = []; 23 | var consume = function() { 24 | clearTimeout(timeout); 25 | if (!stack.length) return; 26 | that.emit('key', stack.shift()); 27 | timeout = setTimeout(consume, 50); 28 | }; 29 | 30 | var read = function(data, key) { 31 | if (buf.slice(0, data.length) !== data) return false; 32 | buf = buf.slice(data.length); 33 | if (!DELAY) { 34 | that.emit('key', key); 35 | } else { 36 | stack.push(key); 37 | clearTimeout(timeout); 38 | timeout = setTimeout(consume, 50); 39 | } 40 | return true; 41 | }; 42 | 43 | process.stdin.on('data', function(data) { 44 | if (data.toString() === '\u0003') return process.exit(0); 45 | 46 | that.emit('data', data); 47 | buf += data.toString(); 48 | 49 | while ( 50 | read(ENTER_1, 'enter') || 51 | read(ENTER_2, 'enter') || 52 | read(UP, 'up') || 53 | read(DOWN, 'down') || 54 | read(RIGHT, 'right') || 55 | read(LEFT, 'left') || 56 | read(SPACE, 'space') || 57 | read(UNDO, 'undo') 58 | ); 59 | }); 60 | 61 | process.stdin.resume(); 62 | module.exports = that; -------------------------------------------------------------------------------- /logo: -------------------------------------------------------------------------------- 1 | 2 |           3 |                 4 |             5 |               6 |               7 | 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "paint", 3 | "version": "0.1.4", 4 | "repository": "git://github.com/mafintosh/paint", 5 | "description": "paint is paint in your terminal", 6 | "keywords": [ 7 | "paint", 8 | "terminal", 9 | "drawing", 10 | "cli" 11 | ], 12 | "bin": { 13 | "paint": "./index.js" 14 | }, 15 | "dependencies": { 16 | "clivas": "0.1.x" 17 | } 18 | } 19 | --------------------------------------------------------------------------------