Merge changes from topic "Rects"
* changes: (Rects View Review Changes) Create SF viewer. Create Viewer for surface flinger traces.
This commit is contained in:
committed by
Android (Google) Code Review
commit
ef46381c91
162
tools/winscope-ng/package-lock.json
generated
162
tools/winscope-ng/package-lock.json
generated
@@ -23,11 +23,14 @@
|
||||
"@ngrx/store": "^14.0.2",
|
||||
"@ngxs/store": "^3.7.4",
|
||||
"@types/jsbn": "^1.2.30",
|
||||
"@types/three": "^0.143.0",
|
||||
"angular2-template-loader": "^0.6.2",
|
||||
"auth0": "^2.42.0",
|
||||
"gl-matrix": "^3.4.3",
|
||||
"html-loader": "^3.1.0",
|
||||
"html-webpack-inline-source-plugin": "^1.0.0-beta.2",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
"jsbn": "^1.1.0",
|
||||
"jsbn-rsa": "^1.0.4",
|
||||
"kotlin": "^1.7.0",
|
||||
@@ -62,7 +65,10 @@
|
||||
"karma-jasmine": "~5.0.0",
|
||||
"karma-sourcemap-loader": "^0.3.8",
|
||||
"karma-webpack": "^5.0.0",
|
||||
"protractor": "^7.0.0"
|
||||
"protractor": "^7.0.0",
|
||||
"three": "^0.143.0",
|
||||
"webgl-utils": "^1.0.1",
|
||||
"webgl-utils.js": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ampproject/remapping": {
|
||||
@@ -3491,12 +3497,25 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/three": {
|
||||
"version": "0.143.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.143.0.tgz",
|
||||
"integrity": "sha512-c5PonXOt8xk5q4ygmyjOX4Ec+FA7gwfdcMT/PveE9xrJs/0DDcf2lJkWrhEcmvx2ZefQCQBcogABnGqB0P4OsA==",
|
||||
"dependencies": {
|
||||
"@types/webxr": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/w3c-web-usb": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/w3c-web-usb/-/w3c-web-usb-1.0.6.tgz",
|
||||
"integrity": "sha512-cSjhgrr8g4KbPnnijAr/KJDNKa/bBa+ixYkywFRvrhvi9n1WEl7yYbtRyzE6jqNQiSxxJxoAW3STaOQwJHndaw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/webxr": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.0.tgz",
|
||||
"integrity": "sha512-IUMDPSXnYIbEO2IereEFcgcqfDREOgmbGqtrMpVPpACTU6pltYLwHgVkrnYv0XhWEcjio9sYEfIEzgn3c7nDqA=="
|
||||
},
|
||||
"node_modules/@types/ws": {
|
||||
"version": "8.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
|
||||
@@ -4442,6 +4461,14 @@
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/base64-arraybuffer": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
|
||||
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
@@ -5611,6 +5638,14 @@
|
||||
"postcss": "^8.4"
|
||||
}
|
||||
},
|
||||
"node_modules/css-line-break": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
|
||||
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
|
||||
"dependencies": {
|
||||
"utrie": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/css-loader": {
|
||||
"version": "6.7.1",
|
||||
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz",
|
||||
@@ -7729,6 +7764,11 @@
|
||||
"assert-plus": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/gl-matrix": {
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz",
|
||||
"integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA=="
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-8.0.1.tgz",
|
||||
@@ -8079,6 +8119,18 @@
|
||||
"webpack": "^5.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html2canvas": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
|
||||
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
|
||||
"dependencies": {
|
||||
"css-line-break": "^2.1.0",
|
||||
"text-segmentation": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/htmlparser2": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz",
|
||||
@@ -14399,12 +14451,26 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/text-segmentation": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
|
||||
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
|
||||
"dependencies": {
|
||||
"utrie": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/text-table": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
|
||||
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/three": {
|
||||
"version": "0.143.0",
|
||||
"resolved": "https://registry.npmjs.org/three/-/three-0.143.0.tgz",
|
||||
"integrity": "sha512-oKcAGYHhJ46TGEuHjodo2n6TY2R6lbvrkp+feKZxqsUL/WkH7GKKaeu6RHeyb2Xjfk2dPLRKLsOP0KM2VgT8Zg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/through": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
||||
@@ -14843,6 +14909,14 @@
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/utrie": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
|
||||
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
|
||||
"dependencies": {
|
||||
"base64-arraybuffer": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
@@ -15104,6 +15178,18 @@
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/webgl-utils": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webgl-utils/-/webgl-utils-1.0.1.tgz",
|
||||
"integrity": "sha512-ox5xQ3YkrrOR6pCZHTOud49zzMXP9LnXzx7jIQvHOinV4FK59rGGJURw2Lq1cCTPgMVU2wAWq7e6vEVjz9FdBw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/webgl-utils.js": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/webgl-utils.js/-/webgl-utils.js-1.1.0.tgz",
|
||||
"integrity": "sha512-cmO2aPd6gR6bK/ttdk8ZIypJfZMOcTvsvXv/LxXZjAFu5TC6vXqFrZYudlPuKxVsA34Pc8Fysq2rCnflu+wuuA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
|
||||
@@ -18141,12 +18227,25 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/three": {
|
||||
"version": "0.143.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.143.0.tgz",
|
||||
"integrity": "sha512-c5PonXOt8xk5q4ygmyjOX4Ec+FA7gwfdcMT/PveE9xrJs/0DDcf2lJkWrhEcmvx2ZefQCQBcogABnGqB0P4OsA==",
|
||||
"requires": {
|
||||
"@types/webxr": "*"
|
||||
}
|
||||
},
|
||||
"@types/w3c-web-usb": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/w3c-web-usb/-/w3c-web-usb-1.0.6.tgz",
|
||||
"integrity": "sha512-cSjhgrr8g4KbPnnijAr/KJDNKa/bBa+ixYkywFRvrhvi9n1WEl7yYbtRyzE6jqNQiSxxJxoAW3STaOQwJHndaw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/webxr": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.0.tgz",
|
||||
"integrity": "sha512-IUMDPSXnYIbEO2IereEFcgcqfDREOgmbGqtrMpVPpACTU6pltYLwHgVkrnYv0XhWEcjio9sYEfIEzgn3c7nDqA=="
|
||||
},
|
||||
"@types/ws": {
|
||||
"version": "8.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
|
||||
@@ -18845,6 +18944,11 @@
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"base64-arraybuffer": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
|
||||
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ=="
|
||||
},
|
||||
"base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
@@ -19749,6 +19853,14 @@
|
||||
"postcss-selector-parser": "^6.0.9"
|
||||
}
|
||||
},
|
||||
"css-line-break": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
|
||||
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
|
||||
"requires": {
|
||||
"utrie": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"css-loader": {
|
||||
"version": "6.7.1",
|
||||
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz",
|
||||
@@ -21244,6 +21356,11 @@
|
||||
"assert-plus": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"gl-matrix": {
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz",
|
||||
"integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA=="
|
||||
},
|
||||
"glob": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-8.0.1.tgz",
|
||||
@@ -21517,6 +21634,15 @@
|
||||
"tapable": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"html2canvas": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
|
||||
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
|
||||
"requires": {
|
||||
"css-line-break": "^2.1.0",
|
||||
"text-segmentation": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"htmlparser2": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz",
|
||||
@@ -26286,12 +26412,26 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"text-segmentation": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
|
||||
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
|
||||
"requires": {
|
||||
"utrie": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"text-table": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
|
||||
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
|
||||
"dev": true
|
||||
},
|
||||
"three": {
|
||||
"version": "0.143.0",
|
||||
"resolved": "https://registry.npmjs.org/three/-/three-0.143.0.tgz",
|
||||
"integrity": "sha512-oKcAGYHhJ46TGEuHjodo2n6TY2R6lbvrkp+feKZxqsUL/WkH7GKKaeu6RHeyb2Xjfk2dPLRKLsOP0KM2VgT8Zg==",
|
||||
"dev": true
|
||||
},
|
||||
"through": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
||||
@@ -26610,6 +26750,14 @@
|
||||
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
|
||||
"dev": true
|
||||
},
|
||||
"utrie": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
|
||||
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
|
||||
"requires": {
|
||||
"base64-arraybuffer": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
@@ -26816,6 +26964,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"webgl-utils": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webgl-utils/-/webgl-utils-1.0.1.tgz",
|
||||
"integrity": "sha512-ox5xQ3YkrrOR6pCZHTOud49zzMXP9LnXzx7jIQvHOinV4FK59rGGJURw2Lq1cCTPgMVU2wAWq7e6vEVjz9FdBw==",
|
||||
"dev": true
|
||||
},
|
||||
"webgl-utils.js": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/webgl-utils.js/-/webgl-utils.js-1.1.0.tgz",
|
||||
"integrity": "sha512-cmO2aPd6gR6bK/ttdk8ZIypJfZMOcTvsvXv/LxXZjAFu5TC6vXqFrZYudlPuKxVsA34Pc8Fysq2rCnflu+wuuA==",
|
||||
"dev": true
|
||||
},
|
||||
"webidl-conversions": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
|
||||
|
||||
@@ -31,11 +31,14 @@
|
||||
"@ngrx/store": "^14.0.2",
|
||||
"@ngxs/store": "^3.7.4",
|
||||
"@types/jsbn": "^1.2.30",
|
||||
"@types/three": "^0.143.0",
|
||||
"angular2-template-loader": "^0.6.2",
|
||||
"auth0": "^2.42.0",
|
||||
"gl-matrix": "^3.4.3",
|
||||
"html-loader": "^3.1.0",
|
||||
"html-webpack-inline-source-plugin": "^1.0.0-beta.2",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
"jsbn": "^1.1.0",
|
||||
"jsbn-rsa": "^1.0.4",
|
||||
"kotlin": "^1.7.0",
|
||||
@@ -70,6 +73,9 @@
|
||||
"karma-jasmine": "~5.0.0",
|
||||
"karma-sourcemap-loader": "^0.3.8",
|
||||
"karma-webpack": "^5.0.0",
|
||||
"protractor": "^7.0.0"
|
||||
"protractor": "^7.0.0",
|
||||
"three": "^0.143.0",
|
||||
"webgl-utils": "^1.0.1",
|
||||
"webgl-utils.js": "^1.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
/*
|
||||
* 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, Inject, Injector, Input} from "@angular/core";
|
||||
import {createCustomElement} from "@angular/elements";
|
||||
import {Timestamp, TimestampType} from "common/trace/timestamp";
|
||||
import {PersistentStore} from "common/persistent_store";
|
||||
import {ViewerWindowManagerComponent} from "viewers/viewer_window_manager/viewer_window_manager.component";
|
||||
import {Core} from "./core";
|
||||
import {ProxyState, proxyClient} from "trace_collection/proxy_client";
|
||||
import { Viewer } from "viewers/viewer";
|
||||
|
||||
@Component({
|
||||
selector: "app-root",
|
||||
template: `
|
||||
<div id="title">
|
||||
<span>Winscope Viewer 2.0</span>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!dataLoaded" fxLayout="row wrap" fxLayoutGap="10px grid" class="home">
|
||||
<mat-card class="homepage-card" id="collect-traces-card">
|
||||
<collect-traces [(core)]="core" (dataLoadedChange)="onDataLoadedChange($event)"[store]="store"></collect-traces>
|
||||
</mat-card>
|
||||
<mat-card class="homepage-card" id="upload-traces-card">
|
||||
<upload-traces [(core)]="core" (dataLoadedChange)="onDataLoadedChange($event)"></upload-traces>
|
||||
</mat-card>
|
||||
</div>
|
||||
|
||||
<div *ngIf="dataLoaded">
|
||||
<mat-card class="homepage-card" id="loaded-data-card">
|
||||
<mat-card-title>Loaded data</mat-card-title>
|
||||
<button mat-raised-button (click)="clearData()">Back to Home</button>
|
||||
</mat-card>
|
||||
</div>
|
||||
|
||||
<div id="timescrub">
|
||||
<button mat-raised-button (click)="notifyCurrentTimestamp()">Update current timestamp</button>
|
||||
</div>
|
||||
|
||||
<div id="timestamps">
|
||||
</div>
|
||||
|
||||
<div id="viewers">
|
||||
</div>
|
||||
`,
|
||||
styles: [".home{width: 100%; display:flex; flex-direction: row; overflow: auto;}"]
|
||||
})
|
||||
export class AppComponent {
|
||||
title = "winscope-ng";
|
||||
core: Core;
|
||||
states = ProxyState;
|
||||
store: PersistentStore = new PersistentStore();
|
||||
@Input() dataLoaded = false;
|
||||
viewersCreated = false;
|
||||
|
||||
constructor(
|
||||
@Inject(Injector) injector: Injector
|
||||
) {
|
||||
this.core = new Core();
|
||||
if (!customElements.get("viewer-window-manager")) {
|
||||
customElements.define("viewer-window-manager",
|
||||
createCustomElement(ViewerWindowManagerComponent, {injector}));
|
||||
}
|
||||
}
|
||||
|
||||
onDataLoadedChange(dataLoaded: boolean) {
|
||||
if (dataLoaded && !this.viewersCreated) {
|
||||
this.core.createViewers();
|
||||
this.createViewerElements();
|
||||
const dummyTimestamp = this.core.getTimestamps()[1]; //TODO: get timestamp from time scrub
|
||||
this.core.notifyCurrentTimestamp(dummyTimestamp);
|
||||
this.viewersCreated = true;
|
||||
this.dataLoaded = dataLoaded;
|
||||
}
|
||||
}
|
||||
|
||||
createViewerElements() {
|
||||
const viewersDiv = document.querySelector("div#viewers")!;
|
||||
viewersDiv.innerHTML = "";
|
||||
|
||||
this.core.getViews().forEach((view: HTMLElement) => {
|
||||
viewersDiv.appendChild(view);
|
||||
});
|
||||
}
|
||||
|
||||
public notifyCurrentTimestamp() {
|
||||
const dummyTimestamp = new Timestamp(TimestampType.ELAPSED, 1000000n);
|
||||
this.core.notifyCurrentTimestamp(dummyTimestamp);
|
||||
}
|
||||
|
||||
public clearData() {
|
||||
this.dataLoaded = false;
|
||||
this.viewersCreated = false;
|
||||
this.core.clearData();
|
||||
proxyClient.adbData = [];
|
||||
}
|
||||
}
|
||||
@@ -13,27 +13,40 @@ import { MatFormFieldModule } from "@angular/material/form-field";
|
||||
import { MatIconModule } from "@angular/material/icon";
|
||||
import { MatInputModule } from "@angular/material/input";
|
||||
import { MatSelectModule } from "@angular/material/select";
|
||||
import { MatRadioModule } from "@angular/material/radio";
|
||||
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||
import { HttpClientModule } from "@angular/common/http";
|
||||
import { MatSliderModule } from "@angular/material/slider";
|
||||
|
||||
import { AppComponent } from "./app.component";
|
||||
import { AppComponent } from "./components/app.component";
|
||||
import { ViewerWindowManagerComponent } from "viewers/viewer_window_manager/viewer_window_manager.component";
|
||||
import { CollectTracesComponent } from "./collect_traces.component";
|
||||
import { AdbProxyComponent } from "./adb_proxy.component";
|
||||
import { WebAdbComponent } from "./web_adb.component";
|
||||
import { TraceConfigComponent } from "./trace_config.component";
|
||||
import { UploadTracesComponent } from "./upload_traces.component";
|
||||
|
||||
import { ViewerSurfaceFlingerComponent } from "viewers/viewer_surface_flinger/viewer_surface_flinger.component";
|
||||
import { CollectTracesComponent } from "./components/collect_traces.component";
|
||||
import { AdbProxyComponent } from "./components/adb_proxy.component";
|
||||
import { WebAdbComponent } from "./components/web_adb.component";
|
||||
import { TraceConfigComponent } from "./components/trace_config.component";
|
||||
import { UploadTracesComponent } from "./components/upload_traces.component";
|
||||
import { HierarchyComponent } from "viewers/hierarchy.component";
|
||||
import { PropertiesComponent } from "viewers/properties.component";
|
||||
import { RectsComponent } from "viewers/rects.component";
|
||||
import { TraceViewHeaderComponent } from "./components/trace_view_header.component";
|
||||
import { TraceViewComponent } from "./components/trace_view.component";
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
ViewerWindowManagerComponent,
|
||||
ViewerSurfaceFlingerComponent,
|
||||
CollectTracesComponent,
|
||||
UploadTracesComponent,
|
||||
AdbProxyComponent,
|
||||
WebAdbComponent,
|
||||
TraceConfigComponent,
|
||||
HierarchyComponent,
|
||||
PropertiesComponent,
|
||||
RectsComponent,
|
||||
TraceViewHeaderComponent,
|
||||
TraceViewComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
@@ -52,9 +65,10 @@ import { UploadTracesComponent } from "./upload_traces.component";
|
||||
MatInputModule,
|
||||
MatSelectModule,
|
||||
BrowserAnimationsModule,
|
||||
HttpClientModule
|
||||
HttpClientModule,
|
||||
MatSliderModule,
|
||||
MatRadioModule
|
||||
],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule { }
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { AdbProxyComponent } from "./adb_proxy.component";
|
||||
import { proxyClient, ProxyState } from "../trace_collection/proxy_client";
|
||||
import { proxyClient, ProxyState } from "trace_collection/proxy_client";
|
||||
import { MatIconModule } from "@angular/material/icon";
|
||||
import { MatFormFieldModule } from "@angular/material/form-field";
|
||||
import { MatInputModule } from "@angular/material/input";
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Component, Input, Output, EventEmitter } from "@angular/core";
|
||||
import { proxyClient, ProxyClient, ProxyState } from "../trace_collection/proxy_client";
|
||||
import { proxyClient, ProxyClient, ProxyState } from "trace_collection/proxy_client";
|
||||
|
||||
@Component({
|
||||
selector: "adb-proxy",
|
||||
@@ -28,6 +28,8 @@ import { WebAdbComponent } from "./web_adb.component";
|
||||
import { TraceConfigComponent } from "./trace_config.component";
|
||||
|
||||
import { ComponentFixtureAutoDetect } from "@angular/core/testing";
|
||||
import { ViewerSurfaceFlingerComponent } from "viewers/viewer_surface_flinger/viewer_surface_flinger.component";
|
||||
import { MatSliderModule } from "@angular/material/slider";
|
||||
|
||||
|
||||
describe("AppComponent", () => {
|
||||
@@ -45,6 +47,7 @@ describe("AppComponent", () => {
|
||||
MatCardModule,
|
||||
MatButtonModule,
|
||||
MatGridListModule,
|
||||
MatSliderModule
|
||||
],
|
||||
declarations: [
|
||||
AppComponent,
|
||||
@@ -53,6 +56,7 @@ describe("AppComponent", () => {
|
||||
AdbProxyComponent,
|
||||
WebAdbComponent,
|
||||
TraceConfigComponent,
|
||||
ViewerSurfaceFlingerComponent
|
||||
],
|
||||
}).overrideComponent(AppComponent, {
|
||||
set: { changeDetection: ChangeDetectionStrategy.Default }
|
||||
@@ -71,7 +75,7 @@ describe("AppComponent", () => {
|
||||
});
|
||||
|
||||
it("renders the page title", () => {
|
||||
expect(htmlElement.querySelector("#title")?.innerHTML).toContain("Winscope Viewer 2.0");
|
||||
expect(htmlElement.querySelector("#app-title")?.innerHTML).toContain("Winscope Viewer 2.0");
|
||||
});
|
||||
|
||||
it("displays correct elements when no data loaded", async () => {
|
||||
@@ -87,6 +91,6 @@ describe("AppComponent", () => {
|
||||
fixture.detectChanges();
|
||||
expect(htmlElement.querySelector("#collect-traces-card")).toBeFalsy();
|
||||
expect(htmlElement.querySelector("#upload-traces-card")).toBeFalsy();
|
||||
expect(htmlElement.querySelector("#loaded-data-card")).toBeTruthy();
|
||||
expect(htmlElement.querySelector(".viewers.show")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
168
tools/winscope-ng/src/app/components/app.component.ts
Normal file
168
tools/winscope-ng/src/app/components/app.component.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* 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, Injector, Inject, ViewEncapsulation, Input } from "@angular/core";
|
||||
import { createCustomElement } from "@angular/elements";
|
||||
import { TraceCoordinator } from "../trace_coordinator";
|
||||
import { proxyClient, ProxyState } from "trace_collection/proxy_client";
|
||||
import { PersistentStore } from "common/persistent_store";
|
||||
import { ViewerWindowManagerComponent } from "viewers/viewer_window_manager/viewer_window_manager.component";
|
||||
import { ViewerSurfaceFlingerComponent } from "viewers/viewer_surface_flinger/viewer_surface_flinger.component";
|
||||
import { TraceViewComponent } from "./trace_view.component";
|
||||
import { Timestamp } from "common/trace/timestamp";
|
||||
import { MatSliderChange } from "@angular/material/slider";
|
||||
import { Viewer } from "viewers/viewer";
|
||||
|
||||
@Component({
|
||||
selector: "app-root",
|
||||
template: `
|
||||
<div id="app-title">
|
||||
<span>Winscope Viewer 2.0</span>
|
||||
<button mat-raised-button *ngIf="dataLoaded" (click)="clearData()">Back to Home</button>
|
||||
<button mat-raised-button *ngIf="dataLoaded" (click)="toggleTimestamp()">Start/End Timestamp</button>
|
||||
<mat-slider
|
||||
*ngIf="dataLoaded"
|
||||
step="1"
|
||||
min="0"
|
||||
[max]="this.allTimestamps.length-1"
|
||||
aria-label="units"
|
||||
[value]="currentTimestampIndex"
|
||||
(input)="updateCurrentTimestamp($event)"
|
||||
class="time-slider"
|
||||
></mat-slider>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!dataLoaded" fxLayout="row wrap" fxLayoutGap="10px grid" class="card-grid">
|
||||
<mat-card class="homepage-card" id="collect-traces-card">
|
||||
<collect-traces [(traceCoordinator)]="traceCoordinator" (dataLoadedChange)="onDataLoadedChange($event)"[store]="store"></collect-traces>
|
||||
</mat-card>
|
||||
<mat-card class="homepage-card" id="upload-traces-card">
|
||||
<upload-traces [(traceCoordinator)]="traceCoordinator" (dataLoadedChange)="onDataLoadedChange($event)"></upload-traces>
|
||||
</mat-card>
|
||||
</div>
|
||||
|
||||
<div id="timescrub">
|
||||
</div>
|
||||
|
||||
<div id="timestamps">
|
||||
</div>
|
||||
|
||||
<div id="viewers" [class]="showViewers()">
|
||||
</div>
|
||||
`,
|
||||
styles: [".time-slider {width: 100%}"],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class AppComponent {
|
||||
title = "winscope-ng";
|
||||
traceCoordinator: TraceCoordinator;
|
||||
states = ProxyState;
|
||||
store: PersistentStore = new PersistentStore();
|
||||
@Input() dataLoaded = false;
|
||||
viewersCreated = false;
|
||||
currentTimestamp?: Timestamp;
|
||||
currentTimestampIndex = 0;
|
||||
allTimestamps: Timestamp[] = [];
|
||||
|
||||
constructor(
|
||||
@Inject(Injector) injector: Injector
|
||||
) {
|
||||
this.traceCoordinator = new TraceCoordinator();
|
||||
if (!customElements.get("viewer-window-manager")) {
|
||||
customElements.define("viewer-window-manager",
|
||||
createCustomElement(ViewerWindowManagerComponent, {injector}));
|
||||
}
|
||||
if (!customElements.get("viewer-surface-flinger")) {
|
||||
customElements.define("viewer-surface-flinger",
|
||||
createCustomElement(ViewerSurfaceFlingerComponent, {injector}));
|
||||
}
|
||||
if (!customElements.get("trace-view")) {
|
||||
customElements.define("trace-view",
|
||||
createCustomElement(TraceViewComponent, {injector}));
|
||||
}
|
||||
}
|
||||
|
||||
onDataLoadedChange(dataLoaded: boolean) {
|
||||
if (dataLoaded && !this.viewersCreated) {
|
||||
this.allTimestamps = this.traceCoordinator.getTimestamps();
|
||||
this.traceCoordinator.createViewers();
|
||||
this.createViewerElements();
|
||||
this.currentTimestampIndex = 0;
|
||||
this.notifyCurrentTimestamp();
|
||||
this.viewersCreated = true;
|
||||
this.dataLoaded = dataLoaded;
|
||||
}
|
||||
}
|
||||
|
||||
createViewerElements() {
|
||||
const viewersDiv = document.querySelector("div#viewers")!;
|
||||
viewersDiv.innerHTML = "";
|
||||
|
||||
let cardCounter = 0;
|
||||
this.traceCoordinator.getViewers().forEach((viewer: Viewer) => {
|
||||
const traceView = document.createElement("trace-view");
|
||||
(traceView as any).title = viewer.getTitle();
|
||||
(traceView as any).dependencies = viewer.getDependencies();
|
||||
(traceView as any).showTrace = true;
|
||||
traceView.addEventListener("saveTraces", ($event: any) => {
|
||||
this.traceCoordinator.saveTraces($event.detail);
|
||||
});
|
||||
viewersDiv.appendChild(traceView);
|
||||
|
||||
const traceCard = traceView.querySelector(".trace-card")!;
|
||||
traceCard.id = `card-${cardCounter}`;
|
||||
(traceView as any).cardId = cardCounter;
|
||||
cardCounter++;
|
||||
|
||||
const traceCardContent = traceCard.querySelector(".trace-card-content")!;
|
||||
const view = viewer.getView();
|
||||
(view as any).showTrace = (traceView as any).showTrace;
|
||||
traceCardContent.appendChild(view);
|
||||
});
|
||||
}
|
||||
|
||||
updateCurrentTimestamp(event: MatSliderChange) {
|
||||
if (event.value) {
|
||||
this.currentTimestampIndex = event.value;
|
||||
this.notifyCurrentTimestamp();
|
||||
}
|
||||
}
|
||||
|
||||
public notifyCurrentTimestamp() {
|
||||
this.currentTimestamp = this.allTimestamps[this.currentTimestampIndex];
|
||||
this.traceCoordinator.notifyCurrentTimestamp(this.currentTimestamp);
|
||||
}
|
||||
|
||||
public toggleTimestamp() {
|
||||
if (this.currentTimestampIndex===0) {
|
||||
this.currentTimestampIndex = this.allTimestamps.length-1;
|
||||
} else {
|
||||
this.currentTimestampIndex = 0;
|
||||
}
|
||||
this.notifyCurrentTimestamp();
|
||||
}
|
||||
|
||||
public clearData() {
|
||||
this.dataLoaded = false;
|
||||
this.viewersCreated = false;
|
||||
this.traceCoordinator.clearData();
|
||||
proxyClient.adbData = [];
|
||||
}
|
||||
|
||||
public showViewers() {
|
||||
const isShown = this.dataLoaded ? "show" : "hide";
|
||||
return ["viewers", isShown];
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import { MatListModule } from "@angular/material/list";
|
||||
import { MatButtonModule } from "@angular/material/button";
|
||||
import { MatProgressBarModule } from "@angular/material/progress-bar";
|
||||
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||
import { NO_ERRORS_SCHEMA } from "@angular/core";
|
||||
|
||||
describe("CollectTracesComponent", () => {
|
||||
let fixture: ComponentFixture<CollectTracesComponent>;
|
||||
@@ -36,7 +37,6 @@ describe("CollectTracesComponent", () => {
|
||||
MatIconModule,
|
||||
MatCardModule,
|
||||
MatListModule,
|
||||
MatIconModule,
|
||||
MatButtonModule,
|
||||
MatProgressBarModule,
|
||||
BrowserAnimationsModule
|
||||
@@ -47,6 +47,7 @@ describe("CollectTracesComponent", () => {
|
||||
WebAdbComponent,
|
||||
TraceConfigComponent,
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
fixture = TestBed.createComponent(CollectTracesComponent);
|
||||
component = fixture.componentInstance;
|
||||
@@ -13,14 +13,14 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Component, Input, OnInit, Output, EventEmitter, NgZone, Inject } from "@angular/core";
|
||||
import { Component, Inject, Input, Output, EventEmitter, OnInit, OnDestroy } from "@angular/core";
|
||||
import { ProxyConnection } from "trace_collection/proxy_connection";
|
||||
import { Connection } from "trace_collection/connection";
|
||||
import { setTraces } from "trace_collection/set_traces";
|
||||
import { ProxyState } from "../trace_collection/proxy_client";
|
||||
import { traceConfigurations, configMap, SelectionConfiguration, EnableConfiguration } from "../trace_collection/trace_collection_utils";
|
||||
import { Core } from "app/core";
|
||||
import { PersistentStore } from "../common/persistent_store";
|
||||
import { ProxyState } from "trace_collection/proxy_client";
|
||||
import { traceConfigurations, configMap, SelectionConfiguration, EnableConfiguration } from "trace_collection/trace_collection_utils";
|
||||
import { TraceCoordinator } from "app/trace_coordinator";
|
||||
import { PersistentStore } from "common/persistent_store";
|
||||
|
||||
|
||||
@Component({
|
||||
@@ -33,9 +33,9 @@ import { PersistentStore } from "../common/persistent_store";
|
||||
|
||||
<div class="set-up-adb" *ngIf="!connect.adbSuccess()">
|
||||
<button id="proxy-tab" mat-raised-button [ngClass]="tabClass(true)" (click)="displayAdbProxyTab()">ADB Proxy</button>
|
||||
<button id="web-tab" mat-raised-button [ngClass]="tabClass(false)" (click)="displayWebAdbTab()">Web ADB</button>
|
||||
<!-- <button id="web-tab" mat-raised-button [ngClass]="tabClass(false)" (click)="displayWebAdbTab()">Web ADB</button> -->
|
||||
<adb-proxy *ngIf="isAdbProxy" [(proxy)]="connect.proxy!" (addKey)="onAddKey($event)"></adb-proxy>
|
||||
<web-adb *ngIf="!isAdbProxy"></web-adb>
|
||||
<!-- <web-adb *ngIf="!isAdbProxy"></web-adb> TODO: fix web adb workflow -->
|
||||
</div>
|
||||
|
||||
<div id="devices-connecting" *ngIf="connect.isDevicesState()">
|
||||
@@ -110,30 +110,26 @@ import { PersistentStore } from "../common/persistent_store";
|
||||
|
||||
</mat-card-content>
|
||||
`,
|
||||
styles: [".device-choice {cursor: pointer}"]
|
||||
styles: [
|
||||
".device-choice {cursor: pointer}",
|
||||
".mat-checkbox .mat-checkbox-frame {transform: scale(0.7); font-size: 10;}",
|
||||
".mat-checkbox-checked .mat-checkbox-background {transform: scale(0.7); font-size: 10;}"
|
||||
]
|
||||
})
|
||||
export class CollectTracesComponent implements OnInit {
|
||||
export class CollectTracesComponent implements OnInit, OnDestroy {
|
||||
objectKeys = Object.keys;
|
||||
isAdbProxy = true;
|
||||
traceConfigurations = traceConfigurations;
|
||||
connect: Connection = new ProxyConnection();
|
||||
setTraces = setTraces;
|
||||
|
||||
@Input()
|
||||
store: PersistentStore = new PersistentStore();
|
||||
|
||||
@Input()
|
||||
core?: Core;
|
||||
|
||||
@Output()
|
||||
coreChange = new EventEmitter<Core>();
|
||||
|
||||
dataLoaded = false;
|
||||
|
||||
@Output()
|
||||
dataLoadedChange = new EventEmitter<boolean>();
|
||||
@Input() store!: PersistentStore;
|
||||
@Input() traceCoordinator!: TraceCoordinator;
|
||||
|
||||
ngOnInit(): void {
|
||||
@Output() dataLoadedChange = new EventEmitter<boolean>();
|
||||
|
||||
ngOnInit() {
|
||||
if (this.isAdbProxy) {
|
||||
this.connect = new ProxyConnection();
|
||||
} else {
|
||||
@@ -142,8 +138,6 @@ export class CollectTracesComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
constructor(@Inject(NgZone) private ngZone: NgZone) {}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.connect.proxy?.removeOnProxyChange(this.onProxyChange);
|
||||
}
|
||||
@@ -258,7 +252,7 @@ export class CollectTracesComponent implements OnInit {
|
||||
if (!setTraces.dumpError) {
|
||||
await this.loadFiles();
|
||||
} else {
|
||||
this.core?.clearData();
|
||||
this.traceCoordinator.clearData();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,13 +267,12 @@ export class CollectTracesComponent implements OnInit {
|
||||
|
||||
public async loadFiles() {
|
||||
console.log("loading files", this.connect.adbData());
|
||||
this.core?.clearData();
|
||||
await this.core?.addTraces(this.connect.adbData());
|
||||
this.ngZone.run(() => {
|
||||
this.dataLoaded = true;
|
||||
this.dataLoadedChange.emit(this.dataLoaded);
|
||||
console.log("finished loading data!");
|
||||
});
|
||||
this.traceCoordinator.clearData();
|
||||
|
||||
await this.traceCoordinator.addTraces(this.connect.adbData());
|
||||
this.dataLoaded = true;
|
||||
this.dataLoadedChange.emit(this.dataLoaded);
|
||||
console.log("finished loading data!");
|
||||
}
|
||||
|
||||
public tabClass(adbTab: boolean) {
|
||||
@@ -104,7 +104,7 @@ describe("TraceConfigComponent", () => {
|
||||
expect(adv?.innerHTML).toContain("tracing level");
|
||||
});
|
||||
|
||||
it("check that changing enable config causes box to change", async () => { spyOn(component, "changeTraceCollectionConfig");
|
||||
it("check that changing enable config causes box to change", async () => {
|
||||
component.trace.config!.enableConfigs[0].enabled = false;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Component, Input } from "@angular/core";
|
||||
import { EnableConfiguration, SelectionConfiguration, TraceConfiguration } from "../trace_collection/trace_collection_utils";
|
||||
import { EnableConfiguration, SelectionConfiguration, TraceConfiguration } from "trace_collection/trace_collection_utils";
|
||||
|
||||
@Component({
|
||||
selector: "trace-config",
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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 { CommonModule } from "@angular/common";
|
||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { TraceViewComponent } from "./trace_view.component";
|
||||
import { MatCardModule } from "@angular/material/card";
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from "@angular/core";
|
||||
import { TraceType } from "common/trace/trace_type";
|
||||
|
||||
describe("TraceViewComponent", () => {
|
||||
let fixture: ComponentFixture<TraceViewComponent>;
|
||||
let component: TraceViewComponent;
|
||||
let htmlElement: HTMLElement;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatCardModule
|
||||
],
|
||||
declarations: [TraceViewComponent],
|
||||
schemas: [NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA]
|
||||
}).compileComponents();
|
||||
fixture = TestBed.createComponent(TraceViewComponent);
|
||||
component = fixture.componentInstance;
|
||||
htmlElement = fixture.nativeElement;
|
||||
component.dependencies = [TraceType.SURFACE_FLINGER, TraceType.WINDOW_MANAGER];
|
||||
component.showTrace = true;
|
||||
});
|
||||
|
||||
it("can be created", () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it("check that mat card title and contents are displayed", () => {
|
||||
fixture.detectChanges();
|
||||
const title = htmlElement.querySelector(".trace-card-title");
|
||||
expect(title).toBeTruthy();
|
||||
const header = title?.querySelector("trace-view-header");
|
||||
expect(header).toBeTruthy();
|
||||
});
|
||||
|
||||
it("check that card content is created", () => {
|
||||
fixture.detectChanges();
|
||||
const content = htmlElement.querySelector(".trace-card-content") as HTMLElement;
|
||||
expect(content).toBeTruthy();
|
||||
});
|
||||
});
|
||||
57
tools/winscope-ng/src/app/components/trace_view.component.ts
Normal file
57
tools/winscope-ng/src/app/components/trace_view.component.ts
Normal file
@@ -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,
|
||||
Output,
|
||||
EventEmitter
|
||||
} from "@angular/core";
|
||||
import { TRACE_INFO } from "../trace_info";
|
||||
import { TraceType } from "common/trace/trace_type";
|
||||
|
||||
@Component({
|
||||
selector: "trace-view",
|
||||
template: `
|
||||
<mat-card class="trace-card">
|
||||
<mat-card-header>
|
||||
<mat-card-title class="trace-card-title" *ngIf="dependencies">
|
||||
<trace-view-header
|
||||
[title]="title"
|
||||
[(showTrace)]="showTrace"
|
||||
[dependencies]="dependencies"
|
||||
[cardId]="cardId"
|
||||
(saveTraceChange)="onSaveTraces($event)"
|
||||
></trace-view-header>
|
||||
</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content class="trace-card-content" [hidden]="!showTrace">
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
`,
|
||||
})
|
||||
export class TraceViewComponent {
|
||||
@Input() title!: string;
|
||||
@Input() dependencies!: TraceType[];
|
||||
@Input() showTrace = true;
|
||||
@Input() cardId = 0;
|
||||
@Output() saveTraces = new EventEmitter<TraceType[]>();
|
||||
|
||||
TRACE_INFO = TRACE_INFO;
|
||||
|
||||
onSaveTraces(dependencies: TraceType[]) {
|
||||
this.saveTraces.emit(dependencies);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* 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 { CommonModule } from "@angular/common";
|
||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { TraceViewHeaderComponent } from "./trace_view_header.component";
|
||||
import { MatIconModule } from "@angular/material/icon";
|
||||
import { MatButtonModule } from "@angular/material/button";
|
||||
import { TraceType } from "common/trace/trace_type";
|
||||
|
||||
describe("TraceViewHeaderComponent", () => {
|
||||
let fixture: ComponentFixture<TraceViewHeaderComponent>;
|
||||
let component: TraceViewHeaderComponent;
|
||||
let htmlElement: HTMLElement;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatIconModule,
|
||||
MatButtonModule
|
||||
],
|
||||
declarations: [TraceViewHeaderComponent]
|
||||
}).compileComponents();
|
||||
fixture = TestBed.createComponent(TraceViewHeaderComponent);
|
||||
component = fixture.componentInstance;
|
||||
htmlElement = fixture.nativeElement;
|
||||
component.dependencies = [TraceType.SURFACE_FLINGER, TraceType.WINDOW_MANAGER];
|
||||
});
|
||||
|
||||
it("can be created", () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it("check that toggle button is displayed, expanded on default", () => {
|
||||
component.showTrace = true;
|
||||
fixture.detectChanges();
|
||||
const toggleButton = htmlElement.querySelector("#toggle-btn");
|
||||
expect(toggleButton).toBeTruthy();
|
||||
const chevronIcon = toggleButton?.querySelector("mat-icon");
|
||||
expect(chevronIcon).toBeTruthy;
|
||||
expect(chevronIcon?.innerHTML).toContain("expand_more");
|
||||
});
|
||||
|
||||
it("check that toggle button icon is a right chevron when minimised ", () => {
|
||||
component.showTrace = false;
|
||||
fixture.detectChanges();
|
||||
const toggleButton = htmlElement.querySelector("#toggle-btn");
|
||||
const chevronIcon = toggleButton?.querySelector("mat-icon");
|
||||
expect(chevronIcon?.innerHTML).toContain("chevron_right");
|
||||
});
|
||||
|
||||
it("check that clicking toggle button causes view to minimise", async () => {
|
||||
component.showTrace = true;
|
||||
fixture.detectChanges();
|
||||
spyOn(component, "toggleView").and.callThrough();
|
||||
const button: HTMLButtonElement | null = htmlElement.querySelector("#toggle-btn");
|
||||
expect(button).toBeInstanceOf(HTMLButtonElement);
|
||||
button?.dispatchEvent(new Event("click"));
|
||||
await fixture.whenStable();
|
||||
expect(component.toggleView).toHaveBeenCalled();
|
||||
fixture.detectChanges();
|
||||
expect (htmlElement.querySelector("#toggle-btn")?.querySelector("mat-icon")?.innerHTML).toContain("chevron_right");
|
||||
});
|
||||
|
||||
it("check that dependency icons show", () => {
|
||||
fixture.detectChanges();
|
||||
const dependencyIcons = htmlElement.querySelectorAll("#dep-icon");
|
||||
expect(dependencyIcons).toBeTruthy();
|
||||
expect(dependencyIcons.length).toBe(2);
|
||||
});
|
||||
|
||||
it("check that title is displayed", () => {
|
||||
component.title = "Surface Flinger, Window Manager";
|
||||
fixture.detectChanges();
|
||||
const title = htmlElement.querySelector(".trace-card-title-text");
|
||||
expect(title).toBeTruthy();
|
||||
expect(title?.innerHTML).toContain("Surface Flinger");
|
||||
expect(title?.innerHTML).toContain("Window Manager");
|
||||
});
|
||||
|
||||
it("check that save button is displayed", () => {
|
||||
fixture.detectChanges();
|
||||
const saveButton = htmlElement.querySelectorAll("#save-btn");
|
||||
expect(saveButton).toBeTruthy();
|
||||
});
|
||||
|
||||
it("check that clicking save button emits", async () => {
|
||||
spyOn(component, "saveTraces").and.callThrough();
|
||||
spyOn(component.saveTraceChange, "emit");
|
||||
const button: HTMLButtonElement | null = htmlElement.querySelector("#save-btn");
|
||||
expect(button).toBeInstanceOf(HTMLButtonElement);
|
||||
button?.dispatchEvent(new Event("click"));
|
||||
await fixture.whenStable();
|
||||
expect(component.saveTraces).toHaveBeenCalled();
|
||||
expect(component.saveTraceChange.emit).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("check that screenshot button is displayed", () => {
|
||||
fixture.detectChanges();
|
||||
const screenshotButton = htmlElement.querySelectorAll("#screenshot-btn");
|
||||
expect(screenshotButton).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -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 {
|
||||
Component,
|
||||
Input,
|
||||
Output,
|
||||
EventEmitter
|
||||
} from "@angular/core";
|
||||
import { TRACE_INFO } from "../trace_info";
|
||||
import { TraceType } from "common/trace/trace_type";
|
||||
import html2canvas from "html2canvas";
|
||||
|
||||
@Component({
|
||||
selector: "trace-view-header",
|
||||
template: `
|
||||
<button class="icon-button" id="toggle-btn" (click)="toggleView()">
|
||||
<mat-icon aria-hidden="true">
|
||||
{{ showTrace ? "expand_more" : "chevron_right" }}
|
||||
</mat-icon>
|
||||
</button>
|
||||
<mat-icon id="dep-icon" *ngFor="let dep of dependencies" aria-hidden="true" class="icon-button">{{TRACE_INFO[dep].icon}}</mat-icon>
|
||||
<span class="trace-card-title-text">
|
||||
{{title}}
|
||||
</span>
|
||||
<button id="save-btn" class="icon-button" (click)="saveTraces()">
|
||||
<mat-icon aria-hidden="true">save_alt</mat-icon>
|
||||
</button>
|
||||
<button id="screenshot-btn" (click)="takeScreenshot()" class="icon-button">
|
||||
<mat-icon aria-hidden="true">camera_alt</mat-icon>
|
||||
</button>
|
||||
`,
|
||||
styles: [
|
||||
".trace-card-title {font: inherit; display: inline-block; vertical-align: middle;}",
|
||||
]
|
||||
})
|
||||
export class TraceViewHeaderComponent {
|
||||
@Input() title?: string;
|
||||
@Input() dependencies?: TraceType[];
|
||||
@Input() showTrace = true;
|
||||
@Input() cardId!: number ;
|
||||
|
||||
@Output() showTraceChange = new EventEmitter<boolean>();
|
||||
@Output() saveTraceChange = new EventEmitter<TraceType[]>();
|
||||
|
||||
TRACE_INFO = TRACE_INFO;
|
||||
|
||||
toggleView() {
|
||||
this.showTrace = !this.showTrace;
|
||||
this.showTraceChange.emit(this.showTrace);
|
||||
}
|
||||
|
||||
public saveTraces() {
|
||||
this.saveTraceChange.emit(this.dependencies);
|
||||
}
|
||||
|
||||
public takeScreenshot() {
|
||||
const el = document.querySelector(`#card-${this.cardId}`);
|
||||
if (el) {
|
||||
html2canvas((el as HTMLElement)).then((canvas) => {
|
||||
const uri = canvas.toDataURL();
|
||||
const filename = "Winscope-Screenshot.png";
|
||||
const link = document.createElement("a");
|
||||
if (typeof link.download === "string") {
|
||||
link.href = uri;
|
||||
link.download = filename;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
} else {
|
||||
window.open(uri);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ import {ComponentFixture, TestBed} from "@angular/core/testing";
|
||||
import {UploadTracesComponent} from "./upload_traces.component";
|
||||
import { MatCardModule } from "@angular/material/card";
|
||||
|
||||
describe("CollectTracesComponent", () => {
|
||||
describe("UploadTracesComponent", () => {
|
||||
let fixture: ComponentFixture<UploadTracesComponent>;
|
||||
let component: UploadTracesComponent;
|
||||
let htmlElement: HTMLElement;
|
||||
@@ -13,9 +13,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Component, Input, Inject, Output, EventEmitter, NgZone } from "@angular/core";
|
||||
import { Core } from "app/core";
|
||||
import { TRACE_ICONS } from "app/trace_icons";
|
||||
import { Component, Input, Output, EventEmitter, Inject, NgZone } from "@angular/core";
|
||||
import { TraceCoordinator } from "app/trace_coordinator";
|
||||
import { TRACE_INFO } from "app/trace_info";
|
||||
import { LoadedTrace } from "app/loaded_trace";
|
||||
|
||||
@Component({
|
||||
@@ -57,45 +57,43 @@ import { LoadedTrace } from "app/loaded_trace";
|
||||
*ngIf="this.loadedTraces.length > 0"
|
||||
>
|
||||
<mat-list-item *ngFor="let trace of loadedTraces">
|
||||
<mat-icon>{{TRACE_ICONS[trace.type]}}</mat-icon>
|
||||
<mat-icon>{{TRACE_INFO[trace.type].icon}}</mat-icon>
|
||||
<span>{{trace.name}} ({{trace.type}})
|
||||
</span>
|
||||
<button
|
||||
(click)="onRemoveTrace(trace)"
|
||||
><mat-icon class="file-icon">close</mat-icon>
|
||||
class="icon-button"
|
||||
><mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
</mat-card-content>
|
||||
`,
|
||||
styles: [".drop-info{font-weight: normal;}"]
|
||||
styles: [
|
||||
".drop-info{font-weight: normal; pointer-events: none;}",
|
||||
]
|
||||
})
|
||||
export class UploadTracesComponent {
|
||||
@Input()
|
||||
core?: Core;
|
||||
|
||||
@Output()
|
||||
coreChange = new EventEmitter<Core>();
|
||||
@Input() traceCoordinator!: TraceCoordinator;
|
||||
|
||||
dataLoaded = false;
|
||||
|
||||
@Output()
|
||||
dataLoadedChange = new EventEmitter<boolean>();
|
||||
|
||||
loadedTraces: LoadedTrace[] = [];
|
||||
TRACE_ICONS = TRACE_ICONS;
|
||||
@Output() dataLoadedChange = new EventEmitter<boolean>();
|
||||
|
||||
constructor(@Inject(NgZone) private ngZone: NgZone) {}
|
||||
|
||||
loadedTraces: LoadedTrace[] = [];
|
||||
TRACE_INFO = TRACE_INFO;
|
||||
|
||||
public async onInputFile(event: Event) {
|
||||
const files = this.getInputFiles(event);
|
||||
await this.processFiles(files);
|
||||
}
|
||||
|
||||
public async processFiles(files: File[]) {
|
||||
await this.core?.addTraces(files);
|
||||
await this.traceCoordinator.addTraces(files);
|
||||
this.ngZone.run(() => {
|
||||
if (this.core) this.loadedTraces = this.core.getLoadedTraces();
|
||||
this.loadedTraces = this.traceCoordinator.getLoadedTraces();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -114,7 +112,10 @@ export class UploadTracesComponent {
|
||||
}
|
||||
|
||||
public onClearData() {
|
||||
this.core?.clearData();
|
||||
this.traceCoordinator.clearData();
|
||||
this.dataLoaded = false;
|
||||
this.loadedTraces = [];
|
||||
this.dataLoadedChange.emit(this.dataLoaded);
|
||||
}
|
||||
|
||||
public onFileDragIn(e: DragEvent) {
|
||||
@@ -136,7 +137,7 @@ export class UploadTracesComponent {
|
||||
}
|
||||
|
||||
public onRemoveTrace(trace: LoadedTrace) {
|
||||
this.core?.removeTrace(trace.type);
|
||||
this.traceCoordinator.removeTrace(trace.type);
|
||||
this.loadedTraces = this.loadedTraces.filter(loaded => loaded.type !== trace.type);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TraceType } from "../common/trace/trace_type";
|
||||
import { TraceType } from "common/trace/trace_type";
|
||||
|
||||
export interface LoadedTrace {
|
||||
name: string;
|
||||
|
||||
@@ -21,8 +21,9 @@ import { setTraces } from "trace_collection/set_traces";
|
||||
import { Viewer } from "viewers/viewer";
|
||||
import { ViewerFactory } from "viewers/viewer_factory";
|
||||
import { LoadedTrace } from "app/loaded_trace";
|
||||
import { TRACE_INFO } from "./trace_info";
|
||||
|
||||
class Core {
|
||||
class TraceCoordinator {
|
||||
private parsers: Parser[];
|
||||
private viewers: Viewer[];
|
||||
|
||||
@@ -37,7 +38,6 @@ class Core {
|
||||
console.log("created parsers: ", this.parsers);
|
||||
}
|
||||
|
||||
|
||||
removeTrace(type: TraceType) {
|
||||
this.parsers = this.parsers.filter(parser => parser.getTraceType() !== type);
|
||||
}
|
||||
@@ -62,10 +62,19 @@ class Core {
|
||||
return this.viewers.map(viewer => viewer.getView());
|
||||
}
|
||||
|
||||
getViewers(): Viewer[] {
|
||||
return this.viewers;
|
||||
}
|
||||
|
||||
loadedTraceTypes(): TraceType[] {
|
||||
return this.parsers.map(parser => parser.getTraceType());
|
||||
}
|
||||
|
||||
findParser(fileType: TraceType): Parser | null {
|
||||
const parser = this.parsers.find(parser => parser.getTraceType() === fileType);
|
||||
return parser ?? null;
|
||||
}
|
||||
|
||||
getTimestamps(): Timestamp[] {
|
||||
for (const type of [TimestampType.REAL, TimestampType.ELAPSED]) {
|
||||
const mergedTimestamps: Timestamp[] = [];
|
||||
@@ -94,8 +103,9 @@ class Core {
|
||||
const traceEntries: Map<TraceType, any> = new Map<TraceType, any>();
|
||||
|
||||
this.parsers.forEach(parser => {
|
||||
const entry = parser.getTraceEntry(timestamp);
|
||||
if (entry != undefined) {
|
||||
const targetTimestamp = timestamp;
|
||||
const entry = parser.getTraceEntry(targetTimestamp);
|
||||
if (entry !== undefined) {
|
||||
traceEntries.set(parser.getTraceType(), entry);
|
||||
}
|
||||
});
|
||||
@@ -106,10 +116,31 @@ class Core {
|
||||
}
|
||||
|
||||
clearData() {
|
||||
this.getViews().forEach(view => view.remove());
|
||||
this.parsers = [];
|
||||
this.viewers = [];
|
||||
setTraces.dataReady = false;
|
||||
}
|
||||
|
||||
saveTraces(traceTypes: TraceType[]) {
|
||||
const blobs: Blob[] = [];
|
||||
traceTypes.forEach(type => {
|
||||
const trace = this.findParser(type)?.getTrace();
|
||||
if (trace) {
|
||||
blobs.push(trace);
|
||||
}
|
||||
});
|
||||
blobs.forEach((blob, idx) => {
|
||||
const a = document.createElement("a");
|
||||
document.body.appendChild(a);
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
a.href = url;
|
||||
a.download = (blob as any).name ?? `${TRACE_INFO[traceTypes[idx]].name}.pb`;
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
document.body.removeChild(a);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export { Core };
|
||||
export { TraceCoordinator };
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TraceType } from "../common/trace/trace_type";
|
||||
import { TraceType } from "common/trace/trace_type";
|
||||
|
||||
const WINDOW_MANAGER_ICON = "view_compact";
|
||||
const SURFACE_FLINGER_ICON = "filter_none";
|
||||
|
||||
88
tools/winscope-ng/src/app/trace_info.ts
Normal file
88
tools/winscope-ng/src/app/trace_info.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { TraceType } from "common/trace/trace_type";
|
||||
|
||||
const WINDOW_MANAGER_ICON = "view_compact";
|
||||
const SURFACE_FLINGER_ICON = "filter_none";
|
||||
const SCREEN_RECORDING_ICON = "videocam";
|
||||
const TRANSACTION_ICON = "timeline";
|
||||
const WAYLAND_ICON = "filter_none";
|
||||
const PROTO_LOG_ICON = "notes";
|
||||
const SYSTEM_UI_ICON = "filter_none";
|
||||
const LAUNCHER_ICON = "filter_none";
|
||||
const IME_ICON = "keyboard";
|
||||
const ACCESSIBILITY_ICON = "accessibility";
|
||||
const TAG_ICON = "details";
|
||||
const TRACE_ERROR_ICON = "warning";
|
||||
|
||||
type traceInfoMap = {
|
||||
[key: number]: {
|
||||
name: string,
|
||||
icon: string
|
||||
};
|
||||
}
|
||||
|
||||
export const TRACE_INFO: traceInfoMap = {
|
||||
[TraceType.ACCESSIBILITY]: {
|
||||
name: "Accessibility",
|
||||
icon: ACCESSIBILITY_ICON
|
||||
},
|
||||
[TraceType.WINDOW_MANAGER]: {
|
||||
name: "Window Manager",
|
||||
icon: WINDOW_MANAGER_ICON
|
||||
},
|
||||
[TraceType.SURFACE_FLINGER]: {
|
||||
name: "Surface Flinger",
|
||||
icon: SURFACE_FLINGER_ICON
|
||||
},
|
||||
[TraceType.SCREEN_RECORDING]: {
|
||||
name: "Screen Recording",
|
||||
icon: SCREEN_RECORDING_ICON
|
||||
},
|
||||
[TraceType.TRANSACTIONS]: {
|
||||
name: "Transactions",
|
||||
icon: TRANSACTION_ICON
|
||||
},
|
||||
[TraceType.TRANSACTIONS_LEGACY]: {
|
||||
name: "Transactions Legacy",
|
||||
icon: TRANSACTION_ICON
|
||||
},
|
||||
[TraceType.WAYLAND]: {
|
||||
name: "Wayland",
|
||||
icon: WAYLAND_ICON
|
||||
},
|
||||
[TraceType.WAYLAND_DUMP]: {
|
||||
name: "Wayland Dump",
|
||||
icon: WAYLAND_ICON
|
||||
},
|
||||
[TraceType.PROTO_LOG]: {
|
||||
name: "Proto Log",
|
||||
icon: PROTO_LOG_ICON
|
||||
},
|
||||
[TraceType.SYSTEM_UI]: {
|
||||
name: "System UI",
|
||||
icon: SYSTEM_UI_ICON
|
||||
},
|
||||
[TraceType.LAUNCHER]: {
|
||||
name: "Launcher",
|
||||
icon: LAUNCHER_ICON
|
||||
},
|
||||
[TraceType.INPUT_METHOD_CLIENTS]: {
|
||||
name: "IME Clients",
|
||||
icon: IME_ICON
|
||||
},
|
||||
[TraceType.INPUT_METHOD_SERVICE]: {
|
||||
name: "IME Service",
|
||||
icon: IME_ICON
|
||||
},
|
||||
[TraceType.INPUT_METHOD_MANAGER_SERVICE]: {
|
||||
name: "IME Manager Service",
|
||||
icon: IME_ICON
|
||||
},
|
||||
[TraceType.TAG]: {
|
||||
name: "Tag",
|
||||
icon: TAG_ICON
|
||||
},
|
||||
[TraceType.ERROR]: {
|
||||
name: "Error",
|
||||
icon: TRACE_ERROR_ICON
|
||||
},
|
||||
};
|
||||
@@ -16,11 +16,18 @@
|
||||
@import "~@angular/material/prebuilt-themes/indigo-pink.css";
|
||||
@import 'https://fonts.googleapis.com/icon?family=Material+Icons';
|
||||
|
||||
#app-title {
|
||||
font-weight: bold;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
color:rgb(194, 65, 108);
|
||||
font-size: 30;
|
||||
}
|
||||
|
||||
#title {
|
||||
font-weight: bold;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
color:rgb(194, 65, 108);
|
||||
font-size: 20;
|
||||
font-size: 24;
|
||||
}
|
||||
|
||||
button {
|
||||
@@ -29,20 +36,53 @@ button {
|
||||
|
||||
.homepage-card {
|
||||
border: 1px solid rgb(129, 129, 129);
|
||||
width: 45rem;
|
||||
height: 30rem;
|
||||
width: 50%;
|
||||
height: 35rem;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
mat-checkbox {
|
||||
margin-left: 10px;
|
||||
.trace-card {
|
||||
border: 1px solid rgb(129, 129, 129);
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
mat-form-field {
|
||||
margin: 10px;
|
||||
height: 5px;
|
||||
.card-grid {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
mat-checkbox {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.mat-checkbox .mat-checkbox-frame {
|
||||
transform: scale(0.7);
|
||||
}
|
||||
|
||||
.mat-checkbox-checked .mat-checkbox-background {
|
||||
transform: scale(0.7);
|
||||
}
|
||||
|
||||
.mat-checkbox-indeterminate .mat-checkbox-background {
|
||||
transform: scale(0.7);
|
||||
}
|
||||
|
||||
.mat-radio-button, .mat-radio-button-frame {
|
||||
transform: scale(0.8);
|
||||
}
|
||||
|
||||
.mat-form-field {
|
||||
transform: scale(0.85);
|
||||
margin: 2px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
mat-icon {
|
||||
@@ -75,12 +115,12 @@ button.mat-raised-button {
|
||||
}
|
||||
|
||||
.drop-box {
|
||||
outline: 2px dashed rgb(194, 65, 108); /* the dash box */
|
||||
outline: 2px dashed rgb(194, 65, 108);
|
||||
outline-offset: -10px;
|
||||
background: white;
|
||||
color: rgb(194, 65, 108);
|
||||
padding: 10px 10px 10px 10px;
|
||||
min-height: 200px; /* minimum height */
|
||||
min-height: 200px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
@@ -104,4 +144,19 @@ button.mat-raised-button {
|
||||
background-color: rgb(194, 65, 108);
|
||||
border-radius: 21.5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.viewers.hide {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.icon-button {
|
||||
background: none;
|
||||
border: none;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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 {browser, element, by, ElementFinder} from "protractor";
|
||||
import {E2eTestUtils} from "./utils";
|
||||
|
||||
describe("Viewer SurfaceFlinger", () => {
|
||||
beforeAll(async () => {
|
||||
browser.manage().timeouts().implicitlyWait(1000);
|
||||
browser.get("file://" + E2eTestUtils.getProductionIndexHtmlPath());
|
||||
}),
|
||||
|
||||
it("processes trace and renders view", () => {
|
||||
const inputFile = element(by.css("input[type=\"file\"]"));
|
||||
inputFile.sendKeys(E2eTestUtils.getFixturePath("traces/elapsed_and_real_timestamp/SurfaceFlinger.pb"));
|
||||
|
||||
const loadData = element(by.css(".load-btn"));
|
||||
loadData.click();
|
||||
|
||||
const surfaceFlingerCard: ElementFinder = element(by.css(".trace-card"));
|
||||
expect(surfaceFlingerCard.getText()).toContain("Surface Flinger");
|
||||
});
|
||||
});
|
||||
@@ -22,7 +22,7 @@ describe("winscope", () => {
|
||||
}),
|
||||
|
||||
it("has title", () => {
|
||||
const title = element(by.css("#title"));
|
||||
const title = element(by.css("#app-title"));
|
||||
expect(title.getText()).toContain("Winscope");
|
||||
});
|
||||
});
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { PersistentStore } from "../common/persistent_store";
|
||||
import { PersistentStore } from "common/persistent_store";
|
||||
import { configMap, TRACES } from "./trace_collection_utils";
|
||||
import { setTraces, SetTraces } from "./set_traces";
|
||||
import { Device } from "./connection";
|
||||
|
||||
416
tools/winscope-ng/src/viewers/canvas_graphics.ts
Normal file
416
tools/winscope-ng/src/viewers/canvas_graphics.ts
Normal file
@@ -0,0 +1,416 @@
|
||||
/*
|
||||
* 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 { Rectangle } from "viewers/viewer_surface_flinger/ui_data";
|
||||
import * as THREE from "three";
|
||||
import { CSS2DRenderer, CSS2DObject } from "three/examples/jsm/renderers/CSS2DRenderer";
|
||||
|
||||
export class CanvasGraphics {
|
||||
constructor() {
|
||||
//set up camera
|
||||
const left = -this.cameraHalfWidth,
|
||||
right = this.cameraHalfWidth,
|
||||
top = this.cameraHalfHeight,
|
||||
bottom = -this.cameraHalfHeight,
|
||||
near = 0.001,
|
||||
far = 100;
|
||||
this.camera = new THREE.OrthographicCamera(
|
||||
left,right,top,bottom,near,far
|
||||
);
|
||||
}
|
||||
|
||||
initialise(canvas: HTMLCanvasElement) {
|
||||
// initialise canvas
|
||||
this.canvas = canvas;
|
||||
}
|
||||
|
||||
refreshCanvas() {
|
||||
//set canvas size
|
||||
this.canvas!.style.width = "100%";
|
||||
this.canvas!.style.height = "40rem";
|
||||
|
||||
// TODO: click and drag rotation control
|
||||
this.camera.position.set(this.xyCameraPos, this.xyCameraPos, 6);
|
||||
this.camera.lookAt(0, 0, 0);
|
||||
this.camera.zoom = this.camZoom;
|
||||
this.camera.updateProjectionMatrix();
|
||||
|
||||
// scene
|
||||
const scene = new THREE.Scene();
|
||||
|
||||
// renderers
|
||||
const renderer = new THREE.WebGLRenderer({
|
||||
antialias: true,
|
||||
canvas: this.canvas,
|
||||
alpha: true
|
||||
});
|
||||
let labelRenderer: CSS2DRenderer;
|
||||
if (document.querySelector("#labels-canvas")) {
|
||||
labelRenderer = new CSS2DRenderer({
|
||||
element: document.querySelector("#labels-canvas")! as HTMLElement
|
||||
});
|
||||
} else {
|
||||
labelRenderer = new CSS2DRenderer();
|
||||
labelRenderer.domElement.style.position = "absolute";
|
||||
labelRenderer.domElement.style.top = "0px";
|
||||
labelRenderer.domElement.style.width = "100%";
|
||||
labelRenderer.domElement.style.height = "40rem";
|
||||
labelRenderer.domElement.id = "labels-canvas";
|
||||
labelRenderer.domElement.style.pointerEvents = "none";
|
||||
document.querySelector(".canvas-container")?.appendChild(labelRenderer.domElement);
|
||||
}
|
||||
|
||||
// set various factors for shading and shifting
|
||||
const visibleDarkFactor = 0, nonVisibleDarkFactor = 0, rectCounter = 0;
|
||||
const numberOfRects = this.rects.length;
|
||||
const numberOfVisibleRects = this.rects.filter(rect => rect.isVisible).length;
|
||||
const numberOfDisplayRects = this.rects.filter(rect => rect.isDisplay).length;
|
||||
|
||||
const zShift = numberOfRects*this.layerSeparation;
|
||||
let xShift = 0, yShift = 3.25, labelYShift = 0;
|
||||
|
||||
if (this.isLandscape) {
|
||||
xShift = 1;
|
||||
yShift = 1.5;
|
||||
labelYShift = 1.25;
|
||||
}
|
||||
|
||||
const lowestY = Math.min(...this.rects.map(rect => {
|
||||
const y = rect.topLeft.y - rect.height + this.lowestYShift;
|
||||
if (this.isLandscape) {
|
||||
if (y<0) {
|
||||
return 0;
|
||||
} else if (y > 2) {
|
||||
return 2;
|
||||
}
|
||||
} else if (y > -1) {
|
||||
return -1;
|
||||
}
|
||||
return y;
|
||||
})) - labelYShift;
|
||||
|
||||
this.drawScene(
|
||||
rectCounter,
|
||||
numberOfVisibleRects,
|
||||
visibleDarkFactor,
|
||||
numberOfDisplayRects,
|
||||
nonVisibleDarkFactor,
|
||||
numberOfRects,
|
||||
scene,
|
||||
xShift,
|
||||
yShift,
|
||||
zShift,
|
||||
lowestY
|
||||
);
|
||||
|
||||
// const axesHelper = new THREE.AxesHelper(1);
|
||||
// const gridHelper = new THREE.GridHelper(5);
|
||||
// scene.add(axesHelper, gridHelper)
|
||||
|
||||
renderer.setSize(this.canvas!.clientWidth, this.canvas!.clientHeight);
|
||||
renderer.setPixelRatio(window.devicePixelRatio);
|
||||
renderer.render(scene, this.camera);
|
||||
|
||||
labelRenderer.setSize(this.canvas!.clientWidth, this.canvas!.clientHeight);
|
||||
labelRenderer.render(scene, this.camera);
|
||||
}
|
||||
|
||||
private drawScene(
|
||||
rectCounter: number,
|
||||
visibleRects: number,
|
||||
visibleDarkFactor:number,
|
||||
displayRects: number,
|
||||
nonVisibleDarkFactor: number,
|
||||
numberOfRects: number,
|
||||
scene: THREE.Scene,
|
||||
xShift: number,
|
||||
yShift: number,
|
||||
zShift: number,
|
||||
lowestY: number
|
||||
) {
|
||||
this.targetObjects = [];
|
||||
this.rects.forEach(rect => {
|
||||
const visibleViewInvisibleRect = this.visibleView && !rect.isVisible;
|
||||
const xrayViewNoVirtualDisplaysVirtualRect = !this.visibleView && !this.showVirtualDisplays && rect.isDisplay && rect.isVirtual;
|
||||
if (visibleViewInvisibleRect || xrayViewNoVirtualDisplaysVirtualRect) {
|
||||
rectCounter++;
|
||||
return;
|
||||
}
|
||||
|
||||
//set colour mapping
|
||||
let planeColor;
|
||||
if (this.highlighted === `${rect.id}`) {
|
||||
planeColor = this.colorMapping("highlight", numberOfRects, 0);
|
||||
} else if (rect.isVisible) {
|
||||
planeColor = this.colorMapping("green", visibleRects, visibleDarkFactor);
|
||||
visibleDarkFactor++;
|
||||
} else if (rect.isDisplay) {
|
||||
planeColor = this.colorMapping("grey", displayRects, nonVisibleDarkFactor);
|
||||
nonVisibleDarkFactor++;
|
||||
} else {
|
||||
planeColor = this.colorMapping("unknown", numberOfRects, 0);
|
||||
}
|
||||
|
||||
//set plane geometry and material
|
||||
const geometry = new THREE.PlaneGeometry(rect.width, rect.height);
|
||||
const planeRect = this.setPlaneMaterial(rect, geometry, planeColor, xShift, yShift, zShift);
|
||||
scene.add(planeRect);
|
||||
zShift -= this.layerSeparation;
|
||||
|
||||
// bolder edges of each plane if in x-ray view
|
||||
if (!this.visibleView) {
|
||||
const edgeSegments = this.setEdgeMaterial(planeRect, geometry);
|
||||
scene.add(edgeSegments);
|
||||
}
|
||||
|
||||
// label circular marker
|
||||
const circle = this.setCircleMaterial(planeRect, rect);
|
||||
scene.add(circle);
|
||||
this.targetObjects.push(planeRect);
|
||||
|
||||
// label line
|
||||
const [line, rectLabel] = this.createLabel(rect, circle, lowestY, rectCounter);
|
||||
scene.add(line);
|
||||
scene.add(rectLabel);
|
||||
rectCounter++;
|
||||
});
|
||||
}
|
||||
|
||||
private setPlaneMaterial(
|
||||
rect: Rectangle,
|
||||
geometry: THREE.PlaneGeometry,
|
||||
color: THREE.Color,
|
||||
xShift: number,
|
||||
yShift: number,
|
||||
zShift: number
|
||||
) {
|
||||
const planeRect = new THREE.Mesh(
|
||||
geometry,
|
||||
new THREE.MeshBasicMaterial({
|
||||
color: color,
|
||||
opacity: this.visibleView ? 1 : 0.75,
|
||||
transparent: true,
|
||||
}));
|
||||
planeRect.position.y = rect.topLeft.y - rect.height/2 + yShift;
|
||||
planeRect.position.x = rect.topLeft.x + rect.width/2 - xShift;
|
||||
planeRect.position.z = zShift;
|
||||
planeRect.name = `${rect.id}`;
|
||||
return planeRect;
|
||||
}
|
||||
|
||||
private setEdgeMaterial(planeRect: THREE.Mesh, geometry: THREE.PlaneGeometry) {
|
||||
const edgeColor = 0x000000;
|
||||
const edgeGeo = new THREE.EdgesGeometry(geometry);
|
||||
const edgeMaterial = new THREE.LineBasicMaterial({color: edgeColor, linewidth: 1});
|
||||
const edgeSegments = new THREE.LineSegments(
|
||||
edgeGeo, edgeMaterial
|
||||
);
|
||||
edgeSegments.position.set(planeRect.position.x, planeRect.position.y, planeRect.position.z);
|
||||
return edgeSegments;
|
||||
}
|
||||
|
||||
private setCircleMaterial(planeRect: THREE.Mesh, rect: Rectangle) {
|
||||
const labelCircle = new THREE.CircleGeometry(0.02, 200);
|
||||
const circleMaterial = new THREE.MeshBasicMaterial({ color: 0x000000 });
|
||||
const circle = new THREE.Mesh(labelCircle, circleMaterial);
|
||||
circle.position.set(
|
||||
planeRect.position.x + rect.width/2 - 0.05,
|
||||
planeRect.position.y,
|
||||
planeRect.position.z + 0.05
|
||||
);
|
||||
circle.rotateY(THREE.MathUtils.degToRad(30));
|
||||
return circle;
|
||||
}
|
||||
|
||||
private createLabel(rect: Rectangle, circle: THREE.Mesh, lowestY: number, rectCounter: number):
|
||||
[THREE.Line, CSS2DObject] {
|
||||
const labelText = this.shortenText(rect.label);
|
||||
const isGrey = !this.visibleView && !rect.isVisible;
|
||||
let cornerPos, endPos;
|
||||
const labelYSeparation = 0.3;
|
||||
if (this.isLandscape) {
|
||||
cornerPos = new THREE.Vector3(
|
||||
circle.position.x, lowestY - 0.5 - rectCounter*labelYSeparation, circle.position.z
|
||||
);
|
||||
} else {
|
||||
cornerPos = new THREE.Vector3(
|
||||
circle.position.x, lowestY + 0.5 - rectCounter*labelYSeparation, circle.position.z
|
||||
);
|
||||
}
|
||||
|
||||
const linePoints = [circle.position, cornerPos];
|
||||
if (this.isLandscape && cornerPos.x > 0 || !this.isLandscape) {
|
||||
endPos = new THREE.Vector3(cornerPos.x - 1, cornerPos.y - this.labelShift, cornerPos.z);
|
||||
} else {
|
||||
endPos = cornerPos;
|
||||
}
|
||||
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) {
|
||||
rectLabelDiv.style.color = "grey";
|
||||
}
|
||||
const rectLabel = new CSS2DObject(rectLabelDiv);
|
||||
rectLabel.name = rect.label;
|
||||
|
||||
const textCanvas = document.createElement("canvas");
|
||||
const labelContext = textCanvas.getContext("2d");
|
||||
let labelWidth = 0;
|
||||
if (labelContext?.font) {
|
||||
labelContext.font = rectLabelDiv.style.font;
|
||||
labelWidth = labelContext?.measureText(labelText).width;
|
||||
}
|
||||
textCanvas.remove();
|
||||
|
||||
if (this.isLandscape && endPos.x < 0) {
|
||||
rectLabel.position.set(
|
||||
endPos.x + 0.6, endPos.y - 0.15, endPos.z - 0.6
|
||||
);
|
||||
} else {
|
||||
rectLabel.position.set(
|
||||
endPos.x - labelWidth * this.labelXFactor, endPos.y - this.labelShift * labelWidth * this.labelXFactor, endPos.z
|
||||
);
|
||||
}
|
||||
|
||||
const lineGeo = new THREE.BufferGeometry().setFromPoints(linePoints);
|
||||
const lineMaterial = new THREE.LineBasicMaterial({color: isGrey ? 0x808080 : 0x000000});
|
||||
const line = new THREE.Line(lineGeo, lineMaterial);
|
||||
|
||||
return [line, rectLabel];
|
||||
}
|
||||
|
||||
getCamera() {
|
||||
return this.camera;
|
||||
}
|
||||
|
||||
getTargetObjects() {
|
||||
return this.targetObjects;
|
||||
}
|
||||
|
||||
getLayerSeparation() {
|
||||
return this.layerSeparation;
|
||||
}
|
||||
|
||||
getVisibleView() {
|
||||
return this.visibleView;
|
||||
}
|
||||
|
||||
getXyCameraPos() {
|
||||
return this.xyCameraPos;
|
||||
}
|
||||
|
||||
getShowVirtualDisplays() {
|
||||
return this.showVirtualDisplays;
|
||||
}
|
||||
|
||||
updateLayerSeparation(userInput: number) {
|
||||
this.layerSeparation = userInput;
|
||||
}
|
||||
|
||||
updateRotation(userInput: number) {
|
||||
this.xyCameraPos = userInput;
|
||||
this.camZoom = userInput/4 * 0.2 + 0.9;
|
||||
this.labelShift = userInput/4 * this.maxLabelShift;
|
||||
this.lowestYShift = userInput/4 + 2;
|
||||
}
|
||||
|
||||
updateHighlighted(highlighted: string) {
|
||||
this.highlighted = highlighted;
|
||||
}
|
||||
|
||||
updateRects(rects: Rectangle[]) {
|
||||
this.rects = rects;
|
||||
}
|
||||
|
||||
updateIsLandscape(isLandscape: boolean) {
|
||||
this.isLandscape = isLandscape;
|
||||
}
|
||||
|
||||
updateVisibleView(visible: boolean) {
|
||||
this.visibleView = visible;
|
||||
}
|
||||
|
||||
updateVirtualDisplays(show: boolean) {
|
||||
this.showVirtualDisplays = show;
|
||||
}
|
||||
|
||||
clearLabelElements() {
|
||||
this.labelElements.forEach(el => el.remove());
|
||||
}
|
||||
|
||||
updateZoom(isZoomIn: boolean) {
|
||||
if (isZoomIn && this.camZoom < 2) {
|
||||
this.camZoom += this.camZoomFactor * 1.5;
|
||||
} else if (!isZoomIn && this.camZoom > 0.5) {
|
||||
this.camZoom -= this.camZoomFactor * 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
colorMapping(scale: string, numberOfRects: number, darkFactor:number): THREE.Color {
|
||||
if (scale === "highlight") {
|
||||
return new THREE.Color(0xD2E3FC);
|
||||
} else if (scale === "grey") {
|
||||
// darkness of grey rect depends on z order - darkest 64, lightest 128
|
||||
//Separate RGB values between 0 and 1
|
||||
const lower = 120;
|
||||
const upper = 220;
|
||||
const darkness = ((upper-lower)*(numberOfRects-darkFactor)/numberOfRects + lower)/255;
|
||||
return new THREE.Color(darkness, darkness, darkness);
|
||||
} else if (scale === "green") {
|
||||
// darkness of green rect depends on z order
|
||||
//Separate RGB values between 0 and 1
|
||||
const red = ((200-45)*(numberOfRects-darkFactor)/numberOfRects + 45)/255;
|
||||
const green = ((232-182)*(numberOfRects-darkFactor)/numberOfRects + 182)/255;
|
||||
const blue = ((183-44)*(numberOfRects-darkFactor)/numberOfRects + 44)/255;
|
||||
return new THREE.Color(red, green, blue);
|
||||
} else {
|
||||
return new THREE.Color(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
shortenText(text: string): string {
|
||||
if (text.length > 40) {
|
||||
text = text.slice(0, 40);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
// dynamic scaling and canvas variables
|
||||
readonly cameraHalfWidth = 2.8;
|
||||
readonly cameraHalfHeight = 3.2;
|
||||
private readonly maxLabelShift = 0.305;
|
||||
private readonly labelXFactor = 0.008;
|
||||
private lowestYShift = 3;
|
||||
private camZoom = 1.1;
|
||||
private camZoomFactor = 0.1;
|
||||
private labelShift = this.maxLabelShift;
|
||||
private highlighted = "";
|
||||
private visibleView = false;
|
||||
private isLandscape = false;
|
||||
private showVirtualDisplays = false;
|
||||
private layerSeparation = 0.4;
|
||||
private xyCameraPos = 4;
|
||||
private camera: THREE.OrthographicCamera;
|
||||
private rects: Rectangle[] = [];
|
||||
private labelElements: HTMLElement[] = [];
|
||||
private targetObjects: any[] = [];
|
||||
private canvas?: HTMLCanvasElement;
|
||||
}
|
||||
29
tools/winscope-ng/src/viewers/hierarchy.component.ts
Normal file
29
tools/winscope-ng/src/viewers/hierarchy.component.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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 } from "@angular/core";
|
||||
|
||||
@Component({
|
||||
selector: "hierarchy-view",
|
||||
template: `
|
||||
<mat-card-title class="trace-view-subtitle">Hierarchy</mat-card-title>
|
||||
`,
|
||||
styles: [
|
||||
".trace-view-subtitle { font-size: 18px}"
|
||||
]
|
||||
})
|
||||
|
||||
export class HierarchyComponent {
|
||||
}
|
||||
26
tools/winscope-ng/src/viewers/properties.component.ts
Normal file
26
tools/winscope-ng/src/viewers/properties.component.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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 } from "@angular/core";
|
||||
|
||||
@Component({
|
||||
selector: "properties-view",
|
||||
template: `
|
||||
<mat-card-title class="trace-view-subtitle">Properties</mat-card-title>
|
||||
`,
|
||||
})
|
||||
|
||||
export class PropertiesComponent {
|
||||
}
|
||||
121
tools/winscope-ng/src/viewers/rects.component.spec.ts
Normal file
121
tools/winscope-ng/src/viewers/rects.component.spec.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* 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 { CommonModule } from "@angular/common";
|
||||
import { Component , ViewChild } from "@angular/core";
|
||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { RectsComponent } from "./rects.component";
|
||||
import { MatCheckboxModule } from "@angular/material/checkbox";
|
||||
import { MatCardModule } from "@angular/material/card";
|
||||
import { MatRadioModule } from "@angular/material/radio";
|
||||
import { MatSliderModule } from "@angular/material/slider";
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
|
||||
import { Rectangle } from "./viewer_surface_flinger/ui_data";
|
||||
|
||||
describe("RectsComponent", () => {
|
||||
let component: TestHostComponent;
|
||||
let fixture: ComponentFixture<TestHostComponent>;
|
||||
let htmlElement: HTMLElement;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatCheckboxModule,
|
||||
MatCardModule,
|
||||
MatSliderModule,
|
||||
MatRadioModule
|
||||
],
|
||||
declarations: [RectsComponent, TestHostComponent],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TestHostComponent);
|
||||
component = fixture.componentInstance;
|
||||
htmlElement = fixture.nativeElement;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it("can be created", () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it("check that layer separation slider is rendered", () => {
|
||||
fixture.detectChanges();
|
||||
const slider = htmlElement.querySelector("mat-slider");
|
||||
expect(slider).toBeTruthy();
|
||||
});
|
||||
|
||||
it("check that layer separation slider causes view to change", () => {
|
||||
const slider = htmlElement.querySelector("mat-slider");
|
||||
spyOn(component.rectsComponent.canvasGraphics, "updateLayerSeparation");
|
||||
slider?.dispatchEvent(new MouseEvent("mousedown"));
|
||||
fixture.detectChanges();
|
||||
expect(component.rectsComponent.canvasGraphics.updateLayerSeparation).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("check that rects canvas is rendered", () => {
|
||||
fixture.detectChanges();
|
||||
const rectsCanvas = htmlElement.querySelector("#rects-canvas");
|
||||
expect(rectsCanvas).toBeTruthy();
|
||||
});
|
||||
|
||||
it("check that canvas is refreshed if rects are present", async () => {
|
||||
component.addRects([
|
||||
{
|
||||
topLeft: {x:0, y:0},
|
||||
bottomRight: {x:1, y:-1},
|
||||
label: "rectangle1",
|
||||
transform: {
|
||||
matrix: {
|
||||
dsdx: 1,
|
||||
dsdy: 0,
|
||||
dtdx: 0,
|
||||
dtdy: 1,
|
||||
tx: 0,
|
||||
ty: 0
|
||||
}
|
||||
},
|
||||
height: 1,
|
||||
width: 1,
|
||||
isVisible: true,
|
||||
isDisplay: false,
|
||||
ref: null,
|
||||
id: 12345,
|
||||
stackId: 0,
|
||||
}
|
||||
]);
|
||||
spyOn(component.rectsComponent, "drawRects").and.callThrough();
|
||||
fixture.detectChanges();
|
||||
expect(component.rectsComponent.drawRects).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@Component({
|
||||
selector: "host-component",
|
||||
template: "<rects-view [rects]=\"rects\"></rects-view>"
|
||||
})
|
||||
class TestHostComponent {
|
||||
public rects: Rectangle[] = [];
|
||||
|
||||
addRects(newRects: Rectangle[]) {
|
||||
this.rects = newRects;
|
||||
}
|
||||
|
||||
@ViewChild(RectsComponent)
|
||||
public rectsComponent!: RectsComponent;
|
||||
}
|
||||
});
|
||||
286
tools/winscope-ng/src/viewers/rects.component.ts
Normal file
286
tools/winscope-ng/src/viewers/rects.component.ts
Normal file
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
* 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, OnChanges, OnDestroy, Inject, ElementRef, SimpleChanges } from "@angular/core";
|
||||
import { RectsUtils } from "./rects_utils";
|
||||
import { Point, Rectangle, RectMatrix, RectTransform } from "viewers/viewer_surface_flinger/ui_data";
|
||||
import { interval, Subscription } from "rxjs";
|
||||
import { CanvasGraphics } from "./canvas_graphics";
|
||||
import * as THREE from "three";
|
||||
|
||||
@Component({
|
||||
selector: "rects-view",
|
||||
template: `
|
||||
<mat-card-header class="view-controls">
|
||||
<mat-radio-group (change)="onChangeView($event.value)">
|
||||
<mat-radio-button class="visible-radio" [value]="true" [checked]="visibleView()">Visible</mat-radio-button>
|
||||
<mat-radio-button class="xray-radio" [value]="false" [checked]="!visibleView()">X-ray</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
<mat-slider
|
||||
step="0.001"
|
||||
min="0.1"
|
||||
max="0.4"
|
||||
aria-label="units"
|
||||
[value]="getLayerSeparation()"
|
||||
(input)="canvasGraphics.updateLayerSeparation($event.value!)"
|
||||
></mat-slider>
|
||||
<mat-slider
|
||||
step="0.01"
|
||||
min="0.00"
|
||||
max="4"
|
||||
aria-label="units"
|
||||
[value]="xyCameraPos()"
|
||||
(input)="canvasGraphics.updateRotation($event.value!)"
|
||||
></mat-slider>
|
||||
<mat-checkbox
|
||||
[hidden]="visibleView()"
|
||||
class="rects-checkbox"
|
||||
[checked]="showVirtualDisplays()"
|
||||
(change)="canvasGraphics.updateVirtualDisplays($event.checked!)"
|
||||
>Show virtual displays</mat-checkbox>
|
||||
</mat-card-header>
|
||||
<mat-card-content class="rects-content">
|
||||
<div class="canvas-container">
|
||||
<div class="zoom-container">
|
||||
<button id="zoom-btn" (click)="canvasGraphics.updateZoom(true)">
|
||||
<mat-icon aria-hidden="true">
|
||||
zoom_in
|
||||
</mat-icon>
|
||||
</button>
|
||||
<button id="zoom-btn" (click)="canvasGraphics.updateZoom(false)">
|
||||
<mat-icon aria-hidden="true">
|
||||
zoom_out
|
||||
</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<canvas id="rects-canvas" (click)="onRectClick($event)">
|
||||
</canvas>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
`,
|
||||
styles: [
|
||||
"@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}",
|
||||
".view-controls {display: inline-block; position: relative; min-height: 72px}",
|
||||
".zoom-container {position: absolute; top: 0px; z-index: 10}",
|
||||
"#zoom-btn {position:relative; display: block; background: none; border: none}",
|
||||
"mat-radio-button {font-size: 16px; font-weight: normal}",
|
||||
".mat-radio-button, .mat-radio-button-frame {transform: scale(0.8);}",
|
||||
".rects-checkbox {font-size: 14px; font-weight: normal}",
|
||||
"mat-icon {margin: 5px}",
|
||||
"mat-checkbox {margin-left: 5px;}",
|
||||
".mat-checkbox .mat-checkbox-frame { transform: scale(0.7);}",
|
||||
".mat-checkbox-checked .mat-checkbox-background {transform: scale(0.7);}",
|
||||
".mat-checkbox-indeterminate .mat-checkbox-background {transform: scale(0.7);}",
|
||||
]
|
||||
})
|
||||
|
||||
export class RectsComponent implements OnChanges, OnDestroy {
|
||||
@Input() rects!: Rectangle[];
|
||||
|
||||
@Input() highlighted = "";
|
||||
|
||||
constructor(
|
||||
@Inject(ElementRef) private elementRef: ElementRef,
|
||||
) {
|
||||
this.canvasGraphics = new CanvasGraphics();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.canvasSubscription) {
|
||||
this.canvasSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (this.rects.length > 0) {
|
||||
//change in rects so they must undergo transformation and scaling before canvas refreshed
|
||||
this.canvasGraphics.clearLabelElements();
|
||||
this.rects = this.rects.filter(rect => rect.isVisible || rect.isDisplay);
|
||||
this.displayRects = this.rects.filter(rect => rect.isDisplay);
|
||||
this.computeBounds();
|
||||
this.rects = this.rects.map(rect => {
|
||||
if (changes["rects"] && rect.transform) {
|
||||
return RectsUtils.transformRect(rect.transform.matrix ?? rect.transform, rect);
|
||||
} else {
|
||||
return rect;
|
||||
}
|
||||
});
|
||||
this.scaleRects();
|
||||
this.drawRects();
|
||||
} else if (this.canvasSubscription) {
|
||||
this.canvasSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
onRectClick(event:PointerEvent) {
|
||||
this.setNormalisedMousePos(event);
|
||||
const raycaster = new THREE.Raycaster();
|
||||
raycaster.setFromCamera(this.mouse, this.canvasGraphics.getCamera());
|
||||
// create an array containing all objects in the scene with which the ray intersects
|
||||
const intersects = raycaster.intersectObjects(this.canvasGraphics.getTargetObjects());
|
||||
// if there is one (or more) intersections
|
||||
if (intersects.length > 0){
|
||||
if (this.highlighted === intersects[0].object.name) {
|
||||
this.highlighted = "";
|
||||
this.canvasGraphics.updateHighlighted("");
|
||||
} else {
|
||||
this.highlighted = intersects[0].object.name;
|
||||
this.canvasGraphics.updateHighlighted(intersects[0].object.name);
|
||||
}
|
||||
this.updateHighlightedRect();
|
||||
}
|
||||
}
|
||||
|
||||
setNormalisedMousePos(event:PointerEvent) {
|
||||
event.preventDefault();
|
||||
const canvas = (event.target as Element);
|
||||
const canvasOffset = canvas.getBoundingClientRect();
|
||||
this.mouse.x = ((event.clientX-canvasOffset.left)/canvas.clientWidth) * 2 - 1;
|
||||
this.mouse.y = -((event.clientY-canvasOffset.top)/canvas.clientHeight) * 2 + 1;
|
||||
this.mouse.z = 0;
|
||||
}
|
||||
|
||||
updateHighlightedRect() {
|
||||
const event: CustomEvent = new CustomEvent("highlightedChange", {
|
||||
bubbles: true,
|
||||
detail: { layerId: this.highlighted }
|
||||
});
|
||||
this.elementRef.nativeElement.dispatchEvent(event);
|
||||
}
|
||||
|
||||
drawRects() {
|
||||
if (this.canvasSubscription) {
|
||||
this.canvasSubscription.unsubscribe();
|
||||
}
|
||||
const canvas = document.getElementById("rects-canvas") as HTMLCanvasElement;
|
||||
this.canvasGraphics.initialise(canvas);
|
||||
this.canvasSubscription = this.drawRectsInterval.subscribe(() => {
|
||||
this.updateVariablesBeforeRefresh();
|
||||
this.canvasGraphics.refreshCanvas();
|
||||
});
|
||||
}
|
||||
|
||||
updateVariablesBeforeRefresh() {
|
||||
this.canvasGraphics.updateRects(this.rects);
|
||||
const biggestX = Math.max(...this.rects.map(rect => rect.topLeft.x + rect.width/2));
|
||||
this.canvasGraphics.updateIsLandscape(biggestX > this.s({x: this.boundsWidth, y:this.boundsHeight}).x/2);
|
||||
}
|
||||
|
||||
onChangeView(visible: boolean) {
|
||||
this.canvasGraphics.updateVisibleView(visible);
|
||||
this.canvasGraphics.clearLabelElements();
|
||||
}
|
||||
|
||||
scaleRects() {
|
||||
this.rects = this.rects.map(rect => {
|
||||
rect.bottomRight = this.s(rect.bottomRight);
|
||||
rect.topLeft = this.s(rect.topLeft);
|
||||
rect.height = Math.abs(rect.topLeft.y - rect.bottomRight.y);
|
||||
rect.width = Math.abs(rect.bottomRight.x - rect.topLeft.x);
|
||||
const mat = this.getMatrix(rect);
|
||||
if (mat) {
|
||||
const newTranslation = this.s({x: mat.tx!, y: mat.ty!});
|
||||
mat.tx = newTranslation.x;
|
||||
mat.ty = newTranslation.y;
|
||||
}
|
||||
return rect;
|
||||
});
|
||||
}
|
||||
|
||||
computeBounds(): any {
|
||||
this.boundsWidth = Math.max(...this.rects.map((rect) => {
|
||||
const mat = this.getMatrix(rect);
|
||||
if (mat) {
|
||||
return RectsUtils.transformRect(mat, rect).width;
|
||||
} else {
|
||||
return rect.width;
|
||||
}}));
|
||||
this.boundsHeight = Math.max(...this.rects.map((rect) => {
|
||||
const mat = this.getMatrix(rect);
|
||||
if (mat) {
|
||||
return RectsUtils.transformRect(mat, rect).height;
|
||||
} else {
|
||||
return rect.height;
|
||||
}}));
|
||||
|
||||
if (this.displayRects.length > 0) {
|
||||
this.boundsWidth = Math.min(this.boundsWidth, this.maxWidth());
|
||||
this.boundsHeight = Math.min(this.boundsHeight, this.maxHeight());
|
||||
}
|
||||
}
|
||||
|
||||
maxWidth() {
|
||||
return Math.max(...this.displayRects.map(rect => rect.width)) * 1.2;
|
||||
}
|
||||
|
||||
maxHeight() {
|
||||
return Math.max(...this.displayRects.map(rect => rect.height)) * 1.2;
|
||||
}
|
||||
|
||||
// scales coordinates to canvas
|
||||
s(sourceCoordinates: Point) {
|
||||
let scale;
|
||||
if (this.boundsWidth < this.boundsHeight) {
|
||||
scale = this.canvasGraphics.cameraHalfHeight*2 * 0.6 / this.boundsHeight;
|
||||
} else {
|
||||
scale = this.canvasGraphics.cameraHalfWidth*2 * 0.6 / this.boundsWidth;
|
||||
}
|
||||
return {
|
||||
x: sourceCoordinates.x * scale,
|
||||
y: sourceCoordinates.y * scale,
|
||||
};
|
||||
}
|
||||
|
||||
getMatrix(rect: Rectangle) {
|
||||
if (rect.transform) {
|
||||
let matrix: RectTransform | RectMatrix = rect.transform;
|
||||
if (rect.transform && rect.transform.matrix) {
|
||||
matrix = rect.transform.matrix;
|
||||
}
|
||||
return matrix;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
visibleView() {
|
||||
return this.canvasGraphics.getVisibleView();
|
||||
}
|
||||
|
||||
getLayerSeparation() {
|
||||
return this.canvasGraphics.getLayerSeparation();
|
||||
}
|
||||
|
||||
xyCameraPos() {
|
||||
return this.canvasGraphics.getXyCameraPos();
|
||||
}
|
||||
|
||||
showVirtualDisplays() {
|
||||
return this.canvasGraphics.getShowVirtualDisplays();
|
||||
}
|
||||
|
||||
canvasGraphics: CanvasGraphics;
|
||||
private readonly _60fpsInterval = 16.66666666666667;
|
||||
private drawRectsInterval = interval(this._60fpsInterval);
|
||||
private boundsWidth = 0;
|
||||
private boundsHeight = 0;
|
||||
private displayRects!: Rectangle[];
|
||||
private canvasSubscription?: Subscription;
|
||||
private mouse = new THREE.Vector3(0, 0, 0);
|
||||
}
|
||||
59
tools/winscope-ng/src/viewers/rects_utils.spec.ts
Normal file
59
tools/winscope-ng/src/viewers/rects_utils.spec.ts
Normal file
@@ -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 { RectsUtils } from "./rects_utils";
|
||||
|
||||
describe("RectsUtils", () => {
|
||||
it("transforms rect", () => {
|
||||
const transform = {
|
||||
matrix: {
|
||||
dsdx: 1,
|
||||
dsdy: 0,
|
||||
dtdx: 0,
|
||||
dtdy: 1,
|
||||
tx: 1,
|
||||
ty: 1
|
||||
}
|
||||
};
|
||||
const rect = {
|
||||
topLeft: {x: 0, y: 0},
|
||||
bottomRight: {x: 1, y: -1},
|
||||
label: "TestRect",
|
||||
transform: transform,
|
||||
isVisible: true,
|
||||
isDisplay: false,
|
||||
height: 1,
|
||||
width: 1,
|
||||
ref: null,
|
||||
id: 12345,
|
||||
stackId: 0
|
||||
};
|
||||
const expected = {
|
||||
topLeft: {x: 1, y: 1},
|
||||
bottomRight: {x: 2, y: 0},
|
||||
label: "TestRect",
|
||||
transform: transform,
|
||||
isVisible: true,
|
||||
isDisplay: false,
|
||||
height: 1,
|
||||
width: 1,
|
||||
ref: null,
|
||||
id: 12345,
|
||||
stackId: 0,
|
||||
isVirtual: undefined
|
||||
};
|
||||
expect(RectsUtils.transformRect(rect.transform.matrix, rect)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
60
tools/winscope-ng/src/viewers/rects_utils.ts
Normal file
60
tools/winscope-ng/src/viewers/rects_utils.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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 { Point, Rectangle, RectMatrix, RectTransform } from "viewers/viewer_surface_flinger/ui_data";
|
||||
|
||||
export const RectsUtils = {
|
||||
multiplyMatrix(matrix:any, corner: Point): Point {
|
||||
if (!matrix) return corner;
|
||||
// |dsdx dsdy tx| | x | |x*dsdx + y*dsdy + tx|
|
||||
// |dtdx dtdy ty| x | y | = |x*dtdx + y*dtdy + ty|
|
||||
// |0 0 1 | | 1 | | 1 |
|
||||
return {
|
||||
x: matrix.dsdx * corner.x + matrix.dsdy * corner.y + matrix.tx,
|
||||
y: matrix.dtdx * corner.x + matrix.dtdy * corner.y + matrix.ty,
|
||||
};
|
||||
},
|
||||
|
||||
transformRect(matrix: RectMatrix | RectTransform, rect:Rectangle): Rectangle {
|
||||
// | dsdx dsdy tx | | left top 1 |
|
||||
// matrix = | dtdx dtdy ty | rect = | 1 1 1 |
|
||||
// | 0 0 1 | | 1 right bottom |
|
||||
const tl = this.multiplyMatrix(matrix, rect.topLeft);
|
||||
const tr = this.multiplyMatrix(matrix, {x:rect.bottomRight.x, y:rect.topLeft.y});
|
||||
const bl = this.multiplyMatrix(matrix, {x:rect.topLeft.x, y:rect.bottomRight.y});
|
||||
const br = this.multiplyMatrix(matrix, rect.bottomRight);
|
||||
|
||||
const left = Math.min(tl.x, tr.x, bl.x, br.x);
|
||||
const top = Math.max(tl.y, tr.y, bl.y, br.y);
|
||||
const right = Math.max(tl.x, tr.x, bl.x, br.x);
|
||||
const bottom = Math.min(tl.y, tr.y, bl.y, br.y);
|
||||
|
||||
const outrect: Rectangle = {
|
||||
topLeft: {x: left, y: top},
|
||||
bottomRight: {x: right, y: bottom},
|
||||
label: rect.label,
|
||||
transform: rect.transform,
|
||||
isVisible: rect.isVisible,
|
||||
isDisplay: rect.isDisplay,
|
||||
height: Math.abs(top - bottom),
|
||||
width: Math.abs(right - left),
|
||||
ref: rect.ref,
|
||||
id: rect.id,
|
||||
stackId: rect.stackId,
|
||||
isVirtual: rect.isVirtual
|
||||
};
|
||||
return outrect;
|
||||
}
|
||||
};
|
||||
@@ -19,6 +19,8 @@ interface Viewer {
|
||||
//TODO: add TraceEntry data type
|
||||
notifyCurrentTraceEntries(entries: Map<TraceType, any>): void;
|
||||
getView(): HTMLElement;
|
||||
getTitle(): string;
|
||||
getDependencies(): TraceType[];
|
||||
}
|
||||
|
||||
export { Viewer };
|
||||
|
||||
@@ -16,10 +16,12 @@
|
||||
import { TraceType } from "common/trace/trace_type";
|
||||
import { Viewer } from "./viewer";
|
||||
import { ViewerWindowManager } from "./viewer_window_manager/viewer_window_manager";
|
||||
import { ViewerSurfaceFlinger } from "./viewer_surface_flinger/viewer_surface_flinger";
|
||||
|
||||
class ViewerFactory {
|
||||
static readonly VIEWERS = [
|
||||
ViewerWindowManager,
|
||||
ViewerSurfaceFlinger
|
||||
];
|
||||
|
||||
public createViewers(activeTraceTypes: Set<TraceType>): Viewer[] {
|
||||
@@ -29,7 +31,6 @@ class ViewerFactory {
|
||||
const areViewerDepsSatisfied = Viewer.DEPENDENCIES.every((traceType: TraceType) =>
|
||||
activeTraceTypes.has(traceType)
|
||||
);
|
||||
|
||||
if (areViewerDepsSatisfied) {
|
||||
viewers.push(new Viewer());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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 { Rectangle, RectMatrix, RectTransform, UiData } from "viewers/viewer_surface_flinger/ui_data";
|
||||
import { TraceType } from "common/trace/trace_type";
|
||||
|
||||
type NotifyViewCallbackType = (uiData: UiData) => void;
|
||||
|
||||
class Presenter {
|
||||
constructor(notifyViewCallback: NotifyViewCallbackType) {
|
||||
this.notifyViewCallback = notifyViewCallback;
|
||||
this.uiData = new UiData("Initial UI data");
|
||||
this.notifyViewCallback(this.uiData);
|
||||
}
|
||||
|
||||
updateHighlightedRect(event: CustomEvent) {
|
||||
this.highlighted = event.detail.layerId;
|
||||
this.uiData.highlighted = this.highlighted;
|
||||
console.log("changed highlighted rect: ", this.uiData.highlighted);
|
||||
this.notifyViewCallback(this.uiData);
|
||||
}
|
||||
|
||||
notifyCurrentTraceEntries(entries: Map<TraceType, any>) {
|
||||
const entry = entries.get(TraceType.SURFACE_FLINGER);
|
||||
this.uiData = new UiData("New surface flinger ui data");
|
||||
const displayRects = entry.displays.map((display: any) => {
|
||||
const rect = display.layerStackSpace;
|
||||
rect.label = display.name;
|
||||
rect.id = display.id;
|
||||
rect.stackId = display.layerStackId;
|
||||
rect.isDisplay = true;
|
||||
rect.isVirtual = display.isVirtual;
|
||||
return rect;
|
||||
}) ?? [];
|
||||
this.uiData.highlighted = this.highlighted;
|
||||
this.uiData.rects = this.rectsToUiData(entry.rects.concat(displayRects));
|
||||
this.notifyViewCallback(this.uiData);
|
||||
}
|
||||
|
||||
rectsToUiData(rects: any[]): Rectangle[] {
|
||||
const uiRects: Rectangle[] = [];
|
||||
rects.forEach((rect: any) => {
|
||||
let t = null;
|
||||
if (rect.transform && rect.transform.matrix) {
|
||||
t = rect.transform.matrix;
|
||||
} else if (rect.transform) {
|
||||
t = rect.transform;
|
||||
}
|
||||
let transform: RectTransform | null = null;
|
||||
if (t !== null) {
|
||||
const matrix: RectMatrix = {
|
||||
dsdx: t.dsdx,
|
||||
dsdy: t.dsdy,
|
||||
dtdx: t.dtdx,
|
||||
dtdy: t.dtdy,
|
||||
tx: t.tx,
|
||||
ty: -t.ty
|
||||
};
|
||||
transform = {
|
||||
matrix: matrix,
|
||||
};
|
||||
}
|
||||
|
||||
let isVisible = false, isDisplay = false;
|
||||
if (rect.ref && rect.ref.isVisible) {
|
||||
isVisible = rect.ref.isVisible;
|
||||
}
|
||||
if (rect.isDisplay) {
|
||||
isDisplay = rect.isDisplay;
|
||||
}
|
||||
|
||||
const newRect: Rectangle = {
|
||||
topLeft: {x: rect.left, y: rect.top},
|
||||
bottomRight: {x: rect.right, y: -rect.bottom},
|
||||
height: rect.height,
|
||||
width: rect.width,
|
||||
label: rect.label,
|
||||
transform: transform,
|
||||
isVisible: isVisible,
|
||||
isDisplay: isDisplay,
|
||||
ref: rect.ref,
|
||||
id: rect.id ?? rect.ref.id,
|
||||
stackId: rect.stackId ?? rect.ref.stackId,
|
||||
isVirtual: rect.isVirtual
|
||||
};
|
||||
uiRects.push(newRect);
|
||||
});
|
||||
return uiRects;
|
||||
}
|
||||
|
||||
private readonly notifyViewCallback: NotifyViewCallbackType;
|
||||
private uiData: UiData;
|
||||
private highlighted = "";
|
||||
}
|
||||
|
||||
export {Presenter};
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
class UiData {
|
||||
constructor(public text: string) {
|
||||
console.log(text);
|
||||
}
|
||||
rects?: Rectangle[] = [];
|
||||
highlighted?: string = "";
|
||||
}
|
||||
|
||||
export interface Rectangle {
|
||||
topLeft: Point;
|
||||
bottomRight: Point;
|
||||
label: string;
|
||||
transform: RectTransform | null;
|
||||
height: number;
|
||||
width: number;
|
||||
isVisible: boolean;
|
||||
isDisplay: boolean;
|
||||
ref: any;
|
||||
id: number;
|
||||
stackId: number;
|
||||
isVirtual?: boolean;
|
||||
}
|
||||
|
||||
export interface Point {
|
||||
x: number,
|
||||
y: number
|
||||
}
|
||||
|
||||
export interface RectTransform {
|
||||
matrix?: RectMatrix;
|
||||
dsdx?: number;
|
||||
dsdy?: number;
|
||||
dtdx?: number;
|
||||
dtdy?: number;
|
||||
tx?: number;
|
||||
ty?: number;
|
||||
}
|
||||
|
||||
export interface RectMatrix {
|
||||
dsdx: number;
|
||||
dsdy: number;
|
||||
dtdx: number;
|
||||
dtdy: number;
|
||||
tx: number;
|
||||
ty: number;
|
||||
}
|
||||
|
||||
export {UiData};
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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 {ViewerSurfaceFlingerComponent} from "./viewer_surface_flinger.component";
|
||||
|
||||
import { HierarchyComponent } from "viewers/hierarchy.component";
|
||||
import { PropertiesComponent } from "viewers/properties.component";
|
||||
import { RectsComponent } from "viewers/rects.component";
|
||||
import { MatIconModule } from "@angular/material/icon";
|
||||
import { MatCardModule } from "@angular/material/card";
|
||||
import { ComponentFixtureAutoDetect } from "@angular/core/testing";
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
|
||||
|
||||
describe("ViewerSurfaceFlingerComponent", () => {
|
||||
let fixture: ComponentFixture<ViewerSurfaceFlingerComponent>;
|
||||
let component: ViewerSurfaceFlingerComponent;
|
||||
let htmlElement: HTMLElement;
|
||||
|
||||
beforeAll(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{ provide: ComponentFixtureAutoDetect, useValue: true }
|
||||
],
|
||||
imports: [
|
||||
MatIconModule,
|
||||
MatCardModule
|
||||
],
|
||||
declarations: [
|
||||
ViewerSurfaceFlingerComponent,
|
||||
HierarchyComponent,
|
||||
PropertiesComponent,
|
||||
RectsComponent
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ViewerSurfaceFlingerComponent);
|
||||
component = fixture.componentInstance;
|
||||
htmlElement = fixture.nativeElement;
|
||||
});
|
||||
|
||||
it("can be created", () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it("creates rects view", () => {
|
||||
const rectsView = htmlElement.querySelector(".rects-view");
|
||||
expect(rectsView).toBeTruthy();
|
||||
});
|
||||
|
||||
it("creates hierarchy view", () => {
|
||||
const hierarchyView = htmlElement.querySelector("#sf-hierarchy-view");
|
||||
expect(hierarchyView).toBeTruthy();
|
||||
});
|
||||
|
||||
it("creates properties view", () => {
|
||||
const propertiesView = htmlElement.querySelector("#sf-properties-view");
|
||||
expect(propertiesView).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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 { UiData } from "./ui_data";
|
||||
import { TRACE_INFO } from "app/trace_info";
|
||||
import { TraceType } from "common/trace/trace_type";
|
||||
|
||||
@Component({
|
||||
selector: "viewer-surface-flinger",
|
||||
template: `
|
||||
<div fxLayout="row wrap" fxLayoutGap="10px grid" class="card-grid">
|
||||
<mat-card class="rects-view">
|
||||
<rects-view
|
||||
[rects]="inputData?.rects ?? []"
|
||||
[highlighted]="inputData?.highlighted ?? ''"
|
||||
class="rects-view"
|
||||
></rects-view>
|
||||
</mat-card>
|
||||
<mat-card id="sf-hierarchy-view" class="hierarchy-view">
|
||||
<hierarchy-view></hierarchy-view>
|
||||
</mat-card>
|
||||
<mat-card id="sf-properties-view" class="properties-view">
|
||||
<properties-view></properties-view>
|
||||
</mat-card>
|
||||
</div>
|
||||
`,
|
||||
styles: [
|
||||
"@import 'https://fonts.googleapis.com/icon?family=Material+Icons';",
|
||||
"mat-icon {margin: 5px}",
|
||||
"viewer-surface-flinger {font-family: Arial, Helvetica, sans-serif;}",
|
||||
".trace-card-title {display: inline-block; vertical-align: middle;}",
|
||||
".header-button {background: none; border: none; display: inline-block; vertical-align: middle;}",
|
||||
".card-grid {width: 100%;height: 100%;display: flex;flex-direction: row;overflow: auto;}",
|
||||
".rects-view {font: inherit; flex: none !important;width: 400px;margin: 8px;}",
|
||||
".hierarchy-view, .properties-view {font: inherit; flex: 1;margin: 8px;min-width: 400px;min-height: 50rem;max-height: 50rem;}",
|
||||
]
|
||||
})
|
||||
export class ViewerSurfaceFlingerComponent {
|
||||
@Input()
|
||||
inputData?: UiData;
|
||||
|
||||
TRACE_INFO = TRACE_INFO;
|
||||
TraceType = TraceType;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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 {TraceType} from "common/trace/trace_type";
|
||||
import {Viewer} from "viewers/viewer";
|
||||
import {Presenter} from "./presenter";
|
||||
import {UiData} from "./ui_data";
|
||||
|
||||
class ViewerSurfaceFlinger implements Viewer {
|
||||
constructor() {
|
||||
this.view = document.createElement("viewer-surface-flinger");
|
||||
this.presenter = new Presenter((uiData: UiData) => {
|
||||
(this.view as any).inputData = uiData;
|
||||
});
|
||||
this.view.addEventListener("highlightedChange", (event) => this.presenter.updateHighlightedRect((event as CustomEvent)));
|
||||
}
|
||||
|
||||
public notifyCurrentTraceEntries(entries: Map<TraceType, any>): void {
|
||||
this.presenter.notifyCurrentTraceEntries(entries);
|
||||
}
|
||||
|
||||
public getView(): HTMLElement {
|
||||
return this.view;
|
||||
}
|
||||
|
||||
public getTitle(): string {
|
||||
return "Surface Flinger";
|
||||
}
|
||||
|
||||
public getDependencies(): TraceType[] {
|
||||
return ViewerSurfaceFlinger.DEPENDENCIES;
|
||||
}
|
||||
|
||||
public static readonly DEPENDENCIES: TraceType[] = [TraceType.SURFACE_FLINGER];
|
||||
private view: HTMLElement;
|
||||
private presenter: Presenter;
|
||||
}
|
||||
|
||||
export {ViewerSurfaceFlinger};
|
||||
@@ -16,29 +16,29 @@
|
||||
import {TraceType} from "common/trace/trace_type";
|
||||
import {UiData} from "./ui_data";
|
||||
|
||||
type UiDataCallbackType = (uiData: UiData) => void;
|
||||
type NotifyViewCallbackType = (uiData: UiData) => void;
|
||||
|
||||
class Presenter {
|
||||
constructor(uiDataCallback: UiDataCallbackType) {
|
||||
this.uiDataCallback = uiDataCallback;
|
||||
constructor(notifyViewCallback: NotifyViewCallbackType) {
|
||||
this.notifyViewCallback = notifyViewCallback;
|
||||
this.uiData = new UiData("Initial UI data");
|
||||
this.uiDataCallback(this.uiData);
|
||||
this.notifyViewCallback(this.uiData);
|
||||
}
|
||||
|
||||
public notifyCurrentTraceEntries(entries: Map<TraceType, any>) {
|
||||
this.uiData = new UiData("UI data selected by user on time scrub");
|
||||
this.uiDataCallback(this.uiData);
|
||||
this.notifyViewCallback(this.uiData);
|
||||
}
|
||||
|
||||
public notifyUiEvent() {
|
||||
const oldUiDataText = this.uiData ? this.uiData.text : "";
|
||||
this.uiData = new UiData(oldUiDataText);
|
||||
this.uiData.text += " | UI data updated because of UI event";
|
||||
this.uiDataCallback(this.uiData!);
|
||||
this.notifyViewCallback(this.uiData!);
|
||||
}
|
||||
|
||||
private readonly uiDataCallback: UiDataCallbackType;
|
||||
private uiData?: UiData;
|
||||
readonly notifyViewCallback: NotifyViewCallbackType;
|
||||
uiData?: UiData;
|
||||
}
|
||||
|
||||
export {Presenter, UiDataCallbackType};
|
||||
export {Presenter};
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
class UiData {
|
||||
constructor(public text: string) {
|
||||
console.log("new UI data", text);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,5 +50,4 @@ describe("ViewerWindowManagerComponent", () => {
|
||||
const divInputValue = htmlElement.querySelector(".viewer-window-manager div.input-value");
|
||||
expect(divInputValue?.innerHTML).toContain("UI Data Value");
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -22,10 +22,11 @@ import {
|
||||
import {UiData} from "./ui_data";
|
||||
|
||||
@Component({
|
||||
selector: "viewer-window-manager",
|
||||
template: `
|
||||
<div class="viewer-window-manager">
|
||||
<div class="title">Window Manager</div>
|
||||
<div class="input-value">Input value: {{inputData.text}}</div>
|
||||
<div class="input-value">Input value: {{inputData?.text}}</div>
|
||||
<div class="button"><button mat-icon-button (click)="generateOutputEvent($event)">Output event!</button></div>
|
||||
</div>
|
||||
`
|
||||
|
||||
@@ -27,6 +27,10 @@ class ViewerWindowManager implements Viewer {
|
||||
this.view.addEventListener("outputEvent", () => this.presenter.notifyUiEvent());
|
||||
}
|
||||
|
||||
public getTitle() {
|
||||
return "Window Manager";
|
||||
}
|
||||
|
||||
public notifyCurrentTraceEntries(entries: Map<TraceType, any>): void {
|
||||
this.presenter.notifyCurrentTraceEntries(entries);
|
||||
}
|
||||
@@ -35,6 +39,10 @@ class ViewerWindowManager implements Viewer {
|
||||
return this.view;
|
||||
}
|
||||
|
||||
public getDependencies(): TraceType[] {
|
||||
return ViewerWindowManager.DEPENDENCIES;
|
||||
}
|
||||
|
||||
public static readonly DEPENDENCIES: TraceType[] = [TraceType.WINDOW_MANAGER];
|
||||
private view: HTMLElement;
|
||||
private presenter: Presenter;
|
||||
|
||||
Reference in New Issue
Block a user