Prep for new data interfaces from parsers.

Added geometry utils, which are used in rects/timeline components to improve typing by unifying a common Rect interface.

Bug: b/307906075 b/311403104
Test: npm run test:unit:ci
Change-Id: Idf39b22c69dc4de7f178afb3b301ffed707b8bb0
diff --git a/tools/winscope/src/app/components/timeline/expanded-timeline/abstract_timeline_row_component.ts b/tools/winscope/src/app/components/timeline/expanded-timeline/abstract_timeline_row_component.ts
index 7795728..dd9ab56 100644
--- a/tools/winscope/src/app/components/timeline/expanded-timeline/abstract_timeline_row_component.ts
+++ b/tools/winscope/src/app/components/timeline/expanded-timeline/abstract_timeline_row_component.ts
@@ -15,6 +15,7 @@
  */
 
 import {ElementRef, EventEmitter, SimpleChanges} from '@angular/core';
+import {Point} from 'common/geometry_utils';
 import {TraceEntry} from 'trace/trace';
 import {TracePosition} from 'trace/trace_position';
 import {CanvasDrawer} from './canvas_drawer';
@@ -100,10 +101,12 @@
   async handleMouseDown(e: MouseEvent) {
     e.preventDefault();
     e.stopPropagation();
-    const mouseX = e.offsetX;
-    const mouseY = e.offsetY;
+    const mousePoint = {
+      x: e.offsetX,
+      y: e.offsetY,
+    };
 
-    const transitionEntry = await this.getEntryAt(mouseX, mouseY);
+    const transitionEntry = await this.getEntryAt(mousePoint);
     // TODO: This can probably get made better by getting the transition and checking both the end and start timestamps match
     if (transitionEntry && transitionEntry !== this.selectedEntry) {
       this.redraw();
@@ -115,23 +118,25 @@
   handleMouseMove(e: MouseEvent) {
     e.preventDefault();
     e.stopPropagation();
-    const mouseX = e.offsetX;
-    const mouseY = e.offsetY;
+    const mousePoint = {
+      x: e.offsetX,
+      y: e.offsetY,
+    };
 
-    this.updateCursor(mouseX, mouseY);
-    this.onHover(mouseX, mouseY);
+    this.updateCursor(mousePoint);
+    this.onHover(mousePoint);
   }
 
-  protected async updateCursor(mouseX: number, mouseY: number) {
-    if (this.getEntryAt(mouseX, mouseY) !== undefined) {
+  protected async updateCursor(mousePoint: Point) {
+    if (this.getEntryAt(mousePoint) !== undefined) {
       this.getCanvas().style.cursor = 'pointer';
     } else {
       this.getCanvas().style.cursor = 'auto';
     }
   }
 
-  protected abstract getEntryAt(mouseX: number, mouseY: number): Promise<TraceEntry<T> | undefined>;
-  protected abstract onHover(mouseX: number, mouseY: number): void;
+  protected abstract getEntryAt(mousePoint: Point): Promise<TraceEntry<T> | undefined>;
+  protected abstract onHover(mousePoint: Point): void;
   protected abstract handleMouseOut(e: MouseEvent): void;
 
   protected async redraw() {
diff --git a/tools/winscope/src/app/components/timeline/expanded-timeline/canvas_drawer.ts b/tools/winscope/src/app/components/timeline/expanded-timeline/canvas_drawer.ts
index 7100d43..2190fca 100644
--- a/tools/winscope/src/app/components/timeline/expanded-timeline/canvas_drawer.ts
+++ b/tools/winscope/src/app/components/timeline/expanded-timeline/canvas_drawer.ts
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+import {Rect} from 'common/geometry_utils';
+
 export class CanvasDrawer {
   private canvas!: HTMLCanvasElement;
   private ctx!: CanvasRenderingContext2D;
@@ -27,23 +29,22 @@
     this.ctx = ctx;
   }
 
-  drawRect(drawParams: {x: number; y: number; w: number; h: number; color: string; alpha: number}) {
-    const {x, y, w, h, color, alpha} = drawParams;
+  drawRect(rect: Rect, color: string, alpha: number) {
     const rgbColor = this.hexToRgb(color);
     if (rgbColor === undefined) {
       throw new Error('Failed to parse provided hex color');
     }
     const {r, g, b} = rgbColor;
 
-    this.defineRectPath(x, y, w, h);
+    this.defineRectPath(rect);
     this.ctx.fillStyle = `rgba(${r},${g},${b},${alpha})`;
     this.ctx.fill();
 
     this.ctx.restore();
   }
 
-  drawRectBorder(x: number, y: number, w: number, h: number) {
-    this.defineRectPath(x, y, w, h);
+  drawRectBorder(rect: Rect) {
+    this.defineRectPath(rect);
     this.highlightPath();
     this.ctx.restore();
   }
@@ -79,13 +80,13 @@
     this.ctx.stroke();
   }
 
-  private defineRectPath(x: number, y: number, w: number, h: number) {
+  private defineRectPath(rect: Rect) {
     this.ctx.beginPath();
-    this.ctx.moveTo(x, y);
-    this.ctx.lineTo(x + w, y);
-    this.ctx.lineTo(x + w, y + h);
-    this.ctx.lineTo(x, y + h);
-    this.ctx.lineTo(x, y);
+    this.ctx.moveTo(rect.x, rect.y);
+    this.ctx.lineTo(rect.x + rect.w, rect.y);
+    this.ctx.lineTo(rect.x + rect.w, rect.y + rect.h);
+    this.ctx.lineTo(rect.x, rect.y + rect.h);
+    this.ctx.lineTo(rect.x, rect.y);
     this.ctx.closePath();
   }
 
diff --git a/tools/winscope/src/app/components/timeline/expanded-timeline/canvas_drawer_test.ts b/tools/winscope/src/app/components/timeline/expanded-timeline/canvas_drawer_test.ts
index 61ecafe..b759cec 100644
--- a/tools/winscope/src/app/components/timeline/expanded-timeline/canvas_drawer_test.ts
+++ b/tools/winscope/src/app/components/timeline/expanded-timeline/canvas_drawer_test.ts
@@ -24,7 +24,7 @@
 
     const canvasDrawer = new CanvasDrawer();
     canvasDrawer.setCanvas(actualCanvas);
-    canvasDrawer.drawRect({x: 10, y: 10, w: 10, h: 10, color: '#333333', alpha: 1.0});
+    canvasDrawer.drawRect({x: 10, y: 10, w: 10, h: 10}, '#333333', 1.0);
 
     expect(pixelsAllMatch(actualCanvas, expectedCanvas)).toBeFalse();
 
@@ -39,7 +39,7 @@
 
     const canvasDrawer = new CanvasDrawer();
     canvasDrawer.setCanvas(actualCanvas);
-    canvasDrawer.drawRect({x: 10, y: 10, w: 10, h: 10, color: '#333333', alpha: 1.0});
+    canvasDrawer.drawRect({x: 10, y: 10, w: 10, h: 10}, '#333333', 1.0);
 
     const expectedCtx = assertDefined(expectedCanvas.getContext('2d'));
     expectedCtx.fillStyle = '#333333';
@@ -55,7 +55,7 @@
 
     const canvasDrawer = new CanvasDrawer();
     canvasDrawer.setCanvas(actualCanvas);
-    canvasDrawer.drawRect({x: 10, y: 10, w: 10, h: 10, color: '#333333', alpha: 0.5});
+    canvasDrawer.drawRect({x: 10, y: 10, w: 10, h: 10}, '#333333', 0.5);
 
     const expectedCtx = assertDefined(expectedCanvas.getContext('2d'));
     expectedCtx.fillStyle = 'rgba(51,51,51,0.5)';
@@ -71,7 +71,7 @@
 
     const canvasDrawer = new CanvasDrawer();
     canvasDrawer.setCanvas(actualCanvas);
-    canvasDrawer.drawRectBorder(10, 10, 10, 10);
+    canvasDrawer.drawRectBorder({x: 10, y: 10, w: 10, h: 10});
 
     const expectedCtx = assertDefined(expectedCanvas.getContext('2d'));
 
@@ -93,8 +93,8 @@
 
     const canvasDrawer = new CanvasDrawer();
     canvasDrawer.setCanvas(actualCanvas);
-    canvasDrawer.drawRect({x: 200, y: 200, w: 10, h: 10, color: '#333333', alpha: 1.0});
-    canvasDrawer.drawRect({x: 95, y: 95, w: 50, h: 50, color: '#333333', alpha: 1.0});
+    canvasDrawer.drawRect({x: 200, y: 200, w: 10, h: 10}, '#333333', 1.0);
+    canvasDrawer.drawRect({x: 95, y: 95, w: 50, h: 50}, '#333333', 1.0);
 
     const expectedCtx = assertDefined(expectedCanvas.getContext('2d'));
     expectedCtx.fillStyle = '#333333';
diff --git a/tools/winscope/src/app/components/timeline/expanded-timeline/default_timeline_row_component.ts b/tools/winscope/src/app/components/timeline/expanded-timeline/default_timeline_row_component.ts
index 3eb5093..4686def 100644
--- a/tools/winscope/src/app/components/timeline/expanded-timeline/default_timeline_row_component.ts
+++ b/tools/winscope/src/app/components/timeline/expanded-timeline/default_timeline_row_component.ts
@@ -15,7 +15,7 @@
  */
 
 import {Component, ElementRef, EventEmitter, Input, Output, ViewChild} from '@angular/core';
-import {isPointInRect} from 'common/geometry_utils';
+import {isPointInRect, Point, Rect} from 'common/geometry_utils';
 import {TimeRange, Timestamp} from 'common/time';
 import {Trace, TraceEntry} from 'trace/trace';
 import {TracePosition} from 'trace/trace_position';
@@ -57,8 +57,8 @@
     }
   }
 
-  override onHover(mouseX: number, mouseY: number) {
-    this.drawEntryHover(mouseX, mouseY);
+  override onHover(mousePoint: Point) {
+    this.drawEntryHover(mousePoint);
   }
 
   override handleMouseOut(e: MouseEvent) {
@@ -70,8 +70,8 @@
     this.hoveringSegment = undefined;
   }
 
-  private async drawEntryHover(mouseX: number, mouseY: number) {
-    const currentHoverEntry = (await this.getEntryAt(mouseX, mouseY))?.getTimestamp();
+  private async drawEntryHover(mousePoint: Point) {
+    const currentHoverEntry = (await this.getEntryAt(mousePoint))?.getTimestamp();
 
     if (this.hoveringEntry === currentHoverEntry) {
       return;
@@ -89,23 +89,20 @@
       return;
     }
 
-    const {x, y, w, h} = this.entryRect(this.hoveringEntry);
+    const rect = this.entryRect(this.hoveringEntry);
 
-    this.canvasDrawer.drawRect({x, y, w, h, color: this.color, alpha: 1.0});
-    this.canvasDrawer.drawRectBorder(x, y, w, h);
+    this.canvasDrawer.drawRect(rect, this.color, 1.0);
+    this.canvasDrawer.drawRectBorder(rect);
   }
 
-  protected override async getEntryAt(
-    mouseX: number,
-    mouseY: number
-  ): Promise<TraceEntry<{}> | undefined> {
-    const timestampOfClick = this.getTimestampOf(mouseX);
+  protected override async getEntryAt(mousePoint: Point): Promise<TraceEntry<{}> | undefined> {
+    const timestampOfClick = this.getTimestampOf(mousePoint.x);
     const candidateEntry = this.trace.findLastLowerOrEqualEntry(timestampOfClick);
 
     if (candidateEntry !== undefined) {
       const timestamp = candidateEntry.getTimestamp();
-      const {x, y, w, h} = this.entryRect(timestamp);
-      if (isPointInRect({x: mouseX, y: mouseY}, {x, y, w, h})) {
+      const rect = this.entryRect(timestamp);
+      if (isPointInRect(mousePoint, rect)) {
         return candidateEntry;
       }
     }
@@ -121,7 +118,7 @@
     return Math.floor(this.canvasDrawer.getScaledCanvasWidth() - this.entryWidth);
   }
 
-  private entryRect(entry: Timestamp, padding = 0) {
+  private entryRect(entry: Timestamp, padding = 0): Rect {
     const xPos = this.getXPosOf(entry);
 
     return {
@@ -156,9 +153,9 @@
   }
 
   private drawEntry(entry: Timestamp) {
-    const {x, y, w, h} = this.entryRect(entry);
+    const rect = this.entryRect(entry);
 
-    this.canvasDrawer.drawRect({x, y, w, h, color: this.color, alpha: 0.2});
+    this.canvasDrawer.drawRect(rect, this.color, 0.2);
   }
 
   private drawSelectedEntry() {
@@ -166,8 +163,8 @@
       return;
     }
 
-    const {x, y, w, h} = this.entryRect(this.selectedEntry.getTimestamp(), 1);
-    this.canvasDrawer.drawRect({x, y, w, h, color: this.color, alpha: 1.0});
-    this.canvasDrawer.drawRectBorder(x, y, w, h);
+    const rect = this.entryRect(this.selectedEntry.getTimestamp(), 1);
+    this.canvasDrawer.drawRect(rect, this.color, 1.0);
+    this.canvasDrawer.drawRectBorder(rect);
   }
 }
diff --git a/tools/winscope/src/app/components/timeline/expanded-timeline/default_timeline_row_component_test.ts b/tools/winscope/src/app/components/timeline/expanded-timeline/default_timeline_row_component_test.ts
index 98b751b..6b3fd40 100644
--- a/tools/winscope/src/app/components/timeline/expanded-timeline/default_timeline_row_component_test.ts
+++ b/tools/winscope/src/app/components/timeline/expanded-timeline/default_timeline_row_component_test.ts
@@ -81,38 +81,46 @@
     const canvasWidth = component.canvasDrawer.getScaledCanvasWidth() - width;
 
     expect(drawRectSpy).toHaveBeenCalledTimes(4);
-    expect(drawRectSpy).toHaveBeenCalledWith({
-      x: 0,
-      y: 0,
-      w: width,
-      h: height,
-      color: component.color,
-      alpha,
-    });
-    expect(drawRectSpy).toHaveBeenCalledWith({
-      x: Math.floor((canvasWidth * 2) / 100),
-      y: 0,
-      w: width,
-      h: height,
-      color: component.color,
-      alpha,
-    });
-    expect(drawRectSpy).toHaveBeenCalledWith({
-      x: Math.floor((canvasWidth * 5) / 100),
-      y: 0,
-      w: width,
-      h: height,
-      color: component.color,
-      alpha,
-    });
-    expect(drawRectSpy).toHaveBeenCalledWith({
-      x: Math.floor((canvasWidth * 60) / 100),
-      y: 0,
-      w: width,
-      h: height,
-      color: component.color,
-      alpha,
-    });
+    expect(drawRectSpy).toHaveBeenCalledWith(
+      {
+        x: 0,
+        y: 0,
+        w: width,
+        h: height,
+      },
+      component.color,
+      alpha
+    );
+    expect(drawRectSpy).toHaveBeenCalledWith(
+      {
+        x: Math.floor((canvasWidth * 2) / 100),
+        y: 0,
+        w: width,
+        h: height,
+      },
+      component.color,
+      alpha
+    );
+    expect(drawRectSpy).toHaveBeenCalledWith(
+      {
+        x: Math.floor((canvasWidth * 5) / 100),
+        y: 0,
+        w: width,
+        h: height,
+      },
+      component.color,
+      alpha
+    );
+    expect(drawRectSpy).toHaveBeenCalledWith(
+      {
+        x: Math.floor((canvasWidth * 60) / 100),
+        y: 0,
+        w: width,
+        h: height,
+      },
+      component.color,
+      alpha
+    );
   });
 
   it('can draw entries zoomed in', async () => {
@@ -132,14 +140,16 @@
     const canvasWidth = component.canvasDrawer.getScaledCanvasWidth() - width;
 
     expect(drawRectSpy).toHaveBeenCalledTimes(1);
-    expect(drawRectSpy).toHaveBeenCalledWith({
-      x: Math.floor((canvasWidth * 10) / 25),
-      y: 0,
-      w: width,
-      h: height,
-      color: component.color,
-      alpha,
-    });
+    expect(drawRectSpy).toHaveBeenCalledWith(
+      {
+        x: Math.floor((canvasWidth * 10) / 25),
+        y: 0,
+        w: width,
+        h: height,
+      },
+      component.color,
+      alpha
+    );
   });
 
   it('can draw hovering entry', async () => {
@@ -167,17 +177,24 @@
 
     expect(assertDefined(component.hoveringEntry).getValueNs()).toBe(10n);
     expect(drawRectSpy).toHaveBeenCalledTimes(1);
-    expect(drawRectSpy).toHaveBeenCalledWith({
+    expect(drawRectSpy).toHaveBeenCalledWith(
+      {
+        x: 0,
+        y: 0,
+        w: 32,
+        h: 32,
+      },
+      component.color,
+      1.0
+    );
+
+    expect(drawRectBorderSpy).toHaveBeenCalledTimes(1);
+    expect(drawRectBorderSpy).toHaveBeenCalledWith({
       x: 0,
       y: 0,
       w: 32,
       h: 32,
-      color: component.color,
-      alpha: 1.0,
     });
-
-    expect(drawRectBorderSpy).toHaveBeenCalledTimes(1);
-    expect(drawRectBorderSpy).toHaveBeenCalledWith(0, 0, 32, 32);
   });
 
   it('can draw correct entry on click of first entry', async () => {
@@ -272,17 +289,17 @@
       expectedTimestampNs
     );
 
-    expect(drawRectSpy).toHaveBeenCalledTimes(rectSpyCalls);
-    expect(drawRectSpy).toHaveBeenCalledWith({
+    const expectedRect = {
       x: xPos + 1,
       y: 1,
       w: 30,
       h: 30,
-      color: component.color,
-      alpha: 1.0,
-    });
+    };
+
+    expect(drawRectSpy).toHaveBeenCalledTimes(rectSpyCalls);
+    expect(drawRectSpy).toHaveBeenCalledWith(expectedRect, component.color, 1.0);
 
     expect(drawRectBorderSpy).toHaveBeenCalledTimes(1);
-    expect(drawRectBorderSpy).toHaveBeenCalledWith(xPos + 1, 1, 30, 30);
+    expect(drawRectBorderSpy).toHaveBeenCalledWith(expectedRect);
   }
 });
diff --git a/tools/winscope/src/app/components/timeline/expanded-timeline/transition_timeline_component.ts b/tools/winscope/src/app/components/timeline/expanded-timeline/transition_timeline_component.ts
index 262669c..c2eb07f 100644
--- a/tools/winscope/src/app/components/timeline/expanded-timeline/transition_timeline_component.ts
+++ b/tools/winscope/src/app/components/timeline/expanded-timeline/transition_timeline_component.ts
@@ -15,7 +15,7 @@
  */
 
 import {Component, ElementRef, EventEmitter, Input, Output, ViewChild} from '@angular/core';
-import {isPointInRect} from 'common/geometry_utils';
+import {isPointInRect, Point, Rect} from 'common/geometry_utils';
 import {ElapsedTimestamp, RealTimestamp, TimeRange, Timestamp, TimestampType} from 'common/time';
 import {Transition} from 'flickerlib/common';
 import {Trace, TraceEntry} from 'trace/trace';
@@ -101,8 +101,8 @@
     return rowToUse;
   }
 
-  override onHover(mouseX: number, mouseY: number) {
-    this.drawSegmentHover(mouseX, mouseY);
+  override onHover(mousePoint: Point) {
+    this.drawSegmentHover(mousePoint);
   }
 
   override handleMouseOut(e: MouseEvent) {
@@ -113,8 +113,8 @@
     this.hoveringEntry = undefined;
   }
 
-  private async drawSegmentHover(mouseX: number, mouseY: number) {
-    const currentHoverEntry = await this.getEntryAt(mouseX, mouseY);
+  private async drawSegmentHover(mousePoint: Point) {
+    const currentHoverEntry = await this.getEntryAt(mousePoint);
 
     if (this.hoveringEntry) {
       this.canvasDrawer.clear();
@@ -129,13 +129,12 @@
 
     const hoveringSegment = await this.getSegmentForTransition(this.hoveringEntry);
     const rowToUse = this.getRowToUseFor(this.hoveringEntry);
-    const {x, y, w, h} = this.getSegmentRect(hoveringSegment.from, hoveringSegment.to, rowToUse);
-    this.canvasDrawer.drawRectBorder(x, y, w, h);
+    const rect = this.getSegmentRect(hoveringSegment.from, hoveringSegment.to, rowToUse);
+    this.canvasDrawer.drawRectBorder(rect);
   }
 
   protected override async getEntryAt(
-    mouseX: number,
-    mouseY: number
+    mousePoint: Point
   ): Promise<TraceEntry<Transition> | undefined> {
     if (this.trace.type !== TraceType.TRANSITION) {
       return undefined;
@@ -150,12 +149,8 @@
           }
           const transitionSegment = await this.getSegmentForTransition(entry);
           const rowToUse = this.getRowToUseFor(entry);
-          const {x, y, w, h} = this.getSegmentRect(
-            transitionSegment.from,
-            transitionSegment.to,
-            rowToUse
-          );
-          if (isPointInRect({x: mouseX, y: mouseY}, {x, y, w, h})) {
+          const rect = this.getSegmentRect(transitionSegment.from, transitionSegment.to, rowToUse);
+          if (isPointInRect(mousePoint, rect)) {
             return entry;
           }
           return undefined;
@@ -188,7 +183,7 @@
     return Number((BigInt(this.availableWidth) * (entry.getValueNs() - start)) / (end - start));
   }
 
-  private getSegmentRect(start: Timestamp, end: Timestamp, rowToUse: number) {
+  private getSegmentRect(start: Timestamp, end: Timestamp, rowToUse: number): Rect {
     const xPosStart = this.getXPosOf(start);
     const selectionStart = this.selectionRange.from.getValueNs();
     const selectionEnd = this.selectionRange.to.getValueNs();
@@ -248,9 +243,9 @@
   }
 
   private drawSegment(start: Timestamp, end: Timestamp, rowToUse: number, aborted: boolean) {
-    const {x, y, w, h} = this.getSegmentRect(start, end, rowToUse);
+    const rect = this.getSegmentRect(start, end, rowToUse);
     const alpha = aborted ? 0.25 : 1.0;
-    this.canvasDrawer.drawRect({x, y, w, h, color: this.color, alpha});
+    this.canvasDrawer.drawRect(rect, this.color, alpha);
   }
 
   private async drawSelectedTransitionEntry() {
@@ -262,14 +257,10 @@
 
     const transition = await this.selectedEntry.getValue();
     const rowIndex = this.getRowToUseFor(this.selectedEntry);
-    const {x, y, w, h} = this.getSegmentRect(
-      transitionSegment.from,
-      transitionSegment.to,
-      rowIndex
-    );
+    const rect = this.getSegmentRect(transitionSegment.from, transitionSegment.to, rowIndex);
     const alpha = transition.aborted ? 0.25 : 1.0;
-    this.canvasDrawer.drawRect({x, y, w, h, color: this.color, alpha});
-    this.canvasDrawer.drawRectBorder(x, y, w, h);
+    this.canvasDrawer.drawRect(rect, this.color, alpha);
+    this.canvasDrawer.drawRectBorder(rect);
   }
 
   private shouldNotRenderEntry(entry: TraceEntry<Transition>): boolean {
diff --git a/tools/winscope/src/app/components/timeline/expanded-timeline/transition_timeline_component_test.ts b/tools/winscope/src/app/components/timeline/expanded-timeline/transition_timeline_component_test.ts
index 4db18ee..a5bbd3e 100644
--- a/tools/winscope/src/app/components/timeline/expanded-timeline/transition_timeline_component_test.ts
+++ b/tools/winscope/src/app/components/timeline/expanded-timeline/transition_timeline_component_test.ts
@@ -95,22 +95,26 @@
     const width = component.canvasDrawer.getScaledCanvasWidth();
 
     expect(drawRectSpy).toHaveBeenCalledTimes(2);
-    expect(drawRectSpy).toHaveBeenCalledWith({
-      x: 0,
-      y: padding,
-      w: Math.floor(width / 5),
-      h: oneRowHeight,
-      color: component.color,
-      alpha: 1,
-    });
-    expect(drawRectSpy).toHaveBeenCalledWith({
-      x: Math.floor(width / 2),
-      y: padding,
-      w: Math.floor(width / 2),
-      h: oneRowHeight,
-      color: component.color,
-      alpha: 1,
-    });
+    expect(drawRectSpy).toHaveBeenCalledWith(
+      {
+        x: 0,
+        y: padding,
+        w: Math.floor(width / 5),
+        h: oneRowHeight,
+      },
+      component.color,
+      1
+    );
+    expect(drawRectSpy).toHaveBeenCalledWith(
+      {
+        x: Math.floor(width / 2),
+        y: padding,
+        w: Math.floor(width / 2),
+        h: oneRowHeight,
+      },
+      component.color,
+      1
+    );
   });
 
   it('can draw transitions zoomed in', async () => {
@@ -142,22 +146,26 @@
     const width = component.canvasDrawer.getScaledCanvasWidth();
 
     expect(drawRectSpy).toHaveBeenCalledTimes(2);
-    expect(drawRectSpy).toHaveBeenCalledWith({
-      x: -Math.floor(width / 10),
-      y: padding,
-      w: Math.floor(width / 5),
-      h: oneRowHeight,
-      color: component.color,
-      alpha: 1,
-    });
-    expect(drawRectSpy).toHaveBeenCalledWith({
-      x: Math.floor(width / 2),
-      y: padding,
-      w: Math.floor(width),
-      h: oneRowHeight,
-      color: component.color,
-      alpha: 1,
-    });
+    expect(drawRectSpy).toHaveBeenCalledWith(
+      {
+        x: -Math.floor(width / 10),
+        y: padding,
+        w: Math.floor(width / 5),
+        h: oneRowHeight,
+      },
+      component.color,
+      1
+    );
+    expect(drawRectSpy).toHaveBeenCalledWith(
+      {
+        x: Math.floor(width / 2),
+        y: padding,
+        w: Math.floor(width),
+        h: oneRowHeight,
+      },
+      component.color,
+      1
+    );
   });
 
   it('can draw selected entry', async () => {
@@ -189,23 +197,17 @@
     const oneRowHeight = oneRowTotalHeight - padding;
     const width = component.canvasDrawer.getScaledCanvasWidth();
 
-    expect(drawRectSpy).toHaveBeenCalledTimes(2); // once drawn as a normal entry another time with rect border
-    expect(drawRectSpy).toHaveBeenCalledWith({
+    const expectedRect = {
       x: Math.floor((width * 1) / 4),
       y: padding,
       w: Math.floor(width / 2),
       h: oneRowHeight,
-      color: component.color,
-      alpha: 0.25,
-    });
+    };
+    expect(drawRectSpy).toHaveBeenCalledTimes(2); // once drawn as a normal entry another time with rect border
+    expect(drawRectSpy).toHaveBeenCalledWith(expectedRect, component.color, 0.25);
 
     expect(drawRectBorderSpy).toHaveBeenCalledTimes(1);
-    expect(drawRectBorderSpy).toHaveBeenCalledWith(
-      Math.floor((width * 1) / 4),
-      padding,
-      Math.floor(width / 2),
-      oneRowHeight
-    );
+    expect(drawRectBorderSpy).toHaveBeenCalledWith(expectedRect);
   });
 
   it('can draw hovering entry', async () => {
@@ -242,23 +244,17 @@
     await waitToBeCalled(drawRectSpy, 1);
     await waitToBeCalled(drawRectBorderSpy, 1);
 
-    expect(drawRectSpy).toHaveBeenCalledTimes(1);
-    expect(drawRectSpy).toHaveBeenCalledWith({
+    const expectedRect = {
       x: Math.floor((width * 1) / 4),
       y: padding,
       w: Math.floor(width / 2),
       h: oneRowHeight,
-      color: component.color,
-      alpha: 0.25,
-    });
+    };
+    expect(drawRectSpy).toHaveBeenCalledTimes(1);
+    expect(drawRectSpy).toHaveBeenCalledWith(expectedRect, component.color, 0.25);
 
     expect(drawRectBorderSpy).toHaveBeenCalledTimes(1);
-    expect(drawRectBorderSpy).toHaveBeenCalledWith(
-      Math.floor((width * 1) / 4),
-      padding,
-      Math.floor(width / 2),
-      oneRowHeight
-    );
+    expect(drawRectBorderSpy).toHaveBeenCalledWith(expectedRect);
   });
 
   it('can draw overlapping transitions (default)', async () => {
@@ -291,22 +287,26 @@
     const width = component.canvasDrawer.getScaledCanvasWidth();
 
     expect(drawRectSpy).toHaveBeenCalledTimes(2);
-    expect(drawRectSpy).toHaveBeenCalledWith({
-      x: 0,
-      y: padding,
-      w: Math.floor((width * 3) / 4),
-      h: oneRowHeight,
-      color: component.color,
-      alpha: 1,
-    });
-    expect(drawRectSpy).toHaveBeenCalledWith({
-      x: Math.floor(width / 2),
-      y: padding + oneRowTotalHeight,
-      w: Math.floor(width / 2),
-      h: oneRowHeight,
-      color: component.color,
-      alpha: 1,
-    });
+    expect(drawRectSpy).toHaveBeenCalledWith(
+      {
+        x: 0,
+        y: padding,
+        w: Math.floor((width * 3) / 4),
+        h: oneRowHeight,
+      },
+      component.color,
+      1
+    );
+    expect(drawRectSpy).toHaveBeenCalledWith(
+      {
+        x: Math.floor(width / 2),
+        y: padding + oneRowTotalHeight,
+        w: Math.floor(width / 2),
+        h: oneRowHeight,
+      },
+      component.color,
+      1
+    );
   });
 
   it('can draw overlapping transitions (contained)', async () => {
@@ -339,22 +339,26 @@
     const width = component.canvasDrawer.getScaledCanvasWidth();
 
     expect(drawRectSpy).toHaveBeenCalledTimes(2);
-    expect(drawRectSpy).toHaveBeenCalledWith({
-      x: 0,
-      y: padding,
-      w: Math.floor((width * 3) / 4),
-      h: oneRowHeight,
-      color: component.color,
-      alpha: 1,
-    });
-    expect(drawRectSpy).toHaveBeenCalledWith({
-      x: Math.floor(width / 4),
-      y: padding + oneRowTotalHeight,
-      w: Math.floor(width / 4),
-      h: oneRowHeight,
-      color: component.color,
-      alpha: 1,
-    });
+    expect(drawRectSpy).toHaveBeenCalledWith(
+      {
+        x: 0,
+        y: padding,
+        w: Math.floor((width * 3) / 4),
+        h: oneRowHeight,
+      },
+      component.color,
+      1
+    );
+    expect(drawRectSpy).toHaveBeenCalledWith(
+      {
+        x: Math.floor(width / 4),
+        y: padding + oneRowTotalHeight,
+        w: Math.floor(width / 4),
+        h: oneRowHeight,
+      },
+      component.color,
+      1
+    );
   });
 
   it('can draw aborted transitions', async () => {
@@ -383,14 +387,16 @@
     const width = component.canvasDrawer.getScaledCanvasWidth();
 
     expect(drawRectSpy).toHaveBeenCalledTimes(1);
-    expect(drawRectSpy).toHaveBeenCalledWith({
-      x: Math.floor((width * 1) / 4),
-      y: padding,
-      w: Math.floor(width / 2),
-      h: oneRowHeight,
-      color: component.color,
-      alpha: 0.25,
-    });
+    expect(drawRectSpy).toHaveBeenCalledWith(
+      {
+        x: Math.floor((width * 1) / 4),
+        y: padding,
+        w: Math.floor(width / 2),
+        h: oneRowHeight,
+      },
+      component.color,
+      0.25
+    );
   });
 
   it('does not render transition with min creation time', async () => {
diff --git a/tools/winscope/src/app/components/timeline/mini-timeline/drawer/canvas_mouse_handler_impl.ts b/tools/winscope/src/app/components/timeline/mini-timeline/drawer/canvas_mouse_handler_impl.ts
index 18e4cd2..dccdd8d 100644
--- a/tools/winscope/src/app/components/timeline/mini-timeline/drawer/canvas_mouse_handler_impl.ts
+++ b/tools/winscope/src/app/components/timeline/mini-timeline/drawer/canvas_mouse_handler_impl.ts
@@ -15,6 +15,7 @@
  */
 
 import {assertDefined} from 'common/assert_utils';
+import {Point} from 'common/geometry_utils';
 import {CanvasMouseHandler, DragListener, DropListener} from './canvas_mouse_handler';
 import {DraggableCanvasObject} from './draggable_canvas_object';
 import {MiniTimelineDrawer} from './mini_timeline_drawer';
@@ -30,7 +31,7 @@
   constructor(
     private drawer: MiniTimelineDrawer,
     private defaultCursor: string = 'auto',
-    private onUnhandledMouseDown: (x: number, y: number) => void = (x, y) => {}
+    private onUnhandledMouseDown: (point: Point) => void = (point) => {}
   ) {
     this.drawer.canvas.addEventListener('mousemove', (event) => {
       this.handleMouseMove(event);
@@ -66,49 +67,49 @@
   private handleMouseDown(e: MouseEvent) {
     e.preventDefault();
     e.stopPropagation();
-    const {mouseX, mouseY} = this.getPos(e);
+    const mousePoint = this.getPos(e);
 
-    const clickedObject = this.objectAt(mouseX, mouseY);
+    const clickedObject = this.objectAt(mousePoint);
     if (clickedObject !== undefined) {
       this.draggingObject = clickedObject;
     } else {
-      this.onUnhandledMouseDown(mouseX, mouseY);
+      this.onUnhandledMouseDown(mousePoint);
     }
-    this.updateCursor(mouseX, mouseY);
+    this.updateCursor(mousePoint);
   }
 
   private handleMouseMove(e: MouseEvent) {
     e.preventDefault();
     e.stopPropagation();
-    const {mouseX, mouseY} = this.getPos(e);
+    const mousePoint = this.getPos(e);
 
     if (this.draggingObject !== undefined) {
       const onDragCallback = this.onDrag.get(this.draggingObject);
       if (onDragCallback !== undefined) {
-        onDragCallback(mouseX, mouseY);
+        onDragCallback(mousePoint.x, mousePoint.y);
       }
     }
 
-    this.updateCursor(mouseX, mouseY);
+    this.updateCursor(mousePoint);
   }
 
   private handleMouseUp(e: MouseEvent) {
     e.preventDefault();
     e.stopPropagation();
-    const {mouseX, mouseY} = this.getPos(e);
+    const mousePoint = this.getPos(e);
 
     if (this.draggingObject !== undefined) {
       const onDropCallback = this.onDrop.get(this.draggingObject);
       if (onDropCallback !== undefined) {
-        onDropCallback(mouseX, mouseY);
+        onDropCallback(mousePoint.x, mousePoint.y);
       }
     }
 
     this.draggingObject = undefined;
-    this.updateCursor(mouseX, mouseY);
+    this.updateCursor(mousePoint);
   }
 
-  private getPos(e: MouseEvent) {
+  private getPos(e: MouseEvent): Point {
     let mouseX = e.offsetX;
     const mouseY = e.offsetY;
 
@@ -120,11 +121,11 @@
       mouseX = this.drawer.getWidth() - this.drawer.padding.right;
     }
 
-    return {mouseX, mouseY};
+    return {x: mouseX, y: mouseY};
   }
 
-  private updateCursor(mouseX: number, mouseY: number) {
-    const hoverObject = this.objectAt(mouseX, mouseY);
+  private updateCursor(mousePoint: Point) {
+    const hoverObject = this.objectAt(mousePoint);
     if (hoverObject !== undefined) {
       if (hoverObject === this.draggingObject) {
         this.drawer.canvas.style.cursor = 'grabbing';
@@ -136,11 +137,11 @@
     }
   }
 
-  private objectAt(mouseX: number, mouseY: number): DraggableCanvasObject | undefined {
+  private objectAt(mousePoint: Point): DraggableCanvasObject | undefined {
     for (const object of this.draggableObjects) {
       const ctx = assertDefined(this.drawer.canvas.getContext('2d'));
       object.definePath(ctx);
-      if (ctx.isPointInPath(mouseX, mouseY)) {
+      if (ctx.isPointInPath(mousePoint.x, mousePoint.y)) {
         return object;
       }
     }
diff --git a/tools/winscope/src/app/components/timeline/mini-timeline/drawer/mini_timeline_drawer_impl.ts b/tools/winscope/src/app/components/timeline/mini-timeline/drawer/mini_timeline_drawer_impl.ts
index fda8db7..e739128 100644
--- a/tools/winscope/src/app/components/timeline/mini-timeline/drawer/mini_timeline_drawer_impl.ts
+++ b/tools/winscope/src/app/components/timeline/mini-timeline/drawer/mini_timeline_drawer_impl.ts
@@ -16,6 +16,7 @@
 
 import {Color} from 'app/colors';
 import {TRACE_INFO} from 'app/trace_info';
+import {Point} from 'common/geometry_utils';
 import {Padding} from 'common/padding';
 import {Timestamp} from 'common/time';
 import {CanvasMouseHandler} from './canvas_mouse_handler';
@@ -78,8 +79,8 @@
 
     this.ctx = ctx;
 
-    const onUnhandledClickInternal = async (x: number, y: number) => {
-      this.onUnhandledClick(this.input.transformer.untransform(x));
+    const onUnhandledClickInternal = async (mousePoint: Point) => {
+      this.onUnhandledClick(this.input.transformer.untransform(mousePoint.x));
     };
     this.handler = new CanvasMouseHandlerImpl(this, 'pointer', onUnhandledClickInternal);
 
diff --git a/tools/winscope/src/app/components/timeline/mini-timeline/slider_component.ts b/tools/winscope/src/app/components/timeline/mini-timeline/slider_component.ts
index 82dc303..5badc62 100644
--- a/tools/winscope/src/app/components/timeline/mini-timeline/slider_component.ts
+++ b/tools/winscope/src/app/components/timeline/mini-timeline/slider_component.ts
@@ -28,6 +28,7 @@
 } from '@angular/core';
 import {Color} from 'app/colors';
 import {assertDefined} from 'common/assert_utils';
+import {Point} from 'common/geometry_utils';
 import {TimeRange, Timestamp} from 'common/time';
 import {TracePosition} from 'trace/trace_position';
 import {Transformer} from './transformer';
@@ -126,7 +127,7 @@
 
   dragging = false;
   sliderWidth = 0;
-  dragPosition = {x: 0, y: 0};
+  dragPosition: Point = {x: 0, y: 0};
   viewInitialized = false;
   cursorOffset = 0;
 
diff --git a/tools/winscope/src/common/geometry_utils.ts b/tools/winscope/src/common/geometry_utils.ts
index 2abeefc..14f2aea 100644
--- a/tools/winscope/src/common/geometry_utils.ts
+++ b/tools/winscope/src/common/geometry_utils.ts
@@ -14,15 +14,7 @@
  * limitations under the License.
  */
 
-export function isPointInRect(
-  point: {x: number; y: number},
-  rect: {
-    x: number;
-    y: number;
-    w: number;
-    h: number;
-  }
-): boolean {
+export function isPointInRect(point: Point, rect: Rect): boolean {
   return (
     rect.x <= point.x &&
     point.x <= rect.x + rect.w &&
@@ -30,3 +22,26 @@
     point.y <= rect.y + rect.h
   );
 }
+
+export interface Point {
+  x: number;
+  y: number;
+}
+
+export interface Rect {
+  x: number;
+  y: number;
+  w: number;
+  h: number;
+}
+
+export interface TransformMatrix {
+  dsdx: number;
+  dtdx: number;
+  tx: number;
+  dsdy: number;
+  dtdy: number;
+  ty: number;
+}
+
+export const IDENTITY_MATRIX = {dsdx: 1, dtdx: 0, tx: 0, dsdy: 0, dtdy: 1, ty: 0};
diff --git a/tools/winscope/src/common/tree_utils.ts b/tools/winscope/src/common/tree_utils.ts
index f94dad9..5047551 100644
--- a/tools/winscope/src/common/tree_utils.ts
+++ b/tools/winscope/src/common/tree_utils.ts
@@ -14,16 +14,19 @@
  * limitations under the License.
  */
 
-interface TreeNode {
+interface TreeUtilsNode {
   name: string;
-  parent?: TreeNode;
-  children?: TreeNode[];
+  parent?: TreeUtilsNode;
+  children?: TreeUtilsNode[];
 }
 
-type FilterType = (node: TreeNode | undefined | null) => boolean;
+type FilterType = (node: TreeUtilsNode | undefined | null) => boolean;
 
 class TreeUtils {
-  static findDescendantNode(node: TreeNode, isTargetNode: FilterType): TreeNode | undefined {
+  static findDescendantNode(
+    node: TreeUtilsNode,
+    isTargetNode: FilterType
+  ): TreeUtilsNode | undefined {
     if (isTargetNode(node)) {
       return node;
     }
@@ -42,7 +45,10 @@
     return undefined;
   }
 
-  static findAncestorNode(node: TreeNode, isTargetNode: FilterType): TreeNode | undefined {
+  static findAncestorNode(
+    node: TreeUtilsNode,
+    isTargetNode: FilterType
+  ): TreeUtilsNode | undefined {
     let ancestor = node.parent;
 
     while (ancestor && !isTargetNode(ancestor)) {
@@ -53,7 +59,7 @@
   }
 
   static makeNodeFilter(filterString: string): FilterType {
-    const filter = (item: TreeNode | undefined | null) => {
+    const filter = (item: TreeUtilsNode | undefined | null) => {
       if (item) {
         const regex = new RegExp(filterString, 'i');
         return filterString.length === 0 || regex.test(`${item.name}`);
@@ -64,4 +70,4 @@
   }
 }
 
-export {TreeNode, TreeUtils, FilterType};
+export {TreeUtilsNode, TreeUtils, FilterType};
diff --git a/tools/winscope/src/parsers/transform_utils.ts b/tools/winscope/src/parsers/transform_utils.ts
new file mode 100644
index 0000000..6a98c7e
--- /dev/null
+++ b/tools/winscope/src/parsers/transform_utils.ts
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2023 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 {IDENTITY_MATRIX, TransformMatrix} from 'common/geometry_utils';
+
+export class Transform {
+  constructor(public type: TransformType, public matrix: TransformMatrix) {}
+}
+
+export enum TransformType {
+  EMPTY = 0x0,
+  TRANSLATE_VAL = 0x0001,
+  ROTATE_VAL = 0x0002,
+  SCALE_VAL = 0x0004,
+  FLIP_H_VAL = 0x0100,
+  FLIP_V_VAL = 0x0200,
+  ROT_90_VAL = 0x0400,
+  ROT_INVALID_VAL = 0x8000,
+}
+
+export const EMPTY_TRANSFORM = new Transform(TransformType.EMPTY, IDENTITY_MATRIX);
+
+export class TransformUtils {
+  static isValidTransform(transform: any): boolean {
+    if (!transform) return false;
+    return transform.dsdx * transform.dtdy !== transform.dtdx * transform.dsdy;
+  }
+
+  static getTransform(transform: any, position: any): Transform {
+    const transformType = transform?.type ?? 0;
+    const x = position?.x ?? 0;
+    const y = position?.y ?? 0;
+
+    if (!transform || TransformUtils.isSimpleTransform(transformType)) {
+      return TransformUtils.getDefaultTransform(transformType, x, y);
+    }
+
+    return new Transform(transformType, {
+      dsdx: transform?.matrix.dsdx ?? 0,
+      dtdx: transform?.matrix.dtdx ?? 0,
+      tx: x,
+      dsdy: transform?.matrix.dsdy ?? 0,
+      dtdy: transform?.matrix.dtdy ?? 0,
+      ty: y,
+    });
+  }
+
+  static isSimpleRotation(transform: any): boolean {
+    return !(transform?.type
+      ? TransformUtils.isFlagSet(transform.type, TransformType.ROT_INVALID_VAL)
+      : false);
+  }
+
+  private static getDefaultTransform(type: TransformType, x: number, y: number): Transform {
+    // IDENTITY
+    if (!type) {
+      return new Transform(type, {dsdx: 1, dtdx: 0, tx: x, dsdy: 0, dtdy: 1, ty: y});
+    }
+
+    // ROT_270 = ROT_90|FLIP_H|FLIP_V
+    if (
+      TransformUtils.isFlagSet(
+        type,
+        TransformType.ROT_90_VAL | TransformType.FLIP_V_VAL | TransformType.FLIP_H_VAL
+      )
+    ) {
+      return new Transform(type, {dsdx: 0, dtdx: -1, tx: x, dsdy: 1, dtdy: 0, ty: y});
+    }
+
+    // ROT_180 = FLIP_H|FLIP_V
+    if (TransformUtils.isFlagSet(type, TransformType.FLIP_V_VAL | TransformType.FLIP_H_VAL)) {
+      return new Transform(type, {dsdx: -1, dtdx: 0, tx: x, dsdy: 0, dtdy: -1, ty: y});
+    }
+
+    // ROT_90
+    if (TransformUtils.isFlagSet(type, TransformType.ROT_90_VAL)) {
+      return new Transform(type, {dsdx: 0, dtdx: 1, tx: x, dsdy: -1, dtdy: 0, ty: y});
+    }
+
+    // IDENTITY
+    if (TransformUtils.isFlagClear(type, TransformType.SCALE_VAL | TransformType.ROTATE_VAL)) {
+      return new Transform(type, {dsdx: 1, dtdx: 0, tx: x, dsdy: 0, dtdy: 1, ty: y});
+    }
+
+    throw new Error(`Unknown transform type ${type}`);
+  }
+
+  private static isFlagSet(type: number, bits: number): boolean {
+    type = type || 0;
+    return (type & bits) === bits;
+  }
+
+  private static isFlagClear(type: number, bits: number): boolean {
+    return (type & bits) === 0;
+  }
+
+  private static isSimpleTransform(type: number): boolean {
+    return TransformUtils.isFlagClear(
+      type,
+      TransformType.ROT_INVALID_VAL | TransformType.SCALE_VAL
+    );
+  }
+}
diff --git a/tools/winscope/src/trace/trace_data_utils.ts b/tools/winscope/src/trace/trace_data_utils.ts
new file mode 100644
index 0000000..273f427
--- /dev/null
+++ b/tools/winscope/src/trace/trace_data_utils.ts
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 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 {Rect} from 'common/geometry_utils';
+import {Transform} from 'parsers/transform_utils';
+
+/* DATA INTERFACES */
+class Item {
+  constructor(public id: string, public label: string) {}
+}
+
+class TreeNode<T> extends Item {
+  constructor(id: string, label: string, public children: Array<TreeNode<T> & T> = []) {
+    super(id, label);
+  }
+}
+
+class TraceRect extends Item implements Rect {
+  constructor(
+    id: string,
+    label: string,
+    public x: number,
+    public y: number,
+    public w: number,
+    public h: number,
+    public cornerRadius: number,
+    public transform: Transform,
+    public zAbs: number,
+    public groupId: number,
+    public isVisible: boolean,
+    public isDisplay: boolean,
+    public isVirtual: boolean
+  ) {
+    super(id, label);
+  }
+}
+
+type Proto = any;
+
+/* GET PROPERTIES TYPES */
+type GetPropertiesFromProtoType = (proto: Proto) => PropertyTreeNode;
+type GetPropertiesType = () => PropertyTreeNode;
+
+/* MIXINS */
+interface PropertiesGetter {
+  getProperties: GetPropertiesType;
+}
+
+enum PropertySource {
+  PROTO,
+  DEFAULT,
+  CALCULATED,
+}
+
+interface PropertyDetails {
+  value: string;
+  source: PropertySource;
+}
+
+interface AssociatedProperty {
+  property: null | PropertyDetails;
+}
+
+interface Constructor<T = {}> {
+  new (...args: any[]): T;
+}
+
+function mixin<T, U>(a: T, b: U): T & U {
+  const ret = {};
+  Object.assign(ret, a);
+  Object.assign(ret, b);
+  return ret as T & U;
+}
+
+type PropertyTreeNode = TreeNode<AssociatedProperty> & AssociatedProperty;
+type HierarchyTreeNode = TreeNode<PropertiesGetter> & PropertiesGetter;
+
+const EMPTY_OBJ_STRING = '{empty}';
+const EMPTY_ARRAY_STRING = '[empty]';
+
+export {
+  Item,
+  TreeNode,
+  TraceRect,
+  Proto,
+  GetPropertiesType,
+  GetPropertiesFromProtoType,
+  PropertiesGetter,
+  PropertySource,
+  PropertyDetails,
+  AssociatedProperty,
+  Constructor,
+  mixin,
+  PropertyTreeNode,
+  HierarchyTreeNode,
+  EMPTY_OBJ_STRING,
+  EMPTY_ARRAY_STRING,
+};
diff --git a/tools/winscope/src/trace/trace_type.ts b/tools/winscope/src/trace/trace_type.ts
index cd4081f..fff5de5 100644
--- a/tools/winscope/src/trace/trace_type.ts
+++ b/tools/winscope/src/trace/trace_type.ts
@@ -16,6 +16,7 @@
 import {Cuj, Event, Transition} from 'flickerlib/common';
 import {LayerTraceEntry} from 'flickerlib/layers/LayerTraceEntry';
 import {WindowManagerState} from 'flickerlib/windows/WindowManagerState';
+import {HierarchyTreeNode, TraceRect, TreeNode} from 'trace/trace_data_utils';
 import {LogMessage} from './protolog';
 import {ScreenRecordingTraceEntry} from './screen_recording';
 
@@ -52,32 +53,46 @@
 export type FrameData = any;
 export type WindowData = any;
 
+export interface TreeAndRects {
+  tree: HierarchyTreeNode;
+  rects: TraceRect[];
+}
+
 export interface TraceEntryTypeMap {
-  [TraceType.PROTO_LOG]: LogMessage;
-  [TraceType.SURFACE_FLINGER]: LayerTraceEntry;
-  [TraceType.SCREEN_RECORDING]: ScreenRecordingTraceEntry;
-  [TraceType.SYSTEM_UI]: object;
-  [TraceType.TRANSACTIONS]: object;
-  [TraceType.TRANSACTIONS_LEGACY]: object;
-  [TraceType.WAYLAND]: object;
-  [TraceType.WAYLAND_DUMP]: object;
-  [TraceType.WINDOW_MANAGER]: WindowManagerState;
-  [TraceType.INPUT_METHOD_CLIENTS]: object;
-  [TraceType.INPUT_METHOD_MANAGER_SERVICE]: object;
-  [TraceType.INPUT_METHOD_SERVICE]: object;
-  [TraceType.EVENT_LOG]: Event;
-  [TraceType.WM_TRANSITION]: object;
-  [TraceType.SHELL_TRANSITION]: object;
-  [TraceType.TRANSITION]: Transition;
-  [TraceType.CUJS]: Cuj;
-  [TraceType.TAG]: object;
-  [TraceType.ERROR]: object;
-  [TraceType.TEST_TRACE_STRING]: string;
-  [TraceType.TEST_TRACE_NUMBER]: number;
-  [TraceType.VIEW_CAPTURE]: object;
-  [TraceType.VIEW_CAPTURE_LAUNCHER_ACTIVITY]: FrameData;
-  [TraceType.VIEW_CAPTURE_TASKBAR_DRAG_LAYER]: FrameData;
-  [TraceType.VIEW_CAPTURE_TASKBAR_OVERLAY_DRAG_LAYER]: FrameData;
+  [TraceType.PROTO_LOG]: {new: LogMessage; legacy: LogMessage};
+  [TraceType.SURFACE_FLINGER]: {new: TreeAndRects; legacy: LayerTraceEntry};
+  [TraceType.SCREEN_RECORDING]: {new: ScreenRecordingTraceEntry; legacy: ScreenRecordingTraceEntry};
+  [TraceType.SYSTEM_UI]: {new: object; legacy: object};
+  [TraceType.TRANSACTIONS]: {new: TreeNode<any>; legacy: object};
+  [TraceType.TRANSACTIONS_LEGACY]: {new: TreeNode<any>; legacy: object};
+  [TraceType.WAYLAND]: {new: object; legacy: object};
+  [TraceType.WAYLAND_DUMP]: {new: object; legacy: object};
+  [TraceType.WINDOW_MANAGER]: {new: TreeAndRects; legacy: WindowManagerState};
+  [TraceType.INPUT_METHOD_CLIENTS]: {new: TreeNode<any>; legacy: object};
+  [TraceType.INPUT_METHOD_MANAGER_SERVICE]: {new: TreeNode<any>; legacy: object};
+  [TraceType.INPUT_METHOD_SERVICE]: {new: TreeNode<any>; legacy: object};
+  [TraceType.EVENT_LOG]: {new: TreeNode<any>; legacy: Event};
+  [TraceType.WM_TRANSITION]: {new: TreeNode<any>; legacy: object};
+  [TraceType.SHELL_TRANSITION]: {new: TreeNode<any>; legacy: object};
+  [TraceType.TRANSITION]: {new: TreeNode<any>; legacy: Transition};
+  [TraceType.CUJS]: {new: TreeNode<any>; legacy: Cuj};
+  [TraceType.TAG]: {new: object; legacy: object};
+  [TraceType.ERROR]: {new: object; legacy: object};
+  [TraceType.TEST_TRACE_STRING]: {new: string; legacy: string};
+  [TraceType.TEST_TRACE_NUMBER]: {new: number; legacy: number};
+  [TraceType.VIEW_CAPTURE]: {new: TreeNode<any>; legacy: object};
+  [TraceType.VIEW_CAPTURE_LAUNCHER_ACTIVITY]: {
+    new: TreeAndRects;
+    legacy: FrameData;
+  };
+  [TraceType.VIEW_CAPTURE_TASKBAR_DRAG_LAYER]: {
+    new: TreeAndRects;
+    legacy: FrameData;
+  };
+  [TraceType.VIEW_CAPTURE_TASKBAR_OVERLAY_DRAG_LAYER]: {
+    new: TreeAndRects;
+    legacy: FrameData;
+  };
 }
 
 export class TraceTypeUtils {
diff --git a/tools/winscope/src/trace/traces.ts b/tools/winscope/src/trace/traces.ts
index 5161e7c..20f2cdd 100644
--- a/tools/winscope/src/trace/traces.ts
+++ b/tools/winscope/src/trace/traces.ts
@@ -22,12 +22,12 @@
 export class Traces {
   private traces = new Map<TraceType, Trace<{}>>();
 
-  setTrace<T extends TraceType>(type: T, trace: Trace<TraceEntryTypeMap[T]>) {
+  setTrace<T extends TraceType>(type: T, trace: Trace<TraceEntryTypeMap[T]['legacy']>) {
     this.traces.set(type, trace);
   }
 
-  getTrace<T extends TraceType>(type: T): Trace<TraceEntryTypeMap[T]> | undefined {
-    return this.traces.get(type) as Trace<TraceEntryTypeMap[T]> | undefined;
+  getTrace<T extends TraceType>(type: T): Trace<TraceEntryTypeMap[T]['legacy']> | undefined {
+    return this.traces.get(type) as Trace<TraceEntryTypeMap[T]['legacy']> | undefined;
   }
 
   deleteTrace<T extends TraceType>(type: T) {
diff --git a/tools/winscope/src/viewers/common/surface_flinger_utils.ts b/tools/winscope/src/viewers/common/surface_flinger_utils.ts
index 413c585..1bca894 100644
--- a/tools/winscope/src/viewers/common/surface_flinger_utils.ts
+++ b/tools/winscope/src/viewers/common/surface_flinger_utils.ts
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
+import {TransformMatrix} from 'common/geometry_utils';
 import {Layer, LayerTraceEntry} from 'flickerlib/common';
-import {Rectangle, TransformMatrix} from 'viewers/components/rects/types2d';
+import {UiRect} from 'viewers/components/rects/types2d';
 import {UserOptions} from './user_options';
 
 export class SurfaceFlingerUtils {
@@ -23,7 +24,7 @@
     entry: LayerTraceEntry,
     viewCapturePackageNames: string[],
     hierarchyUserOptions: UserOptions
-  ): Rectangle[] {
+  ): UiRect[] {
     const layerRects = SurfaceFlingerUtils.makeLayerRects(
       entry,
       viewCapturePackageNames,
@@ -37,7 +38,7 @@
     entry: LayerTraceEntry,
     viewCapturePackageNames: string[],
     hierarchyUserOptions: UserOptions
-  ): Rectangle[] {
+  ): UiRect[] {
     return entry.flattenedLayers
       .filter((layer: Layer) => {
         return SurfaceFlingerUtils.isLayerToRenderInRectsComponent(layer, hierarchyUserOptions);
@@ -45,9 +46,11 @@
       .sort(SurfaceFlingerUtils.compareLayerZ)
       .map((it: Layer) => {
         const transform: TransformMatrix = it.rect.transform?.matrix ?? it.rect.transform;
-        const rect: Rectangle = {
-          topLeft: {x: it.rect.left, y: it.rect.top},
-          bottomRight: {x: it.rect.right, y: it.rect.bottom},
+        const rect: UiRect = {
+          x: it.rect.left,
+          y: it.rect.top,
+          w: it.rect.right - it.rect.left,
+          h: it.rect.bottom - it.rect.top,
           label: it.rect.label,
           transform,
           isVisible: it.isVisible,
@@ -65,16 +68,18 @@
       });
   }
 
-  private static makeDisplayRects(entry: LayerTraceEntry): Rectangle[] {
+  private static makeDisplayRects(entry: LayerTraceEntry): UiRect[] {
     if (!entry.displays) {
       return [];
     }
 
     return entry.displays?.map((display: any) => {
       const transform: TransformMatrix = display.transform?.matrix ?? display.transform;
-      const rect: Rectangle = {
-        topLeft: {x: 0, y: 0},
-        bottomRight: {x: display.size.width, y: display.size.height},
+      const rect: UiRect = {
+        x: 0,
+        y: 0,
+        w: display.size.width,
+        h: display.size.height,
         label: 'Display',
         transform,
         isVisible: false,
diff --git a/tools/winscope/src/viewers/common/tree_generator.ts b/tools/winscope/src/viewers/common/tree_generator.ts
index e5d2fc3..244353d 100644
--- a/tools/winscope/src/viewers/common/tree_generator.ts
+++ b/tools/winscope/src/viewers/common/tree_generator.ts
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {FilterType, TreeNode} from 'common/tree_utils';
+import {FilterType, TreeUtilsNode} from 'common/tree_utils';
 import {ObjectFormatter} from 'flickerlib/ObjectFormatter';
 import {TraceTreeNode} from 'trace/trace_tree_node';
 import {
@@ -155,7 +155,7 @@
     }
   }
 
-  private filterMatches(item?: TreeNode | null): boolean {
+  private filterMatches(item?: TreeUtilsNode | null): boolean {
     return this.filter(item) ?? false;
   }
 
diff --git a/tools/winscope/src/viewers/common/tree_transformer.ts b/tools/winscope/src/viewers/common/tree_transformer.ts
index 15b7640..86218bd 100644
--- a/tools/winscope/src/viewers/common/tree_transformer.ts
+++ b/tools/winscope/src/viewers/common/tree_transformer.ts
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {FilterType, TreeNode} from 'common/tree_utils';
+import {FilterType, TreeUtilsNode} from 'common/tree_utils';
 import {ObjectFormatter} from 'flickerlib/ObjectFormatter';
 import {TraceTreeNode} from 'trace/trace_tree_node';
 
@@ -343,7 +343,7 @@
 
   private filterMatches(item: PropertiesDump | null): boolean {
     //TODO: fix PropertiesDump type. What is it? Why does it declare only a "key" property and yet it is used as a TreeNode?
-    return this.filter(item as TreeNode) ?? false;
+    return this.filter(item as TreeUtilsNode) ?? false;
   }
 
   private transformProperties(
diff --git a/tools/winscope/src/viewers/components/rects/canvas.ts b/tools/winscope/src/viewers/components/rects/canvas.ts
index b106768..e0436f8 100644
--- a/tools/winscope/src/viewers/components/rects/canvas.ts
+++ b/tools/winscope/src/viewers/components/rects/canvas.ts
@@ -13,10 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+import {TransformMatrix} from 'common/geometry_utils';
 import * as THREE from 'three';
 import {CSS2DObject, CSS2DRenderer} from 'three/examples/jsm/renderers/CSS2DRenderer';
 import {ViewerEvents} from 'viewers/common/viewer_events';
-import {Circle3D, ColorType, Label3D, Point3D, Rect3D, Scene3D, TransformMatrix} from './types3d';
+import {Circle3D, ColorType, Label3D, Point3D, Rect3D, Scene3D} from './types3d';
 
 export class Canvas {
   static readonly TARGET_SCENE_DIAGONAL = 4;
diff --git a/tools/winscope/src/viewers/components/rects/mapper3d.ts b/tools/winscope/src/viewers/components/rects/mapper3d.ts
index ad47dae..0643669 100644
--- a/tools/winscope/src/viewers/components/rects/mapper3d.ts
+++ b/tools/winscope/src/viewers/components/rects/mapper3d.ts
@@ -14,17 +14,9 @@
  * limitations under the License.
  */
 
-import {Rectangle, Size} from 'viewers/components/rects/types2d';
-import {
-  Box3D,
-  ColorType,
-  Distance2D,
-  Label3D,
-  Point3D,
-  Rect3D,
-  Scene3D,
-  TransformMatrix,
-} from './types3d';
+import {IDENTITY_MATRIX, TransformMatrix} from 'common/geometry_utils';
+import {Size, UiRect} from 'viewers/components/rects/types2d';
+import {Box3D, ColorType, Distance2D, Label3D, Point3D, Rect3D, Scene3D} from './types3d';
 
 class Mapper3D {
   private static readonly CAMERA_ROTATION_FACTOR_INIT = 1;
@@ -38,16 +30,8 @@
   private static readonly ZOOM_FACTOR_MIN = 0.1;
   private static readonly ZOOM_FACTOR_MAX = 8.5;
   private static readonly ZOOM_FACTOR_STEP = 0.2;
-  private static readonly IDENTITY_TRANSFORM: TransformMatrix = {
-    dsdx: 1,
-    dsdy: 0,
-    tx: 0,
-    dtdx: 0,
-    dtdy: 1,
-    ty: 0,
-  };
 
-  private rects: Rectangle[] = [];
+  private rects: UiRect[] = [];
   private highlightedRectId: string = '';
   private cameraRotationFactor = Mapper3D.CAMERA_ROTATION_FACTOR_INIT;
   private zSpacingFactor = Mapper3D.Z_SPACING_FACTOR_INIT;
@@ -57,7 +41,7 @@
   private showVirtualMode = false; // by default don't show virtual displays
   private currentDisplayId = 0; // default stack id is usually 0
 
-  setRects(rects: Rectangle[]) {
+  setRects(rects: UiRect[]) {
     this.rects = rects;
   }
 
@@ -148,7 +132,7 @@
     return scene;
   }
 
-  private selectRectsToDraw(rects: Rectangle[]): Rectangle[] {
+  private selectRectsToDraw(rects: UiRect[]): UiRect[] {
     rects = rects.filter((rect) => rect.displayId === this.currentDisplayId);
 
     if (this.showOnlyVisibleMode) {
@@ -162,7 +146,7 @@
     return rects;
   }
 
-  private computeRects(rects2d: Rectangle[]): Rect3D[] {
+  private computeRects(rects2d: UiRect[]): Rect3D[] {
     let visibleRectsSoFar = 0;
     let visibleRectsTotal = 0;
     let nonVisibleRectsSoFar = 0;
@@ -206,13 +190,13 @@
       const rect = {
         id: rect2d.id,
         topLeft: {
-          x: rect2d.topLeft.x,
-          y: rect2d.topLeft.y,
+          x: rect2d.x,
+          y: rect2d.y,
           z,
         },
         bottomRight: {
-          x: rect2d.bottomRight.x,
-          y: rect2d.bottomRight.y,
+          x: rect2d.x + rect2d.w,
+          y: rect2d.y + rect2d.h,
           z,
         },
         isOversized: false,
@@ -220,7 +204,7 @@
         darkFactor,
         colorType: this.getColorType(rect2d),
         isClickable: rect2d.isClickable,
-        transform: rect2d.transform ?? Mapper3D.IDENTITY_TRANSFORM,
+        transform: rect2d.transform ?? IDENTITY_MATRIX,
       };
       return this.cropOversizedRect(rect, maxDisplaySize);
     });
@@ -228,7 +212,7 @@
     return rects3d;
   }
 
-  private getColorType(rect2d: Rectangle): ColorType {
+  private getColorType(rect2d: UiRect): ColorType {
     let colorType: ColorType;
     if (this.highlightedRectId === rect2d.id) {
       colorType = ColorType.HIGHLIGHTED;
@@ -242,19 +226,15 @@
     return colorType;
   }
 
-  private getMaxDisplaySize(rects2d: Rectangle[]): Size {
+  private getMaxDisplaySize(rects2d: UiRect[]): Size {
     const displays = rects2d.filter((rect2d) => rect2d.isDisplay);
 
     let maxWidth = 0;
     let maxHeight = 0;
     if (displays.length > 0) {
-      maxWidth = Math.max(
-        ...displays.map((rect2d): number => Math.abs(rect2d.topLeft.x - rect2d.bottomRight.x))
-      );
+      maxWidth = Math.max(...displays.map((rect2d): number => Math.abs(rect2d.w)));
 
-      maxHeight = Math.max(
-        ...displays.map((rect2d): number => Math.abs(rect2d.topLeft.y - rect2d.bottomRight.y))
-      );
+      maxHeight = Math.max(...displays.map((rect2d): number => Math.abs(rect2d.h)));
     }
     return {
       width: maxWidth,
@@ -286,7 +266,7 @@
     return rect3d;
   }
 
-  private computeLabels(rects2d: Rectangle[], rects3d: Rect3D[]): Label3D[] {
+  private computeLabels(rects2d: UiRect[], rects3d: Rect3D[]): Label3D[] {
     const labels3d: Label3D[] = [];
 
     let labelY =
@@ -305,12 +285,12 @@
 
       const bottomLeft: Point3D = {
         x: rect3d.topLeft.x,
-        y: rect3d.bottomRight.y,
+        y: rect3d.topLeft.y,
         z: rect3d.topLeft.z,
       };
       const topRight: Point3D = {
         x: rect3d.bottomRight.x,
-        y: rect3d.topLeft.y,
+        y: rect3d.bottomRight.y,
         z: rect3d.bottomRight.z,
       };
       const lineStarts = [
diff --git a/tools/winscope/src/viewers/components/rects/rects_component.ts b/tools/winscope/src/viewers/components/rects/rects_component.ts
index 0c43ad5..4781592 100644
--- a/tools/winscope/src/viewers/components/rects/rects_component.ts
+++ b/tools/winscope/src/viewers/components/rects/rects_component.ts
@@ -15,7 +15,7 @@
  */
 import {Component, ElementRef, HostListener, Inject, Input, OnDestroy, OnInit} from '@angular/core';
 import {RectDblClickDetail, ViewerEvents} from 'viewers/common/viewer_events';
-import {Rectangle} from 'viewers/components/rects/types2d';
+import {UiRect} from 'viewers/components/rects/types2d';
 import {Canvas} from './canvas';
 import {Mapper3D} from './mapper3d';
 import {Distance2D} from './types3d';
@@ -185,11 +185,11 @@
   @Input() title = 'title';
   @Input() enableShowVirtualButton: boolean = true;
   @Input() zoomFactor: number = 1;
-  @Input() set rects(rects: Rectangle[]) {
+  @Input() set rects(rects: UiRect[]) {
     this.internalRects = rects;
     this.drawLargeRectsAndLabels();
   }
-  @Input() set miniRects(rects: Rectangle[] | undefined) {
+  @Input() set miniRects(rects: UiRect[] | undefined) {
     this.internalMiniRects = rects;
     this.drawMiniRects();
   }
@@ -208,8 +208,8 @@
     this.drawLargeRectsAndLabels();
   }
 
-  private internalRects: Rectangle[] = [];
-  private internalMiniRects?: Rectangle[];
+  private internalRects: UiRect[] = [];
+  private internalMiniRects?: UiRect[];
   private internalDisplayIds: number[] = [];
   private internalHighlightedItem: string = '';
 
diff --git a/tools/winscope/src/viewers/components/rects/rects_component_test.ts b/tools/winscope/src/viewers/components/rects/rects_component_test.ts
index feda1dd..09e87ef 100644
--- a/tools/winscope/src/viewers/components/rects/rects_component_test.ts
+++ b/tools/winscope/src/viewers/components/rects/rects_component_test.ts
@@ -21,7 +21,7 @@
 import {MatRadioModule} from '@angular/material/radio';
 import {MatSliderModule} from '@angular/material/slider';
 import {RectsComponent} from 'viewers/components/rects/rects_component';
-import {Rectangle} from 'viewers/components/rects/types2d';
+import {UiRect} from 'viewers/components/rects/types2d';
 import {Canvas} from './canvas';
 
 describe('RectsComponent', () => {
@@ -64,9 +64,11 @@
   it('draws scene when input data changes', async () => {
     spyOn(Canvas.prototype, 'draw').and.callThrough();
 
-    const inputRect: Rectangle = {
-      topLeft: {x: 0, y: 0},
-      bottomRight: {x: 1, y: -1},
+    const inputRect: UiRect = {
+      x: 0,
+      y: 0,
+      w: 1,
+      h: 1,
       label: 'rectangle1',
       transform: {
         dsdx: 1,
diff --git a/tools/winscope/src/viewers/components/rects/types2d.ts b/tools/winscope/src/viewers/components/rects/types2d.ts
index badb59c..fe34955 100644
--- a/tools/winscope/src/viewers/components/rects/types2d.ts
+++ b/tools/winscope/src/viewers/components/rects/types2d.ts
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-export interface Rectangle {
-  topLeft: Point;
-  bottomRight: Point;
+import {Rect, TransformMatrix} from 'common/geometry_utils';
+
+export interface UiRect extends Rect {
   label: string;
   transform?: TransformMatrix;
   isVisible: boolean;
@@ -30,21 +30,7 @@
   hasContent?: boolean;
 }
 
-export interface Point {
-  x: number;
-  y: number;
-}
-
 export interface Size {
   width: number;
   height: number;
 }
-
-export interface TransformMatrix {
-  dsdx: number;
-  dsdy: number;
-  dtdx: number;
-  dtdy: number;
-  tx: number;
-  ty: number;
-}
diff --git a/tools/winscope/src/viewers/components/rects/types3d.ts b/tools/winscope/src/viewers/components/rects/types3d.ts
index efb239c..fa06e00 100644
--- a/tools/winscope/src/viewers/components/rects/types3d.ts
+++ b/tools/winscope/src/viewers/components/rects/types3d.ts
@@ -14,9 +14,7 @@
  * limitations under the License.
  */
 
-import {TransformMatrix} from 'viewers/components/rects/types2d';
-
-export {TransformMatrix};
+import {TransformMatrix} from 'common/geometry_utils';
 
 export enum ColorType {
   VISIBLE,
diff --git a/tools/winscope/src/viewers/viewer_surface_flinger/presenter_test.ts b/tools/winscope/src/viewers/viewer_surface_flinger/presenter_test.ts
index 5b3627e..0264ad8 100644
--- a/tools/winscope/src/viewers/viewer_surface_flinger/presenter_test.ts
+++ b/tools/winscope/src/viewers/viewer_surface_flinger/presenter_test.ts
@@ -107,8 +107,10 @@
   it('creates input data for rects view', async () => {
     await presenter.onAppEvent(positionUpdate);
     expect(uiData.rects.length).toBeGreaterThan(0);
-    expect(uiData.rects[0].topLeft).toEqual({x: 0, y: 0});
-    expect(uiData.rects[0].bottomRight).toEqual({x: 1080, y: 74});
+    expect(uiData.rects[0].x).toEqual(0);
+    expect(uiData.rects[0].y).toEqual(0);
+    expect(uiData.rects[0].w).toEqual(1080);
+    expect(uiData.rects[0].h).toEqual(74);
   });
 
   it('updates pinned items', () => {
diff --git a/tools/winscope/src/viewers/viewer_surface_flinger/ui_data.ts b/tools/winscope/src/viewers/viewer_surface_flinger/ui_data.ts
index bc65e63..36fe0ae 100644
--- a/tools/winscope/src/viewers/viewer_surface_flinger/ui_data.ts
+++ b/tools/winscope/src/viewers/viewer_surface_flinger/ui_data.ts
@@ -17,11 +17,11 @@
 import {TraceType} from 'trace/trace_type';
 import {HierarchyTreeNode, PropertiesTreeNode} from 'viewers/common/ui_tree_utils';
 import {UserOptions} from 'viewers/common/user_options';
-import {Rectangle} from 'viewers/components/rects/types2d';
+import {UiRect} from 'viewers/components/rects/types2d';
 
 export class UiData {
   dependencies: TraceType[];
-  rects: Rectangle[] = [];
+  rects: UiRect[] = [];
   displayIds: number[] = [];
   highlightedItem: string = '';
   highlightedProperty: string = '';
diff --git a/tools/winscope/src/viewers/viewer_view_capture/presenter.ts b/tools/winscope/src/viewers/viewer_view_capture/presenter.ts
index a21287e..579f626 100644
--- a/tools/winscope/src/viewers/viewer_view_capture/presenter.ts
+++ b/tools/winscope/src/viewers/viewer_view_capture/presenter.ts
@@ -28,7 +28,7 @@
 import {HierarchyTreeNode, PropertiesTreeNode} from 'viewers/common/ui_tree_utils';
 import {UserOptions} from 'viewers/common/user_options';
 import {ViewCaptureUtils} from 'viewers/common/view_capture_utils';
-import {Rectangle} from 'viewers/components/rects/types2d';
+import {UiRect} from 'viewers/components/rects/types2d';
 import {UiData} from './ui_data';
 
 export class Presenter {
@@ -153,7 +153,7 @@
     }
 
     this.uiData = new UiData(
-      this.generateViewCaptureRectangles(),
+      this.generateViewCaptureUiRects(),
       this.uiData?.sfRects,
       tree,
       this.hierarchyUserOptions,
@@ -167,19 +167,15 @@
     this.notifyUiDataCallback(this.uiData);
   }
 
-  private generateViewCaptureRectangles(): Rectangle[] {
-    const rectangles: Rectangle[] = [];
+  private generateViewCaptureUiRects(): UiRect[] {
+    const rectangles: UiRect[] = [];
 
     function inner(node: any /* ViewNode */) {
-      const aRectangle: Rectangle = {
-        topLeft: {
-          x: node.boxPos.left,
-          y: node.boxPos.top,
-        },
-        bottomRight: {
-          x: node.boxPos.left + node.boxPos.width,
-          y: node.boxPos.top + node.boxPos.height,
-        },
+      const aUiRect: UiRect = {
+        x: node.boxPos.left,
+        y: node.boxPos.top,
+        w: node.boxPos.width,
+        h: node.boxPos.height,
         label: '',
         transform: undefined,
         isVisible: node.isVisible,
@@ -192,7 +188,7 @@
         depth: node.depth,
         hasContent: node.isVisible,
       };
-      rectangles.push(aRectangle);
+      rectangles.push(aUiRect);
       node.children.forEach((it: any) /* ViewNode */ => inner(it));
     }
     if (this.selectedFrameData?.node) {
diff --git a/tools/winscope/src/viewers/viewer_view_capture/presenter_test.ts b/tools/winscope/src/viewers/viewer_view_capture/presenter_test.ts
index bdf9bd4..a57f9b5 100644
--- a/tools/winscope/src/viewers/viewer_view_capture/presenter_test.ts
+++ b/tools/winscope/src/viewers/viewer_view_capture/presenter_test.ts
@@ -91,8 +91,10 @@
   it('creates input data for rects view', async () => {
     await presenter.onAppEvent(positionUpdate);
     expect(uiData.rects.length).toBeGreaterThan(0);
-    expect(uiData.rects[0].topLeft).toEqual({x: 0, y: 0});
-    expect(uiData.rects[0].bottomRight).toEqual({x: 1080, y: 249});
+    expect(uiData.rects[0].x).toEqual(0);
+    expect(uiData.rects[0].y).toEqual(0);
+    expect(uiData.rects[0].w).toEqual(1080);
+    expect(uiData.rects[0].h).toEqual(249);
   });
 
   it('updates pinned items', async () => {
diff --git a/tools/winscope/src/viewers/viewer_view_capture/ui_data.ts b/tools/winscope/src/viewers/viewer_view_capture/ui_data.ts
index 744f6ea..2b39050 100644
--- a/tools/winscope/src/viewers/viewer_view_capture/ui_data.ts
+++ b/tools/winscope/src/viewers/viewer_view_capture/ui_data.ts
@@ -17,15 +17,15 @@
 import {TraceType, ViewNode} from 'trace/trace_type';
 import {HierarchyTreeNode, PropertiesTreeNode} from 'viewers/common/ui_tree_utils';
 import {UserOptions} from 'viewers/common/user_options';
-import {Rectangle} from 'viewers/components/rects/types2d';
+import {UiRect} from 'viewers/components/rects/types2d';
 
 export class UiData {
   readonly dependencies: TraceType[] = [TraceType.VIEW_CAPTURE];
   readonly displayPropertyGroups = false;
 
   constructor(
-    readonly rects: Rectangle[],
-    public sfRects: Rectangle[] | undefined,
+    readonly rects: UiRect[],
+    public sfRects: UiRect[] | undefined,
     public tree: HierarchyTreeNode | null,
     public hierarchyUserOptions: UserOptions,
     public propertiesUserOptions: UserOptions,
diff --git a/tools/winscope/src/viewers/viewer_window_manager/presenter.ts b/tools/winscope/src/viewers/viewer_window_manager/presenter.ts
index ffed296..4ac0faa 100644
--- a/tools/winscope/src/viewers/viewer_window_manager/presenter.ts
+++ b/tools/winscope/src/viewers/viewer_window_manager/presenter.ts
@@ -16,6 +16,7 @@
 
 import {AppEvent, AppEventType} from 'app/app_event';
 import {assertDefined} from 'common/assert_utils';
+import {TransformMatrix} from 'common/geometry_utils';
 import {PersistentStoreProxy} from 'common/persistent_store_proxy';
 import {FilterType, TreeUtils} from 'common/tree_utils';
 import {DisplayContent} from 'flickerlib/windows/DisplayContent';
@@ -29,7 +30,7 @@
 import {TreeTransformer} from 'viewers/common/tree_transformer';
 import {HierarchyTreeNode, PropertiesTreeNode} from 'viewers/common/ui_tree_utils';
 import {UserOptions} from 'viewers/common/user_options';
-import {Rectangle, TransformMatrix} from 'viewers/components/rects/types2d';
+import {UiRect} from 'viewers/components/rects/types2d';
 import {UiData} from './ui_data';
 
 type NotifyViewCallbackType = (uiData: UiData) => void;
@@ -194,7 +195,7 @@
     });
   }
 
-  private generateRects(entry: TraceTreeNode): Rectangle[] {
+  private generateRects(entry: TraceTreeNode): UiRect[] {
     const identityMatrix: TransformMatrix = {
       dsdx: 1,
       dsdy: 0,
@@ -203,11 +204,13 @@
       dtdy: 1,
       ty: 0,
     };
-    const displayRects: Rectangle[] =
+    const displayRects: UiRect[] =
       entry.displays?.map((display: DisplayContent) => {
-        const rect: Rectangle = {
-          topLeft: {x: display.displayRect.left, y: display.displayRect.top},
-          bottomRight: {x: display.displayRect.right, y: display.displayRect.bottom},
+        const rect: UiRect = {
+          x: display.displayRect.left,
+          y: display.displayRect.top,
+          w: display.displayRect.right - display.displayRect.left,
+          h: display.displayRect.bottom - display.displayRect.top,
           label: `Display - ${display.title}`,
           transform: identityMatrix,
           isVisible: false, //TODO: check if displayRect.ref.isVisible exists
@@ -221,13 +224,15 @@
         return rect;
       }) ?? [];
 
-    const windowRects: Rectangle[] =
+    const windowRects: UiRect[] =
       entry.windowStates
         ?.sort((a: any, b: any) => b.computedZ - a.computedZ)
         .map((it: any) => {
-          const rect: Rectangle = {
-            topLeft: {x: it.rect.left, y: it.rect.top},
-            bottomRight: {x: it.rect.right, y: it.rect.bottom},
+          const rect: UiRect = {
+            x: it.rect.left,
+            y: it.rect.top,
+            w: it.rect.right - it.rect.left,
+            h: it.rect.bottom - it.rect.top,
             label: it.rect.label,
             transform: identityMatrix,
             isVisible: it.isVisible,
diff --git a/tools/winscope/src/viewers/viewer_window_manager/presenter_test.ts b/tools/winscope/src/viewers/viewer_window_manager/presenter_test.ts
index aecc7e5..4ed8f0f 100644
--- a/tools/winscope/src/viewers/viewer_window_manager/presenter_test.ts
+++ b/tools/winscope/src/viewers/viewer_window_manager/presenter_test.ts
@@ -113,8 +113,10 @@
   it('creates input data for rects view', async () => {
     await presenter.onAppEvent(positionUpdate);
     expect(uiData.rects.length).toBeGreaterThan(0);
-    expect(uiData.rects[0].topLeft).toEqual({x: 0, y: 2326});
-    expect(uiData.rects[0].bottomRight).toEqual({x: 1080, y: 2400});
+    expect(uiData.rects[0].x).toEqual(0);
+    expect(uiData.rects[0].y).toEqual(2326);
+    expect(uiData.rects[0].w).toEqual(1080);
+    expect(uiData.rects[0].h).toEqual(74);
   });
 
   it('updates pinned items', async () => {
diff --git a/tools/winscope/src/viewers/viewer_window_manager/ui_data.ts b/tools/winscope/src/viewers/viewer_window_manager/ui_data.ts
index ee9ad3c..320615e 100644
--- a/tools/winscope/src/viewers/viewer_window_manager/ui_data.ts
+++ b/tools/winscope/src/viewers/viewer_window_manager/ui_data.ts
@@ -16,11 +16,11 @@
 import {TraceType} from 'trace/trace_type';
 import {HierarchyTreeNode, PropertiesTreeNode} from 'viewers/common/ui_tree_utils';
 import {UserOptions} from 'viewers/common/user_options';
-import {Rectangle} from 'viewers/components/rects/types2d';
+import {UiRect} from 'viewers/components/rects/types2d';
 
 export class UiData {
   dependencies: TraceType[];
-  rects: Rectangle[] = [];
+  rects: UiRect[] = [];
   displayIds: number[] = [];
   highlightedItem: string = '';
   highlightedProperty: string = '';