| <!-- The <crosshair-sk> custom element declaration. |
| |
| The crosshair-sk element works with the canvas-layers-sk element |
| and draws a crosshair on one of the given layers. Use the update_on |
| attribute to control when the crosshair is moved. |
| |
| Attributes: |
| x, y - The x,y coords of the crosshair, as measured in the natural |
| dimensions of the image. I.e. not in the coords of the img |
| if it has a different size as determined by CSS styling. |
| |
| target - The id of the canvas-layers-sk element we are drawing |
| a crosshair on. |
| |
| name - The name of the layer in the canvas-layer-sk element |
| we are drawing a crosshair on. |
| |
| hidden - If true then don't actually draw the crosshair. |
| |
| update_on - Update the crosshair: |
| "move" - when the mouse moves. |
| "click" - when the user clicks. |
| "none" - (default) do not update the crosshair based on user action. |
| |
| Events: |
| crosshair - Produced if the crosshair is moved by clicking in the image. |
| The coordinates of the click are in e.detail.x and e.detail.y. They |
| are in offsets from the image origin in the images natural dimensions. |
| That is, even if the image is sized using CSS the x and y values |
| will be reported as values in the images original size. |
| |
| Methods: |
| coordinatesUpdated - to be called if the x or y attribute was changed by other code. |
| prefer calling this and setting useObservers=false if you control all the places which modify |
| x and y. |
| |
| --> |
| <link rel="import" href="/res/imp/bower_components/polymer/polymer.html"> |
| <dom-module id="crosshair-sk"> |
| <template> |
| </template> |
| <script> |
| Polymer({ |
| is: 'crosshair-sk', |
| |
| properties: { |
| x: { |
| type: Number, |
| value: 0, |
| reflectToAttribute: true, |
| notify: true, |
| observer: "_updatedOneCoord", |
| }, |
| y: { |
| type: Number, |
| value: 0, |
| reflectToAttribute: true, |
| notify: true, |
| observer: "_updatedOneCoord", |
| }, |
| useObservers: { |
| type: Boolean, |
| value: true, |
| reflectToAttribute: true, |
| }, |
| target: { |
| type: String, |
| value: "", |
| observer: '_layerChanged', |
| reflectToAttribute: true |
| }, |
| hidden: { |
| type: Boolean, |
| value: false, |
| observer: '_hiddenChanged', |
| reflectToAttribute: true |
| }, |
| name: { |
| type: String, |
| value: "", |
| observer: '_layerChanged', |
| reflectToAttribute: true |
| }, |
| update_on: { |
| // Valid values are "click", "move", and "none". |
| type: String, |
| value: "none", |
| observer: '_registerCallbacks', |
| reflectToAttribute: true |
| }, |
| }, |
| |
| ready: function () { |
| // The current location of the crosshair, in CSS units. |
| this.thumb = { |
| x: 0, |
| y: 0, |
| }; |
| |
| // If we are tracking mousemove events we'll store the coords here. |
| this._move = { |
| x: 0, |
| y: 0, |
| }; |
| |
| // If we are tracking mousemove events we'll store the previous coords |
| // here, so we only redraw the crosshair if it's actually moved. |
| this._lastMove = { |
| x: 0, |
| y: 0, |
| }; |
| |
| this.willUpdate = null; |
| |
| this._xyChangedBound = this.coordinatesUpdated.bind(this); |
| this._moveEventHandlerBound = this._moveEventHandler.bind(this); |
| this._clickEventHandlerBound = this._clickEventHandler.bind(this); |
| this._moveToMouseBound = this._moveToMouse.bind(this); |
| |
| this._animRequest = null; |
| }, |
| |
| _updatedOneCoord() { |
| if (!this.useObservers) { return; } |
| // Wait and see if the other coordinate updates. |
| if (!this.willUpdate) { |
| this.willUpdate = setTimeout(() => { |
| this.coordinatesUpdated(); |
| }, 1); |
| } |
| }, |
| |
| coordinatesUpdated() { |
| this.willUpdate = null; |
| if (!this._img) { return; } |
| this._imgSize = this._standardSize(this._img); |
| this.thumb.x = this._imgSize.visibleWidth * (this.x / this._imgSize.naturalWidth); |
| this.thumb.y = this._imgSize.visibleHeight * (this.y / this._imgSize.naturalHeight); |
| this._drawCrosshair(); |
| this._event(); |
| }, |
| |
| // Returns both the visible and natural size of a canvas or image element, |
| // Natural size - the width and height of it in it's native resolution. |
| // Visible size - the width and height of it on the screen as controlled by css |
| _standardSize(element) { |
| if (element.nodeName === 'IMG') { |
| return { |
| 'visibleWidth': element.width, |
| 'visibleHeight': element.height, |
| 'naturalWidth': element.naturalWidth, |
| 'naturalHeight': element.naturalHeight, |
| }; |
| } else { |
| var strW = window.getComputedStyle(element, null).width; |
| var strH = window.getComputedStyle(element, null).height; |
| return { |
| // Trim 'px' off the end of the style string and convert to a number. |
| 'visibleWidth': parseFloat(strW.substring(0, strW.length-2)), |
| 'visibleHeight': parseFloat(strH.substring(0, strH.length-2)), |
| 'naturalWidth': element.width, |
| 'naturalHeight': element.height, |
| }; |
| } |
| }, |
| |
| _layerChanged: function() { |
| if (!(this.target && this.name)) { |
| return; |
| } |
| $$$("#" + this.target, this._findParent()).addEventListener( |
| 'canvas-layers-updated', this._canvasLayersUpdated.bind(this)); |
| }, |
| |
| _hiddenChanged: function() { |
| if (this._ctx) { |
| this._drawCrosshair(); |
| } |
| }, |
| |
| _findParent: function() { |
| var p = this.parentNode; |
| while (p.parentNode != null) { |
| p = p.parentNode; |
| } |
| return p |
| }, |
| |
| _canvasLayersUpdated: function() { |
| var layer = $$$("#" + this.target, Polymer.dom(this).parentNode); |
| if (this._canvas) { |
| // If we are switching to a new canvas then unregister all of our old callbacks. |
| this._unregisterAllCallbacks(); |
| } |
| this._canvas = layer.canvas(this.name); |
| this._img = layer.subject(); |
| if (this._canvas) { |
| this._registerCallbacks(); |
| this._ctx = this._canvas.getContext('2d'); |
| } |
| this._imgSize = this._standardSize(this._img); |
| }, |
| |
| _registerCallbacks: function() { |
| if (!this._canvas) { |
| return; |
| } |
| this._canvas.addEventListener('canvas-update', this._xyChangedBound); |
| if (this.update_on === "click") { |
| this._canvas.addEventListener('click', this._clickEventHandlerBound); |
| this._canvas.removeEventListener('mousemove', this._moveEventHandlerBound); |
| } else if (this.update_on === "move") { |
| this._canvas.addEventListener('mousemove', this._moveEventHandlerBound); |
| this._canvas.removeEventListener('click', this._clickEventHandlerBound); |
| } else { |
| this._canvas.removeEventListener('mousemove', this._moveEventHandlerBound); |
| this._canvas.removeEventListener('click', this._clickEventHandlerBound); |
| } |
| }, |
| |
| _unregisterAllCallbacks: function() { |
| this._canvas.removeEventListener('mousemove', this._moveEventHandlerBound); |
| this._canvas.removeEventListener('click', this._clickEventHandlerBound); |
| this._canvas.removeEventListener('canvas-update', this._xyChangedBound); |
| }, |
| |
| _moveEventHandler: function (e) { |
| this._move.x = e.clientX; |
| this._move.y = e.clientY; |
| |
| if (this._lastMove.x != this._move.x || this._lastMove.y != this._move.y) { |
| // Don't necessarily move it right now. Several more mouse move events may be triggered |
| // before it's time to draw the next frame. register or replace our callback with the |
| // window to move the crosshair before the next frame. |
| this._animRequest = window.requestAnimationFrame(this._moveToMouseBound); |
| this._lastMove.x = this._move.x; |
| this._lastMove.y = this._move.y; |
| } |
| }, |
| |
| // Moving to the last recorded mouse position. Registered as the callback |
| // of window.requestAnimationFrame |
| _moveToMouse(_) { |
| this._moveCrosshair(this._move.x, this._move.y); |
| }, |
| |
| // moving to a clicked position. |
| _clickEventHandler: function (e) { |
| this._moveCrosshair(e.clientX, e.clientY); |
| }, |
| |
| _moveCrosshair: function (clientX, clientY) { |
| if (!this._img.width || !this._img.height) { |
| return; |
| } |
| var p = sk.elePos(this._canvas); |
| this.thumb.x = clientX - p.x; |
| this.thumb.y = clientY - p.y; |
| this._drawCrosshair(); |
| // Cannot set properties atomically in Polymer 1 |
| this.x = Math.floor(this.thumb.x / this._imgSize.visibleWidth * this._imgSize.naturalWidth); |
| this.y = Math.floor(this.thumb.y / this._imgSize.visibleHeight * this._imgSize.naturalHeight); |
| this._event(); |
| }, |
| |
| _event: function() { |
| var detail = { |
| x: this.x, |
| y: this.y |
| }; |
| var evt = new CustomEvent('crosshair', { |
| detail: detail, |
| bubbles: true |
| }); |
| this.dispatchEvent(evt); |
| }, |
| |
| _drawCrosshair: function () { |
| this._ctx.clearRect(0, 0, this._ctx.canvas.width, this._ctx.canvas.height); |
| if (this.hidden) { |
| return; |
| } |
| this._ctx.lineWidth = 1; |
| this._ctx.strokeStyle = '#F00'; |
| this._ctx.beginPath(); |
| this._ctx.moveTo(0, this.thumb.y + 0.5); |
| this._ctx.lineTo(this._ctx.canvas.width, this.thumb.y + 0.5); |
| this._ctx.stroke(); |
| this._ctx.beginPath(); |
| this._ctx.moveTo(this.thumb.x + 0.5, 0); |
| this._ctx.lineTo(this.thumb.x + 0.5, this._ctx.canvas.height); |
| this._ctx.stroke(); |
| } |
| }); |
| </script> |
| </dom-module> |