From 974af634b5b1befc45c52e853be6ee210ed53c45 Mon Sep 17 00:00:00 2001 From: Daniel Garcia Briseno <94071409+dgarciabriseno@users.noreply.github.com> Date: Fri, 26 Jan 2024 10:25:08 -0500 Subject: [PATCH] Enable smooth zoom desktop (#520) This enables scaling images continuously on helioviewer, rather than having discrete zoom levels. * Remove references to old zoom controls * Implement mousewheel smooth zoom * Update mouse scale for smooth zoom * Remove stale comment * Keep markers from moving during zoom * Keep constant UI scale during zoom * Animate zoom in/out buttons * Fix computing viewport tile range * Fix zooming on focal points * Update movie to use proper viewport coords * Flip mousewheel direction * Add constant-size class, update trajectory UI scale. * Restore Config.js * Update trajectory lines z-index --- index.php | 1 + package-lock.json | 2 +- resources/build/build.xml | 4 +- resources/css/celestial-bodies.css | 6 +- resources/css/events.css | 2 + resources/js/Events/EventMarker.js | 4 +- resources/js/HelioviewerClient.js | 6 +- resources/js/HelioviewerEmbeddedClient.js | 6 +- resources/js/HelioviewerWebClient.js | 8 +- resources/js/Media/MovieManagerUI.js | 2 +- resources/js/Media/ScreenshotManagerUI.js | 2 +- .../js/Tiling/Manager/TileLayerManager.js | 11 +- resources/js/UI/ZoomControls.js | 149 ------------------ .../js/Viewport/CelestialBodiesSatellites.js | 18 ++- resources/js/Viewport/HelioviewerViewport.js | 8 +- .../Helper/HelioviewerMouseCoordinates.js | 2 +- .../js/Viewport/Helper/HelioviewerZoomer.js | 115 +++++++++++++- .../js/Viewport/Helper/MouseCoordinates.js | 36 +---- resources/js/Viewport/Helper/ScrollZoom.js | 65 ++++++++ .../Viewport/Helper/ViewportMovementHelper.js | 18 +-- 20 files changed, 231 insertions(+), 234 deletions(-) delete mode 100644 resources/js/UI/ZoomControls.js create mode 100644 resources/js/Viewport/Helper/ScrollZoom.js diff --git a/index.php b/index.php index 5e906462a..828d4106e 100644 --- a/index.php +++ b/index.php @@ -193,6 +193,7 @@ function attr($attr, $file) { } } ?> + > diff --git a/package-lock.json b/package-lock.json index 7a0e7288a..8588db5c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "html", + "name": "helioviewer.org", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/resources/build/build.xml b/resources/build/build.xml index ac595bba8..0d9cd1be7 100644 --- a/resources/build/build.xml +++ b/resources/build/build.xml @@ -42,7 +42,7 @@ concatenating JavaScript... - + concatenating CSS... @@ -90,7 +90,7 @@ concatenating JavaScript... - + concatenating CSS... diff --git a/resources/css/celestial-bodies.css b/resources/css/celestial-bodies.css index 77ec3576a..ce01d73ec 100644 --- a/resources/css/celestial-bodies.css +++ b/resources/css/celestial-bodies.css @@ -3,6 +3,7 @@ z-index: 100; text-shadow: 0px 0px 2px #000, 0px 0px 4px #000, 0px 0px 6px #000; white-space: nowrap; + transform-origin: top left; } .celestial-bodies-label:hover { @@ -15,7 +16,8 @@ } .hover-date-container { - transform : translateY(-90px) translateX(-45px) rotate(-45deg); + transform : translateY(-20px) rotate(-45deg); + transform-origin : bottom left; position : absolute; width : 210px; height : 20px; @@ -33,6 +35,7 @@ left : 0px; bottom : 0px; z-index : 200; + transform-origin: bottom left; } .observer-jstree { @@ -125,6 +128,7 @@ text-overflow: ellipsis; white-space: nowrap; width: auto; + transform-origin: top left; } .body-popup h1 { display: inline-block; diff --git a/resources/css/events.css b/resources/css/events.css index eafeda3fc..04bacbf86 100644 --- a/resources/css/events.css +++ b/resources/css/events.css @@ -25,6 +25,7 @@ height: 39px; position: absolute; width: 23px; + transform-origin: bottom; } .event-label, .event-label-hover { color: white; @@ -69,6 +70,7 @@ text-overflow: ellipsis; white-space: nowrap; width: 340px; + transform-origin: left top; } .event-popup h1 { display: inline-block; diff --git a/resources/js/Events/EventMarker.js b/resources/js/Events/EventMarker.js index b54d2710e..db584e971 100644 --- a/resources/js/Events/EventMarker.js +++ b/resources/js/Events/EventMarker.js @@ -62,7 +62,7 @@ var EventMarker = Class.extend( // Create event marker DOM node this.eventMarkerDomNode = $('
'); this.eventMarkerDomNode.attr({ - 'class' : "event-marker" + 'class' : "event-marker constant-size" }); var id = this.id; @@ -873,7 +873,7 @@ var EventMarker = Class.extend( this.eventPopupDomNode = $('
'); this.eventPopupDomNode.hide(); this.eventPopupDomNode.attr({ - 'class' : "event-popup" + 'class' : "event-popup constant-size" }); this.eventPopupDomNode.html(content); diff --git a/resources/js/HelioviewerClient.js b/resources/js/HelioviewerClient.js index c0baf45d6..63e35e27b 100644 --- a/resources/js/HelioviewerClient.js +++ b/resources/js/HelioviewerClient.js @@ -6,7 +6,7 @@ /*jslint browser: true, white: true, onevar: true, undef: true, nomen: false, eqeqeq: true, plusplus: true, bitwise: true, regexp: true, strict: true, newcap: true, immed: true, maxlen: 120, sub: true */ /*global document, window, $, Class, TooltipHelper, HelioviewerViewport, - KeyboardManager, SettingsLoader, ZoomControls, assignTouchHandlers + KeyboardManager, SettingsLoader, assignTouchHandlers */ "use strict"; @@ -27,6 +27,7 @@ var HelioviewerClient = Class.extend( this._checkBrowser(); // Determines browser support this.serverSettings = serverSettings; + this.zoomLevels = zoomLevels; Helioviewer.api = serverSettings['backEnd']; Helioviewer.dataType = "json"; @@ -98,7 +99,8 @@ var HelioviewerClient = Class.extend( marginTop : marginTop, marginBottom : marginBottom, warnMouseCoords: Helioviewer.userSettings.get( - 'notifications.coordinates') + 'notifications.coordinates'), + zoomLevels : this.zoomLevels }); }, diff --git a/resources/js/HelioviewerEmbeddedClient.js b/resources/js/HelioviewerEmbeddedClient.js index 81aa61312..d81c4f9f5 100644 --- a/resources/js/HelioviewerEmbeddedClient.js +++ b/resources/js/HelioviewerEmbeddedClient.js @@ -6,7 +6,7 @@ bitwise: true, regexp: true, strict: true, newcap: true, immed: true, maxlen: 120, sub: true */ /*global document, window, $, HelioviewerClient, TooltipHelper, HelioviewerViewport, KeyboardManager, Helioviewer, - SettingsLoader, ZoomControls, assignTouchHandlers */ + SettingsLoader, assignTouchHandlers */ "use strict"; var HelioviewerEmbeddedClient = HelioviewerClient.extend( /** @lends HelioviewerWebClient.prototype */ @@ -43,10 +43,6 @@ var HelioviewerEmbeddedClient = HelioviewerClient.extend( // Get available data sources and initialize viewport this._initViewport("body", date, 0, 0); - - // User Interface components - this.zoomControls = new ZoomControls('#zoomControls', imageScale, zoomLevels, - this.serverSettings.minImageScale, this.serverSettings.maxImageScale); }, /** diff --git a/resources/js/HelioviewerWebClient.js b/resources/js/HelioviewerWebClient.js index fa5df33a6..2f00529dc 100644 --- a/resources/js/HelioviewerWebClient.js +++ b/resources/js/HelioviewerWebClient.js @@ -8,8 +8,7 @@ /*global document, window, $, HelioviewerClient, ImageSelectTool, MovieBuilder, TooltipHelper, HelioviewerViewport, ScreenshotBuilder, ScreenshotHistory, MovieHistory, UserVideoGallery, MessageConsole, Helioviewer, - KeyboardManager, SettingsLoader, TimeControls, - ZoomControls, ScreenshotManagerUI, MovieManagerUI, assignTouchHandlers, + KeyboardManager, SettingsLoader, TimeControls, ScreenshotManagerUI, MovieManagerUI, assignTouchHandlers, TileLayerAccordion, VisualGlossary, _gaq */ "use strict"; var HelioviewerWebClient = HelioviewerClient.extend( @@ -111,11 +110,6 @@ var HelioviewerWebClient = HelioviewerClient.extend( this.messageConsole = new MessageConsole(); this.keyboard = new KeyboardManager(); - // User Interface components - this.zoomControls = new ZoomControls('#zoomControls', imageScale, - zoomLevels, this.serverSettings.minImageScale, - this.serverSettings.maxImageScale); - this.earthScale = new ImageScale(); this.displayBlogFeed(7, true); diff --git a/resources/js/Media/MovieManagerUI.js b/resources/js/Media/MovieManagerUI.js index c31545d1e..0e5295b57 100644 --- a/resources/js/Media/MovieManagerUI.js +++ b/resources/js/Media/MovieManagerUI.js @@ -210,7 +210,7 @@ var MovieManagerUI = MediaManagerUI.extend( */ _loadMovieQueueParameters: function (roi) { if (typeof roi === "undefined") { - roi = helioviewer.getZoomedRegionOfInterest(); + roi = helioviewer.getViewportRegionOfInterest(); } var layers = helioviewer.getVisibleLayers(roi); diff --git a/resources/js/Media/ScreenshotManagerUI.js b/resources/js/Media/ScreenshotManagerUI.js index db959ed44..02777b74a 100644 --- a/resources/js/Media/ScreenshotManagerUI.js +++ b/resources/js/Media/ScreenshotManagerUI.js @@ -167,7 +167,7 @@ var ScreenshotManagerUI = MediaManagerUI.extend( celestialBodiesTrajectories, screenshot, self = this; if (typeof roi === "undefined") { - roi = helioviewer.getZoomedRegionOfInterest(); + roi = helioviewer.getViewportRegionOfInterest(); } imageScale = helioviewer.getZoomedImageScale(); diff --git a/resources/js/Tiling/Manager/TileLayerManager.js b/resources/js/Tiling/Manager/TileLayerManager.js index 6ebf6f717..c42ee2134 100644 --- a/resources/js/Tiling/Manager/TileLayerManager.js +++ b/resources/js/Tiling/Manager/TileLayerManager.js @@ -131,7 +131,8 @@ var TileLayerManager = LayerManager.extend( var old, ts, self, vp; old = this.tileVisibilityRange; // Expand to fit tile increment - ts = this.tileSize; + let zoom = (Helioviewer.userSettings.get('mobileZoomScale') || 1); + ts = this.tileSize * zoom; vp = { top: vpCoords.top - ts - (vpCoords.top % ts), left: vpCoords.left - ts - (vpCoords.left % ts), @@ -141,10 +142,10 @@ var TileLayerManager = LayerManager.extend( // Indices to display (one subtracted from ends to account for "0th" tiles). this.tileVisibilityRange = { - xStart : vp.left / ts, - yStart : vp.top / ts, - xEnd : (vp.right / ts) - 1, - yEnd : (vp.bottom / ts) - 1 + xStart : Math.round(vp.left / ts), + yStart : Math.round(vp.top / ts), + xEnd : Math.round((vp.right / ts) - 1), + yEnd : Math.round((vp.bottom / ts) - 1) }; self = this; diff --git a/resources/js/UI/ZoomControls.js b/resources/js/UI/ZoomControls.js deleted file mode 100644 index 050e134db..000000000 --- a/resources/js/UI/ZoomControls.js +++ /dev/null @@ -1,149 +0,0 @@ -/** - * @fileOverview Contains the class definition for an ZoomControls class. - * @author Jeff Stys - * @author Keith Hughitt - */ -/*jslint browser: true, white: true, onevar: true, undef: true, nomen: false, -eqeqeq: true, plusplus: true, bitwise: true, regexp: false, strict: true, -newcap: true, immed: true, maxlen: 80, sub: true */ -/*globals $, Class */ -"use strict"; -var ZoomControls = Class.extend( - /** @lends ZoomControls.prototype */ - { - /** - * @constructs - * - * Creates a new ZoomControl - */ - init: function (id, imageScale, increments, minImageScale, maxImageScale) { - this.id = id; - this.imageScale = imageScale; - this.increments = increments; - this.minImageScale = minImageScale; - this.maxImageScale = maxImageScale; - - this.zoomInBtn = $('#zoom-in-button'); - this.zoomSlider = $('#zoomControlSlider'); - this.zoomOutBtn = $('#zoom-out-button'); - - this._initSlider(); - this._initEventHandlers(); - }, - - /** - * Adjusts the zoom-control slider - * - * @param {Integer} v The new zoom value. - */ - _onSlide: function (v) { - this._setImageScale(v); - }, - - /** - * Translates from jQuery slider values to zoom-levels, and updates the - * zoom-level. - * - * @param {Object} v jQuery slider value - */ - _setImageScale: function (v) { - $(document).trigger('image-scale-changed', [this.increments[v]]); - $(document).trigger('replot-celestial-objects'); - $(document).trigger('replot-event-markers'); - $(document).trigger('earth-scale'); - $(document).trigger('update-external-datasource-integration'); - }, - - /** - * @description Initializes zoom level slider - */ - _initSlider: function () { - var description, self = this; - - // Reverse orientation so that moving slider up zooms in - this.increments.reverse(); - - // Initialize slider - this.zoomSlider.slider({ - slide: function (event, slider) { - self._onSlide(slider.value); - //slider.handle = slider.value; - }, - min: 0, - max: this.increments.length - 1, - step: 1, - orientation: 'vertical', - value: $.inArray(this.imageScale, this.increments) - }); - - // Add tooltip - description = "Drag this handle up and down to zoom in and out of " + - "the displayed image."; - $("#zoomControlSlider > .ui-slider-handle").attr('title', description) - .qtip(); - }, - - /** - * @description Responds to zoom in button click - */ - _onZoomInBtnClick: function () { - var index = this.zoomSlider.slider("value") + 1; - - if (this.increments[index] >= this.minImageScale) { - this.zoomSlider.slider("value", index); - this._setImageScale(index); - } - }, - - /** - * @description Responds to zoom out button click - */ - _onZoomOutBtnClick: function () { - var index = this.zoomSlider.slider("value") - 1; - - if (this.increments[index] <= this.maxImageScale) { - this.zoomSlider.slider("value", index); - this._setImageScale(index); - } - }, - - /** - * Handles mouse-wheel movements - * - * @param {Event} event Event class - */ - _onMouseWheelMove: function (e, delta) { - if(scrollLock){ - return false; - } - - //Lock the scroll - scrollLock = true; - window.setTimeout(function(){ - scrollLock = false; - },500); - - if (delta > 0) { - this.zoomInBtn.click(); - } else { - this.zoomOutBtn.click(); - } - - return false; - }, - - /** - * @description Initializes zoom control-related event-handlers - */ - _initEventHandlers: function () { - this.zoomInBtn.click($.proxy(this._onZoomInBtnClick, this)); - this.zoomOutBtn.click($.proxy(this._onZoomOutBtnClick, this)); - - $("#helioviewer-viewport").mousewheel( - $.proxy(this._onMouseWheelMove, this)); - - $(document).bind("zoom-in", $.proxy(this._onZoomInBtnClick, this)) - .bind("zoom-out", $.proxy(this._onZoomOutBtnClick, this)); - - } -}); diff --git a/resources/js/Viewport/CelestialBodiesSatellites.js b/resources/js/Viewport/CelestialBodiesSatellites.js index cf6fc654b..200fd10c0 100644 --- a/resources/js/Viewport/CelestialBodiesSatellites.js +++ b/resources/js/Viewport/CelestialBodiesSatellites.js @@ -111,7 +111,7 @@ var CelestialBodiesSatellites = Class.extend( this.labelsContainer.attr('id','labels-container').css({'position' : 'absolute', 'z-index' : '250'}).appendTo(this.bodiesContainer); this.trajectoriesContainer = $('
');//new container for the trajectories - this.trajectoriesContainer.attr('id','trajectories-container').css({'position' : 'absolute'}).appendTo(this.bodiesContainer); + this.trajectoriesContainer.attr('id','trajectories-container').css({'position' : 'absolute', 'z-index': '0'}).appendTo(this.bodiesContainer); this.popupsContainer = $('
');//new container for info popups this.popupsContainer.attr('id','popups-container').css({'position' : 'absolute', 'z-index' : '350'}).appendTo(this.bodiesContainer); @@ -321,7 +321,7 @@ var CelestialBodiesSatellites = Class.extend( 'left' : ( currentPoint.x - Math.floor(pointBoundingBox/2) + 1 ) + 'px', 'top' : ( currentPoint.y - Math.floor(pointBoundingBox/2) + 1 ) + 'px', 'z-index' : 210+(pointRadius*2) - }).addClass('celestial-pointer').appendTo(trajectoryContainer); + }).addClass('celestial-pointer constant-size').appendTo(trajectoryContainer); var textDate = new Date(); textDate.setTime(timeAttribute); textDate = textDate.toUTCString().slice(5); @@ -330,7 +330,7 @@ var CelestialBodiesSatellites = Class.extend( var bodyTextDate = bodyCapitalized + ' on
' + textDate; var hoverDateContainer = $('
').attr({ 'id' : containerName+'-hover-date-'+point - }).addClass('hover-date-container').css({ + }).addClass('hover-date-container constant-size').css({ 'left' : currentPoint.x + 'px', 'bottom' : -currentPoint.y + 'px' }).html(bodyTextDate).hide().appendTo(trajectoryContainer); @@ -461,7 +461,7 @@ var CelestialBodiesSatellites = Class.extend( if($('#'+containerName).length == 0){//label container div does not exist yet var labelContainer = $('
');//make a new div labelContainer.attr({'id':containerName, 'time':currentTime});//set the id - labelContainer.addClass('celestial-bodies-label'); + labelContainer.addClass('celestial-bodies-label constant-size'); observerLabelsContainer.append(labelContainer);//append it to the observerLabelsContainer div }else{//label ontainer div exists firstRun = false; @@ -525,6 +525,7 @@ var CelestialBodiesSatellites = Class.extend( }, _replotCoordinates: function(){ + console.log("Called replot coordinates"); var self = this; var currentRequestTime = helioviewer.timeControls.getTimestamp(); @@ -567,6 +568,7 @@ var CelestialBodiesSatellites = Class.extend( } var observers = Object.keys(this.trajectories); + console.log(observers); for(var observer of observers){ var bodies = Object.keys(this.trajectories[observer]); for(var body of bodies){ @@ -631,7 +633,7 @@ var CelestialBodiesSatellites = Class.extend( 'left' : ( currentPoint.x - Math.floor(pointBoundingBox/2) + 1 ) + 'px', 'top' : ( currentPoint.y - Math.floor(pointBoundingBox/2) + 1 ) + 'px', 'z-index' : 210+(pointRadius*2) - }).addClass('celestial-pointer').appendTo(trajectoryContainer); + }).addClass('celestial-pointer constant-size').appendTo(trajectoryContainer); var textDate = new Date(); textDate.setTime(timeAttribute); textDate = textDate.toUTCString().slice(5); @@ -640,7 +642,7 @@ var CelestialBodiesSatellites = Class.extend( var bodyTextDate = bodyCapitalized + ' on
' + textDate; var hoverDateContainer = $('
').attr({ 'id' : containerName+'-hover-date-'+point - }).addClass('hover-date-container').css({ + }).addClass('hover-date-container constant-size').css({ 'left' : currentPoint.x + 'px', 'bottom' : -currentPoint.y + 'px' }).html(bodyTextDate).hide().appendTo(trajectoryContainer); @@ -822,7 +824,7 @@ var CelestialBodiesSatellites = Class.extend( } eventPopupDomNode.attr({ 'id' : observer + '_' + body + '_popup', - 'class' : "body-popup", + 'class' : "body-popup constant-size", 'time' : this.currentTime }); eventPopupDomNode.css({ @@ -891,7 +893,7 @@ var CelestialBodiesSatellites = Class.extend( id : "point-date-underline-container", width : '200', height : '200', - }).addClass('svg-underline').hide().appendTo(this.bodiesContainer); + }).addClass('svg-underline constant-size').hide().appendTo(this.bodiesContainer); //vertical line $(document.createElementNS('http://www.w3.org/2000/svg','line')).attr({ x1: 1, diff --git a/resources/js/Viewport/HelioviewerViewport.js b/resources/js/Viewport/HelioviewerViewport.js index 457496470..defb14b6d 100644 --- a/resources/js/Viewport/HelioviewerViewport.js +++ b/resources/js/Viewport/HelioviewerViewport.js @@ -52,7 +52,7 @@ var HelioviewerViewport = Class.extend( this.movementHelper = new ViewportMovementHelper(this.domNode, this.mouseCoords, centerX, centerY); this.pinchDetector = new PinchDetector(); - this.helioZoom = new HelioviewerZoomer(this.pinchDetector); + this.helioZoom = new HelioviewerZoomer(this.pinchDetector, this.zoomLevels); this.touchMover = new TouchMover(document.getElementById('toptouchlayer'), this.pinchDetector, $.proxy(this.movementHelper.moveViewport, this.movementHelper)); // toptouchlayer this.loadDataSources(); @@ -66,7 +66,7 @@ var HelioviewerViewport = Class.extend( * and resizes when done. */ loadDataSources: function () { - var callback, dataType, tileLayerAccordion, self = this; + var callback, self = this; callback = function (dataSources) { self.dataSources = dataSources; @@ -322,7 +322,7 @@ var HelioviewerViewport = Class.extend( // Pixel coordinates for the ROI edges coordinates = this.movementHelper.getViewportCoords(); - imageScale = this.getImageScale(); + imageScale = this.getZoomedImageScale(); // ROI Offset from solar center (in arc-seconds) offsetX = imageScale * ((coordinates.left + coordinates.right) / 2); @@ -443,7 +443,7 @@ var HelioviewerViewport = Class.extend( * Returns the middle time of all of the layers currently loaded */ getEarliestLayerDate: function () { - var startDate, endDate, difference, dates = []; + var startDate, dates = []; // Get the observation dates associated with each later $.each(this._tileLayerManager._layers, function (i, layer) { diff --git a/resources/js/Viewport/Helper/HelioviewerMouseCoordinates.js b/resources/js/Viewport/Helper/HelioviewerMouseCoordinates.js index db1514a87..562cf547d 100644 --- a/resources/js/Viewport/Helper/HelioviewerMouseCoordinates.js +++ b/resources/js/Viewport/Helper/HelioviewerMouseCoordinates.js @@ -146,7 +146,7 @@ var HelioviewerMouseCoordinates = MouseCoordinates.extend( } // Compute coordinates relative to top-left corner of the viewport - cartesian = this.computeMouseCoords(event.pageX, event.pageY); + cartesian = this.computeMouseCoords(event.clientX, event.clientY); // Arc-seconds if (this.mouseCoords === "arcseconds") { diff --git a/resources/js/Viewport/Helper/HelioviewerZoomer.js b/resources/js/Viewport/Helper/HelioviewerZoomer.js index f5c935051..3f6aff7e4 100644 --- a/resources/js/Viewport/Helper/HelioviewerZoomer.js +++ b/resources/js/Viewport/Helper/HelioviewerZoomer.js @@ -6,25 +6,55 @@ /** * @constructs * @param {PinchDetector} pinchDetector lib for detecting pinches + * @param {number[]} zoomLevels List of available zoom levels */ - constructor(pinchDetector) { + constructor(pinchDetector, zoomLevels) { this.pinchDetector = pinchDetector; + this.zoomLevels = zoomLevels.reverse(); + this._initZoomLevel(); this._initializePinchListeners(); this._zoomInBtn = document.getElementById('zoom-in-button'); + this._zoomInBtn.addEventListener('click', this._smoothZoomIn.bind(this)); this._zoomOutBtn = document.getElementById('zoom-out-button'); + this._zoomOutBtn.addEventListener('click', this._smoothZoomOut.bind(this)); this._mc = document.getElementById('moving-container'); this._sandbox = document.getElementById('sandbox'); this._scale = 1; this._anchor = {left: 0, top: 0}; this._last_size = 0; + this._css_rules = []; Helioviewer.userSettings.set('mobileZoomScale', 1); - $(document).bind("update-viewport", $.proxy(this.onUpdateViewport, this)) + } + + /** + * Initializes the zoom level + */ + _initZoomLevel() { + let imageScale = Helioviewer.userSettings.get("state.imageScale"); + let value = $.inArray(imageScale, this.zoomLevels) + this._zoomIndex = value; + } + + /** + * Updates the application's current zoom level. + * + * @param {number} level zoom index + */ + _setAppImageScale(level) { + $(document).trigger('image-scale-changed', [this.zoomLevels[level]]); + $(document).trigger('replot-celestial-objects'); + $(document).trigger('replot-event-markers'); + $(document).trigger('earth-scale'); + $(document).trigger('update-external-datasource-integration'); } _initializePinchListeners() { this.pinchDetector.addPinchStartListener($.proxy(this.pinchStart, this)); this.pinchDetector.addPinchUpdateListener($.proxy(this.pinchUpdate, this)); this.pinchDetector.addPinchEndListener($.proxy(this.pinchEnd, this)); + this._scrollzoom = new ScrollZoom(); + this._scrollzoom.onstart($.proxy(this.pinchStart, this)); + this._scrollzoom.onupdate($.proxy(this.pinchUpdate, this)); } /** @@ -69,9 +99,33 @@ $(document).on('image-scale-changed', null, null, closure); // Trigger the zoom if (isZoomIn) { - this._zoomInBtn.click(); + this._zoomIn(); } else { - this._zoomOutBtn.click(); + this._zoomOut(); + } + } + + /** + * Trigger application level zoom in. + * This increases the resolution of the displayed image + */ + _zoomIn() { + let nextValue = this._zoomIndex + 1; + if (nextValue < this.zoomLevels.length) { + this._zoomIndex = nextValue; + this._setAppImageScale(this._zoomIndex); + } + } + + /** + * Trigger application level zoom out. + * This decreases the resolution of the displayed image + */ + _zoomOut() { + let nextValue = this._zoomIndex - 1; + if (nextValue >= 0) { + this._zoomIndex = nextValue; + this._setAppImageScale(this._zoomIndex); } } @@ -111,11 +165,26 @@ Helioviewer.userSettings.set('mobileZoomScale', scale); this._scale = scale; this._mc.style.transform = "scale(" + this._scale + ")"; + this._updateUIScale(scale); this._updateReferenceScale(scale) + $(document).trigger('update-viewport'); } } } + _updateUIScale(scale) { + let scaleFactor = 1/scale; + /** @type {HTMLStyleElement} */ + let js_styles = document.getElementById('js-styles'); + this._css_rules.forEach((rule) => { + js_styles.sheet.deleteRule(rule); + }); + this._css_rules = []; + this._css_rules.push(js_styles.sheet.insertRule(`.constant-size { + scale: ${scaleFactor}; + }`)); + } + shiftViewportForNewAnchor(anchor) { let apparent_x = this.getApparentPosition(parseFloat(this._mc.style.left), this._scale, this._anchor.left); let apparent_y = this.getApparentPosition(parseFloat(this._mc.style.top), this._scale, this._anchor.top); @@ -189,4 +258,42 @@ getRealPosition(apparent, scale, anchor) { return apparent + (scale - 1) * anchor; } + + /** + * Automatically animate zooming. + * @param {number} factor The change in scale. + * @param {number} duration Length of animation in seconds + */ + _animateZoom(factor, duration) { + // Compute animation frame details. + let fps = 120; + let frame_delay = 1/fps; + let num_frames = fps * duration; + + // Compute the amount to change the scale each frame. + let target_scale = this._scale * factor; + let delta_scale = target_scale - this._scale; + let frame_delta = delta_scale / num_frames; + + let ticks = 0; + let interval = setInterval(() => { + this.setScale(this._scale + frame_delta); + ticks += 1; + if (ticks == num_frames) { clearInterval(interval); } + }, frame_delay) + } + + /** + * Executed when the zoom in button is clicked. + */ + _smoothZoomIn() { + this._animateZoom(2, 0.25); + } + + /** + * Executed when the zoom out button is clicked. + */ + _smoothZoomOut() { + this._animateZoom(0.5, 0.25); + } }; diff --git a/resources/js/Viewport/Helper/MouseCoordinates.js b/resources/js/Viewport/Helper/MouseCoordinates.js index b94599033..8fd39183c 100644 --- a/resources/js/Viewport/Helper/MouseCoordinates.js +++ b/resources/js/Viewport/Helper/MouseCoordinates.js @@ -80,42 +80,22 @@ var MouseCoordinates = Class.extend( /** * @description Computes the scaled mouse coordinates relative to the size and center of the Sun. - * - * Explanation: - * - * X = location of mouse-pointer - * V = viewport top-left corner - * S = sandbox top-left corner - * M = moving container top-let corner - * - * Each of the two-letter abbreviations represents the vector going from one - * location to the other. See wiki documentation below for more details. - * * @see http://helioviewer.org/wiki/Co-ordinate_System_I */ - computeMouseCoords: function (screenX, screenY) { - var VX, negSV, SV, SM, MX, scale, x, y; - - // Coordinates realtive to viewport top-left corner - VX = this.getRelativeCoords(screenX, screenY); - negSV = this.sandbox.position(); - SV = { - x: -negSV.left, - y: -negSV.top - }; - SM = this.movingContainer.position(); - MX = { - x: VX.x + (SV.x - SM.left), - y: VX.y + (SV.y - SM.top) + computeMouseCoords: function (clientX, clientY) { + let container = this.movingContainer[0].getBoundingClientRect(); + let mouse_pos = { + x: clientX - container.left, + y: clientY - container.top }; //scale let zoom = (Helioviewer.userSettings.get('mobileZoomScale') || 1); scale = this.imageScale / zoom; // TODO: Apply scaling fix depending on the current layer - x = scale * MX.x; - y = scale * MX.y; - let correctedCoord = this.correctCoordinate(this.imageScale, MX.x, -MX.y) + x = scale * mouse_pos.x; + y = scale * mouse_pos.y; + let correctedCoord = this.correctCoordinate(scale, mouse_pos.x, -mouse_pos.y) // Return scaled coords return { diff --git a/resources/js/Viewport/Helper/ScrollZoom.js b/resources/js/Viewport/Helper/ScrollZoom.js new file mode 100644 index 000000000..21c7a0a02 --- /dev/null +++ b/resources/js/Viewport/Helper/ScrollZoom.js @@ -0,0 +1,65 @@ +class ScrollZoom { + constructor() { + this._scrolling = false; + this._delta = 0; + this._timeout = undefined; + document.getElementById('sandbox').addEventListener("wheel", this._wheeling.bind(this)); + } + + /** + * Executed when the mousewheel is used + * @param {WheelEvent} event + */ + _wheeling(event) { + this._ClearTimeout(); + this._SetTimeout(); + if (!this._scrolling) { + this._StartScrolling(event); + } else { + this._UpdateScrolling(event); + } + } + + /** + * @param {WheelEvent} event + */ + _StartScrolling(event) { + this._scrolling = true; + if (this._onstart) { + let position = { left: event.pageX, top: event.pageY }; + this._onstart(position); + } + } + + /** + * @param {WheelEvent} event + */ + _UpdateScrolling(event) { + this._delta -= event.deltaY; + if (this._onupdate) { + this._onupdate(this._delta); + } + } + + _ClearTimeout() { + if (this._timeout) { + clearTimeout(this._timeout); + } + } + + _SetTimeout() { + this._timeout = setTimeout(() => { + this._scrolling = false; + this._timeout = undefined; + this._delta = 0; + }, 250); + } + + onstart(fn) { + this._onstart = fn; + } + + onupdate(fn) { + this._onupdate = fn; + } +} \ No newline at end of file diff --git a/resources/js/Viewport/Helper/ViewportMovementHelper.js b/resources/js/Viewport/Helper/ViewportMovementHelper.js index 0d0f477f4..15e2038a4 100644 --- a/resources/js/Viewport/Helper/ViewportMovementHelper.js +++ b/resources/js/Viewport/Helper/ViewportMovementHelper.js @@ -178,20 +178,12 @@ var ViewportMovementHelper = Class.extend( * @returns {Object} The coordinates for the top-left and bottom-right corners of the viewport */ getViewportCoords: function () { - var sb, mc, left, top, vpWidth, vpHeight; + let rect = this.movingContainer[0].getBoundingClientRect(); + let left = -rect.left; + let top = -rect.top; - sb = this.sandbox.position(); - // mc = this.movingContainer.position(); - mc = { - left: parseFloat(this.movingContainer[0].style.left), - top: parseFloat(this.movingContainer[0].style.top) - }; - - left = -(sb.left + mc.left); - top = -(sb.top + mc.top); - - vpWidth = this.domNode.width(); - vpHeight = this.domNode.height(); + let vpWidth = this.domNode.width(); + let vpHeight = this.domNode.height(); return { left: left,