Merge "perfetto-ui: Draw area selection as a box"
diff --git a/ui/src/frontend/frontend_local_state.ts b/ui/src/frontend/frontend_local_state.ts
index 97acfd3..a64c13a 100644
--- a/ui/src/frontend/frontend_local_state.ts
+++ b/ui/src/frontend/frontend_local_state.ts
@@ -100,8 +100,6 @@
localOnlyMode = false;
sidebarVisible = true;
showPanningHint = false;
- // This is used to calculate the tracks within a Y range for area selection.
- areaY: Range = {};
visibleTracks = new Set<string>();
prevVisibleTracks = new Set<string>();
searchIndex = -1;
@@ -109,6 +107,12 @@
scrollToTrackId?: string|number;
httpRpcState: HttpRpcState = {connected: false};
newVersionAvailable = false;
+
+ // This is used to calculate the tracks within a Y range for area selection.
+ areaY: Range = {};
+ // True if the user is in the process of doing an area selection.
+ selectingArea = false;
+
private scrollBarWidth?: number;
private _omniboxState: OmniboxState = {
diff --git a/ui/src/frontend/pan_and_zoom_handler.ts b/ui/src/frontend/pan_and_zoom_handler.ts
index 1f504b4..5d2af09 100644
--- a/ui/src/frontend/pan_and_zoom_handler.ts
+++ b/ui/src/frontend/pan_and_zoom_handler.ts
@@ -44,7 +44,7 @@
const WHEEL_ZOOM_SPEED = -0.02;
const EDITING_RANGE_CURSOR = 'ew-resize';
-const DRAG_CURSOR = 'text';
+const DRAG_CURSOR = 'default';
const PAN_CURSOR = 'move';
enum Pan {
@@ -98,25 +98,38 @@
private onSelection:
(dragStartX: number, dragStartY: number, prevX: number, currentX: number,
currentY: number, editing: boolean) => void;
+ private selectingStarted: () => void;
+ private selectingEnded: () => void;
- constructor(
- {element, contentOffsetX, onPanned, onZoomed, editSelection, onSelection}:
- {
- element: HTMLElement,
- contentOffsetX: number,
- onPanned: (movedPx: number) => void,
- onZoomed: (zoomPositionPx: number, zoomRatio: number) => void,
- editSelection: (currentPx: number) => boolean,
- onSelection:
- (dragStartX: number, dragStartY: number, prevX: number,
- currentX: number, currentY: number, editing: boolean) => void,
- }) {
+ constructor({
+ element,
+ contentOffsetX,
+ onPanned,
+ onZoomed,
+ editSelection,
+ onSelection,
+ selectingStarted,
+ selectingEnded
+ }: {
+ element: HTMLElement,
+ contentOffsetX: number,
+ onPanned: (movedPx: number) => void,
+ onZoomed: (zoomPositionPx: number, zoomRatio: number) => void,
+ editSelection: (currentPx: number) => boolean,
+ onSelection:
+ (dragStartX: number, dragStartY: number, prevX: number,
+ currentX: number, currentY: number, editing: boolean) => void,
+ selectingStarted: () => void,
+ selectingEnded: () => void,
+ }) {
this.element = element;
this.contentOffsetX = contentOffsetX;
this.onPanned = onPanned;
this.onZoomed = onZoomed;
this.editSelection = editSelection;
this.onSelection = onSelection;
+ this.selectingStarted = selectingStarted;
+ this.selectingEnded = selectingEnded;
document.body.addEventListener('keydown', this.boundOnKeyDown);
document.body.addEventListener('keyup', this.boundOnKeyUp);
@@ -147,6 +160,7 @@
if (edit) {
this.element.style.cursor = EDITING_RANGE_CURSOR;
} else if (!this.shiftDown) {
+ this.selectingStarted();
this.element.style.cursor = DRAG_CURSOR;
}
},
@@ -155,6 +169,7 @@
this.element.style.cursor = this.shiftDown ? PAN_CURSOR : DRAG_CURSOR;
dragStartX = -1;
dragStartY = -1;
+ this.selectingEnded();
});
}
diff --git a/ui/src/frontend/panel_container.ts b/ui/src/frontend/panel_container.ts
index 8698422..b2b34ba 100644
--- a/ui/src/frontend/panel_container.ts
+++ b/ui/src/frontend/panel_container.ts
@@ -343,10 +343,87 @@
this.ctx.restore();
panelYStart += panelHeight;
}
+
+ this.drawTopLayerOnCanvas();
const redrawDur = debugNow() - redrawStart;
this.updatePerfStats(redrawDur, panels.length, totalOnCanvas);
}
+ // The panels each draw on the canvas but some details need to be drawn across
+ // the whole canvas rather than per panel.
+ private drawTopLayerOnCanvas() {
+ if (!this.ctx) return;
+ if (this.attrs.kind !== 'TRACKS') return;
+ const selection = globals.frontendLocalState.selectedArea;
+ const area = selection.area;
+ if (area === undefined ||
+ globals.frontendLocalState.areaY.start === undefined ||
+ globals.frontendLocalState.areaY.end === undefined ||
+ !globals.frontendLocalState.selectingArea) {
+ return;
+ }
+ if (this.panelPositions.length === 0) return;
+ this.ctx.save();
+ this.ctx.strokeStyle = 'rgba(52,69,150)';
+ this.ctx.lineWidth = 1;
+ const canvasYStart =
+ Math.floor(this.scrollTop - this.getCanvasOverdrawHeightPerSide());
+ this.ctx.translate(TRACK_SHELL_WIDTH, -canvasYStart);
+
+ const startX = globals.frontendLocalState.timeScale.timeToPx(area.startSec);
+ const endX = globals.frontendLocalState.timeScale.timeToPx(area.endSec);
+ const firstPanelY = this.panelPositions[0].y;
+ const boxTop = Math.min(
+ globals.frontendLocalState.areaY.start,
+ globals.frontendLocalState.areaY.end) +
+ TOPBAR_HEIGHT;
+ const boxBottom = Math.max(
+ globals.frontendLocalState.areaY.start,
+ globals.frontendLocalState.areaY.end) +
+ TOPBAR_HEIGHT;
+
+ let snapStartY = -1;
+ let snapEndY = -1;
+ for (let i = 0; i < this.panelPositions.length; i++) {
+ const y = this.panelPositions[i].y;
+ // If the box top is within this panel set the snapStart to the top of
+ // the panel.
+ if (y <= boxTop && y + this.panelPositions[i].height >= boxTop) {
+ snapStartY = y;
+ }
+ // If the box bottom is within this panel set the snapBottom to the bottom
+ // of the panel.
+ if (y <= boxBottom && y + this.panelPositions[i].height >= boxBottom) {
+ snapEndY = y + this.panelPositions[i].height;
+ break;
+ }
+ }
+
+ // If the y selection is outside all panels, do not draw a box.
+ if (snapStartY === -1 && snapEndY === -1) {
+ this.ctx.restore();
+ return;
+ }
+
+ // If the y selection starts above all panels, snap to the first panel.
+ if (snapStartY === -1) {
+ snapStartY = firstPanelY;
+ }
+ // If the y selection ends after all panels, snap to the bottom of the
+ // last panel.
+ if (snapEndY === -1) {
+ snapEndY = this.totalPanelHeight;
+ }
+
+ // The areaY values are given from the top of the pan and zoom handler.
+ // To align with the current panel container subtract the first panel Y.
+ snapStartY -= firstPanelY;
+ snapEndY -= firstPanelY;
+ this.ctx.strokeRect(
+ startX, snapStartY, endX - startX, snapEndY - snapStartY);
+ this.ctx.restore();
+ }
+
private updatePanelStats(
panelIndex: number, panel: Panel, renderTime: number,
ctx: CanvasRenderingContext2D, size: PanelSize) {
diff --git a/ui/src/frontend/track_group_panel.ts b/ui/src/frontend/track_group_panel.ts
index f08a0e2..89d143e 100644
--- a/ui/src/frontend/track_group_panel.ts
+++ b/ui/src/frontend/track_group_panel.ts
@@ -81,8 +81,9 @@
}
const selectedArea = globals.frontendLocalState.selectedArea.area;
- const markSelectedClass =
- selectedArea && selectedArea.tracks.includes(attrs.trackGroupId) ?
+ const markSelectedClass = selectedArea &&
+ selectedArea.tracks.includes(attrs.trackGroupId) &&
+ !globals.frontendLocalState.selectingArea ?
'selected' :
'';
@@ -121,6 +122,19 @@
getComputedStyle(dom).getPropertyValue('--collapsed-background');
}
+ highlightIfTrackSelected(ctx: CanvasRenderingContext2D, size: PanelSize) {
+ const localState = globals.frontendLocalState;
+ const area = localState.selectedArea.area;
+ if (area && area.tracks.includes(this.trackGroupId)) {
+ ctx.fillStyle = '#ebeef9';
+ ctx.fillRect(
+ localState.timeScale.timeToPx(area.startSec) + this.shellWidth,
+ 0,
+ localState.timeScale.deltaTimeToPx(area.endSec - area.startSec),
+ size.height);
+ }
+ }
+
renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize) {
const collapsed = this.trackGroupState.collapsed;
if (!collapsed) return;
@@ -130,6 +144,8 @@
ctx.fillStyle = this.backgroundColor;
ctx.fillRect(0, 0, size.width, size.height);
+ this.highlightIfTrackSelected(ctx, size);
+
drawGridLines(
ctx,
globals.frontendLocalState.timeScale,
@@ -138,6 +154,7 @@
size.height);
ctx.translate(this.shellWidth, 0);
+
if (this.summaryTrack) {
this.summaryTrack.render(ctx);
}
@@ -160,7 +177,8 @@
size.height,
`rgb(52,69,150)`);
}
- if (localState.selectedArea.area !== undefined) {
+ if (localState.selectedArea.area !== undefined &&
+ !globals.frontendLocalState.selectingArea) {
drawVerticalSelection(
ctx,
localState.timeScale,
diff --git a/ui/src/frontend/track_panel.ts b/ui/src/frontend/track_panel.ts
index 098bd81..fb798b4 100644
--- a/ui/src/frontend/track_panel.ts
+++ b/ui/src/frontend/track_panel.ts
@@ -64,8 +64,9 @@
const dragClass = this.dragging ? `drag` : '';
const dropClass = this.dropping ? `drop-${this.dropping}` : '';
const selectedArea = globals.frontendLocalState.selectedArea.area;
- const markSelectedClass =
- selectedArea && selectedArea.tracks.includes(attrs.trackState.id) ?
+ const markSelectedClass = selectedArea &&
+ selectedArea.tracks.includes(attrs.trackState.id) &&
+ !globals.frontendLocalState.selectingArea ?
'selected' :
'';
return m(
@@ -253,8 +254,25 @@
return m(TrackComponent, {trackState: this.trackState, track: this.track});
}
+ highlightIfTrackSelected(ctx: CanvasRenderingContext2D, size: PanelSize) {
+ const localState = globals.frontendLocalState;
+ const area = localState.selectedArea.area;
+ if (area && area.tracks.includes(this.trackState.id)) {
+ const timeScale = localState.timeScale;
+ ctx.fillStyle = '#ebeef9';
+ ctx.fillRect(
+ timeScale.timeToPx(area.startSec) + TRACK_SHELL_WIDTH,
+ 0,
+ timeScale.deltaTimeToPx(area.endSec - area.startSec),
+ size.height);
+ }
+ }
+
renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize) {
ctx.save();
+
+ this.highlightIfTrackSelected(ctx, size);
+
drawGridLines(
ctx,
globals.frontendLocalState.timeScale,
@@ -286,7 +304,8 @@
size.height,
`rgb(52,69,150)`);
}
- if (localState.selectedArea.area !== undefined) {
+ if (localState.selectedArea.area !== undefined &&
+ !globals.frontendLocalState.selectingArea) {
drawVerticalSelection(
ctx,
localState.timeScale,
diff --git a/ui/src/frontend/viewer_page.ts b/ui/src/frontend/viewer_page.ts
index aa8d0c4..c64a852 100644
--- a/ui/src/frontend/viewer_page.ts
+++ b/ui/src/frontend/viewer_page.ts
@@ -225,6 +225,16 @@
frontendLocalState.areaY.end = currentY;
}
globals.rafScheduler.scheduleRedraw();
+ },
+ selectingStarted: () => {
+ globals.frontendLocalState.selectingArea = true;
+ },
+ selectingEnded: () => {
+ globals.frontendLocalState.selectingArea = false;
+ globals.frontendLocalState.areaY.start = undefined;
+ globals.frontendLocalState.areaY.end = undefined;
+ // Full redraw to color track shell.
+ globals.rafScheduler.scheduleFullRedraw();
}
});
}