[Libreoffice-commits] online.git: loleaflet/Makefile.am loleaflet/src

Dennis Francis (via logerrit) logerrit at kemper.freedesktop.org
Wed Jul 8 14:58:06 UTC 2020


 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 <dennis.francis at collabora.com>
AuthorDate: Tue Jul 7 14:29:54 2020 +0530
Commit:     Dennis Francis <dennis.francis at collabora.com>
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 <jenkinscollaboraoffice at gmail.com>
    Tested-by: Jenkins
    Reviewed-by: Dennis Francis <dennis.francis at collabora.com>

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);
+};


More information about the Libreoffice-commits mailing list