mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-19 13:04:39 +08:00
DISABLE_THIRD_PARTY_CHECK=updated linting formatting Bug: 1106746 Change-Id: I371bca95b7cf1e8b7eaa4f9e7c3860af4eedee3e Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/3057042 Commit-Queue: Kriti Sapra <kritisapra@google.com> Reviewed-by: Tim van der Lippe <tvanderlippe@chromium.org> Reviewed-by: Jack Franklin <jacktfranklin@chromium.org>
490 lines
15 KiB
TypeScript
490 lines
15 KiB
TypeScript
// 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.
|
|
|
|
// Copyright (C) 2012 Google Inc. All rights reserved.
|
|
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions
|
|
// are met:
|
|
|
|
// 1. Redistributions of source code must retain the above copyright
|
|
// notice, this list of conditions and the following disclaimer.
|
|
// 2. Redistributions in binary form must reproduce the above copyright
|
|
// notice, this list of conditions and the following disclaimer in the
|
|
// documentation and/or other materials provided with the distribution.
|
|
// 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
|
|
// its contributors may be used to endorse or promote products derived
|
|
// from this software without specific prior written permission.
|
|
|
|
// THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
|
|
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
// DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
|
|
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
import type {AreaBounds, Bounds} from './common.js';
|
|
import type {GridLabelState} from './css_grid_label_helpers.js';
|
|
import {drawGridLabels} from './css_grid_label_helpers.js';
|
|
import {applyMatrixToPoint, buildPath, emptyBounds, hatchFillPath} from './highlight_common.js';
|
|
|
|
// TODO(alexrudenko): Grid label unit tests depend on this style so it cannot be extracted yet.
|
|
export const gridStyle = `
|
|
/* Grid row and column labels */
|
|
.grid-label-content {
|
|
position: absolute;
|
|
-webkit-user-select: none;
|
|
padding: 2px;
|
|
font-family: Menlo, monospace;
|
|
font-size: 10px;
|
|
min-width: 17px;
|
|
min-height: 15px;
|
|
border-radius: 2px;
|
|
box-sizing: border-box;
|
|
z-index: 1;
|
|
background-clip: padding-box;
|
|
pointer-events: none;
|
|
text-align: center;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
|
|
.grid-label-content[data-direction=row] {
|
|
background-color: var(--row-label-color, #1A73E8);
|
|
color: var(--row-label-text-color, #121212);
|
|
}
|
|
|
|
.grid-label-content[data-direction=column] {
|
|
background-color: var(--column-label-color, #1A73E8);
|
|
color: var(--column-label-text-color,#121212);
|
|
}
|
|
|
|
.line-names ul,
|
|
.line-names .line-name {
|
|
margin: 0;
|
|
padding: 0;
|
|
list-style: none;
|
|
}
|
|
|
|
.line-names .line-name {
|
|
max-width: 100px;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.line-names .grid-label-content,
|
|
.line-numbers .grid-label-content,
|
|
.track-sizes .grid-label-content {
|
|
border: 1px solid white;
|
|
--inner-corner-avoid-distance: 15px;
|
|
}
|
|
|
|
.grid-label-content.top-left.inner-shared-corner,
|
|
.grid-label-content.top-right.inner-shared-corner {
|
|
transform: translateY(var(--inner-corner-avoid-distance));
|
|
}
|
|
|
|
.grid-label-content.bottom-left.inner-shared-corner,
|
|
.grid-label-content.bottom-right.inner-shared-corner {
|
|
transform: translateY(calc(var(--inner-corner-avoid-distance) * -1));
|
|
}
|
|
|
|
.grid-label-content.left-top.inner-shared-corner,
|
|
.grid-label-content.left-bottom.inner-shared-corner {
|
|
transform: translateX(var(--inner-corner-avoid-distance));
|
|
}
|
|
|
|
.grid-label-content.right-top.inner-shared-corner,
|
|
.grid-label-content.right-bottom.inner-shared-corner {
|
|
transform: translateX(calc(var(--inner-corner-avoid-distance) * -1));
|
|
}
|
|
|
|
.line-names .grid-label-content::before,
|
|
.line-numbers .grid-label-content::before,
|
|
.track-sizes .grid-label-content::before {
|
|
position: absolute;
|
|
z-index: 1;
|
|
pointer-events: none;
|
|
content: "";
|
|
width: 3px;
|
|
height: 3px;
|
|
border: 1px solid white;
|
|
border-width: 0 1px 1px 0;
|
|
}
|
|
|
|
.line-names .grid-label-content[data-direction=row]::before,
|
|
.line-numbers .grid-label-content[data-direction=row]::before,
|
|
.track-sizes .grid-label-content[data-direction=row]::before {
|
|
background: var(--row-label-color, #1A73E8);
|
|
}
|
|
|
|
.line-names .grid-label-content[data-direction=column]::before,
|
|
.line-numbers .grid-label-content[data-direction=column]::before,
|
|
.track-sizes .grid-label-content[data-direction=column]::before {
|
|
background: var(--column-label-color, #1A73E8);
|
|
}
|
|
|
|
.grid-label-content.bottom-mid::before {
|
|
transform: translateY(-1px) rotate(45deg);
|
|
top: 100%;
|
|
}
|
|
|
|
.grid-label-content.top-mid::before {
|
|
transform: translateY(-3px) rotate(-135deg);
|
|
top: 0%;
|
|
}
|
|
|
|
.grid-label-content.left-mid::before {
|
|
transform: translateX(-3px) rotate(135deg);
|
|
left: 0%
|
|
}
|
|
|
|
.grid-label-content.right-mid::before {
|
|
transform: translateX(3px) rotate(-45deg);
|
|
right: 0%;
|
|
}
|
|
|
|
.grid-label-content.right-top::before {
|
|
transform: translateX(3px) translateY(-1px) rotate(-90deg) skewY(30deg);
|
|
right: 0%;
|
|
top: 0%;
|
|
}
|
|
|
|
.grid-label-content.right-bottom::before {
|
|
transform: translateX(3px) translateY(-3px) skewX(30deg);
|
|
right: 0%;
|
|
top: 100%;
|
|
}
|
|
|
|
.grid-label-content.bottom-right::before {
|
|
transform: translateX(1px) translateY(-1px) skewY(30deg);
|
|
right: 0%;
|
|
top: 100%;
|
|
}
|
|
|
|
.grid-label-content.bottom-left::before {
|
|
transform: translateX(-1px) translateY(-1px) rotate(90deg) skewX(30deg);
|
|
left: 0%;
|
|
top: 100%;
|
|
}
|
|
|
|
.grid-label-content.left-top::before {
|
|
transform: translateX(-3px) translateY(-1px) rotate(180deg) skewX(30deg);
|
|
left: 0%;
|
|
top: 0%;
|
|
}
|
|
|
|
.grid-label-content.left-bottom::before {
|
|
transform: translateX(-3px) translateY(-3px) rotate(90deg) skewY(30deg);
|
|
left: 0%;
|
|
top: 100%;
|
|
}
|
|
|
|
.grid-label-content.top-right::before {
|
|
transform: translateX(1px) translateY(-3px) rotate(-90deg) skewX(30deg);
|
|
right: 0%;
|
|
top: 0%;
|
|
}
|
|
|
|
.grid-label-content.top-left::before {
|
|
transform: translateX(-1px) translateY(-3px) rotate(180deg) skewY(30deg);
|
|
left: 0%;
|
|
top: 0%;
|
|
}
|
|
|
|
@media (forced-colors: active) {
|
|
.grid-label-content {
|
|
border-color: Highlight;
|
|
background-color: Canvas;
|
|
color: Text;
|
|
forced-color-adjust: none;
|
|
}
|
|
.grid-label-content::before {
|
|
background-color: Canvas;
|
|
border-color: Highlight;
|
|
}
|
|
}`;
|
|
|
|
export interface GridHighlight {
|
|
gridBorder: Array<string|number>;
|
|
writingMode: string;
|
|
rowGaps: Array<string|number>;
|
|
rotationAngle: number;
|
|
columnGaps: Array<string|number>;
|
|
rows: Array<string|number>;
|
|
columns: Array<string|number>;
|
|
areaNames: {[key: string]: Array<string|number>};
|
|
gridHighlightConfig: {
|
|
gridBackgroundColor?: string,
|
|
gridBorderColor?: string,
|
|
rowGapColor?: string,
|
|
columnGapColor?: string,
|
|
areaBorderColor?: string,
|
|
gridBorderDash: boolean,
|
|
rowLineDash: boolean,
|
|
columnLineDash: boolean,
|
|
showGridExtensionLines: boolean,
|
|
showPositiveLineNumbers: boolean,
|
|
showNegativeLineNumbers: boolean,
|
|
rowLineColor: string,
|
|
columnLineColor: string,
|
|
rowHatchColor: string,
|
|
columnHatchColor: string,
|
|
showLineNames: boolean,
|
|
};
|
|
}
|
|
|
|
export function drawLayoutGridHighlight(
|
|
highlight: GridHighlight, context: CanvasRenderingContext2D, deviceScaleFactor: number, canvasWidth: number,
|
|
canvasHeight: number, emulationScaleFactor: number, labelState: GridLabelState) {
|
|
const gridBounds = emptyBounds();
|
|
const gridPath = buildPath(highlight.gridBorder, gridBounds, emulationScaleFactor);
|
|
|
|
// Transform the context to match the current writing-mode.
|
|
context.save();
|
|
applyWritingModeTransformation(highlight.writingMode, gridBounds, context);
|
|
|
|
// Draw grid background
|
|
if (highlight.gridHighlightConfig.gridBackgroundColor) {
|
|
context.fillStyle = highlight.gridHighlightConfig.gridBackgroundColor;
|
|
context.fill(gridPath);
|
|
}
|
|
|
|
// Draw Grid border
|
|
if (highlight.gridHighlightConfig.gridBorderColor) {
|
|
context.save();
|
|
context.translate(0.5, 0.5);
|
|
context.lineWidth = 0;
|
|
if (highlight.gridHighlightConfig.gridBorderDash) {
|
|
context.setLineDash([3, 3]);
|
|
}
|
|
context.strokeStyle = highlight.gridHighlightConfig.gridBorderColor;
|
|
context.stroke(gridPath);
|
|
context.restore();
|
|
}
|
|
|
|
// Draw grid lines
|
|
const rowBounds = drawGridLines(context, highlight, 'row', emulationScaleFactor);
|
|
const columnBounds = drawGridLines(context, highlight, 'column', emulationScaleFactor);
|
|
|
|
// Draw gaps
|
|
drawGridGap(
|
|
context, highlight.rowGaps, highlight.gridHighlightConfig.rowGapColor,
|
|
highlight.gridHighlightConfig.rowHatchColor, highlight.rotationAngle, emulationScaleFactor,
|
|
/* flipDirection */ true);
|
|
drawGridGap(
|
|
context, highlight.columnGaps, highlight.gridHighlightConfig.columnGapColor,
|
|
highlight.gridHighlightConfig.columnHatchColor, highlight.rotationAngle, emulationScaleFactor,
|
|
/* flipDirection */ false);
|
|
|
|
// Draw named grid areas
|
|
const areaBounds =
|
|
drawGridAreas(context, highlight.areaNames, highlight.gridHighlightConfig.areaBorderColor, emulationScaleFactor);
|
|
|
|
// The rest of the overlay is drawn without the writing-mode transformation, but we keep the matrix to transform relevant points.
|
|
const writingModeMatrix = context.getTransform();
|
|
writingModeMatrix.scaleSelf(1 / deviceScaleFactor);
|
|
context.restore();
|
|
|
|
if (highlight.gridHighlightConfig.showGridExtensionLines) {
|
|
if (rowBounds) {
|
|
drawExtendedGridLines(
|
|
context, rowBounds, highlight.gridHighlightConfig.rowLineColor, highlight.gridHighlightConfig.rowLineDash,
|
|
writingModeMatrix, canvasWidth, canvasHeight);
|
|
}
|
|
if (columnBounds) {
|
|
drawExtendedGridLines(
|
|
context, columnBounds, highlight.gridHighlightConfig.columnLineColor,
|
|
highlight.gridHighlightConfig.columnLineDash, writingModeMatrix, canvasWidth, canvasHeight);
|
|
}
|
|
}
|
|
|
|
// Draw all the labels
|
|
drawGridLabels(
|
|
highlight, gridBounds, areaBounds, {canvasWidth, canvasHeight}, labelState, emulationScaleFactor,
|
|
writingModeMatrix);
|
|
}
|
|
|
|
function applyWritingModeTransformation(writingMode: string, gridBounds: Bounds, context: CanvasRenderingContext2D) {
|
|
if (writingMode !== 'vertical-rl' && writingMode !== 'vertical-lr') {
|
|
return;
|
|
}
|
|
|
|
const topLeft = gridBounds.allPoints[0];
|
|
const bottomLeft = gridBounds.allPoints[3];
|
|
|
|
// Move to the top-left corner to do all transformations there.
|
|
context.translate(topLeft.x, topLeft.y);
|
|
|
|
if (writingMode === 'vertical-rl') {
|
|
context.rotate(90 * Math.PI / 180);
|
|
context.translate(0, -1 * (bottomLeft.y - topLeft.y));
|
|
}
|
|
|
|
if (writingMode === 'vertical-lr') {
|
|
context.rotate(90 * Math.PI / 180);
|
|
context.scale(1, -1);
|
|
}
|
|
|
|
// Move back to the original point.
|
|
context.translate(topLeft.x * -1, topLeft.y * -1);
|
|
}
|
|
|
|
function drawGridLines(
|
|
context: CanvasRenderingContext2D, highlight: GridHighlight, direction: 'row'|'column',
|
|
emulationScaleFactor: number) {
|
|
const tracks = highlight[`${direction}s` as 'rows' | 'columns'];
|
|
const color = highlight.gridHighlightConfig[`${direction}LineColor` as 'rowLineColor' | 'columnLineColor'];
|
|
const dash = highlight.gridHighlightConfig[`${direction}LineDash` as 'rowLineDash' | 'columnLineDash'];
|
|
|
|
if (!color) {
|
|
return null;
|
|
}
|
|
|
|
const bounds = emptyBounds();
|
|
const path = buildPath(tracks, bounds, emulationScaleFactor);
|
|
|
|
context.save();
|
|
context.translate(0.5, 0.5);
|
|
if (dash) {
|
|
context.setLineDash([3, 3]);
|
|
}
|
|
context.lineWidth = 0;
|
|
context.strokeStyle = color;
|
|
|
|
context.save();
|
|
context.stroke(path);
|
|
context.restore();
|
|
|
|
context.restore();
|
|
|
|
return bounds;
|
|
}
|
|
|
|
function drawExtendedGridLines(
|
|
context: CanvasRenderingContext2D, bounds: Bounds, color: string, dash: boolean|undefined,
|
|
writingModeMatrix: DOMMatrix, canvasWidth: number, canvasHeight: number) {
|
|
context.save();
|
|
context.strokeStyle = color;
|
|
context.lineWidth = 1;
|
|
context.translate(0.5, 0.5);
|
|
if (dash) {
|
|
context.setLineDash([3, 3]);
|
|
}
|
|
|
|
// A grid track path is a list of lines defined by 2 points.
|
|
// Here we're going through the list of all points 2 by 2, so we can draw the extensions at the edges of each line.
|
|
for (let i = 0; i < bounds.allPoints.length; i += 2) {
|
|
let point1 = applyMatrixToPoint(bounds.allPoints[i], writingModeMatrix);
|
|
let point2 = applyMatrixToPoint(bounds.allPoints[i + 1], writingModeMatrix);
|
|
let edgePoint1;
|
|
let edgePoint2;
|
|
|
|
if (point1.x === point2.x) {
|
|
// Special case for a vertical line.
|
|
edgePoint1 = {x: point1.x, y: 0};
|
|
edgePoint2 = {x: point1.x, y: canvasHeight};
|
|
if (point2.y < point1.y) {
|
|
[point1, point2] = [point2, point1];
|
|
}
|
|
} else if (point1.y === point2.y) {
|
|
// Special case for a horizontal line.
|
|
edgePoint1 = {x: 0, y: point1.y};
|
|
edgePoint2 = {x: canvasWidth, y: point1.y};
|
|
if (point2.x < point1.x) {
|
|
[point1, point2] = [point2, point1];
|
|
}
|
|
} else {
|
|
// When the line isn't straight, we need to do some maths.
|
|
const a = (point2.y - point1.y) / (point2.x - point1.x);
|
|
const b = (point1.y * point2.x - point2.y * point1.x) / (point2.x - point1.x);
|
|
|
|
edgePoint1 = {x: 0, y: b};
|
|
edgePoint2 = {x: canvasWidth, y: (canvasWidth * a) + b};
|
|
|
|
if (point2.x < point1.x) {
|
|
[point1, point2] = [point2, point1];
|
|
}
|
|
}
|
|
|
|
context.beginPath();
|
|
context.moveTo(edgePoint1.x, edgePoint1.y);
|
|
context.lineTo(point1.x, point1.y);
|
|
context.moveTo(point2.x, point2.y);
|
|
context.lineTo(edgePoint2.x, edgePoint2.y);
|
|
context.stroke();
|
|
}
|
|
|
|
context.restore();
|
|
}
|
|
|
|
/**
|
|
* Draw all of the named grid area paths. This does not draw the labels, as
|
|
* placing labels in and around the grid for various things is handled later.
|
|
*/
|
|
function drawGridAreas(
|
|
context: CanvasRenderingContext2D, areas: {[key: string]: Array<string|number>}, borderColor: string|undefined,
|
|
emulationScaleFactor: number): AreaBounds[] {
|
|
if (!areas || !Object.keys(areas).length) {
|
|
return [];
|
|
}
|
|
|
|
context.save();
|
|
if (borderColor) {
|
|
context.strokeStyle = borderColor;
|
|
}
|
|
context.lineWidth = 2;
|
|
|
|
const areaBounds = [];
|
|
|
|
for (const name in areas) {
|
|
const areaCommands = areas[name];
|
|
|
|
const bounds = emptyBounds();
|
|
const path = buildPath(areaCommands, bounds, emulationScaleFactor);
|
|
|
|
context.stroke(path);
|
|
|
|
areaBounds.push({name, bounds});
|
|
}
|
|
|
|
context.restore();
|
|
|
|
return areaBounds;
|
|
}
|
|
|
|
function drawGridGap(
|
|
context: CanvasRenderingContext2D, gapCommands: Array<number|string>, gapColor: string|undefined,
|
|
hatchColor: string|undefined, rotationAngle: number, emulationScaleFactor: number,
|
|
flipDirection: boolean|undefined) {
|
|
if (!gapColor && !hatchColor) {
|
|
return;
|
|
}
|
|
|
|
context.save();
|
|
context.translate(0.5, 0.5);
|
|
context.lineWidth = 0;
|
|
|
|
const bounds = emptyBounds();
|
|
const path = buildPath(gapCommands, bounds, emulationScaleFactor);
|
|
|
|
// Fill the gap background if needed.
|
|
if (gapColor) {
|
|
context.fillStyle = gapColor;
|
|
context.fill(path);
|
|
}
|
|
|
|
// And draw the hatch pattern if needed.
|
|
if (hatchColor) {
|
|
hatchFillPath(context, path, bounds, /* delta */ 10, hatchColor, rotationAngle, flipDirection);
|
|
}
|
|
context.restore();
|
|
}
|