Flex: Draw self-alignment at the container level in flex overlay This draws a thicker line where the align-items value of the flex container is, as well as an arrow (or 2 arrows) pointing to where alignment happens. Baseline alignment is missing as it will require specific support from the backend (the offset of the baseline). Backend CL: https://chromium-review.googlesource.com/c/chromium/src/+/2552282/ Screenshots: https://imgur.com/a/uvkKJIl Bug: 1139949 Change-Id: Ie9387714abc8b9953deb38e5b89f5346206dcc13 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2552427 Commit-Queue: Patrick Brosset <patrick.brosset@microsoft.com> Reviewed-by: Alex Rudenko <alexrudenko@chromium.org>
diff --git a/front_end/elements/StylesSidebarPane.js b/front_end/elements/StylesSidebarPane.js index c78f03a..c4773b8 100644 --- a/front_end/elements/StylesSidebarPane.js +++ b/front_end/elements/StylesSidebarPane.js
@@ -49,20 +49,18 @@ // Highlightable properties are those that can be hovered in the sidebar to trigger a specific // highlighting mode on the current element. const HIGHLIGHTABLE_PROPERTIES = [ - {property: 'padding', mode: 'padding'}, - {property: 'border', mode: 'border'}, - {property: 'margin', mode: 'margin'}, - {property: 'grid-gap', mode: 'gap'}, - {property: 'gap', mode: 'gap'}, - {property: 'grid-column-gap', mode: 'column-gap'}, - {property: 'grid-row-gap', mode: 'row-gap'}, - {property: 'column-gap', mode: 'column-gap'}, - {property: 'row-gap', mode: 'row-gap'}, - {property: 'grid-template-areas', mode: 'grid-areas'}, - {property: 'grid-template-columns', mode: 'grid-template-columns'}, - {property: 'grid-template-rows', mode: 'grid-template-rows'}, - {property: 'justify-content', mode: 'justify-content'}, - {property: 'align-content', mode: 'align-content'}, + {mode: 'padding', properties: ['padding']}, + {mode: 'border', properties: ['border']}, + {mode: 'margin', properties: ['margin']}, + {mode: 'gap', properties: ['gap', 'grid-gap']}, + {mode: 'column-gap', properties: ['column-gap', 'grid-column-gap']}, + {mode: 'row-gap', properties: ['row-gap', 'grid-row-gap']}, + {mode: 'grid-template-columns', properties: ['grid-template-columns']}, + {mode: 'grid-template-rows', properties: ['grid-template-rows']}, + {mode: 'grid-template-areas', properties: ['grid-areas']}, + {mode: 'justify-content', properties: ['justify-content']}, + {mode: 'align-content', properties: ['align-content']}, + {mode: 'align-items', properties: ['align-items']}, ]; /** @type {!StylesSidebarPane} */ @@ -554,8 +552,8 @@ const rule = treeElement.property.ownerStyle.parentRule; const selectorList = (rule instanceof SDK.CSSRule.CSSStyleRule) ? rule.selectorText() : undefined; - for (const {property, mode} of HIGHLIGHTABLE_PROPERTIES) { - if (!treeElement.name.startsWith(property)) { + for (const {properties, mode} of HIGHLIGHTABLE_PROPERTIES) { + if (!properties.includes(treeElement.name)) { continue; } const node = this.node();
diff --git a/front_end/sdk/OverlayModel.js b/front_end/sdk/OverlayModel.js index 3ce1d14..9daed1c 100644 --- a/front_end/sdk/OverlayModel.js +++ b/front_end/sdk/OverlayModel.js
@@ -547,7 +547,7 @@ pattern: Protocol.Overlay.LineStylePattern.Dashed, }, mainDistributedSpace: {hatchColor: Common.Color.PageHighlight.FlexContainerBorder.toProtocolRGBA()}, - crossDistributedSpace: {hatchColor: Common.Color.PageHighlight.FlexContainerBorder.toProtocolRGBA()} + crossDistributedSpace: {hatchColor: Common.Color.PageHighlight.FlexContainerBorder.toProtocolRGBA()}, }; } } @@ -618,6 +618,20 @@ }; } + if (mode === 'align-items' && this._flexFeaturesExperimentEnabled) { + highlightConfig.flexContainerHighlightConfig = { + containerBorder: { + color: Common.Color.PageHighlight.FlexContainerBorder.toProtocolRGBA(), + pattern: Protocol.Overlay.LineStylePattern.Dashed + }, + lineSeparator: { + color: Common.Color.PageHighlight.FlexContainerBorder.toProtocolRGBA(), + pattern: Protocol.Overlay.LineStylePattern.Dashed + }, + crossAlignment: {color: Common.Color.PageHighlight.FlexContainerBorder.toProtocolRGBA()} + }; + } + // the backend does not support the 'original' format because // it currently cannot retrieve the original format using computed styles const supportedColorFormats = new Set(['rgb', 'hsl', 'hex']);
diff --git a/inspector_overlay/BUILD.gn b/inspector_overlay/BUILD.gn index 0c92fa9..92e6db3 100644 --- a/inspector_overlay/BUILD.gn +++ b/inspector_overlay/BUILD.gn
@@ -37,7 +37,7 @@ args = [ "main.js", - "90000", # max_size in bytes + "93000", # max_size in bytes "--input_path", rebase_path(target_gen_dir, root_build_dir), "--output_path",
diff --git a/inspector_overlay/debug/tool_highlight_flex_alignment.html b/inspector_overlay/debug/tool_highlight_flex_alignment.html new file mode 100644 index 0000000..326050c --- /dev/null +++ b/inspector_overlay/debug/tool_highlight_flex_alignment.html
@@ -0,0 +1,88 @@ +<!-- +Copyright 2020 The Chromium Authors. All rights reserved. +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. +--> +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <title>Debug</title> + <meta name="viewport" content="width=device-width,initial-scale=1"> + <script type="module" src="/out/Default/resources/inspector_overlay/main.js"></script> +</head> +<body> + <script type="module"> + const viewportWidth = document.documentElement.clientWidth; + const viewportHeight = document.documentElement.clientHeight; + + dispatch(["setPlatform", "mac"]); + dispatch(["setOverlay", "highlight"]); + dispatch(["reset", { + viewportSize: { + width: viewportWidth, + height: viewportHeight, + }, + deviceScaleFactor: 1, + pageScaleFactor: 1, + pageZoomFactor: 1, + emulationScaleFactor: 1, + scrollX: 0, + scrollY: 0, + }]); + + let response = { "highlight": { "paths": [ { "path": [ "M", 633.3333740234375, 291.3333435058594, "L", 933.3333740234375, 291.3333435058594, "L", 933.3333740234375, 591.3333740234375, "L", 633.3333740234375, 591.3333740234375, "Z" ], "fillColor": "rgba(255, 0, 0, 0)", "outlineColor": "rgba(128, 0, 0, 0)", "name": "content" }, { "path": [ "M", 633.3333740234375, 291.3333435058594, "L", 933.3333740234375, 291.3333435058594, "L", 933.3333740234375, 591.3333740234375, "L", 633.3333740234375, 591.3333740234375, "Z" ], "fillColor": "rgba(0, 255, 0, 0)", "name": "padding" }, { "path": [ "M", 633.3333740234375, 291.3333435058594, "L", 933.3333740234375, 291.3333435058594, "L", 933.3333740234375, 591.3333740234375, "L", 633.3333740234375, 591.3333740234375, "Z" ], "fillColor": "rgba(0, 0, 255, 0)", "name": "border" }, { "path": [ "M", 633.3333740234375, 291.3333435058594, "L", 1186.666748046875, 291.3333435058594, "L", 1186.666748046875, 591.3333740234375, "L", 633.3333740234375, 591.3333740234375, "Z" ], "fillColor": "rgba(255, 255, 255, 0)", "name": "margin" } ], "showRulers": true, "showExtensionLines": true, "showAccessibilityInfo": true, "colorFormat": "hex", "elementInfo": { "tagName": "div", "idValue": "test-multi-line-space", "nodeWidth": "300", "nodeHeight": "300", "isKeyboardFocusable": false, "accessibleName": "", "accessibleRole": "generic", "layoutObjectName": "LayoutNGFlexibleBox", "showAccessibilityInfo": true }, "flexInfo": [ + { "containerBorder": [ "M", 633.3333740234375, 291.3333435058594, "L", 933.3333740234375, 291.3333435058594, "L", 933.3333740234375, 591.3333740234375, "L", 633.3333740234375, 591.3333740234375, "Z" ], + "lines": [ + [ + [ "M", 650, 350, "L", 730, 350, "L", 730, 390, "L", 650, 390, "Z" ], + [ "M", 743.3333740234375, 352.3333435058594, "L", 823.3333740234375, 352.3333435058594, "L", 823.3333740234375, 371, "L", 743.3333740234375, 371, "Z" ], + [ "M", 823.3333740234375, 330, "L", 903.3333740234375, 330, "L", 903.3333740234375, 380, "L", 823.3333740234375, 380, "Z" ] + ], + [ + [ "M", 663.3333740234375, 420, "L", 743.3333740234375, 420, "L", 743.3333740234375, 450.66668701171875, "L", 663.3333740234375, 450.66668701171875, "Z" ], + [ "M", 743.3333740234375, 432, "L", 823.3333740234375, 432, "L", 823.3333740234375, 440.66668701171875, "L", 743.3333740234375, 440.66668701171875, "Z" ], + [ "M", 830, 432, "L", 903.3333740234375, 432, "L", 903.3333740234375, 465, "L", 830, 465, "Z" ] + ], + [ + [ "M", 743.3333740234375, 511.66668701171875, "L", 823.3333740234375, 511.66668701171875, "L", 823.3333740234375, 530.3333740234375, "L", 743.3333740234375, 530.3333740234375, "Z" ] + ] + ], + "alignItemsStyle": "center", + "isHorizontalFlow": true, + "flexContainerHighlightConfig": { + "containerBorder": { + "color": "rgba(255, 0, 0, 1)", + "pattern": "dashed" + }, + "lineSeparator": { + "color": "rgba(0, 0, 255, 1)", + "pattern": "dashed" + }, + "itemSeparator": { + "color": "rgba(0, 255, 0, 1)", + "pattern": "dotted" + }, + "mainDistributedSpace": { + "hatchColor": "red", + }, + "crossDistributedSpace": { + "hatchColor": "blue", + }, + "crossAlignment": { + "color": "red", + "pattern": "solid" + } + } + } ] } } + + // Massage the data a bit so we can see a nice flex overlay. + response = response.highlight; + delete response.elementInfo; + response.showRulers = false; + response.showExtensionLines = false; + + dispatch(["drawHighlight", response]); + </script> +</body> +</html>
diff --git a/inspector_overlay/debug/tool_highlight_flex_transformed.html b/inspector_overlay/debug/tool_highlight_flex_transformed.html new file mode 100644 index 0000000..8412910 --- /dev/null +++ b/inspector_overlay/debug/tool_highlight_flex_transformed.html
@@ -0,0 +1,45 @@ +<!-- +Copyright 2020 The Chromium Authors. All rights reserved. +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. +--> +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <title>Debug</title> + <meta name="viewport" content="width=device-width,initial-scale=1"> + <script type="module" src="/out/Default/resources/inspector_overlay/main.js"></script> +</head> +<body> + <script type="module"> + const viewportWidth = document.documentElement.clientWidth; + const viewportHeight = document.documentElement.clientHeight; + + dispatch(["setPlatform", "mac"]); + dispatch(["setOverlay", "highlight"]); + dispatch(["reset", { + viewportSize: { + width: viewportWidth, + height: viewportHeight, + }, + deviceScaleFactor: 1, + pageScaleFactor: 1, + pageZoomFactor: 1, + emulationScaleFactor: 1, + scrollX: 0, + scrollY: 0, + }]); + + let response = { "highlight": { "paths": [ { "path": [ "M", 195.80020141601562, 30.00569725036621, "L", 467.17559814453125, 199.5798797607422, "L", 308.1998291015625, 453.99432373046875, "L", 36.82441711425781, 284.4201354980469, "Z" ], "fillColor": "rgba(255, 0, 0, 0)", "outlineColor": "rgba(128, 0, 0, 0)", "name": "content" }, { "path": [ "M", 195.80020141601562, 30.00569725036621, "L", 467.17559814453125, 199.5798797607422, "L", 308.1998291015625, 453.99432373046875, "L", 36.82441711425781, 284.4201354980469, "Z" ], "fillColor": "rgba(0, 255, 0, 0)", "name": "padding" }, { "path": [ "M", 195.80020141601562, 30.00569725036621, "L", 467.17559814453125, 199.5798797607422, "L", 308.1998291015625, 453.99432373046875, "L", 36.82441711425781, 284.4201354980469, "Z" ], "fillColor": "rgba(0, 0, 255, 0)", "name": "border" }, { "path": [ "M", 195.80020141601562, 30.00569725036621, "L", 808.65625, 412.960693359375, "L", 649.6805419921875, 667.3751220703125, "L", 36.82441711425781, 284.4201354980469, "Z" ], "fillColor": "rgba(255, 255, 255, 0)", "name": "margin" } ], "showRulers": true, "showExtensionLines": true, "showAccessibilityInfo": true, "colorFormat": "hex", "elementInfo": { "tagName": "div", "idValue": "test-multi-line-space", "nodeWidth": "430.351", "nodeHeight": "423.989", "isKeyboardFocusable": false, "accessibleName": "", "accessibleRole": "generic", "layoutObjectName": "LayoutNGFlexibleBox", "showAccessibilityInfo": true }, "flexInfo": [ { "containerBorder": [ "M", 195.80020141601562, 30.00569725036621, "L", 467.17559814453125, 199.5798797607422, "L", 308.1998291015625, 453.99432373046875, "L", 36.82441711425781, 284.4201354980469, "Z" ], "lines": [ [ [ "M", 159.76568603515625, 87.6729736328125, "L", 227.6095428466797, 130.0665283203125, "L", 202.17340087890625, 170.7728271484375, "L", 134.32955932617188, 128.3792724609375, "Z" ], [ "M", 227.6095428466797, 130.0665283203125, "L", 295.4533996582031, 172.46006774902344, "L", 270.01727294921875, 213.16636657714844, "L", 202.17340087890625, 170.7728271484375, "Z" ], [ "M", 295.4533996582031, 172.46006774902344, "L", 363.2972412109375, 214.85360717773438, "L", 337.8611145019531, 255.55990600585938, "L", 270.01727294921875, 213.16636657714844, "Z" ], [ "M", 363.2972412109375, 214.85360717773438, "L", 431.1410827636719, 257.24713134765625, "L", 405.7049560546875, 297.9534606933594, "L", 337.8611145019531, 255.55990600585938, "Z" ] ], [ [ "M", 132.21697998046875, 207.2433319091797, "L", 200.06082153320312, 249.63687133789062, "L", 174.62469482421875, 290.34320068359375, "L", 106.78085327148438, 247.9496307373047, "Z" ], [ "M", 200.06082153320312, 249.63687133789062, "L", 267.9046630859375, 292.0304260253906, "L", 242.4685516357422, 332.7367248535156, "L", 174.62469482421875, 290.34320068359375, "Z" ], [ "M", 267.9046630859375, 292.0304260253906, "L", 335.74853515625, 334.4239501953125, "L", 310.3124084472656, 375.1302490234375, "L", 242.4685516357422, 332.7367248535156, "Z" ] ] ], "isHorizontalFlow": true, "alignItemsStyle": "stretch", "flexContainerHighlightConfig": { "containerBorder": { "color": "rgba(255, 0, 0, 0.6)", "pattern": "dashed" }, "lineSeparator": { "color": "rgba(255, 0, 0, 0.6)", "pattern": "dashed" }, "itemSeparator": { "color": "rgba(255, 0, 0, 0.6)", "pattern": "dotted" }, "mainDistributedSpace": { "fillColor": "rgba(255, 0, 0, 0.6)", "hatchColor": "rgba(255, 0, 0, 0.6)" }, "crossDistributedSpace": { "fillColor": "rgba(255, 0, 0, 0.6)", "hatchColor": "rgba(255, 0, 0, 0.6)" }, "rowGapSpace": { "fillColor": "rgba(255, 0, 0, 0.6)", "hatchColor": "rgba(255, 0, 0, 0.6)" }, "columnGapSpace": { "fillColor": "rgba(255, 0, 0, 0.6)", "hatchColor": "rgba(255, 0, 0, 0.6)" }, "crossAlignment": { "color": "red", "pattern": "solid" } } } ] } }; + + // Massage the data a bit so we can see a nice flex overlay. + response = response.highlight; + delete response.elementInfo; + response.showRulers = false; + response.showExtensionLines = false; + + dispatch(["drawHighlight", response]); + </script> +</body> +</html>
diff --git a/inspector_overlay/highlight_common.ts b/inspector_overlay/highlight_common.ts index 34d0108..be021cd 100644 --- a/inspector_overlay/highlight_common.ts +++ b/inspector_overlay/highlight_common.ts
@@ -53,11 +53,12 @@ Dashed = 'dashed' } -export function drawPathWithLineStyle(context: CanvasRenderingContext2D, path: Path2D, lineStyle?: LineStyle) { +export function drawPathWithLineStyle( + context: CanvasRenderingContext2D, path: Path2D, lineStyle?: LineStyle, lineWidth: number = 1) { if (lineStyle && lineStyle.color) { context.save(); context.translate(0.5, 0.5); - context.lineWidth = 1; + context.lineWidth = lineWidth; if (lineStyle.pattern === LinePattern.Dashed) { context.setLineDash([3, 3]); }
diff --git a/inspector_overlay/highlight_flex_common.ts b/inspector_overlay/highlight_flex_common.ts index d5db641..cee3e46 100644 --- a/inspector_overlay/highlight_flex_common.ts +++ b/inspector_overlay/highlight_flex_common.ts
@@ -9,6 +9,7 @@ containerBorder: PathCommands; lines: Array<Array<PathCommands>>; isHorizontalFlow: boolean; + alignItemsStyle: string; flexContainerHighlightConfig: { containerBorder?: LineStyle; lineSeparator?: LineStyle; @@ -17,6 +18,7 @@ crossDistributedSpace?: BoxStyle; rowGapSpace?: BoxStyle; columnGapSpace?: BoxStyle; + crossAlignment?: LineStyle; }; } @@ -25,6 +27,13 @@ items: Quad[]; } +const ALIGNMENT_LINE_THICKNESS = 2; +const ALIGNMENT_ARROW_BODY_HEIGHT = 5; +const ALIGNMENT_ARROW_BODY_WIDTH = 5; +const ALIGNMENT_ARROW_TIP_HEIGHT = 6; +const ALIGNMENT_ARROW_TIP_WIDTH = 11; +const ALIGNMENT_ARROW_DISTANCE_FROM_LINE = 2; + export function drawLayoutFlexContainerHighlight( highlight: FlexContainerHighlight, context: CanvasRenderingContext2D, deviceScaleFactor: number, canvasWidth: number, canvasHeight: number, emulationScaleFactor: number) { @@ -47,6 +56,9 @@ // Draw the hatching pattern outside of items. drawFlexSpace(highlight, context, emulationScaleFactor, highlight.containerBorder, lineQuads); + + // Draw the self-alignment lines and arrows. + drawFlexAlignment(highlight, context, emulationScaleFactor, lineQuads); } function drawFlexLinesAndItems( @@ -129,15 +141,195 @@ } } +function drawFlexAlignment( + highlight: FlexContainerHighlight, context: CanvasRenderingContext2D, emulationScaleFactor: number, + lineQuads: LineQuads[]) { + for (const {quad} of lineQuads) { + drawFlexAlignmentForLine(highlight, context, emulationScaleFactor, quad); + } +} + +function drawFlexAlignmentForLine( + highlight: FlexContainerHighlight, context: CanvasRenderingContext2D, emulationScaleFactor: number, + lineQuad: Quad) { + const {alignItemsStyle, isHorizontalFlow} = highlight; + const {crossAlignment} = highlight.flexContainerHighlightConfig; + if (!crossAlignment || !crossAlignment.color) { + return; + } + + // Note that the order of the 2 points in the array matters as it is used to determine where the arrow will be drawn. + // + // first second + // point point + // o--------------------o + // ^ + // | + // arrow + // + // + // arrow + // second | first + // point V point + // o--------------------o + const linesToDraw: [Position, Position][] = []; + + switch (alignItemsStyle) { + case 'flex-start': + linesToDraw.push([ + isHorizontalFlow ? lineQuad.p1 : lineQuad.p4, + isHorizontalFlow ? lineQuad.p2 : lineQuad.p1, + ]); + break; + case 'flex-end': + linesToDraw.push([ + isHorizontalFlow ? lineQuad.p3 : lineQuad.p2, + isHorizontalFlow ? lineQuad.p4 : lineQuad.p3, + ]); + break; + case 'center': + if (isHorizontalFlow) { + linesToDraw.push([ + { + x: (lineQuad.p1.x + lineQuad.p4.x) / 2, + y: (lineQuad.p1.y + lineQuad.p4.y) / 2, + }, + { + x: (lineQuad.p2.x + lineQuad.p3.x) / 2, + y: (lineQuad.p2.y + lineQuad.p3.y) / 2, + }, + ]); + linesToDraw.push([ + { + x: (lineQuad.p2.x + lineQuad.p3.x) / 2, + y: (lineQuad.p2.y + lineQuad.p3.y) / 2, + }, + { + x: (lineQuad.p1.x + lineQuad.p4.x) / 2, + y: (lineQuad.p1.y + lineQuad.p4.y) / 2, + }, + ]); + } else { + linesToDraw.push([ + { + x: (lineQuad.p1.x + lineQuad.p2.x) / 2, + y: (lineQuad.p1.y + lineQuad.p2.y) / 2, + }, + { + x: (lineQuad.p3.x + lineQuad.p4.x) / 2, + y: (lineQuad.p3.y + lineQuad.p4.y) / 2, + }, + ]); + linesToDraw.push([ + { + x: (lineQuad.p3.x + lineQuad.p4.x) / 2, + y: (lineQuad.p3.y + lineQuad.p4.y) / 2, + }, + { + x: (lineQuad.p1.x + lineQuad.p2.x) / 2, + y: (lineQuad.p1.y + lineQuad.p2.y) / 2, + }, + ]); + } + break; + case 'stretch': + case 'normal': + linesToDraw.push([ + isHorizontalFlow ? lineQuad.p1 : lineQuad.p4, + isHorizontalFlow ? lineQuad.p2 : lineQuad.p1, + ]); + linesToDraw.push([ + isHorizontalFlow ? lineQuad.p3 : lineQuad.p2, + isHorizontalFlow ? lineQuad.p4 : lineQuad.p3, + ]); + break; + } + + for (const points of linesToDraw) { + const path = segmentToPath(points); + drawPathWithLineStyle( + context, buildPath(path, emptyBounds(), emulationScaleFactor), crossAlignment, ALIGNMENT_LINE_THICKNESS); + drawAlignmentArrow(highlight, context, emulationScaleFactor, points[0], points[1]); + } +} + +/** + * Draw an arrow pointed at the middle of a segment. The segment isn't necessarily vertical or horizontal. + * + * start C end + * o-------------x--------------o + * / \ + * / \ + * /_ _\ + * | | + * |_| + */ +function drawAlignmentArrow( + highlight: FlexContainerHighlight, context: CanvasRenderingContext2D, emulationScaleFactor: number, + startPoint: Position, endPoint: Position) { + const {crossAlignment} = highlight.flexContainerHighlightConfig; + if (!crossAlignment || !crossAlignment.color) { + return; + } + + // The angle of the segment. + const angle = Math.atan2(endPoint.y - startPoint.y, endPoint.x - startPoint.x); + + // Where the tip of the arrow meets the segment, plus some offset so they don't overlap. + const C = { + x: (-ALIGNMENT_ARROW_DISTANCE_FROM_LINE * Math.cos(angle - .5 * Math.PI)) + ((startPoint.x + endPoint.x) / 2), + y: (-ALIGNMENT_ARROW_DISTANCE_FROM_LINE * Math.sin(angle - .5 * Math.PI)) + ((startPoint.y + endPoint.y) / 2), + }; + + const path = buildPath( + [ + 'M', + C.x, + C.y, + 'L', + C.x + (ALIGNMENT_ARROW_TIP_WIDTH / 2), + C.y + ALIGNMENT_ARROW_TIP_HEIGHT, + 'L', + C.x + (ALIGNMENT_ARROW_BODY_WIDTH / 2), + C.y + ALIGNMENT_ARROW_TIP_HEIGHT, + 'L', + C.x + (ALIGNMENT_ARROW_BODY_WIDTH / 2), + C.y + ALIGNMENT_ARROW_TIP_HEIGHT + ALIGNMENT_ARROW_BODY_HEIGHT, + 'L', + C.x - (ALIGNMENT_ARROW_BODY_WIDTH / 2), + C.y + ALIGNMENT_ARROW_TIP_HEIGHT + ALIGNMENT_ARROW_BODY_HEIGHT, + 'L', + C.x - (ALIGNMENT_ARROW_BODY_WIDTH / 2), + C.y + ALIGNMENT_ARROW_TIP_HEIGHT, + 'L', + C.x - (ALIGNMENT_ARROW_TIP_WIDTH / 2), + C.y + ALIGNMENT_ARROW_TIP_HEIGHT, + 'Z', + ], + emptyBounds(), emulationScaleFactor); + + context.save(); + context.translate(C.x, C.y); + context.rotate(angle); + context.translate(-C.x, -C.y); + + context.fillStyle = crossAlignment.color; + context.fill(path); + + context.restore(); +} + function drawHatchPatternInQuad( outerQuad: Quad, quadsToClip: Quad[], boxStyle: BoxStyle|undefined, context: CanvasRenderingContext2D, emulationScaleFactor: number) { if (!boxStyle || !boxStyle.hatchColor) { return; } + + const angle = Math.atan2(outerQuad.p2.y - outerQuad.p1.y, outerQuad.p2.x - outerQuad.p1.x) * 180 / Math.PI; const bounds = emptyBounds(); const path = createPathForQuad(outerQuad, quadsToClip, bounds, emulationScaleFactor); - hatchFillPath(context, path, bounds, 10, boxStyle.hatchColor, 0, false); + hatchFillPath(context, path, bounds, 10, boxStyle.hatchColor, angle, false); } /** @@ -194,6 +386,10 @@ return skipEndLine ? startLine : [...startLine, 'M', quad.p3.x, quad.p3.y, 'L', quad.p2.x, quad.p2.y]; } +function segmentToPath(segment: [Position, Position]): PathCommands { + return ['M', segment[0].x, segment[0].y, 'L', segment[1].x, segment[1].y]; +} + /** * Transform a path array (as returned by the backend) that corresponds to a rectangle into a quad. * @param commands