diff --git a/src/editor/ImageViewer.js b/src/editor/ImageViewer.js index 9fa167bae5d..e4bf8a1585b 100644 --- a/src/editor/ImageViewer.js +++ b/src/editor/ImageViewer.js @@ -23,7 +23,7 @@ /*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */ -/*global define, $, Mustache */ +/*global define, $, window, Mustache */ define(function (require, exports, module) { "use strict"; @@ -36,15 +36,19 @@ define(function (require, exports, module) { StringUtils = require("utils/StringUtils"), FileSystem = require("filesystem/FileSystem"); - var _naturalWidth = 0; + var _naturalWidth = 0, + _scale = 100, + _scaleDivInfo = null; // coordinates of hidden scale sticker /** Update the scale element, i.e. on resize * @param {!string} currentWidth actual width of image in view */ function _updateScale(currentWidth) { if (currentWidth < _naturalWidth) { - var scale = Math.floor(currentWidth / _naturalWidth * 100); - $("#img-scale").text(scale + "%") + _scale = currentWidth / _naturalWidth * 100; + $("#img-scale").text(Math.floor(_scale) + "%") + // Keep the position of the image scale div relative to the image. + .css("left", $("#img-preview").position().left + 5) .show(); } else { $("#img-scale").hide(); @@ -70,6 +74,200 @@ define(function (require, exports, module) { } } + /** + * Check mouse entering/exiting the scale sticker. + * Hide it when entering and show it again when exiting. + * + * @param {number} offsetX mouse offset from the left of the previewing image + * @param {number} offsetY mouseoffset from the top of the previewing image + */ + function _handleMouseEnterOrExitScaleSticker(offsetX, offsetY) { + var imagePos = $("#img-preview").position(), + scaleDivPos = $("#img-scale").position(), + imgWidth = $("#img-preview").width(), + imgHeight = $("#img-preview").height(), + scaleDivLeft, + scaleDivTop, + scaleDivRight, + scaleDivBottom; + + if (_scaleDivInfo) { + scaleDivLeft = _scaleDivInfo.left; + scaleDivTop = _scaleDivInfo.top; + scaleDivRight = _scaleDivInfo.right; + scaleDivBottom = _scaleDivInfo.bottom; + + if ((imgWidth + imagePos.left) < scaleDivRight) { + scaleDivRight = imgWidth + imagePos.left; + } + + if ((imgHeight + imagePos.top) < scaleDivBottom) { + scaleDivBottom = imgHeight + imagePos.top; + } + + } else { + scaleDivLeft = scaleDivPos.left; + scaleDivTop = scaleDivPos.top; + scaleDivRight = $("#img-scale").width() + scaleDivLeft; + scaleDivBottom = $("#img-scale").height() + scaleDivTop; + } + + if (_scaleDivInfo) { + // See whether the cursor is no longer inside the hidden scale div. + // If so, show it again. + if ((offsetX < scaleDivLeft || offsetX > scaleDivRight) || + (offsetY < scaleDivTop || offsetY > scaleDivBottom)) { + _scaleDivInfo = null; + $("#img-scale").show(); + } + } else if ((offsetX >= scaleDivLeft && offsetX <= scaleDivRight) && + (offsetY >= scaleDivTop && offsetY <= scaleDivBottom)) { + // Handle mouse inside image scale div. + // But hide it only if the pixel under mouse is also in the image. + if (offsetX < (imagePos.left + imgWidth) && + offsetY < (imagePos.top + imgHeight)) { + // Remember image scale div coordinates before hiding it. + _scaleDivInfo = {left: scaleDivPos.left, + top: scaleDivPos.top, + right: scaleDivRight, + bottom: scaleDivBottom}; + $("#img-scale").hide(); + } + } + } + + /** + * Show image coordinates under the mouse cursor + * + * @param {MouseEvent} e mouse move event + */ + function _showImageTip(e) { + // Don't show image tip if _scale is close to zero. + // since we won't have enough room to show tip anyway. + if (Math.floor(_scale) === 0) { + return; + } + + var x = Math.floor(e.offsetX * 100 / _scale), + y = Math.floor(e.offsetY * 100 / _scale), + $target = $(e.target), + targetPos = $target.position(), + tipPos = $("#img-tip").position(), + imagePos = $("#img-preview").position(), + scaleDivPos = $("#img-scale").position(), + left = e.offsetX + imagePos.left, + top = e.offsetY + imagePos.top, + width = $("#img-preview").width(), + height = $("#img-preview").height(), + windowWidth = $(window).width(), + fourDigitImageWidth = _naturalWidth.toString().length === 4, + infoWidth1 = 112, // info div width 96px + vertical toolbar width 16px + infoWidth2 = 120, // info div width 104px (for 4-digit image width) + vertical toolbar width 16px + tipOffsetX = 10, // adjustment for info div left from x coordinate of cursor + tipOffsetY = -54, // adjustment for info div top from y coordinate of cursor + tipMinusOffsetX1 = -82, // for less than 4-digit image width + tipMinusOffsetX2 = -90; // for 4-digit image width + + // Adjust left, top, x and y based on which element contains the cursor. + // Return if the target element is no longer available as in the case of + // a vertical guide that has its left equals to zero. + if ($target.is(".img-guide")) { + if ($target.is("#vert-guide")) { + if (targetPos.left === 0) { + return; + } + left = targetPos.left; + x = Math.floor(left * 100 / _scale); + } else { + if (targetPos.top === 0) { + return; + } + top = targetPos.top; + y = Math.floor(top * 100 / _scale); + } + } else if (!$target.is("#img-preview")) { + if ($target.is("#img-scale")) { + left = scaleDivPos.left + e.offsetX; + top = scaleDivPos.top + e.offsetY; + x = Math.floor(left * 100 / _scale); + y = Math.floor(top * 100 / _scale); + } else if (tipPos.left && tipPos.top) { + // Cursor must be inside the image tip. + left = tipPos.left + e.offsetX; + top = tipPos.top + e.offsetY; + x = Math.floor(left * 100 / _scale); + y = Math.floor(top * 100 / _scale); + } else { + return; + } + } + + _handleMouseEnterOrExitScaleSticker(left, top); + if ($(e.target).is("#img-scale")) { + // If we're in the scale sticker, then just return. + return; + } + + // Check whether to show the image tip on the left. + if ((e.pageX + infoWidth1) > windowWidth || + (fourDigitImageWidth && (e.pageX + infoWidth2) > windowWidth)) { + tipOffsetX = fourDigitImageWidth ? tipMinusOffsetX2 : tipMinusOffsetX1; + } + + // For some reason we're getting -1 for e.offset when hovering over the very + // first pixel of a scaled image. So adjust x to 0 if it is negative. + if (x < 0) { + x = 0; + } + + $("#x-value").text(x + "px"); + $("#y-value").text(y + "px"); + + $("#img-tip").css({ + left: left + tipOffsetX, + top: top + tipOffsetY + }).show(); + + $("#horiz-guide").css({ + left: imagePos.left, + top: top, + width: width - 1 + }).show(); + + $("#vert-guide").css({ + left: left, + top: imagePos.top, + height: height - 1 + }).show(); + } + + /** + * Show image coordinates under the mouse cursor + * + * @param {MouseEvent} e mouse leave event + */ + function _hideImageTip(e) { + var $target = $(e.target), + targetPos = $target.position(), + imagePos = $("#img-preview").position(), + right = imagePos.left + $("#img-preview").width(), + bottom = imagePos.top + $("#img-preview").height(), + offsetX = e.offsetX, + offsetY = e.offsetY; + + if ($target.is(".img-guide")) { + offsetX = targetPos.left + offsetX; + offsetY = targetPos.top + offsetY; + } + + // Hide image tip and guides only if the cursor is outside of the image. + if (offsetX < imagePos.left || offsetX >= right || + offsetY < imagePos.top || offsetY >= bottom) { + $("#img-tip").hide(); + $(".img-guide").hide(); + } + } + /** * creates a DOM node to place in the editor-holder * in order to display an image. @@ -87,15 +285,21 @@ define(function (require, exports, module) { function _removeListeners() { $(PanelManager).off("editorAreaResize", _onEditorAreaResize); $(DocumentManager).off("fileNameChange", _onFileNameChange); + $("#img").off("mousemove", "#img-preview, #img-scale, #img-tip, .img-guide", _showImageTip) + .off("mouseleave", "#img-preview, #img-scale, #img-tip, .img-guide", _hideImageTip); } - /** Perform decorations on the view that require loading the image in the browser, - * i.e. getting actual and natural width and height andplacing the scale sticker - * @param {!string} fullPath path to the image file + /** + * Perform decorations on the view that require loading the image in the browser, + * i.e. getting actual and natural width and height andplacing the scale sticker + * @param {!string} fullPath path to the image file */ function render(fullPath) { var relPath = ProjectManager.makeProjectRelativeIfPossible(fullPath); + _scale = 100; // initialize to 100 + _scaleDivInfo = null; + $("#img-path").text(relPath) .attr("title", relPath); $("#img-preview").on("load", function () { @@ -104,6 +308,7 @@ define(function (require, exports, module) { var dimensionString = _naturalWidth + " × " + this.naturalHeight + " " + Strings.UNIT_PIXELS; // get image size var file = FileSystem.getFileForPath(fullPath); + var minimumPixels = 20; // for showing crosshair cursor file.stat(function (err, stat) { if (err) { $("#img-data").html(dimensionString); @@ -125,9 +330,25 @@ define(function (require, exports, module) { // listen to removal to stop listening to resize events $(EditorManager).on("removeCustomViewer", _removeListeners); $(DocumentManager).on("fileNameChange", _onFileNameChange); + + $("#img-tip").hide(); + $(".img-guide").hide(); + $("#img").on("mousemove", "#img-preview, #img-scale, #img-tip, .img-guide", _showImageTip) + .on("mouseleave", "#img-preview, #img-scale, #img-tip, .img-guide", _hideImageTip); + _updateScale($(this).width()); + minimumPixels = Math.floor(minimumPixels * 100 / _scale); + + // If the image size is too narrow in width or height, then + // show the crosshair cursor since guides are almost invisible + // in narrow images. + if (this.naturalWidth < minimumPixels || this.naturalHeight < minimumPixels) { + $("#img-preview").css("cursor", "crosshair"); + $(".img-guide").css("cursor", "crosshair"); + } }); } + exports.getCustomViewHolder = getCustomViewHolder; exports.render = render; }); diff --git a/src/htmlContent/image-holder.html b/src/htmlContent/image-holder.html index ee616d352bc..f82100825b3 100644 --- a/src/htmlContent/image-holder.html +++ b/src/htmlContent/image-holder.html @@ -7,6 +7,20 @@
+
+ + + + + + + + + +
x:
y:
+
+
+
\ No newline at end of file diff --git a/src/styles/brackets.less b/src/styles/brackets.less index c6c19d0137e..d2339b69daf 100644 --- a/src/styles/brackets.less +++ b/src/styles/brackets.less @@ -235,6 +235,7 @@ a, img { .box-align(center); background: @background-color-3 url('images/no_content_bg.svg') no-repeat center 45%; } + /* Image Preview */ #image-holder { overflow: hidden; @@ -291,17 +292,17 @@ a, img { #img-path::selection { background: @selection-color-focused; } - + #img { position: relative; - - /* These properties keep the img percentage sticky */ - display: inline-block; - min-width: 1px; } + .img-guide, + #img-preview { + cursor: none; + } + #img-scale { - display: none; display: block; position: absolute; top: 5px; @@ -313,6 +314,50 @@ a, img { border-radius: 3px; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); } + + #img-tip { + display: block; + position: absolute; + text-align: left; + white-space: nowrap; + padding: 6px 9px; + color: #fff; + background-color: rgba(0, 0, 0, 0.8); + font-size: 11px; + font-family: SourceCodePro; + line-height: 13px; + border-radius: 3px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.24); + } + + #x-value, + #y-value { + text-align: right; + } + + .tip-container { + border: 0; + } + + #horiz-guide { + background-image: url("images/horizontal-dash.svg"); + background-repeat: repeat-x; + width: 8px; + height: 1px; + } + + #vert-guide { + background-image: url("images/vertical-dash.svg"); + background-repeat: repeat-y; + width: 1px; + height: 8px; + } + + #horiz-guide, + #vert-guide { + position: absolute; + display: block; + } } .vert-resizer { diff --git a/src/styles/images/horizontal-dash.svg b/src/styles/images/horizontal-dash.svg new file mode 100644 index 00000000000..f5034b79866 --- /dev/null +++ b/src/styles/images/horizontal-dash.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/styles/images/vertical-dash.svg b/src/styles/images/vertical-dash.svg new file mode 100644 index 00000000000..d3234ee6c89 --- /dev/null +++ b/src/styles/images/vertical-dash.svg @@ -0,0 +1,4 @@ + + + +