loleaflet/Makefile.am | 2 loleaflet/src/layer/vector/CircleMarker.js | 2 loleaflet/src/layer/vector/Path.Drag.js | 11 loleaflet/src/layer/vector/Path.Transform.SVG.js | 36 ++ loleaflet/src/layer/vector/Path.js | 137 +++++++++ loleaflet/src/layer/vector/Polygon.js | 4 loleaflet/src/layer/vector/Polyline.js | 2 loleaflet/src/layer/vector/Renderer.js | 67 ++++ loleaflet/src/layer/vector/SVG.js | 36 +- loleaflet/src/layer/vector/SVGGroup.js | 224 ++++++++++++--- loleaflet/src/layer/vector/SplitPanesRenderer.js | 59 ++++ loleaflet/src/layer/vector/SplitPanesSVG.js | 323 +++++++++++++++++++++++ 12 files changed, 815 insertions(+), 88 deletions(-)
New commits: commit a59076e1ca88a6f2c8c67ce45bf769e963cf0717 Author: Dennis Francis <[email protected]> AuthorDate: Tue Jul 7 14:29:54 2020 +0530 Commit: Dennis Francis <[email protected]> CommitDate: Wed Jul 8 16:57:44 2020 +0200 add split-panes support for overlay layer (This is only for svg renderer) There are separate svg DOM-nodes for each split-pane with view-box set appropriately. The L.Path based objects are shared for each split-pane, but there will be separate identical 'path' DOM-nodes for each svg container. This patch introduces L.SplitPanesRenderer/L.SplitPanesSVG (has same external api as L.Renderer/L.SVG). These are wrapper classes to host child renderers, one per split-pane and delegate calls to them appropriately. Change-Id: Id44e9a1312500e6b43cdd8e4f42e235b43d22772 Reviewed-on: https://gerrit.libreoffice.org/c/online/+/98354 Tested-by: Jenkins CollaboraOffice <[email protected]> Tested-by: Jenkins Reviewed-by: Dennis Francis <[email protected]> diff --git a/loleaflet/Makefile.am b/loleaflet/Makefile.am index 2b2dd22c8..589a95448 100644 --- a/loleaflet/Makefile.am +++ b/loleaflet/Makefile.am @@ -234,6 +234,8 @@ LOLEAFLET_JS =\ src/layer/vector/CircleMarker.js \ src/layer/vector/Circle.js \ src/layer/vector/SVG.js \ + src/layer/vector/SplitPanesRenderer.js \ + src/layer/vector/SplitPanesSVG.js \ src/layer/vector/Path.Transform.SVG.js \ src/core/Handler.js \ src/layer/vector/SVGGroup.js \ diff --git a/loleaflet/src/layer/vector/CircleMarker.js b/loleaflet/src/layer/vector/CircleMarker.js index 837471440..0e45bddf6 100644 --- a/loleaflet/src/layer/vector/CircleMarker.js +++ b/loleaflet/src/layer/vector/CircleMarker.js @@ -66,7 +66,7 @@ L.CircleMarker = L.Path.extend({ }, _empty: function () { - return this._radius && !this._renderer._bounds.intersects(this._pxBounds); + return this._radius && !this._renderer.intersectsBounds(this._pxBounds); } }); diff --git a/loleaflet/src/layer/vector/Path.Drag.js b/loleaflet/src/layer/vector/Path.Drag.js index 4ca7ff847..b1375aacf 100644 --- a/loleaflet/src/layer/vector/Path.Drag.js +++ b/loleaflet/src/layer/vector/Path.Drag.js @@ -72,9 +72,7 @@ L.Handler.PathDrag = L.Handler.extend(/** @lends L.Path.Drag.prototype */ { (this._path.options.className + ' ' + L.Handler.PathDrag.DRAGGING_CLS) : L.Handler.PathDrag.DRAGGING_CLS; - if (this._path._path) { - L.DomUtil.addClass(this._path._path, L.Handler.PathDrag.DRAGGING_CLS); - } + this._path.addClass(L.Handler.PathDrag.DRAGGING_CLS); }, /** @@ -85,9 +83,8 @@ L.Handler.PathDrag = L.Handler.extend(/** @lends L.Path.Drag.prototype */ { this._path.options.className = this._path.options.className .replace(new RegExp('\\s+' + L.Handler.PathDrag.DRAGGING_CLS), ''); - if (this._path._path) { - L.DomUtil.removeClass(this._path._path, L.Handler.PathDrag.DRAGGING_CLS); - } + + this._path.removeClass(L.Handler.PathDrag.DRAGGING_CLS); if (!this._path.options.manualDrag) { L.DomEvent.off(document, 'mousemove touchmove', this._onDrag, this); @@ -119,7 +116,7 @@ L.Handler.PathDrag = L.Handler.extend(/** @lends L.Path.Drag.prototype */ { this._matrix = [1, 0, 0, 1, 0, 0]; L.DomEvent.stop(evt.originalEvent); - L.DomUtil.addClass(this._path._renderer._container, 'leaflet-interactive'); + this._path._renderer.addContainerClass('leaflet-interactive'); if (!this._path.options.manualDrag) { L.DomEvent diff --git a/loleaflet/src/layer/vector/Path.Transform.SVG.js b/loleaflet/src/layer/vector/Path.Transform.SVG.js index fb76c9cf6..4abc4528a 100644 --- a/loleaflet/src/layer/vector/Path.Transform.SVG.js +++ b/loleaflet/src/layer/vector/Path.Transform.SVG.js @@ -4,7 +4,7 @@ L.SVG.include({ * Reset transform matrix */ _resetTransformPath: function(layer) { - layer._path.setAttributeNS(null, 'transform', ''); + layer.getPathNode(this).setAttributeNS(null, 'transform', ''); }, /** @@ -13,8 +13,40 @@ L.SVG.include({ * @param {Array.<Number>} matrix */ transformPath: function(layer, matrix) { - layer._path.setAttributeNS(null, 'transform', + layer.getPathNode(this).setAttributeNS(null, 'transform', 'matrix(' + matrix.join(' ') + ')'); } }); + +L.SplitPanesSVG.include({ + /** + * Reset transform matrix + */ + _resetTransformPath: function(layer) { + if (layer.options.fixed === true) { + this._childRenderers['fixed']._resetTransformPath(layer); + return; + } + + this._forEachPaneRenderer(function (paneRenderer) { + paneRenderer._resetTransformPath(layer); + }); + }, + + /** + * Applies matrix transformation to SVG + * @param {L.Path} layer + * @param {Array.<Number>} matrix + */ + transformPath: function(layer, matrix) { + if (layer.options.fixed === true) { + this._childRenderers['fixed'].transformPath(layer, matrix); + return; + } + + this._forEachPaneRenderer(function (paneRenderer) { + paneRenderer.transformPath(layer, matrix); + }); + } +}); diff --git a/loleaflet/src/layer/vector/Path.js b/loleaflet/src/layer/vector/Path.js index 51665ba9d..1c06e53ef 100644 --- a/loleaflet/src/layer/vector/Path.js +++ b/loleaflet/src/layer/vector/Path.js @@ -21,10 +21,12 @@ L.Path = L.Layer.extend({ fillRule: 'evenodd', // className: '' - interactive: true + interactive: true, + fixed: false, }, onAdd: function () { + this._pathNodeCollection = new L.Path.PathNodeCollection(); this._renderer = this._map.getRenderer(this); this._renderer._initPath(this); this._reset(); @@ -33,6 +35,7 @@ L.Path = L.Layer.extend({ onRemove: function () { this._renderer._removePath(this); + this._pathNodeCollection.clear(); }, getEvents: function () { @@ -80,5 +83,135 @@ L.Path = L.Layer.extend({ _clickTolerance: function () { // used when doing hit detection for Canvas layers return (this.options.stroke ? this.options.weight / 2 : 0) + (L.Browser.touch ? 10 : 0); - } + }, + + addPathNode: function (pathNode, actualRenderer) { + + this._path = undefined; + + if (!this._pathNodeCollection) { + this._pathNodeCollection = new L.Path.PathNodeCollection(); + } + + this._pathNodeCollection.add(new L.Path.PathNodeData(pathNode, actualRenderer)); + }, + + getPathNode: function (actualRenderer) { + return this._pathNodeCollection.getPathNode(actualRenderer); + }, + + addClass: function (className) { + this._pathNodeCollection.addOrRemoveClass(className, true /* add */); + }, + + removeClass: function (className) { + this._pathNodeCollection.addOrRemoveClass(className, false /* add */); + }, + +}); + +L.Path.PathNodeData = L.Class.extend({ + + initialize: function (pathNode, actualRenderer) { + + console.assert(pathNode, 'invalid pathNode argument!'); + console.assert(actualRenderer, 'invalid actualRenderer argument!'); + + if (!(pathNode instanceof Node)) { + console.error('Not a node instance!'); + } + + this._pathNode = pathNode; + this._actualRenderer = actualRenderer; + this._data = {}; + }, + + key: function () { + return L.Path.PathNodeData.key(this._actualRenderer); + }, + + getNode: function () { + if (!(this._pathNode instanceof Node)) { + console.error('Not a node instance!'); + } + return this._pathNode; + }, + + getActualRenderer: function () { + return this._actualRenderer; + }, + + setCustomField: function (fieldName, value) { + console.assert(typeof fieldName === 'string' && fieldName, 'invalid fieldName'); + this._data[fieldName] = value; + }, + + getCustomField: function (fieldName) { + console.assert(typeof fieldName === 'string' && fieldName, 'invalid fieldName'); + return this._data[fieldName]; + }, + + clearCustomField: function (fieldName) { + console.assert(typeof fieldName === 'string' && fieldName, 'invalid fieldName'); + delete this._data[fieldName]; + }, + + addOrRemoveClass: function (className, add) { + if (add) { + L.DomUtil.addClass(this._pathNode, className); + } + else { + L.DomUtil.removeClass(this._pathNode, className); + } + }, + +}); + +L.Path.PathNodeData.key = function (layer) { + return L.stamp(layer); +}; + +L.Path.PathNodeCollection = L.Class.extend({ + + initialize: function () { + this.clear(); + }, + + add: function (pathNodeData) { + + console.assert(pathNodeData instanceof L.Path.PathNodeData, + 'invalid pathNodeData argument!'); + + this._collection[pathNodeData.key()] = pathNodeData; + }, + + clear: function () { + this._collection = {}; + }, + + getPathNode: function (actualRenderer) { + + console.assert(actualRenderer, 'invalid actualRenderer argument!'); + var key = L.Path.PathNodeData.key(actualRenderer); + var nodeData = this._collection[key]; + + console.assert(nodeData, 'cannot find path node!'); + + return nodeData.getNode(); + }, + + forEachNode: function (callback) { + var that = this; + Object.keys(this._collection).forEach(function (key) { + callback(that._collection[key]); + }); + }, + + addOrRemoveClass: function (className, add) { + console.assert(className, 'className not provided!'); + this.forEachNode(function (nodeData) { + nodeData.addOrRemoveClass(className, add); + }); + }, + }); diff --git a/loleaflet/src/layer/vector/Polygon.js b/loleaflet/src/layer/vector/Polygon.js index 647438a8b..25118e62c 100644 --- a/loleaflet/src/layer/vector/Polygon.js +++ b/loleaflet/src/layer/vector/Polygon.js @@ -42,7 +42,9 @@ L.Polygon = L.Polyline.extend({ }, _clipPoints: function () { - if (this.options.noClip) { + if (this.options.noClip || this._renderer instanceof L.SplitPanesSVG) { + // TODO: need some work to get this right and performant, especially in the case of + // a poly* spread across multiple split-panes. this._parts = this._rings; return; } diff --git a/loleaflet/src/layer/vector/Polyline.js b/loleaflet/src/layer/vector/Polyline.js index c0650db51..0cab5e463 100644 --- a/loleaflet/src/layer/vector/Polyline.js +++ b/loleaflet/src/layer/vector/Polyline.js @@ -165,7 +165,7 @@ L.Polyline = L.Path.extend({ // clip polyline by renderer bounds so that we have less to render for performance _clipPoints: function () { - if (this.options.noClip) { + if (this.options.noClip || this._renderer instanceof L.SplitPanesSVG) { this._parts = this._rings; return; } diff --git a/loleaflet/src/layer/vector/Renderer.js b/loleaflet/src/layer/vector/Renderer.js index eff612bc9..b2507db6d 100644 --- a/loleaflet/src/layer/vector/Renderer.js +++ b/loleaflet/src/layer/vector/Renderer.js @@ -17,6 +17,11 @@ L.Renderer = L.Layer.extend({ L.stamp(this); }, + setParentRenderer: function (parent) { + console.assert(parent !== this, 'self reference'); + this._parentRenderer = parent; + }, + onAdd: function () { if (!this._container) { this._initContainer(); // defined by renderer implementations @@ -26,7 +31,13 @@ L.Renderer = L.Layer.extend({ } } - this.getPane().appendChild(this._container); + if (this._parentRenderer) { + this._parentRenderer.getContainer().appendChild(this._container); + } + else { + this.getPane().appendChild(this._container); + } + this._update(); }, @@ -53,12 +64,41 @@ L.Renderer = L.Layer.extend({ _update: function () { // update pixel bounds of renderer container (for positioning/sizing/clipping later) + if (this._parentRenderer) { + var posBounds = this._parentRenderer.getChildPosBounds(this); + this._position = posBounds.position; + this._bounds = posBounds.bounds; + return; + } + var p = this.options.padding, size = this._map.getSize(), - min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round(); + min = this._map.containerPointToLayerPointIgnoreSplits(size.multiplyBy(-p)).round(); this._bounds = new L.Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round()); - } + this._position = this._bounds.min; + }, + + getContainer: function () { + return this._container; + }, + + getBounds: function () { + return this._bounds; + }, + + intersectsBounds: function (pxBounds) { + return this._bounds.intersects(pxBounds); + }, + + addContainerClass: function (className) { + L.DomUtil.addClass(this._container, className); + }, + + removeContainerClass: function (className) { + L.DomUtil.removeClass(this._container, className); + }, + }); @@ -68,9 +108,17 @@ L.Map.include({ var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer; if (!renderer) { - renderer = this._renderer = (L.SVG && L.svg()) || (L.Canvas && L.canvas()); + if (this._splitPanesContext) { + renderer = this._renderer = (L.SVG && L.SplitPanesSVG && L.splitPanesSVG()) || + (L.Canvas && L.SplitPanesCanvas && L.splitPanesCanvas()); + } + else { + renderer = this._renderer = (L.SVG && L.svg()) || (L.Canvas && L.canvas()); + } } + console.assert(renderer, 'Could create a renderer!'); + if (!this.hasLayer(renderer)) { this.addLayer(renderer); } @@ -84,9 +132,18 @@ L.Map.include({ var renderer = this._paneRenderers[name]; if (renderer === undefined) { - renderer = (L.SVG && L.svg({pane: name})) || (L.Canvas && L.canvas({pane: name})); + if (this._splitPanesContext) { + renderer = (L.SVG && L.SplitPanesSVG && L.splitPanesSVG({pane: name})) || + (L.Canvas && L.SplitPanesCanvas && L.splitPanesCanvas({pane: name})); + } + else { + renderer = (L.SVG && L.svg({pane: name})) || (L.Canvas && L.canvas({pane: name})); + } + + console.assert(renderer, 'Could create a renderer!'); this._paneRenderers[name] = renderer; } + return renderer; } }); diff --git a/loleaflet/src/layer/vector/SVG.js b/loleaflet/src/layer/vector/SVG.js index 4ae1e3edb..3ac66e901 100644 --- a/loleaflet/src/layer/vector/SVG.js +++ b/loleaflet/src/layer/vector/SVG.js @@ -17,11 +17,12 @@ L.SVG = L.Renderer.extend({ L.Renderer.prototype._update.call(this); - var b = this._bounds, - size = b.getSize(), - container = this._container; + var b = this._bounds; + var size = b.getSize(); + var position = this._position; + var container = this._container; - L.DomUtil.setPosition(container, b.min); + L.DomUtil.setPosition(container, position); // set size of svg-container if changed if (!this._svgSize || !this._svgSize.equals(size)) { @@ -31,14 +32,15 @@ L.SVG = L.Renderer.extend({ } // movement: update container viewBox so that we don't have to change coordinates of individual layers - L.DomUtil.setPosition(container, b.min); + L.DomUtil.setPosition(container, position); container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' ')); }, // methods below are called by vector layers implementations _initPath: function (layer) { - var path = layer._path = L.SVG.create('path'); + var path = L.SVG.create('path'); + layer.addPathNode(path, this); if (layer.options.className) { L.DomUtil.addClass(path, layer.options.className); @@ -57,7 +59,7 @@ L.SVG = L.Renderer.extend({ }, _initGroup: function (layer) { - layer._path = L.SVG.create('g'); + layer.addPathNode(L.SVG.create('g'), this); }, _fireMouseEvent: function (e) { @@ -77,21 +79,21 @@ L.SVG = L.Renderer.extend({ }, _addGroup: function (layer) { - this._container.appendChild(layer._path); + this._container.appendChild(layer.getPathNode(this)); }, _addPath: function (layer) { - this._container.appendChild(layer._path); - layer.addInteractiveTarget(layer._path); + this._container.appendChild(layer.getPathNode(this)); + layer.addInteractiveTarget(layer.getPathNode(this)); }, _removeGroup: function (layer) { - L.DomUtil.remove(layer._path); + L.DomUtil.remove(layer.getPathNode(this)); }, _removePath: function (layer) { - L.DomUtil.remove(layer._path); - layer.removeInteractiveTarget(layer._path); + L.DomUtil.remove(layer.getPathNode(this)); + layer.removeInteractiveTarget(layer.getPathNode(this)); }, _updatePath: function (layer) { @@ -100,7 +102,7 @@ L.SVG = L.Renderer.extend({ }, _updateStyle: function (layer) { - var path = layer._path, + var path = layer.getPathNode(this), options = layer.options; if (!path) { return; } @@ -158,16 +160,16 @@ L.SVG = L.Renderer.extend({ }, _setPath: function (layer, path) { - layer._path.setAttribute('d', path); + layer.getPathNode(this).setAttribute('d', path); }, // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements _bringToFront: function (layer) { - L.DomUtil.toFront(layer._path); + L.DomUtil.toFront(layer.getPathNode(this)); }, _bringToBack: function (layer) { - L.DomUtil.toBack(layer._path); + L.DomUtil.toBack(layer.getPathNode(this)); } }); diff --git a/loleaflet/src/layer/vector/SVGGroup.js b/loleaflet/src/layer/vector/SVGGroup.js index 8ded23f26..7aca7c219 100644 --- a/loleaflet/src/layer/vector/SVGGroup.js +++ b/loleaflet/src/layer/vector/SVGGroup.js @@ -12,8 +12,10 @@ L.SVGGroup = L.Layer.extend({ initialize: function (bounds, options) { L.setOptions(this, options); + this._pathNodeCollection = new L.Path.PathNodeCollection(); this._bounds = bounds; this._rect = L.rectangle(bounds, this.options); + this._hasSVGNode = false; if (L.Browser.touch && !L.Browser.pointer) { this.options.manualDrag = true; } @@ -23,20 +25,28 @@ L.SVGGroup = L.Layer.extend({ }, setVisible: function (visible) { - if (this._svg != null) { + this._forEachSVGNode(function (svgNode) { + svgNode.setAttribute('opacity', 0); if (visible) - this._svg.setAttribute('visibility', 'visible'); + svgNode.setAttribute('visibility', 'visible'); else - this._svg.setAttribute('visibility', 'hidden'); - } + svgNode.setAttribute('visibility', 'hidden'); + }); }, sizeSVG: function () { + + if (!this._hasSVGNode) { + return; + } + var size = L.bounds(this._map.latLngToLayerPoint(this._bounds.getNorthWest()), this._map.latLngToLayerPoint(this._bounds.getSouthEast())).getSize(); - this._svg.setAttribute('width', size.x); - this._svg.setAttribute('height', size.y); + this._forEachSVGNode(function (svgNode) { + svgNode.setAttribute('width', size.x); + svgNode.setAttribute('height', size.y); + }); }, parseSVG: function (svgString) { @@ -45,30 +55,41 @@ L.SVGGroup = L.Layer.extend({ }, addEmbeddedSVG: function (svgString) { - var doc = this.parseSVG(svgString); + var svgDoc = this.parseSVG(svgString); - if (doc.lastChild.localName !== 'svg') + if (svgDoc.lastChild.localName !== 'svg') return; - this._svg = this._path.insertBefore(doc.lastChild, this._rect._path); - this._dragShape = this._rect._path; - this._svg.setAttribute('pointer-events', 'none'); - this._svg.setAttribute('opacity', this._dragStarted ? 1 : 0); + var svgLastChild = svgDoc.lastChild; + var thisObj = this; + this._forEachGroupNode(function (groupNode, rectNode, nodeData) { + var svgNode = groupNode.insertBefore(svgLastChild, rectNode); + nodeData.setCustomField('svg', svgNode); + nodeData.setCustomField('dragShape', rectNode); + thisObj._dragShapePresent = true; + svgNode.setAttribute('pointer-events', 'none'); + svgNode.setAttribute('opacity', thisObj._dragStarted ? 1 : 0); + }); + + this._hasSVGNode = true; + this.sizeSVG(); this._update(); }, _onDragStart: function(evt) { - if (!this._map || !this._dragShape || !this.dragging) + if (!this._map || !this._dragShapePresent || !this.dragging) return; this._dragStarted = true; this._moved = false; if (!this.options.manualDrag) { - L.DomEvent.on(this._dragShape, 'mousemove', this._onDrag, this); - L.DomEvent.on(this._dragShape, 'mouseup', this._onDragEnd, this); - if (this.dragging.constraint) - L.DomEvent.on(this._dragShape, 'mouseout', this._onDragEnd, this); + this._forEachDragShape(function (dragShape) { + L.DomEvent.on(dragShape, 'mousemove', this._onDrag, this); + L.DomEvent.on(dragShape, 'mouseup', this._onDragEnd, this); + if (this.dragging.constraint) + L.DomEvent.on(dragShape, 'mouseout', this._onDragEnd, this); + }.bind(this)); } var data = { @@ -82,7 +103,7 @@ L.SVGGroup = L.Layer.extend({ }, _onDrag: function(evt) { - if (!this._map || !this._dragShape || !this.dragging) + if (!this._map || !this._dragShapePresent || !this.dragging) return; if (!this._moved) { @@ -94,14 +115,16 @@ L.SVGGroup = L.Layer.extend({ }, _onDragEnd: function(evt) { - if (!this._map || !this._dragShape || !this.dragging) + if (!this._map || !this._dragShapePresent || !this.dragging) return; if (!this.options.manualDrag) { - L.DomEvent.off(this._dragShape, 'mousemove', this._onDrag, this); - L.DomEvent.off(this._dragShape, 'mouseup', this._onDragEnd, this); - if (this.dragging.constraint) - L.DomEvent.off(this._dragShape, 'mouseout', this._onDragEnd, this); + this._forEachDragShape(function (dragShape) { + L.DomEvent.off(dragShape, 'mousemove', this._onDrag, this); + L.DomEvent.off(dragShape, 'mouseup', this._onDragEnd, this); + if (this.dragging.constraint) + L.DomEvent.off(dragShape, 'mouseout', this._onDragEnd, this); + }.bind(this)); } this._moved = false; @@ -146,53 +169,78 @@ L.SVGGroup = L.Layer.extend({ this._renderer._initPath(this._rect); this._renderer._addGroup(this); - if (this._path && this._rect._path) { + this._forEachGroupNode(function (groupNode, rectNode, nodeData) { + + if (!groupNode || !rectNode) { + return; + } + this._rect._map = this._map; this._rect._renderer = this._renderer; - L.DomUtil.addClass(this._path, 'leaflet-control-buttons-disabled'); + L.DomUtil.addClass(groupNode, 'leaflet-control-buttons-disabled'); if (this.options.svg) { var doc = this.parseSVG(this.options.svg); if (doc && doc.lastChild.localName === 'svg') { - this._svg = this._path.appendChild(doc.lastChild); - this._svg.setAttribute('opacity', 0); - this._svg.setAttribute('pointer-events', 'none'); - this.sizeSVG(); + this._hasSVGNode = true; + var svgNode = groupNode.appendChild(doc.lastChild); + nodeData.setCustomField('svg', svgNode); + svgNode.setAttribute('opacity', 0); + svgNode.setAttribute('pointer-events', 'none'); } delete this.options.svg; } - this._path.appendChild(this._rect._path); - this._dragShape = this._rect._path; + groupNode.appendChild(rectNode); + nodeData.setCustomField('dragShape', rectNode); + this._dragShapePresent = true; if (!this.options.manualDrag) { - L.DomEvent.on(this._rect._path, 'mousedown', this._onDragStart, this); + L.DomEvent.on(rectNode, 'mousedown', this._onDragStart, this); } - } + }.bind(this)); + + this.sizeSVG(); + this._update(); }, onRemove: function () { this._rect._map = this._rect._renderer = null; - this.removeInteractiveTarget(this._rect._path); - L.DomUtil.remove(this._rect._path); + this._pathNodeCollection.forEachNode(function (nodeData) { + + var actualRenderer = nodeData.getActualRenderer(); + var rectNode = this._rect.getPathNode(actualRenderer); + + this.removeInteractiveTarget(rectNode); + L.DomUtil.remove(rectNode); + + }.bind(this)); + this.removeEmbeddedSVG(); this._renderer._removeGroup(this); }, removeEmbeddedSVG: function () { - if (this._svg) { - this._dragShape = null; - L.DomUtil.remove(this._svg); - delete this._svg; - this._update(); + if (!this._hasSVGNode) { + return; } + + this._pathNodeCollection.forEachNode(function (nodeData) { + var svgNode = nodeData.getCustomField('svg'); + L.DomUtil.remove(svgNode); + nodeData.clearCustomField('svg'); + }); + + this._dragShapePresent = false; + this._hasSVGNode = false; + this._update(); }, _hideEmbeddedSVG: function () { - if (this._svg) { - this._svg.setAttribute('opacity', 0); - } + this._forEachSVGNode(function (svgNode) { + svgNode.setAttribute('opacity', 0); + }); }, _transform: function(matrix) { @@ -216,23 +264,95 @@ L.SVGGroup = L.Layer.extend({ }, _showEmbeddedSVG: function () { - if (this._svg) { - this._svg.setAttribute('opacity', 1); - } + this._forEachSVGNode(function (svgNode) { + svgNode.setAttribute('opacity', 1); + }); }, _update: function () { this._rect.setBounds(this._bounds); - if (this._svg) { - var point = this._map.latLngToLayerPoint(this._bounds.getNorthWest()); - this._svg.setAttribute('x', point.x); - this._svg.setAttribute('y', point.y); - } + var point = this._map.latLngToLayerPoint(this._bounds.getNorthWest()); + + this._forEachSVGNode(function (svgNode) { + svgNode.setAttribute('x', point.x); + svgNode.setAttribute('y', point.y); + }.bind(this)); }, _updatePath: function () { this._update(); - } + }, + + addPathNode: function (pathNode, actualRenderer) { + + this._path = undefined; + + if (!this._pathNodeCollection) { + this._pathNodeCollection = new L.Path.PathNodeCollection(); + } + + this._pathNodeCollection.add(new L.Path.PathNodeData(pathNode, actualRenderer)); + }, + + getPathNode: function (actualRenderer) { + + console.assert(this._pathNodeCollection, 'missing _pathNodeCollection member!'); + return this._pathNodeCollection.getPathNode(actualRenderer); + }, + + addClass: function (className) { + this._pathNodeCollection.addOrRemoveClass(className, true /* add */); + }, + + removeClass: function (className) { + this._pathNodeCollection.addOrRemoveClass(className, false /* add */); + }, + + _forEachGroupNode: function (callback) { + + var that = this; + this._pathNodeCollection.forEachNode(function (nodeData) { + + var actualRenderer = nodeData.getActualRenderer(); + var groupNode = nodeData.getNode(); + var rectNode = that._rect.getPathNode(actualRenderer); + + callback(groupNode, rectNode, nodeData); + + }); + + return true; + }, + + _forEachSVGNode: function (callback) { + if (!this._hasSVGNode) { + return false; + } + + this._pathNodeCollection.forEachNode(function (nodeData) { + var svgNode = nodeData.getCustomField('svg'); + if (svgNode) { + callback(svgNode); + } + }); + + return true; + }, + + _forEachDragShape: function (callback) { + if (!this._dragShapePresent) { + return false; + } + + this._pathNodeCollection.forEachNode(function (nodeData) { + var dragShape = nodeData.getCustomField('dragShape'); + if (dragShape) { + callback(dragShape); + } + }); + + return true; + }, }); diff --git a/loleaflet/src/layer/vector/SplitPanesRenderer.js b/loleaflet/src/layer/vector/SplitPanesRenderer.js new file mode 100644 index 000000000..4f7f06073 --- /dev/null +++ b/loleaflet/src/layer/vector/SplitPanesRenderer.js @@ -0,0 +1,59 @@ +/* -*- js-indent-level: 8 -*- */ +/* + * L.SplitPanesRenderer is a base class for split-panes renderer implementations (only SVG for now); + * handles renderer container, bounds and zoom animation. + */ + +L.SplitPanesRenderer = L.Layer.extend({ + + options: { + // how much to extend the clip area around the map view (relative to its size) + // e.g. 0.1 would be 10% of map view in each direction; defaults to clip with the map view + padding: 0 + }, + + initialize: function (options) { + L.setOptions(this, options); + L.stamp(this); + }, + + onAdd: function () { + + this._splitPanesContext = this._map.getSplitPanesContext(); + console.assert(this._splitPanesContext, 'no split-panes context object!'); + + if (!this._container) { + this._initContainer(); // defined by renderer implementations + } + + this.getPane().appendChild(this._container); + this._update(); + }, + + onRemove: function () { + L.DomUtil.remove(this._container); + }, + + setParentRenderer: function () { + console.error('SplitPanesRenderer cannot be a child renderer!'); + }, + + // All child renderers have dedicated event listeners. + getEvents: function () { + return {}; + }, + + _animateZoom: function () { + }, + + _update: function () { + }, + + getContainer: function () { + return this._container; + }, + + getBoundsList: function () { + return undefined; + }, +}); diff --git a/loleaflet/src/layer/vector/SplitPanesSVG.js b/loleaflet/src/layer/vector/SplitPanesSVG.js new file mode 100644 index 000000000..c0f8974ce --- /dev/null +++ b/loleaflet/src/layer/vector/SplitPanesSVG.js @@ -0,0 +1,323 @@ +/* -*- js-indent-level: 8 -*- */ +/* + * L.SplitPanesSVG renders vector layers with SVG for split-panes. + */ + +L.SplitPanesSVG = L.SplitPanesRenderer.extend({ + _initContainer: function () { + + this._container = document.createElement('div'); + + this._setupPaneRenderers(); + }, + + _setupPaneRenderers: function () { + + if (this._childRenderers) { + return; + } + + var map = this._map; + this._rendererIds = ['fixed', 'topleft', 'topright', 'bottomleft', 'bottomright']; + this._splitPaneNames = ['topleft', 'topright', 'bottomleft', 'bottomright']; + this._childRenderers = {}; + this._rendererIds.forEach(function (rendererId) { + var svgRenderer = L.svg(this.options); + this._childRenderers[rendererId] = svgRenderer; + svgRenderer.rendererId = rendererId; + svgRenderer.setParentRenderer(this); + map.addLayer(svgRenderer); + }, this); + }, + + _forEachPaneRenderer: function (callback) { + return this._forEachChildRenderer(callback, true /* skipFixed */); + }, + + _forEachChildRenderer: function (callback, skipFixed) { + if (!this._childRenderers) { + return false; + } + + this._rendererIds.forEach(function (rendererId) { + + if (skipFixed === true && rendererId === 'fixed') { + return; + } + + var renderer = this._childRenderers[rendererId]; + callback(renderer, rendererId); + + }, this); + + return true; + }, + + _disposePaneRenderers: function () { + + if (!this._childRenderers) { + return; + } + + this._rendererIds.forEach(function (rendererId) { + this._map.removeLayer(this._childRenderers[rendererId]); + this._childRenderers[rendererId] = undefined; + }, this); + + this._childRenderers = undefined; + }, + + onRemove: function () { + this._disposePaneRenderers(); + L.SplitPanesRenderer.prototype.onRemove.call(this); + }, + + getChildPosBounds: function (childRenderer) { + console.assert(typeof childRenderer.rendererId === 'string', 'Child renderer does not have a rendererId!'); + var rendererId = childRenderer.rendererId; + var renderer = this._childRenderers[rendererId]; + console.assert(renderer && L.stamp(renderer) === L.stamp(childRenderer), 'Child renderer does not belong to parent!'); + + var splitPos = this._splitPanesContext.getSplitPos(); + var size = this._map.getSize(); + var pixelOrigin = this._map.getPixelOrigin(); + // Container coordinates. + var topLeft = new L.Point(0, 0); + // pos and boundPos should be in layer coordinates. + var pos = undefined; + var boundPos = undefined; + + if (rendererId === 'fixed') { + // This is for displaying the pane-splitter horizontal/vertical lines. + // is always glued to (0, 0) of the document. + // The size is always the map's view size. + pos = this._map.containerPointToLayerPointIgnoreSplits(topLeft).round(); + boundPos = topLeft.subtract(pixelOrigin); + } + else if (rendererId === 'bottomright') { + // this is the default splitPane where are no visible splits (splitPos = (0, 0)). + topLeft = splitPos; + size = size.subtract(splitPos); + pos = this._map.containerPointToLayerPointIgnoreSplits(topLeft).round(); + boundPos = pos; + } + else if (rendererId === 'topleft') { + // is always glued to (0, 0) of the document. + size.x = splitPos.x - 1; + size.y = splitPos.y - 1; + pos = this._map.containerPointToLayerPointIgnoreSplits(topLeft).round(); + boundPos = topLeft.subtract(pixelOrigin); + } + else if (rendererId === 'topright') { + // is always glued to top (y = 0) of the document. + topLeft.x = splitPos.x; + size.x -= splitPos.x; + size.y = splitPos.y - 1; + pos = this._map.containerPointToLayerPointIgnoreSplits(topLeft).round(); + boundPos = new L.Point(pos.x, topLeft.y - pixelOrigin.y); + } + else if (rendererId === 'bottomleft') { + // is always glued to left (x = 0) of the document. + topLeft.y = splitPos.y; + size.y -= splitPos.y; + size.x = splitPos.x - 1; + pos = this._map.containerPointToLayerPointIgnoreSplits(topLeft).round(); + boundPos = new L.Point(topLeft.x - pixelOrigin.x, pos.y); + } + else { + console.error('unhandled rendererId : ' + rendererId); + } + + var bounds = new L.Bounds(boundPos, boundPos.add(size)); + + return { + bounds: bounds, + position: pos, + }; + }, + + getEvents: function () { + var events = { + splitposchanged: this._update + }; + + return events; + }, + + _update: function () { + + this._forEachChildRenderer(function (renderer) { + renderer._update(); + }); + }, + + // methods below are called by vector layers implementations + + _initPath: function (layer) { + + if (layer.options.fixed === true) { + this._childRenderers['fixed']._initPath(layer); + return; + } + + this._forEachPaneRenderer(function (paneRenderer) { + paneRenderer._initPath(layer); + }); + }, + + _initGroup: function (layer) { + + this._forEachPaneRenderer(function (paneRenderer) { + paneRenderer._initGroup(layer); + }); + }, + + _fireMouseEvent: function () { + // child renderers listen for ['mouseenter', 'mouseout'], and it refires with additional info + // but these events have any listeners ? may be DomEvent.js generates other events ? + + // TODO: make the child renderers call this and create the right ones. + }, + + _addGroup: function (layer) { + + this._forEachPaneRenderer(function (paneRenderer) { + paneRenderer._addGroup(layer); + }); + }, + + _addPath: function (layer) { + + if (layer.options.fixed === true) { + this._childRenderers['fixed']._addPath(layer); + return; + } + + this._forEachPaneRenderer(function (paneRenderer) { + paneRenderer._addPath(layer); + }); + }, + + _removeGroup: function (layer) { + + this._forEachPaneRenderer(function (paneRenderer) { + paneRenderer._removeGroup(layer); + }); + }, + + _removePath: function (layer) { + if (layer.options.fixed === true) { + this._childRenderers['fixed']._removePath(layer); + return; + } + + this._forEachPaneRenderer(function (paneRenderer) { + paneRenderer._removePath(layer); + }); + }, + + // should not forward to children. + _updatePath: function (layer) { + layer._project(); + layer._update(); + }, + + _updateStyle: function (layer) { + + if (layer.options.fixed === true) { + this._childRenderers['fixed']._updateStyle(layer); + return; + } + + this._forEachPaneRenderer(function (paneRenderer) { + paneRenderer._updateStyle(layer); + }); + }, + + // enough to forward the _setPath for the actual path-node modification part. + _updatePoly: function (layer, closed) { + this._setPath(layer, L.SVG.pointsToPath(layer._parts, closed)); + }, + + // enough to forward the _setPath for the actual path-node modification part. + _updateCircle: function (layer) { + var p = layer._point; + var r = layer._radius; + var r2 = layer._radiusY || r; + var arc = 'a' + r + ',' + r2 + ' 0 1,0 '; + + // drawing a circle with two half-arcs + var d = layer._empty() ? 'M0 0' : + 'M' + (p.x - r) + ',' + p.y + + arc + (r * 2) + ',0 ' + + arc + (-r * 2) + ',0 '; + + this._setPath(layer, d); + }, + + _setPath: function (layer, path) { + + if (layer.options.fixed === true) { + this._childRenderers['fixed']._setPath(layer, path); + return; + } + + this._forEachPaneRenderer(function (paneRenderer) { + paneRenderer._setPath(layer, path); + }); + }, + + // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements + _bringToFront: function (layer) { + if (layer.options.fixed === true) { + this._childRenderers['fixed']._bringToFront(layer); + return; + } + + this._forEachPaneRenderer(function (paneRenderer) { + paneRenderer._bringToFront(layer); + }); + }, + + _bringToBack: function (layer) { + + if (layer.options.fixed === true) { + this._childRenderers['fixed']._bringToBack(layer); + return; + } + + this._forEachPaneRenderer(function (paneRenderer) { + paneRenderer._bringToBack(layer); + }); + }, + + intersectsBounds: function (pxBounds) { + for (var i = 0; i < this._rendererIds.length; ++i) { + var rendererId = this._rendererIds[i]; + if (this._childRenderers[rendererId].intersectsBounds(pxBounds)) { + return true; + } + } + + return false; + }, + + addContainerClass: function (className) { + L.DomUtil.addClass(this._container, className); + this._forEachChildRenderer(function (childRenderer) { + childRenderer.addContainerClass(className); + }); + }, + + removeContainerClass: function (className) { + L.DomUtil.removeClass(this._container, className); + this._forEachChildRenderer(function (childRenderer) { + childRenderer.removeContainerClass(className); + }); + }, + +}); + +L.splitPanesSVG = function (options) { + return new L.SplitPanesSVG(options); +}; _______________________________________________ Libreoffice-commits mailing list [email protected] https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits
