optimize render process
This commit is contained in:
parent
de2a977a42
commit
0bc817cde1
4 changed files with 185 additions and 97 deletions
|
|
@ -5,40 +5,58 @@
|
||||||
* @license LGPLv3 <http://www.gnu.org/licenses/lgpl-3.0.txt>
|
* @license LGPLv3 <http://www.gnu.org/licenses/lgpl-3.0.txt>
|
||||||
* @author Fabian Jakobs <fabian AT ajax DOT org>
|
* @author Fabian Jakobs <fabian AT ajax DOT org>
|
||||||
*/
|
*/
|
||||||
require.def("ace/VirtualRenderer",
|
require.def("ace/RenderLoop",
|
||||||
["ace/lib/oop",
|
["ace/lib/oop",
|
||||||
"ace/MEventEmitter"],
|
"ace/MEventEmitter"],
|
||||||
function(oop, MEventEmitter) {
|
function(oop, MEventEmitter) {
|
||||||
|
|
||||||
var RenderLoop = function(fps) {
|
var RenderLoop = function(fps) {
|
||||||
this.running = false;
|
|
||||||
this.interval = 1000 / fps;
|
this.interval = 1000 / fps;
|
||||||
}
|
};
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
|
||||||
oop.implement(this, MEventEmitter);
|
oop.implement(this, MEventEmitter);
|
||||||
|
|
||||||
this.start = function() {
|
if (window.mozRequestAnimationFrame) {
|
||||||
var _self = this;
|
|
||||||
this.stop();
|
|
||||||
|
|
||||||
this.running = true;
|
|
||||||
this.$timer = setTimeout(onTimeout, 0);
|
|
||||||
|
|
||||||
function onTimeout() {
|
this.start = function() {
|
||||||
var start = new Date();
|
var _self = this;
|
||||||
_self.$dispatchEvent("tick");
|
this.stop();
|
||||||
var end = new Date();
|
|
||||||
var timeout = Math.max(10, _self.interval - (end - start));
|
this.$onTimeout = function() {
|
||||||
this.$timer = setTimeout(onTimeout, timeout);
|
_self.$dispatchEvent("tick");
|
||||||
}
|
window.mozRequestAnimationFrame();
|
||||||
};
|
}
|
||||||
|
window.addEventListener("MozBeforePaint", this.$onTimeout, false);
|
||||||
|
window.mozRequestAnimationFrame();
|
||||||
|
};
|
||||||
|
|
||||||
this.stop = function() {
|
this.stop = function() {
|
||||||
this.running = false;
|
window.removeEventListener("MozBeforePaint", this.$onTimeout, false);
|
||||||
if (this.$timer)
|
}
|
||||||
clearTimeout(this.$timer);
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
this.start = function() {
|
||||||
|
var _self = this;
|
||||||
|
this.stop();
|
||||||
|
|
||||||
|
this.$timer = setTimeout(onTimeout, 0);
|
||||||
|
|
||||||
|
function onTimeout() {
|
||||||
|
var start = new Date();
|
||||||
|
_self.$dispatchEvent("tick");
|
||||||
|
var end = new Date();
|
||||||
|
var timeout = Math.max(10, _self.interval - (end - start));
|
||||||
|
this.$timer = setTimeout(onTimeout, timeout);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.stop = function() {
|
||||||
|
if (this.$timer)
|
||||||
|
clearTimeout(this.$timer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}).call(RenderLoop.prototype);
|
}).call(RenderLoop.prototype);
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,11 @@ require.def("ace/VirtualRenderer",
|
||||||
"ace/layer/Text",
|
"ace/layer/Text",
|
||||||
"ace/layer/Cursor",
|
"ace/layer/Cursor",
|
||||||
"ace/ScrollBar",
|
"ace/ScrollBar",
|
||||||
|
"ace/RenderLoop",
|
||||||
"ace/MEventEmitter"
|
"ace/MEventEmitter"
|
||||||
], function(oop, lang, dom, event, GutterLayer, MarkerLayer, TextLayer, CursorLayer, ScrollBar, MEventEmitter) {
|
], function(
|
||||||
|
oop, lang, dom, event, GutterLayer, MarkerLayer, TextLayer,
|
||||||
|
CursorLayer, ScrollBar, RenderLoop, MEventEmitter) {
|
||||||
|
|
||||||
var VirtualRenderer = function(container) {
|
var VirtualRenderer = function(container) {
|
||||||
this.container = container;
|
this.container = container;
|
||||||
|
|
@ -58,23 +61,30 @@ var VirtualRenderer = function(container) {
|
||||||
column : 0
|
column : 0
|
||||||
};
|
};
|
||||||
|
|
||||||
this.$updatePrintMargin();
|
|
||||||
this.onResize();
|
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
this.$textLayer.addEventListener("changeCharaterSize", function() {
|
this.$textLayer.addEventListener("changeCharaterSize", function() {
|
||||||
self.characterWidth = textLayer.getCharacterWidth();
|
self.characterWidth = textLayer.getCharacterWidth();
|
||||||
self.lineHeight = textLayer.getLineHeight();
|
self.lineHeight = textLayer.getLineHeight();
|
||||||
self.onResize();
|
|
||||||
|
this.$changes = this.$changes | this.CHANGE_FULL;
|
||||||
});
|
});
|
||||||
event.addListener(this.$gutter, "click", lang.bind(this.$onGutterClick, this));
|
event.addListener(this.$gutter, "click", lang.bind(this.$onGutterClick, this));
|
||||||
event.addListener(this.$gutter, "dblclick", lang.bind(this.$onGutterClick, this));
|
event.addListener(this.$gutter, "dblclick", lang.bind(this.$onGutterClick, this));
|
||||||
|
|
||||||
this.$changes = 0;
|
|
||||||
this.$size = {
|
this.$size = {
|
||||||
width: 0,
|
width: 0,
|
||||||
height: 0
|
height: 0,
|
||||||
|
scrollerHeight: 0,
|
||||||
|
scrollerWidth: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.$updatePrintMargin();
|
||||||
|
|
||||||
|
this.loop = new RenderLoop(50);
|
||||||
|
this.loop.on("tick", lang.bind(this.$renderChanges, this));
|
||||||
|
this.loop.start();
|
||||||
|
|
||||||
|
this.$changes = this.CHANGE_FULL;
|
||||||
};
|
};
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
|
@ -149,9 +159,7 @@ var VirtualRenderer = function(container) {
|
||||||
this.scrollBar.setHeight(height);
|
this.scrollBar.setHeight(height);
|
||||||
|
|
||||||
if (this.doc) {
|
if (this.doc) {
|
||||||
this.$updateScrollBar();
|
|
||||||
this.scrollToY(this.getScrollTop());
|
this.scrollToY(this.getScrollTop());
|
||||||
|
|
||||||
this.$changes = this.$changes | this.CHANGE_FULL;
|
this.$changes = this.$changes | this.CHANGE_FULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -164,6 +172,9 @@ var VirtualRenderer = function(container) {
|
||||||
this.scroller.style.left = gutterWidth + "px";
|
this.scroller.style.left = gutterWidth + "px";
|
||||||
this.scroller.style.width = Math.max(0, width - gutterWidth - this.scrollBar.getWidth()) + "px";
|
this.scroller.style.width = Math.max(0, width - gutterWidth - this.scrollBar.getWidth()) + "px";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.$size.scrollerWidth = this.scroller.clientWidth;
|
||||||
|
this.$size.scrollerHeight = this.scroller.clientHeight;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.setTokenizer = function(tokenizer) {
|
this.setTokenizer = function(tokenizer) {
|
||||||
|
|
@ -257,40 +268,87 @@ var VirtualRenderer = function(container) {
|
||||||
this.scrollBar.setScrollTop(this.scrollTop);
|
this.scrollBar.setScrollTop(this.scrollTop);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.$updateLines = function(firstRow, lastRow) {
|
this.$renderChanges = function() {
|
||||||
var layerConfig = this.layerConfig;
|
if (!this.$changes)
|
||||||
|
return;
|
||||||
|
|
||||||
// if the update changes the width of the document do a full redraw
|
// text, scrolling and resize changes can cause the view port size to change
|
||||||
if (layerConfig.width != this.$getLongestLine())
|
if (!this.layerConfig ||
|
||||||
return this.$draw(false);
|
this.$changes & this.CHANGE_FULL ||
|
||||||
|
this.$changes & this.CHANGE_SIZE ||
|
||||||
if (firstRow > layerConfig.lastRow + 1) { return; }
|
this.$changes & this.CHANGE_TEXT ||
|
||||||
if (lastRow < layerConfig.firstRow) { return; }
|
this.$changes & this.CHANGE_LINES ||
|
||||||
|
this.$changes & this.CHANGE_SCROLL
|
||||||
// if the last row is unknown -> redraw everything
|
)
|
||||||
if (lastRow === undefined) {
|
this.$computeLayerConfig();
|
||||||
this.$draw();
|
|
||||||
|
// full
|
||||||
|
if (this.$changes & this.CHANGE_FULL) {
|
||||||
|
this.$changes = 0;
|
||||||
|
this.$textLayer.update(this.layerConfig);
|
||||||
|
this.$gutterLayer.update(this.layerConfig);
|
||||||
|
this.$markerLayer.update(this.layerConfig);
|
||||||
|
this.$cursorLayer.update(this.layerConfig);
|
||||||
|
this.$updateScrollBar();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// scrolling
|
||||||
|
if (this.$changes & this.CHANGE_SCROLL) {
|
||||||
|
if (this.$changes & this.CHANGE_TEXT || this.$changes & this.CHANGE_LINES) {
|
||||||
|
this.$textLayer.scrollLines(this.layerConfig);
|
||||||
|
this.$gutterLayer.update(this.layerConfig);
|
||||||
|
this.$markerLayer.update(this.layerConfig);
|
||||||
|
this.$cursorLayer.update(this.layerConfig);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.$textLayer.update(this.layerConfig);
|
||||||
|
this.$gutterLayer.update(this.layerConfig);
|
||||||
|
this.$markerLayer.update(this.layerConfig);
|
||||||
|
this.$cursorLayer.update(this.layerConfig);
|
||||||
|
}
|
||||||
|
this.$updateScrollBar();
|
||||||
|
this.$changes = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.$changes & this.CHANGE_TEXT) {
|
||||||
|
this.$textLayer.update(this.layerConfig);
|
||||||
|
this.$gutterLayer.update(this.layerConfig);
|
||||||
|
}
|
||||||
|
else if (this.$changes & this.CHANGE_LINES) {
|
||||||
|
this.$updateLines();
|
||||||
|
}
|
||||||
|
else if (this.$changes & this.CHANGE_SCROLL) {
|
||||||
|
this.$textLayer.scrollLines(this.layerConfig);
|
||||||
|
this.$gutterLayer.update(this.layerConfig);
|
||||||
|
} if (this.$changes & this.CHANGE_GUTTER) {
|
||||||
|
this.$gutterLayer.update(this.layerConfig);
|
||||||
|
}
|
||||||
|
|
||||||
// else update only the changed rows
|
if (this.$changes & this.CHANGE_CURSOR)
|
||||||
this.$textLayer.updateLines(layerConfig, firstRow, lastRow);
|
this.$cursorLayer.update(this.layerConfig);
|
||||||
|
|
||||||
|
if (this.$changes & this.CHANGE_MARKER) {
|
||||||
|
this.$markerLayer.update(this.layerConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.$changes & this.CHANGE_SIZE)
|
||||||
|
this.$updateScrollBar();
|
||||||
|
|
||||||
|
this.$changes = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.$draw = function(scrollOnly) {
|
this.$computeLayerConfig = function() {
|
||||||
//var start = new Date();
|
|
||||||
|
|
||||||
var lines = this.lines;
|
|
||||||
|
|
||||||
var offset = this.scrollTop % this.lineHeight;
|
var offset = this.scrollTop % this.lineHeight;
|
||||||
var minHeight = this.scroller.clientHeight + offset;
|
var minHeight = this.$size.scrollerHeight + this.lineHeight;
|
||||||
|
|
||||||
var longestLine = this.$getLongestLine();
|
var longestLine = this.$getLongestLine();
|
||||||
var widthChanged = this.layerConfig && (this.layerConfig.width != longestLine);
|
var widthChanged = !this.layerConfig ? true : (this.layerConfig.width != longestLine);
|
||||||
|
|
||||||
var lineCount = Math.ceil(minHeight / this.lineHeight);
|
var lineCount = Math.ceil(minHeight / this.lineHeight);
|
||||||
var firstRow = Math.round((this.scrollTop - offset) / this.lineHeight);
|
var firstRow = Math.round((this.scrollTop - offset) / this.lineHeight);
|
||||||
var lastRow = Math.min(lines.length, firstRow + lineCount) - 1;
|
var lastRow = Math.min(this.lines.length, firstRow + lineCount) - 1;
|
||||||
|
|
||||||
var layerConfig = this.layerConfig = {
|
var layerConfig = this.layerConfig = {
|
||||||
width : longestLine,
|
width : longestLine,
|
||||||
|
|
@ -298,40 +356,51 @@ var VirtualRenderer = function(container) {
|
||||||
lastRow : lastRow,
|
lastRow : lastRow,
|
||||||
lineHeight : this.lineHeight,
|
lineHeight : this.lineHeight,
|
||||||
characterWidth : this.characterWidth,
|
characterWidth : this.characterWidth,
|
||||||
minHeight : minHeight,
|
minHeight : minHeight
|
||||||
scrollOnly: !!scrollOnly
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.content.style.marginTop = (-offset) + "px";
|
|
||||||
this.content.style.height = minHeight + "px";
|
|
||||||
|
|
||||||
for ( var i = 0; i < this.layers.length; i++) {
|
for ( var i = 0; i < this.layers.length; i++) {
|
||||||
var layer = this.layers[i];
|
var layer = this.layers[i];
|
||||||
|
|
||||||
if (widthChanged) {
|
if (widthChanged) {
|
||||||
var style = layer.element.style;
|
var style = layer.element.style;
|
||||||
style.width = longestLine + "px";
|
style.width = longestLine + "px";
|
||||||
}
|
}
|
||||||
|
|
||||||
layer.update(layerConfig);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.$gutterLayer.element.style.marginTop = (-offset) + "px";
|
this.$gutterLayer.element.style.marginTop = (-offset) + "px";
|
||||||
this.$gutterLayer.update(layerConfig);
|
this.content.style.marginTop = (-offset) + "px";
|
||||||
|
this.content.style.height = minHeight + "px";
|
||||||
|
};
|
||||||
|
|
||||||
//console.log("compute", new Date() - start, "ms")
|
this.$updateLines = function() {
|
||||||
this.$updateScrollBar();
|
var firstRow = this.$updateLines.firstRow;
|
||||||
//console.log("compute+render", new Date() - start, "ms")
|
var lastRow = this.$updateLines.lastRow;
|
||||||
|
|
||||||
|
var layerConfig = this.layerConfig;
|
||||||
|
|
||||||
|
// if the update changes the width of the document do a full redraw
|
||||||
|
if (layerConfig.width != this.$getLongestLine())
|
||||||
|
return this.$textLayer.update(layerConfig);
|
||||||
|
|
||||||
|
if (firstRow > layerConfig.lastRow + 1) { return; }
|
||||||
|
if (lastRow < layerConfig.firstRow) { return; }
|
||||||
|
|
||||||
|
// if the last row is unknown -> redraw everything
|
||||||
|
if (lastRow === undefined)
|
||||||
|
return this.$textLayer.update(layerConfig);
|
||||||
|
|
||||||
|
// else update only the changed rows
|
||||||
|
this.$textLayer.updateLines(layerConfig, firstRow, lastRow);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.$getLongestLine = function() {
|
this.$getLongestLine = function() {
|
||||||
var charCount = this.doc.getScreenWidth();
|
var charCount = this.doc.getScreenWidth();
|
||||||
if (this.$showInvisibles)
|
if (this.$showInvisibles)
|
||||||
charCount += 1;
|
charCount += 1;
|
||||||
|
|
||||||
return Math.max(this.scroller.clientWidth, Math.round(charCount * this.characterWidth));
|
return Math.max(this.$size.scrollerWidth, Math.round(charCount * this.characterWidth));
|
||||||
};
|
};
|
||||||
|
|
||||||
this.addMarker = function(range, clazz, type) {
|
this.addMarker = function(range, clazz, type) {
|
||||||
return this.$markerLayer.addMarker(range, clazz, type);
|
return this.$markerLayer.addMarker(range, clazz, type);
|
||||||
this.$changes = this.$changes | this.CHANGE_MARKER;
|
this.$changes = this.$changes | this.CHANGE_MARKER;
|
||||||
|
|
@ -370,19 +439,19 @@ var VirtualRenderer = function(container) {
|
||||||
this.scrollToY(top);
|
this.scrollToY(top);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.getScrollTop() + this.scroller.clientHeight < top
|
if (this.getScrollTop() + this.$size.scrollerHeight < top
|
||||||
+ this.lineHeight) {
|
+ this.lineHeight) {
|
||||||
this.scrollToY(top + this.lineHeight - this.scroller.clientHeight);
|
this.scrollToY(top + this.lineHeight - this.$size.scrollerHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.scroller.scrollLeft > left) {
|
if (this.scroller.scrollLeft > left) {
|
||||||
this.scroller.scrollLeft = left;
|
this.scroller.scrollLeft = left;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.scroller.scrollLeft + this.scroller.clientWidth < left
|
if (this.scroller.scrollLeft + this.$size.scrollerWidth < left
|
||||||
+ this.characterWidth) {
|
+ this.characterWidth) {
|
||||||
this.scroller.scrollLeft = Math.round(left + this.characterWidth
|
this.scroller.scrollLeft = Math.round(left + this.characterWidth
|
||||||
- this.scroller.clientWidth);
|
- this.$size.scrollerWidth);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -399,13 +468,11 @@ var VirtualRenderer = function(container) {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.scrollToY = function(scrollTop) {
|
this.scrollToY = function(scrollTop) {
|
||||||
var maxHeight = this.lines.length * this.lineHeight - this.scroller.clientHeight;
|
var maxHeight = this.lines.length * this.lineHeight - this.$size.scrollerHeight;
|
||||||
var scrollTop = Math.max(0, Math.min(maxHeight, scrollTop));
|
var scrollTop = Math.max(0, Math.min(maxHeight, scrollTop));
|
||||||
|
|
||||||
if (this.scrollTop !== scrollTop) {
|
if (this.scrollTop !== scrollTop) {
|
||||||
this.scrollTop = scrollTop;
|
this.scrollTop = scrollTop;
|
||||||
this.$updateScrollBar();
|
|
||||||
|
|
||||||
this.$changes = this.$changes | this.CHANGE_SCROLL;
|
this.$changes = this.$changes | this.CHANGE_SCROLL;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -69,15 +69,27 @@ var Cursor = function(parentEl) {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.getPixelPosition = function() {
|
this.getPixelPosition = function() {
|
||||||
return this.pixelPos || {
|
if (!this.config || !this.position) {
|
||||||
left : 0,
|
return {
|
||||||
top : 0
|
left : 0,
|
||||||
|
top : 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var cursorLeft = Math.round(this.position.column * this.config.characterWidth);
|
||||||
|
var cursorTop = this.position.row * this.config.lineHeight;
|
||||||
|
|
||||||
|
return {
|
||||||
|
left : cursorLeft,
|
||||||
|
top : cursorTop
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
this.update = function(config) {
|
this.update = function(config) {
|
||||||
if (!this.position)
|
if (!this.position)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
this.config = config;
|
||||||
|
|
||||||
var cursorLeft = Math.round(this.position.column * config.characterWidth);
|
var cursorLeft = Math.round(this.position.column * config.characterWidth);
|
||||||
var cursorTop = this.position.row * config.lineHeight;
|
var cursorTop = this.position.row * config.lineHeight;
|
||||||
|
|
|
||||||
|
|
@ -126,12 +126,16 @@ var Text = function(parentEl) {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
this.$scrollLines = function(oldConfig, config) {
|
this.scrollLines = function(config) {
|
||||||
if (oldConfig.lastRow < config.firstRow)
|
this.$computeTabString();
|
||||||
return this.$fullUpdate(config);
|
var oldConfig = this.config;
|
||||||
|
this.config = config;
|
||||||
|
|
||||||
|
if (!oldConfig || oldConfig.lastRow < config.firstRow)
|
||||||
|
return this.update(config);
|
||||||
|
|
||||||
if (config.lastRow < oldConfig.firstRow)
|
if (config.lastRow < oldConfig.firstRow)
|
||||||
return this.$fullUpdate(config);
|
return this.update(config);
|
||||||
|
|
||||||
var el = this.element;
|
var el = this.element;
|
||||||
|
|
||||||
|
|
@ -175,21 +179,8 @@ var Text = function(parentEl) {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.update = function(config) {
|
this.update = function(config) {
|
||||||
if (!config.minHeight)
|
|
||||||
return;
|
|
||||||
|
|
||||||
this.$computeTabString();
|
this.$computeTabString();
|
||||||
|
|
||||||
if (this.config && config.scrollOnly) {
|
|
||||||
this.$scrollLines(this.config, config);
|
|
||||||
} else {
|
|
||||||
this.$fullUpdate(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.config = config;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.$fullUpdate = function(config) {
|
|
||||||
var html = [];
|
var html = [];
|
||||||
for ( var i = config.firstRow; i <= config.lastRow; i++) {
|
for ( var i = config.firstRow; i <= config.lastRow; i++) {
|
||||||
html.push("<div class='ace_line' style='height:" + this.$characterSize.height + "px;", "width:",
|
html.push("<div class='ace_line' style='height:" + this.$characterSize.height + "px;", "width:",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue