add synchronous setImage for Image elements.

This commit is contained in:
Christopher Jeffrey 2015-05-02 02:57:18 -07:00
parent e6c763a73c
commit 9260848868
2 changed files with 384 additions and 65 deletions

View File

@ -2563,6 +2563,7 @@ Element.prototype.free = function() {
Element.prototype.destroy = function() { Element.prototype.destroy = function() {
this.detach(); this.detach();
this.free(); this.free();
this.emit('destroy');
}; };
Element.prototype.hide = function() { Element.prototype.hide = function() {
@ -3070,6 +3071,10 @@ Element.prototype.enableDrag = function(verify) {
return; return;
} }
// This can happen in edge cases where the user is
// already dragging and element when it is detached.
if (!self.parent) return;
var ox = self._drag.x var ox = self._drag.x
, oy = self._drag.y , oy = self._drag.y
, px = self.parent.aleft , px = self.parent.aleft
@ -8467,7 +8472,7 @@ Terminal.prototype.bootstrap = function() {
// Incoming keys and mouse inputs. // Incoming keys and mouse inputs.
// NOTE: Cannot pass mouse events - coordinates will be off! // NOTE: Cannot pass mouse events - coordinates will be off!
this.screen.program.input.on('data', function(data) { this.screen.program.input.on('data', this._onData = function(data) {
if (self.screen.focused === self && !self._isMouse(data)) { if (self.screen.focused === self && !self._isMouse(data)) {
self.handler(data); self.handler(data);
} }
@ -8582,6 +8587,11 @@ Terminal.prototype.bootstrap = function() {
}); });
this.screen._listenKeys(this); this.screen._listenKeys(this);
this.on('destroy', function() {
self.screen.program.removeListener('data', self._onData);
self.pty.destroy();
});
}; };
Terminal.prototype.write = function(data) { Terminal.prototype.write = function(data) {
@ -8779,6 +8789,44 @@ function Image(options) {
self.setImage(self._lastFile); self.setImage(self._lastFile);
}); });
this.onScreenEvent('resize', function() {
self._needsRatio = true;
});
// Get images to overlap properly. Maybe not worth it:
// this.onScreenEvent('render', function() {
// self.screen.program.flush();
// if (!self._noImage) return;
// function display(el, next) {
// if (el.type === 'image' && el.file) {
// el.setImage(el.file, next);
// } else {
// next();
// }
// }
// function done(el) {
// el.children.forEach(recurse);
// }
// function recurse(el) {
// display(el, function() {
// var pending = el.children.length;
// el.children.forEach(function(el) {
// display(el, function() {
// if (!--pending) done(el);
// });
// });
// });
// }
// recurse(self.screen);
// });
this.onScreenEvent('render', function() {
self.screen.program.flush();
if (!self._noImage) {
self.setImage(self.file);
}
});
if (this.options.file || this.options.img) { if (this.options.file || this.options.img) {
this.setImage(this.options.file || this.options.img); this.setImage(this.options.file || this.options.img);
} }
@ -8790,15 +8838,6 @@ Image.prototype.type = 'image';
Image.w3mdisplay = '/usr/lib/w3m/w3mimgdisplay'; Image.w3mdisplay = '/usr/lib/w3m/w3mimgdisplay';
Image.prototype.render = function() {
var ret = this._render();
if (!ret) return;
if (!this._noImage) {
this.setImage(this.file);
}
return ret;
};
Image.prototype.spawn = function(file, args, opt, callback) { Image.prototype.spawn = function(file, args, opt, callback) {
var screen = this.screen var screen = this.screen
, opt = opt || {} , opt = opt || {}
@ -8824,19 +8863,123 @@ Image.prototype.spawn = function(file, args, opt, callback) {
Image.prototype.setImage = function(img, callback) { Image.prototype.setImage = function(img, callback) {
var self = this; var self = this;
if (this._settingImage) {
this._queue = this._queue || [];
this._queue.push([img, callback]);
return;
}
this._settingImage = true;
var reset = function(err, success) {
self._settingImage = false;
self._queue = self._queue || [];
var item = self._queue.shift();
if (item) {
self.setImage(item[0], item[1]);
}
};
if (Image.hasW3MDisplay === false) { if (Image.hasW3MDisplay === false) {
reset();
if (!callback) return; if (!callback) return;
return callback(new Error('W3M Image Display not available.')); return callback(new Error('W3M Image Display not available.'));
} }
if (!img) { if (!img) {
reset();
if (!callback) return; if (!callback) return;
return callback(new Error('No image.')); return callback(new Error('No image.'));
} }
this.file = img; this.file = img;
function renderImage(ratio, callback) { return this.getPixelRatio(function(err, ratio) {
if (err) {
reset();
if (!callback) return;
return callback(err);
}
return self.renderImage(img, ratio, function(err, success) {
if (err) {
reset();
if (!callback) return;
return callback(err);
}
if (self.shrink || self.options.autofit) {
delete self.shrink;
delete self.options.shrink;
self.options.autofit = true;
return self.imageSize(function(err, size) {
if (err) {
reset();
if (!callback) return;
return callback(err);
}
if (self._lastSize
&& ratio.tw === self._lastSize.tw
&& ratio.th === self._lastSize.th
&& size.width === self._lastSize.width
&& size.height === self._lastSize.height
&& self.aleft === self._lastSize.aleft
&& self.atop === self._lastSize.atop) {
reset();
if (!callback) return;
return callback(null, success);
}
self._lastSize = {
tw: ratio.tw,
th: ratio.th,
width: size.width,
height: size.height,
aleft: self.aleft,
atop: self.atop
};
self.position.width = size.width / ratio.tw | 0;
self.position.height = size.height / ratio.th | 0;
self._noImage = true;
self.screen.render();
self._noImage = false;
reset();
return self.renderImage(img, ratio, callback);
});
}
reset();
if (!callback) return;
return callback(null, success);
});
});
};
Image.prototype.renderImage = function(img, ratio, callback) {
var self = this;
if (cp.execSync) {
callback = callback || function() {};
try {
return callback(null, this.renderImageSync(img, ratio));
} catch (e) {
return callback(e);
}
}
if (Image.hasW3MDisplay === false) {
if (!callback) return;
return callback(new Error('W3M Image Display not available.'));
}
if (!ratio) {
if (!callback) return;
return callback(new Error('No ratio.'));
}
// clearImage unsets these: // clearImage unsets these:
var _file = self.file; var _file = self.file;
var _lastSize = self._lastSize; var _lastSize = self._lastSize;
@ -8882,76 +9025,25 @@ Image.prototype.setImage = function(img, callback) {
ps.stdin.write(input); ps.stdin.write(input);
ps.stdin.end(); ps.stdin.end();
}); });
}
return this.getPixelRatio(function(err, ratio) {
if (err) {
if (!callback) return;
return callback(err);
}
return renderImage(ratio, function(err, success) {
if (err) {
if (!callback) return;
return callback(err);
}
if (self.shrink || self.options.autofit) {
delete self.shrink;
delete self.options.shrink;
self.options.autofit = true;
return self.imageSize(function(err, size) {
if (err) {
if (!callback) return;
return callback(err);
}
if (self._lastSize
&& ratio.tw === self._lastSize.tw
&& ratio.th === self._lastSize.th
&& size.width === self._lastSize.width
&& size.height === self._lastSize.height
&& self.aleft === self._lastSize.aleft
&& self.atop === self._lastSize.atop) {
if (!callback) return;
return callback(null, success);
}
self._lastSize = {
tw: ratio.tw,
th: ratio.th,
width: size.width,
height: size.height,
aleft: self.aleft,
atop: self.atop
};
self.position.width = size.width / ratio.tw | 0;
self.position.height = size.height / ratio.th | 0;
self._noImage = true;
self.screen.render();
self._noImage = false;
return renderImage(ratio, callback);
});
}
if (!callback) return;
return callback(null, success);
});
});
}; };
Image.prototype.clearImage = function(callback) { Image.prototype.clearImage = function(callback) {
var self = this; var self = this;
if (cp.execSync) {
callback = callback || function() {};
try {
return callback(null, this.clearImageSync());
} catch (e) {
return callback(e);
}
}
if (Image.hasW3MDisplay === false) { if (Image.hasW3MDisplay === false) {
if (!callback) return; if (!callback) return;
return callback(new Error('W3M Image Display not available.')); return callback(new Error('W3M Image Display not available.'));
} }
if (!this._props) { if (!this._props) {
if (!callback) return; if (!callback) return;
return callback(null); return callback(null);
@ -8975,6 +9067,13 @@ Image.prototype.clearImage = function(callback) {
, aleft = this._props.aleft , aleft = this._props.aleft
, atop = this._props.atop; , atop = this._props.atop;
if (this._drag) {
aleft -= 10;
atop -= 10;
width += 10;
height += 10;
}
var input = '6;' var input = '6;'
+ aleft + ';' + aleft + ';'
+ atop + ';' + atop + ';'
@ -8994,6 +9093,15 @@ Image.prototype.imageSize = function(callback) {
var self = this; var self = this;
var img = this.file; var img = this.file;
if (cp.execSync) {
callback = callback || function() {};
try {
return callback(null, this.imageSizeSync());
} catch (e) {
return callback(e);
}
}
if (Image.hasW3MDisplay === false) { if (Image.hasW3MDisplay === false) {
if (!callback) return; if (!callback) return;
return callback(new Error('W3M Image Display not available.')); return callback(new Error('W3M Image Display not available.'));
@ -9044,6 +9152,15 @@ Image.prototype.imageSize = function(callback) {
Image.prototype.termSize = function(callback) { Image.prototype.termSize = function(callback) {
var self = this; var self = this;
if (cp.execSync) {
callback = callback || function() {};
try {
return callback(null, this.termSizeSync());
} catch (e) {
return callback(e);
}
}
if (Image.hasW3MDisplay === false) { if (Image.hasW3MDisplay === false) {
if (!callback) return; if (!callback) return;
return callback(new Error('W3M Image Display not available.')); return callback(new Error('W3M Image Display not available.'));
@ -9094,11 +9211,20 @@ Image.prototype.termSize = function(callback) {
Image.prototype.getPixelRatio = function(callback) { Image.prototype.getPixelRatio = function(callback) {
var self = this; var self = this;
if (cp.execSync) {
callback = callback || function() {};
try {
return callback(null, this.getPixelRatioSync());
} catch (e) {
return callback(e);
}
}
// XXX We could cache this, but sometimes it's better // XXX We could cache this, but sometimes it's better
// to recalculate to be pixel perfect. // to recalculate to be pixel perfect.
// if (this._ratio) { if (this._ratio && !this._needsRatio) {
// return callback(null, this._ratio); return callback(null, this._ratio);
// } }
return this.termSize(function(err, dimensions) { return this.termSize(function(err, dimensions) {
if (err) return callback(err); if (err) return callback(err);
@ -9108,10 +9234,201 @@ Image.prototype.getPixelRatio = function(callback) {
th: dimensions.height / self.screen.height th: dimensions.height / self.screen.height
}; };
self._needsRatio = false;
return callback(null, self._ratio); return callback(null, self._ratio);
}); });
}; };
Image.prototype.renderImageSync = function(img, ratio) {
var self = this;
if (Image.hasW3MDisplay === false) {
throw new Error('W3M Image Display not available.');
}
if (!ratio) {
throw new Error('No ratio.');
}
// clearImage unsets these:
var _file = this.file;
var _lastSize = this._lastSize;
this.clearImageSync();
this.file = _file;
this._lastSize = _lastSize;
var width = this.width * ratio.tw | 0
, height = this.height * ratio.th | 0
, aleft = this.aleft * ratio.tw | 0
, atop = this.atop * ratio.th | 0;
var input = '0;1;'
+ aleft + ';'
+ atop + ';'
+ width + ';'
+ height + ';;;;;'
+ img
+ '\n4;\n3;\n';
this._props = {
aleft: aleft,
atop: atop,
width: width,
height: height
};
try {
cp.execFileSync(Image.w3mdisplay, [], {
env: process.env,
encoding: 'utf8',
input: input,
timeout: 1000
});
} catch (e) {
;
}
return true;
};
Image.prototype.clearImageSync = function() {
if (Image.hasW3MDisplay === false) {
throw new Error('W3M Image Display not available.');
}
if (!this._props) {
return false;
}
var width = this._props.width + 2
, height = this._props.height + 2
, aleft = this._props.aleft
, atop = this._props.atop;
if (this._drag) {
aleft -= 10;
atop -= 10;
width += 10;
height += 10;
}
var input = '6;'
+ aleft + ';'
+ atop + ';'
+ width + ';'
+ height
+ '\n4;\n3;\n';
delete this.file;
delete this._props;
delete this._lastSize;
try {
cp.execFileSync(Image.w3mdisplay, [], {
env: process.env,
encoding: 'utf8',
input: input,
timeout: 1000
});
} catch (e) {
;
}
return true;
};
Image.prototype.imageSizeSync = function() {
var img = this.file;
if (Image.hasW3MDisplay === false) {
throw new Error('W3M Image Display not available.');
}
if (!img) {
throw new Error('No image.');
}
var buf = '';
var input = '5;' + img + '\n';
try {
buf = cp.execFileSync(Image.w3mdisplay, [], {
env: process.env,
encoding: 'utf8',
input: input,
timeout: 1000
});
} catch (e) {
;
}
var size = buf.trim().split(/\s+/);
return {
raw: buf.trim(),
width: +size[0],
height: +size[1]
};
};
Image.prototype.termSizeSync = function(_, recurse) {
if (Image.hasW3MDisplay === false) {
throw new Error('W3M Image Display not available.');
}
var buf = '';
try {
buf = cp.execFileSync(Image.w3mdisplay, ['-test'], {
env: process.env,
encoding: 'utf8',
timeout: 1000
});
} catch (e) {
;
}
if (!buf.trim()) {
// Bug: w3mimgdisplay will sometimes
// output nothing. Try again:
if (++recurse === 5) {
throw new Error('Term size not determined.');
}
return this.termSizeSync(_, recurse);
}
var size = buf.trim().split(/\s+/);
return {
raw: buf.trim(),
width: +size[0],
height: +size[1]
};
};
Image.prototype.getPixelRatioSync = function() {
var self = this;
// XXX We could cache this, but sometimes it's better
// to recalculate to be pixel perfect.
if (this._ratio && !this._needsRatio) {
return this._ratio;
}
this._needsRatio = false;
var dimensions = this.termSizeSync();
this._ratio = {
tw: dimensions.width / this.screen.width,
th: dimensions.height / this.screen.height
};
return this._ratio;
};
Image.prototype.displayImage = function(callback) { Image.prototype.displayImage = function(callback) {
return this.screen.displayImage(this.file, callback); return this.screen.displayImage(this.file, callback);
}; };

View File

@ -22,7 +22,8 @@ var image = blessed.image({
height: 'shrink', height: 'shrink',
style: { style: {
bg: 'green' bg: 'green'
} },
draggable: true
}); });
setTimeout(function() { setTimeout(function() {
@ -45,6 +46,7 @@ setTimeout(function() {
screen.render(); screen.render();
setTimeout(function() { setTimeout(function() {
screen.append(image); screen.append(image);
image.enableMouse();
screen.render(); screen.render();
}, 1000); }, 1000);
}, 1000); }, 1000);