Merge "Draw rounded corners when layers have them"

This commit is contained in:
TreeHugger Robot
2022-12-28 16:22:10 +00:00
committed by Android (Google) Code Review
9 changed files with 199 additions and 150 deletions

View File

@@ -19,8 +19,6 @@ export interface Rectangle {
bottomRight: Point;
label: string;
transform: RectTransform | null;
height: number;
width: number;
isVisible: boolean;
isDisplay: boolean;
ref: any;
@@ -28,6 +26,7 @@ export interface Rectangle {
displayId: number;
isVirtual: boolean;
isClickable: boolean;
cornerRadius: number;
}
export interface Point {
@@ -35,6 +34,11 @@ export interface Point {
y: number
}
export interface Size {
width: number,
height: number
}
export interface RectTransform {
matrix?: RectMatrix;
dsdx?: number;

View File

@@ -23,9 +23,12 @@ export class Canvas {
private static readonly TARGET_SCENE_DIAGONAL = 4;
private static readonly RECT_COLOR_HIGHLIGHTED = new THREE.Color(0xD2E3FC);
private static readonly RECT_EDGE_COLOR = 0x000000;
private static readonly RECT_EDGE_COLOR_ROUNDED = 0x848884;
private static readonly LABEL_CIRCLE_COLOR = 0x000000;
private static readonly LABEL_LINE_COLOR = 0x000000;
private static readonly LABEL_LINE_COLOR_HIGHLIGHTED = 0x808080;
private static readonly OPACITY_REGULAR = 0.75;
private static readonly OPACITY_OVERSIZED = 0.25;
private canvasRects: HTMLCanvasElement;
private canvasLabels: HTMLElement;
@@ -121,16 +124,11 @@ export class Canvas {
private drawRects(rects: Rect3D[]) {
this.clickableObjects = [];
rects.forEach(rect => {
const rectGeometry = new THREE.PlaneGeometry(rect.width, rect.height);
const rectMesh = this.makeRectMesh(rect, rectGeometry);
const rectEdges = this.makeRectLineSegments(rectMesh, rectGeometry);
const rectMesh = this.makeRectMesh(rect);
const transform = this.toMatrix4(rect.transform);
rectMesh.applyMatrix4(transform);
rectEdges.applyMatrix4(transform);
this.scene?.add(rectMesh);
this.scene?.add(rectEdges);
if (rect.isClickable) {
this.clickableObjects.push(rectMesh);
@@ -203,11 +201,54 @@ export class Canvas {
}
private makeRectMesh(
rect: Rect3D,
geometry: THREE.PlaneGeometry,
rect: Rect3D
): THREE.Mesh {
let color: THREE.Color;
const rectShape = this.createRectShape(rect);
const rectGeometry = new THREE.ShapeGeometry(rectShape);
const rectBorders = this.createRectBorders(rect, rectGeometry);
let opacity = Canvas.OPACITY_REGULAR;
if (rect.isOversized) {
opacity = Canvas.OPACITY_OVERSIZED;
}
// Crate mesh to draw
const mesh = new THREE.Mesh(
rectGeometry,
new THREE.MeshBasicMaterial({
color: this.getColor(rect),
opacity: opacity,
transparent: true,
}));
mesh.add(rectBorders);
mesh.position.x = 0;
mesh.position.y = 0;
mesh.position.z = rect.topLeft.z;
mesh.name = rect.id;
return mesh;
}
private createRectShape(rect: Rect3D): THREE.Shape {
const bottomLeft: Point3D = { x: rect.topLeft.x, y: rect.bottomRight.y, z: rect.topLeft.z };
const topRight: Point3D = { x: rect.bottomRight.x, y: rect.topLeft.y, z: rect.bottomRight.z };
// Create (rounded) rect shape
return new THREE.Shape()
.moveTo(rect.topLeft.x, rect.topLeft.y + rect.cornerRadius)
.lineTo(bottomLeft.x, bottomLeft.y - rect.cornerRadius)
.quadraticCurveTo(bottomLeft.x, bottomLeft.y, bottomLeft.x + rect.cornerRadius, bottomLeft.y)
.lineTo(rect.bottomRight.x - rect.cornerRadius, rect.bottomRight.y)
.quadraticCurveTo(rect.bottomRight.x, rect.bottomRight.y, rect.bottomRight.x, rect.bottomRight.y - rect.cornerRadius)
.lineTo(topRight.x, topRight.y + rect.cornerRadius)
.quadraticCurveTo(topRight.x, topRight.y, topRight.x - rect.cornerRadius, topRight.y)
.lineTo(rect.topLeft.x + rect.cornerRadius, rect.topLeft.y)
.quadraticCurveTo(rect.topLeft.x, rect.topLeft.y, rect.topLeft.x, rect.topLeft.y + rect.cornerRadius);
}
private getColor(rect: Rect3D): THREE.Color {
let color: THREE.Color = Canvas.RECT_COLOR_HIGHLIGHTED;
switch (rect.colorType) {
case ColorType.VISIBLE: {
// green (darkness depends on z order)
@@ -230,32 +271,29 @@ export class Canvas {
break;
}
}
const mesh = new THREE.Mesh(
geometry,
new THREE.MeshBasicMaterial({
color: color,
opacity: 0.75,
transparent: true,
}));
mesh.position.x = rect.center.x;
mesh.position.y = rect.center.y;
mesh.position.z = rect.center.z;
mesh.name = rect.id;
return mesh;
return color;
}
private makeRectLineSegments(rectMesh: THREE.Mesh, geometry: THREE.PlaneGeometry): THREE.LineSegments {
const edgeGeo = new THREE.EdgesGeometry(geometry);
const edgeMaterial = new THREE.LineBasicMaterial({
color: Canvas.RECT_EDGE_COLOR, linewidth: 1
});
const edgeSegments = new THREE.LineSegments(
private createRectBorders(rect: Rect3D, rectGeometry: THREE.ShapeGeometry): THREE.LineSegments {
// create line edges for rect
const edgeGeo = new THREE.EdgesGeometry(rectGeometry);
let edgeMaterial: THREE.Material;
if (rect.cornerRadius) {
edgeMaterial = new THREE.LineBasicMaterial({
color: Canvas.RECT_EDGE_COLOR_ROUNDED,
linewidth: 1
});
} else {
edgeMaterial = new THREE.LineBasicMaterial({
color: Canvas.RECT_EDGE_COLOR,
linewidth: 1
});
}
const lineSegments = new THREE.LineSegments(
edgeGeo, edgeMaterial
);
edgeSegments.position.set(rectMesh.position.x, rectMesh.position.y, rectMesh.position.z);
return edgeSegments;
lineSegments.computeLineDistances();
return lineSegments;
}
private makeLabelCircleMesh(circle: Circle3D): THREE.Mesh {

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
import {Rectangle} from "viewers/common/rectangle";
import {Rectangle, Size} from "viewers/common/rectangle";
import {
Box3D,
ColorType,
@@ -37,6 +37,14 @@ class Mapper3D {
private static readonly ZOOM_FACTOR_MIN = 0.1;
private static readonly ZOOM_FACTOR_MAX = 8.5;
private static readonly ZOOM_FACTOR_STEP = 0.2;
private static readonly IDENTITY_TRANSFORM: Transform3D = {
dsdx: 1,
dsdy: 0,
tx: 0,
dtdx: 0,
dtdy: 1,
ty: 0
};
private rects: Rectangle[] = [];
private highlightedRectIds: string[] = [];
@@ -169,102 +177,119 @@ class Mapper3D {
let z = 0;
const displays = rects2d.filter((rect2d) => rect2d.isDisplay);
// Arbitrary max size for a rect (2x the maximum display)
let maxDimension = Number.MAX_VALUE;
if (displays.length > 0) {
maxDimension = Math.max(
...displays.map((rect2d): number => Math.max(rect2d.width, rect2d.height))
) * 2;
}
const maxDisplaySize = this.getMaxDisplaySize(rects2d);
const rects3d = rects2d.map((rect2d): Rect3D => {
const identity: Transform3D = {
dsdx: 1,
dsdy: 0,
tx: 0,
dtdx: 0,
dtdy: 1,
ty: 0
};
let center: Point3D = {
x: rect2d.topLeft.x + rect2d.width / 2,
y: rect2d.topLeft.y + rect2d.height / 2,
z: z
};
z -= Mapper3D.Z_SPACING_MAX * this.zSpacingFactor;
const darkFactor = rect2d.isVisible ?
(visibleRectsTotal - visibleRectsSoFar++) / visibleRectsTotal :
(nonVisibleRectsTotal - nonVisibleRectsSoFar++) / nonVisibleRectsTotal;
let colorType: ColorType;
if (this.highlightedRectIds.includes(rect2d.id)) {
colorType = ColorType.HIGHLIGHTED;
} else if (rect2d.isVisible) {
colorType = ColorType.VISIBLE;
} else {
colorType = ColorType.NOT_VISIBLE;
}
let transform: Transform3D;
if (rect2d.transform?.matrix) {
transform = {
dsdx: rect2d.transform.matrix.dsdx,
dsdy: rect2d.transform.matrix.dsdy,
tx: rect2d.transform.matrix.tx,
dtdx: rect2d.transform.matrix.dtdx,
dtdy: rect2d.transform.matrix.dtdy,
ty: rect2d.transform.matrix.ty,
};
} else {
transform = identity;
}
let height = rect2d.height;
let width = rect2d.width;
// Crop oversized rectangles (e.g. BackColorSurface to make it easier to see elements)
if (width > maxDimension && height > maxDimension) {
width = maxDimension;
height = maxDimension;
// centralize the new rect
center = {
x: maxDimension / 4,
y: 0,
z: center.z
};
}
return {
const rect = {
id: rect2d.id,
center: center,
width: width,
height: height,
topLeft: {
x: rect2d.topLeft.x,
y: rect2d.topLeft.y,
z: z
},
bottomRight: {
x: rect2d.bottomRight.x,
y: rect2d.bottomRight.y,
z: z
},
isOversized: false,
cornerRadius: rect2d.cornerRadius,
darkFactor: darkFactor,
colorType: colorType,
colorType: this.getColorType(rect2d),
isClickable: rect2d.isClickable,
transform: transform,
transform: this.getTransform(rect2d),
};
return this.cropOversizedRect(rect, maxDisplaySize);
});
return rects3d;
}
private getColorType(rect2d: Rectangle): ColorType {
let colorType: ColorType;
if (this.highlightedRectIds.includes(rect2d.id)) {
colorType = ColorType.HIGHLIGHTED;
} else if (rect2d.isVisible) {
colorType = ColorType.VISIBLE;
} else {
colorType = ColorType.NOT_VISIBLE;
}
return colorType;
}
private getTransform(rect2d: Rectangle): Transform3D {
let transform: Transform3D;
if (rect2d.transform?.matrix) {
transform = {
dsdx: rect2d.transform.matrix.dsdx,
dsdy: rect2d.transform.matrix.dsdy,
tx: rect2d.transform.matrix.tx,
dtdx: rect2d.transform.matrix.dtdx,
dtdy: rect2d.transform.matrix.dtdy,
ty: rect2d.transform.matrix.ty,
};
} else {
transform = Mapper3D.IDENTITY_TRANSFORM;
}
return transform;
}
private getMaxDisplaySize(rects2d: Rectangle[]): Size {
const displays = rects2d.filter((rect2d) => rect2d.isDisplay);
let maxWidth = 0;
let maxHeight = 0;
if (displays.length > 0) {
maxWidth = Math.max(
...displays.map((rect2d): number => Math.abs(rect2d.topLeft.x - rect2d.bottomRight.x))
);
maxHeight = Math.max(
...displays.map((rect2d): number => Math.abs(rect2d.topLeft.y - rect2d.bottomRight.y))
);
}
return {
width: maxWidth,
height: maxHeight
}
}
private cropOversizedRect(rect3d: Rect3D, maxDisplaySize: Size): Rect3D {
// Arbitrary max size for a rect (2x the maximum display)
let maxDimension = Number.MAX_VALUE;
if (maxDisplaySize.height > 0) {
maxDimension = Math.max(maxDisplaySize.width, maxDisplaySize.height) * 2
}
const height = Math.abs(rect3d.topLeft.y - rect3d.bottomRight.y);
const width = Math.abs(rect3d.topLeft.x - rect3d.bottomRight.x);
if (width > maxDimension) {
rect3d.isOversized = true;
rect3d.topLeft.x = (maxDimension - (maxDisplaySize.width / 2)) * -1,
rect3d.bottomRight.x = maxDimension
}
if (height > maxDimension) {
rect3d.isOversized = true;
rect3d.topLeft.y = (maxDimension - (maxDisplaySize.height / 2)) * -1
rect3d.bottomRight.y = maxDimension
}
return rect3d;
}
private computeLabels(rects2d: Rectangle[], rects3d: Rect3D[]): Label3D[] {
const labels3d: Label3D[] = [];
let labelY = Math.max(...rects3d.map(rect => {
const bottomRight = {
x: rect.center.x + rect.width / 2,
y: rect.center.y + rect.height / 2,
z: rect.center.z,
};
return this.matMultiply(rect.transform, bottomRight).y;
return this.matMultiply(rect.transform, rect.bottomRight).y;
})) + Mapper3D.LABEL_FIRST_Y_OFFSET;
rects2d.forEach((rect2d, index) => {
@@ -274,27 +299,13 @@ class Mapper3D {
const rect3d = rects3d[index];
const bottomLeft: Point3D = { x: rect3d.topLeft.x, y: rect3d.bottomRight.y, z: rect3d.topLeft.z };
const topRight: Point3D = { x: rect3d.bottomRight.x, y: rect3d.topLeft.y, z: rect3d.bottomRight.z };
const lineStarts = [
this.matMultiply(rect3d.transform, {
x: rect3d.center.x - rect3d.width / 2,
y: rect3d.center.y,
z: rect3d.center.z
}),
this.matMultiply(rect3d.transform, {
x: rect3d.center.x + rect3d.width / 2,
y: rect3d.center.y,
z: rect3d.center.z
}),
this.matMultiply(rect3d.transform, {
x: rect3d.center.x,
y: rect3d.center.y - rect3d.width / 2,
z: rect3d.center.z
}),
this.matMultiply(rect3d.transform, {
x: rect3d.center.x,
y: rect3d.center.y + rect3d.width / 2,
z: rect3d.center.z
})
this.matMultiply(rect3d.transform, rect3d.topLeft),
this.matMultiply(rect3d.transform, rect3d.bottomRight),
this.matMultiply(rect3d.transform, bottomLeft),
this.matMultiply(rect3d.transform, topRight)
];
let maxIndex = 0;
for (let i = 1; i < lineStarts.length; i++) {
@@ -350,7 +361,7 @@ class Mapper3D {
width: 1,
height: 1,
depth: 1,
center: {x: 0, y: 0, z: 0},
center: { x: 0, y: 0, z: 0 },
diagonal: Math.sqrt(3)
};
}
@@ -372,7 +383,7 @@ class Mapper3D {
};
rects.forEach(rect => {
const topLeft: Point3D = {
/*const topLeft: Point3D = {
x: rect.center.x - rect.width / 2,
y: rect.center.y + rect.height / 2,
z: rect.center.z
@@ -381,9 +392,9 @@ class Mapper3D {
x: rect.center.x + rect.width / 2,
y: rect.center.y - rect.height / 2,
z: rect.center.z
};
updateMinMaxCoordinates(topLeft);
updateMinMaxCoordinates(bottomRight);
};*/
updateMinMaxCoordinates(rect.topLeft);
updateMinMaxCoordinates(rect.bottomRight);
});
labels.forEach(label => {
@@ -412,4 +423,4 @@ class Mapper3D {
}
}
export {Mapper3D};
export { Mapper3D };

View File

@@ -84,15 +84,14 @@ describe("RectsComponent", () => {
ty: 0
}
},
height: 1,
width: 1,
isVisible: true,
isDisplay: false,
ref: null,
id: "test-id-1234",
displayId: 0,
isVirtual: false,
isClickable: false
isClickable: false,
cornerRadius: 0
};
expect(Canvas.prototype.draw).toHaveBeenCalledTimes(0);

View File

@@ -35,13 +35,14 @@ export interface Box3D {
export interface Rect3D {
id: string;
center: Point3D;
width: number;
height: number;
topLeft: Point3D;
bottomRight: Point3D;
cornerRadius: number;
darkFactor: number;
colorType: ColorType;
isClickable: boolean;
transform: Transform3D;
isOversized: boolean;
}
export interface Transform3D {

View File

@@ -75,8 +75,6 @@ describe("PresenterSurfaceFlinger", () => {
expect(uiData.rects.length).toBeGreaterThan(0);
expect(uiData.rects[0].topLeft).toEqual({x: 0, y: 0});
expect(uiData.rects[0].bottomRight).toEqual({x: 1080, y: 118});
expect(uiData.rects[0].width).toEqual(1080);
expect(uiData.rects[0].height).toEqual(118);
});
it("updates pinned items", () => {

View File

@@ -114,6 +114,7 @@ export class Presenter {
rect.stableId = `Display - ${display.id}`;
rect.displayId = display.layerStackId;
rect.isDisplay = true;
rect.cornerRadius = 0;
rect.isVirtual = display.isVirtual ?? false;
rect.transform = {
matrix: display.transform.matrix
@@ -126,6 +127,7 @@ export class Presenter {
.map((it: any) => {
const rect = it.rect;
rect.displayId = it.stackId;
rect.cornerRadius = it.cornerRadius;
if (!this.displayIds.includes(it.stackId)) {
this.displayIds.push(it.stackId);
}
@@ -214,8 +216,6 @@ export class Presenter {
const newRect: Rectangle = {
topLeft: {x: rect.left, y: rect.top},
bottomRight: {x: rect.right, y: rect.bottom},
height: rect.height,
width: rect.width,
label: rect.label,
transform: transform,
isVisible: rect.ref?.isVisible ?? false,
@@ -224,7 +224,8 @@ export class Presenter {
id: rect.stableId ?? rect.ref.stableId,
displayId: rect.displayId ?? rect.ref.stackId,
isVirtual: rect.isVirtual ?? false,
isClickable: !(rect.isDisplay ?? false)
isClickable: !(rect.isDisplay ?? false),
cornerRadius: rect.cornerRadius
};
uiRects.push(newRect);
});

View File

@@ -79,8 +79,6 @@ describe("PresenterWindowManager", () => {
expect(uiData.rects.length).toBeGreaterThan(0);
expect(uiData.rects[0].topLeft).toEqual({x: 0, y: 2326});
expect(uiData.rects[0].bottomRight).toEqual({x: 1080, y: 2400});
expect(uiData.rects[0].width).toEqual(1080);
expect(uiData.rects[0].height).toEqual(74);
});
it("updates pinned items", () => {

View File

@@ -178,8 +178,6 @@ export class Presenter {
const newRect: Rectangle = {
topLeft: {x: rect.left, y: rect.top},
bottomRight: {x: rect.right, y: rect.bottom},
height: rect.height,
width: rect.width,
label: rect.label,
transform: transform,
isVisible: rect.ref?.isVisible ?? false,
@@ -189,6 +187,7 @@ export class Presenter {
displayId: rect.displayId ?? rect.ref.stackId,
isVirtual: rect.isVirtual ?? false,
isClickable: !rect.isDisplay,
cornerRadius: rect.cornerRadius,
};
uiRects.push(newRect);
});