(Properties View) Create viewer for SF traces.
Created the viewer component for SF traces, including rects view, properties view, hierarchy view (for re-use in other apps). This CL contains properties view and changes to associated reusable components. Currently follows same design as go/winscope-beta. Bug: b/238089034 b/232081297 Test: npm run test:all. upload/run any SF trace to see what it looks like. Change-Id: I6f9f0b1002ab9901f1aee113cbb0fef4cbd078a6
This commit is contained in:
@@ -35,7 +35,10 @@ import { TraceViewHeaderComponent } from "./components/trace_view_header.compone
|
||||
import { TraceViewComponent } from "./components/trace_view.component";
|
||||
import { TreeComponent } from "viewers/components/tree.component";
|
||||
import { TreeNodeComponent } from "viewers/components/tree_node.component";
|
||||
import { TreeElementComponent } from "viewers/components/tree_element.component";
|
||||
import { TreeNodeDataViewComponent } from "viewers/components/tree_node_data_view.component";
|
||||
import { TreeNodePropertiesDataViewComponent } from "viewers/components/tree_node_properties_data_view.component";
|
||||
import { PropertyGroupsComponent } from "viewers/components/property_groups.component";
|
||||
import { TransformMatrixComponent } from "viewers/components/transform_matrix.component";
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -54,7 +57,10 @@ import { TreeElementComponent } from "viewers/components/tree_element.component"
|
||||
TraceViewComponent,
|
||||
TreeComponent,
|
||||
TreeNodeComponent,
|
||||
TreeElementComponent
|
||||
TreeNodeDataViewComponent,
|
||||
TreeNodePropertiesDataViewComponent,
|
||||
PropertyGroupsComponent,
|
||||
TransformMatrixComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
|
||||
@@ -49,7 +49,7 @@ import { LoadedTrace } from "app/loaded_trace";
|
||||
<mat-icon class= "listed-file-item">{{TRACE_INFO[trace.type].icon}}</mat-icon>
|
||||
<span class="listed-file-item">{{trace.name}} ({{TRACE_INFO[trace.type].name}})</span>
|
||||
<button
|
||||
(click)="onRemoveTrace(trace)"
|
||||
(click)="onRemoveTrace($event, trace)"
|
||||
class="icon-button close-btn listed-file-item"
|
||||
><mat-icon>close</mat-icon>
|
||||
</button>
|
||||
@@ -180,7 +180,9 @@ export class UploadTracesComponent {
|
||||
await this.processFiles(Array.from(droppedFiles));
|
||||
}
|
||||
|
||||
public onRemoveTrace(trace: LoadedTrace) {
|
||||
public onRemoveTrace(event: MouseEvent, trace: LoadedTrace) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.traceCoordinator.removeTrace(trace.type);
|
||||
this.loadedTraces = this.loadedTraces.filter(loaded => loaded.type !== trace.type);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
--default-border: #DADCE0;
|
||||
--default-blue: #1A73E8;
|
||||
}
|
||||
|
||||
#app-title {
|
||||
font-family: 'Google Sans', sans-serif;
|
||||
font-size: 30;
|
||||
@@ -31,6 +32,10 @@
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.labels-canvas div {
|
||||
font-family: 'Google Sans', sans-serif;
|
||||
}
|
||||
|
||||
h1, p, span {
|
||||
font-family: 'Google Sans Text', sans-serif;
|
||||
font-weight: 400;
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { RELATIVE_Z_CHIP } from "viewers/common/chip";
|
||||
import { getFilter, TreeGenerator } from "viewers/common/tree_utils";
|
||||
import { DiffType, getFilter } from "viewers/common/tree_utils";
|
||||
import { TreeGenerator } from "viewers/common/tree_generator";
|
||||
|
||||
describe("TreeGenerator", () => {
|
||||
it("generates tree", () => {
|
||||
@@ -71,9 +72,8 @@ describe("TreeGenerator", () => {
|
||||
showInFilteredView: true,
|
||||
};
|
||||
|
||||
const userOptions = {};
|
||||
const filter = getFilter("");
|
||||
const generator = new TreeGenerator(tree, userOptions, filter);
|
||||
const generator = new TreeGenerator(tree, filter);
|
||||
expect(generator.generateTree()).toEqual(expected);
|
||||
});
|
||||
|
||||
@@ -119,27 +119,26 @@ describe("TreeGenerator", () => {
|
||||
showInFilteredView: true,
|
||||
stableId: "2",
|
||||
shortName: undefined,
|
||||
diff: "none",
|
||||
diffType: DiffType.NONE,
|
||||
chips: [ RELATIVE_Z_CHIP ]
|
||||
}],
|
||||
kind: "3",
|
||||
shortName: undefined,
|
||||
simplifyNames: false,
|
||||
showInFilteredView: true,
|
||||
diff: "none",
|
||||
chips: [ RELATIVE_Z_CHIP ]
|
||||
chips: [ RELATIVE_Z_CHIP ],
|
||||
diffType: DiffType.NONE
|
||||
}
|
||||
],
|
||||
kind: "entry",
|
||||
shortName: "BLTE",
|
||||
chips: [],
|
||||
diffType: DiffType.NONE,
|
||||
showInFilteredView: true,
|
||||
diff: "none"
|
||||
};
|
||||
|
||||
const userOptions = {};
|
||||
const filter = getFilter("");
|
||||
const generator = new TreeGenerator(tree, userOptions, filter);
|
||||
const generator = new TreeGenerator(tree, filter);
|
||||
expect(generator.withUniqueNodeId((node: any) => {
|
||||
if (node) return node.stableId;
|
||||
else return null;
|
||||
@@ -201,48 +200,47 @@ describe("TreeGenerator", () => {
|
||||
id: "3",
|
||||
stableId: "3",
|
||||
name: "Child1",
|
||||
diff: "none",
|
||||
children: [ {
|
||||
kind: "2",
|
||||
id: "2",
|
||||
name: "Child2",
|
||||
diff: "addedMove",
|
||||
children: [],
|
||||
simplifyNames: false,
|
||||
showInFilteredView: true,
|
||||
stableId: "2",
|
||||
shortName: undefined,
|
||||
diffType: DiffType.ADDED_MOVE,
|
||||
chips: [ RELATIVE_Z_CHIP ]
|
||||
}],
|
||||
kind: "3",
|
||||
shortName: undefined,
|
||||
simplifyNames: false,
|
||||
showInFilteredView: true,
|
||||
chips: [ RELATIVE_Z_CHIP ]
|
||||
chips: [ RELATIVE_Z_CHIP ],
|
||||
diffType: DiffType.NONE
|
||||
},
|
||||
{
|
||||
kind: "2",
|
||||
id: "2",
|
||||
name: "Child2",
|
||||
diff: "deletedMove",
|
||||
children: [],
|
||||
simplifyNames: false,
|
||||
showInFilteredView: true,
|
||||
stableId: "2",
|
||||
shortName: undefined,
|
||||
chips: [ RELATIVE_Z_CHIP ]
|
||||
chips: [ RELATIVE_Z_CHIP ],
|
||||
diffType: DiffType.DELETED_MOVE
|
||||
}
|
||||
],
|
||||
kind: "entry",
|
||||
shortName: "BLTE",
|
||||
chips: [],
|
||||
showInFilteredView: true,
|
||||
diff: "none"
|
||||
diffType: DiffType.NONE,
|
||||
showInFilteredView: true
|
||||
};
|
||||
|
||||
const userOptions = {};
|
||||
const filter = getFilter("");
|
||||
const generator = new TreeGenerator(tree, userOptions, filter);
|
||||
const generator = new TreeGenerator(tree, filter);
|
||||
const newDiffTree = generator.withUniqueNodeId((node: any) => {
|
||||
if (node) return node.stableId;
|
||||
else return null;
|
||||
432
tools/winscope-ng/src/viewers/common/tree_generator.ts
Normal file
432
tools/winscope-ng/src/viewers/common/tree_generator.ts
Normal file
@@ -0,0 +1,432 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {
|
||||
FilterType,
|
||||
Tree,
|
||||
DiffType
|
||||
} from "./tree_utils";
|
||||
import ObjectFormatter from "common/trace/flickerlib/ObjectFormatter";
|
||||
import {
|
||||
HWC_CHIP,
|
||||
GPU_CHIP,
|
||||
MISSING_LAYER,
|
||||
VISIBLE_CHIP,
|
||||
RELATIVE_Z_CHIP,
|
||||
RELATIVE_Z_PARENT_CHIP
|
||||
} from "viewers/common/chip";
|
||||
|
||||
type GetNodeIdCallbackType = (node: Tree | null) => number | null;
|
||||
type IsModifiedCallbackType = (newTree: Tree | null, oldTree: Tree | null) => boolean;
|
||||
interface IdNodeMap {
|
||||
[key: string]: Tree
|
||||
}
|
||||
const HwcCompositionType = {
|
||||
CLIENT: 1,
|
||||
DEVICE: 2,
|
||||
SOLID_COLOR: 3,
|
||||
};
|
||||
|
||||
export class TreeGenerator {
|
||||
private isOnlyVisibleView = false;
|
||||
private isSimplifyNames = false;
|
||||
private isFlatView = false;
|
||||
private filter: FilterType;
|
||||
private tree: Tree;
|
||||
private diffWithTree: Tree | null = null;
|
||||
private getNodeId?: GetNodeIdCallbackType;
|
||||
private isModified?: IsModifiedCallbackType;
|
||||
private newMapping: IdNodeMap | null = null;
|
||||
private oldMapping: IdNodeMap | null = null;
|
||||
private readonly pinnedIds: Array<string>;
|
||||
private pinnedItems: Array<Tree> = [];
|
||||
private relZParentIds: Array<number> = [];
|
||||
private flattenedChildren: Array<Tree> = [];
|
||||
|
||||
constructor(tree: Tree, filter: FilterType, pinnedIds?: Array<string>) {
|
||||
this.tree = tree;
|
||||
this.filter = filter;
|
||||
this.pinnedIds = pinnedIds ?? [];
|
||||
}
|
||||
|
||||
public setIsOnlyVisibleView(enabled: boolean) {
|
||||
this.isOnlyVisibleView = enabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
public setIsSimplifyNames(enabled: boolean) {
|
||||
this.isSimplifyNames = enabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
public setIsFlatView(enabled: boolean) {
|
||||
this.isFlatView = enabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
public generateTree(): Tree {
|
||||
return this.getCustomisedTree(this.tree);
|
||||
}
|
||||
|
||||
public compareWith(tree: Tree | null): TreeGenerator {
|
||||
this.diffWithTree = tree;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withUniqueNodeId(getNodeId?: GetNodeIdCallbackType): TreeGenerator {
|
||||
this.getNodeId = (node: Tree | null) => {
|
||||
const id = getNodeId ? getNodeId(node) : this.defaultNodeIdCallback(node);
|
||||
if (id === null || id === undefined) {
|
||||
console.error("Null node ID for node", node);
|
||||
throw new Error("Node ID can't be null or undefined");
|
||||
}
|
||||
return id;
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
public withModifiedCheck(isModified?: IsModifiedCallbackType): TreeGenerator {
|
||||
this.isModified = isModified ?? this.defaultModifiedCheck;
|
||||
return this;
|
||||
}
|
||||
|
||||
public generateFinalDiffTree(): Tree {
|
||||
this.newMapping = this.generateIdToNodeMapping(this.tree);
|
||||
this.oldMapping = this.diffWithTree ? this.generateIdToNodeMapping(this.diffWithTree) : null;
|
||||
|
||||
const diffTrees = this.generateDiffTree(this.tree, this.diffWithTree, [], []);
|
||||
|
||||
let diffTree;
|
||||
if (diffTrees.length > 1) {
|
||||
diffTree = {
|
||||
kind: "",
|
||||
name: "DiffTree",
|
||||
children: diffTrees,
|
||||
stableId: "DiffTree",
|
||||
};
|
||||
} else {
|
||||
diffTree = diffTrees[0];
|
||||
}
|
||||
return this.getCustomisedTree(diffTree);
|
||||
}
|
||||
|
||||
private getCustomisedTree(tree: Tree | null) {
|
||||
if (!tree) return null;
|
||||
tree = this.generateTreeWithUserOptions(tree, false);
|
||||
tree = this.updateTreeWithRelZParentChips(tree);
|
||||
|
||||
if (this.isFlatView && tree.children) {
|
||||
this.flattenChildren(tree.children);
|
||||
tree.children = this.flattenedChildren;
|
||||
}
|
||||
return Object.freeze(tree);
|
||||
}
|
||||
|
||||
public getPinnedItems() {
|
||||
return this.pinnedItems;
|
||||
}
|
||||
|
||||
private flattenChildren(children: Array<Tree>): Tree {
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i];
|
||||
const showInOnlyVisibleView = this.isOnlyVisibleView && child.isVisible;
|
||||
const passVisibleCheck = !this.isOnlyVisibleView || showInOnlyVisibleView;
|
||||
if (this.filterMatches(child) && passVisibleCheck) {
|
||||
this.flattenedChildren.push(child);
|
||||
}
|
||||
if (child.children) {
|
||||
this.flattenChildren(child.children);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private filterMatches(item: Tree | null): boolean {
|
||||
return this.filter(item) ?? false;
|
||||
}
|
||||
|
||||
private generateTreeWithUserOptions(
|
||||
tree: Tree | null,
|
||||
parentFilterMatch: boolean
|
||||
): Tree | null {
|
||||
return tree ? this.applyChecks(
|
||||
tree,
|
||||
this.cloneNode(tree, true),
|
||||
parentFilterMatch
|
||||
) : null;
|
||||
}
|
||||
|
||||
private updateTreeWithRelZParentChips(tree: Tree): Tree {
|
||||
return this.applyRelZParentCheck(tree);
|
||||
}
|
||||
|
||||
private applyRelZParentCheck(tree: Tree) {
|
||||
if (this.relZParentIds.includes(tree.id)) {
|
||||
tree.chips.push(RELATIVE_Z_PARENT_CHIP);
|
||||
}
|
||||
|
||||
const numOfChildren = tree.children?.length ?? 0;
|
||||
for (let i = 0; i < numOfChildren; i++) {
|
||||
tree.children[i] = this.updateTreeWithRelZParentChips(tree.children[i]);
|
||||
}
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
private addChips(tree: Tree) {
|
||||
tree.chips = [];
|
||||
if (tree.hwcCompositionType == HwcCompositionType.CLIENT) {
|
||||
tree.chips.push(GPU_CHIP);
|
||||
} else if ((tree.hwcCompositionType == HwcCompositionType.DEVICE ||
|
||||
tree.hwcCompositionType == HwcCompositionType.SOLID_COLOR)) {
|
||||
tree.chips.push(HWC_CHIP);
|
||||
}
|
||||
if (tree.isVisible && tree.kind !== "entry") {
|
||||
tree.chips.push(VISIBLE_CHIP);
|
||||
}
|
||||
if (tree.zOrderRelativeOfId !== -1 && tree.kind !== "entry" && !tree.isRootLayer) {
|
||||
tree.chips.push(RELATIVE_Z_CHIP);
|
||||
this.relZParentIds.push(tree.zOrderRelativeOfId);
|
||||
}
|
||||
if (tree.isMissing) {
|
||||
tree.chips.push(MISSING_LAYER);
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
|
||||
private applyChecks(
|
||||
tree: Tree | null,
|
||||
newTree: Tree | null,
|
||||
parentFilterMatch: boolean
|
||||
): Tree | null {
|
||||
if (!tree || !newTree) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// simplify names check
|
||||
newTree.simplifyNames = this.isSimplifyNames;
|
||||
|
||||
// check item either matches filter, or has parents/children matching filter
|
||||
if (tree.kind === "entry" || parentFilterMatch) {
|
||||
newTree.showInFilteredView = true;
|
||||
} else {
|
||||
newTree.showInFilteredView = this.filterMatches(tree);
|
||||
parentFilterMatch = newTree.showInFilteredView;
|
||||
}
|
||||
|
||||
if (this.isOnlyVisibleView) {
|
||||
newTree.showInOnlyVisibleView = newTree.isVisible;
|
||||
}
|
||||
|
||||
newTree.children = [];
|
||||
const numOfChildren = tree.children?.length ?? 0;
|
||||
for (let i = 0; i < numOfChildren; i++) {
|
||||
const child = tree.children[i];
|
||||
const newTreeChild = this.generateTreeWithUserOptions(child, parentFilterMatch);
|
||||
|
||||
if (newTreeChild) {
|
||||
if (newTreeChild.showInFilteredView) {
|
||||
newTree.showInFilteredView = true;
|
||||
}
|
||||
|
||||
if (this.isOnlyVisibleView && newTreeChild.showInOnlyVisibleView) {
|
||||
newTree.showInOnlyVisibleView = true;
|
||||
}
|
||||
|
||||
newTree.children.push(newTreeChild);
|
||||
}
|
||||
}
|
||||
|
||||
const doNotShowInOnlyVisibleView = this.isOnlyVisibleView && !newTree.showInOnlyVisibleView;
|
||||
if (!newTree.showInFilteredView || doNotShowInOnlyVisibleView) {
|
||||
return null;
|
||||
}
|
||||
|
||||
newTree = this.addChips(newTree);
|
||||
|
||||
if (this.pinnedIds.includes(`${newTree.id}`)) {
|
||||
this.pinnedItems.push(newTree);
|
||||
}
|
||||
|
||||
return newTree;
|
||||
}
|
||||
|
||||
private generateIdToNodeMapping(node: Tree, acc?: IdNodeMap): IdNodeMap {
|
||||
acc = acc || {};
|
||||
|
||||
const nodeId = this.getNodeId!(node)!;
|
||||
|
||||
if (acc[nodeId]) {
|
||||
throw new Error(`Duplicate node id '${nodeId}' detected...`);
|
||||
}
|
||||
acc[nodeId] = node;
|
||||
|
||||
if (node.children) {
|
||||
for (const child of node.children) {
|
||||
this.generateIdToNodeMapping(child, acc);
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
private cloneNode(node: Tree | null, postDiff = false): Tree | null {
|
||||
const clone = ObjectFormatter.cloneObject(node);
|
||||
if (node) {
|
||||
clone.children = node.children;
|
||||
clone.name = node.name;
|
||||
clone.kind = node.kind;
|
||||
clone.stableId = node.stableId;
|
||||
clone.shortName = node.shortName;
|
||||
if ("chips" in node) {
|
||||
clone.chips = node.chips.slice();
|
||||
}
|
||||
if (postDiff && "diffType" in node) {
|
||||
clone.diffType = node.diffType;
|
||||
}
|
||||
}
|
||||
return clone;
|
||||
}
|
||||
|
||||
private generateDiffTree(
|
||||
newTree: Tree | null,
|
||||
oldTree: Tree | null,
|
||||
newTreeSiblings: Array<Tree | null>,
|
||||
oldTreeSiblings: Array<Tree | null>
|
||||
): Array<Tree | null> {
|
||||
const diffTrees = [];
|
||||
// NOTE: A null ID represents a non existent node.
|
||||
if (!this.getNodeId) {
|
||||
return [];
|
||||
}
|
||||
const newId = newTree ? this.getNodeId(newTree) : null;
|
||||
const oldId = oldTree ? this.getNodeId(oldTree) : null;
|
||||
|
||||
const newTreeSiblingIds = newTreeSiblings.map(this.getNodeId);
|
||||
const oldTreeSiblingIds = oldTreeSiblings.map(this.getNodeId);
|
||||
|
||||
if (newTree) {
|
||||
// Clone is required because trees are frozen objects — we can't modify the original tree object.
|
||||
const diffTree = this.cloneNode(newTree)!;
|
||||
|
||||
// Default to no changes
|
||||
diffTree.diffType = DiffType.NONE;
|
||||
|
||||
if (newTree.kind !== "entry" && newId !== oldId) {
|
||||
// A move, addition, or deletion has occurred
|
||||
let nextOldTree = null;
|
||||
|
||||
// Check if newTree has been added or moved
|
||||
if (newId && !oldTreeSiblingIds.includes(newId)) {
|
||||
if (this.oldMapping && this.oldMapping[newId]) {
|
||||
// Objected existed in old tree, so DELETED_MOVE will be/has been flagged and added to the
|
||||
// diffTree when visiting it in the oldTree.
|
||||
diffTree.diffType = DiffType.ADDED_MOVE;
|
||||
|
||||
// Switch out oldTree for new one to compare against
|
||||
nextOldTree = this.oldMapping[newId];
|
||||
} else {
|
||||
diffTree.diffType = DiffType.ADDED;
|
||||
|
||||
// Stop comparing against oldTree
|
||||
nextOldTree = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if oldTree has been deleted of moved
|
||||
if (oldId && oldTree && !newTreeSiblingIds.includes(oldId)) {
|
||||
const deletedTreeDiff = this.cloneNode(oldTree)!;
|
||||
|
||||
if (this.newMapping![oldId]) {
|
||||
deletedTreeDiff.diffType = DiffType.DELETED_MOVE;
|
||||
|
||||
// Stop comparing against oldTree, will be/has been
|
||||
// visited when object is seen in newTree
|
||||
nextOldTree = null;
|
||||
} else {
|
||||
deletedTreeDiff.diffType = DiffType.DELETED;
|
||||
|
||||
// Visit all children to check if they have been deleted or moved
|
||||
deletedTreeDiff.children = this.visitChildren(null, oldTree);
|
||||
}
|
||||
|
||||
diffTrees.push(deletedTreeDiff);
|
||||
}
|
||||
|
||||
oldTree = nextOldTree;
|
||||
} else {
|
||||
if (this.isModified && this.isModified(newTree, oldTree)) {
|
||||
diffTree.diffType = DiffType.MODIFIED;
|
||||
}
|
||||
}
|
||||
|
||||
diffTree.children = this.visitChildren(newTree, oldTree);
|
||||
diffTrees.push(diffTree);
|
||||
} else if (oldTree) {
|
||||
if (oldId && !newTreeSiblingIds.includes(oldId)) {
|
||||
// Deep clone oldTree omitting children field
|
||||
const diffTree = this.cloneNode(oldTree)!;
|
||||
|
||||
// newTree doesn't exist, oldTree has either been moved or deleted.
|
||||
if (this.newMapping![oldId]) {
|
||||
diffTree.diffType = DiffType.DELETED_MOVE;
|
||||
} else {
|
||||
diffTree.diffType = DiffType.DELETED;
|
||||
}
|
||||
|
||||
diffTree.children = this.visitChildren(null, oldTree);
|
||||
diffTrees.push(diffTree);
|
||||
}
|
||||
} else {
|
||||
throw new Error("Both newTree and oldTree are undefined...");
|
||||
}
|
||||
|
||||
return diffTrees;
|
||||
}
|
||||
|
||||
private visitChildren(newTree: Tree | null, oldTree: Tree | null) {
|
||||
// Recursively traverse all children of new and old tree.
|
||||
const diffChildren = [];
|
||||
|
||||
if (!newTree) newTree = {};
|
||||
if (!oldTree) oldTree = {};
|
||||
const numOfChildren = Math.max(newTree.children?.length ?? 0, oldTree.children?.length ?? 0);
|
||||
for (let i = 0; i < numOfChildren; i++) {
|
||||
const newChild = newTree.children ? newTree.children[i] : null;
|
||||
const oldChild = oldTree.children ? oldTree.children[i] : null;
|
||||
|
||||
const childDiffTrees = this.generateDiffTree(
|
||||
newChild, oldChild,
|
||||
newTree.children ?? [], oldTree.children ?? [],
|
||||
);
|
||||
diffChildren.push(...childDiffTrees);
|
||||
}
|
||||
|
||||
return diffChildren;
|
||||
}
|
||||
|
||||
private defaultNodeIdCallback(node: Tree | null): number | null {
|
||||
return node ? node.stableId : null;
|
||||
}
|
||||
|
||||
private defaultModifiedCheck(newNode: Tree | null, oldNode: Tree | null): boolean {
|
||||
if (!newNode && !oldNode) {
|
||||
return false;
|
||||
} else if (newNode && newNode.kind==="entry") {
|
||||
return false;
|
||||
} else if ((newNode && !oldNode) || (!newNode && oldNode)) {
|
||||
return true;
|
||||
}
|
||||
return !newNode.equals(oldNode);
|
||||
}
|
||||
}
|
||||
383
tools/winscope-ng/src/viewers/common/tree_transformer.ts
Normal file
383
tools/winscope-ng/src/viewers/common/tree_transformer.ts
Normal file
@@ -0,0 +1,383 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import ObjectFormatter from "common/trace/flickerlib/ObjectFormatter";
|
||||
|
||||
import {
|
||||
FilterType,
|
||||
PropertiesTree,
|
||||
Tree,
|
||||
DiffType,
|
||||
Terminal
|
||||
} from "./tree_utils";
|
||||
|
||||
interface TransformOptions {
|
||||
freeze: boolean;
|
||||
keepOriginal: boolean;
|
||||
metadataKey: string | null;
|
||||
}
|
||||
interface TreeTransformerOptions {
|
||||
skip?: any;
|
||||
formatter?: any;
|
||||
}
|
||||
interface TransformedPropertiesObject {
|
||||
properties: any;
|
||||
diffType?: string;
|
||||
}
|
||||
|
||||
export class TreeTransformer {
|
||||
private stableId: string;
|
||||
private rootName: string;
|
||||
private isShowDefaults = false;
|
||||
private isShowDiff = false;
|
||||
private filter: FilterType;
|
||||
private properties: PropertiesTree;
|
||||
private compareWithProperties: PropertiesTree | null = null;
|
||||
private options?: TreeTransformerOptions;
|
||||
private transformOptions: TransformOptions = {
|
||||
keepOriginal: false, freeze: true, metadataKey: null,
|
||||
};
|
||||
|
||||
constructor(tree: Tree, filter: FilterType) {
|
||||
this.stableId = this.compatibleStableId(tree);
|
||||
this.rootName = tree.name;
|
||||
this.filter = filter;
|
||||
this.setProperties(tree);
|
||||
}
|
||||
|
||||
public setIsShowDefaults(enabled: boolean) {
|
||||
this.isShowDefaults = enabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
public setIsShowDiff(enabled: boolean) {
|
||||
this.isShowDiff = enabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
public setTransformerOptions(options: TreeTransformerOptions) {
|
||||
this.options = options;
|
||||
if (!this.options.formatter) {
|
||||
this.options.formatter = this.formatProto;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public setProperties(tree: Tree) {
|
||||
const target = tree.obj ?? tree;
|
||||
ObjectFormatter.displayDefaults = this.isShowDefaults;
|
||||
this.properties = this.getPropertiesForDisplay(target);
|
||||
}
|
||||
|
||||
public setDiffProperties(previousEntry: any) {
|
||||
if (this.isShowDiff) {
|
||||
const tree = this.findTree(previousEntry, this.stableId);
|
||||
const target = tree ? tree.obj ?? tree : null;
|
||||
this.compareWithProperties = this.getPropertiesForDisplay(target);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public getOriginalLayer(entry: any, stableId: string) {
|
||||
return this.findTree(entry, stableId);
|
||||
}
|
||||
|
||||
private getPropertiesForDisplay(entry: any): any {
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
|
||||
let obj: any = {};
|
||||
obj.proto = Object.assign({}, entry.proto);
|
||||
if (obj.proto.children) delete obj.proto.children;
|
||||
if (obj.proto.childWindows) delete obj.proto.childWindows;
|
||||
if (obj.proto.childrenWindows) delete obj.proto.childrenWindows;
|
||||
if (obj.proto.childContainers) delete obj.proto.childContainers;
|
||||
if (obj.proto.windowToken) delete obj.proto.windowToken;
|
||||
if (obj.proto.rootDisplayArea) delete obj.proto.rootDisplayArea;
|
||||
if (obj.proto.rootWindowContainer) delete obj.proto.rootWindowContainer;
|
||||
if (obj.proto.windowContainer?.children) delete obj.proto.windowContainer.children;
|
||||
|
||||
obj = ObjectFormatter.format(obj);
|
||||
|
||||
Object.keys(obj.proto).forEach((prop: string) => {
|
||||
if (Object.keys(obj.proto[prop]).length === 0) {
|
||||
obj.proto[prop] = "empty";
|
||||
}
|
||||
});
|
||||
return obj;
|
||||
}
|
||||
|
||||
private findTree(tree: any, stableId: string) {
|
||||
if (!tree) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (tree.stableId && tree.stableId === stableId) {
|
||||
return tree;
|
||||
}
|
||||
|
||||
if (!tree.children) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (const child of tree.children) {
|
||||
const foundEntry: any = this.findTree(child, stableId);
|
||||
if (foundEntry) {
|
||||
return foundEntry;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public transform() {
|
||||
const {formatter} = this.options!;
|
||||
if (!formatter) {
|
||||
throw new Error("Missing formatter, please set with setOptions()");
|
||||
}
|
||||
|
||||
const transformedTree = this.transformTree(this.properties, this.rootName,
|
||||
this.compareWithProperties, this.rootName,
|
||||
this.stableId, this.transformOptions);
|
||||
|
||||
return transformedTree;
|
||||
}
|
||||
|
||||
private transformTree(
|
||||
properties: PropertiesTree | Terminal,
|
||||
name: string | Terminal,
|
||||
compareWithProperties: PropertiesTree | Terminal,
|
||||
compareWithName: string | Terminal,
|
||||
stableId: string,
|
||||
transformOptions: TransformOptions,
|
||||
) {
|
||||
const originalProperties = properties;
|
||||
const metadata = this.getMetadata(
|
||||
originalProperties, transformOptions.metadataKey
|
||||
);
|
||||
|
||||
const children: any[] = [];
|
||||
|
||||
if (!this.isTerminal(properties)) {
|
||||
const transformedProperties = this.transformProperties(properties, transformOptions.metadataKey);
|
||||
properties = transformedProperties.properties;
|
||||
}
|
||||
|
||||
if (!this.isTerminal(compareWithProperties)) {
|
||||
const transformedProperties = this.transformProperties(
|
||||
compareWithProperties,
|
||||
transformOptions.metadataKey
|
||||
);
|
||||
compareWithProperties = transformedProperties.properties;
|
||||
}
|
||||
|
||||
for (const key in properties) {
|
||||
if (properties[key]) {
|
||||
let compareWithChild = new Terminal();
|
||||
let compareWithChildName = new Terminal();
|
||||
if (compareWithProperties[key]) {
|
||||
compareWithChild = compareWithProperties[key];
|
||||
compareWithChildName = key;
|
||||
}
|
||||
const child = this.transformTree(properties[key], key,
|
||||
compareWithChild, compareWithChildName,
|
||||
`${stableId}.${key}`, transformOptions);
|
||||
|
||||
children.push(child);
|
||||
}
|
||||
}
|
||||
|
||||
// Takes care of adding deleted items to final tree
|
||||
for (const key in compareWithProperties) {
|
||||
if (!properties[key] && compareWithProperties[key]) {
|
||||
const child = this.transformTree(new Terminal(), new Terminal(),
|
||||
compareWithProperties[key], key,
|
||||
`${stableId}.${key}`, transformOptions);
|
||||
|
||||
children.push(child);
|
||||
}
|
||||
}
|
||||
|
||||
let transformedProperties: any;
|
||||
if (
|
||||
children.length == 1 &&
|
||||
children[0].children?.length == 0 &&
|
||||
!children[0].combined
|
||||
) {
|
||||
// Merge leaf key value pairs.
|
||||
const child = children[0];
|
||||
|
||||
transformedProperties = {
|
||||
kind: "",
|
||||
name: (this.isTerminal(name) ? compareWithName : name) + ": " + child.name,
|
||||
stableId,
|
||||
children: child.children,
|
||||
combined: true,
|
||||
};
|
||||
|
||||
if (this.isShowDiff) {
|
||||
transformedProperties.diffType = child.diffType;
|
||||
}
|
||||
} else {
|
||||
transformedProperties = {
|
||||
kind: "",
|
||||
name,
|
||||
stableId,
|
||||
children,
|
||||
};
|
||||
|
||||
if (this.isShowDiff) {
|
||||
const diffType = this.getDiff(name, compareWithName);
|
||||
transformedProperties.diffType = diffType;
|
||||
|
||||
if (diffType == DiffType.DELETED) {
|
||||
transformedProperties.name = compareWithName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (transformOptions.keepOriginal) {
|
||||
transformedProperties.properties = originalProperties;
|
||||
}
|
||||
|
||||
if (metadata && transformOptions.metadataKey) {
|
||||
transformedProperties[transformOptions.metadataKey] = metadata;
|
||||
}
|
||||
|
||||
if (!this.isTerminal(transformedProperties.name)) {
|
||||
transformedProperties.propertyKey = this.getPropertyKey(transformedProperties);
|
||||
transformedProperties.propertyValue = this.getPropertyValue(transformedProperties);
|
||||
}
|
||||
|
||||
if (!this.filterMatches(transformedProperties) &&
|
||||
!this.hasChildMatchingFilter(transformedProperties?.children)) {
|
||||
transformedProperties.propertyKey = new Terminal();
|
||||
}
|
||||
|
||||
return transformOptions.freeze ? Object.freeze(transformedProperties) : transformedProperties;
|
||||
}
|
||||
|
||||
private hasChildMatchingFilter(children: PropertiesTree[] | null | undefined) {
|
||||
if (!children || children.length === 0) return false;
|
||||
|
||||
let match = false;
|
||||
for (let i=0; i<children.length; i++) {
|
||||
if (this.filterMatches(children[i]) || this.hasChildMatchingFilter(children[i].children)) {
|
||||
match = true;
|
||||
}
|
||||
}
|
||||
|
||||
return match;
|
||||
}
|
||||
|
||||
private getMetadata(obj: PropertiesTree, metadataKey: string | null) {
|
||||
if (metadataKey && obj[metadataKey]) {
|
||||
const metadata = obj[metadataKey];
|
||||
obj[metadataKey] = undefined;
|
||||
return metadata;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private getPropertyKey(item: PropertiesTree) {
|
||||
if (!item.children || item.children.length === 0) {
|
||||
return item.name.split(": ")[0];
|
||||
}
|
||||
return item.name;
|
||||
}
|
||||
|
||||
private getPropertyValue(item: PropertiesTree) {
|
||||
if (!item.children || item.children.length === 0) {
|
||||
return item.name.split(": ").slice(1).join(": ");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private filterMatches(item: PropertiesTree | null): boolean {
|
||||
return this.filter(item) ?? false;
|
||||
}
|
||||
|
||||
private transformProperties(properties: PropertiesTree, metadataKey: string | null) {
|
||||
const {skip, formatter} = this.options!;
|
||||
const transformedProperties: TransformedPropertiesObject = {
|
||||
properties: {},
|
||||
};
|
||||
let formatted = undefined;
|
||||
|
||||
if (skip && skip.includes(properties)) {
|
||||
// skip
|
||||
} else if ((formatted = formatter(properties))) {
|
||||
// Obj has been formatted into a terminal node — has no children.
|
||||
transformedProperties.properties[formatted] = new Terminal();
|
||||
} else if (Array.isArray(properties)) {
|
||||
properties.forEach((e, i) => {
|
||||
transformedProperties.properties["" + i] = e;
|
||||
});
|
||||
} else if (typeof properties == "string") {
|
||||
// Object is a primitive type — has no children. Set to terminal
|
||||
// to differentiate between null object and Terminal element.
|
||||
transformedProperties.properties[properties] = new Terminal();
|
||||
} else if (typeof properties == "number" || typeof properties == "boolean") {
|
||||
// Similar to above — primitive type node has no children.
|
||||
transformedProperties.properties["" + properties] = new Terminal();
|
||||
} else if (properties && typeof properties == "object") {
|
||||
Object.keys(properties).forEach((key) => {
|
||||
if (key === metadataKey) {
|
||||
return;
|
||||
}
|
||||
transformedProperties.properties[key] = properties[key];
|
||||
});
|
||||
} else if (properties === null) {
|
||||
// Null object has no children — set to be terminal node.
|
||||
transformedProperties.properties.null = new Terminal();
|
||||
}
|
||||
return transformedProperties;
|
||||
}
|
||||
|
||||
private getDiff(val: string | Terminal, compareVal: string | Terminal) {
|
||||
if (val && this.isTerminal(compareVal)) {
|
||||
return DiffType.ADDED;
|
||||
} else if (this.isTerminal(val) && compareVal) {
|
||||
return DiffType.DELETED;
|
||||
} else if (compareVal != val) {
|
||||
return DiffType.MODIFIED;
|
||||
} else {
|
||||
return DiffType.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
private compatibleStableId(item: Tree) {
|
||||
// For backwards compatibility
|
||||
// (the only item that doesn't have a unique stable ID in the tree)
|
||||
if (item.stableId === "winToken|-|") {
|
||||
return item.stableId + item.children[0].stableId;
|
||||
}
|
||||
return item.stableId;
|
||||
}
|
||||
|
||||
private formatProto(item: any) {
|
||||
if (item?.prettyPrint) {
|
||||
return item.prettyPrint();
|
||||
}
|
||||
}
|
||||
|
||||
private isTerminal(item: any) {
|
||||
return item instanceof Terminal;
|
||||
}
|
||||
}
|
||||
@@ -14,14 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Layer, BaseLayerTraceEntry } from "common/trace/flickerlib/common";
|
||||
import ObjectFormatter from "common/trace/flickerlib/ObjectFormatter";
|
||||
import { UserOptions } from "viewers/common/user_options";
|
||||
import { HWC_CHIP, GPU_CHIP, MISSING_LAYER, VISIBLE_CHIP, RELATIVE_Z_CHIP, RELATIVE_Z_PARENT_CHIP } from "viewers/common/chip";
|
||||
|
||||
type GetNodeIdCallbackType = (node: Tree | null) => number | null;
|
||||
type IsModifiedCallbackType = (newTree: Tree | null, oldTree: Tree | null) => boolean;
|
||||
interface IdNodeMap { [key: string]: Tree }
|
||||
const DiffType = {
|
||||
export type FilterType = (item: Tree | null) => boolean;
|
||||
export type Tree = Layer | BaseLayerTraceEntry;
|
||||
export type PropertiesTree = any; //TODO: make specific
|
||||
export type TreeSummary = Array<{key: string, value: string}>
|
||||
|
||||
export const DiffType = {
|
||||
NONE: "none",
|
||||
ADDED: "added",
|
||||
DELETED: "deleted",
|
||||
@@ -29,18 +28,12 @@ const DiffType = {
|
||||
DELETED_MOVE: "deletedMove",
|
||||
MODIFIED: "modified",
|
||||
};
|
||||
const HwcCompositionType = {
|
||||
CLIENT: 1,
|
||||
DEVICE: 2,
|
||||
SOLID_COLOR: 3,
|
||||
};
|
||||
|
||||
export type FilterType = (item: Tree | null) => boolean;
|
||||
export type Tree = Layer | BaseLayerTraceEntry;
|
||||
export class Terminal {}
|
||||
|
||||
export function diffClass(item: Tree): string {
|
||||
const diff = item!.diff;
|
||||
return diff ?? "";
|
||||
const diffType = item!.diffType;
|
||||
return diffType ?? "";
|
||||
}
|
||||
|
||||
export function isHighlighted(item: Tree, highlightedItems: Array<string>) {
|
||||
@@ -61,7 +54,7 @@ export function getFilter(filterString: string): FilterType {
|
||||
positive.push((s:any) => regex.test(s));
|
||||
}
|
||||
});
|
||||
const filter = (item:Tree | null) => {
|
||||
const filter = (item: any) => {
|
||||
if (item) {
|
||||
const apply = (f:any) => f(`${item.name}`);
|
||||
return (positive.length === 0 || positive.some(apply)) &&
|
||||
@@ -71,379 +64,3 @@ export function getFilter(filterString: string): FilterType {
|
||||
};
|
||||
return filter;
|
||||
}
|
||||
|
||||
export class TreeGenerator {
|
||||
private userOptions: UserOptions;
|
||||
private filter: FilterType;
|
||||
private tree: Tree;
|
||||
private diffWithTree: Tree | null = null;
|
||||
private getNodeId?: GetNodeIdCallbackType;
|
||||
private isModified?: IsModifiedCallbackType;
|
||||
private newMapping: IdNodeMap | null = null;
|
||||
private oldMapping: IdNodeMap | null = null;
|
||||
private readonly pinnedIds: Array<string>;
|
||||
private pinnedItems: Array<Tree> = [];
|
||||
private relZParentIds: Array<number> = [];
|
||||
private flattenedChildren: Array<Tree> = [];
|
||||
|
||||
constructor(tree: Tree, userOptions: UserOptions, filter: FilterType, pinnedIds?: Array<string>) {
|
||||
this.tree = tree;
|
||||
this.userOptions = userOptions;
|
||||
this.filter = filter;
|
||||
this.pinnedIds = pinnedIds ?? [];
|
||||
}
|
||||
|
||||
public generateTree(): Tree {
|
||||
return this.getCustomisedTree(this.tree);
|
||||
}
|
||||
|
||||
public compareWith(tree: Tree | null): TreeGenerator {
|
||||
this.diffWithTree = tree;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withUniqueNodeId(getNodeId?: GetNodeIdCallbackType): TreeGenerator {
|
||||
this.getNodeId = (node: Tree | null) => {
|
||||
const id = getNodeId ? getNodeId(node) : this.defaultNodeIdCallback(node);
|
||||
if (id === null || id === undefined) {
|
||||
console.error("Null node ID for node", node);
|
||||
throw new Error("Node ID can't be null or undefined");
|
||||
}
|
||||
return id;
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
public withModifiedCheck(isModified?: IsModifiedCallbackType): TreeGenerator {
|
||||
this.isModified = isModified ?? this.defaultModifiedCheck;
|
||||
return this;
|
||||
}
|
||||
|
||||
public generateFinalDiffTree(): Tree {
|
||||
this.newMapping = this.generateIdToNodeMapping(this.tree);
|
||||
this.oldMapping = this.diffWithTree ? this.generateIdToNodeMapping(this.diffWithTree) : null;
|
||||
|
||||
const diffTrees = this.generateDiffTree(this.tree, this.diffWithTree, [], []);
|
||||
|
||||
let diffTree;
|
||||
if (diffTrees.length > 1) {
|
||||
diffTree = {
|
||||
kind: "",
|
||||
name: "DiffTree",
|
||||
children: diffTrees,
|
||||
stableId: "DiffTree",
|
||||
};
|
||||
} else {
|
||||
diffTree = diffTrees[0];
|
||||
}
|
||||
return this.getCustomisedTree(diffTree);
|
||||
}
|
||||
|
||||
private getCustomisedTree(tree: Tree | null) {
|
||||
if (!tree) return null;
|
||||
tree = this.generateTreeWithUserOptions(tree, false);
|
||||
tree = this.updateTreeWithRelZParentChips(tree);
|
||||
|
||||
if (this.isFlatView() && tree.children) {
|
||||
this.flattenChildren(tree.children);
|
||||
tree.children = this.flattenedChildren;
|
||||
}
|
||||
return Object.freeze(tree);
|
||||
}
|
||||
|
||||
public getPinnedItems() {
|
||||
return this.pinnedItems;
|
||||
}
|
||||
|
||||
private flattenChildren(children: Array<Tree>): Tree {
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i];
|
||||
const showInOnlyVisibleView = this.isOnlyVisibleView() && child.isVisible;
|
||||
const passVisibleCheck = !this.isOnlyVisibleView() || showInOnlyVisibleView;
|
||||
if (this.filterMatches(child) && passVisibleCheck) {
|
||||
this.flattenedChildren.push(child);
|
||||
}
|
||||
if (child.children) {
|
||||
this.flattenChildren(child.children);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private isOnlyVisibleView(): boolean {
|
||||
return this.userOptions["onlyVisible"]?.enabled ?? false;
|
||||
}
|
||||
|
||||
private isSimplifyNames(): boolean {
|
||||
return this.userOptions["simplifyNames"]?.enabled ?? false;
|
||||
}
|
||||
|
||||
private isFlatView(): boolean {
|
||||
return this.userOptions["flat"]?.enabled ?? false;
|
||||
}
|
||||
|
||||
private filterMatches(item: Tree | null): boolean {
|
||||
return this.filter(item) ?? false;
|
||||
}
|
||||
|
||||
private generateTreeWithUserOptions(tree: Tree | null, parentFilterMatch: boolean): Tree | null {
|
||||
return tree ? this.applyChecks(tree, this.cloneNode(tree), parentFilterMatch) : null;
|
||||
}
|
||||
|
||||
private updateTreeWithRelZParentChips(tree: Tree): Tree {
|
||||
return this.applyRelZParentCheck(tree);
|
||||
}
|
||||
|
||||
private applyRelZParentCheck(tree: Tree) {
|
||||
if (this.relZParentIds.includes(tree.id)) {
|
||||
tree.chips.push(RELATIVE_Z_PARENT_CHIP);
|
||||
}
|
||||
|
||||
const numOfChildren = tree.children?.length ?? 0;
|
||||
for (let i = 0; i < numOfChildren; i++) {
|
||||
tree.children[i] = this.updateTreeWithRelZParentChips(tree.children[i]);
|
||||
}
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
private addChips(tree: Tree) {
|
||||
tree.chips = [];
|
||||
if (tree.hwcCompositionType == HwcCompositionType.CLIENT) {
|
||||
tree.chips.push(GPU_CHIP);
|
||||
} else if ((tree.hwcCompositionType == HwcCompositionType.DEVICE || tree.hwcCompositionType == HwcCompositionType.SOLID_COLOR)) {
|
||||
tree.chips.push(HWC_CHIP);
|
||||
}
|
||||
if (tree.isVisible && tree.kind !== "entry") {
|
||||
tree.chips.push(VISIBLE_CHIP);
|
||||
}
|
||||
if (tree.zOrderRelativeOfId !== -1 && tree.kind !== "entry" && !tree.isRootLayer) {
|
||||
tree.chips.push(RELATIVE_Z_CHIP);
|
||||
this.relZParentIds.push(tree.zOrderRelativeOfId);
|
||||
}
|
||||
if (tree.isMissing) {
|
||||
tree.chips.push(MISSING_LAYER);
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
|
||||
private applyChecks(tree: Tree | null, newTree: Tree | null, parentFilterMatch: boolean): Tree | null {
|
||||
if (!tree || !newTree) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// simplify names check
|
||||
newTree.simplifyNames = this.isSimplifyNames();
|
||||
|
||||
// check item either matches filter, or has parents/children matching filter
|
||||
if (tree.kind === "entry" || parentFilterMatch) {
|
||||
newTree.showInFilteredView = true;
|
||||
} else {
|
||||
newTree.showInFilteredView = this.filterMatches(tree);
|
||||
parentFilterMatch = newTree.showInFilteredView;
|
||||
}
|
||||
|
||||
if (this.isOnlyVisibleView()) {
|
||||
newTree.showInOnlyVisibleView = newTree.isVisible;
|
||||
}
|
||||
|
||||
newTree.children = [];
|
||||
const numOfChildren = tree.children?.length ?? 0;
|
||||
for (let i = 0; i < numOfChildren; i++) {
|
||||
const child = tree.children[i];
|
||||
const newTreeChild = this.generateTreeWithUserOptions(child, parentFilterMatch);
|
||||
|
||||
if (newTreeChild) {
|
||||
if (newTreeChild.showInFilteredView) {
|
||||
newTree.showInFilteredView = true;
|
||||
}
|
||||
|
||||
if (this.isOnlyVisibleView() && newTreeChild.showInOnlyVisibleView) {
|
||||
newTree.showInOnlyVisibleView = true;
|
||||
}
|
||||
|
||||
newTree.children.push(newTreeChild);
|
||||
}
|
||||
}
|
||||
|
||||
const doNotShowInOnlyVisibleView = this.isOnlyVisibleView() && !newTree.showInOnlyVisibleView;
|
||||
if (!newTree.showInFilteredView || doNotShowInOnlyVisibleView) {
|
||||
return null;
|
||||
}
|
||||
|
||||
newTree = this.addChips(newTree);
|
||||
|
||||
if (this.pinnedIds.includes(`${newTree.id}`)) {
|
||||
this.pinnedItems.push(newTree);
|
||||
}
|
||||
|
||||
return newTree;
|
||||
}
|
||||
|
||||
private generateIdToNodeMapping(node: Tree, acc?: IdNodeMap): IdNodeMap {
|
||||
acc = acc || {};
|
||||
|
||||
const nodeId = this.getNodeId!(node)!;
|
||||
|
||||
if (acc[nodeId]) {
|
||||
throw new Error(`Duplicate node id '${nodeId}' detected...`);
|
||||
}
|
||||
acc[nodeId] = node;
|
||||
|
||||
if (node.children) {
|
||||
for (const child of node.children) {
|
||||
this.generateIdToNodeMapping(child, acc);
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
private cloneNode(node: Tree | null): Tree | null {
|
||||
const clone = ObjectFormatter.cloneObject(node);
|
||||
if (node) {
|
||||
clone.children = node.children;
|
||||
clone.name = node.name;
|
||||
clone.kind = node.kind;
|
||||
clone.stableId = node.stableId;
|
||||
clone.shortName = node.shortName;
|
||||
if ("chips" in node) {
|
||||
clone.chips = node.chips.slice();
|
||||
}
|
||||
if ("diff" in node) {
|
||||
clone.diff = node.diff;
|
||||
}
|
||||
}
|
||||
return clone;
|
||||
}
|
||||
|
||||
private generateDiffTree(
|
||||
newTree: Tree | null,
|
||||
oldTree: Tree | null,
|
||||
newTreeSiblings: Array<Tree | null>,
|
||||
oldTreeSiblings: Array<Tree | null>
|
||||
): Array<Tree | null> {
|
||||
const diffTrees = [];
|
||||
// NOTE: A null ID represents a non existent node.
|
||||
if (!this.getNodeId) {
|
||||
return [];
|
||||
}
|
||||
const newId = newTree ? this.getNodeId(newTree) : null;
|
||||
const oldId = oldTree ? this.getNodeId(oldTree) : null;
|
||||
|
||||
const newTreeSiblingIds = newTreeSiblings.map(this.getNodeId);
|
||||
const oldTreeSiblingIds = oldTreeSiblings.map(this.getNodeId);
|
||||
|
||||
if (newTree) {
|
||||
// Clone is required because trees are frozen objects — we can't modify the original tree object.
|
||||
const diffTree = this.cloneNode(newTree)!;
|
||||
|
||||
// Default to no changes
|
||||
diffTree.diff = DiffType.NONE;
|
||||
|
||||
if (newTree.kind !== "entry" && newId !== oldId) {
|
||||
// A move, addition, or deletion has occurred
|
||||
let nextOldTree = null;
|
||||
|
||||
// Check if newTree has been added or moved
|
||||
if (newId && !oldTreeSiblingIds.includes(newId)) {
|
||||
if (this.oldMapping && this.oldMapping[newId]) {
|
||||
// Objected existed in old tree, so DELETED_MOVE will be/has been flagged and added to the
|
||||
// diffTree when visiting it in the oldTree.
|
||||
diffTree.diff = DiffType.ADDED_MOVE;
|
||||
|
||||
// Switch out oldTree for new one to compare against
|
||||
nextOldTree = this.oldMapping[newId];
|
||||
} else {
|
||||
diffTree.diff = DiffType.ADDED;
|
||||
|
||||
// Stop comparing against oldTree
|
||||
nextOldTree = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if oldTree has been deleted of moved
|
||||
if (oldId && oldTree && !newTreeSiblingIds.includes(oldId)) {
|
||||
const deletedTreeDiff = this.cloneNode(oldTree)!;
|
||||
|
||||
if (this.newMapping![oldId]) {
|
||||
deletedTreeDiff.diff = DiffType.DELETED_MOVE;
|
||||
|
||||
// Stop comparing against oldTree, will be/has been
|
||||
// visited when object is seen in newTree
|
||||
nextOldTree = null;
|
||||
} else {
|
||||
deletedTreeDiff.diff = DiffType.DELETED;
|
||||
|
||||
// Visit all children to check if they have been deleted or moved
|
||||
deletedTreeDiff.children = this.visitChildren(null, oldTree);
|
||||
}
|
||||
|
||||
diffTrees.push(deletedTreeDiff);
|
||||
}
|
||||
|
||||
oldTree = nextOldTree;
|
||||
} else {
|
||||
if (this.isModified && this.isModified(newTree, oldTree)) {
|
||||
diffTree.diff = DiffType.MODIFIED;
|
||||
}
|
||||
}
|
||||
|
||||
diffTree.children = this.visitChildren(newTree, oldTree);
|
||||
diffTrees.push(diffTree);
|
||||
} else if (oldTree) {
|
||||
if (oldId && !newTreeSiblingIds.includes(oldId)) {
|
||||
// Deep clone oldTree omitting children field
|
||||
const diffTree = this.cloneNode(oldTree)!;
|
||||
|
||||
// newTree doesn't exist, oldTree has either been moved or deleted.
|
||||
if (this.newMapping![oldId]) {
|
||||
diffTree.diff = DiffType.DELETED_MOVE;
|
||||
} else {
|
||||
diffTree.diff = DiffType.DELETED;
|
||||
}
|
||||
|
||||
diffTree.children = this.visitChildren(null, oldTree);
|
||||
diffTrees.push(diffTree);
|
||||
}
|
||||
} else {
|
||||
throw new Error("Both newTree and oldTree are undefined...");
|
||||
}
|
||||
|
||||
return diffTrees;
|
||||
}
|
||||
|
||||
private visitChildren(newTree: Tree | null, oldTree: Tree | null) {
|
||||
// Recursively traverse all children of new and old tree.
|
||||
const diffChildren = [];
|
||||
|
||||
if (!newTree) newTree = {};
|
||||
if (!oldTree) oldTree = {};
|
||||
const numOfChildren = Math.max(newTree.children?.length ?? 0, oldTree.children?.length ?? 0);
|
||||
for (let i = 0; i < numOfChildren; i++) {
|
||||
const newChild = newTree.children ? newTree.children[i] : null;
|
||||
const oldChild = oldTree.children ? oldTree.children[i] : null;
|
||||
|
||||
const childDiffTrees = this.generateDiffTree(
|
||||
newChild, oldChild,
|
||||
newTree.children ?? [], oldTree.children ?? [],
|
||||
);
|
||||
diffChildren.push(...childDiffTrees);
|
||||
}
|
||||
|
||||
return diffChildren;
|
||||
}
|
||||
|
||||
private defaultNodeIdCallback(node: Tree | null): number | null {
|
||||
return node ? node.stableId : null;
|
||||
}
|
||||
|
||||
private defaultModifiedCheck(newNode: Tree | null, oldNode: Tree | null): boolean {
|
||||
if (!newNode && !oldNode) {
|
||||
return false;
|
||||
} else if (newNode && newNode.kind==="entry") {
|
||||
return false;
|
||||
} else if ((newNode && !oldNode) || (!newNode && oldNode)) {
|
||||
return true;
|
||||
}
|
||||
return !newNode.equals(oldNode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
export interface UserOptions {
|
||||
[key: string]: {
|
||||
name: string,
|
||||
enabled: boolean
|
||||
enabled: boolean,
|
||||
tooltip?: string
|
||||
}
|
||||
}
|
||||
@@ -17,5 +17,8 @@ export const ViewerEvents = {
|
||||
HierarchyPinnedChange: "HierarchyPinnedChange",
|
||||
HighlightedChange: "HighlightedChange",
|
||||
HierarchyUserOptionsChange: "HierarchyUserOptionsChange",
|
||||
HierarchyFilterChange: "HierarchyFilterChange"
|
||||
};
|
||||
HierarchyFilterChange: "HierarchyFilterChange",
|
||||
SelectedTreeChange: "SelectedTreeChange",
|
||||
PropertiesUserOptionsChange: "PropertiesUserOptionsChange",
|
||||
PropertiesFilterChange: "PropertiesFilterChange"
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@ import { Component, Input, Inject, ElementRef } from "@angular/core";
|
||||
import { UserOptions } from "viewers/common/user_options";
|
||||
import { PersistentStore } from "common/persistent_store";
|
||||
import { Tree, diffClass, isHighlighted } from "viewers/common/tree_utils";
|
||||
import { nodeStyles } from "viewers/styles/node.styles";
|
||||
import { nodeStyles } from "viewers/components/styles/node.styles";
|
||||
import { ViewerEvents } from "viewers/common/viewer_events";
|
||||
import { TraceType } from "common/trace/trace_type";
|
||||
|
||||
@@ -56,7 +56,7 @@ import { TraceType } from "common/trace/trace_type";
|
||||
[isPinned]="true"
|
||||
[isInPinnedSection]="true"
|
||||
(pinNodeChange)="pinnedItemChange($event)"
|
||||
(click)="onPinnedNodeClick($event, pinnedItem.id)"
|
||||
(click)="onPinnedNodeClick($event, pinnedItem.id, pinnedItem)"
|
||||
></tree-node>
|
||||
</div>
|
||||
</mat-card-header>
|
||||
@@ -76,6 +76,7 @@ import { TraceType } from "common/trace/trace_type";
|
||||
[pinnedItems]="pinnedItems"
|
||||
(highlightedItemChange)="highlightedItemChange($event)"
|
||||
(pinnedItemChange)="pinnedItemChange($event)"
|
||||
(selectedTreeChange)="selectedTreeChange($event)"
|
||||
></tree-view>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
@@ -169,12 +170,13 @@ export class HierarchyComponent {
|
||||
};
|
||||
}
|
||||
|
||||
onPinnedNodeClick(event: MouseEvent, pinnedItemId: string) {
|
||||
onPinnedNodeClick(event: MouseEvent, pinnedItemId: string, pinnedItem: Tree) {
|
||||
event.preventDefault();
|
||||
if (window.getSelection()?.type === "range") {
|
||||
return;
|
||||
}
|
||||
this.highlightedItemChange(`${pinnedItemId}`);
|
||||
this.selectedTreeChange(pinnedItem);
|
||||
}
|
||||
|
||||
updateTree() {
|
||||
@@ -207,6 +209,16 @@ export class HierarchyComponent {
|
||||
this.elementRef.nativeElement.dispatchEvent(event);
|
||||
}
|
||||
|
||||
selectedTreeChange(item: Tree) {
|
||||
const event: CustomEvent = new CustomEvent(
|
||||
ViewerEvents.SelectedTreeChange,
|
||||
{
|
||||
bubbles: true,
|
||||
detail: { selectedItem: item }
|
||||
});
|
||||
this.elementRef.nativeElement.dispatchEvent(event);
|
||||
}
|
||||
|
||||
pinnedItemChange(item: Tree) {
|
||||
const event: CustomEvent = new CustomEvent(
|
||||
ViewerEvents.HierarchyPinnedChange,
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {ComponentFixture, TestBed, ComponentFixtureAutoDetect} from "@angular/core/testing";
|
||||
import { PropertiesComponent } from "./properties.component";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { NO_ERRORS_SCHEMA } from "@angular/core";
|
||||
import { MatInputModule } from "@angular/material/input";
|
||||
import { MatFormFieldModule } from "@angular/material/form-field";
|
||||
import { MatCheckboxModule } from "@angular/material/checkbox";
|
||||
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||
import { PropertyGroupsComponent } from "./property_groups.component";
|
||||
import { TreeComponent } from "./tree.component";
|
||||
|
||||
describe("PropertiesComponent", () => {
|
||||
let fixture: ComponentFixture<PropertiesComponent>;
|
||||
let component: PropertiesComponent;
|
||||
let htmlElement: HTMLElement;
|
||||
|
||||
beforeAll(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{ provide: ComponentFixtureAutoDetect, useValue: true }
|
||||
],
|
||||
declarations: [
|
||||
PropertiesComponent,
|
||||
PropertyGroupsComponent,
|
||||
TreeComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatInputModule,
|
||||
MatFormFieldModule,
|
||||
MatCheckboxModule,
|
||||
BrowserAnimationsModule
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(PropertiesComponent);
|
||||
component = fixture.componentInstance;
|
||||
htmlElement = fixture.nativeElement;
|
||||
component.selectedTree = {};
|
||||
component.userOptions = {
|
||||
showDefaults: {
|
||||
name: "Show defaults",
|
||||
enabled: false
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it("can be created", () => {
|
||||
fixture.detectChanges();
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it("creates title", () => {
|
||||
fixture.detectChanges();
|
||||
const title = htmlElement.querySelector(".properties-title");
|
||||
expect(title).toBeTruthy();
|
||||
});
|
||||
|
||||
it("creates view controls", () => {
|
||||
fixture.detectChanges();
|
||||
const viewControls = htmlElement.querySelector(".view-controls");
|
||||
expect(viewControls).toBeTruthy();
|
||||
});
|
||||
|
||||
it("creates initial tree elements", () => {
|
||||
fixture.detectChanges();
|
||||
const tree = htmlElement.querySelector(".tree-wrapper");
|
||||
expect(tree).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -13,14 +13,172 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Component } from "@angular/core";
|
||||
import { Component, Input, Inject, ElementRef } from "@angular/core";
|
||||
import { UserOptions } from "viewers/common/user_options";
|
||||
import { ViewerEvents } from "viewers/common/viewer_events";
|
||||
import { PropertiesTree, TreeSummary, Terminal } from "viewers/common/tree_utils";
|
||||
import { Layer } from "common/trace/flickerlib/common";
|
||||
|
||||
@Component({
|
||||
selector: "properties-view",
|
||||
template: `
|
||||
<mat-card-title class="trace-view-subtitle">Properties</mat-card-title>
|
||||
<mat-card-header class="view-header">
|
||||
<mat-card-title class="title-filter">
|
||||
<span class="properties-title">Properties</span>
|
||||
<mat-form-field class="filter-field">
|
||||
<mat-label>Filter...</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[(ngModel)]="filterString"
|
||||
(ngModelChange)="filterTree()"
|
||||
name="filter"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</mat-card-title>
|
||||
<div class="view-controls">
|
||||
<mat-checkbox
|
||||
*ngFor="let option of objectKeys(userOptions)"
|
||||
class="trace-box"
|
||||
[(ngModel)]="userOptions[option].enabled"
|
||||
(ngModelChange)="updateTree()"
|
||||
[matTooltip]="userOptions[option].tooltip ?? ''"
|
||||
>{{userOptions[option].name}}</mat-checkbox>
|
||||
</div>
|
||||
<div *ngIf="objectKeys(selectedLayer).length > 0 && propertyGroups" class="element-summary">
|
||||
<property-groups
|
||||
[item]="selectedLayer"
|
||||
[summary]="summary"
|
||||
></property-groups>
|
||||
</div>
|
||||
</mat-card-header>
|
||||
<mat-card-content class="properties-content" [style]="maxPropertiesHeight()">
|
||||
<div class="tree-wrapper">
|
||||
<tree-view
|
||||
class="tree-view"
|
||||
[item]="selectedTree"
|
||||
[showNode]="showNode"
|
||||
[isLeaf]="isLeaf"
|
||||
*ngIf="objectKeys(selectedTree).length > 0"
|
||||
[isPropertiesTree]="true"
|
||||
[isAlwaysCollapsed]="true"
|
||||
></tree-view>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
`,
|
||||
styles: [
|
||||
`
|
||||
.view-header {
|
||||
display: block;
|
||||
width: 100%;
|
||||
min-height: 3.75rem;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid lightgrey;
|
||||
}
|
||||
|
||||
.title-filter {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.properties-title {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.filter-field {
|
||||
font-size: 16px;
|
||||
transform: scale(0.7);
|
||||
right: 0px;
|
||||
position: absolute
|
||||
}
|
||||
|
||||
.view-controls {
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
margin-left: 5px
|
||||
}
|
||||
|
||||
.properties-content{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
overflow-x:hidden
|
||||
}
|
||||
|
||||
.element-summary {
|
||||
padding: 1rem;
|
||||
border-bottom: thin solid rgba(0,0,0,.12);
|
||||
}
|
||||
|
||||
.element-summary .key {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.element-summary .value {
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
}
|
||||
|
||||
.tree-view {
|
||||
white-space: pre-line;
|
||||
flex: 1 0 0;
|
||||
height: 100%;
|
||||
overflow-y: auto
|
||||
}
|
||||
`,
|
||||
],
|
||||
})
|
||||
|
||||
export class PropertiesComponent {
|
||||
objectKeys = Object.keys;
|
||||
filterString = "";
|
||||
|
||||
@Input() userOptions: UserOptions = {};
|
||||
@Input() selectedTree: PropertiesTree = {};
|
||||
@Input() selectedLayer: Layer = {};
|
||||
@Input() propertyGroups = false;
|
||||
@Input() summary?: TreeSummary = [];
|
||||
|
||||
constructor(
|
||||
@Inject(ElementRef) private elementRef: ElementRef,
|
||||
) {}
|
||||
|
||||
maxPropertiesHeight() {
|
||||
const headerHeight = this.elementRef.nativeElement.querySelector(".view-header").clientHeight;
|
||||
return {
|
||||
height: `${800 - headerHeight}px`
|
||||
};
|
||||
}
|
||||
|
||||
filterTree() {
|
||||
const event: CustomEvent = new CustomEvent(
|
||||
ViewerEvents.PropertiesFilterChange,
|
||||
{
|
||||
bubbles: true,
|
||||
detail: { filterString: this.filterString }
|
||||
});
|
||||
this.elementRef.nativeElement.dispatchEvent(event);
|
||||
}
|
||||
|
||||
updateTree() {
|
||||
const event: CustomEvent = new CustomEvent(
|
||||
ViewerEvents.PropertiesUserOptionsChange,
|
||||
{
|
||||
bubbles: true,
|
||||
detail: { userOptions: this.userOptions }
|
||||
});
|
||||
this.elementRef.nativeElement.dispatchEvent(event);
|
||||
}
|
||||
|
||||
showNode(item: any) {
|
||||
return !(item instanceof Terminal)
|
||||
&& !(item.name instanceof Terminal)
|
||||
&& !(item.propertyKey instanceof Terminal);
|
||||
}
|
||||
|
||||
isLeaf(item: any) {
|
||||
return !item.children || item.children.length === 0
|
||||
|| item.children.filter((c: any) => !(c instanceof Terminal)).length === 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {ComponentFixture, TestBed} from "@angular/core/testing";
|
||||
import { PropertyGroupsComponent } from "./property_groups.component";
|
||||
import { ComponentFixtureAutoDetect } from "@angular/core/testing";
|
||||
import { TransformMatrixComponent } from "./transform_matrix.component";
|
||||
|
||||
describe("PropertyGroupsComponent", () => {
|
||||
let fixture: ComponentFixture<PropertyGroupsComponent>;
|
||||
let component: PropertyGroupsComponent;
|
||||
let htmlElement: HTMLElement;
|
||||
|
||||
beforeAll(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{ provide: ComponentFixtureAutoDetect, useValue: true }
|
||||
],
|
||||
declarations: [
|
||||
PropertyGroupsComponent,
|
||||
TransformMatrixComponent
|
||||
],
|
||||
schemas: []
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(PropertyGroupsComponent);
|
||||
component = fixture.componentInstance;
|
||||
htmlElement = fixture.nativeElement;
|
||||
});
|
||||
|
||||
it("can be created", () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,305 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Component, Input } from "@angular/core";
|
||||
import { TreeSummary } from "viewers/common/tree_utils";
|
||||
import { Layer } from "common/trace/flickerlib/common";
|
||||
|
||||
@Component({
|
||||
selector: "property-groups",
|
||||
template: `
|
||||
<div>
|
||||
<div class="group">
|
||||
<span class="group-header">Geometry</span>
|
||||
<div class="left-column">
|
||||
<div class="column-header">Calculated</div>
|
||||
<span class="key">Transform:</span>
|
||||
<transform-matrix [transform]="item.transform" [formatFloat]="formatFloat"></transform-matrix>
|
||||
<div></div>
|
||||
<span
|
||||
class="key"
|
||||
matTooltip="Raw value read from proto.bounds. This is the buffer size or
|
||||
requested crop cropped by parent bounds."
|
||||
>Crop:</span>
|
||||
<span class="value">{{ item.bounds }}</span>
|
||||
<div></div>
|
||||
<span
|
||||
class="key"
|
||||
matTooltip="Raw value read from proto.screenBounds. This is the calculated crop
|
||||
transformed."
|
||||
>Final Bounds:</span>
|
||||
<span class="value">{{ item.screenBounds }}</span>
|
||||
</div>
|
||||
<div class="right-column">
|
||||
<div class="column-header">Requested</div>
|
||||
<span class="key">Transform:</span>
|
||||
<transform-matrix [transform]="item.requestedTransform" [formatFloat]="formatFloat"></transform-matrix>
|
||||
<div></div>
|
||||
<span class="key">Crop:</span>
|
||||
<span class="value">{{ item.crop ? item.crop : "[empty]" }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="group">
|
||||
<span class="group-header">
|
||||
<span class="group-heading">Buffer</span>
|
||||
</span>
|
||||
<div *ngIf="item.isBufferLayer" class="left-column">
|
||||
<div></div>
|
||||
<span class="key">Size:</span>
|
||||
<span class="value">{{ item.activeBuffer }}</span>
|
||||
<div></div>
|
||||
<span class="key">Frame Number:</span>
|
||||
<span class="value">{{ item.currFrame }}</span>
|
||||
<div></div>
|
||||
<span
|
||||
class="key"
|
||||
matTooltip="Rotates or flips the buffer in place. Used with display transform
|
||||
hint to cancel out any buffer transformation when sending to
|
||||
HWC."
|
||||
>Transform:</span>
|
||||
<span class="value">{{ item.bufferTransform }}</span>
|
||||
</div>
|
||||
<div *ngIf="item.isBufferLayer" class="right-column">
|
||||
<div></div>
|
||||
<span
|
||||
class="key"
|
||||
matTooltip="Scales buffer to the frame by overriding the requested transform
|
||||
for this layer."
|
||||
>Destination Frame:</span>
|
||||
<span class="value">{{ getDestinationFrame() }}</span>
|
||||
<div></div>
|
||||
<span
|
||||
*ngIf="hasIgnoreDestinationFrame()"
|
||||
class="value"
|
||||
>Destination Frame ignored because layer has eIgnoreDestinationFrame
|
||||
flag set.
|
||||
</span>
|
||||
</div>
|
||||
<div *ngIf="item.isContainerLayer" class="left-column">
|
||||
<span class="key"></span>
|
||||
<span class="value">Container layer</span>
|
||||
</div>
|
||||
<div *ngIf="item.isEffectLayer" class="left-column">
|
||||
<span class="key"></span>
|
||||
<span class="value">Effect layer</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="group">
|
||||
<span class="group-header">
|
||||
<span class="group-heading">Hierarchy</span>
|
||||
</span>
|
||||
<div class="left-column">
|
||||
<div></div>
|
||||
<span class="key">z-order:</span>
|
||||
<span class="value">{{ item.z }}</span>
|
||||
<div></div>
|
||||
<span
|
||||
class="key"
|
||||
matTooltip="Layer is z-ordered relative to its relative parents but its bounds
|
||||
and other properties are inherited from its parents."
|
||||
>relative parent:</span>
|
||||
<span class="value">
|
||||
{{ item.zOrderRelativeOfId == -1 ? "none" : item.zOrderRelativeOfId }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="group">
|
||||
<span class="group-header">
|
||||
<span class="group-heading">Effects</span>
|
||||
</span>
|
||||
<div class="left-column">
|
||||
<div class="column-header">Calculated</div>
|
||||
<span class="key">Color:</span>
|
||||
<span class="value">{{ item.color }}</span>
|
||||
<div></div>
|
||||
<span class="key">Shadow:</span>
|
||||
<span class="value">{{ item.shadowRadius }} px</span>
|
||||
<div></div>
|
||||
<span class="key">Corner Radius:</span>
|
||||
<span class="value">{{ formatFloat(item.cornerRadius) }} px</span>
|
||||
<div></div>
|
||||
<span
|
||||
class="key"
|
||||
matTooltip="Crop used to define the bounds of the corner radii. If the bounds
|
||||
are greater than the layer bounds then the rounded corner will not
|
||||
be visible."
|
||||
>Corner Radius Crop:</span>
|
||||
<span class="value">{{ item.cornerRadiusCrop }}</span>
|
||||
<div></div>
|
||||
<span class="key">Blur:</span>
|
||||
<span class="value">
|
||||
{{
|
||||
item.proto?.backgroundBlurRadius
|
||||
? item.proto?.backgroundBlurRadius
|
||||
: 0
|
||||
}} px
|
||||
</span>
|
||||
</div>
|
||||
<div class="right-column">
|
||||
<div class="column-header">Requested</div>
|
||||
<span class="key">Color:</span>
|
||||
<span class="value">{{ item.requestedColor }}</span>
|
||||
<div></div>
|
||||
<span class="key">Shadow:</span>
|
||||
<span class="value">
|
||||
{{
|
||||
item.proto?.requestedShadowRadius
|
||||
? item.proto?.requestedShadowRadius
|
||||
: 0
|
||||
}} px
|
||||
</span>
|
||||
<div></div>
|
||||
<span class="key">Corner Radius:</span>
|
||||
<span class="value">
|
||||
{{
|
||||
item.proto?.requestedCornerRadius
|
||||
? formatFloat(item.proto?.requestedCornerRadius)
|
||||
: 0
|
||||
}} px
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="group">
|
||||
<span class="group-header">
|
||||
<span class="group-heading">Input</span>
|
||||
</span>
|
||||
<div *ngIf="item.proto?.inputWindowInfo" class="left-column">
|
||||
<span class="key">To Display Transform:</span>
|
||||
<transform-matrix [transform]="item.inputTransform" [formatFloat]="formatFloat"></transform-matrix>
|
||||
<div></div>
|
||||
<span class="key">Touchable Region:</span>
|
||||
<span class="value">{{ item.inputRegion }}</span>
|
||||
</div>
|
||||
<div *ngIf="item.proto?.inputWindowInfo" class="right-column">
|
||||
<span class="key">Config:</span>
|
||||
<span class="value"></span>
|
||||
<div></div>
|
||||
<span class="key">Focusable:</span>
|
||||
<span class="value">{{ item.proto?.inputWindowInfo.focusable }}</span>
|
||||
<div></div>
|
||||
<span class="key">Crop touch region with layer:</span>
|
||||
<span class="value">
|
||||
{{
|
||||
item.proto?.inputWindowInfo.cropLayerId <= 0
|
||||
? "none"
|
||||
: item.proto?.inputWindowInfo.cropLayerId
|
||||
}}
|
||||
</span>
|
||||
<div></div>
|
||||
<span class="key">Replace touch region with crop:</span>
|
||||
<span class="value">
|
||||
{{
|
||||
item.proto?.inputWindowInfo.replaceTouchableRegionWithCrop
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<div *ngIf="!item.proto?.inputWindowInfo" class="left-column">
|
||||
<span class="key"></span>
|
||||
<span class="value">No input channel set</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="group">
|
||||
<span class="group-header">
|
||||
<span class="group-heading">Visibility</span>
|
||||
</span>
|
||||
<div class="left-column">
|
||||
<span class="key">Flags:</span>
|
||||
<span class="value">{{ item.flags }}</span>
|
||||
<div></div>
|
||||
<div *ngIf="summary">
|
||||
<div *ngFor="let reason of summary">
|
||||
<span class="key">{{ reason.key }}:</span>
|
||||
<span class="value">{{ reason.value }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
styles: [
|
||||
`
|
||||
.group {
|
||||
padding: 0.5rem;
|
||||
border-bottom: thin solid rgba(0, 0, 0, 0.12);
|
||||
flex-direction: row;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.group .key {
|
||||
font-weight: 500;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.group .value {
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
}
|
||||
|
||||
.group-header {
|
||||
justify-content: center;
|
||||
padding: 0px 5px;
|
||||
width: 80px;
|
||||
display: inline-block;
|
||||
font-size: bigger;
|
||||
color: grey;
|
||||
}
|
||||
|
||||
.left-column {
|
||||
width: 320px;
|
||||
max-width: 100%;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
overflow: auto;
|
||||
border-right: 5px solid rgba(#000, 0.12);
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.right-column {
|
||||
width: 320px;
|
||||
max-width: 100%;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
overflow: auto;
|
||||
border: 1px solid rgba(#000, 0.12);
|
||||
}
|
||||
|
||||
.column-header {
|
||||
font-weight: lighter;
|
||||
font-size: smaller;
|
||||
}
|
||||
`
|
||||
],
|
||||
})
|
||||
|
||||
export class PropertyGroupsComponent {
|
||||
@Input() item!: Layer;
|
||||
@Input() summary?: TreeSummary | null = null;
|
||||
|
||||
getDestinationFrame() {
|
||||
const frame = this.item.proto?.destinationFrame;
|
||||
if (frame) {
|
||||
return ` left: ${frame.left}, top: ${frame.top}, right: ${frame.right}, bottom: ${frame.bottom}`;
|
||||
}
|
||||
else return "";
|
||||
}
|
||||
|
||||
hasIgnoreDestinationFrame() {
|
||||
return (this.item.flags & 0x400) === 0x400;
|
||||
}
|
||||
|
||||
formatFloat(num: number) {
|
||||
return Math.round(num * 100) / 100;
|
||||
}
|
||||
}
|
||||
@@ -56,9 +56,9 @@ export class CanvasGraphics {
|
||||
alpha: true
|
||||
});
|
||||
let labelRenderer: CSS2DRenderer;
|
||||
if (document.querySelector("#labels-canvas")) {
|
||||
if (document.querySelector(".labels-canvas")) {
|
||||
labelRenderer = new CSS2DRenderer({
|
||||
element: document.querySelector("#labels-canvas")! as HTMLElement
|
||||
element: document.querySelector(".labels-canvas")! as HTMLElement
|
||||
});
|
||||
} else {
|
||||
labelRenderer = new CSS2DRenderer();
|
||||
@@ -66,7 +66,7 @@ export class CanvasGraphics {
|
||||
labelRenderer.domElement.style.top = "0px";
|
||||
labelRenderer.domElement.style.width = "100%";
|
||||
labelRenderer.domElement.style.height = "40rem";
|
||||
labelRenderer.domElement.id = "labels-canvas";
|
||||
labelRenderer.domElement.className = "labels-canvas";
|
||||
labelRenderer.domElement.style.pointerEvents = "none";
|
||||
document.querySelector(".canvas-container")?.appendChild(labelRenderer.domElement);
|
||||
}
|
||||
@@ -141,6 +141,7 @@ export class CanvasGraphics {
|
||||
lowestY: number
|
||||
) {
|
||||
this.targetObjects = [];
|
||||
this.clearLabelElements();
|
||||
this.rects.forEach(rect => {
|
||||
const mustNotDrawInVisibleView = this.visibleView && !rect.isVisible;
|
||||
const mustNotDrawInXrayViewWithoutVirtualDisplays = !this.visibleView && !this.showVirtualDisplays && rect.isDisplay && rect.isVirtual;
|
||||
@@ -175,17 +176,18 @@ export class CanvasGraphics {
|
||||
scene.add(edgeSegments);
|
||||
}
|
||||
|
||||
// label circular marker
|
||||
const circle = this.setCircleMaterial(planeRect, rect);
|
||||
scene.add(circle);
|
||||
|
||||
// if not a display rect, should be clickable
|
||||
if (!rect.isDisplay) this.targetObjects.push(planeRect);
|
||||
|
||||
// label line
|
||||
const [line, rectLabel] = this.createLabel(rect, circle, lowestY, rectCounter);
|
||||
scene.add(line);
|
||||
scene.add(rectLabel);
|
||||
// labelling elements
|
||||
if (rect.label.length > 0) {
|
||||
const circle = this.setCircleMaterial(planeRect, rect);
|
||||
scene.add(circle);
|
||||
const [line, rectLabel] = this.createLabel(rect, circle, lowestY, rectCounter);
|
||||
scene.add(line);
|
||||
scene.add(rectLabel);
|
||||
}
|
||||
|
||||
rectCounter++;
|
||||
});
|
||||
}
|
||||
@@ -261,10 +263,8 @@ export class CanvasGraphics {
|
||||
linePoints.push(endPos);
|
||||
|
||||
//add rectangle label
|
||||
document.querySelector(`.label-${rectCounter}`)?.remove();
|
||||
const rectLabelDiv: HTMLElement = document.createElement("div");
|
||||
this.labelElements.push(rectLabelDiv);
|
||||
rectLabelDiv.className = `label-${rectCounter}`;
|
||||
rectLabelDiv.textContent = labelText;
|
||||
rectLabelDiv.style.fontSize = "10px";
|
||||
if (isGrey) {
|
||||
|
||||
@@ -62,15 +62,15 @@ describe("RectsComponent", () => {
|
||||
|
||||
it("check that layer separation slider causes view to change", () => {
|
||||
const slider = htmlElement.querySelector(".spacing-slider");
|
||||
spyOn(component.rectsComponent.canvasGraphics, "updateLayerSeparation");
|
||||
spyOn(component.rectsComponent, "updateLayerSeparation");
|
||||
slider?.dispatchEvent(new MouseEvent("mousedown"));
|
||||
fixture.detectChanges();
|
||||
expect(component.rectsComponent.canvasGraphics.updateLayerSeparation).toHaveBeenCalled();
|
||||
expect(component.rectsComponent.updateLayerSeparation).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("check that rects canvas is rendered", () => {
|
||||
fixture.detectChanges();
|
||||
const rectsCanvas = htmlElement.querySelector("#rects-canvas");
|
||||
const rectsCanvas = htmlElement.querySelector(".rects-canvas");
|
||||
expect(rectsCanvas).toBeTruthy();
|
||||
});
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ import { ViewerEvents } from "viewers/common/viewer_events";
|
||||
</mat-card-header>
|
||||
<mat-card-content class="rects-content">
|
||||
<div class="canvas-container">
|
||||
<canvas id="rects-canvas" (click)="onRectClick($event)">
|
||||
<canvas class="rects-canvas" (click)="onRectClick($event)">
|
||||
</canvas>
|
||||
</div>
|
||||
<div class="tabs" *ngIf="displayIds.length > 1">
|
||||
@@ -92,8 +92,8 @@ import { ViewerEvents } from "viewers/common/viewer_events";
|
||||
"@import 'https://fonts.googleapis.com/icon?family=Material+Icons';",
|
||||
".rects-content {position: relative}",
|
||||
".canvas-container {height: 40rem; width: 100%; position: relative}",
|
||||
"#rects-canvas {height: 40rem; width: 100%; cursor: pointer; position: absolute; top: 0px}",
|
||||
"#labels-canvas {height: 40rem; width: 100%; position: absolute; top: 0px}",
|
||||
".rects-canvas {height: 40rem; width: 100%; cursor: pointer; position: absolute; top: 0px}",
|
||||
".labels-canvas {height: 40rem; width: 100%; position: absolute; top: 0px}",
|
||||
".view-controls {display: inline-block; position: relative; min-height: 4rem; width: 100%;}",
|
||||
".slider-view-controls {display: inline-block; position: relative; height: 3rem; width: 100%;}",
|
||||
".slider {display: inline-block}",
|
||||
@@ -131,10 +131,15 @@ export class RectsComponent implements OnInit, OnChanges, OnDestroy {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
window.addEventListener('resize', () => this.refreshCanvas());
|
||||
window.addEventListener("resize", () => this.refreshCanvas());
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes["displayIds"]) {
|
||||
if (!this.displayIds.includes(this.currentDisplayId)) {
|
||||
this.currentDisplayId = this.displayIds[0];
|
||||
}
|
||||
}
|
||||
if (changes["highlightedItems"]) {
|
||||
this.canvasGraphics.updateHighlightedItems(this.highlightedItems);
|
||||
}
|
||||
@@ -157,7 +162,7 @@ export class RectsComponent implements OnInit, OnChanges, OnDestroy {
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
window.removeEventListener('resize', () => this.refreshCanvas());
|
||||
window.removeEventListener("resize", () => this.refreshCanvas());
|
||||
}
|
||||
|
||||
onRectClick(event:MouseEvent) {
|
||||
@@ -193,7 +198,7 @@ export class RectsComponent implements OnInit, OnChanges, OnDestroy {
|
||||
}
|
||||
|
||||
drawRects() {
|
||||
const canvas = document.getElementById("rects-canvas") as HTMLCanvasElement;
|
||||
const canvas = this.elementRef.nativeElement.querySelector(".rects-canvas") as HTMLCanvasElement;
|
||||
this.canvasGraphics.initialise(canvas);
|
||||
this.refreshCanvas();
|
||||
}
|
||||
|
||||
@@ -17,23 +17,18 @@ export const nodeStyles = `
|
||||
.node {position: relative;display: inline-block;padding: 2px; height: 100%; width: 100%;}
|
||||
.node.clickable {cursor: pointer;}
|
||||
.node:not(.selected).added,
|
||||
.node:not(.selected).addedMove,
|
||||
.expand-tree-btn.added,
|
||||
.expand-tree-btn.addedMove {
|
||||
.node:not(.selected).addedMove {
|
||||
background: #03ff35;
|
||||
}
|
||||
|
||||
.node:not(.selected).deleted,
|
||||
.node:not(.selected).deletedMove,
|
||||
.expand-tree-btn.deleted,
|
||||
.expand-tree-btn.deletedMove {
|
||||
.node:not(.selected).deletedMove {
|
||||
background: #ff6b6b;
|
||||
}
|
||||
|
||||
.node:hover:not(.selected) {background: #f1f1f1;}
|
||||
|
||||
.node:not(.selected).modified,
|
||||
.expand-tree-btn.modified {
|
||||
.node:not(.selected).modified {
|
||||
background: cyan;
|
||||
}
|
||||
|
||||
@@ -50,7 +45,7 @@ export const nodeStyles = `
|
||||
.selected {background-color: #365179;color: white;}
|
||||
`;
|
||||
|
||||
export const treeNodeStyles = `
|
||||
export const treeNodeDataViewStyles = `
|
||||
.node.shaded:not(:hover):not(.selected):not(.added):not(.addedMove):not(.deleted):not(.deletedMove):not(.modified) {background: #f8f9fa}
|
||||
.node.selected + .children {border-left: 1px solid rgb(200, 200, 200);}
|
||||
.node.child-hover + .children {border-left: 1px solid #b4b4b4;}
|
||||
@@ -65,4 +60,24 @@ export const nodeInnerItemStyles = `
|
||||
.description {align-items: center; flex: 1 1 auto; vertical-align: middle; word-break: break-all;}
|
||||
.leaf-node-icon-wrapper{padding-left: 6px; padding-right: 6px; min-height: 24px; width: 24px; position:relative; align-content: center; vertical-align: middle;}
|
||||
.icon-button { background: none;border: none;display: inline-block;vertical-align: middle;}
|
||||
|
||||
.expand-tree-btn {
|
||||
float: right;
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
.expand-tree-btn.modified {
|
||||
background: cyan;
|
||||
}
|
||||
|
||||
.expand-tree-btn.deleted,
|
||||
.expand-tree-btn.deletedMove {
|
||||
background: #ff6b6b;
|
||||
}
|
||||
|
||||
.expand-tree-btn.added,
|
||||
.expand-tree-btn.addedMove {
|
||||
background: #03ff35;
|
||||
}
|
||||
`;
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export const treeElementStyles = `
|
||||
export const treeNodeDataViewStyles = `
|
||||
.kind {font-weight: bold}
|
||||
|
||||
span {overflow-wrap: break-word; flex: 1 1 auto; width: 0; word-break: break-all}
|
||||
@@ -49,3 +49,24 @@ export const treeElementStyles = `
|
||||
color: black;
|
||||
}
|
||||
`;
|
||||
|
||||
export const treeNodePropertiesDataViewStyles = `
|
||||
.key {
|
||||
color: #4b4b4b;
|
||||
}
|
||||
.value {
|
||||
color: #8A2BE2;
|
||||
}
|
||||
.value.null {
|
||||
color: #e1e1e1;
|
||||
}
|
||||
.value.number {
|
||||
color: #4c75fd;
|
||||
}
|
||||
.value.true {
|
||||
color: #2ECC40;
|
||||
}
|
||||
.value.false {
|
||||
color: #FF4136;
|
||||
}
|
||||
`;
|
||||
@@ -14,13 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {ComponentFixture, TestBed} from "@angular/core/testing";
|
||||
import { TreeElementComponent } from "./tree_element.component";
|
||||
import { TransformMatrixComponent } from "./transform_matrix.component";
|
||||
import { ComponentFixtureAutoDetect } from "@angular/core/testing";
|
||||
import { NO_ERRORS_SCHEMA } from "@angular/core";
|
||||
|
||||
describe("TreeElementComponent", () => {
|
||||
let fixture: ComponentFixture<TreeElementComponent>;
|
||||
let component: TreeElementComponent;
|
||||
describe("TransformMatrixComponent", () => {
|
||||
let fixture: ComponentFixture<TransformMatrixComponent>;
|
||||
let component: TransformMatrixComponent;
|
||||
let htmlElement: HTMLElement;
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -29,14 +29,14 @@ describe("TreeElementComponent", () => {
|
||||
{ provide: ComponentFixtureAutoDetect, useValue: true }
|
||||
],
|
||||
declarations: [
|
||||
TreeElementComponent
|
||||
TransformMatrixComponent
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TreeElementComponent);
|
||||
fixture = TestBed.createComponent(TransformMatrixComponent);
|
||||
component = fixture.componentInstance;
|
||||
htmlElement = fixture.nativeElement;
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Component, Input } from "@angular/core";
|
||||
import { Transform } from "common/trace/flickerlib/common";
|
||||
|
||||
@Component({
|
||||
selector: "transform-matrix",
|
||||
template: `
|
||||
<div class="matrix" *ngIf="transform" [matTooltip]="transform.getTypeAsString()">
|
||||
<div class="cell">{{ formatFloat(transform.matrix.dsdx) }}</div>
|
||||
<div class="cell">{{ formatFloat(transform.matrix.dsdy) }}</div>
|
||||
<div class="cell" matTooltip="Translate x">
|
||||
{{ formatFloat(transform.matrix.tx) }}
|
||||
</div>
|
||||
|
||||
<div class="cell">{{ formatFloat(transform.matrix.dtdx) }}</div>
|
||||
<div class="cell">{{ formatFloat(transform.matrix.dtdy) }}</div>
|
||||
<div class="cell" matTooltip="Translate y">
|
||||
{{ formatFloat(transform.matrix.ty) }}
|
||||
</div>
|
||||
|
||||
<div class="cell">0</div>
|
||||
<div class="cell">0</div>
|
||||
<div class="cell">1</div>
|
||||
</div>
|
||||
`,
|
||||
styles: [
|
||||
`
|
||||
.matrix {
|
||||
display: grid;
|
||||
grid-gap: 1px;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.cell {
|
||||
padding-left: 10px;
|
||||
background-color: #F8F9FA;
|
||||
}
|
||||
`
|
||||
],
|
||||
})
|
||||
|
||||
export class TransformMatrixComponent {
|
||||
@Input() transform!: Transform;
|
||||
@Input() formatFloat!: (num: number) => number;
|
||||
}
|
||||
@@ -13,14 +13,14 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {ComponentFixture, TestBed} from "@angular/core/testing";
|
||||
import { ComponentFixture, TestBed, ComponentFixtureAutoDetect } from "@angular/core/testing";
|
||||
import { TreeComponent } from "./tree.component";
|
||||
import { ComponentFixtureAutoDetect } from "@angular/core/testing";
|
||||
import { NO_ERRORS_SCHEMA } from "@angular/core";
|
||||
import { Component, ViewChild, NO_ERRORS_SCHEMA } from "@angular/core";
|
||||
import { PersistentStore } from "common/persistent_store";
|
||||
|
||||
describe("TreeComponent", () => {
|
||||
let fixture: ComponentFixture<TreeComponent>;
|
||||
let component: TreeComponent;
|
||||
let fixture: ComponentFixture<TestHostComponent>;
|
||||
let component: TestHostComponent;
|
||||
let htmlElement: HTMLElement;
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -29,28 +29,16 @@ describe("TreeComponent", () => {
|
||||
{ provide: ComponentFixtureAutoDetect, useValue: true }
|
||||
],
|
||||
declarations: [
|
||||
TreeComponent
|
||||
TreeComponent, TestHostComponent
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TreeComponent);
|
||||
fixture = TestBed.createComponent(TestHostComponent);
|
||||
component = fixture.componentInstance;
|
||||
htmlElement = fixture.nativeElement;
|
||||
component.isFlattened = true;
|
||||
component.item = {
|
||||
simplifyNames: false,
|
||||
kind: "entry",
|
||||
name: "BaseLayerTraceEntry",
|
||||
shortName: "BLTE",
|
||||
chips: [],
|
||||
children: [{kind: "3", id: "3", name: "Child1"}]
|
||||
};
|
||||
component.diffClass = jasmine.createSpy().and.returnValue("none");
|
||||
component.isHighlighted = jasmine.createSpy().and.returnValue(false);
|
||||
component.hasChildren = jasmine.createSpy().and.returnValue(true);
|
||||
});
|
||||
|
||||
it("can be created", () => {
|
||||
@@ -58,9 +46,35 @@ describe("TreeComponent", () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it("creates node element", () => {
|
||||
fixture.detectChanges();
|
||||
const nodeElement = htmlElement.querySelector(".node");
|
||||
expect(nodeElement).toBeTruthy();
|
||||
});
|
||||
@Component({
|
||||
selector: "host-component",
|
||||
template: `
|
||||
<tree-view
|
||||
[item]="item"
|
||||
[store]="store"
|
||||
[isFlattened]="isFlattened"
|
||||
[diffClass]="diffClass"
|
||||
[isHighlighted]="isHighlighted"
|
||||
[hasChildren]="hasChildren"
|
||||
></tree-view>
|
||||
`
|
||||
})
|
||||
class TestHostComponent {
|
||||
isFlattened = true;
|
||||
item = {
|
||||
simplifyNames: false,
|
||||
kind: "entry",
|
||||
name: "BaseLayerTraceEntry",
|
||||
shortName: "BLTE",
|
||||
chips: [],
|
||||
children: [{kind: "3", id: "3", name: "Child1"}]
|
||||
};
|
||||
store = new PersistentStore();
|
||||
diffClass = jasmine.createSpy().and.returnValue("none");
|
||||
isHighlighted = jasmine.createSpy().and.returnValue(false);
|
||||
hasChildren = jasmine.createSpy().and.returnValue(true);
|
||||
|
||||
@ViewChild(TreeComponent)
|
||||
public treeComponent!: TreeComponent;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -15,9 +15,10 @@
|
||||
*/
|
||||
import { Component, Inject, Input, Output, ElementRef, EventEmitter } from "@angular/core";
|
||||
import { PersistentStore } from "common/persistent_store";
|
||||
import { nodeStyles, treeNodeStyles } from "viewers/styles/node.styles";
|
||||
import { Tree, diffClass, isHighlighted } from "viewers/common/tree_utils";
|
||||
import { nodeStyles, treeNodeDataViewStyles } from "viewers/components/styles/node.styles";
|
||||
import { Tree, diffClass, isHighlighted, PropertiesTree, Terminal } from "viewers/common/tree_utils";
|
||||
import { TraceType } from "common/trace/trace_type";
|
||||
import { TreeNodePropertiesDataViewComponent } from "./tree_node_properties_data_view.component";
|
||||
|
||||
@Component({
|
||||
selector: "tree-view",
|
||||
@@ -25,41 +26,48 @@ import { TraceType } from "common/trace/trace_type";
|
||||
<div class="tree-view">
|
||||
<tree-node
|
||||
class="node"
|
||||
[class.leaf]="isLeaf()"
|
||||
*ngIf="showNode(item)"
|
||||
[class.leaf]="isLeaf(this.item)"
|
||||
[class.selected]="isHighlighted(item, highlightedItems)"
|
||||
[class.clickable]="isClickable()"
|
||||
[class.shaded]="isShaded"
|
||||
[class.hover]="nodeHover"
|
||||
[class.childHover]="childHover"
|
||||
[isAlwaysCollapsed]="isAlwaysCollapsed"
|
||||
[class]="diffClass(item)"
|
||||
[style]="nodeOffsetStyle()"
|
||||
[item]="item"
|
||||
[flattened]="isFlattened"
|
||||
[isLeaf]="isLeaf()"
|
||||
[isCollapsed]="isCollapsed()"
|
||||
[isLeaf]="isLeaf(this.item)"
|
||||
[isCollapsed]="isAlwaysCollapsed ?? isCollapsed()"
|
||||
[isPropertiesTreeNode]="isPropertiesTree"
|
||||
[hasChildren]="hasChildren()"
|
||||
[isPinned]="isPinned()"
|
||||
(toggleTreeChange)="toggleTree()"
|
||||
(click)="onNodeClick($event)"
|
||||
(expandTreeChange)="expandTree()"
|
||||
(pinNodeChange)="sendNewPinnedItemToHierarchy($event)"
|
||||
(pinNodeChange)="propagateNewPinnedItem($event)"
|
||||
></tree-node>
|
||||
|
||||
<div class="children" *ngIf="hasChildren()" [hidden]="isCollapsed()" [style]="childrenIndentation()">
|
||||
<div class="children" *ngIf="hasChildren()" [hidden]="!isCollapsed()" [style]="childrenIndentation()">
|
||||
<ng-container *ngFor="let child of children()">
|
||||
<tree-view
|
||||
class="childrenTree"
|
||||
[item]="child"
|
||||
[store]="store"
|
||||
[showNode]="showNode"
|
||||
[isLeaf]="isLeaf"
|
||||
[dependencies]="dependencies"
|
||||
[isFlattened]="isFlattened"
|
||||
[isPropertiesTree]="isPropertiesTree"
|
||||
[isShaded]="!isShaded"
|
||||
[useGlobalCollapsedState]="useGlobalCollapsedState"
|
||||
[initialDepth]="initialDepth + 1"
|
||||
[highlightedItems]="highlightedItems"
|
||||
[pinnedItems]="pinnedItems"
|
||||
(highlightedItemChange)="sendNewHighlightedItemToHierarchy($event)"
|
||||
(pinnedItemChange)="sendNewPinnedItemToHierarchy($event)"
|
||||
(highlightedItemChange)="propagateNewHighlightedItem($event)"
|
||||
(pinnedItemChange)="propagateNewPinnedItem($event)"
|
||||
(selectedTreeChange)="propagateNewSelectedTree($event)"
|
||||
[itemsClickable]="itemsClickable"
|
||||
(hoverStart)="childHover = true"
|
||||
(hoverEnd)="childHover = false"
|
||||
@@ -68,14 +76,14 @@ import { TraceType } from "common/trace/trace_type";
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
styles: [nodeStyles, treeNodeStyles]
|
||||
styles: [nodeStyles, treeNodeDataViewStyles]
|
||||
})
|
||||
|
||||
export class TreeComponent {
|
||||
diffClass = diffClass;
|
||||
isHighlighted = isHighlighted;
|
||||
|
||||
@Input() item!: Tree;
|
||||
@Input() item!: Tree | PropertiesTree | Terminal;
|
||||
@Input() dependencies: Array<TraceType> = [];
|
||||
@Input() store!: PersistentStore;
|
||||
@Input() isFlattened? = false;
|
||||
@@ -85,8 +93,13 @@ export class TreeComponent {
|
||||
@Input() pinnedItems?: Array<Tree> = [];
|
||||
@Input() itemsClickable?: boolean;
|
||||
@Input() useGlobalCollapsedState?: boolean;
|
||||
@Input() isPropertiesTree?: boolean;
|
||||
@Input() isAlwaysCollapsed?: boolean;
|
||||
@Input() showNode: (item?: any) => boolean = () => true;
|
||||
@Input() isLeaf: (item: any) => boolean = (item: any) => !item.children || item.children.length === 0;
|
||||
|
||||
@Output() highlightedItemChange = new EventEmitter<string>();
|
||||
@Output() selectedTreeChange = new EventEmitter<Tree>();
|
||||
@Output() pinnedItemChange = new EventEmitter<Tree>();
|
||||
@Output() hoverStart = new EventEmitter<void>();
|
||||
@Output() hoverEnd = new EventEmitter<void>();
|
||||
@@ -99,7 +112,7 @@ export class TreeComponent {
|
||||
nodeElement: HTMLElement;
|
||||
|
||||
constructor(
|
||||
@Inject(ElementRef) elementRef: ElementRef,
|
||||
@Inject(ElementRef) public elementRef: ElementRef,
|
||||
) {
|
||||
this.nodeElement = elementRef.nativeElement.querySelector(".node");
|
||||
this.nodeElement?.addEventListener("mousedown", this.nodeMouseDownEventListener);
|
||||
@@ -107,6 +120,18 @@ export class TreeComponent {
|
||||
this.nodeElement?.addEventListener("mouseleave", this.nodeMouseLeaveEventListener);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.isCollapsedByDefault) {
|
||||
this.setCollapseValue(this.isCollapsedByDefault);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
if (isHighlighted(this.item, this.highlightedItems)) {
|
||||
this.selectedTreeChange.emit(this.item);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.nodeElement?.removeEventListener("mousedown", this.nodeMouseDownEventListener);
|
||||
this.nodeElement?.removeEventListener("mouseenter", this.nodeMouseEnterEventListener);
|
||||
@@ -119,7 +144,7 @@ export class TreeComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isLeaf() && event.detail % 2 === 0) {
|
||||
if (!this.isLeaf(this.item) && event.detail % 2 === 0) {
|
||||
// Double click collapsable node
|
||||
event.preventDefault();
|
||||
this.toggleTree();
|
||||
@@ -140,6 +165,8 @@ export class TreeComponent {
|
||||
updateHighlightedItems() {
|
||||
if (this.item && this.item.id) {
|
||||
this.highlightedItemChange.emit(`${this.item.id}`);
|
||||
} else if (!this.item.id) {
|
||||
this.selectedTreeChange.emit(this.item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,20 +177,20 @@ export class TreeComponent {
|
||||
return false;
|
||||
}
|
||||
|
||||
sendNewHighlightedItemToHierarchy(newId: string) {
|
||||
propagateNewHighlightedItem(newId: string) {
|
||||
this.highlightedItemChange.emit(newId);
|
||||
}
|
||||
|
||||
sendNewPinnedItemToHierarchy(newPinnedItem: Tree) {
|
||||
propagateNewPinnedItem(newPinnedItem: Tree) {
|
||||
this.pinnedItemChange.emit(newPinnedItem);
|
||||
}
|
||||
|
||||
isLeaf() {
|
||||
return !this.item.children || this.item.children.length === 0;
|
||||
propagateNewSelectedTree(newTree: Tree) {
|
||||
this.selectedTreeChange.emit(newTree);
|
||||
}
|
||||
|
||||
isClickable() {
|
||||
return !this.isLeaf() || this.itemsClickable;
|
||||
return !this.isLeaf(this.item) || this.itemsClickable;
|
||||
}
|
||||
|
||||
toggleTree() {
|
||||
@@ -171,19 +198,18 @@ export class TreeComponent {
|
||||
}
|
||||
|
||||
expandTree() {
|
||||
this.setCollapseValue(false);
|
||||
this.setCollapseValue(true);
|
||||
}
|
||||
|
||||
isCollapsed() {
|
||||
if (this.isLeaf()) {
|
||||
return false;
|
||||
if (this.isAlwaysCollapsed || this.isLeaf(this.item)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.useGlobalCollapsedState) {
|
||||
return this.store.getFromStore(`collapsedState.item.${this.dependencies}.${this.item.id}`)==="true"
|
||||
?? this.isCollapsedByDefault;
|
||||
}
|
||||
|
||||
return this.localCollapsedState;
|
||||
}
|
||||
|
||||
@@ -193,10 +219,10 @@ export class TreeComponent {
|
||||
|
||||
hasChildren() {
|
||||
const isParentEntryInFlatView = this.item.kind === "entry" && this.isFlattened;
|
||||
return (!this.isFlattened || isParentEntryInFlatView) && !this.isLeaf();
|
||||
return (!this.isFlattened || isParentEntryInFlatView) && !this.isLeaf(this.item);
|
||||
}
|
||||
|
||||
setCollapseValue(isCollapsed:boolean) {
|
||||
setCollapseValue(isCollapsed: boolean) {
|
||||
if (this.useGlobalCollapsedState) {
|
||||
this.store.addToStore(`collapsedState.item.${this.dependencies}.${this.item.id}`, `${isCollapsed}`);
|
||||
} else {
|
||||
|
||||
@@ -13,14 +13,13 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {ComponentFixture, TestBed} from "@angular/core/testing";
|
||||
import { ComponentFixture, TestBed, ComponentFixtureAutoDetect } from "@angular/core/testing";
|
||||
import { TreeNodeComponent } from "./tree_node.component";
|
||||
import { ComponentFixtureAutoDetect } from "@angular/core/testing";
|
||||
import { NO_ERRORS_SCHEMA } from "@angular/core";
|
||||
import { Component, ViewChild, NO_ERRORS_SCHEMA } from "@angular/core";
|
||||
|
||||
describe("TreeNodeComponent", () => {
|
||||
let fixture: ComponentFixture<TreeNodeComponent>;
|
||||
let component: TreeNodeComponent;
|
||||
let fixture: ComponentFixture<TestHostComponent>;
|
||||
let component: TestHostComponent;
|
||||
let htmlElement: HTMLElement;
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -29,27 +28,16 @@ describe("TreeNodeComponent", () => {
|
||||
{ provide: ComponentFixtureAutoDetect, useValue: true }
|
||||
],
|
||||
declarations: [
|
||||
TreeNodeComponent
|
||||
TreeNodeComponent, TestHostComponent
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TreeNodeComponent);
|
||||
fixture = TestBed.createComponent(TestHostComponent);
|
||||
component = fixture.componentInstance;
|
||||
htmlElement = fixture.nativeElement;
|
||||
component.item = {
|
||||
simplifyNames: false,
|
||||
kind: "entry",
|
||||
name: "BaseLayerTraceEntry",
|
||||
shortName: "BLTE",
|
||||
chips: [],
|
||||
};
|
||||
component.isCollapsed = true;
|
||||
component.hasChildren = false;
|
||||
component.isPinned = false;
|
||||
component.isInPinnedSection = false;
|
||||
});
|
||||
|
||||
it("can be created", () => {
|
||||
@@ -57,9 +45,29 @@ describe("TreeNodeComponent", () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it("creates tree element", () => {
|
||||
fixture.detectChanges();
|
||||
const treeElement = htmlElement.querySelector("tree-element");
|
||||
expect(treeElement).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@Component({
|
||||
selector: "host-component",
|
||||
template: `
|
||||
<tree-node
|
||||
[item]="item"
|
||||
[isCollapsed]="true"
|
||||
[isPinned]="false"
|
||||
[isInPinnedSection]="false"
|
||||
[hasChildren]="false"
|
||||
[isPropertiesTreeNode]="false"
|
||||
></tree-node>
|
||||
`
|
||||
})
|
||||
class TestHostComponent {
|
||||
item = {
|
||||
simplifyNames: false,
|
||||
kind: "entry",
|
||||
name: "BaseLayerTraceEntry",
|
||||
shortName: "BLTE",
|
||||
chips: [],
|
||||
};
|
||||
|
||||
@ViewChild(TreeNodeComponent)
|
||||
public treeNodeComponent!: TreeNodeComponent;
|
||||
}
|
||||
});
|
||||
@@ -14,8 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Component, Input, Output, EventEmitter } from "@angular/core";
|
||||
import { nodeInnerItemStyles } from "viewers/styles/node.styles";
|
||||
import { Tree } from "viewers/common/tree_utils";
|
||||
import { nodeInnerItemStyles } from "viewers/components/styles/node.styles";
|
||||
import { PropertiesTree, Tree, DiffType } from "viewers/common/tree_utils";
|
||||
|
||||
@Component({
|
||||
selector: "tree-node",
|
||||
@@ -26,7 +26,7 @@ import { Tree } from "viewers/common/tree_utils";
|
||||
*ngIf="showChevron()"
|
||||
>
|
||||
<mat-icon class="icon-button">
|
||||
{{isCollapsed ? "chevron_right" : "arrow_drop_down"}}
|
||||
{{isCollapsed ? "arrow_drop_down" : "chevron_right"}}
|
||||
</mat-icon>
|
||||
</button>
|
||||
|
||||
@@ -40,7 +40,7 @@ import { Tree } from "viewers/common/tree_utils";
|
||||
<button
|
||||
class="icon-button pin-node-btn"
|
||||
(click)="pinNode($event)"
|
||||
*ngIf="!isEntryNode()"
|
||||
*ngIf="showPinNodeIcon()"
|
||||
>
|
||||
<mat-icon class="icon-button">
|
||||
{{isPinned ? "star" : "star_border"}}
|
||||
@@ -48,15 +48,21 @@ import { Tree } from "viewers/common/tree_utils";
|
||||
</button>
|
||||
|
||||
<div class="description">
|
||||
<tree-element
|
||||
<tree-node-data-view
|
||||
[item]="item"
|
||||
></tree-element>
|
||||
*ngIf="!isPropertiesTreeNode"
|
||||
></tree-node-data-view>
|
||||
<tree-node-properties-data-view
|
||||
[item]="item"
|
||||
*ngIf="isPropertiesTreeNode"
|
||||
></tree-node-properties-data-view>
|
||||
</div>
|
||||
|
||||
<button
|
||||
*ngIf="hasChildren && isCollapsed"
|
||||
*ngIf="hasChildren && !isCollapsed"
|
||||
(click)="expandTree($event)"
|
||||
class="icon-button expand-tree-btn"
|
||||
[class]="collapseDiffClass"
|
||||
>
|
||||
<mat-icon
|
||||
aria-hidden="true"
|
||||
@@ -70,25 +76,35 @@ import { Tree } from "viewers/common/tree_utils";
|
||||
})
|
||||
|
||||
export class TreeNodeComponent {
|
||||
@Input() item!: Tree | null;
|
||||
@Input() item!: Tree | PropertiesTree;
|
||||
@Input() isLeaf?: boolean;
|
||||
@Input() flattened?: boolean;
|
||||
@Input() isCollapsed?: boolean;
|
||||
@Input() hasChildren?: boolean = false;
|
||||
@Input() isPinned?: boolean = false;
|
||||
@Input() isInPinnedSection?: boolean = false;
|
||||
@Input() isPropertiesTreeNode?: boolean;
|
||||
@Input() isAlwaysCollapsed?: boolean;
|
||||
|
||||
@Output() toggleTreeChange = new EventEmitter<void>();
|
||||
@Output() expandTreeChange = new EventEmitter<boolean>();
|
||||
@Output() pinNodeChange = new EventEmitter<Tree>();
|
||||
|
||||
isEntryNode() {
|
||||
return this.item.kind === "entry" ?? false;
|
||||
collapseDiffClass = "";
|
||||
|
||||
ngOnChanges() {
|
||||
this.collapseDiffClass = this.updateCollapseDiffClass();
|
||||
}
|
||||
|
||||
showPinNodeIcon() {
|
||||
return (!this.isPropertiesTreeNode && this.item.kind !== "entry") ?? false;
|
||||
}
|
||||
|
||||
toggleTree(event: MouseEvent) {
|
||||
event.stopPropagation();
|
||||
this.toggleTreeChange.emit();
|
||||
if (!this.isAlwaysCollapsed) {
|
||||
event.stopPropagation();
|
||||
this.toggleTreeChange.emit();
|
||||
}
|
||||
}
|
||||
|
||||
showChevron() {
|
||||
@@ -108,4 +124,42 @@ export class TreeNodeComponent {
|
||||
event.stopPropagation();
|
||||
this.pinNodeChange.emit(this.item);
|
||||
}
|
||||
|
||||
updateCollapseDiffClass() {
|
||||
if (this.isCollapsed) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const childrenDiffClasses = this.getAllDiffTypesOfChildren(this.item);
|
||||
|
||||
childrenDiffClasses.delete(DiffType.NONE);
|
||||
childrenDiffClasses.delete(undefined);
|
||||
|
||||
if (childrenDiffClasses.size === 0) {
|
||||
return "";
|
||||
}
|
||||
if (childrenDiffClasses.size === 1) {
|
||||
const diffType = childrenDiffClasses.values().next().value;
|
||||
return diffType;
|
||||
}
|
||||
return DiffType.MODIFIED;
|
||||
}
|
||||
|
||||
getAllDiffTypesOfChildren(item: Tree | PropertiesTree) {
|
||||
if (!item.children) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
const classes = new Set();
|
||||
for (const child of item.children) {
|
||||
if (child.diffType) {
|
||||
classes.add(child.diffType);
|
||||
}
|
||||
for (const diffClass of this.getAllDiffTypesOfChildren(child)) {
|
||||
classes.add(diffClass);
|
||||
}
|
||||
}
|
||||
|
||||
return classes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {ComponentFixture, TestBed} from "@angular/core/testing";
|
||||
import { TreeNodeDataViewComponent } from "./tree_node_data_view.component";
|
||||
import { ComponentFixtureAutoDetect } from "@angular/core/testing";
|
||||
import { NO_ERRORS_SCHEMA } from "@angular/core";
|
||||
|
||||
describe("TreeNodeDataViewComponent", () => {
|
||||
let fixture: ComponentFixture<TreeNodeDataViewComponent>;
|
||||
let component: TreeNodeDataViewComponent;
|
||||
let htmlElement: HTMLElement;
|
||||
|
||||
beforeAll(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{ provide: ComponentFixtureAutoDetect, useValue: true }
|
||||
],
|
||||
declarations: [
|
||||
TreeNodeDataViewComponent
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TreeNodeDataViewComponent);
|
||||
component = fixture.componentInstance;
|
||||
htmlElement = fixture.nativeElement;
|
||||
});
|
||||
|
||||
it("can be created", () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -14,12 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Component, Input } from "@angular/core";
|
||||
import { treeElementStyles } from "viewers/styles/tree_element.styles";
|
||||
import { treeNodeDataViewStyles } from "viewers/components/styles/tree_node_data_view.styles";
|
||||
import { Tree } from "viewers/common/tree_utils";
|
||||
import Chip from "viewers/common/chip";
|
||||
|
||||
@Component({
|
||||
selector: "tree-element",
|
||||
selector: "tree-node-data-view",
|
||||
template: `
|
||||
<span>
|
||||
<span class="kind">{{item.kind}}</span>
|
||||
@@ -33,10 +33,10 @@ import Chip from "viewers/common/chip";
|
||||
>{{chip.short}}</div>
|
||||
</span>
|
||||
`,
|
||||
styles: [ treeElementStyles ]
|
||||
styles: [ treeNodeDataViewStyles ]
|
||||
})
|
||||
|
||||
export class TreeElementComponent {
|
||||
export class TreeNodeDataViewComponent {
|
||||
@Input() item!: Tree;
|
||||
|
||||
showShortName() {
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {ComponentFixture, TestBed} from "@angular/core/testing";
|
||||
import { TreeNodePropertiesDataViewComponent } from "./tree_node_properties_data_view.component";
|
||||
import { ComponentFixtureAutoDetect } from "@angular/core/testing";
|
||||
|
||||
describe("TreeNodePropertiesDataViewComponent", () => {
|
||||
let fixture: ComponentFixture<TreeNodePropertiesDataViewComponent>;
|
||||
let component: TreeNodePropertiesDataViewComponent;
|
||||
let htmlElement: HTMLElement;
|
||||
|
||||
beforeAll(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{ provide: ComponentFixtureAutoDetect, useValue: true }
|
||||
],
|
||||
declarations: [
|
||||
TreeNodePropertiesDataViewComponent
|
||||
],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TreeNodePropertiesDataViewComponent);
|
||||
component = fixture.componentInstance;
|
||||
htmlElement = fixture.nativeElement;
|
||||
});
|
||||
|
||||
it("can be created", () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Component, Input } from "@angular/core";
|
||||
import { treeNodePropertiesDataViewStyles } from "viewers/components/styles/tree_node_data_view.styles";
|
||||
import { PropertiesTree } from "viewers/common/tree_utils";
|
||||
|
||||
@Component({
|
||||
selector: "tree-node-properties-data-view",
|
||||
template: `
|
||||
<span>
|
||||
<span class="key">{{ item.propertyKey }}</span>
|
||||
<span *ngIf="item.propertyValue">: </span>
|
||||
<span class="value" *ngIf="item.propertyValue" [class]="[valueClass()]">{{ item.propertyValue }}</span>
|
||||
</span>
|
||||
`,
|
||||
styles: [ treeNodePropertiesDataViewStyles ]
|
||||
})
|
||||
|
||||
export class TreeNodePropertiesDataViewComponent {
|
||||
@Input() item!: PropertiesTree;
|
||||
|
||||
valueClass() {
|
||||
if (!this.item.propertyValue) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.item.propertyValue == "null") {
|
||||
return "null";
|
||||
}
|
||||
|
||||
if (this.item.propertyValue == "true") {
|
||||
return "true";
|
||||
}
|
||||
|
||||
if (this.item.propertyValue == "false") {
|
||||
return "false";
|
||||
}
|
||||
|
||||
if (!isNaN(this.item.propertyValue)) {
|
||||
return "number";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@
|
||||
import { TraceType } from "common/trace/trace_type";
|
||||
|
||||
interface Viewer {
|
||||
//TODO: add TraceEntry data type
|
||||
notifyCurrentTraceEntries(entries: Map<TraceType, any>): void;
|
||||
getView(): HTMLElement;
|
||||
getTitle(): string;
|
||||
|
||||
@@ -16,19 +16,20 @@
|
||||
import { Rectangle, RectMatrix, RectTransform, UiData } from "viewers/viewer_surface_flinger/ui_data";
|
||||
import { TraceType } from "common/trace/trace_type";
|
||||
import { UserOptions } from "viewers/common/user_options";
|
||||
import { TreeGenerator, getFilter, FilterType, Tree } from "viewers/common/tree_utils";
|
||||
import { getFilter, FilterType, Tree, TreeSummary } from "viewers/common/tree_utils";
|
||||
import { TreeGenerator } from "viewers/common/tree_generator";
|
||||
import { TreeTransformer } from "viewers/common/tree_transformer";
|
||||
|
||||
type NotifyViewCallbackType = (uiData: UiData) => void;
|
||||
|
||||
class Presenter {
|
||||
export class Presenter {
|
||||
constructor(notifyViewCallback: NotifyViewCallbackType) {
|
||||
this.notifyViewCallback = notifyViewCallback;
|
||||
this.uiData = new UiData();
|
||||
this.notifyViewCallback(this.uiData);
|
||||
}
|
||||
|
||||
public updatePinnedItems(event: CustomEvent) {
|
||||
const pinnedItem = event.detail.pinnedItem;
|
||||
public updatePinnedItems(pinnedItem: Tree) {
|
||||
const pinnedId = `${pinnedItem.id}`;
|
||||
if (this.pinnedItems.map(item => `${item.id}`).includes(pinnedId)) {
|
||||
this.pinnedItems = this.pinnedItems.filter(pinned => `${pinned.id}` != pinnedId);
|
||||
@@ -40,8 +41,7 @@ class Presenter {
|
||||
this.notifyViewCallback(this.uiData);
|
||||
}
|
||||
|
||||
public updateHighlightedItems(event: CustomEvent) {
|
||||
const id = `${event.detail.id}`;
|
||||
public updateHighlightedItems(id: string) {
|
||||
if (this.highlightedItems.includes(id)) {
|
||||
this.highlightedItems = this.highlightedItems.filter(hl => hl != id);
|
||||
} else {
|
||||
@@ -52,23 +52,86 @@ class Presenter {
|
||||
this.notifyViewCallback(this.uiData);
|
||||
}
|
||||
|
||||
public updateHierarchyTree(event: CustomEvent) {
|
||||
this.hierarchyUserOptions = event.detail.userOptions;
|
||||
public updateHierarchyTree(userOptions: UserOptions) {
|
||||
this.hierarchyUserOptions = userOptions;
|
||||
this.uiData.hierarchyUserOptions = this.hierarchyUserOptions;
|
||||
this.uiData.tree = this.generateTree();
|
||||
this.notifyViewCallback(this.uiData);
|
||||
}
|
||||
|
||||
public filterHierarchyTree(event: CustomEvent) {
|
||||
this.hierarchyFilter = getFilter(event.detail.filterString);
|
||||
public filterHierarchyTree(filterString: string) {
|
||||
this.hierarchyFilter = getFilter(filterString);
|
||||
this.uiData.tree = this.generateTree();
|
||||
this.notifyViewCallback(this.uiData);
|
||||
}
|
||||
|
||||
public updatePropertiesTree(userOptions: UserOptions) {
|
||||
this.propertiesUserOptions = userOptions;
|
||||
this.uiData.propertiesUserOptions = this.propertiesUserOptions;
|
||||
this.updateSelectedTreeUiData();
|
||||
}
|
||||
|
||||
public filterPropertiesTree(filterString: string) {
|
||||
this.propertiesFilter = getFilter(filterString);
|
||||
this.updateSelectedTreeUiData();
|
||||
}
|
||||
|
||||
public newPropertiesTree(selectedItem: any) {
|
||||
this.selectedTree = selectedItem;
|
||||
this.updateSelectedTreeUiData();
|
||||
}
|
||||
|
||||
private updateSelectedTreeUiData() {
|
||||
this.uiData.selectedTree = this.getTreeWithTransformedProperties(this.selectedTree);
|
||||
this.uiData.selectedTreeSummary = this.getSelectedTreeSummary(this.selectedTree);
|
||||
this.notifyViewCallback(this.uiData);
|
||||
}
|
||||
|
||||
private getSelectedTreeSummary(layer: Tree): TreeSummary | undefined {
|
||||
const summary = [];
|
||||
|
||||
if (layer?.visibilityReason?.length > 0) {
|
||||
let reason = "";
|
||||
if (Array.isArray(layer.visibilityReason)) {
|
||||
reason = layer.visibilityReason.join(", ");
|
||||
} else {
|
||||
reason = layer.visibilityReason;
|
||||
}
|
||||
|
||||
summary.push({key: "Invisible due to", value: reason});
|
||||
}
|
||||
|
||||
if (layer?.occludedBy?.length > 0) {
|
||||
summary.push({key: "Occluded by", value: layer.occludedBy.map((it:Tree) => it.id).join(", ")});
|
||||
}
|
||||
|
||||
if (layer?.partiallyOccludedBy?.length > 0) {
|
||||
summary.push({
|
||||
key: "Partially occluded by",
|
||||
value: layer.partiallyOccludedBy.map((it:Tree) => it.id).join(", "),
|
||||
});
|
||||
}
|
||||
|
||||
if (layer?.coveredBy?.length > 0) {
|
||||
summary.push({key: "Covered by", value: layer.coveredBy.map((it:Tree) => it.id).join(", ")});
|
||||
}
|
||||
|
||||
if (summary.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return summary;
|
||||
}
|
||||
|
||||
public notifyCurrentTraceEntries(entries: Map<TraceType, any>) {
|
||||
this.uiData = new UiData();
|
||||
const entry = entries.get(TraceType.SURFACE_FLINGER)[0];
|
||||
this.uiData.rects = [];
|
||||
this.previousEntry = entries.get(TraceType.SURFACE_FLINGER)[1];
|
||||
|
||||
this.uiData = new UiData();
|
||||
|
||||
this.uiData.highlightedItems = this.highlightedItems;
|
||||
|
||||
const displayRects = entry.displays.map((display: any) => {
|
||||
const rect = display.layerStackSpace;
|
||||
rect.label = display.name;
|
||||
@@ -78,7 +141,6 @@ class Presenter {
|
||||
rect.isVirtual = display.isVirtual ?? false;
|
||||
return rect;
|
||||
}) ?? [];
|
||||
|
||||
this.displayIds = [];
|
||||
const rects = entry.visibleLayers
|
||||
.sort((a: any, b: any) => (b.absoluteZ > a.absoluteZ) ? 1 : (a.absoluteZ == b.absoluteZ) ? 0 : -1)
|
||||
@@ -92,13 +154,12 @@ class Presenter {
|
||||
});
|
||||
this.uiData.rects = this.rectsToUiData(rects.concat(displayRects));
|
||||
this.uiData.displayIds = this.displayIds;
|
||||
this.uiData.highlightedItems = this.highlightedItems;
|
||||
this.uiData.rects = this.rectsToUiData(entry.rects.concat(displayRects));
|
||||
this.uiData.hierarchyUserOptions = this.hierarchyUserOptions;
|
||||
this.previousEntry = entries.get(TraceType.SURFACE_FLINGER)[1];
|
||||
this.entry = entry;
|
||||
|
||||
this.entry = entry;
|
||||
this.uiData.hierarchyUserOptions = this.hierarchyUserOptions;
|
||||
this.uiData.propertiesUserOptions = this.propertiesUserOptions;
|
||||
this.uiData.tree = this.generateTree();
|
||||
|
||||
this.notifyViewCallback(this.uiData);
|
||||
}
|
||||
|
||||
@@ -106,7 +167,11 @@ class Presenter {
|
||||
if (!this.entry) {
|
||||
return null;
|
||||
}
|
||||
const generator = new TreeGenerator(this.entry, this.hierarchyUserOptions, this.hierarchyFilter, this.pinnedIds)
|
||||
|
||||
const generator = new TreeGenerator(this.entry, this.hierarchyFilter, this.pinnedIds)
|
||||
.setIsOnlyVisibleView(this.hierarchyUserOptions["onlyVisible"]?.enabled)
|
||||
.setIsSimplifyNames(this.hierarchyUserOptions["simplifyNames"]?.enabled)
|
||||
.setIsFlatView(this.hierarchyUserOptions["flat"]?.enabled)
|
||||
.withUniqueNodeId();
|
||||
let tree: Tree;
|
||||
if (!this.hierarchyUserOptions["showDiff"]?.enabled) {
|
||||
@@ -172,13 +237,26 @@ class Presenter {
|
||||
}
|
||||
}
|
||||
|
||||
private getTreeWithTransformedProperties(selectedTree: Tree) {
|
||||
const transformer = new TreeTransformer(selectedTree, this.propertiesFilter)
|
||||
.setIsShowDefaults(this.propertiesUserOptions["showDefaults"]?.enabled)
|
||||
.setIsShowDiff(this.propertiesUserOptions["showDiff"]?.enabled)
|
||||
.setTransformerOptions({skip: selectedTree.skip})
|
||||
.setDiffProperties(this.previousEntry);
|
||||
this.uiData.selectedLayer = transformer.getOriginalLayer(this.entry, selectedTree.stableId);
|
||||
const transformedTree = transformer.transform();
|
||||
return transformedTree;
|
||||
}
|
||||
|
||||
private readonly notifyViewCallback: NotifyViewCallbackType;
|
||||
private uiData: UiData;
|
||||
private displayIds: Array<number> = [];
|
||||
private hierarchyFilter: FilterType = getFilter("");
|
||||
private propertiesFilter: FilterType = getFilter("");
|
||||
private highlightedItems: Array<string> = [];
|
||||
private displayIds: Array<number> = [];
|
||||
private pinnedItems: Array<Tree> = [];
|
||||
private pinnedIds: Array<string> = [];
|
||||
private selectedTree: any = null;
|
||||
private previousEntry: any = null;
|
||||
private entry: any = null;
|
||||
private hierarchyUserOptions: UserOptions = {
|
||||
@@ -199,6 +277,20 @@ class Presenter {
|
||||
enabled: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export {Presenter};
|
||||
private propertiesUserOptions: UserOptions = {
|
||||
showDiff: {
|
||||
name: "Show diff",
|
||||
enabled: false
|
||||
},
|
||||
showDefaults: {
|
||||
name: "Show defaults",
|
||||
enabled: true,
|
||||
tooltip: `
|
||||
If checked, shows the value of all properties.
|
||||
Otherwise, hides all properties whose value is
|
||||
the default for its data type.
|
||||
`
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -13,9 +13,10 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { TraceType } from "common/trace/trace_type";
|
||||
import { Tree } from "viewers/common/tree_utils";
|
||||
import { Tree, TreeSummary } from "viewers/common/tree_utils";
|
||||
import { UserOptions } from "viewers/common/user_options";
|
||||
import { Layer } from "common/trace/flickerlib/common";
|
||||
import { TraceType } from "common/trace/trace_type";
|
||||
|
||||
export class UiData {
|
||||
dependencies: Array<TraceType> = [TraceType.SURFACE_FLINGER];
|
||||
@@ -24,7 +25,11 @@ export class UiData {
|
||||
highlightedItems?: Array<string> = [];
|
||||
pinnedItems?: Array<Tree> = [];
|
||||
hierarchyUserOptions?: UserOptions = {};
|
||||
propertiesUserOptions?: UserOptions = {};
|
||||
tree?: Tree | null = null;
|
||||
selectedTree?: any = {};
|
||||
selectedLayer?: Layer = {};
|
||||
selectedTreeSummary?: TreeSummary = [];
|
||||
}
|
||||
|
||||
export interface Rectangle {
|
||||
|
||||
@@ -31,6 +31,7 @@ import { PersistentStore } from "common/persistent_store";
|
||||
[rects]="inputData?.rects ?? []"
|
||||
[displayIds]="inputData?.displayIds ?? []"
|
||||
[highlightedItems]="inputData?.highlightedItems ?? []"
|
||||
[displayIds]="inputData?.displayIds ?? []"
|
||||
></rects-view>
|
||||
</mat-card>
|
||||
<div fxLayout="row wrap" fxLayoutGap="10px grid" class="card-grid">
|
||||
@@ -45,7 +46,13 @@ import { PersistentStore } from "common/persistent_store";
|
||||
></hierarchy-view>
|
||||
</mat-card>
|
||||
<mat-card id="sf-properties-view" class="properties-view">
|
||||
<properties-view></properties-view>
|
||||
<properties-view
|
||||
[userOptions]="inputData?.propertiesUserOptions ?? {}"
|
||||
[selectedTree]="inputData?.selectedTree ?? {}"
|
||||
[selectedLayer]="inputData?.selectedLayer ?? {}"
|
||||
[summary]="inputData?.selectedTreeSummary ?? []"
|
||||
[propertyGroups]="true"
|
||||
></properties-view>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -25,10 +25,13 @@ class ViewerSurfaceFlinger implements Viewer {
|
||||
this.presenter = new Presenter((uiData: UiData) => {
|
||||
(this.view as any).inputData = uiData;
|
||||
});
|
||||
this.view.addEventListener(ViewerEvents.HierarchyPinnedChange, (event) => this.presenter.updatePinnedItems((event as CustomEvent)));
|
||||
this.view.addEventListener(ViewerEvents.HighlightedChange, (event) => this.presenter.updateHighlightedItems((event as CustomEvent)));
|
||||
this.view.addEventListener(ViewerEvents.HierarchyUserOptionsChange, (event) => this.presenter.updateHierarchyTree((event as CustomEvent)));
|
||||
this.view.addEventListener(ViewerEvents.HierarchyFilterChange, (event) => this.presenter.filterHierarchyTree((event as CustomEvent)));
|
||||
this.view.addEventListener(ViewerEvents.HierarchyPinnedChange, (event) => this.presenter.updatePinnedItems(((event as CustomEvent).detail.pinnedItem)));
|
||||
this.view.addEventListener(ViewerEvents.HighlightedChange, (event) => this.presenter.updateHighlightedItems(`${(event as CustomEvent).detail.id}`));
|
||||
this.view.addEventListener(ViewerEvents.HierarchyUserOptionsChange, (event) => this.presenter.updateHierarchyTree((event as CustomEvent).detail.userOptions));
|
||||
this.view.addEventListener(ViewerEvents.HierarchyFilterChange, (event) => this.presenter.filterHierarchyTree((event as CustomEvent).detail.filterString));
|
||||
this.view.addEventListener(ViewerEvents.PropertiesUserOptionsChange, (event) => this.presenter.updatePropertiesTree((event as CustomEvent).detail.userOptions));
|
||||
this.view.addEventListener(ViewerEvents.PropertiesFilterChange, (event) => this.presenter.filterPropertiesTree((event as CustomEvent).detail.filterString));
|
||||
this.view.addEventListener(ViewerEvents.SelectedTreeChange, (event) => this.presenter.newPropertiesTree((event as CustomEvent).detail.selectedItem));
|
||||
}
|
||||
|
||||
public notifyCurrentTraceEntries(entries: Map<TraceType, any>): void {
|
||||
|
||||
Reference in New Issue
Block a user