measuring-tool.js (22087B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 const { 8 getCurrentZoom, 9 getWindowDimensions, 10 setIgnoreLayoutChanges, 11 } = require("resource://devtools/shared/layout/utils.js"); 12 const { 13 CanvasFrameAnonymousContentHelper, 14 } = require("resource://devtools/server/actors/highlighters/utils/markup.js"); 15 16 // Hard coded value about the size of measuring tool label, in order to 17 // position and flip it when is needed. 18 const LABEL_SIZE_MARGIN = 8; 19 const LABEL_SIZE_WIDTH = 80; 20 const LABEL_SIZE_HEIGHT = 52; 21 const LABEL_POS_MARGIN = 4; 22 const LABEL_POS_WIDTH = 40; 23 const LABEL_POS_HEIGHT = 34; 24 const LABEL_TYPE_SIZE = "size"; 25 const LABEL_TYPE_POSITION = "position"; 26 27 // List of all DOM Events subscribed directly to the document from the 28 // Measuring Tool highlighter 29 const DOM_EVENTS = [ 30 "mousedown", 31 "mousemove", 32 "mouseup", 33 "mouseleave", 34 "scroll", 35 "pagehide", 36 "keydown", 37 "keyup", 38 ]; 39 40 const SIDES = ["top", "right", "bottom", "left"]; 41 const HANDLERS = [...SIDES, "topleft", "topright", "bottomleft", "bottomright"]; 42 const HANDLER_SIZE = 6; 43 const HIGHLIGHTED_HANDLER_CLASSNAME = "highlight"; 44 45 const IS_OSX = Services.appinfo.OS === "Darwin"; 46 47 /** 48 * The MeasuringToolHighlighter is used to measure distances in a content page. 49 * It allows users to click and drag with their mouse to draw an area whose 50 * dimensions will be displayed in a tooltip next to it. 51 * This allows users to measure distances between elements on a page. 52 */ 53 class MeasuringToolHighlighter { 54 constructor(highlighterEnv) { 55 this.env = highlighterEnv; 56 this.markup = new CanvasFrameAnonymousContentHelper( 57 highlighterEnv, 58 this._buildMarkup.bind(this), 59 { 60 contentRootHostClassName: "devtools-highlighter-measuring-tool", 61 } 62 ); 63 this.isReady = this.markup.initialize(); 64 65 this.rect = { x: 0, y: 0, w: 0, h: 0 }; 66 this.mouseCoords = { x: 0, y: 0 }; 67 68 const { pageListenerTarget } = highlighterEnv; 69 70 // Register the measuring tool instance to all events we're interested in. 71 DOM_EVENTS.forEach(type => pageListenerTarget.addEventListener(type, this)); 72 } 73 74 _buildMarkup() { 75 const container = this.markup.createNode({ 76 attributes: { class: "highlighter-container" }, 77 }); 78 79 const root = this.markup.createNode({ 80 parent: container, 81 attributes: { 82 id: "measuring-tool-root", 83 class: "measuring-tool-root", 84 hidden: "true", 85 }, 86 }); 87 88 const svg = this.markup.createSVGNode({ 89 nodeType: "svg", 90 parent: root, 91 attributes: { 92 id: "measuring-tool-elements", 93 class: "measuring-tool-elements", 94 width: "100%", 95 height: "100%", 96 }, 97 }); 98 99 for (const side of SIDES) { 100 this.markup.createSVGNode({ 101 nodeType: "line", 102 parent: svg, 103 attributes: { 104 class: `measuring-tool-guide-${side}`, 105 id: `measuring-tool-guide-${side}`, 106 hidden: "true", 107 }, 108 }); 109 } 110 111 this.markup.createNode({ 112 nodeType: "label", 113 attributes: { 114 id: "measuring-tool-label-size", 115 class: "measuring-tool-label-size", 116 hidden: "true", 117 }, 118 parent: root, 119 }); 120 121 this.markup.createNode({ 122 nodeType: "label", 123 attributes: { 124 id: "measuring-tool-label-position", 125 class: "measuring-tool-label-position", 126 hidden: "true", 127 }, 128 parent: root, 129 }); 130 131 // Creating a <g> element in order to group all the paths below, that 132 // together represent the measuring tool; so that would be easier move them 133 // around 134 const g = this.markup.createSVGNode({ 135 nodeType: "g", 136 attributes: { 137 id: "measuring-tool-tool", 138 }, 139 parent: svg, 140 }); 141 142 this.markup.createSVGNode({ 143 nodeType: "path", 144 attributes: { 145 id: "measuring-tool-box-path", 146 class: "measuring-tool-box-path", 147 }, 148 parent: g, 149 }); 150 151 this.markup.createSVGNode({ 152 nodeType: "path", 153 attributes: { 154 id: "measuring-tool-diagonal-path", 155 class: "measuring-tool-diagonal-path", 156 }, 157 parent: g, 158 }); 159 160 for (const handler of HANDLERS) { 161 this.markup.createSVGNode({ 162 nodeType: "circle", 163 parent: g, 164 attributes: { 165 class: `measuring-tool-handler-${handler}`, 166 id: `measuring-tool-handler-${handler}`, 167 r: HANDLER_SIZE, 168 hidden: "true", 169 }, 170 }); 171 } 172 173 return container; 174 } 175 176 _update() { 177 const { window } = this.env; 178 179 setIgnoreLayoutChanges(true); 180 181 const zoom = getCurrentZoom(window); 182 183 const { width, height } = getWindowDimensions(window); 184 185 const { rect } = this; 186 187 const isZoomChanged = zoom !== rect.zoom; 188 189 if (isZoomChanged) { 190 rect.zoom = zoom; 191 this.updateLabel(); 192 } 193 194 const isDocumentSizeChanged = 195 width !== rect.documentWidth || height !== rect.documentHeight; 196 197 if (isDocumentSizeChanged) { 198 rect.documentWidth = width; 199 rect.documentHeight = height; 200 } 201 202 // If either the document's size or the zoom is changed since the last 203 // repaint, we update the tool's size as well. 204 if (isZoomChanged || isDocumentSizeChanged) { 205 this.updateViewport(); 206 } 207 208 setIgnoreLayoutChanges(false, window.document.documentElement); 209 210 this._rafID = window.requestAnimationFrame(() => this._update()); 211 } 212 213 _cancelUpdate() { 214 if (this._rafID) { 215 this.env.window.cancelAnimationFrame(this._rafID); 216 this._rafID = 0; 217 } 218 } 219 220 destroy() { 221 this.hide(); 222 223 this._cancelUpdate(); 224 225 const { pageListenerTarget } = this.env; 226 227 if (pageListenerTarget) { 228 DOM_EVENTS.forEach(type => 229 pageListenerTarget.removeEventListener(type, this) 230 ); 231 } 232 233 this.markup.destroy(); 234 } 235 236 show() { 237 setIgnoreLayoutChanges(true); 238 239 this.getElement("measuring-tool-root").removeAttribute("hidden"); 240 241 this._update(); 242 243 setIgnoreLayoutChanges(false, this.env.window.document.documentElement); 244 } 245 246 hide() { 247 setIgnoreLayoutChanges(true); 248 249 this.hideLabel(LABEL_TYPE_SIZE); 250 this.hideLabel(LABEL_TYPE_POSITION); 251 252 this.getElement("measuring-tool-root").setAttribute("hidden", "true"); 253 254 this._cancelUpdate(); 255 256 setIgnoreLayoutChanges(false, this.env.window.document.documentElement); 257 } 258 259 getElement(id) { 260 return this.markup.getElement(id); 261 } 262 263 setSize(w, h) { 264 this.setRect(undefined, undefined, w, h); 265 } 266 267 setRect(x, y, w, h) { 268 const { rect } = this; 269 270 if (typeof x !== "undefined") { 271 rect.x = x; 272 } 273 274 if (typeof y !== "undefined") { 275 rect.y = y; 276 } 277 278 if (typeof w !== "undefined") { 279 rect.w = w; 280 } 281 282 if (typeof h !== "undefined") { 283 rect.h = h; 284 } 285 286 setIgnoreLayoutChanges(true); 287 288 if (this._dragging) { 289 this.updatePaths(); 290 this.updateHandlers(); 291 } 292 293 this.updateLabel(); 294 295 setIgnoreLayoutChanges(false, this.env.window.document.documentElement); 296 } 297 298 updatePaths() { 299 const { x, y, w, h } = this.rect; 300 const dir = `M0 0 L${w} 0 L${w} ${h} L0 ${h}z`; 301 302 // Adding correction to the line path, otherwise some pixels are drawn 303 // outside the main rectangle area. 304 const x1 = w > 0 ? 0.5 : 0; 305 const y1 = w < 0 && h < 0 ? -0.5 : 0; 306 const w1 = w + (h < 0 && w < 0 ? 0.5 : 0); 307 const h1 = h + (h > 0 && w > 0 ? -0.5 : 0); 308 309 const linedir = `M${x1} ${y1} L${w1} ${h1}`; 310 311 this.getElement("measuring-tool-box-path").setAttribute("d", dir); 312 this.getElement("measuring-tool-diagonal-path").setAttribute("d", linedir); 313 this.getElement("measuring-tool-tool").setAttribute( 314 "transform", 315 `translate(${x},${y})` 316 ); 317 } 318 319 updateLabel(type) { 320 type = type || (this._dragging ? LABEL_TYPE_SIZE : LABEL_TYPE_POSITION); 321 322 const isSizeLabel = type === LABEL_TYPE_SIZE; 323 324 const label = this.getElement(`measuring-tool-label-${type}`); 325 326 let origin = "top left"; 327 328 const { innerWidth, innerHeight, scrollX, scrollY } = this.env.window; 329 const { x: mouseX, y: mouseY } = this.mouseCoords; 330 let { x, y, w, h, zoom } = this.rect; 331 const scale = 1 / zoom; 332 333 w = w || 0; 334 h = h || 0; 335 x = x || 0; 336 y = y || 0; 337 if (type === LABEL_TYPE_SIZE) { 338 x += w; 339 y += h; 340 } else { 341 x = mouseX; 342 y = mouseY; 343 } 344 345 let labelMargin, labelHeight, labelWidth; 346 347 if (isSizeLabel) { 348 labelMargin = LABEL_SIZE_MARGIN; 349 labelWidth = LABEL_SIZE_WIDTH; 350 labelHeight = LABEL_SIZE_HEIGHT; 351 352 const d = Math.hypot(w, h).toFixed(2); 353 354 label.setTextContent(`W: ${Math.abs(w)} px 355 H: ${Math.abs(h)} px 356 ↘: ${d}px`); 357 } else { 358 labelMargin = LABEL_POS_MARGIN; 359 labelWidth = LABEL_POS_WIDTH; 360 labelHeight = LABEL_POS_HEIGHT; 361 362 label.setTextContent(`${mouseX} 363 ${mouseY}`); 364 } 365 366 // Size used to position properly the label 367 const labelBoxWidth = (labelWidth + labelMargin) * scale; 368 const labelBoxHeight = (labelHeight + labelMargin) * scale; 369 370 const isGoingLeft = w < scrollX; 371 const isSizeGoingLeft = isSizeLabel && isGoingLeft; 372 const isExceedingLeftMargin = x - labelBoxWidth < scrollX; 373 const isExceedingRightMargin = x + labelBoxWidth > innerWidth + scrollX; 374 const isExceedingTopMargin = y - labelBoxHeight < scrollY; 375 const isExceedingBottomMargin = y + labelBoxHeight > innerHeight + scrollY; 376 377 if ((isSizeGoingLeft && !isExceedingLeftMargin) || isExceedingRightMargin) { 378 x -= labelBoxWidth; 379 origin = "top right"; 380 } else { 381 x += labelMargin * scale; 382 } 383 384 if (isSizeLabel) { 385 y += isExceedingTopMargin ? labelMargin * scale : -labelBoxHeight; 386 } else { 387 y += isExceedingBottomMargin ? -labelBoxHeight : labelMargin * scale; 388 } 389 390 label.setAttribute( 391 "style", 392 ` 393 width: ${labelWidth}px; 394 height: ${labelHeight}px; 395 transform-origin: ${origin}; 396 transform: translate(${x}px,${y}px) scale(${scale}) 397 ` 398 ); 399 400 if (!isSizeLabel) { 401 const labelSize = this.getElement("measuring-tool-label-size"); 402 const style = labelSize.getAttribute("style"); 403 404 if (style) { 405 labelSize.setAttribute( 406 "style", 407 style.replace(/scale[^)]+\)/, `scale(${scale})`) 408 ); 409 } 410 } 411 } 412 413 updateViewport() { 414 const { devicePixelRatio } = this.env.window; 415 const { documentWidth, documentHeight, zoom } = this.rect; 416 417 // Because `devicePixelRatio` is affected by zoom (see bug 809788), 418 // in order to get the "real" device pixel ratio, we need divide by `zoom` 419 const pixelRatio = devicePixelRatio / zoom; 420 421 // The "real" device pixel ratio is used to calculate the max stroke 422 // width we can actually assign: on retina, for instance, it would be 0.5, 423 // where on non high dpi monitor would be 1. 424 const minWidth = 1 / pixelRatio; 425 const strokeWidth = minWidth / zoom; 426 427 this.getElement("measuring-tool-root").setAttribute( 428 "style", 429 `stroke-width:${strokeWidth}; 430 width:${documentWidth}px; 431 height:${documentHeight}px;` 432 ); 433 } 434 435 updateGuides() { 436 const { x, y, w, h } = this.rect; 437 438 let guide = this.getElement("measuring-tool-guide-top"); 439 440 guide.setAttribute("x1", "0"); 441 guide.setAttribute("y1", y); 442 guide.setAttribute("x2", "100%"); 443 guide.setAttribute("y2", y); 444 445 guide = this.getElement("measuring-tool-guide-right"); 446 447 guide.setAttribute("x1", x + w); 448 guide.setAttribute("y1", 0); 449 guide.setAttribute("x2", x + w); 450 guide.setAttribute("y2", "100%"); 451 452 guide = this.getElement("measuring-tool-guide-bottom"); 453 454 guide.setAttribute("x1", "0"); 455 guide.setAttribute("y1", y + h); 456 guide.setAttribute("x2", "100%"); 457 guide.setAttribute("y2", y + h); 458 459 guide = this.getElement("measuring-tool-guide-left"); 460 461 guide.setAttribute("x1", x); 462 guide.setAttribute("y1", 0); 463 guide.setAttribute("x2", x); 464 guide.setAttribute("y2", "100%"); 465 } 466 467 setHandlerPosition(handler, x, y) { 468 const handlerElement = this.getElement(`measuring-tool-handler-${handler}`); 469 handlerElement.setAttribute("cx", x); 470 handlerElement.setAttribute("cy", y); 471 } 472 473 updateHandlers() { 474 const { w, h } = this.rect; 475 476 this.setHandlerPosition("top", w / 2, 0); 477 this.setHandlerPosition("topright", w, 0); 478 this.setHandlerPosition("right", w, h / 2); 479 this.setHandlerPosition("bottomright", w, h); 480 this.setHandlerPosition("bottom", w / 2, h); 481 this.setHandlerPosition("bottomleft", 0, h); 482 this.setHandlerPosition("left", 0, h / 2); 483 this.setHandlerPosition("topleft", 0, 0); 484 } 485 486 showLabel(type) { 487 setIgnoreLayoutChanges(true); 488 489 this.getElement(`measuring-tool-label-${type}`).removeAttribute("hidden"); 490 491 setIgnoreLayoutChanges(false, this.env.window.document.documentElement); 492 } 493 494 hideLabel(type) { 495 setIgnoreLayoutChanges(true); 496 497 this.getElement(`measuring-tool-label-${type}`).setAttribute( 498 "hidden", 499 "true" 500 ); 501 502 setIgnoreLayoutChanges(false, this.env.window.document.documentElement); 503 } 504 505 showGuides() { 506 const prefix = "measuring-tool-guide-"; 507 508 for (const side of SIDES) { 509 this.markup.removeAttributeForElement(`${prefix + side}`, "hidden"); 510 } 511 } 512 513 hideGuides() { 514 const prefix = "measuring-tool-guide-"; 515 516 for (const side of SIDES) { 517 this.markup.setAttributeForElement(`${prefix + side}`, "hidden", "true"); 518 } 519 } 520 521 showHandler(id) { 522 const prefix = "measuring-tool-handler-"; 523 this.markup.removeAttributeForElement(prefix + id, "hidden"); 524 } 525 526 showHandlers() { 527 const prefix = "measuring-tool-handler-"; 528 529 for (const handler of HANDLERS) { 530 this.markup.removeAttributeForElement(prefix + handler, "hidden"); 531 } 532 } 533 534 hideAll() { 535 this.hideLabel(LABEL_TYPE_POSITION); 536 this.hideLabel(LABEL_TYPE_SIZE); 537 this.hideGuides(); 538 this.hideHandlers(); 539 } 540 541 showGuidesAndHandlers() { 542 // Shows the guides and handlers only if an actual area is selected 543 if (this.rect.w !== 0 && this.rect.h !== 0) { 544 this.updateGuides(); 545 this.showGuides(); 546 this.updateHandlers(); 547 this.showHandlers(); 548 } 549 } 550 551 hideHandlers() { 552 const prefix = "measuring-tool-handler-"; 553 554 for (const handler of HANDLERS) { 555 this.markup.setAttributeForElement(prefix + handler, "hidden", "true"); 556 } 557 } 558 559 handleEvent(event) { 560 const { target, type } = event; 561 562 switch (type) { 563 case "mousedown": { 564 if (event.button || this._dragging) { 565 return; 566 } 567 568 const isHandler = event.originalTarget.id.includes("handler"); 569 if (isHandler) { 570 this.handleResizingMouseDownEvent(event); 571 } else { 572 this.handleMouseDownEvent(event); 573 } 574 break; 575 } 576 case "mousemove": 577 if (this._dragging && this._dragging.handler) { 578 this.handleResizingMouseMoveEvent(event); 579 } else { 580 this.handleMouseMoveEvent(event); 581 } 582 break; 583 case "mouseup": 584 if (this._dragging) { 585 if (this._dragging.handler) { 586 this.handleResizingMouseUpEvent(); 587 } else { 588 this.handleMouseUpEvent(); 589 } 590 } 591 break; 592 case "mouseleave": { 593 if (!this._dragging) { 594 this.hideLabel(LABEL_TYPE_POSITION); 595 } 596 break; 597 } 598 case "scroll": { 599 this.hideLabel(LABEL_TYPE_POSITION); 600 break; 601 } 602 case "pagehide": { 603 // If a page hide event is triggered for current window's highlighter, hide the 604 // highlighter. 605 if (target.defaultView === this.env.window) { 606 this.destroy(); 607 } 608 break; 609 } 610 case "keydown": { 611 this.handleKeyDown(event); 612 break; 613 } 614 case "keyup": { 615 if (MeasuringToolHighlighter.#isResizeModifierPressed(event)) { 616 this.getElement("measuring-tool-handler-topleft").classList?.remove( 617 HIGHLIGHTED_HANDLER_CLASSNAME 618 ); 619 } 620 break; 621 } 622 } 623 } 624 625 handleMouseDownEvent(event) { 626 const { pageX, pageY } = event; 627 const { window } = this.env; 628 const elementId = `measuring-tool-tool`; 629 630 setIgnoreLayoutChanges(true); 631 632 this.markup.getElement(elementId)?.classList.add("dragging"); 633 634 this.hideAll(); 635 636 setIgnoreLayoutChanges(false, window.document.documentElement); 637 638 // Store all the initial values needed for drag & drop 639 this._dragging = { 640 handler: null, 641 x: pageX, 642 y: pageY, 643 }; 644 645 this.setRect(pageX, pageY, 0, 0); 646 } 647 648 handleMouseMoveEvent(event) { 649 const { pageX, pageY } = event; 650 const { mouseCoords } = this; 651 let { x, y, w, h } = this.rect; 652 let labelType; 653 654 if (this._dragging) { 655 w = pageX - x; 656 h = pageY - y; 657 658 this.setRect(x, y, w, h); 659 660 labelType = LABEL_TYPE_SIZE; 661 } else { 662 mouseCoords.x = pageX; 663 mouseCoords.y = pageY; 664 this.updateLabel(LABEL_TYPE_POSITION); 665 666 labelType = LABEL_TYPE_POSITION; 667 } 668 669 this.showLabel(labelType); 670 } 671 672 handleMouseUpEvent() { 673 setIgnoreLayoutChanges(true); 674 675 this.getElement("measuring-tool-tool").classList?.remove("dragging"); 676 677 this.showGuidesAndHandlers(); 678 679 setIgnoreLayoutChanges(false, this.env.window.document.documentElement); 680 this._dragging = null; 681 } 682 683 handleResizingMouseDownEvent(event) { 684 const { originalTarget, pageX, pageY } = event; 685 const { window } = this.env; 686 const prefix = "measuring-tool-handler-"; 687 const handler = originalTarget.id.replace(prefix, ""); 688 689 setIgnoreLayoutChanges(true); 690 691 this.markup.getElement(originalTarget.id)?.classList.add("dragging"); 692 693 this.hideAll(); 694 this.showHandler(handler); 695 696 // Set coordinates to the current measurement area's position 697 const [, x, y] = this.getElement("measuring-tool-tool") 698 .getAttribute("transform") 699 .match(/(\d+),(\d+)/); 700 this.setRect(Number(x), Number(y)); 701 702 setIgnoreLayoutChanges(false, window.document.documentElement); 703 704 // Store all the initial values needed for drag & drop 705 this._dragging = { 706 handler, 707 x: pageX, 708 y: pageY, 709 }; 710 } 711 712 handleResizingMouseMoveEvent(event) { 713 const { pageX, pageY } = event; 714 const { rect } = this; 715 let { x, y, w, h } = rect; 716 717 const { handler } = this._dragging; 718 719 switch (handler) { 720 case "top": 721 y = pageY; 722 h = rect.y + rect.h - pageY; 723 break; 724 case "topright": 725 y = pageY; 726 w = pageX - rect.x; 727 h = rect.y + rect.h - pageY; 728 break; 729 case "right": 730 w = pageX - rect.x; 731 break; 732 case "bottomright": 733 w = pageX - rect.x; 734 h = pageY - rect.y; 735 break; 736 case "bottom": 737 h = pageY - rect.y; 738 break; 739 case "bottomleft": 740 x = pageX; 741 w = rect.x + rect.w - pageX; 742 h = pageY - rect.y; 743 break; 744 case "left": 745 x = pageX; 746 w = rect.x + rect.w - pageX; 747 break; 748 case "topleft": 749 x = pageX; 750 y = pageY; 751 w = rect.x + rect.w - pageX; 752 h = rect.y + rect.h - pageY; 753 break; 754 } 755 756 this.setRect(x, y, w, h); 757 758 // Changes the resizing cursors in case the measuring box is mirrored 759 const isMirrored = 760 (rect.w < 0 || rect.h < 0) && !(rect.w < 0 && rect.h < 0); 761 this.getElement("measuring-tool-tool").classList.toggle( 762 "mirrored", 763 isMirrored 764 ); 765 766 this.showLabel("size"); 767 } 768 769 handleResizingMouseUpEvent() { 770 const { handler } = this._dragging; 771 772 setIgnoreLayoutChanges(true); 773 774 this.getElement(`measuring-tool-handler-${handler}`).classList?.remove( 775 "dragging" 776 ); 777 this.showHandlers(); 778 779 this.showGuidesAndHandlers(); 780 781 setIgnoreLayoutChanges(false, this.env.window.document.documentElement); 782 this._dragging = null; 783 } 784 785 handleKeyDown(event) { 786 if (MeasuringToolHighlighter.#isResizeModifierPressed(event)) { 787 this.getElement("measuring-tool-handler-topleft").classList?.add( 788 HIGHLIGHTED_HANDLER_CLASSNAME 789 ); 790 } 791 792 if ( 793 !["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(event.key) 794 ) { 795 return; 796 } 797 798 const { x, y, w, h } = this.rect; 799 const modifier = event.shiftKey ? 10 : 1; 800 801 event.preventDefault(); 802 if (MeasuringToolHighlighter.#isResizeModifierHeld(event)) { 803 // If Ctrl (or Command on OS X) is held, resize the tool 804 switch (event.key) { 805 case "ArrowUp": 806 this.setSize(undefined, h - modifier); 807 break; 808 case "ArrowDown": 809 this.setSize(undefined, h + modifier); 810 break; 811 case "ArrowLeft": 812 this.setSize(w - modifier, undefined); 813 break; 814 case "ArrowRight": 815 this.setSize(w + modifier, undefined); 816 break; 817 } 818 } else { 819 // Arrow keys with no modifier move the tool 820 switch (event.key) { 821 case "ArrowUp": 822 this.setRect(undefined, y - modifier); 823 break; 824 case "ArrowDown": 825 this.setRect(undefined, y + modifier); 826 break; 827 case "ArrowLeft": 828 this.setRect(x - modifier, undefined); 829 break; 830 case "ArrowRight": 831 this.setRect(x + modifier, undefined); 832 break; 833 } 834 } 835 836 this.updatePaths(); 837 this.updateGuides(); 838 this.updateHandlers(); 839 this.updateLabel(LABEL_TYPE_SIZE); 840 } 841 842 static #isResizeModifierPressed(event) { 843 return ( 844 (!IS_OSX && event.key === "Control") || (IS_OSX && event.key === "Meta") 845 ); 846 } 847 848 static #isResizeModifierHeld(event) { 849 return (!IS_OSX && event.ctrlKey) || (IS_OSX && event.metaKey); 850 } 851 } 852 exports.MeasuringToolHighlighter = MeasuringToolHighlighter;