diff --git a/README.md b/README.md index ae95807..ebd49e1 100644 --- a/README.md +++ b/README.md @@ -103,10 +103,8 @@ box.on('click', function(data) { screen.render(); }); -screen.on('keypress', function(ch, key) { - if (key.name === 'escape') { - return process.exit(0); - } +screen.key('escape', function(ch, key) { + return process.exit(0); }); screen.render(); @@ -159,6 +157,8 @@ The base node which everything inherits from. - **detach()** - remove node from its parent. - **emitDescendants(type, args..., [iterator])** - emit event for element, and recursively emit same event for all descendants. +- **get(name, [default])** - get user property with a potential default value. +- **set(name, value)** - set user property to value. #### Screen (from Node) @@ -218,11 +218,24 @@ The screen on which every other node renders. - **saveFocus()** - save the focused element. - **restoreFocus()** - restore the saved focused element. - **key(name, listener)** - bind a keypress listener for a specific key. +- **onceKey(name, listener)** - bind a keypress listener for a specific key + once. +- **unkey(name, listener)** - remove a keypress listener for a specific key. - **spawn(file, args, options)** - spawn a process in the foreground, return to blessed app after exit. - **exec(file, args, options, callback)** - spawn a process in the foreground, return to blessed app after exit. executes callback on error or exit. - **readEditor([options], callback)** - read data from text editor. +- **setEffects(el, fel, over, out, effects, temp)** - set effects based on + two events and attributes. +- **insertLine(n, y, top, bottom)** - insert a line into the screen (using csr: + this bypasses the output buffer). +- **deleteLine(n, y, top, bottom)** - delete a line from the screen (using csr: + this bypasses the output buffer). +- **insertBottom(top, bottom)** - insert a line at the bottom of the screen. +- **insertTop(top, bottom)** - insert a line at the top of the screen. +- **deleteBottom(top, bottom)** - delete a line at the bottom of the screen. +- **deleteTop(top, bottom)** - delete a line at the top of the screen. #### Element (from Node) @@ -291,13 +304,54 @@ The base element. - **toggle()** - toggle hidden/shown. - **focus()** - focus element. - **key(name, listener)** - bind a keypress listener for a specific key. +- **onceKey(name, listener)** - bind a keypress listener for a specific key + once. +- **unkey(name, listener)** - remove a keypress listener for a specific key. +- **onScreenEvent(type, listener)** - same as`el.on('screen', ...)` except this + will automatically cleanup listeners after the element is detached. #### Box (from Element) A box element which draws a simple box containing `content` or other elements. -Inherits all options, properties, events, and methods from Element. +##### Options: + +- inherits all from Element. + +##### Properties: + +- inherits all from Element. + +##### Events: + +- inherits all from Element. + +##### Methods: + +- inherits all from Element. +- **insertLine(i, line)** - insert a line into the box's content. +- **deleteLine(i)** - delete a line from the box's content. +- **getLine(i)** - get a line from the box's content. +- **setLine(i, line)** - set a line in the box's content. +- **clearLine(i)** - clear a line from the box's content. +- **insertTop(line)** - insert a line at the top of the box. +- **insertBottom(line)** - insert a line at the bottom of the box. +- **deleteTop()** - delete a line at the top of the box. +- **deleteBottom()** - delete a line at the bottom of the box. + + +#### Line (from Box) + +A simple line which can be `ascii` or `bg` styled. + +##### Options: + +- inherits all from Box. +- **orientation** - can be `vertical` or `horizontal`. +- **type, bg, fg, ch** - treated the same as a border object. + +Inherits all options, properties, events, and methods from Box. #### Text (from Element) @@ -646,6 +700,7 @@ box.setContent('line 1\nline 2'); box.insertBottom('line 3'); box.insertBottom('line 4'); box.insertTop('line 0'); +box.insertLine(1, 'line 1.5'); ``` If your element has the same width as the screen, the line insertion will be @@ -658,6 +713,7 @@ Outputting: ``` | line 0 | | line 1 | +| line 1.5 | | line 2 | | line 3 | | line 4 | diff --git a/lib/program.js b/lib/program.js index b214192..d239597 100644 --- a/lib/program.js +++ b/lib/program.js @@ -128,13 +128,23 @@ Program.prototype.listen = function() { // Input this.input.on('keypress', function(ch, key) { - key = key || 0; + key = key || { ch: ch }; + if (key.name === 'undefined' && (key.code === '[M' || key.code === '[I' || key.code === '[O')) { // A mouse sequence. The readline module doesn't understand these. return; } + + var name = (key.ctrl ? 'C-' : '') + + (key.meta ? 'M-' : '') + + (key.shift && key.name ? 'S-' : '') + + (key.name || ch); + + key.full = name; + self.emit('keypress', ch, key); + self.emit('key ' + name, ch, key); }); this.input.on('data', function(data) { diff --git a/lib/widget.js b/lib/widget.js index 57989d3..acb92a6 100644 --- a/lib/widget.js +++ b/lib/widget.js @@ -201,6 +201,17 @@ Node.prototype.gon = function(type, callback) { }); }; +Node.prototype.get = function(name, value) { + if (this.data.hasOwnProperty(name)) { + return this.data[name]; + } + return value; +}; + +Node.prototype.set = function(name, value) { + return this.data[name] = value; +}; + /** * Screen */ @@ -295,6 +306,18 @@ function Screen(options) { if (type === 'keypress' || type.indexOf('key ') === 0) self._listenKeys(); if (type === 'mouse') self._listenMouse(); } + if (type === 'mouse' + || type === 'click' + || type === 'mouseover' + || type === 'mouseout' + || type === 'mousedown' + || type === 'mouseup' + || type === 'mousewheel' + || type === 'wheeldown' + || type === 'wheelup' + || type === 'mousemove') { + self._listenMouse(); + } }); } @@ -419,7 +442,7 @@ Screen.prototype._listenKeys = function(el) { if (!grabKeys) { self.emit('keypress', ch, key); - self.emit('key ' + (key.name || ch), ch, key); + self.emit('key ' + key.full, ch, key); } // If something changed from the screen key handler, stop. @@ -429,9 +452,9 @@ Screen.prototype._listenKeys = function(el) { if (~self.input.indexOf(focused)) { focused.emit('keypress', ch, key); - focused.emit('key ' + (key.name || ch), ch, key); + focused.emit('key ' + key.full, ch, key); // self.emit('element keypress', focused, ch, key); - // self.emit('element key ' + (key.name || ch), focused, ch, key); + // self.emit('element key ' + key.full, focused, ch, key); } }); }; @@ -804,16 +827,25 @@ Screen.prototype.fillRegion = function(attr, ch, xi, xl, yi, yl) { }; Screen.prototype.key = function(key, listener) { - return this.on('key ' + key, listener); + if (typeof key === 'string') key = [key]; + key.forEach(function(key) { + return this.on('key ' + key, listener); + }, this); }; Screen.prototype.onceKey = function(key, listener) { - return this.once('key ' + key, listener); + if (typeof key === 'string') key = [key]; + key.forEach(function(key) { + return this.once('key ' + key, listener); + }, this); }; Screen.prototype.unkey = Screen.prototype.removeKey = function(key, listener) { - return this.removeListener('key ' + key, listener); + if (typeof key === 'string') key = [key]; + key.forEach(function(key) { + return this.removeListener('key ' + key, listener); + }, this); }; Screen.prototype.spawn = function(file, args, options) { @@ -1216,8 +1248,17 @@ Element.prototype.__defineGetter__('detached', function() { return false; }); -Element.prototype.key = function(key, listener) { - return this.on('key ' + key, listener); +Element.prototype.key = function() { + return Screen.prototype.key.apply(this, arguments); +}; + +Element.prototype.onceKey = function() { + return Screen.prototype.onceKey.apply(this, arguments); +}; + +Element.prototype.unkey = +Element.prototype.removeKey = function() { + return Screen.prototype.unkey.apply(this, arguments); }; Element.prototype.clearPos = function() { @@ -2767,7 +2808,7 @@ function Button(options) { self.press(); }); - if (this.options.effects) { + if (this.options.defaultEffects) { this.on('mouseover', function() { self.inverse = !self.options.inverse; self.screen.render(); @@ -2793,7 +2834,7 @@ Button.prototype.type = 'button'; Button.prototype.press = function() { var self = this; this.emit('press'); - if (this.border && this.options.effects) { + if (this.border && this.options.defaultEffects) { var color = this.border.fg; this.border.fg = 2; this.screen.render(); @@ -2985,6 +3026,14 @@ function sp(line, width, align) { // But `jjjj` is: // |jj | // |jj | +// A possibly related bug: +// For some reason (see jitsu-ui): +// {red-fg}my-app2{/red-fg} gets wrapped to: +// {red-fg}my-app\n2{/red-fg} when my-app2 +// does not. Since escape codes are not printable +// characters, this means wrapContent is doing +// something wrong and determining length including +// at least 1 char from the escape code. function wrapContent(content, width, tags, state) { var lines = content.split('\n') , out = [];