853 lines
21 KiB
JavaScript
853 lines
21 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
"use strict";
|
|
|
|
const EventEmitter = require("resource://devtools/shared/event-emitter.js");
|
|
const {
|
|
getCurrentZoom,
|
|
getWindowDimensions,
|
|
setIgnoreLayoutChanges,
|
|
} = require("resource://devtools/shared/layout/utils.js");
|
|
const {
|
|
CanvasFrameAnonymousContentHelper,
|
|
} = require("resource://devtools/server/actors/highlighters/utils/markup.js");
|
|
|
|
// Hard coded value about the size of measuring tool label, in order to
|
|
// position and flip it when is needed.
|
|
const LABEL_SIZE_MARGIN = 8;
|
|
const LABEL_SIZE_WIDTH = 80;
|
|
const LABEL_SIZE_HEIGHT = 52;
|
|
const LABEL_POS_MARGIN = 4;
|
|
const LABEL_POS_WIDTH = 40;
|
|
const LABEL_POS_HEIGHT = 34;
|
|
const LABEL_TYPE_SIZE = "size";
|
|
const LABEL_TYPE_POSITION = "position";
|
|
|
|
// List of all DOM Events subscribed directly to the document from the
|
|
// Measuring Tool highlighter
|
|
const DOM_EVENTS = [
|
|
"mousedown",
|
|
"mousemove",
|
|
"mouseup",
|
|
"mouseleave",
|
|
"scroll",
|
|
"pagehide",
|
|
"keydown",
|
|
"keyup",
|
|
];
|
|
|
|
const SIDES = ["top", "right", "bottom", "left"];
|
|
const HANDLERS = [...SIDES, "topleft", "topright", "bottomleft", "bottomright"];
|
|
const HANDLER_SIZE = 6;
|
|
const HIGHLIGHTED_HANDLER_CLASSNAME = "highlight";
|
|
|
|
const IS_OSX = Services.appinfo.OS === "Darwin";
|
|
|
|
/**
|
|
* The MeasuringToolHighlighter is used to measure distances in a content page.
|
|
* It allows users to click and drag with their mouse to draw an area whose
|
|
* dimensions will be displayed in a tooltip next to it.
|
|
* This allows users to measure distances between elements on a page.
|
|
*/
|
|
class MeasuringToolHighlighter {
|
|
constructor(highlighterEnv) {
|
|
this.env = highlighterEnv;
|
|
this.markup = new CanvasFrameAnonymousContentHelper(
|
|
highlighterEnv,
|
|
this._buildMarkup.bind(this)
|
|
);
|
|
this.isReady = this.markup.initialize();
|
|
|
|
this.rect = { x: 0, y: 0, w: 0, h: 0 };
|
|
this.mouseCoords = { x: 0, y: 0 };
|
|
|
|
const { pageListenerTarget } = highlighterEnv;
|
|
|
|
// Register the measuring tool instance to all events we're interested in.
|
|
DOM_EVENTS.forEach(type => pageListenerTarget.addEventListener(type, this));
|
|
}
|
|
|
|
ID_CLASS_PREFIX = "measuring-tool-";
|
|
|
|
_buildMarkup() {
|
|
const prefix = this.ID_CLASS_PREFIX;
|
|
|
|
const container = this.markup.createNode({
|
|
attributes: { class: "highlighter-container" },
|
|
});
|
|
|
|
const root = this.markup.createNode({
|
|
parent: container,
|
|
attributes: {
|
|
id: "root",
|
|
class: "root",
|
|
hidden: "true",
|
|
},
|
|
prefix,
|
|
});
|
|
|
|
const svg = this.markup.createSVGNode({
|
|
nodeType: "svg",
|
|
parent: root,
|
|
attributes: {
|
|
id: "elements",
|
|
class: "elements",
|
|
width: "100%",
|
|
height: "100%",
|
|
},
|
|
prefix,
|
|
});
|
|
|
|
for (const side of SIDES) {
|
|
this.markup.createSVGNode({
|
|
nodeType: "line",
|
|
parent: svg,
|
|
attributes: {
|
|
class: `guide-${side}`,
|
|
id: `guide-${side}`,
|
|
hidden: "true",
|
|
},
|
|
prefix,
|
|
});
|
|
}
|
|
|
|
this.markup.createNode({
|
|
nodeType: "label",
|
|
attributes: {
|
|
id: "label-size",
|
|
class: "label-size",
|
|
hidden: "true",
|
|
},
|
|
parent: root,
|
|
prefix,
|
|
});
|
|
|
|
this.markup.createNode({
|
|
nodeType: "label",
|
|
attributes: {
|
|
id: "label-position",
|
|
class: "label-position",
|
|
hidden: "true",
|
|
},
|
|
parent: root,
|
|
prefix,
|
|
});
|
|
|
|
// Creating a <g> element in order to group all the paths below, that
|
|
// together represent the measuring tool; so that would be easier move them
|
|
// around
|
|
const g = this.markup.createSVGNode({
|
|
nodeType: "g",
|
|
attributes: {
|
|
id: "tool",
|
|
},
|
|
parent: svg,
|
|
prefix,
|
|
});
|
|
|
|
this.markup.createSVGNode({
|
|
nodeType: "path",
|
|
attributes: {
|
|
id: "box-path",
|
|
class: "box-path",
|
|
},
|
|
parent: g,
|
|
prefix,
|
|
});
|
|
|
|
this.markup.createSVGNode({
|
|
nodeType: "path",
|
|
attributes: {
|
|
id: "diagonal-path",
|
|
class: "diagonal-path",
|
|
},
|
|
parent: g,
|
|
prefix,
|
|
});
|
|
|
|
for (const handler of HANDLERS) {
|
|
this.markup.createSVGNode({
|
|
nodeType: "circle",
|
|
parent: g,
|
|
attributes: {
|
|
class: `handler-${handler}`,
|
|
id: `handler-${handler}`,
|
|
r: HANDLER_SIZE,
|
|
hidden: "true",
|
|
},
|
|
prefix,
|
|
});
|
|
}
|
|
|
|
return container;
|
|
}
|
|
|
|
_update() {
|
|
const { window } = this.env;
|
|
|
|
setIgnoreLayoutChanges(true);
|
|
|
|
const zoom = getCurrentZoom(window);
|
|
|
|
const { width, height } = getWindowDimensions(window);
|
|
|
|
const { rect } = this;
|
|
|
|
const isZoomChanged = zoom !== rect.zoom;
|
|
|
|
if (isZoomChanged) {
|
|
rect.zoom = zoom;
|
|
this.updateLabel();
|
|
}
|
|
|
|
const isDocumentSizeChanged =
|
|
width !== rect.documentWidth || height !== rect.documentHeight;
|
|
|
|
if (isDocumentSizeChanged) {
|
|
rect.documentWidth = width;
|
|
rect.documentHeight = height;
|
|
}
|
|
|
|
// If either the document's size or the zoom is changed since the last
|
|
// repaint, we update the tool's size as well.
|
|
if (isZoomChanged || isDocumentSizeChanged) {
|
|
this.updateViewport();
|
|
}
|
|
|
|
setIgnoreLayoutChanges(false, window.document.documentElement);
|
|
|
|
this._rafID = window.requestAnimationFrame(() => this._update());
|
|
}
|
|
|
|
_cancelUpdate() {
|
|
if (this._rafID) {
|
|
this.env.window.cancelAnimationFrame(this._rafID);
|
|
this._rafID = 0;
|
|
}
|
|
}
|
|
|
|
destroy() {
|
|
this.hide();
|
|
|
|
this._cancelUpdate();
|
|
|
|
const { pageListenerTarget } = this.env;
|
|
|
|
if (pageListenerTarget) {
|
|
DOM_EVENTS.forEach(type =>
|
|
pageListenerTarget.removeEventListener(type, this)
|
|
);
|
|
}
|
|
|
|
this.markup.destroy();
|
|
|
|
EventEmitter.emit(this, "destroy");
|
|
}
|
|
|
|
show() {
|
|
setIgnoreLayoutChanges(true);
|
|
|
|
this.getElement("root").removeAttribute("hidden");
|
|
|
|
this._update();
|
|
|
|
setIgnoreLayoutChanges(false, this.env.window.document.documentElement);
|
|
}
|
|
|
|
hide() {
|
|
setIgnoreLayoutChanges(true);
|
|
|
|
this.hideLabel(LABEL_TYPE_SIZE);
|
|
this.hideLabel(LABEL_TYPE_POSITION);
|
|
|
|
this.getElement("root").setAttribute("hidden", "true");
|
|
|
|
this._cancelUpdate();
|
|
|
|
setIgnoreLayoutChanges(false, this.env.window.document.documentElement);
|
|
}
|
|
|
|
getElement(id) {
|
|
return this.markup.getElement(this.ID_CLASS_PREFIX + id);
|
|
}
|
|
|
|
setSize(w, h) {
|
|
this.setRect(undefined, undefined, w, h);
|
|
}
|
|
|
|
setRect(x, y, w, h) {
|
|
const { rect } = this;
|
|
|
|
if (typeof x !== "undefined") {
|
|
rect.x = x;
|
|
}
|
|
|
|
if (typeof y !== "undefined") {
|
|
rect.y = y;
|
|
}
|
|
|
|
if (typeof w !== "undefined") {
|
|
rect.w = w;
|
|
}
|
|
|
|
if (typeof h !== "undefined") {
|
|
rect.h = h;
|
|
}
|
|
|
|
setIgnoreLayoutChanges(true);
|
|
|
|
if (this._dragging) {
|
|
this.updatePaths();
|
|
this.updateHandlers();
|
|
}
|
|
|
|
this.updateLabel();
|
|
|
|
setIgnoreLayoutChanges(false, this.env.window.document.documentElement);
|
|
}
|
|
|
|
updatePaths() {
|
|
const { x, y, w, h } = this.rect;
|
|
const dir = `M0 0 L${w} 0 L${w} ${h} L0 ${h}z`;
|
|
|
|
// Adding correction to the line path, otherwise some pixels are drawn
|
|
// outside the main rectangle area.
|
|
const x1 = w > 0 ? 0.5 : 0;
|
|
const y1 = w < 0 && h < 0 ? -0.5 : 0;
|
|
const w1 = w + (h < 0 && w < 0 ? 0.5 : 0);
|
|
const h1 = h + (h > 0 && w > 0 ? -0.5 : 0);
|
|
|
|
const linedir = `M${x1} ${y1} L${w1} ${h1}`;
|
|
|
|
this.getElement("box-path").setAttribute("d", dir);
|
|
this.getElement("diagonal-path").setAttribute("d", linedir);
|
|
this.getElement("tool").setAttribute("transform", `translate(${x},${y})`);
|
|
}
|
|
|
|
updateLabel(type) {
|
|
type = type || (this._dragging ? LABEL_TYPE_SIZE : LABEL_TYPE_POSITION);
|
|
|
|
const isSizeLabel = type === LABEL_TYPE_SIZE;
|
|
|
|
const label = this.getElement(`label-${type}`);
|
|
|
|
let origin = "top left";
|
|
|
|
const { innerWidth, innerHeight, scrollX, scrollY } = this.env.window;
|
|
const { x: mouseX, y: mouseY } = this.mouseCoords;
|
|
let { x, y, w, h, zoom } = this.rect;
|
|
const scale = 1 / zoom;
|
|
|
|
w = w || 0;
|
|
h = h || 0;
|
|
x = x || 0;
|
|
y = y || 0;
|
|
if (type === LABEL_TYPE_SIZE) {
|
|
x += w;
|
|
y += h;
|
|
} else {
|
|
x = mouseX;
|
|
y = mouseY;
|
|
}
|
|
|
|
let labelMargin, labelHeight, labelWidth;
|
|
|
|
if (isSizeLabel) {
|
|
labelMargin = LABEL_SIZE_MARGIN;
|
|
labelWidth = LABEL_SIZE_WIDTH;
|
|
labelHeight = LABEL_SIZE_HEIGHT;
|
|
|
|
const d = Math.hypot(w, h).toFixed(2);
|
|
|
|
label.setTextContent(`W: ${Math.abs(w)} px
|
|
H: ${Math.abs(h)} px
|
|
↘: ${d}px`);
|
|
} else {
|
|
labelMargin = LABEL_POS_MARGIN;
|
|
labelWidth = LABEL_POS_WIDTH;
|
|
labelHeight = LABEL_POS_HEIGHT;
|
|
|
|
label.setTextContent(`${mouseX}
|
|
${mouseY}`);
|
|
}
|
|
|
|
// Size used to position properly the label
|
|
const labelBoxWidth = (labelWidth + labelMargin) * scale;
|
|
const labelBoxHeight = (labelHeight + labelMargin) * scale;
|
|
|
|
const isGoingLeft = w < scrollX;
|
|
const isSizeGoingLeft = isSizeLabel && isGoingLeft;
|
|
const isExceedingLeftMargin = x - labelBoxWidth < scrollX;
|
|
const isExceedingRightMargin = x + labelBoxWidth > innerWidth + scrollX;
|
|
const isExceedingTopMargin = y - labelBoxHeight < scrollY;
|
|
const isExceedingBottomMargin = y + labelBoxHeight > innerHeight + scrollY;
|
|
|
|
if ((isSizeGoingLeft && !isExceedingLeftMargin) || isExceedingRightMargin) {
|
|
x -= labelBoxWidth;
|
|
origin = "top right";
|
|
} else {
|
|
x += labelMargin * scale;
|
|
}
|
|
|
|
if (isSizeLabel) {
|
|
y += isExceedingTopMargin ? labelMargin * scale : -labelBoxHeight;
|
|
} else {
|
|
y += isExceedingBottomMargin ? -labelBoxHeight : labelMargin * scale;
|
|
}
|
|
|
|
label.setAttribute(
|
|
"style",
|
|
`
|
|
width: ${labelWidth}px;
|
|
height: ${labelHeight}px;
|
|
transform-origin: ${origin};
|
|
transform: translate(${x}px,${y}px) scale(${scale})
|
|
`
|
|
);
|
|
|
|
if (!isSizeLabel) {
|
|
const labelSize = this.getElement("label-size");
|
|
const style = labelSize.getAttribute("style");
|
|
|
|
if (style) {
|
|
labelSize.setAttribute(
|
|
"style",
|
|
style.replace(/scale[^)]+\)/, `scale(${scale})`)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
updateViewport() {
|
|
const { devicePixelRatio } = this.env.window;
|
|
const { documentWidth, documentHeight, zoom } = this.rect;
|
|
|
|
// Because `devicePixelRatio` is affected by zoom (see bug 809788),
|
|
// in order to get the "real" device pixel ratio, we need divide by `zoom`
|
|
const pixelRatio = devicePixelRatio / zoom;
|
|
|
|
// The "real" device pixel ratio is used to calculate the max stroke
|
|
// width we can actually assign: on retina, for instance, it would be 0.5,
|
|
// where on non high dpi monitor would be 1.
|
|
const minWidth = 1 / pixelRatio;
|
|
const strokeWidth = minWidth / zoom;
|
|
|
|
this.getElement("root").setAttribute(
|
|
"style",
|
|
`stroke-width:${strokeWidth};
|
|
width:${documentWidth}px;
|
|
height:${documentHeight}px;`
|
|
);
|
|
}
|
|
|
|
updateGuides() {
|
|
const { x, y, w, h } = this.rect;
|
|
|
|
let guide = this.getElement("guide-top");
|
|
|
|
guide.setAttribute("x1", "0");
|
|
guide.setAttribute("y1", y);
|
|
guide.setAttribute("x2", "100%");
|
|
guide.setAttribute("y2", y);
|
|
|
|
guide = this.getElement("guide-right");
|
|
|
|
guide.setAttribute("x1", x + w);
|
|
guide.setAttribute("y1", 0);
|
|
guide.setAttribute("x2", x + w);
|
|
guide.setAttribute("y2", "100%");
|
|
|
|
guide = this.getElement("guide-bottom");
|
|
|
|
guide.setAttribute("x1", "0");
|
|
guide.setAttribute("y1", y + h);
|
|
guide.setAttribute("x2", "100%");
|
|
guide.setAttribute("y2", y + h);
|
|
|
|
guide = this.getElement("guide-left");
|
|
|
|
guide.setAttribute("x1", x);
|
|
guide.setAttribute("y1", 0);
|
|
guide.setAttribute("x2", x);
|
|
guide.setAttribute("y2", "100%");
|
|
}
|
|
|
|
setHandlerPosition(handler, x, y) {
|
|
const handlerElement = this.getElement(`handler-${handler}`);
|
|
handlerElement.setAttribute("cx", x);
|
|
handlerElement.setAttribute("cy", y);
|
|
}
|
|
|
|
updateHandlers() {
|
|
const { w, h } = this.rect;
|
|
|
|
this.setHandlerPosition("top", w / 2, 0);
|
|
this.setHandlerPosition("topright", w, 0);
|
|
this.setHandlerPosition("right", w, h / 2);
|
|
this.setHandlerPosition("bottomright", w, h);
|
|
this.setHandlerPosition("bottom", w / 2, h);
|
|
this.setHandlerPosition("bottomleft", 0, h);
|
|
this.setHandlerPosition("left", 0, h / 2);
|
|
this.setHandlerPosition("topleft", 0, 0);
|
|
}
|
|
|
|
showLabel(type) {
|
|
setIgnoreLayoutChanges(true);
|
|
|
|
this.getElement(`label-${type}`).removeAttribute("hidden");
|
|
|
|
setIgnoreLayoutChanges(false, this.env.window.document.documentElement);
|
|
}
|
|
|
|
hideLabel(type) {
|
|
setIgnoreLayoutChanges(true);
|
|
|
|
this.getElement(`label-${type}`).setAttribute("hidden", "true");
|
|
|
|
setIgnoreLayoutChanges(false, this.env.window.document.documentElement);
|
|
}
|
|
|
|
showGuides() {
|
|
const prefix = this.ID_CLASS_PREFIX + "guide-";
|
|
|
|
for (const side of SIDES) {
|
|
this.markup.removeAttributeForElement(`${prefix + side}`, "hidden");
|
|
}
|
|
}
|
|
|
|
hideGuides() {
|
|
const prefix = this.ID_CLASS_PREFIX + "guide-";
|
|
|
|
for (const side of SIDES) {
|
|
this.markup.setAttributeForElement(`${prefix + side}`, "hidden", "true");
|
|
}
|
|
}
|
|
|
|
showHandler(id) {
|
|
const prefix = this.ID_CLASS_PREFIX + "handler-";
|
|
this.markup.removeAttributeForElement(prefix + id, "hidden");
|
|
}
|
|
|
|
showHandlers() {
|
|
const prefix = this.ID_CLASS_PREFIX + "handler-";
|
|
|
|
for (const handler of HANDLERS) {
|
|
this.markup.removeAttributeForElement(prefix + handler, "hidden");
|
|
}
|
|
}
|
|
|
|
hideAll() {
|
|
this.hideLabel(LABEL_TYPE_POSITION);
|
|
this.hideLabel(LABEL_TYPE_SIZE);
|
|
this.hideGuides();
|
|
this.hideHandlers();
|
|
}
|
|
|
|
showGuidesAndHandlers() {
|
|
// Shows the guides and handlers only if an actual area is selected
|
|
if (this.rect.w !== 0 && this.rect.h !== 0) {
|
|
this.updateGuides();
|
|
this.showGuides();
|
|
this.updateHandlers();
|
|
this.showHandlers();
|
|
}
|
|
}
|
|
|
|
hideHandlers() {
|
|
const prefix = this.ID_CLASS_PREFIX + "handler-";
|
|
|
|
for (const handler of HANDLERS) {
|
|
this.markup.setAttributeForElement(prefix + handler, "hidden", "true");
|
|
}
|
|
}
|
|
|
|
handleEvent(event) {
|
|
const { target, type } = event;
|
|
|
|
switch (type) {
|
|
case "mousedown":
|
|
if (event.button || this._dragging) {
|
|
return;
|
|
}
|
|
|
|
const isHandler = event.originalTarget.id.includes("handler");
|
|
if (isHandler) {
|
|
this.handleResizingMouseDownEvent(event);
|
|
} else {
|
|
this.handleMouseDownEvent(event);
|
|
}
|
|
break;
|
|
case "mousemove":
|
|
if (this._dragging && this._dragging.handler) {
|
|
this.handleResizingMouseMoveEvent(event);
|
|
} else {
|
|
this.handleMouseMoveEvent(event);
|
|
}
|
|
break;
|
|
case "mouseup":
|
|
if (this._dragging) {
|
|
if (this._dragging.handler) {
|
|
this.handleResizingMouseUpEvent();
|
|
} else {
|
|
this.handleMouseUpEvent();
|
|
}
|
|
}
|
|
break;
|
|
case "mouseleave": {
|
|
if (!this._dragging) {
|
|
this.hideLabel(LABEL_TYPE_POSITION);
|
|
}
|
|
break;
|
|
}
|
|
case "scroll": {
|
|
this.hideLabel(LABEL_TYPE_POSITION);
|
|
break;
|
|
}
|
|
case "pagehide": {
|
|
// If a page hide event is triggered for current window's highlighter, hide the
|
|
// highlighter.
|
|
if (target.defaultView === this.env.window) {
|
|
this.destroy();
|
|
}
|
|
break;
|
|
}
|
|
case "keydown": {
|
|
this.handleKeyDown(event);
|
|
break;
|
|
}
|
|
case "keyup": {
|
|
if (MeasuringToolHighlighter.#isResizeModifierPressed(event)) {
|
|
this.getElement("handler-topleft").classList.remove(
|
|
HIGHLIGHTED_HANDLER_CLASSNAME
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
handleMouseDownEvent(event) {
|
|
const { pageX, pageY } = event;
|
|
const { window } = this.env;
|
|
const elementId = `${this.ID_CLASS_PREFIX}tool`;
|
|
|
|
setIgnoreLayoutChanges(true);
|
|
|
|
this.markup.getElement(elementId).classList.add("dragging");
|
|
|
|
this.hideAll();
|
|
|
|
setIgnoreLayoutChanges(false, window.document.documentElement);
|
|
|
|
// Store all the initial values needed for drag & drop
|
|
this._dragging = {
|
|
handler: null,
|
|
x: pageX,
|
|
y: pageY,
|
|
};
|
|
|
|
this.setRect(pageX, pageY, 0, 0);
|
|
}
|
|
|
|
handleMouseMoveEvent(event) {
|
|
const { pageX, pageY } = event;
|
|
const { mouseCoords } = this;
|
|
let { x, y, w, h } = this.rect;
|
|
let labelType;
|
|
|
|
if (this._dragging) {
|
|
w = pageX - x;
|
|
h = pageY - y;
|
|
|
|
this.setRect(x, y, w, h);
|
|
|
|
labelType = LABEL_TYPE_SIZE;
|
|
} else {
|
|
mouseCoords.x = pageX;
|
|
mouseCoords.y = pageY;
|
|
this.updateLabel(LABEL_TYPE_POSITION);
|
|
|
|
labelType = LABEL_TYPE_POSITION;
|
|
}
|
|
|
|
this.showLabel(labelType);
|
|
}
|
|
|
|
handleMouseUpEvent() {
|
|
setIgnoreLayoutChanges(true);
|
|
|
|
this.getElement("tool").classList.remove("dragging");
|
|
|
|
this.showGuidesAndHandlers();
|
|
|
|
setIgnoreLayoutChanges(false, this.env.window.document.documentElement);
|
|
this._dragging = null;
|
|
}
|
|
|
|
handleResizingMouseDownEvent(event) {
|
|
const { originalTarget, pageX, pageY } = event;
|
|
const { window } = this.env;
|
|
const prefix = this.ID_CLASS_PREFIX + "handler-";
|
|
const handler = originalTarget.id.replace(prefix, "");
|
|
|
|
setIgnoreLayoutChanges(true);
|
|
|
|
this.markup.getElement(originalTarget.id).classList.add("dragging");
|
|
|
|
this.hideAll();
|
|
this.showHandler(handler);
|
|
|
|
// Set coordinates to the current measurement area's position
|
|
const [, x, y] = this.getElement("tool")
|
|
.getAttribute("transform")
|
|
.match(/(\d+),(\d+)/);
|
|
this.setRect(Number(x), Number(y));
|
|
|
|
setIgnoreLayoutChanges(false, window.document.documentElement);
|
|
|
|
// Store all the initial values needed for drag & drop
|
|
this._dragging = {
|
|
handler,
|
|
x: pageX,
|
|
y: pageY,
|
|
};
|
|
}
|
|
|
|
handleResizingMouseMoveEvent(event) {
|
|
const { pageX, pageY } = event;
|
|
const { rect } = this;
|
|
let { x, y, w, h } = rect;
|
|
|
|
const { handler } = this._dragging;
|
|
|
|
switch (handler) {
|
|
case "top":
|
|
y = pageY;
|
|
h = rect.y + rect.h - pageY;
|
|
break;
|
|
case "topright":
|
|
y = pageY;
|
|
w = pageX - rect.x;
|
|
h = rect.y + rect.h - pageY;
|
|
break;
|
|
case "right":
|
|
w = pageX - rect.x;
|
|
break;
|
|
case "bottomright":
|
|
w = pageX - rect.x;
|
|
h = pageY - rect.y;
|
|
break;
|
|
case "bottom":
|
|
h = pageY - rect.y;
|
|
break;
|
|
case "bottomleft":
|
|
x = pageX;
|
|
w = rect.x + rect.w - pageX;
|
|
h = pageY - rect.y;
|
|
break;
|
|
case "left":
|
|
x = pageX;
|
|
w = rect.x + rect.w - pageX;
|
|
break;
|
|
case "topleft":
|
|
x = pageX;
|
|
y = pageY;
|
|
w = rect.x + rect.w - pageX;
|
|
h = rect.y + rect.h - pageY;
|
|
break;
|
|
}
|
|
|
|
this.setRect(x, y, w, h);
|
|
|
|
// Changes the resizing cursors in case the measuring box is mirrored
|
|
const isMirrored =
|
|
(rect.w < 0 || rect.h < 0) && !(rect.w < 0 && rect.h < 0);
|
|
this.getElement("tool").classList.toggle("mirrored", isMirrored);
|
|
|
|
this.showLabel("size");
|
|
}
|
|
|
|
handleResizingMouseUpEvent() {
|
|
const { handler } = this._dragging;
|
|
|
|
setIgnoreLayoutChanges(true);
|
|
|
|
this.getElement(`handler-${handler}`).classList.remove("dragging");
|
|
this.showHandlers();
|
|
|
|
this.showGuidesAndHandlers();
|
|
|
|
setIgnoreLayoutChanges(false, this.env.window.document.documentElement);
|
|
this._dragging = null;
|
|
}
|
|
|
|
handleKeyDown(event) {
|
|
if (MeasuringToolHighlighter.#isResizeModifierPressed(event)) {
|
|
this.getElement("handler-topleft").classList.add(
|
|
HIGHLIGHTED_HANDLER_CLASSNAME
|
|
);
|
|
}
|
|
|
|
if (
|
|
!["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(event.key)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
const { x, y, w, h } = this.rect;
|
|
const modifier = event.shiftKey ? 10 : 1;
|
|
|
|
event.preventDefault();
|
|
if (MeasuringToolHighlighter.#isResizeModifierHeld(event)) {
|
|
// If Ctrl (or Command on OS X) is held, resize the tool
|
|
switch (event.key) {
|
|
case "ArrowUp":
|
|
this.setSize(undefined, h - modifier);
|
|
break;
|
|
case "ArrowDown":
|
|
this.setSize(undefined, h + modifier);
|
|
break;
|
|
case "ArrowLeft":
|
|
this.setSize(w - modifier, undefined);
|
|
break;
|
|
case "ArrowRight":
|
|
this.setSize(w + modifier, undefined);
|
|
break;
|
|
}
|
|
} else {
|
|
// Arrow keys with no modifier move the tool
|
|
switch (event.key) {
|
|
case "ArrowUp":
|
|
this.setRect(undefined, y - modifier);
|
|
break;
|
|
case "ArrowDown":
|
|
this.setRect(undefined, y + modifier);
|
|
break;
|
|
case "ArrowLeft":
|
|
this.setRect(x - modifier, undefined);
|
|
break;
|
|
case "ArrowRight":
|
|
this.setRect(x + modifier, undefined);
|
|
break;
|
|
}
|
|
}
|
|
|
|
this.updatePaths();
|
|
this.updateGuides();
|
|
this.updateHandlers();
|
|
this.updateLabel(LABEL_TYPE_SIZE);
|
|
}
|
|
|
|
static #isResizeModifierPressed(event) {
|
|
return (
|
|
(!IS_OSX && event.key === "Control") || (IS_OSX && event.key === "Meta")
|
|
);
|
|
}
|
|
|
|
static #isResizeModifierHeld(event) {
|
|
return (!IS_OSX && event.ctrlKey) || (IS_OSX && event.metaKey);
|
|
}
|
|
}
|
|
exports.MeasuringToolHighlighter = MeasuringToolHighlighter;
|