ace/src/ace/VirtualRenderer.js
2010-09-20 19:40:53 +02:00

384 lines
11 KiB
JavaScript

require.def("ace/VirtualRenderer",
[
"ace/ace",
"ace/layer/Gutter",
"ace/layer/Marker",
"ace/layer/Text",
"ace/layer/Cursor",
"ace/ScrollBar",
"ace/MEventEmitter"
], function(ace, GutterLayer, MarkerLayer, TextLayer, CursorLayer, ScrollBar, MEventEmitter) {
var VirtualRenderer = function(container) {
this.container = container;
ace.addCssClass(this.container, "ace_editor");
this.scroller = document.createElement("div");
this.scroller.className = "ace_scroller";
this.container.appendChild(this.scroller);
this.$gutter = document.createElement("div");
this.$gutter.className = "ace_gutter";
this.container.appendChild(this.$gutter);
this.content = document.createElement("div");
this.content.style.position = "absolute";
this.scroller.appendChild(this.content);
this.$gutterLayer = new GutterLayer(this.$gutter);
this.$markerLayer = new MarkerLayer(this.content);
var textLayer = this.$textLayer = new TextLayer(this.content);
this.canvas = textLayer.element;
this.characterWidth = textLayer.getCharacterWidth();
this.lineHeight = textLayer.getLineHeight();
this.$cursorLayer = new CursorLayer(this.content);
this.layers = [ this.$markerLayer, textLayer, this.$cursorLayer ];
this.scrollBar = new ScrollBar(container);
this.scrollBar.addEventListener("scroll", ace.bind(this.onScroll, this));
this.scrollTop = 0;
this.cursorPos = {
row : 0,
column : 0
};
this.$drawCallbacks = [];
this.$updatePrintMargin();
this.onResize();
var self = this;
this.$textLayer.addEventListener("changeCharaterSize", function() {
self.characterWidth = textLayer.getCharacterWidth();
self.lineHeight = textLayer.getLineHeight();
self.onResize();
});
ace.addListener(this.$gutter, "click", ace.bind(this.$onGutterClick, this));
ace.addListener(this.$gutter, "dblclick", ace.bind(this.$onGutterClick, this));
};
(function() {
ace.implement(this, MEventEmitter);
this.setDocument = function(doc) {
this.lines = doc.lines;
this.doc = doc;
this.$cursorLayer.setDocument(doc);
this.$markerLayer.setDocument(doc);
this.$textLayer.setDocument(doc);
};
this.setTokenizer = function(tokenizer) {
this.$textLayer.setTokenizer(tokenizer);
};
this.$onGutterClick = function(e) {
var pageX = ace.getDocumentX(e);
var pageY = ace.getDocumentY(e);
var event = {
row: this.screenToTextCoordinates(pageX, pageY).row,
htmlEvent: e
};
var type = "gutter" + e.type;
this.$dispatchEvent(type, event);
};
this.$showInvisibles = true;
this.setShowInvisibles = function(showInvisibles) {
this.$showInvisibles = showInvisibles;
this.$textLayer.setShowInvisibles(showInvisibles);
};
this.getShowInvisibles = function() {
return this.showInvisibles;
};
this.$showPrintMargin = true;
this.setShowPrintMargin = function(showPrintMargin) {
this.$showPrintMargin = showPrintMargin;
this.$updatePrintMargin();
};
this.getShowPrintMargin = function() {
return this.$showPrintMargin;
};
this.$printMarginColumn = 80;
this.setPrintMarginColumn = function(showPrintMargin) {
this.$printMarginColumn = showPrintMargin;
this.$updatePrintMargin();
};
this.getPrintMarginColumn = function() {
return this.$printMarginColumn;
};
this.$updatePrintMargin = function() {
if (!this.$showPrintMargin && !this.$printMarginEl)
return;
if (!this.$printMarginEl) {
this.$printMarginEl = document.createElement("div");
this.$printMarginEl.className = "ace_printMargin";
this.content.insertBefore(this.$printMarginEl, this.$cursorLayer.element);
}
var style = this.$printMarginEl.style;
style.left = (this.characterWidth * this.$printMarginColumn) + "px";
style.visibility = this.$showPrintMargin ? "visible" : "hidden";
};
this.getContainerElement = function() {
return this.container;
};
this.getMouseEventTarget = function() {
return this.content;
};
this.getFirstVisibleRow = function() {
return this.layerConfig.firstRow || 0;
};
this.getLastVisibleRow = function() {
return this.layerConfig.lastRow || 0;
};
this.onResize = function()
{
var height = ace.getInnerHeight(this.container);
this.scroller.style.height = height + "px";
this.scrollBar.setHeight(height);
var width = ace.getInnerWidth(this.container);
var gutterWidth = this.$gutter.offsetWidth;
this.scroller.style.left = gutterWidth + "px";
this.scroller.style.width = Math.max(0, width - gutterWidth - this.scrollBar.getWidth()) + "px";
if (this.doc) {
this.$updateScrollBar();
this.scrollToY(this.getScrollTop());
this.draw();
}
};
this.onScroll = function(e) {
this.scrollToY(e.data);
};
this.$updateScrollBar = function() {
this.scrollBar.setInnerHeight(this.doc.getLength() * this.lineHeight);
this.scrollBar.setScrollTop(this.scrollTop);
};
this.updateLines = function(firstRow, lastRow) {
var layerConfig = this.layerConfig;
if (firstRow > layerConfig.lastRow + 1) { return; }
if (lastRow < layerConfig.firstRow) { return; }
// if the last row is unknown -> redraw everything
if (lastRow === undefined) {
this.draw();
return;
}
// else update only the changed rows
this.$textLayer.updateLines(layerConfig, firstRow, lastRow);
};
this.draw = function(scrollOnly, callback) {
if (this.$drawTimer) {
clearInterval(this.$drawTimer);
this.scrollOnly = this.scrollOnly && scrollOnly;
} else {
this.scollOnly = scrollOnly;
}
if (callback)
this.$drawCallbacks.push(callback);
var _self = this;
this.$drawTimer = setTimeout(function() {
_self.$draw(_self.scrollOnly);
for (var i=0; i<_self.$drawCallbacks.length; i++)
_self.$drawCallbacks[i]();
_self.$drawCallbacks = [];
delete _self.$drawTimer;
}, 0);
};
this.$draw = function(scrollOnly) {
//var start = new Date();
var lines = this.lines;
var offset = this.scrollTop % this.lineHeight;
var minHeight = this.scroller.clientHeight + offset;
var charCount = this.doc.getScreenWidth();
if (this.$showInvisibles)
charCount += 1;
var longestLine = Math.max(this.scroller.clientWidth, Math.round(charCount * this.characterWidth));
var lineCount = Math.ceil(minHeight / this.lineHeight);
var firstRow = Math.round((this.scrollTop - offset) / this.lineHeight);
var lastRow = Math.min(lines.length, firstRow + lineCount) - 1;
var layerConfig = this.layerConfig = {
width : longestLine,
firstRow : firstRow,
lastRow : lastRow,
lineHeight : this.lineHeight,
characterWidth : this.characterWidth,
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++) {
var layer = this.layers[i];
var style = layer.element.style;
style.width = longestLine + "px";
layer.update(layerConfig);
};
this.$gutterLayer.element.style.marginTop = (-offset) + "px";
this.$gutterLayer.update(layerConfig);
//console.log("compute", new Date() - start, "ms")
this.$updateScrollBar();
//console.log("compute+render", new Date() - start, "ms")
};
this.addMarker = function(range, clazz, type) {
return this.$markerLayer.addMarker(range, clazz, type);
};
this.removeMarker = function(markerId) {
this.$markerLayer.removeMarker(markerId);
};
this.setBreakpoints = function(rows) {
this.$gutterLayer.setBreakpoints(rows);
};
this.updateCursor = function(position, overwrite) {
this.$cursorLayer.setCursor(position, overwrite);
this.$cursorLayer.update(this.layerConfig);
};
this.hideCursor = function() {
this.$cursorLayer.hideCursor();
};
this.showCursor = function() {
this.$cursorLayer.showCursor();
};
this.scrollCursorIntoView = function() {
var pos = this.$cursorLayer.getPixelPosition();
var left = pos.left;
var top = pos.top;
if (this.getScrollTop() > top) {
this.scrollToY(top);
}
if (this.getScrollTop() + this.scroller.clientHeight < top
+ this.lineHeight) {
this.scrollToY(top + this.lineHeight - this.scroller.clientHeight);
}
if (this.scroller.scrollLeft > left) {
this.scroller.scrollLeft = left;
}
if (this.scroller.scrollLeft + this.scroller.clientWidth < left
+ this.characterWidth) {
this.scroller.scrollLeft = Math.round(left + this.characterWidth
- this.scroller.clientWidth);
}
},
this.getScrollTop = function() {
return this.scrollTop;
};
this.getScrollTopRow = function() {
return this.scrollTop / this.lineHeight;
};
this.scrollToRow = function(row) {
this.scrollToY(row * this.lineHeight);
};
this.scrollToY = function(scrollTop) {
var maxHeight = this.lines.length * this.lineHeight
- this.scroller.clientHeight;
var scrollTop = Math.max(0, Math.min(maxHeight, scrollTop));
if (this.scrollTop !== scrollTop) {
this.scrollTop = scrollTop;
this.$updateScrollBar();
this.draw(true);
}
};
this.scrollBy = function(deltaX, deltaY) {
deltaY && this.scrollToY(this.scrollTop + deltaY);
deltaX && (this.scroller.scrollLeft += deltaX);
};
this.screenToTextCoordinates = function(pageX, pageY) {
var canvasPos = this.scroller.getBoundingClientRect();
var col = Math.round((pageX + this.scroller.scrollLeft - canvasPos.left)
/ this.characterWidth);
var row = Math.floor((pageY + this.scrollTop - canvasPos.top)
/ this.lineHeight);
return {
row : row,
column : this.doc.screenToDocumentColumn(Math.max(0, Math.min(row, this.doc.getLength()-1)), col)
};
};
this.visualizeFocus = function() {
ace.addCssClass(this.container, "ace_focus");
};
this.visualizeBlur = function() {
ace.removeCssClass(this.container, "ace_focus");
};
this.showComposition = function(position) {
};
this.setCompositionText = function(text) {
};
this.hideComposition = function() {
};
}).call(VirtualRenderer.prototype);
return VirtualRenderer;
});