From e52cbf7a599fc20fbbb6361aee64172ce4be80bd Mon Sep 17 00:00:00 2001 From: David Manthey Date: Tue, 28 Mar 2017 13:12:22 -0400 Subject: [PATCH 1/2] Add a visible() function to layers. Note that, much like feature level visibility, you might need to call layer.draw() after making the layer visible to ensure an update. For feature layers, visibility cascades down to individual features (which disables their interaction). --- src/feature.js | 23 ++++++++++++++----- src/featureLayer.js | 45 ++++++++++++++++++++++++++++++++----- src/gl/quadFeature.js | 4 ++-- src/layer.js | 23 +++++++++++++++++++ src/pixelmapFeature.js | 11 ++++----- src/pointFeature.js | 4 ---- src/polygonFeature.js | 2 +- src/tileLayer.js | 38 ++++++++++++++++++++++++++----- tests/cases/feature.js | 14 ++++++++++++ tests/cases/featureLayer.js | 13 +++++++++++ tests/cases/tileLayer.js | 25 +++++++++++++++++++++ 11 files changed, 174 insertions(+), 28 deletions(-) diff --git a/src/feature.js b/src/feature.js index a0f48ef41a..63ead89bc4 100644 --- a/src/feature.js +++ b/src/feature.js @@ -413,24 +413,37 @@ var feature = function (arg) { //////////////////////////////////////////////////////////////////////////// /** * Get/Set visibility of the feature + * + * @param {boolean|undefined} val: undefined to return the visibility, a + * boolean to change the visibility. + * @param {boolean} direct: if true, when getting the visibility, disregard + * the visibility of the parent layer, and when setting, refresh the state + * regardless of whether it has changed or not. + * @return {boolean|object} either the visibility (if getting) or the feature + * (if setting). */ //////////////////////////////////////////////////////////////////////////// - this.visible = function (val) { + this.visible = function (val, direct) { if (val === undefined) { + if (!direct && m_layer && m_layer.visible && !m_layer.visible()) { + return false; + } return m_visible; } - if (m_visible !== val) { + if (m_visible !== val || direct) { m_visible = val; m_this.modified(); - + if (m_layer && m_layer.visible && !m_layer.visible()) { + val = false; + } // bind or unbind mouse handlers on visibility change - if (m_visible) { + if (val) { m_this._bindMouseHandlers(); } else { m_this._unbindMouseHandlers(); } for (var i = 0; i < m_dependentFeatures.length; i += 1) { - m_dependentFeatures[i].visible(val); + m_dependentFeatures[i].visible(m_visible, direct); } } return m_this; diff --git a/src/featureLayer.js b/src/featureLayer.js index 1f2fa62dc9..84b6d9f899 100644 --- a/src/featureLayer.js +++ b/src/featureLayer.js @@ -30,6 +30,7 @@ var featureLayer = function (arg) { s_init = this._init, s_exit = this._exit, s_update = this._update, + s_visible = this.visible, s_draw = this.draw; //////////////////////////////////////////////////////////////////////////// @@ -231,13 +232,45 @@ var featureLayer = function (arg) { */ //////////////////////////////////////////////////////////////////////////// this.draw = function () { - // Call sceneObject.draw, which calls draw on all child objects. - s_draw(); + if (m_this.visible()) { + // Call sceneObject.draw, which calls draw on all child objects. + s_draw(); - // Now call render on the renderer. In certain cases it may not do - // anything if the if the child objects are drawn on the screen already. - if (m_this.renderer()) { - m_this.renderer()._render(); + // Now call render on the renderer. In certain cases it may not do + // anything if the child objects are drawn on the screen already. + if (m_this.renderer()) { + m_this.renderer()._render(); + } + } + return m_this; + }; + + //////////////////////////////////////////////////////////////////////////// + /** + * Get/Set visibility of the layer + * + * @param {boolean|undefined} val: undefined to return the visibility, a + * boolean to change the visibility. + * @return {boolean|object} either the visibility (if getting) or the layer + * (if setting). + */ + //////////////////////////////////////////////////////////////////////////// + this.visible = function (val) { + if (val === undefined) { + return s_visible(); + } + if (m_this.visible() !== val) { + s_visible(val); + + // take a copy of the features; changing visible could mutate them. + var features = m_features.slice(), i; + + for (i = 0; i < features.length; i += 1) { + features[i].visible(features[i].visible(undefined, true), true); + } + if (val) { + m_this.draw(); + } } return m_this; }; diff --git a/src/gl/quadFeature.js b/src/gl/quadFeature.js index 6be4e59c40..55fe0a8f52 100644 --- a/src/gl/quadFeature.js +++ b/src/gl/quadFeature.js @@ -373,11 +373,11 @@ var gl_quadFeature = function (arg) { m_this._build(); } if (m_actor_color) { - m_actor_color.setVisible(m_this.visible()); + m_actor_color.setVisible(m_this.visible(undefined, true)); m_actor_color.material().setBinNumber(m_this.bin()); } if (m_actor_image) { - m_actor_image.setVisible(m_this.visible()); + m_actor_image.setVisible(m_this.visible(undefined, true)); m_actor_image.material().setBinNumber(m_this.bin()); } m_this.updateTime().modified(); diff --git a/src/layer.js b/src/layer.js index 74cf53a6ae..46f55c4a56 100644 --- a/src/layer.js +++ b/src/layer.js @@ -55,6 +55,7 @@ var layer = function (arg) { m_active = arg.active === undefined ? true : arg.active, m_opacity = arg.opacity === undefined ? 1 : arg.opacity, m_attribution = arg.attribution || null, + m_visible = arg.visible === undefined ? true : arg.visible, m_zIndex; m_rendererName = checkRenderer(m_rendererName); @@ -352,6 +353,28 @@ var layer = function (arg) { return m_attribution; }; + //////////////////////////////////////////////////////////////////////////// + /** + * Get/Set visibility of the layer + * + * @param {boolean|undefined} val: undefined to return the visibility, a + * boolean to change the visibility. + * @return {boolean|object} either the visibility (if getting) or the layer + * (if setting). + */ + //////////////////////////////////////////////////////////////////////////// + this.visible = function (val) { + if (val === undefined) { + return m_visible; + } + if (m_visible !== val) { + m_visible = val; + m_node.css('display', m_visible ? '' : 'none'); + m_this.modified(); + } + return m_this; + }; + //////////////////////////////////////////////////////////////////////////// /** * Init layer diff --git a/src/pixelmapFeature.js b/src/pixelmapFeature.js index 43c5edb237..facb46bc5d 100644 --- a/src/pixelmapFeature.js +++ b/src/pixelmapFeature.js @@ -339,13 +339,14 @@ var pixelmapFeature = function (arg) { m_quadFeature = m_this.layer().createFeature('quad', { selectionAPI: false, gcs: m_this.gcs(), - visible: m_this.visible() + visible: m_this.visible(undefined, true) }); m_this.dependentFeatures([m_quadFeature]); - m_quadFeature.style({image: m_info.canvas, - position: m_this.style.get('position')}) - .data([{}]) - .draw(); + m_quadFeature.style({ + image: m_info.canvas, + position: m_this.style.get('position')}) + .data([{}]) + .draw(); } /* If we prepared the pixelmap and rendered it, send a prepared event */ if (prepared) { diff --git a/src/pointFeature.js b/src/pointFeature.js index ca557802d0..7d41f6c65e 100644 --- a/src/pointFeature.js +++ b/src/pointFeature.js @@ -224,10 +224,6 @@ var pointFeature = function (arg) { strokeWidth = m_this.style.get('strokeWidth'), radius = m_this.style.get('radius'); - if (!m_this.selectionAPI()) { - return []; - } - data = m_this.data(); if (!data || !data.length) { return { diff --git a/src/polygonFeature.js b/src/polygonFeature.js index f745177bf4..3b5bc753af 100644 --- a/src/polygonFeature.js +++ b/src/polygonFeature.js @@ -271,7 +271,7 @@ var polygonFeature = function (arg) { m_lineFeature = m_this.layer().createFeature('line', { selectionAPI: false, gcs: m_this.gcs(), - visible: m_this.visible() + visible: m_this.visible(undefined, true) }); m_this.dependentFeatures([m_lineFeature]); } diff --git a/src/tileLayer.js b/src/tileLayer.js index ac39d2a963..efab5ab1c8 100644 --- a/src/tileLayer.js +++ b/src/tileLayer.js @@ -138,6 +138,11 @@ module.exports = (function () { */ ////////////////////////////////////////////////////////////////////////////// var tileLayer = function (options) { + 'use strict'; + if (!(this instanceof tileLayer)) { + return new tileLayer(options); + } + featureLayer.call(this, options); var $ = require('jquery'); var geo_event = require('./event'); @@ -147,11 +152,6 @@ module.exports = (function () { var adjustLayerForRenderer = require('./registry').adjustLayerForRenderer; var Tile = require('./tile'); - if (!(this instanceof tileLayer)) { - return new tileLayer(options); - } - featureLayer.call(this, options); - options = $.extend(true, {}, this.constructor.defaults, options || {}); if (!options.cacheSize) { // this size should be sufficient for a 4k display @@ -177,6 +177,7 @@ module.exports = (function () { var s_init = this._init, s_exit = this._exit, + s_visible = this.visible, m_lastTileSet = [], m_maxBounds = [], m_exited; @@ -1063,6 +1064,9 @@ module.exports = (function () { evt.event.event === geo_event.rotate)) { return; } + if (!this.visible()) { + return; + } var map = this.map(), bounds = map.bounds(undefined, null), mapZoom = map.zoom(), @@ -1430,6 +1434,30 @@ module.exports = (function () { return m_tileOffsetValues[level]; }; + //////////////////////////////////////////////////////////////////////////// + /** + * Get/Set visibility of the layer + * + * @param {boolean|undefined} val: undefined to return the visibility, a + * boolean to change the visibility. + * @return {boolean|object} either the visibility (if getting) or the layer + * (if setting). + */ + //////////////////////////////////////////////////////////////////////////// + this.visible = function (val) { + if (val === undefined) { + return s_visible(); + } + if (this.visible() !== val) { + s_visible(val); + + if (val) { + this._update(); + } + } + return this; + }; + /** * Initialize after the layer is added to the map. */ diff --git a/tests/cases/feature.js b/tests/cases/feature.js index 1360d89205..2a8ed5c5a6 100644 --- a/tests/cases/feature.js +++ b/tests/cases/feature.js @@ -198,6 +198,20 @@ describe('geo.feature', function () { feat.dependentFeatures([]); expect(feat.visible(true)).toBe(feat); expect(depFeat.visible()).toBe(false); + + // the layer can control the visibility + expect(feat.visible()).toBe(true); + expect(feat.visible(undefined, true)).toBe(true); + layer.visible(false); + expect(feat.visible()).toBe(false); + expect(feat.visible(undefined, true)).toBe(true); + expect(feat.visible(false, true)).toBe(feat); + expect(feat.visible()).toBe(false); + expect(feat.visible(undefined, true)).toBe(false); + layer.visible(true); + expect(feat.visible()).toBe(false); + expect(feat.visible(true, true)).toBe(feat); + expect(feat.visible()).toBe(true); }); }); describe('Check class accessors', function () { diff --git a/tests/cases/featureLayer.js b/tests/cases/featureLayer.js index ec4ba6648c..0e8ab96608 100644 --- a/tests/cases/featureLayer.js +++ b/tests/cases/featureLayer.js @@ -127,6 +127,19 @@ describe('geo.featureLayer', function () { expect(layer.features().length).toBe(2); expect(layer.features()).toEqual([feat2, feat1]); }); + it('visible', function () { + expect(layer.visible()).toBe(true); + expect(feat1.visible()).toBe(true); + expect(feat1.visible(undefined, true)).toBe(true); + expect(layer.visible(false)).toBe(layer); + expect(layer.visible()).toBe(false); + expect(feat1.visible()).toBe(false); + expect(feat1.visible(undefined, true)).toBe(true); + expect(layer.visible(true)).toBe(layer); + expect(layer.visible()).toBe(true); + expect(feat1.visible()).toBe(true); + expect(feat1.visible(undefined, true)).toBe(true); + }); it('draw', function () { sinon.stub(feat1, 'draw', function () {}); expect(layer.draw()).toBe(layer); diff --git a/tests/cases/tileLayer.js b/tests/cases/tileLayer.js index 6a81bbe8e2..428374b290 100644 --- a/tests/cases/tileLayer.js +++ b/tests/cases/tileLayer.js @@ -96,6 +96,8 @@ describe('geo.tileLayer', function () { }, updateAttribution: function () { }, + bounds: function () { + }, node: get_set('node'), children: function () { return []; @@ -415,6 +417,29 @@ describe('geo.tileLayer', function () { expect(l.tilesAtZoom(2)).toEqual({x: 4, y: 3}); expect(l.tilesAtZoom(3)).toEqual({x: 8, y: 6}); }); + it('visible', function () { + var m = map(), layer, count = 0; + opts.map = m; + layer = geo.tileLayer(opts); + // check if we are updating by doing the least possible and tracking it + layer._getTiles = function () { + count += 1; + }; + layer._updateSubLayers = undefined; + + expect(layer.visible()).toBe(true); + layer._update(); + expect(count).toBe(1); + expect(layer.visible(false)).toBe(layer); + expect(layer.visible()).toBe(false); + layer._update(); + expect(count).toBe(1); + expect(layer.visible(true)).toBe(layer); + expect(layer.visible()).toBe(true); + expect(count).toBe(2); + layer._update(); + expect(count).toBe(3); + }); }); describe('Public utility methods', function () { describe('isValid', function () { From 3dad631dc00e3df1eb2b6740585ffaa01721f041 Mon Sep 17 00:00:00 2001 From: David Manthey Date: Tue, 28 Mar 2017 16:31:20 -0400 Subject: [PATCH 2/2] Add a layer selectionAPI property. The active property can now be changed. --- src/feature.js | 20 ++++++++++++++----- src/featureLayer.js | 28 +++++++++++++++++++++++++++ src/layer.js | 38 ++++++++++++++++++++++++++++++++----- tests/cases/feature.js | 15 +++++++++++++++ tests/cases/featureLayer.js | 24 +++++++++++++++++++++++ 5 files changed, 115 insertions(+), 10 deletions(-) diff --git a/src/feature.js b/src/feature.js index 63ead89bc4..9f9941cadc 100644 --- a/src/feature.js +++ b/src/feature.js @@ -53,7 +53,7 @@ var feature = function (arg) { // Don't bind handlers for improved performance on features that don't // require it. - if (!m_selectionAPI) { + if (!this.selectionAPI()) { return; } @@ -545,16 +545,26 @@ var feature = function (arg) { //////////////////////////////////////////////////////////////////////////// /** - * Query or set if the selection API is enabled for this feature. - * @returns {bool} + * Get/Set if the selection API is enabled for this feature. + * + * @param {boolean|undefined} val: undefined to return the selectionAPI + * state, or a boolean to change the state. + * @param {boolean} direct: if true, when getting the selectionAPI state, + * disregard the state of the parent layer, and when setting, refresh the + * state regardless of whether it has changed or not. + * @return {boolean|object} either the selectionAPI state (if getting) or the + * feature (if setting). */ //////////////////////////////////////////////////////////////////////////// - this.selectionAPI = function (arg) { + this.selectionAPI = function (arg, direct) { if (arg === undefined) { + if (!direct && m_layer && m_layer.selectionAPI && !m_layer.selectionAPI()) { + return false; + } return m_selectionAPI; } arg = !!arg; - if (arg !== m_selectionAPI) { + if (arg !== m_selectionAPI || direct) { m_selectionAPI = arg; this._unbindMouseHandlers(); this._bindMouseHandlers(); diff --git a/src/featureLayer.js b/src/featureLayer.js index 84b6d9f899..7ca8d0ab95 100644 --- a/src/featureLayer.js +++ b/src/featureLayer.js @@ -31,6 +31,7 @@ var featureLayer = function (arg) { s_exit = this._exit, s_update = this._update, s_visible = this.visible, + s_selectionAPI = this.selectionAPI, s_draw = this.draw; //////////////////////////////////////////////////////////////////////////// @@ -275,6 +276,33 @@ var featureLayer = function (arg) { return m_this; }; + //////////////////////////////////////////////////////////////////////////// + /** + * Get/Set selectionAPI of the layer + * + * @param {boolean|undefined} val: undefined to return the selectionAPI + * state, or a boolean to change it. + * @return {boolean|object} either the selectionAPI state (if getting) or the + * layer (if setting). + */ + //////////////////////////////////////////////////////////////////////////// + this.selectionAPI = function (val) { + if (val === undefined) { + return s_selectionAPI(); + } + if (m_this.selectionAPI() !== val) { + s_selectionAPI(val); + + // take a copy of the features; changing selectionAPI could mutate them. + var features = m_features.slice(), i; + + for (i = 0; i < features.length; i += 1) { + features[i].selectionAPI(features[i].selectionAPI(undefined, true), true); + } + } + return m_this; + }; + //////////////////////////////////////////////////////////////////////////// /** * Clear all features in layer diff --git a/src/layer.js b/src/layer.js index 46f55c4a56..da5676b7d2 100644 --- a/src/layer.js +++ b/src/layer.js @@ -56,6 +56,7 @@ var layer = function (arg) { m_opacity = arg.opacity === undefined ? 1 : arg.opacity, m_attribution = arg.attribution || null, m_visible = arg.visible === undefined ? true : arg.visible, + m_selectionAPI = arg.selectionAPI === undefined ? true : arg.selectionAPI, m_zIndex; m_rendererName = checkRenderer(m_rendererName); @@ -193,20 +194,27 @@ var layer = function (arg) { //////////////////////////////////////////////////////////////////////////// /** - * Get whether or not the layer is active. An active layer will receive + * Get/Set whether or not the layer is active. An active layer will receive * native mouse when the layer is on top. Non-active layers will never * receive native mouse events. * - * @returns {Boolean} + * @returns {Boolean|object} */ //////////////////////////////////////////////////////////////////////////// - this.active = function () { - return m_active; + this.active = function (arg) { + if (arg === undefined) { + return m_active; + } + if (m_active !== arg) { + m_active = arg; + m_node.toggleClass('active', m_active); + } + return this; }; //////////////////////////////////////////////////////////////////////////// /** - * Get/Set root node of the layer + * Get root node of the layer * * @returns {div} */ @@ -375,6 +383,26 @@ var layer = function (arg) { return m_this; }; + //////////////////////////////////////////////////////////////////////////// + /** + * Get/Set selectionAPI of the layer + * + * @param {boolean|undefined} val: undefined to return the selectionAPI + * state, or a boolean to change it. + * @return {boolean|object} either the selectionAPI state (if getting) or the + * layer (if setting). + */ + //////////////////////////////////////////////////////////////////////////// + this.selectionAPI = function (val) { + if (val === undefined) { + return m_selectionAPI; + } + if (m_selectionAPI !== val) { + m_selectionAPI = val; + } + return m_this; + }; + //////////////////////////////////////////////////////////////////////////// /** * Init layer diff --git a/tests/cases/feature.js b/tests/cases/feature.js index 2a8ed5c5a6..304f9974bc 100644 --- a/tests/cases/feature.js +++ b/tests/cases/feature.js @@ -295,6 +295,21 @@ describe('geo.feature', function () { expect(feat.selectionAPI()).toBe(true); expect(feat.selectionAPI(0)).toBe(feat); expect(feat.selectionAPI()).toBe(false); + + // the layer can control the visibility + feat.selectionAPI(true); + expect(feat.selectionAPI()).toBe(true); + expect(feat.selectionAPI(undefined, true)).toBe(true); + layer.selectionAPI(false); + expect(feat.selectionAPI()).toBe(false); + expect(feat.selectionAPI(undefined, true)).toBe(true); + expect(feat.selectionAPI(false, true)).toBe(feat); + expect(feat.selectionAPI()).toBe(false); + expect(feat.selectionAPI(undefined, true)).toBe(false); + layer.selectionAPI(true); + expect(feat.selectionAPI()).toBe(false); + expect(feat.selectionAPI(true, true)).toBe(feat); + expect(feat.selectionAPI()).toBe(true); }); }); }); diff --git a/tests/cases/featureLayer.js b/tests/cases/featureLayer.js index 0e8ab96608..9dcfe3f239 100644 --- a/tests/cases/featureLayer.js +++ b/tests/cases/featureLayer.js @@ -140,6 +140,30 @@ describe('geo.featureLayer', function () { expect(feat1.visible()).toBe(true); expect(feat1.visible(undefined, true)).toBe(true); }); + it('selectionAPI', function () { + feat1.selectionAPI(true); + expect(layer.selectionAPI()).toBe(true); + expect(feat1.selectionAPI()).toBe(true); + expect(feat1.selectionAPI(undefined, true)).toBe(true); + expect(layer.selectionAPI(false)).toBe(layer); + expect(layer.selectionAPI()).toBe(false); + expect(feat1.selectionAPI()).toBe(false); + expect(feat1.selectionAPI(undefined, true)).toBe(true); + expect(layer.selectionAPI(true)).toBe(layer); + expect(layer.selectionAPI()).toBe(true); + expect(feat1.selectionAPI()).toBe(true); + expect(feat1.selectionAPI(undefined, true)).toBe(true); + }); + it('active', function () { + expect(layer.active()).toBe(true); + expect(layer.node().hasClass('active')).toBe(true); + expect(layer.active(false)).toBe(layer); + expect(layer.active()).toBe(false); + expect(layer.node().hasClass('active')).toBe(false); + expect(layer.active(true)).toBe(layer); + expect(layer.active()).toBe(true); + expect(layer.node().hasClass('active')).toBe(true); + }); it('draw', function () { sinon.stub(feat1, 'draw', function () {}); expect(layer.draw()).toBe(layer);