From d30684b6c840b321e3ab36ed0a285a4df5e6fa71 Mon Sep 17 00:00:00 2001 From: Norbert Renner Date: Thu, 21 May 2015 20:16:04 +0200 Subject: [PATCH] add opacity slider (bootstrap-slider) for route track and markers --- README.md | 2 ++ bower.json | 3 ++- css/style.css | 48 ++++++++++++++++++++++++++++++++++ js/control/OpacitySlider.js | 52 +++++++++++++++++++++++++++++++++++++ js/index.js | 6 ++++- js/plugin/Routing.js | 36 +++++++++++++++++++++++-- 6 files changed, 143 insertions(+), 4 deletions(-) create mode 100644 js/control/OpacitySlider.js diff --git a/README.md b/README.md index ba391c5..0927693 100644 --- a/README.md +++ b/README.md @@ -97,3 +97,5 @@ Copyright (c) 2011-2014 Twitter, Inc; [MIT License](https://github.com/twbs/boot Copyright 2005, 2014 jQuery Foundation and other contributors; [MIT License](https://github.com/jquery/jquery/blob/master/LICENSE.txt) * [DataTables](https://github.com/DataTables/DataTables) Copyright (C) 2008-2014, SpryMedia Ltd.; [MIT License](http://www.datatables.net/license/mit) +* [bootstrap-slider](https://github.com/seiyria/bootstrap-slider) +Copyright (c) 2015 Kyle Kemp, Rohit Kalkur, and contributors; [MIT License](https://github.com/seiyria/bootstrap-slider/blob/master/LICENSE.md) diff --git a/bower.json b/bower.json index a8d3555..fa807e2 100644 --- a/bower.json +++ b/bower.json @@ -18,7 +18,8 @@ "Leaflet.Elevation": "MrMufflon/Leaflet.Elevation#master", "leaflet-control-geocoder": "~1.1.0", "L.EasyButton": "*", - "bootbox": "~4.4.0" + "bootbox": "~4.4.0", + "seiyria-bootstrap-slider": "~4.8.1" }, "overrides": { "leaflet": { diff --git a/css/style.css b/css/style.css index 1eb35d7..289f7ad 100644 --- a/css/style.css +++ b/css/style.css @@ -264,3 +264,51 @@ table.dataTable.display tbody tr.odd:hover, table.dataTable.display tbody tr.even:hover { background-color: rgba(255,255,0,0.2); } + +/* + * opacity slider control (seiyria-bootstrap-slider) + */ + +.control-slider { + background: #fff; + border-radius: 10px; + padding-top: 10px; + padding-bottom: 10px; +} + +.slider.slider-vertical { + height: 80px; +} + +/* invert track and selection styles to get partial gradient for "selection" */ +.slider.slider-vertical .slider-track { + width: 8px; + margin-left: -4px; + background-image: linear-gradient(to right, #f0f0f0 0%, #e9e9e9 100%); + box-shadow: inset -1px -0px 1px rgba(55, 55, 55, 0.3), inset 1px 0px 1px rgba(230, 230, 230, 1); +} + +.control-slider:hover .slider-track, +.control-slider:active .slider-track { + background-image: linear-gradient(to bottom, magenta 0%, white 100%); +} + +.slider-selection { + background-color: #C6C6C6; + background-image: none; + box-shadow: inset 1px 1px 3px rgba(0, 0, 0, 0.6); +} + +.slider.slider-vertical .slider-tick, +.slider.slider-vertical .slider-handle { + margin-left: -6px; + cursor: ns-resize; + box-sizing: border-box; + background: none; + + /* bootstrap .btn-default */ + background-image: linear-gradient(to bottom,#fff 0,#e0e0e0 100%); + background-repeat: repeat-x; + box-shadow: inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075); + border: 1px solid #adadad; +} diff --git a/js/control/OpacitySlider.js b/js/control/OpacitySlider.js new file mode 100644 index 0000000..38ec888 --- /dev/null +++ b/js/control/OpacitySlider.js @@ -0,0 +1,52 @@ +BR.OpacitySlider = L.Control.extend({ + options: { + position: 'topleft', + callback: function(opacity) {} + }, + + onAdd: function (map) { + var container = L.DomUtil.create('div', 'leaflet-bar control-slider'), + input = $(''), + item = localStorage.opacitySliderValue, + value = item ? parseInt(item) : 100; + + var stopClickAfterSlide = function(evt) { + L.DomEvent.stop(evt); + removeStopClickListeners(); + }; + var removeStopClickListeners = function() { + document.removeEventListener('click', stopClickAfterSlide, true); + document.removeEventListener('mousedown', removeStopClickListeners, true); + }; + + $(container).html(input); + $(container).attr('title', 'Set transparency of route track and markers'); + + input.slider({ + min: 0, + max: 100, + step: 1, + value: value, + orientation: 'vertical', + reversed : true, + selection: 'before', // inverted, serves as track style, see css + tooltip: 'hide' + }).on('slide slideStop', { self: this }, function (evt) { + evt.data.self.options.callback(evt.value / 100); + }).on('slideStop', function (evt) { + localStorage.opacitySliderValue = evt.value; + + // When dragging outside slider and over map, click event after mouseup + // adds marker when active on Chromium. So disable click (not needed) + // once after sliding. + document.addEventListener('click', stopClickAfterSlide, true); + // Firefox does not fire click event in this case, so make sure stop listener + // is always removed on next mousedown. + document.addEventListener('mousedown', removeStopClickListeners, true); + }); + + this.options.callback(value / 100); + + return container; + } +}); diff --git a/js/index.js b/js/index.js index 440091c..5955a83 100644 --- a/js/index.js +++ b/js/index.js @@ -217,6 +217,7 @@ trackCasing: { weight: 8, color: 'white', + // assumed to be same as track, see setOpacity opacity: 1 }, nodata: { @@ -276,6 +277,9 @@ nogos.addTo(map); routing.addTo(map); + map.addControl(new BR.OpacitySlider({ + callback: L.bind(routing.setOpacity, routing) + })); // initial option settings (after controls are added and initialized with onAdd, before permalink) router.setOptions(nogos.getOptions()); @@ -293,7 +297,7 @@ profile: profile }).addTo(map); } - + initMap(); initApp(); diff --git a/js/plugin/Routing.js b/js/plugin/Routing.js index ae9cc10..7a2a423 100644 --- a/js/plugin/Routing.js +++ b/js/plugin/Routing.js @@ -1,12 +1,14 @@ BR.Routing = L.Routing.extend({ options: { + position: 'topright', icons: { /* not implemented yet start: new L.Icon.Default({iconUrl: 'bower_components/leaflet-gpx/pin-icon-start.png'}), end: new L.Icon.Default(), normal: new L.Icon.Default() */ - draw: false + draw: false, + opacity: 1 }, snapping: null, zIndexOffset: -2000 @@ -20,6 +22,8 @@ BR.Routing = L.Routing.extend({ this._segments.on('layeradd', this._addSegmentCasing, this); this._segments.on('layerremove', this._removeSegmentCasing, this); + this._waypoints.on('layeradd', this._setMarkerOpacity, this); + // turn line mouse marker off while over waypoint marker this.on('waypoint:mouseover', function(e) { // L.Routing.Edit._segmentOnMouseout without firing 'segment:mouseout' (enables draw) @@ -102,6 +106,33 @@ BR.Routing = L.Routing.extend({ this._segmentsCasing.removeLayer(e.layer._casing); } + ,setOpacity: function(opacity) { + // Due to the second Polyline layer for casing, the combined opacity is less + // transparent than with a single layer and the slider is non-linear. The + // inverted formula is used to get the same result as with a single layer. + // SVG simple alpha compositing: Ca' = 1 - (1 - Ea) * (1 - Ca) + // http://www.w3.org/TR/SVG11/masking.html#SimpleAlphaBlending + var sourceOpacity = 1 - Math.sqrt(1 - opacity); + + this.options.styles.track.opacity = sourceOpacity; + this.options.styles.trackCasing.opacity = sourceOpacity; + this.options.icons.opacity = opacity; + + this._segments.setStyle({ + opacity: sourceOpacity + }); + this._segmentsCasing.setStyle({ + opacity: sourceOpacity + }); + this._waypoints.eachLayer(function(marker) { + marker.setOpacity(opacity); + }); + } + + ,_setMarkerOpacity: function(e) { + e.layer.setOpacity(this.options.icons.opacity); + } + ,_removeMarkerEvents: function(marker) { marker.off('mouseover', this._fireWaypointEvent, this); marker.off('mouseout' , this._fireWaypointEvent, this); @@ -186,7 +217,8 @@ BR.Routing = L.Routing.extend({ // animate dashed trailer as loading indicator if (m1 && m2) { loadingTrailer = new L.Polyline([m1.getLatLng(), m2.getLatLng()], { - opacity: 0.6, + color: this.options.styles.track.color, + opacity: this.options.styles.trailer.opacity, dashArray: [10, 10], className: 'loading-trailer' });