From a23ef8e775856e2ae03b27d34a02eaa4ec615d6b Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Wed, 7 Apr 2010 12:19:44 +0200 Subject: [PATCH] split layers into separate files --- CursorLayer.js | 58 +++++++++++ Editor.js | 4 +- MarkerLayer.js | 97 ++++++++++++++++++ TextDocument.js | 11 +- TextLayer.js | 78 ++++++++++++++ VirtualRenderer.js | 248 +++++++-------------------------------------- editor.css | 5 + editor.html | 3 + 8 files changed, 286 insertions(+), 218 deletions(-) create mode 100644 CursorLayer.js create mode 100644 MarkerLayer.js create mode 100644 TextLayer.js diff --git a/CursorLayer.js b/CursorLayer.js new file mode 100644 index 00000000..b7efcbb6 --- /dev/null +++ b/CursorLayer.js @@ -0,0 +1,58 @@ +function CursorLayer(parentEl) +{ + this.element = document.createElement("div"); + this.element.className = "cursor-layer"; + parentEl.appendChild(this.element); + + this.cursor = document.createElement("div"); + this.cursor.className = "cursor"; + + this.isVisible = false; +} + +CursorLayer.prototype.setCursor = function(position) +{ + this.position = { + row: position.row, + column: position.column + }; +}; + +CursorLayer.prototype.hideCursor = function() +{ + this.isVisible = false; + if (this.cursor.parentNode) { + this.cursor.parentNode.removeChild(this.cursor); + } +}; + +CursorLayer.prototype.showCursor = function() +{ + this.isVisible = true; + this.element.appendChild(this.cursor); +}; + +CursorLayer.prototype.getPixelPosition = function() { + return this.pixelPos || {left: 0, top:0}; +} + +CursorLayer.prototype.update = function(config) +{ + if (!this.position) return; + + var cursorLeft = this.position.column * config.characterWidth; + var cursorTop = this.position.row * config.lineHeight; + + this.pixelPos = { + left: cursorLeft, + top: cursorTop + }; + + this.cursor.style.left = cursorLeft + "px"; + this.cursor.style.top = (cursorTop - (config.firstRow * config.lineHeight)) + "px"; + this.cursor.style.height = config.lineHeight + "px"; + + if (this.isVisible) { + this.element.appendChild(this.cursor); + } +}; \ No newline at end of file diff --git a/Editor.js b/Editor.js index 47274acf..7b827fa5 100644 --- a/Editor.js +++ b/Editor.js @@ -277,7 +277,7 @@ Editor.prototype = }; capture(this.container, onMouseSelection, onMouseSelectionEnd); - var timerId = setInterval(onSelectionInterval, 100); + var timerId = setInterval(onSelectionInterval, 20); return preventDefault(e); }, @@ -437,6 +437,8 @@ Editor.prototype = setSelectionAnchor : function(row, column) { + this.clearSelection(); + this.selectionAnchor = { row: Math.min(this.doc.getLength()-1, Math.max(0, row)), column: Math.min(this.doc.getLine(this.cursor.row).length, Math.max(0, column)) diff --git a/MarkerLayer.js b/MarkerLayer.js new file mode 100644 index 00000000..4bc21136 --- /dev/null +++ b/MarkerLayer.js @@ -0,0 +1,97 @@ +function MarkerLayer(parentEl) +{ + this.element = document.createElement("div"); + this.element.className = "markers"; + parentEl.appendChild(this.element); + + this.markers = {}; + this._markerId = 1; +} + +MarkerLayer.prototype.addMarker = function(range, clazz) +{ + var id = this._markerId++; + this.markers[id] = { + range: range, + type: "line", + clazz: clazz + }; + + this.update(); + return id; +}; + +MarkerLayer.prototype.removeMarker = function(markerId) +{ + var marker = this.markers[markerId]; + if (marker) { + delete(this.markers[markerId]); + this.update(); + } +}; + +MarkerLayer.prototype.update = function(config) +{ + var config = config || this.config; + if (!config) return; + + this.config = config; + + var html = []; + for (var key in this.markers) + { + var marker = this.markers[key]; + var range = marker.range; + + if (range.start.row !== range.end.row) + { + if (range.start.row >= config.firstRow && range.start.row <= config.lastRow) + { + html.push( + "
" + ); + } + + if (range.end.row >= config.firstRow && range.end.row <= config.lastRow) + { + html.push( + "
" + ); + }; + + for (var row=range.start.row+1; row < range.end.row; row++) + { + if (row >= config.firstRow && row <= config.lastRow) + { + html.push( + "
" + ); + } + }; + } + else + { + if (range.start.row >= config.firstRow && range.start.row <= config.lastRow) + { + html.push( + "
" + ); + } + } + } + this.element.innerHTML = html.join(""); +}; \ No newline at end of file diff --git a/TextDocument.js b/TextDocument.js index a5591285..91b52c4b 100644 --- a/TextDocument.js +++ b/TextDocument.js @@ -123,11 +123,12 @@ TextDocument.prototype = if (range.start.row == range.end.row) { return this.lines[range.start.row].substring(range.start.column, range.end.column); } else { - return ( - this.lines[range.start.row].substring(range.start.column) + - this.lines.slice(range.start.row+1, range.end.row+1) + - this.lines[range.end.row].substring(0, range.end.column) - ); + var lines = []; + lines.push(this.lines[range.start.row].substring(range.start.column)); + lines.push.apply(lines, this.lines.slice(range.start.row+1, range.end.row+1)); + lines.push(this.lines[range.end.row].substring(0, range.end.column)); + + return lines.join("\n"); } }, diff --git a/TextLayer.js b/TextLayer.js new file mode 100644 index 00000000..5d7f65ee --- /dev/null +++ b/TextLayer.js @@ -0,0 +1,78 @@ +function TextLayer(parentEl) +{ + this.element = document.createElement("div"); + this.element.className = "canvas"; + parentEl.appendChild(this.element); + + this._measureSizes(); +} + +TextLayer.prototype.setDocument = function(doc) { + this.lines = doc.lines; + this.doc = doc; +}; + +TextLayer.prototype.getLineHeight = function() { + return this.lineHeight; +}; + +TextLayer.prototype.getCharacterWidth = function() { + return this.characterWidth; +}; + +TextLayer.prototype._measureSizes = function() +{ + var measureNode = document.createElement("div"); + var style = measureNode.style; + style.width = style.height = "auto"; + style.left = style.top = "-1000px"; + style.visibility = "hidden"; + style.position = "absolute"; + style.overflow = "visible"; + + measureNode.innerHTML = "X
X"; + this.element.appendChild(measureNode); + + this.lineHeight = Math.round(measureNode.offsetHeight / 2); + this.characterWidth = measureNode.offsetWidth; + + this.element.removeChild(measureNode); +}; + +TextLayer.prototype.update = function(config) +{ + var html = []; + for (var i=config.firstRow; i" + ); + this.renderLine(html, i), + html.push(""); + } + + this.element.innerHTML = html.join(""); +}; + +TextLayer.prototype.renderLine = function(stringBuilder, row) +{ + var tokens = this.doc.getLineTokens(row); + for (var i=0; i < tokens.length; i++) + { + var token = tokens[i]; + + var output = token.value. + replace(/&/g, "&"). + replace(/", output, ""); + } else { + stringBuilder.push(output); + } + }; +}; \ No newline at end of file diff --git a/VirtualRenderer.js b/VirtualRenderer.js index 3839ab50..00682073 100644 --- a/VirtualRenderer.js +++ b/VirtualRenderer.js @@ -3,75 +3,35 @@ function VirtualRenderer(containerId) this.container = document.getElementById(containerId); this.container.className += "editor"; - this.canvas = document.createElement("div"); - this.canvas.className = "canvas"; - this.container.appendChild(this.canvas); + var textLayer = this.textLayer = new TextLayer(this.container); + this.canvas = textLayer.element; - this._measureSizes(); + this.characterWidth = textLayer.getCharacterWidth(); + this.lineHeight = textLayer.getLineHeight(); - this.composition = document.createElement("div"); - this.composition.className = "composition"; - this.composition.style.height = this.lineHeight + "px"; + this.cursorLayer = new CursorLayer(this.container); + this.markerLayer = new MarkerLayer(this.container); - this.cursor = document.createElement("div"); - this.cursor.className = "cursor"; - this.cursor.style.height = this.lineHeight + "px"; + this.layers = [this.markerLayer, textLayer, this.cursorLayer]; - this.markers = {}; - this._markerId = 1; - this.scrollTop = 0; - this.firstRow = 0; this.cursorPos = { row: 0, column: 0 }; - - this.layers = []; - this.layers.push({ - element: this.canvas, - update: this.updateLines - }); - - this.markerEl = document.createElement("div"); - this.markerEl.className = "markers"; - this.container.appendChild(this.markerEl); - - this.layers.push({ - element: this.markerEl, - update: this.updateMarkers - }); } -VirtualRenderer.prototype.setDocument = function(doc) { +VirtualRenderer.prototype.setDocument = function(doc) +{ this.lines = doc.lines; - this.doc = doc; + this.textLayer.setDocument(doc); }; VirtualRenderer.prototype.getContainerElement = function() { return this.container; }; -VirtualRenderer.prototype._measureSizes = function() -{ - var measureNode = document.createElement("div"); - var style = measureNode.style; - style.width = style.height = "auto"; - style.left = style.top = "-1000px"; - style.visibility = "hidden"; - style.position = "absolute"; - style.overflow = "visible"; - - measureNode.innerHTML = "X
X"; - this.canvas.appendChild(measureNode); - - this.lineHeight = Math.round(measureNode.offsetHeight / 2); - this.characterWidth = measureNode.offsetWidth; - - this.canvas.removeChild(measureNode); -}; - VirtualRenderer.prototype.getLongestLineWidth = function(lines) { var longestLine = this.container.clientWidth; @@ -90,9 +50,17 @@ VirtualRenderer.prototype.draw = function() var longestLine = this.getLongestLineWidth(lines); var lineCount = Math.ceil(minHeight / this.lineHeight); - this.firstRow = 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); + var layerConfig = this.layerConfig = { + width: longestLine, + firstRow: firstRow, + lastRow: lastRow, + lineHeight: this.lineHeight, + characterWidth: this.characterWidth + }; + for (var i=0; i < this.layers.length; i++) { var layer = this.layers[i]; @@ -102,171 +70,38 @@ VirtualRenderer.prototype.draw = function() style.height = minHeight + "px"; style.width = longestLine + "px"; - layer.update.call(this, layer.element, firstRow, lastRow, longestLine); + layer.update(layerConfig); }; - - this.updateCursor(this.cursorPos); } -VirtualRenderer.prototype.updateLines = function(element, firstRow, lastRow, width) -{ - var html = []; - for (var i=firstRow; i" - ); - this.renderLine(html, i), - html.push(""); - } - - element.innerHTML = html.join(""); +VirtualRenderer.prototype.addMarker = function(range, clazz) { + return this.markerLayer.addMarker(range, clazz); }; -VirtualRenderer.prototype.renderLine = function(stringBuilder, row) -{ - var tokens = this.doc.getLineTokens(row); - for (var i=0; i < tokens.length; i++) - { - var token = tokens[i]; - - var output = token.value. - replace(/&/g, "&"). - replace(/", output, ""); - } else { - stringBuilder.push(output); - } - }; +VirtualRenderer.prototype.removeMarker = function(markerId) { + this.markerLayer.removeMarker(markerId); }; - -VirtualRenderer.prototype.updateMarkers = function(element, firstRow, lastRow, width) +VirtualRenderer.prototype.updateCursor = function(position) { - var html = []; - for (var key in this.markers) - { - var marker = this.markers[key]; - var range = marker.range; - - if (range.start.row !== range.end.row) - { - if (range.start.row >= firstRow && range.start.row <= lastRow) - { - html.push( - "
" - ); - } - - if (range.end.row >= firstRow && range.end.row <= lastRow) - { - html.push( - "
" - ); - }; - - for (var row=range.start.row+1; row < range.end.row; row++) - { - if (row >= firstRow && row <= lastRow) - { - html.push( - "
" - ); - } - }; - } - else - { - if (range.start.row >= firstRow && range.start.row <= lastRow) - { - html.push( - "
" - ); - } - } - } - element.innerHTML = html.join(""); + this.cursorLayer.setCursor(position); + this.cursorLayer.update(this.layerConfig); }; -VirtualRenderer.prototype.addMarker = function(range, clazz) -{ - var id = this._markerId++; - this.markers[id] = { - range: range, - type: "line", - clazz: clazz - }; - - this.draw(); - - return id; +VirtualRenderer.prototype.hideCursor = function() { + this.cursorLayer.hideCursor(); }; -VirtualRenderer.prototype.removeMarker = function(markerId) -{ - var marker = this.markers[markerId]; - if (marker) { - delete(this.markers[markerId]); - this.draw(); - } -}; - -VirtualRenderer.prototype.updateCursor = function(position) -{ - this.cursorPos = { - row: position.row, - column: position.column - } - - var left = this.cursorLeft = position.column * this.characterWidth; - var top = this.cursorTop = position.row * this.lineHeight; - - this.cursor.style.left = left + "px"; - this.cursor.style.top = (top - (this.firstRow * this.lineHeight)) + "px"; - - if (this.cursorVisible) { - this.canvas.appendChild(this.cursor); - } -}; - -VirtualRenderer.prototype.hideCursor = function() -{ - this.cursorVisible = true; - if (this.cursor.parentNode) { - this.cursor.parentNode.removeChild(this.cursor); - } -}; - -VirtualRenderer.prototype.showCursor = function() -{ - this.cursorVisible = true; - this.canvas.appendChild(this.cursor); +VirtualRenderer.prototype.showCursor = function() { + this.cursorLayer.showCursor(); }; VirtualRenderer.prototype.scrollCursorIntoView = function() { - var left = this.cursorLeft; - var top = this.cursorTop; + var pos = this.cursorLayer.getPixelPosition(); + + var left = pos.left + var top = pos.top; if (this.getScrollTop() > top) { this.scrollToY(top); @@ -321,22 +156,11 @@ VirtualRenderer.prototype.visualizeBlur = function() { this.container.className = "editor"; }; -VirtualRenderer.prototype.showComposition = function(position) -{ - setText(this.composition, ""); - - this.composition.style.left = (position.column * this.characterWidth+1) + "px"; - this.composition.style.top = (position.row * this.lineHeight+1) + "px"; - - this.container.appendChild(this.composition); +VirtualRenderer.prototype.showComposition = function(position) { }; VirtualRenderer.prototype.setCompositionText = function(text) { - setText(this.composition, text); }; VirtualRenderer.prototype.hideComposition = function() { - if (this.composition.parentNode) { - this.container.removeChild(this.composition); - } }; \ No newline at end of file diff --git a/editor.css b/editor.css index ceebf793..e571e121 100644 --- a/editor.css +++ b/editor.css @@ -58,6 +58,11 @@ z-index: 1; } +.editor .cursor-layer { + position: absolute; + z-index: 3; +} + .selection { position: absolute; background: rgba(77, 151, 255, 0.33); diff --git a/editor.html b/editor.html index dd7e4eed..f39218f8 100644 --- a/editor.html +++ b/editor.html @@ -21,6 +21,9 @@ + + +