diff --git a/tools/winscope-ng/src/app/app.module.ts b/tools/winscope-ng/src/app/app.module.ts index f7dd508a8..b01019dd5 100644 --- a/tools/winscope-ng/src/app/app.module.ts +++ b/tools/winscope-ng/src/app/app.module.ts @@ -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, diff --git a/tools/winscope-ng/src/app/components/upload_traces.component.ts b/tools/winscope-ng/src/app/components/upload_traces.component.ts index e05b81d94..102545aa8 100644 --- a/tools/winscope-ng/src/app/components/upload_traces.component.ts +++ b/tools/winscope-ng/src/app/components/upload_traces.component.ts @@ -49,7 +49,7 @@ import { LoadedTrace } from "app/loaded_trace"; {{TRACE_INFO[trace.type].icon}} {{trace.name}} ({{TRACE_INFO[trace.type].name}}) @@ -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); } diff --git a/tools/winscope-ng/src/styles.css b/tools/winscope-ng/src/styles.css index 32b55acaf..2b31e3e18 100644 --- a/tools/winscope-ng/src/styles.css +++ b/tools/winscope-ng/src/styles.css @@ -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; diff --git a/tools/winscope-ng/src/viewers/common/tree_utils.spec.ts b/tools/winscope-ng/src/viewers/common/tree_generator.spec.ts similarity index 89% rename from tools/winscope-ng/src/viewers/common/tree_utils.spec.ts rename to tools/winscope-ng/src/viewers/common/tree_generator.spec.ts index 5579bb77f..5c44324a0 100644 --- a/tools/winscope-ng/src/viewers/common/tree_utils.spec.ts +++ b/tools/winscope-ng/src/viewers/common/tree_generator.spec.ts @@ -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; diff --git a/tools/winscope-ng/src/viewers/common/tree_generator.ts b/tools/winscope-ng/src/viewers/common/tree_generator.ts new file mode 100644 index 000000000..ec59a331b --- /dev/null +++ b/tools/winscope-ng/src/viewers/common/tree_generator.ts @@ -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; + private pinnedItems: Array = []; + private relZParentIds: Array = []; + private flattenedChildren: Array = []; + + constructor(tree: Tree, filter: FilterType, pinnedIds?: Array) { + 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 { + 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, + oldTreeSiblings: Array + ): Array { + 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); + } +} diff --git a/tools/winscope-ng/src/viewers/common/tree_transformer.ts b/tools/winscope-ng/src/viewers/common/tree_transformer.ts new file mode 100644 index 000000000..483470282 --- /dev/null +++ b/tools/winscope-ng/src/viewers/common/tree_transformer.ts @@ -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 { + 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; + } +} diff --git a/tools/winscope-ng/src/viewers/common/tree_utils.ts b/tools/winscope-ng/src/viewers/common/tree_utils.ts index 95e3af050..1e14ae79e 100644 --- a/tools/winscope-ng/src/viewers/common/tree_utils.ts +++ b/tools/winscope-ng/src/viewers/common/tree_utils.ts @@ -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) { @@ -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; - private pinnedItems: Array = []; - private relZParentIds: Array = []; - private flattenedChildren: Array = []; - - constructor(tree: Tree, userOptions: UserOptions, filter: FilterType, pinnedIds?: Array) { - 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 { - 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, - oldTreeSiblings: Array - ): Array { - 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); - } -} diff --git a/tools/winscope-ng/src/viewers/common/user_options.ts b/tools/winscope-ng/src/viewers/common/user_options.ts index c1f2c1c42..9da222512 100644 --- a/tools/winscope-ng/src/viewers/common/user_options.ts +++ b/tools/winscope-ng/src/viewers/common/user_options.ts @@ -16,6 +16,7 @@ export interface UserOptions { [key: string]: { name: string, - enabled: boolean + enabled: boolean, + tooltip?: string } } \ No newline at end of file diff --git a/tools/winscope-ng/src/viewers/common/viewer_events.ts b/tools/winscope-ng/src/viewers/common/viewer_events.ts index b9cda8fff..68040e29d 100644 --- a/tools/winscope-ng/src/viewers/common/viewer_events.ts +++ b/tools/winscope-ng/src/viewers/common/viewer_events.ts @@ -17,5 +17,8 @@ export const ViewerEvents = { HierarchyPinnedChange: "HierarchyPinnedChange", HighlightedChange: "HighlightedChange", HierarchyUserOptionsChange: "HierarchyUserOptionsChange", - HierarchyFilterChange: "HierarchyFilterChange" -}; \ No newline at end of file + HierarchyFilterChange: "HierarchyFilterChange", + SelectedTreeChange: "SelectedTreeChange", + PropertiesUserOptionsChange: "PropertiesUserOptionsChange", + PropertiesFilterChange: "PropertiesFilterChange" +}; diff --git a/tools/winscope-ng/src/viewers/components/hierarchy.component.ts b/tools/winscope-ng/src/viewers/components/hierarchy.component.ts index b71aaf570..ab7a49d2b 100644 --- a/tools/winscope-ng/src/viewers/components/hierarchy.component.ts +++ b/tools/winscope-ng/src/viewers/components/hierarchy.component.ts @@ -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)" > @@ -76,6 +76,7 @@ import { TraceType } from "common/trace/trace_type"; [pinnedItems]="pinnedItems" (highlightedItemChange)="highlightedItemChange($event)" (pinnedItemChange)="pinnedItemChange($event)" + (selectedTreeChange)="selectedTreeChange($event)" > @@ -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, diff --git a/tools/winscope-ng/src/viewers/components/properties.component.spec.ts b/tools/winscope-ng/src/viewers/components/properties.component.spec.ts new file mode 100644 index 000000000..2e5681186 --- /dev/null +++ b/tools/winscope-ng/src/viewers/components/properties.component.spec.ts @@ -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; + 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(); + }); +}); diff --git a/tools/winscope-ng/src/viewers/components/properties.component.ts b/tools/winscope-ng/src/viewers/components/properties.component.ts index 92fc1ff1c..52a1b0bbd 100644 --- a/tools/winscope-ng/src/viewers/components/properties.component.ts +++ b/tools/winscope-ng/src/viewers/components/properties.component.ts @@ -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: ` - Properties + + + Properties + + Filter... + + + +
+ {{userOptions[option].name}} +
+
+ +
+
+ +
+ +
+
`, + 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; + } } diff --git a/tools/winscope-ng/src/viewers/components/property_groups.component.spec.ts b/tools/winscope-ng/src/viewers/components/property_groups.component.spec.ts new file mode 100644 index 000000000..8ec04b7ea --- /dev/null +++ b/tools/winscope-ng/src/viewers/components/property_groups.component.spec.ts @@ -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; + 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(); + }); +}); diff --git a/tools/winscope-ng/src/viewers/components/property_groups.component.ts b/tools/winscope-ng/src/viewers/components/property_groups.component.ts new file mode 100644 index 000000000..e517c64f5 --- /dev/null +++ b/tools/winscope-ng/src/viewers/components/property_groups.component.ts @@ -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: ` +
+
+ Geometry +
+
Calculated
+ Transform: + +
+ Crop: + {{ item.bounds }} +
+ Final Bounds: + {{ item.screenBounds }} +
+
+
Requested
+ Transform: + +
+ Crop: + {{ item.crop ? item.crop : "[empty]" }} +
+
+
+ + Buffer + +
+
+ Size: + {{ item.activeBuffer }} +
+ Frame Number: + {{ item.currFrame }} +
+ Transform: + {{ item.bufferTransform }} +
+
+
+ Destination Frame: + {{ getDestinationFrame() }} +
+ Destination Frame ignored because layer has eIgnoreDestinationFrame + flag set. + +
+
+ + Container layer +
+
+ + Effect layer +
+
+
+ + Hierarchy + +
+
+ z-order: + {{ item.z }} +
+ relative parent: + + {{ item.zOrderRelativeOfId == -1 ? "none" : item.zOrderRelativeOfId }} + +
+
+
+ + Effects + +
+
Calculated
+ Color: + {{ item.color }} +
+ Shadow: + {{ item.shadowRadius }} px +
+ Corner Radius: + {{ formatFloat(item.cornerRadius) }} px +
+ Corner Radius Crop: + {{ item.cornerRadiusCrop }} +
+ Blur: + + {{ + item.proto?.backgroundBlurRadius + ? item.proto?.backgroundBlurRadius + : 0 + }} px + +
+
+
Requested
+ Color: + {{ item.requestedColor }} +
+ Shadow: + + {{ + item.proto?.requestedShadowRadius + ? item.proto?.requestedShadowRadius + : 0 + }} px + +
+ Corner Radius: + + {{ + item.proto?.requestedCornerRadius + ? formatFloat(item.proto?.requestedCornerRadius) + : 0 + }} px + +
+
+
+ + Input + +
+ To Display Transform: + +
+ Touchable Region: + {{ item.inputRegion }} +
+
+ Config: + +
+ Focusable: + {{ item.proto?.inputWindowInfo.focusable }} +
+ Crop touch region with layer: + + {{ + item.proto?.inputWindowInfo.cropLayerId <= 0 + ? "none" + : item.proto?.inputWindowInfo.cropLayerId + }} + +
+ Replace touch region with crop: + + {{ + item.proto?.inputWindowInfo.replaceTouchableRegionWithCrop + }} + +
+
+ + No input channel set +
+
+ +
+ + Visibility + +
+ Flags: + {{ item.flags }} +
+
+
+ {{ reason.key }}: + {{ reason.value }} +
+
+
+
+
+ `, + 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; + } +} diff --git a/tools/winscope-ng/src/viewers/components/rects/canvas_graphics.ts b/tools/winscope-ng/src/viewers/components/rects/canvas_graphics.ts index 79b25566d..1de3c5e6a 100644 --- a/tools/winscope-ng/src/viewers/components/rects/canvas_graphics.ts +++ b/tools/winscope-ng/src/viewers/components/rects/canvas_graphics.ts @@ -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) { diff --git a/tools/winscope-ng/src/viewers/components/rects/rects.component.spec.ts b/tools/winscope-ng/src/viewers/components/rects/rects.component.spec.ts index b10e76a9d..24ddd59d5 100644 --- a/tools/winscope-ng/src/viewers/components/rects/rects.component.spec.ts +++ b/tools/winscope-ng/src/viewers/components/rects/rects.component.spec.ts @@ -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(); }); diff --git a/tools/winscope-ng/src/viewers/components/rects/rects.component.ts b/tools/winscope-ng/src/viewers/components/rects/rects.component.ts index 90b7b06fa..a55a1a924 100644 --- a/tools/winscope-ng/src/viewers/components/rects/rects.component.ts +++ b/tools/winscope-ng/src/viewers/components/rects/rects.component.ts @@ -80,7 +80,7 @@ import { ViewerEvents } from "viewers/common/viewer_events";
- +
@@ -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(); } diff --git a/tools/winscope-ng/src/viewers/styles/node.styles.ts b/tools/winscope-ng/src/viewers/components/styles/node.styles.ts similarity index 87% rename from tools/winscope-ng/src/viewers/styles/node.styles.ts rename to tools/winscope-ng/src/viewers/components/styles/node.styles.ts index b74b5dd02..0f03eaf20 100644 --- a/tools/winscope-ng/src/viewers/styles/node.styles.ts +++ b/tools/winscope-ng/src/viewers/components/styles/node.styles.ts @@ -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; + } `; \ No newline at end of file diff --git a/tools/winscope-ng/src/viewers/styles/tree_element.styles.ts b/tools/winscope-ng/src/viewers/components/styles/tree_node_data_view.styles.ts similarity index 78% rename from tools/winscope-ng/src/viewers/styles/tree_element.styles.ts rename to tools/winscope-ng/src/viewers/components/styles/tree_node_data_view.styles.ts index 8b1cc61f8..cea15e028 100644 --- a/tools/winscope-ng/src/viewers/styles/tree_element.styles.ts +++ b/tools/winscope-ng/src/viewers/components/styles/tree_node_data_view.styles.ts @@ -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; + } +`; \ No newline at end of file diff --git a/tools/winscope-ng/src/viewers/components/tree_element.component.spec.ts b/tools/winscope-ng/src/viewers/components/transform_matrix.component.spec.ts similarity index 80% rename from tools/winscope-ng/src/viewers/components/tree_element.component.spec.ts rename to tools/winscope-ng/src/viewers/components/transform_matrix.component.spec.ts index 8322e8929..45bfe61ac 100644 --- a/tools/winscope-ng/src/viewers/components/tree_element.component.spec.ts +++ b/tools/winscope-ng/src/viewers/components/transform_matrix.component.spec.ts @@ -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; - let component: TreeElementComponent; +describe("TransformMatrixComponent", () => { + let fixture: ComponentFixture; + 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; }); diff --git a/tools/winscope-ng/src/viewers/components/transform_matrix.component.ts b/tools/winscope-ng/src/viewers/components/transform_matrix.component.ts new file mode 100644 index 000000000..015fc27ee --- /dev/null +++ b/tools/winscope-ng/src/viewers/components/transform_matrix.component.ts @@ -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: ` +
+
{{ formatFloat(transform.matrix.dsdx) }}
+
{{ formatFloat(transform.matrix.dsdy) }}
+
+ {{ formatFloat(transform.matrix.tx) }} +
+ +
{{ formatFloat(transform.matrix.dtdx) }}
+
{{ formatFloat(transform.matrix.dtdy) }}
+
+ {{ formatFloat(transform.matrix.ty) }} +
+ +
0
+
0
+
1
+
+ `, + 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; +} diff --git a/tools/winscope-ng/src/viewers/components/tree.component.spec.ts b/tools/winscope-ng/src/viewers/components/tree.component.spec.ts index 4732dc629..22f62b7fc 100644 --- a/tools/winscope-ng/src/viewers/components/tree.component.spec.ts +++ b/tools/winscope-ng/src/viewers/components/tree.component.spec.ts @@ -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; - let component: TreeComponent; + let fixture: ComponentFixture; + 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: ` + + ` + }) + 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; + } }); diff --git a/tools/winscope-ng/src/viewers/components/tree.component.ts b/tools/winscope-ng/src/viewers/components/tree.component.ts index 0c1e705b0..b5574ac90 100644 --- a/tools/winscope-ng/src/viewers/components/tree.component.ts +++ b/tools/winscope-ng/src/viewers/components/tree.component.ts @@ -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";
-
+
`, - styles: [nodeStyles, treeNodeStyles] + styles: [nodeStyles, treeNodeDataViewStyles] }) export class TreeComponent { diffClass = diffClass; isHighlighted = isHighlighted; - @Input() item!: Tree; + @Input() item!: Tree | PropertiesTree | Terminal; @Input() dependencies: Array = []; @Input() store!: PersistentStore; @Input() isFlattened? = false; @@ -85,8 +93,13 @@ export class TreeComponent { @Input() pinnedItems?: Array = []; @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(); + @Output() selectedTreeChange = new EventEmitter(); @Output() pinnedItemChange = new EventEmitter(); @Output() hoverStart = new EventEmitter(); @Output() hoverEnd = new EventEmitter(); @@ -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 { diff --git a/tools/winscope-ng/src/viewers/components/tree_node.component.spec.ts b/tools/winscope-ng/src/viewers/components/tree_node.component.spec.ts index d8cd4bb1d..b76035a90 100644 --- a/tools/winscope-ng/src/viewers/components/tree_node.component.spec.ts +++ b/tools/winscope-ng/src/viewers/components/tree_node.component.spec.ts @@ -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; - let component: TreeNodeComponent; + let fixture: ComponentFixture; + 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: ` + + ` + }) + class TestHostComponent { + item = { + simplifyNames: false, + kind: "entry", + name: "BaseLayerTraceEntry", + shortName: "BLTE", + chips: [], + }; + + @ViewChild(TreeNodeComponent) + public treeNodeComponent!: TreeNodeComponent; + } +}); \ No newline at end of file diff --git a/tools/winscope-ng/src/viewers/components/tree_node.component.ts b/tools/winscope-ng/src/viewers/components/tree_node.component.ts index 44c44d3d3..f51b342a9 100644 --- a/tools/winscope-ng/src/viewers/components/tree_node.component.ts +++ b/tools/winscope-ng/src/viewers/components/tree_node.component.ts @@ -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()" > - {{isCollapsed ? "chevron_right" : "arrow_drop_down"}} + {{isCollapsed ? "arrow_drop_down" : "chevron_right"}} @@ -40,7 +40,7 @@ import { Tree } from "viewers/common/tree_utils";
- + *ngIf="!isPropertiesTreeNode" + > +
`, - styles: [ treeElementStyles ] + styles: [ treeNodeDataViewStyles ] }) -export class TreeElementComponent { +export class TreeNodeDataViewComponent { @Input() item!: Tree; showShortName() { diff --git a/tools/winscope-ng/src/viewers/components/tree_node_properties_data_view.component.spec.ts b/tools/winscope-ng/src/viewers/components/tree_node_properties_data_view.component.spec.ts new file mode 100644 index 000000000..1d913d4ce --- /dev/null +++ b/tools/winscope-ng/src/viewers/components/tree_node_properties_data_view.component.spec.ts @@ -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; + 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(); + }); +}); diff --git a/tools/winscope-ng/src/viewers/components/tree_node_properties_data_view.component.ts b/tools/winscope-ng/src/viewers/components/tree_node_properties_data_view.component.ts new file mode 100644 index 000000000..589981c55 --- /dev/null +++ b/tools/winscope-ng/src/viewers/components/tree_node_properties_data_view.component.ts @@ -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: ` + + {{ item.propertyKey }} + : + {{ item.propertyValue }} + + `, + 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; + } +} diff --git a/tools/winscope-ng/src/viewers/viewer.ts b/tools/winscope-ng/src/viewers/viewer.ts index 5ad1857e0..009fa80ba 100644 --- a/tools/winscope-ng/src/viewers/viewer.ts +++ b/tools/winscope-ng/src/viewers/viewer.ts @@ -16,7 +16,6 @@ import { TraceType } from "common/trace/trace_type"; interface Viewer { - //TODO: add TraceEntry data type notifyCurrentTraceEntries(entries: Map): void; getView(): HTMLElement; getTitle(): string; diff --git a/tools/winscope-ng/src/viewers/viewer_surface_flinger/presenter.ts b/tools/winscope-ng/src/viewers/viewer_surface_flinger/presenter.ts index d4ccd4bbc..f2ffaf887 100644 --- a/tools/winscope-ng/src/viewers/viewer_surface_flinger/presenter.ts +++ b/tools/winscope-ng/src/viewers/viewer_surface_flinger/presenter.ts @@ -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) { 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 = []; private hierarchyFilter: FilterType = getFilter(""); + private propertiesFilter: FilterType = getFilter(""); private highlightedItems: Array = []; + private displayIds: Array = []; private pinnedItems: Array = []; private pinnedIds: Array = []; + 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. + ` + }, + }; +} diff --git a/tools/winscope-ng/src/viewers/viewer_surface_flinger/ui_data.ts b/tools/winscope-ng/src/viewers/viewer_surface_flinger/ui_data.ts index b318c7f69..ebd56defd 100644 --- a/tools/winscope-ng/src/viewers/viewer_surface_flinger/ui_data.ts +++ b/tools/winscope-ng/src/viewers/viewer_surface_flinger/ui_data.ts @@ -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.SURFACE_FLINGER]; @@ -24,7 +25,11 @@ export class UiData { highlightedItems?: Array = []; pinnedItems?: Array = []; hierarchyUserOptions?: UserOptions = {}; + propertiesUserOptions?: UserOptions = {}; tree?: Tree | null = null; + selectedTree?: any = {}; + selectedLayer?: Layer = {}; + selectedTreeSummary?: TreeSummary = []; } export interface Rectangle { diff --git a/tools/winscope-ng/src/viewers/viewer_surface_flinger/viewer_surface_flinger.component.ts b/tools/winscope-ng/src/viewers/viewer_surface_flinger/viewer_surface_flinger.component.ts index 52dd48119..9d1e0c06e 100644 --- a/tools/winscope-ng/src/viewers/viewer_surface_flinger/viewer_surface_flinger.component.ts +++ b/tools/winscope-ng/src/viewers/viewer_surface_flinger/viewer_surface_flinger.component.ts @@ -31,6 +31,7 @@ import { PersistentStore } from "common/persistent_store"; [rects]="inputData?.rects ?? []" [displayIds]="inputData?.displayIds ?? []" [highlightedItems]="inputData?.highlightedItems ?? []" + [displayIds]="inputData?.displayIds ?? []" >
@@ -45,7 +46,13 @@ import { PersistentStore } from "common/persistent_store"; > - +
diff --git a/tools/winscope-ng/src/viewers/viewer_surface_flinger/viewer_surface_flinger.ts b/tools/winscope-ng/src/viewers/viewer_surface_flinger/viewer_surface_flinger.ts index 20fea48ac..bf38e87f4 100644 --- a/tools/winscope-ng/src/viewers/viewer_surface_flinger/viewer_surface_flinger.ts +++ b/tools/winscope-ng/src/viewers/viewer_surface_flinger/viewer_surface_flinger.ts @@ -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): void {