blob: a5169f09a96e46dd90ea06d6eed2d73483867e96 [file] [log] [blame]
Deepanjan Royabd79aa2018-08-28 07:29:15 -04001// Copyright (C) 2018 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import * as m from 'mithril';
Isabelle Taylora16dec22019-12-03 16:34:13 +000016import {TimestampedAreaSelection} from 'src/common/state';
Deepanjan Royabd79aa2018-08-28 07:29:15 -040017
Deepanjan Roy1f658fe2018-09-11 08:38:17 -040018import {assertExists, assertTrue} from '../base/logging';
Deepanjan Royabd79aa2018-08-28 07:29:15 -040019
Isabelle Taylora16dec22019-12-03 16:34:13 +000020import {TOPBAR_HEIGHT, TRACK_SHELL_WIDTH} from './css_constants';
Deepanjan Royabd79aa2018-08-28 07:29:15 -040021import {globals} from './globals';
Hector Dearman191741d2020-06-09 16:00:40 +010022import {isPanelVNode, Panel, PanelSize, PanelVNode} from './panel';
Deepanjan Roy9a906ed2018-09-20 11:06:00 -040023import {
24 debugNow,
25 perfDebug,
26 perfDisplay,
27 RunningStatistics,
28 runningStatStr
29} from './perf';
Deepanjan Royabd79aa2018-08-28 07:29:15 -040030
31/**
32 * If the panel container scrolls, the backing canvas height is
33 * SCROLLING_CANVAS_OVERDRAW_FACTOR * parent container height.
34 */
Hector Dearman0d825912019-02-15 10:33:37 +000035const SCROLLING_CANVAS_OVERDRAW_FACTOR = 1.2;
Deepanjan Royabd79aa2018-08-28 07:29:15 -040036
Deepanjan Roy4fa0ecc2018-10-03 16:10:46 -040037// We need any here so we can accept vnodes with arbitrary attrs.
38// tslint:disable-next-line:no-any
39export type AnyAttrsVnode = m.Vnode<any, {}>;
40
Isabelle Taylora16dec22019-12-03 16:34:13 +000041export interface Attrs {
Deepanjan Roy4fa0ecc2018-10-03 16:10:46 -040042 panels: AnyAttrsVnode[];
Deepanjan Royabd79aa2018-08-28 07:29:15 -040043 doesScroll: boolean;
Isabelle Taylorf41b7c72019-07-24 13:55:43 +010044 kind: 'TRACKS'|'OVERVIEW'|'DETAILS';
Deepanjan Royabd79aa2018-08-28 07:29:15 -040045}
46
Isabelle Taylora16dec22019-12-03 16:34:13 +000047interface PanelPosition {
Isabelle Taylor9b4a81d2020-01-31 11:11:02 +000048 id: string;
Isabelle Taylora16dec22019-12-03 16:34:13 +000049 height: number;
50 width: number;
51 x: number;
52 y: number;
53}
54
Deepanjan Roy1f658fe2018-09-11 08:38:17 -040055export class PanelContainer implements m.ClassComponent<Attrs> {
56 // These values are updated with proper values in oncreate.
57 private parentWidth = 0;
58 private parentHeight = 0;
59 private scrollTop = 0;
Isabelle Taylora16dec22019-12-03 16:34:13 +000060 private panelPositions: PanelPosition[] = [];
Deepanjan Roy1f658fe2018-09-11 08:38:17 -040061 private totalPanelHeight = 0;
62 private canvasHeight = 0;
Isabelle Taylora16dec22019-12-03 16:34:13 +000063 private prevAreaSelection?: TimestampedAreaSelection;
Deepanjan Royabd79aa2018-08-28 07:29:15 -040064
Deepanjan Roy9a906ed2018-09-20 11:06:00 -040065 private panelPerfStats = new WeakMap<Panel, RunningStatistics>();
66 private perfStats = {
67 totalPanels: 0,
68 panelsOnCanvas: 0,
69 renderStats: new RunningStatistics(10),
70 };
71
Deepanjan Roy1a225c22018-10-04 07:33:02 -040072 // Attrs received in the most recent mithril redraw. We receive a new vnode
73 // with new attrs on every redraw, and we cache it here so that resize
74 // listeners and canvas redraw callbacks can access it.
75 private attrs: Attrs;
Deepanjan Roy1f658fe2018-09-11 08:38:17 -040076
Deepanjan Roy1f658fe2018-09-11 08:38:17 -040077 private ctx?: CanvasRenderingContext2D;
78
79 private onResize: () => void = () => {};
80 private parentOnScroll: () => void = () => {};
81 private canvasRedrawer: () => void;
82
Deepanjan Roy1a225c22018-10-04 07:33:02 -040083 get canvasOverdrawFactor() {
84 return this.attrs.doesScroll ? SCROLLING_CANVAS_OVERDRAW_FACTOR : 1;
85 }
86
Isabelle Taylora16dec22019-12-03 16:34:13 +000087 getPanelsInRegion(startX: number, endX: number, startY: number, endY: number):
88 AnyAttrsVnode[] {
89 const minX = Math.min(startX, endX);
90 const maxX = Math.max(startX, endX);
91 const minY = Math.min(startY, endY);
92 const maxY = Math.max(startY, endY);
93 const panels: AnyAttrsVnode[] = [];
94 for (let i = 0; i < this.panelPositions.length; i++) {
95 const pos = this.panelPositions[i];
96 const realPosX = pos.x - TRACK_SHELL_WIDTH;
97 if (realPosX + pos.width >= minX && realPosX <= maxX &&
Isabelle Taylor9b4a81d2020-01-31 11:11:02 +000098 pos.y + pos.height >= minY && pos.y <= maxY &&
99 this.attrs.panels[i].attrs.selectable) {
Isabelle Taylora16dec22019-12-03 16:34:13 +0000100 panels.push(this.attrs.panels[i]);
101 }
102 }
103 return panels;
104 }
105
106 handleAreaSelection() {
107 const selection = globals.frontendLocalState.selectedArea;
108 const area = selection.area;
109 if ((this.prevAreaSelection &&
110 this.prevAreaSelection.lastUpdate >= selection.lastUpdate) ||
111 area === undefined ||
112 globals.frontendLocalState.areaY.start === undefined ||
Isabelle Taylor4255fd52020-02-21 14:43:39 +0000113 globals.frontendLocalState.areaY.end === undefined ||
114 this.panelPositions.length === 0) {
Isabelle Taylora16dec22019-12-03 16:34:13 +0000115 return;
116 }
Isabelle Taylor9b4a81d2020-01-31 11:11:02 +0000117 // Only get panels from the current panel container if the selection began
118 // in this container.
119 const panelContainerTop = this.panelPositions[0].y;
120 const panelContainerBottom =
121 this.panelPositions[this.panelPositions.length - 1].y +
122 this.panelPositions[this.panelPositions.length - 1].height;
123 if (globals.frontendLocalState.areaY.start + TOPBAR_HEIGHT <
124 panelContainerTop ||
125 globals.frontendLocalState.areaY.start + TOPBAR_HEIGHT >
126 panelContainerBottom) {
127 return;
128 }
129
Isabelle Taylora16dec22019-12-03 16:34:13 +0000130 // The Y value is given from the top of the pan and zoom region, we want it
131 // from the top of the panel container. The parent offset corrects that.
132 const panels = this.getPanelsInRegion(
133 globals.frontendLocalState.timeScale.timeToPx(area.startSec),
134 globals.frontendLocalState.timeScale.timeToPx(area.endSec),
135 globals.frontendLocalState.areaY.start + TOPBAR_HEIGHT,
136 globals.frontendLocalState.areaY.end + TOPBAR_HEIGHT);
137 // Get the track ids from the panels.
138 const tracks = [];
139 for (const panel of panels) {
140 if (panel.attrs.id !== undefined) {
141 tracks.push(panel.attrs.id);
142 continue;
143 }
144 if (panel.attrs.trackGroupId !== undefined) {
145 const trackGroup = globals.state.trackGroups[panel.attrs.trackGroupId];
146 // Only select a track group and all child tracks if it is closed.
147 if (trackGroup.collapsed) {
148 tracks.push(panel.attrs.trackGroupId);
149 for (const track of trackGroup.tracks) {
150 tracks.push(track);
151 }
152 }
153 }
154 }
155 globals.frontendLocalState.selectArea(area.startSec, area.endSec, tracks);
156 this.prevAreaSelection = globals.frontendLocalState.selectedArea;
157 }
158
Deepanjan Roy1f658fe2018-09-11 08:38:17 -0400159 constructor(vnode: m.CVnode<Attrs>) {
Deepanjan Roy1a225c22018-10-04 07:33:02 -0400160 this.attrs = vnode.attrs;
Deepanjan Roy1f658fe2018-09-11 08:38:17 -0400161 this.canvasRedrawer = () => this.redrawCanvas();
Deepanjan Royabd79aa2018-08-28 07:29:15 -0400162 globals.rafScheduler.addRedrawCallback(this.canvasRedrawer);
Deepanjan Roy9a906ed2018-09-20 11:06:00 -0400163 perfDisplay.addContainer(this);
Deepanjan Roy1f658fe2018-09-11 08:38:17 -0400164 }
Deepanjan Royabd79aa2018-08-28 07:29:15 -0400165
Deepanjan Roy1f658fe2018-09-11 08:38:17 -0400166 oncreate(vnodeDom: m.CVnodeDOM<Attrs>) {
Deepanjan Royabd79aa2018-08-28 07:29:15 -0400167 // Save the canvas context in the state.
168 const canvas =
169 vnodeDom.dom.querySelector('.main-canvas') as HTMLCanvasElement;
170 const ctx = canvas.getContext('2d');
171 if (!ctx) {
172 throw Error('Cannot create canvas context');
173 }
174 this.ctx = ctx;
175
Isabelle Taylora16dec22019-12-03 16:34:13 +0000176 this.readParentSizeFromDom(vnodeDom.dom);
Deepanjan Roy1a225c22018-10-04 07:33:02 -0400177 this.readPanelHeightsFromDom(vnodeDom.dom);
Deepanjan Roy1f658fe2018-09-11 08:38:17 -0400178
Deepanjan Roy1a225c22018-10-04 07:33:02 -0400179 this.updateCanvasDimensions();
180 this.repositionCanvas();
Deepanjan Royabd79aa2018-08-28 07:29:15 -0400181
182 // Save the resize handler in the state so we can remove it later.
183 // TODO: Encapsulate resize handling better.
184 this.onResize = () => {
Deepanjan Roy1a225c22018-10-04 07:33:02 -0400185 this.readParentSizeFromDom(vnodeDom.dom);
186 this.updateCanvasDimensions();
187 this.repositionCanvas();
Deepanjan Roy112ff6a2018-09-10 08:31:43 -0400188 globals.rafScheduler.scheduleFullRedraw();
Deepanjan Royabd79aa2018-08-28 07:29:15 -0400189 };
190
191 // Once ResizeObservers are out, we can stop accessing the window here.
192 window.addEventListener('resize', this.onResize);
193
Deepanjan Roy1f658fe2018-09-11 08:38:17 -0400194 // TODO(dproy): Handle change in doesScroll attribute.
Deepanjan Roy1a225c22018-10-04 07:33:02 -0400195 if (this.attrs.doesScroll) {
Deepanjan Royabd79aa2018-08-28 07:29:15 -0400196 this.parentOnScroll = () => {
Deepanjan Roy1a225c22018-10-04 07:33:02 -0400197 this.scrollTop = assertExists(vnodeDom.dom.parentElement).scrollTop;
198 this.repositionCanvas();
Deepanjan Royf190cb22018-08-28 10:43:07 -0400199 globals.rafScheduler.scheduleRedraw();
Deepanjan Royabd79aa2018-08-28 07:29:15 -0400200 };
201 vnodeDom.dom.parentElement!.addEventListener(
202 'scroll', this.parentOnScroll, {passive: true});
203 }
Deepanjan Roy1f658fe2018-09-11 08:38:17 -0400204 }
Deepanjan Royabd79aa2018-08-28 07:29:15 -0400205
Deepanjan Roy1f658fe2018-09-11 08:38:17 -0400206 onremove({attrs, dom}: m.CVnodeDOM<Attrs>) {
Deepanjan Royabd79aa2018-08-28 07:29:15 -0400207 window.removeEventListener('resize', this.onResize);
208 globals.rafScheduler.removeRedrawCallback(this.canvasRedrawer);
209 if (attrs.doesScroll) {
210 dom.parentElement!.removeEventListener('scroll', this.parentOnScroll);
211 }
Deepanjan Roy9a906ed2018-09-20 11:06:00 -0400212 perfDisplay.removeContainer(this);
Deepanjan Roy1f658fe2018-09-11 08:38:17 -0400213 }
Deepanjan Royabd79aa2018-08-28 07:29:15 -0400214
Deepanjan Roy1f658fe2018-09-11 08:38:17 -0400215 view({attrs}: m.CVnode<Attrs>) {
Deepanjan Roy1f658fe2018-09-11 08:38:17 -0400216 this.attrs = attrs;
Deepanjan Roy9a906ed2018-09-20 11:06:00 -0400217 const renderPanel = (panel: m.Vnode) => perfDebug() ?
218 m('.panel', panel, m('.debug-panel-border')) :
Isabelle Taylor1acfa2b2019-05-17 17:26:40 +0100219 m('.panel', {key: panel.key}, panel);
Deepanjan Roy9a906ed2018-09-20 11:06:00 -0400220
Isabelle Taylorf4e0f682019-11-20 10:47:49 +0000221 return [
222 m(
223 '.scroll-limiter',
224 m('canvas.main-canvas'),
225 ),
226 m('.panels', attrs.panels.map(renderPanel))
227 ];
Deepanjan Royabd79aa2018-08-28 07:29:15 -0400228 }
Deepanjan Roy1f658fe2018-09-11 08:38:17 -0400229
230 onupdate(vnodeDom: m.CVnodeDOM<Attrs>) {
Deepanjan Roy1a225c22018-10-04 07:33:02 -0400231 const totalPanelHeightChanged = this.readPanelHeightsFromDom(vnodeDom.dom);
232 const parentSizeChanged = this.readParentSizeFromDom(vnodeDom.dom);
Deepanjan Roy1a225c22018-10-04 07:33:02 -0400233 const canvasSizeShouldChange =
Isabelle Taylorf41b7c72019-07-24 13:55:43 +0100234 parentSizeChanged || !this.attrs.doesScroll && totalPanelHeightChanged;
Deepanjan Roy1a225c22018-10-04 07:33:02 -0400235 if (canvasSizeShouldChange) {
236 this.updateCanvasDimensions();
237 this.repositionCanvas();
Isabelle Taylorf41b7c72019-07-24 13:55:43 +0100238 if (this.attrs.kind === 'TRACKS') {
239 globals.frontendLocalState.timeScale.setLimitsPx(
240 0, this.parentWidth - TRACK_SHELL_WIDTH);
241 }
242 this.redrawCanvas();
Deepanjan Roy1f658fe2018-09-11 08:38:17 -0400243 }
244 }
245
Deepanjan Roy1a225c22018-10-04 07:33:02 -0400246 private updateCanvasDimensions() {
Hector Dearmanafa51b22018-12-11 16:38:30 +0000247 this.canvasHeight = Math.floor(
248 this.attrs.doesScroll ? this.parentHeight * this.canvasOverdrawFactor :
249 this.totalPanelHeight);
Deepanjan Roy1f658fe2018-09-11 08:38:17 -0400250 const ctx = assertExists(this.ctx);
Deepanjan Roy1a225c22018-10-04 07:33:02 -0400251 const canvas = assertExists(ctx.canvas);
Deepanjan Roy1f658fe2018-09-11 08:38:17 -0400252 canvas.style.height = `${this.canvasHeight}px`;
Hector Dearman4f8ea6a2020-01-17 14:52:34 +0000253
254 // If're we're non-scrolling canvas and the scroll-limiter should always
255 // have the same height. Enforce this by explicitly setting the height.
256 if (!this.attrs.doesScroll) {
257 const scrollLimiter = canvas.parentElement;
258 if (scrollLimiter) {
259 scrollLimiter.style.height = `${this.canvasHeight}px`;
260 }
261 }
262
Deepanjan Roy1f658fe2018-09-11 08:38:17 -0400263 const dpr = window.devicePixelRatio;
Isabelle Tayloref902422020-01-24 10:35:55 +0000264 ctx.canvas.width = this.parentWidth * dpr;
Deepanjan Roy1f658fe2018-09-11 08:38:17 -0400265 ctx.canvas.height = this.canvasHeight * dpr;
266 ctx.scale(dpr, dpr);
267 }
268
Deepanjan Roy1a225c22018-10-04 07:33:02 -0400269 private repositionCanvas() {
270 const canvas = assertExists(assertExists(this.ctx).canvas);
Hector Dearmanafa51b22018-12-11 16:38:30 +0000271 const canvasYStart =
272 Math.floor(this.scrollTop - this.getCanvasOverdrawHeightPerSide());
Deepanjan Roy1a225c22018-10-04 07:33:02 -0400273 canvas.style.transform = `translateY(${canvasYStart}px)`;
274 }
275
276 /**
277 * Reads dimensions of parent node. Returns true if read dimensions are
278 * different from what was cached in the state.
279 */
280 private readParentSizeFromDom(dom: Element): boolean {
281 const oldWidth = this.parentWidth;
282 const oldHeight = this.parentHeight;
283 const clientRect = assertExists(dom.parentElement).getBoundingClientRect();
Isabelle Tayloref902422020-01-24 10:35:55 +0000284 // On non-MacOS if there is a solid scroll bar it can cover important
285 // pixels, reduce the size of the canvas so it doesn't overlap with
286 // the scroll bar.
287 this.parentWidth =
288 clientRect.width - globals.frontendLocalState.getScrollbarWidth();
Deepanjan Roy1a225c22018-10-04 07:33:02 -0400289 this.parentHeight = clientRect.height;
290 return this.parentHeight !== oldHeight || this.parentWidth !== oldWidth;
291 }
292
293 /**
294 * Reads dimensions of panels. Returns true if total panel height is different
295 * from what was cached in state.
296 */
297 private readPanelHeightsFromDom(dom: Element): boolean {
Deepanjan Roy1f658fe2018-09-11 08:38:17 -0400298 const prevHeight = this.totalPanelHeight;
Isabelle Taylora16dec22019-12-03 16:34:13 +0000299 this.panelPositions = [];
Deepanjan Roy1f658fe2018-09-11 08:38:17 -0400300 this.totalPanelHeight = 0;
301
Isabelle Taylorf4e0f682019-11-20 10:47:49 +0000302 const panels = dom.parentElement!.querySelectorAll('.panel');
Deepanjan Roy1a225c22018-10-04 07:33:02 -0400303 assertTrue(panels.length === this.attrs.panels.length);
Deepanjan Roy1f658fe2018-09-11 08:38:17 -0400304 for (let i = 0; i < panels.length; i++) {
Hector Dearman191741d2020-06-09 16:00:40 +0100305 const rect = panels[i].getBoundingClientRect();
Isabelle Taylor9b4a81d2020-01-31 11:11:02 +0000306 const id = this.attrs.panels[i].attrs.id ||
307 this.attrs.panels[i].attrs.trackGroupId;
Isabelle Taylora16dec22019-12-03 16:34:13 +0000308 this.panelPositions[i] =
Isabelle Taylor9b4a81d2020-01-31 11:11:02 +0000309 {id, height: rect.height, width: rect.width, x: rect.x, y: rect.y};
Isabelle Taylora16dec22019-12-03 16:34:13 +0000310 this.totalPanelHeight += rect.height;
Deepanjan Roy1f658fe2018-09-11 08:38:17 -0400311 }
312
313 return this.totalPanelHeight !== prevHeight;
314 }
315
Deepanjan Roy1f658fe2018-09-11 08:38:17 -0400316 private overlapsCanvas(yStart: number, yEnd: number) {
317 return yEnd > 0 && yStart < this.canvasHeight;
318 }
319
320 private redrawCanvas() {
Deepanjan Roy9a906ed2018-09-20 11:06:00 -0400321 const redrawStart = debugNow();
Deepanjan Roy1f658fe2018-09-11 08:38:17 -0400322 if (!this.ctx) return;
323 this.ctx.clearRect(0, 0, this.parentWidth, this.canvasHeight);
Hector Dearmanf74cf222018-10-31 16:22:29 +0000324 const canvasYStart =
Hector Dearmanafa51b22018-12-11 16:38:30 +0000325 Math.floor(this.scrollTop - this.getCanvasOverdrawHeightPerSide());
Deepanjan Roy1f658fe2018-09-11 08:38:17 -0400326
Isabelle Taylor9b4a81d2020-01-31 11:11:02 +0000327 this.handleAreaSelection();
Isabelle Taylora16dec22019-12-03 16:34:13 +0000328
Deepanjan Roy1f658fe2018-09-11 08:38:17 -0400329 let panelYStart = 0;
330 const panels = assertExists(this.attrs).panels;
Isabelle Taylora16dec22019-12-03 16:34:13 +0000331 assertTrue(panels.length === this.panelPositions.length);
Deepanjan Roy9a906ed2018-09-20 11:06:00 -0400332 let totalOnCanvas = 0;
Deepanjan Roy1f658fe2018-09-11 08:38:17 -0400333 for (let i = 0; i < panels.length; i++) {
334 const panel = panels[i];
Isabelle Taylora16dec22019-12-03 16:34:13 +0000335 const panelHeight = this.panelPositions[i].height;
Deepanjan Roy1f658fe2018-09-11 08:38:17 -0400336 const yStartOnCanvas = panelYStart - canvasYStart;
337
338 if (!this.overlapsCanvas(yStartOnCanvas, yStartOnCanvas + panelHeight)) {
339 panelYStart += panelHeight;
340 continue;
341 }
342
Deepanjan Roy9a906ed2018-09-20 11:06:00 -0400343 totalOnCanvas++;
344
Deepanjan Roy1f658fe2018-09-11 08:38:17 -0400345 if (!isPanelVNode(panel)) {
Hector Dearman191741d2020-06-09 16:00:40 +0100346 throw new Error('Vnode passed to panel container is not a panel');
Deepanjan Roy1f658fe2018-09-11 08:38:17 -0400347 }
348
Hector Dearman191741d2020-06-09 16:00:40 +0100349 // TODO(hjd): This cast should be unnecessary given the type guard above.
350 const p = panel as PanelVNode<{}>;
351
Deepanjan Roy1f658fe2018-09-11 08:38:17 -0400352 this.ctx.save();
353 this.ctx.translate(0, yStartOnCanvas);
354 const clipRect = new Path2D();
355 const size = {width: this.parentWidth, height: panelHeight};
356 clipRect.rect(0, 0, size.width, size.height);
357 this.ctx.clip(clipRect);
Deepanjan Roy9a906ed2018-09-20 11:06:00 -0400358 const beforeRender = debugNow();
Hector Dearman191741d2020-06-09 16:00:40 +0100359 p.state.renderCanvas(this.ctx, size, p);
Deepanjan Roy9a906ed2018-09-20 11:06:00 -0400360 this.updatePanelStats(
Hector Dearman191741d2020-06-09 16:00:40 +0100361 i, p.state, debugNow() - beforeRender, this.ctx, size);
Deepanjan Roy1f658fe2018-09-11 08:38:17 -0400362 this.ctx.restore();
363 panelYStart += panelHeight;
364 }
Isabelle Taylor01254f72020-01-27 15:29:14 +0000365
366 this.drawTopLayerOnCanvas();
Deepanjan Roy9a906ed2018-09-20 11:06:00 -0400367 const redrawDur = debugNow() - redrawStart;
368 this.updatePerfStats(redrawDur, panels.length, totalOnCanvas);
369 }
370
Isabelle Taylor01254f72020-01-27 15:29:14 +0000371 // The panels each draw on the canvas but some details need to be drawn across
372 // the whole canvas rather than per panel.
373 private drawTopLayerOnCanvas() {
374 if (!this.ctx) return;
Isabelle Taylor01254f72020-01-27 15:29:14 +0000375 const selection = globals.frontendLocalState.selectedArea;
376 const area = selection.area;
377 if (area === undefined ||
378 globals.frontendLocalState.areaY.start === undefined ||
379 globals.frontendLocalState.areaY.end === undefined ||
380 !globals.frontendLocalState.selectingArea) {
381 return;
382 }
Isabelle Taylor9b4a81d2020-01-31 11:11:02 +0000383 if (this.panelPositions.length === 0 || area.tracks.length === 0) return;
384
385 // Find the minY and maxY of the selected tracks in this panel container.
386 const panelContainerTop = this.panelPositions[0].y;
387 const panelContainerBottom =
388 this.panelPositions[this.panelPositions.length - 1].y +
389 this.panelPositions[this.panelPositions.length - 1].height;
390 let selectedTracksMinY = panelContainerBottom;
391 let selectedTracksMaxY = panelContainerTop;
392 let trackFromCurrentContainerSelected = false;
393 for (let i = 0; i < this.panelPositions.length; i++) {
394 if (area.tracks.includes(this.panelPositions[i].id)) {
395 trackFromCurrentContainerSelected = true;
396 selectedTracksMinY =
397 Math.min(selectedTracksMinY, this.panelPositions[i].y);
398 selectedTracksMaxY = Math.max(
399 selectedTracksMaxY,
400 this.panelPositions[i].y + this.panelPositions[i].height);
401 }
402 }
403
404 // No box should be drawn if there are no selected tracks in the current
405 // container.
406 if (!trackFromCurrentContainerSelected) {
407 return;
408 }
409
410 const startX = globals.frontendLocalState.timeScale.timeToPx(area.startSec);
411 const endX = globals.frontendLocalState.timeScale.timeToPx(area.endSec);
412 // To align with where to draw on the canvas subtract the first panel Y.
413 selectedTracksMinY -= panelContainerTop;
414 selectedTracksMaxY -= panelContainerTop;
Isabelle Taylor01254f72020-01-27 15:29:14 +0000415 this.ctx.save();
416 this.ctx.strokeStyle = 'rgba(52,69,150)';
417 this.ctx.lineWidth = 1;
418 const canvasYStart =
419 Math.floor(this.scrollTop - this.getCanvasOverdrawHeightPerSide());
420 this.ctx.translate(TRACK_SHELL_WIDTH, -canvasYStart);
Isabelle Taylor01254f72020-01-27 15:29:14 +0000421 this.ctx.strokeRect(
Isabelle Taylor9b4a81d2020-01-31 11:11:02 +0000422 startX,
423 selectedTracksMaxY,
424 endX - startX,
425 selectedTracksMinY - selectedTracksMaxY);
Isabelle Taylor01254f72020-01-27 15:29:14 +0000426 this.ctx.restore();
427 }
428
Deepanjan Roy9a906ed2018-09-20 11:06:00 -0400429 private updatePanelStats(
430 panelIndex: number, panel: Panel, renderTime: number,
431 ctx: CanvasRenderingContext2D, size: PanelSize) {
432 if (!perfDebug()) return;
433 let renderStats = this.panelPerfStats.get(panel);
434 if (renderStats === undefined) {
435 renderStats = new RunningStatistics();
436 this.panelPerfStats.set(panel, renderStats);
437 }
438 renderStats.addValue(renderTime);
439
440 const statW = 300;
441 ctx.fillStyle = 'hsl(97, 100%, 96%)';
442 ctx.fillRect(size.width - statW, size.height - 20, statW, 20);
443 ctx.fillStyle = 'hsla(122, 77%, 22%)';
444 const statStr = `Panel ${panelIndex + 1} | ` + runningStatStr(renderStats);
445 ctx.fillText(statStr, size.width - statW, size.height - 10);
446 }
447
448 private updatePerfStats(
449 renderTime: number, totalPanels: number, panelsOnCanvas: number) {
450 if (!perfDebug()) return;
451 this.perfStats.renderStats.addValue(renderTime);
452 this.perfStats.totalPanels = totalPanels;
453 this.perfStats.panelsOnCanvas = panelsOnCanvas;
454 }
455
456 renderPerfStats(index: number) {
457 assertTrue(perfDebug());
458 return [m(
459 'section',
460 m('div', `Panel Container ${index + 1}`),
461 m('div',
462 `${this.perfStats.totalPanels} panels, ` +
463 `${this.perfStats.panelsOnCanvas} on canvas.`),
464 m('div', runningStatStr(this.perfStats.renderStats)), )];
Deepanjan Roy1f658fe2018-09-11 08:38:17 -0400465 }
466
467 private getCanvasOverdrawHeightPerSide() {
468 const overdrawHeight = (this.canvasOverdrawFactor - 1) * this.parentHeight;
469 return overdrawHeight / 2;
470 }
471}