1*6dbdd20aSAndroid Build Coastguard Worker// Copyright (C) 2018 The Android Open Source Project 2*6dbdd20aSAndroid Build Coastguard Worker// 3*6dbdd20aSAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License"); 4*6dbdd20aSAndroid Build Coastguard Worker// you may not use this file except in compliance with the License. 5*6dbdd20aSAndroid Build Coastguard Worker// You may obtain a copy of the License at 6*6dbdd20aSAndroid Build Coastguard Worker// 7*6dbdd20aSAndroid Build Coastguard Worker// http://www.apache.org/licenses/LICENSE-2.0 8*6dbdd20aSAndroid Build Coastguard Worker// 9*6dbdd20aSAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software 10*6dbdd20aSAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS, 11*6dbdd20aSAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*6dbdd20aSAndroid Build Coastguard Worker// See the License for the specific language governing permissions and 13*6dbdd20aSAndroid Build Coastguard Worker// limitations under the License. 14*6dbdd20aSAndroid Build Coastguard Worker 15*6dbdd20aSAndroid Build Coastguard Workerimport m from 'mithril'; 16*6dbdd20aSAndroid Build Coastguard Workerimport {canvasClip, canvasSave} from '../base/canvas_utils'; 17*6dbdd20aSAndroid Build Coastguard Workerimport {classNames} from '../base/classnames'; 18*6dbdd20aSAndroid Build Coastguard Workerimport {Bounds2D, Size2D, VerticalBounds} from '../base/geom'; 19*6dbdd20aSAndroid Build Coastguard Workerimport {Icons} from '../base/semantic_icons'; 20*6dbdd20aSAndroid Build Coastguard Workerimport {TimeScale} from '../base/time_scale'; 21*6dbdd20aSAndroid Build Coastguard Workerimport {RequiredField} from '../base/utils'; 22*6dbdd20aSAndroid Build Coastguard Workerimport {calculateResolution} from '../common/resolution'; 23*6dbdd20aSAndroid Build Coastguard Workerimport {featureFlags} from '../core/feature_flags'; 24*6dbdd20aSAndroid Build Coastguard Workerimport {TrackRenderer} from '../core/track_manager'; 25*6dbdd20aSAndroid Build Coastguard Workerimport {TrackDescriptor, TrackRenderContext} from '../public/track'; 26*6dbdd20aSAndroid Build Coastguard Workerimport {TrackNode} from '../public/workspace'; 27*6dbdd20aSAndroid Build Coastguard Workerimport {Button} from '../widgets/button'; 28*6dbdd20aSAndroid Build Coastguard Workerimport {Popup, PopupPosition} from '../widgets/popup'; 29*6dbdd20aSAndroid Build Coastguard Workerimport {Tree, TreeNode} from '../widgets/tree'; 30*6dbdd20aSAndroid Build Coastguard Workerimport {SELECTION_FILL_COLOR, TRACK_SHELL_WIDTH} from './css_constants'; 31*6dbdd20aSAndroid Build Coastguard Workerimport {Panel} from './panel_container'; 32*6dbdd20aSAndroid Build Coastguard Workerimport {TrackWidget} from '../widgets/track_widget'; 33*6dbdd20aSAndroid Build Coastguard Workerimport {raf} from '../core/raf_scheduler'; 34*6dbdd20aSAndroid Build Coastguard Workerimport {Intent} from '../widgets/common'; 35*6dbdd20aSAndroid Build Coastguard Workerimport {TraceImpl} from '../core/trace_impl'; 36*6dbdd20aSAndroid Build Coastguard Worker 37*6dbdd20aSAndroid Build Coastguard Workerconst SHOW_TRACK_DETAILS_BUTTON = featureFlags.register({ 38*6dbdd20aSAndroid Build Coastguard Worker id: 'showTrackDetailsButton', 39*6dbdd20aSAndroid Build Coastguard Worker name: 'Show track details button', 40*6dbdd20aSAndroid Build Coastguard Worker description: 'Show track details button in track shells.', 41*6dbdd20aSAndroid Build Coastguard Worker defaultValue: false, 42*6dbdd20aSAndroid Build Coastguard Worker}); 43*6dbdd20aSAndroid Build Coastguard Worker 44*6dbdd20aSAndroid Build Coastguard Worker// Default height of a track element that has no track, or is collapsed. 45*6dbdd20aSAndroid Build Coastguard Worker// Note: This is designed to roughly match the height of a cpu slice track. 46*6dbdd20aSAndroid Build Coastguard Workerexport const DEFAULT_TRACK_HEIGHT_PX = 30; 47*6dbdd20aSAndroid Build Coastguard Worker 48*6dbdd20aSAndroid Build Coastguard Workerinterface TrackPanelAttrs { 49*6dbdd20aSAndroid Build Coastguard Worker readonly trace: TraceImpl; 50*6dbdd20aSAndroid Build Coastguard Worker readonly node: TrackNode; 51*6dbdd20aSAndroid Build Coastguard Worker readonly indentationLevel: number; 52*6dbdd20aSAndroid Build Coastguard Worker readonly trackRenderer?: TrackRenderer; 53*6dbdd20aSAndroid Build Coastguard Worker readonly revealOnCreate?: boolean; 54*6dbdd20aSAndroid Build Coastguard Worker readonly topOffsetPx: number; 55*6dbdd20aSAndroid Build Coastguard Worker readonly reorderable?: boolean; 56*6dbdd20aSAndroid Build Coastguard Worker} 57*6dbdd20aSAndroid Build Coastguard Worker 58*6dbdd20aSAndroid Build Coastguard Workerexport class TrackPanel implements Panel { 59*6dbdd20aSAndroid Build Coastguard Worker readonly kind = 'panel'; 60*6dbdd20aSAndroid Build Coastguard Worker readonly selectable = true; 61*6dbdd20aSAndroid Build Coastguard Worker readonly trackNode?: TrackNode; 62*6dbdd20aSAndroid Build Coastguard Worker 63*6dbdd20aSAndroid Build Coastguard Worker private readonly attrs: TrackPanelAttrs; 64*6dbdd20aSAndroid Build Coastguard Worker 65*6dbdd20aSAndroid Build Coastguard Worker constructor(attrs: TrackPanelAttrs) { 66*6dbdd20aSAndroid Build Coastguard Worker this.attrs = attrs; 67*6dbdd20aSAndroid Build Coastguard Worker this.trackNode = attrs.node; 68*6dbdd20aSAndroid Build Coastguard Worker } 69*6dbdd20aSAndroid Build Coastguard Worker 70*6dbdd20aSAndroid Build Coastguard Worker get heightPx(): number { 71*6dbdd20aSAndroid Build Coastguard Worker const {trackRenderer, node} = this.attrs; 72*6dbdd20aSAndroid Build Coastguard Worker 73*6dbdd20aSAndroid Build Coastguard Worker // If the node is a summary track and is expanded, shrink it to save 74*6dbdd20aSAndroid Build Coastguard Worker // vertical real estate). 75*6dbdd20aSAndroid Build Coastguard Worker if (node.isSummary && node.expanded) return DEFAULT_TRACK_HEIGHT_PX; 76*6dbdd20aSAndroid Build Coastguard Worker 77*6dbdd20aSAndroid Build Coastguard Worker // Otherwise return the height of the track, if we have one. 78*6dbdd20aSAndroid Build Coastguard Worker return trackRenderer?.track.getHeight() ?? DEFAULT_TRACK_HEIGHT_PX; 79*6dbdd20aSAndroid Build Coastguard Worker } 80*6dbdd20aSAndroid Build Coastguard Worker 81*6dbdd20aSAndroid Build Coastguard Worker render(): m.Children { 82*6dbdd20aSAndroid Build Coastguard Worker const { 83*6dbdd20aSAndroid Build Coastguard Worker node, 84*6dbdd20aSAndroid Build Coastguard Worker indentationLevel, 85*6dbdd20aSAndroid Build Coastguard Worker trackRenderer, 86*6dbdd20aSAndroid Build Coastguard Worker revealOnCreate, 87*6dbdd20aSAndroid Build Coastguard Worker topOffsetPx, 88*6dbdd20aSAndroid Build Coastguard Worker reorderable = false, 89*6dbdd20aSAndroid Build Coastguard Worker } = this.attrs; 90*6dbdd20aSAndroid Build Coastguard Worker 91*6dbdd20aSAndroid Build Coastguard Worker const error = trackRenderer?.getError(); 92*6dbdd20aSAndroid Build Coastguard Worker 93*6dbdd20aSAndroid Build Coastguard Worker const buttons = [ 94*6dbdd20aSAndroid Build Coastguard Worker SHOW_TRACK_DETAILS_BUTTON.get() && 95*6dbdd20aSAndroid Build Coastguard Worker renderTrackDetailsButton(node, trackRenderer?.desc), 96*6dbdd20aSAndroid Build Coastguard Worker trackRenderer?.track.getTrackShellButtons?.(), 97*6dbdd20aSAndroid Build Coastguard Worker node.removable && renderCloseButton(node), 98*6dbdd20aSAndroid Build Coastguard Worker // We don't want summary tracks to be pinned as they rarely have 99*6dbdd20aSAndroid Build Coastguard Worker // useful information. 100*6dbdd20aSAndroid Build Coastguard Worker !node.isSummary && renderPinButton(node), 101*6dbdd20aSAndroid Build Coastguard Worker this.renderAreaSelectionCheckbox(node), 102*6dbdd20aSAndroid Build Coastguard Worker error && renderCrashButton(error, trackRenderer?.desc.pluginId), 103*6dbdd20aSAndroid Build Coastguard Worker ]; 104*6dbdd20aSAndroid Build Coastguard Worker 105*6dbdd20aSAndroid Build Coastguard Worker let scrollIntoView = false; 106*6dbdd20aSAndroid Build Coastguard Worker const tracks = this.attrs.trace.tracks; 107*6dbdd20aSAndroid Build Coastguard Worker if (tracks.scrollToTrackNodeId === node.id) { 108*6dbdd20aSAndroid Build Coastguard Worker tracks.scrollToTrackNodeId = undefined; 109*6dbdd20aSAndroid Build Coastguard Worker scrollIntoView = true; 110*6dbdd20aSAndroid Build Coastguard Worker } 111*6dbdd20aSAndroid Build Coastguard Worker 112*6dbdd20aSAndroid Build Coastguard Worker return m(TrackWidget, { 113*6dbdd20aSAndroid Build Coastguard Worker id: node.id, 114*6dbdd20aSAndroid Build Coastguard Worker title: node.title, 115*6dbdd20aSAndroid Build Coastguard Worker path: node.fullPath.join('/'), 116*6dbdd20aSAndroid Build Coastguard Worker heightPx: this.heightPx, 117*6dbdd20aSAndroid Build Coastguard Worker error: Boolean(trackRenderer?.getError()), 118*6dbdd20aSAndroid Build Coastguard Worker chips: trackRenderer?.desc.chips, 119*6dbdd20aSAndroid Build Coastguard Worker indentationLevel, 120*6dbdd20aSAndroid Build Coastguard Worker topOffsetPx, 121*6dbdd20aSAndroid Build Coastguard Worker buttons, 122*6dbdd20aSAndroid Build Coastguard Worker revealOnCreate: revealOnCreate || scrollIntoView, 123*6dbdd20aSAndroid Build Coastguard Worker collapsible: node.hasChildren, 124*6dbdd20aSAndroid Build Coastguard Worker collapsed: node.collapsed, 125*6dbdd20aSAndroid Build Coastguard Worker highlight: this.isHighlighted(node), 126*6dbdd20aSAndroid Build Coastguard Worker isSummary: node.isSummary, 127*6dbdd20aSAndroid Build Coastguard Worker reorderable, 128*6dbdd20aSAndroid Build Coastguard Worker onToggleCollapsed: () => { 129*6dbdd20aSAndroid Build Coastguard Worker node.hasChildren && node.toggleCollapsed(); 130*6dbdd20aSAndroid Build Coastguard Worker }, 131*6dbdd20aSAndroid Build Coastguard Worker onTrackContentMouseMove: (pos, bounds) => { 132*6dbdd20aSAndroid Build Coastguard Worker const timescale = this.getTimescaleForBounds(bounds); 133*6dbdd20aSAndroid Build Coastguard Worker trackRenderer?.track.onMouseMove?.({ 134*6dbdd20aSAndroid Build Coastguard Worker ...pos, 135*6dbdd20aSAndroid Build Coastguard Worker timescale, 136*6dbdd20aSAndroid Build Coastguard Worker }); 137*6dbdd20aSAndroid Build Coastguard Worker raf.scheduleCanvasRedraw(); 138*6dbdd20aSAndroid Build Coastguard Worker }, 139*6dbdd20aSAndroid Build Coastguard Worker onTrackContentMouseOut: () => { 140*6dbdd20aSAndroid Build Coastguard Worker trackRenderer?.track.onMouseOut?.(); 141*6dbdd20aSAndroid Build Coastguard Worker raf.scheduleCanvasRedraw(); 142*6dbdd20aSAndroid Build Coastguard Worker }, 143*6dbdd20aSAndroid Build Coastguard Worker onTrackContentClick: (pos, bounds) => { 144*6dbdd20aSAndroid Build Coastguard Worker const timescale = this.getTimescaleForBounds(bounds); 145*6dbdd20aSAndroid Build Coastguard Worker raf.scheduleCanvasRedraw(); 146*6dbdd20aSAndroid Build Coastguard Worker return ( 147*6dbdd20aSAndroid Build Coastguard Worker trackRenderer?.track.onMouseClick?.({ 148*6dbdd20aSAndroid Build Coastguard Worker ...pos, 149*6dbdd20aSAndroid Build Coastguard Worker timescale, 150*6dbdd20aSAndroid Build Coastguard Worker }) ?? false 151*6dbdd20aSAndroid Build Coastguard Worker ); 152*6dbdd20aSAndroid Build Coastguard Worker }, 153*6dbdd20aSAndroid Build Coastguard Worker onupdate: () => { 154*6dbdd20aSAndroid Build Coastguard Worker trackRenderer?.track.onFullRedraw?.(); 155*6dbdd20aSAndroid Build Coastguard Worker }, 156*6dbdd20aSAndroid Build Coastguard Worker onMoveBefore: (nodeId: string) => { 157*6dbdd20aSAndroid Build Coastguard Worker const targetNode = node.workspace?.getTrackById(nodeId); 158*6dbdd20aSAndroid Build Coastguard Worker if (targetNode !== undefined) { 159*6dbdd20aSAndroid Build Coastguard Worker // Insert the target node before this one 160*6dbdd20aSAndroid Build Coastguard Worker targetNode.parent?.addChildBefore(targetNode, node); 161*6dbdd20aSAndroid Build Coastguard Worker } 162*6dbdd20aSAndroid Build Coastguard Worker }, 163*6dbdd20aSAndroid Build Coastguard Worker onMoveAfter: (nodeId: string) => { 164*6dbdd20aSAndroid Build Coastguard Worker const targetNode = node.workspace?.getTrackById(nodeId); 165*6dbdd20aSAndroid Build Coastguard Worker if (targetNode !== undefined) { 166*6dbdd20aSAndroid Build Coastguard Worker // Insert the target node after this one 167*6dbdd20aSAndroid Build Coastguard Worker targetNode.parent?.addChildAfter(targetNode, node); 168*6dbdd20aSAndroid Build Coastguard Worker } 169*6dbdd20aSAndroid Build Coastguard Worker }, 170*6dbdd20aSAndroid Build Coastguard Worker }); 171*6dbdd20aSAndroid Build Coastguard Worker } 172*6dbdd20aSAndroid Build Coastguard Worker 173*6dbdd20aSAndroid Build Coastguard Worker renderCanvas(ctx: CanvasRenderingContext2D, size: Size2D) { 174*6dbdd20aSAndroid Build Coastguard Worker const {trackRenderer: tr, node} = this.attrs; 175*6dbdd20aSAndroid Build Coastguard Worker 176*6dbdd20aSAndroid Build Coastguard Worker // Don't render if expanded and isSummary 177*6dbdd20aSAndroid Build Coastguard Worker if (node.isSummary && node.expanded) { 178*6dbdd20aSAndroid Build Coastguard Worker return; 179*6dbdd20aSAndroid Build Coastguard Worker } 180*6dbdd20aSAndroid Build Coastguard Worker 181*6dbdd20aSAndroid Build Coastguard Worker const trackSize = { 182*6dbdd20aSAndroid Build Coastguard Worker width: size.width - TRACK_SHELL_WIDTH, 183*6dbdd20aSAndroid Build Coastguard Worker height: size.height, 184*6dbdd20aSAndroid Build Coastguard Worker }; 185*6dbdd20aSAndroid Build Coastguard Worker 186*6dbdd20aSAndroid Build Coastguard Worker using _ = canvasSave(ctx); 187*6dbdd20aSAndroid Build Coastguard Worker ctx.translate(TRACK_SHELL_WIDTH, 0); 188*6dbdd20aSAndroid Build Coastguard Worker canvasClip(ctx, 0, 0, trackSize.width, trackSize.height); 189*6dbdd20aSAndroid Build Coastguard Worker 190*6dbdd20aSAndroid Build Coastguard Worker const visibleWindow = this.attrs.trace.timeline.visibleWindow; 191*6dbdd20aSAndroid Build Coastguard Worker const timescale = new TimeScale(visibleWindow, { 192*6dbdd20aSAndroid Build Coastguard Worker left: 0, 193*6dbdd20aSAndroid Build Coastguard Worker right: trackSize.width, 194*6dbdd20aSAndroid Build Coastguard Worker }); 195*6dbdd20aSAndroid Build Coastguard Worker 196*6dbdd20aSAndroid Build Coastguard Worker if (tr) { 197*6dbdd20aSAndroid Build Coastguard Worker if (!tr.getError()) { 198*6dbdd20aSAndroid Build Coastguard Worker const trackRenderCtx: TrackRenderContext = { 199*6dbdd20aSAndroid Build Coastguard Worker trackUri: tr.desc.uri, 200*6dbdd20aSAndroid Build Coastguard Worker visibleWindow, 201*6dbdd20aSAndroid Build Coastguard Worker size: trackSize, 202*6dbdd20aSAndroid Build Coastguard Worker resolution: calculateResolution(visibleWindow, trackSize.width), 203*6dbdd20aSAndroid Build Coastguard Worker ctx, 204*6dbdd20aSAndroid Build Coastguard Worker timescale, 205*6dbdd20aSAndroid Build Coastguard Worker }; 206*6dbdd20aSAndroid Build Coastguard Worker tr.render(trackRenderCtx); 207*6dbdd20aSAndroid Build Coastguard Worker } 208*6dbdd20aSAndroid Build Coastguard Worker } 209*6dbdd20aSAndroid Build Coastguard Worker 210*6dbdd20aSAndroid Build Coastguard Worker this.highlightIfTrackInAreaSelection(ctx, timescale, node, trackSize); 211*6dbdd20aSAndroid Build Coastguard Worker } 212*6dbdd20aSAndroid Build Coastguard Worker 213*6dbdd20aSAndroid Build Coastguard Worker getSliceVerticalBounds(depth: number): VerticalBounds | undefined { 214*6dbdd20aSAndroid Build Coastguard Worker if (this.attrs.trackRenderer === undefined) { 215*6dbdd20aSAndroid Build Coastguard Worker return undefined; 216*6dbdd20aSAndroid Build Coastguard Worker } 217*6dbdd20aSAndroid Build Coastguard Worker return this.attrs.trackRenderer.track.getSliceVerticalBounds?.(depth); 218*6dbdd20aSAndroid Build Coastguard Worker } 219*6dbdd20aSAndroid Build Coastguard Worker 220*6dbdd20aSAndroid Build Coastguard Worker private getTimescaleForBounds(bounds: Bounds2D) { 221*6dbdd20aSAndroid Build Coastguard Worker const timeWindow = this.attrs.trace.timeline.visibleWindow; 222*6dbdd20aSAndroid Build Coastguard Worker return new TimeScale(timeWindow, { 223*6dbdd20aSAndroid Build Coastguard Worker left: 0, 224*6dbdd20aSAndroid Build Coastguard Worker right: bounds.right - bounds.left, 225*6dbdd20aSAndroid Build Coastguard Worker }); 226*6dbdd20aSAndroid Build Coastguard Worker } 227*6dbdd20aSAndroid Build Coastguard Worker 228*6dbdd20aSAndroid Build Coastguard Worker private isHighlighted(node: TrackNode) { 229*6dbdd20aSAndroid Build Coastguard Worker // The track should be highlighted if the current search result matches this 230*6dbdd20aSAndroid Build Coastguard Worker // track or one of its children. 231*6dbdd20aSAndroid Build Coastguard Worker const searchIndex = this.attrs.trace.search.resultIndex; 232*6dbdd20aSAndroid Build Coastguard Worker const searchResults = this.attrs.trace.search.searchResults; 233*6dbdd20aSAndroid Build Coastguard Worker 234*6dbdd20aSAndroid Build Coastguard Worker if (searchIndex !== -1 && searchResults !== undefined) { 235*6dbdd20aSAndroid Build Coastguard Worker const uri = searchResults.trackUris[searchIndex]; 236*6dbdd20aSAndroid Build Coastguard Worker // Highlight if this or any children match the search results 237*6dbdd20aSAndroid Build Coastguard Worker if ( 238*6dbdd20aSAndroid Build Coastguard Worker uri === node.uri || 239*6dbdd20aSAndroid Build Coastguard Worker node.flatTracksOrdered.find((t) => t.uri === uri) 240*6dbdd20aSAndroid Build Coastguard Worker ) { 241*6dbdd20aSAndroid Build Coastguard Worker return true; 242*6dbdd20aSAndroid Build Coastguard Worker } 243*6dbdd20aSAndroid Build Coastguard Worker } 244*6dbdd20aSAndroid Build Coastguard Worker 245*6dbdd20aSAndroid Build Coastguard Worker const curSelection = this.attrs.trace.selection; 246*6dbdd20aSAndroid Build Coastguard Worker if ( 247*6dbdd20aSAndroid Build Coastguard Worker curSelection.selection.kind === 'track' && 248*6dbdd20aSAndroid Build Coastguard Worker curSelection.selection.trackUri === node.uri 249*6dbdd20aSAndroid Build Coastguard Worker ) { 250*6dbdd20aSAndroid Build Coastguard Worker return true; 251*6dbdd20aSAndroid Build Coastguard Worker } 252*6dbdd20aSAndroid Build Coastguard Worker 253*6dbdd20aSAndroid Build Coastguard Worker return false; 254*6dbdd20aSAndroid Build Coastguard Worker } 255*6dbdd20aSAndroid Build Coastguard Worker 256*6dbdd20aSAndroid Build Coastguard Worker private highlightIfTrackInAreaSelection( 257*6dbdd20aSAndroid Build Coastguard Worker ctx: CanvasRenderingContext2D, 258*6dbdd20aSAndroid Build Coastguard Worker timescale: TimeScale, 259*6dbdd20aSAndroid Build Coastguard Worker node: TrackNode, 260*6dbdd20aSAndroid Build Coastguard Worker size: Size2D, 261*6dbdd20aSAndroid Build Coastguard Worker ) { 262*6dbdd20aSAndroid Build Coastguard Worker const selection = this.attrs.trace.selection.selection; 263*6dbdd20aSAndroid Build Coastguard Worker if (selection.kind !== 'area') { 264*6dbdd20aSAndroid Build Coastguard Worker return; 265*6dbdd20aSAndroid Build Coastguard Worker } 266*6dbdd20aSAndroid Build Coastguard Worker 267*6dbdd20aSAndroid Build Coastguard Worker const tracksWithUris = node.flatTracks.filter( 268*6dbdd20aSAndroid Build Coastguard Worker (t) => t.uri !== undefined, 269*6dbdd20aSAndroid Build Coastguard Worker ) as ReadonlyArray<RequiredField<TrackNode, 'uri'>>; 270*6dbdd20aSAndroid Build Coastguard Worker 271*6dbdd20aSAndroid Build Coastguard Worker let selected = false; 272*6dbdd20aSAndroid Build Coastguard Worker if (node.isSummary) { 273*6dbdd20aSAndroid Build Coastguard Worker selected = tracksWithUris.some((track) => 274*6dbdd20aSAndroid Build Coastguard Worker selection.trackUris.includes(track.uri), 275*6dbdd20aSAndroid Build Coastguard Worker ); 276*6dbdd20aSAndroid Build Coastguard Worker } else { 277*6dbdd20aSAndroid Build Coastguard Worker if (node.uri) { 278*6dbdd20aSAndroid Build Coastguard Worker selected = selection.trackUris.includes(node.uri); 279*6dbdd20aSAndroid Build Coastguard Worker } 280*6dbdd20aSAndroid Build Coastguard Worker } 281*6dbdd20aSAndroid Build Coastguard Worker 282*6dbdd20aSAndroid Build Coastguard Worker if (selected) { 283*6dbdd20aSAndroid Build Coastguard Worker const selectedAreaDuration = selection.end - selection.start; 284*6dbdd20aSAndroid Build Coastguard Worker ctx.fillStyle = SELECTION_FILL_COLOR; 285*6dbdd20aSAndroid Build Coastguard Worker ctx.fillRect( 286*6dbdd20aSAndroid Build Coastguard Worker timescale.timeToPx(selection.start), 287*6dbdd20aSAndroid Build Coastguard Worker 0, 288*6dbdd20aSAndroid Build Coastguard Worker timescale.durationToPx(selectedAreaDuration), 289*6dbdd20aSAndroid Build Coastguard Worker size.height, 290*6dbdd20aSAndroid Build Coastguard Worker ); 291*6dbdd20aSAndroid Build Coastguard Worker } 292*6dbdd20aSAndroid Build Coastguard Worker } 293*6dbdd20aSAndroid Build Coastguard Worker 294*6dbdd20aSAndroid Build Coastguard Worker private renderAreaSelectionCheckbox(node: TrackNode): m.Children { 295*6dbdd20aSAndroid Build Coastguard Worker const selectionManager = this.attrs.trace.selection; 296*6dbdd20aSAndroid Build Coastguard Worker const selection = selectionManager.selection; 297*6dbdd20aSAndroid Build Coastguard Worker if (selection.kind === 'area') { 298*6dbdd20aSAndroid Build Coastguard Worker if (node.isSummary) { 299*6dbdd20aSAndroid Build Coastguard Worker const tracksWithUris = node.flatTracks.filter( 300*6dbdd20aSAndroid Build Coastguard Worker (t) => t.uri !== undefined, 301*6dbdd20aSAndroid Build Coastguard Worker ) as ReadonlyArray<RequiredField<TrackNode, 'uri'>>; 302*6dbdd20aSAndroid Build Coastguard Worker // Check if any nodes within are selected 303*6dbdd20aSAndroid Build Coastguard Worker const childTracksInSelection = tracksWithUris.map((t) => 304*6dbdd20aSAndroid Build Coastguard Worker selection.trackUris.includes(t.uri), 305*6dbdd20aSAndroid Build Coastguard Worker ); 306*6dbdd20aSAndroid Build Coastguard Worker if (childTracksInSelection.every((b) => b)) { 307*6dbdd20aSAndroid Build Coastguard Worker return m(Button, { 308*6dbdd20aSAndroid Build Coastguard Worker onclick: (e: MouseEvent) => { 309*6dbdd20aSAndroid Build Coastguard Worker const uris = tracksWithUris.map((t) => t.uri); 310*6dbdd20aSAndroid Build Coastguard Worker selectionManager.toggleGroupAreaSelection(uris); 311*6dbdd20aSAndroid Build Coastguard Worker e.stopPropagation(); 312*6dbdd20aSAndroid Build Coastguard Worker }, 313*6dbdd20aSAndroid Build Coastguard Worker compact: true, 314*6dbdd20aSAndroid Build Coastguard Worker icon: Icons.Checkbox, 315*6dbdd20aSAndroid Build Coastguard Worker title: 'Remove child tracks from selection', 316*6dbdd20aSAndroid Build Coastguard Worker }); 317*6dbdd20aSAndroid Build Coastguard Worker } else if (childTracksInSelection.some((b) => b)) { 318*6dbdd20aSAndroid Build Coastguard Worker return m(Button, { 319*6dbdd20aSAndroid Build Coastguard Worker onclick: (e: MouseEvent) => { 320*6dbdd20aSAndroid Build Coastguard Worker const uris = tracksWithUris.map((t) => t.uri); 321*6dbdd20aSAndroid Build Coastguard Worker selectionManager.toggleGroupAreaSelection(uris); 322*6dbdd20aSAndroid Build Coastguard Worker e.stopPropagation(); 323*6dbdd20aSAndroid Build Coastguard Worker }, 324*6dbdd20aSAndroid Build Coastguard Worker compact: true, 325*6dbdd20aSAndroid Build Coastguard Worker icon: Icons.IndeterminateCheckbox, 326*6dbdd20aSAndroid Build Coastguard Worker title: 'Add remaining child tracks to selection', 327*6dbdd20aSAndroid Build Coastguard Worker }); 328*6dbdd20aSAndroid Build Coastguard Worker } else { 329*6dbdd20aSAndroid Build Coastguard Worker return m(Button, { 330*6dbdd20aSAndroid Build Coastguard Worker onclick: (e: MouseEvent) => { 331*6dbdd20aSAndroid Build Coastguard Worker const uris = tracksWithUris.map((t) => t.uri); 332*6dbdd20aSAndroid Build Coastguard Worker selectionManager.toggleGroupAreaSelection(uris); 333*6dbdd20aSAndroid Build Coastguard Worker e.stopPropagation(); 334*6dbdd20aSAndroid Build Coastguard Worker }, 335*6dbdd20aSAndroid Build Coastguard Worker compact: true, 336*6dbdd20aSAndroid Build Coastguard Worker icon: Icons.BlankCheckbox, 337*6dbdd20aSAndroid Build Coastguard Worker title: 'Add child tracks to selection', 338*6dbdd20aSAndroid Build Coastguard Worker }); 339*6dbdd20aSAndroid Build Coastguard Worker } 340*6dbdd20aSAndroid Build Coastguard Worker } else { 341*6dbdd20aSAndroid Build Coastguard Worker const nodeUri = node.uri; 342*6dbdd20aSAndroid Build Coastguard Worker if (nodeUri) { 343*6dbdd20aSAndroid Build Coastguard Worker return ( 344*6dbdd20aSAndroid Build Coastguard Worker selection.kind === 'area' && 345*6dbdd20aSAndroid Build Coastguard Worker m(Button, { 346*6dbdd20aSAndroid Build Coastguard Worker onclick: (e: MouseEvent) => { 347*6dbdd20aSAndroid Build Coastguard Worker selectionManager.toggleTrackAreaSelection(nodeUri); 348*6dbdd20aSAndroid Build Coastguard Worker e.stopPropagation(); 349*6dbdd20aSAndroid Build Coastguard Worker }, 350*6dbdd20aSAndroid Build Coastguard Worker compact: true, 351*6dbdd20aSAndroid Build Coastguard Worker ...(selection.trackUris.includes(nodeUri) 352*6dbdd20aSAndroid Build Coastguard Worker ? {icon: Icons.Checkbox, title: 'Remove track'} 353*6dbdd20aSAndroid Build Coastguard Worker : {icon: Icons.BlankCheckbox, title: 'Add track to selection'}), 354*6dbdd20aSAndroid Build Coastguard Worker }) 355*6dbdd20aSAndroid Build Coastguard Worker ); 356*6dbdd20aSAndroid Build Coastguard Worker } 357*6dbdd20aSAndroid Build Coastguard Worker } 358*6dbdd20aSAndroid Build Coastguard Worker } 359*6dbdd20aSAndroid Build Coastguard Worker return undefined; 360*6dbdd20aSAndroid Build Coastguard Worker } 361*6dbdd20aSAndroid Build Coastguard Worker} 362*6dbdd20aSAndroid Build Coastguard Worker 363*6dbdd20aSAndroid Build Coastguard Workerfunction renderCrashButton(error: Error, pluginId?: string) { 364*6dbdd20aSAndroid Build Coastguard Worker return m( 365*6dbdd20aSAndroid Build Coastguard Worker Popup, 366*6dbdd20aSAndroid Build Coastguard Worker { 367*6dbdd20aSAndroid Build Coastguard Worker trigger: m(Button, { 368*6dbdd20aSAndroid Build Coastguard Worker icon: Icons.Crashed, 369*6dbdd20aSAndroid Build Coastguard Worker compact: true, 370*6dbdd20aSAndroid Build Coastguard Worker }), 371*6dbdd20aSAndroid Build Coastguard Worker }, 372*6dbdd20aSAndroid Build Coastguard Worker m( 373*6dbdd20aSAndroid Build Coastguard Worker '.pf-track-crash-popup', 374*6dbdd20aSAndroid Build Coastguard Worker m('span', 'This track has crashed.'), 375*6dbdd20aSAndroid Build Coastguard Worker pluginId && m('span', `Owning plugin: ${pluginId}`), 376*6dbdd20aSAndroid Build Coastguard Worker m(Button, { 377*6dbdd20aSAndroid Build Coastguard Worker label: 'View & Report Crash', 378*6dbdd20aSAndroid Build Coastguard Worker intent: Intent.Primary, 379*6dbdd20aSAndroid Build Coastguard Worker className: Popup.DISMISS_POPUP_GROUP_CLASS, 380*6dbdd20aSAndroid Build Coastguard Worker onclick: () => { 381*6dbdd20aSAndroid Build Coastguard Worker throw error; 382*6dbdd20aSAndroid Build Coastguard Worker }, 383*6dbdd20aSAndroid Build Coastguard Worker }), 384*6dbdd20aSAndroid Build Coastguard Worker // TODO(stevegolton): In the future we should provide a quick way to 385*6dbdd20aSAndroid Build Coastguard Worker // disable the plugin, or provide a link to the plugin page, but this 386*6dbdd20aSAndroid Build Coastguard Worker // relies on the plugin page being fully functional. 387*6dbdd20aSAndroid Build Coastguard Worker ), 388*6dbdd20aSAndroid Build Coastguard Worker ); 389*6dbdd20aSAndroid Build Coastguard Worker} 390*6dbdd20aSAndroid Build Coastguard Worker 391*6dbdd20aSAndroid Build Coastguard Workerfunction renderCloseButton(node: TrackNode) { 392*6dbdd20aSAndroid Build Coastguard Worker return m(Button, { 393*6dbdd20aSAndroid Build Coastguard Worker onclick: (e) => { 394*6dbdd20aSAndroid Build Coastguard Worker node.remove(); 395*6dbdd20aSAndroid Build Coastguard Worker e.stopPropagation(); 396*6dbdd20aSAndroid Build Coastguard Worker }, 397*6dbdd20aSAndroid Build Coastguard Worker icon: Icons.Close, 398*6dbdd20aSAndroid Build Coastguard Worker title: 'Close track', 399*6dbdd20aSAndroid Build Coastguard Worker compact: true, 400*6dbdd20aSAndroid Build Coastguard Worker }); 401*6dbdd20aSAndroid Build Coastguard Worker} 402*6dbdd20aSAndroid Build Coastguard Worker 403*6dbdd20aSAndroid Build Coastguard Workerfunction renderPinButton(node: TrackNode): m.Children { 404*6dbdd20aSAndroid Build Coastguard Worker const isPinned = node.isPinned; 405*6dbdd20aSAndroid Build Coastguard Worker return m(Button, { 406*6dbdd20aSAndroid Build Coastguard Worker className: classNames(!isPinned && 'pf-visible-on-hover'), 407*6dbdd20aSAndroid Build Coastguard Worker onclick: (e) => { 408*6dbdd20aSAndroid Build Coastguard Worker isPinned ? node.unpin() : node.pin(); 409*6dbdd20aSAndroid Build Coastguard Worker e.stopPropagation(); 410*6dbdd20aSAndroid Build Coastguard Worker }, 411*6dbdd20aSAndroid Build Coastguard Worker icon: Icons.Pin, 412*6dbdd20aSAndroid Build Coastguard Worker iconFilled: isPinned, 413*6dbdd20aSAndroid Build Coastguard Worker title: isPinned ? 'Unpin' : 'Pin to top', 414*6dbdd20aSAndroid Build Coastguard Worker compact: true, 415*6dbdd20aSAndroid Build Coastguard Worker }); 416*6dbdd20aSAndroid Build Coastguard Worker} 417*6dbdd20aSAndroid Build Coastguard Worker 418*6dbdd20aSAndroid Build Coastguard Workerfunction renderTrackDetailsButton( 419*6dbdd20aSAndroid Build Coastguard Worker node: TrackNode, 420*6dbdd20aSAndroid Build Coastguard Worker td?: TrackDescriptor, 421*6dbdd20aSAndroid Build Coastguard Worker): m.Children { 422*6dbdd20aSAndroid Build Coastguard Worker let parent = node.parent; 423*6dbdd20aSAndroid Build Coastguard Worker let fullPath: m.ChildArray = [node.title]; 424*6dbdd20aSAndroid Build Coastguard Worker while (parent && parent instanceof TrackNode) { 425*6dbdd20aSAndroid Build Coastguard Worker fullPath = [parent.title, ' \u2023 ', ...fullPath]; 426*6dbdd20aSAndroid Build Coastguard Worker parent = parent.parent; 427*6dbdd20aSAndroid Build Coastguard Worker } 428*6dbdd20aSAndroid Build Coastguard Worker return m( 429*6dbdd20aSAndroid Build Coastguard Worker Popup, 430*6dbdd20aSAndroid Build Coastguard Worker { 431*6dbdd20aSAndroid Build Coastguard Worker trigger: m(Button, { 432*6dbdd20aSAndroid Build Coastguard Worker className: 'pf-visible-on-hover', 433*6dbdd20aSAndroid Build Coastguard Worker icon: 'info', 434*6dbdd20aSAndroid Build Coastguard Worker title: 'Show track details', 435*6dbdd20aSAndroid Build Coastguard Worker compact: true, 436*6dbdd20aSAndroid Build Coastguard Worker }), 437*6dbdd20aSAndroid Build Coastguard Worker position: PopupPosition.Bottom, 438*6dbdd20aSAndroid Build Coastguard Worker }, 439*6dbdd20aSAndroid Build Coastguard Worker m( 440*6dbdd20aSAndroid Build Coastguard Worker '.pf-track-details-dropdown', 441*6dbdd20aSAndroid Build Coastguard Worker m( 442*6dbdd20aSAndroid Build Coastguard Worker Tree, 443*6dbdd20aSAndroid Build Coastguard Worker m(TreeNode, {left: 'Track Node ID', right: node.id}), 444*6dbdd20aSAndroid Build Coastguard Worker m(TreeNode, {left: 'Collapsed', right: `${node.collapsed}`}), 445*6dbdd20aSAndroid Build Coastguard Worker m(TreeNode, {left: 'URI', right: node.uri}), 446*6dbdd20aSAndroid Build Coastguard Worker m(TreeNode, {left: 'Is Summary Track', right: `${node.isSummary}`}), 447*6dbdd20aSAndroid Build Coastguard Worker m(TreeNode, { 448*6dbdd20aSAndroid Build Coastguard Worker left: 'SortOrder', 449*6dbdd20aSAndroid Build Coastguard Worker right: node.sortOrder ?? '0 (undefined)', 450*6dbdd20aSAndroid Build Coastguard Worker }), 451*6dbdd20aSAndroid Build Coastguard Worker m(TreeNode, {left: 'Path', right: fullPath}), 452*6dbdd20aSAndroid Build Coastguard Worker m(TreeNode, {left: 'Title', right: node.title}), 453*6dbdd20aSAndroid Build Coastguard Worker m(TreeNode, { 454*6dbdd20aSAndroid Build Coastguard Worker left: 'Workspace', 455*6dbdd20aSAndroid Build Coastguard Worker right: node.workspace?.title ?? '[no workspace]', 456*6dbdd20aSAndroid Build Coastguard Worker }), 457*6dbdd20aSAndroid Build Coastguard Worker td && m(TreeNode, {left: 'Plugin ID', right: td.pluginId}), 458*6dbdd20aSAndroid Build Coastguard Worker td && 459*6dbdd20aSAndroid Build Coastguard Worker m( 460*6dbdd20aSAndroid Build Coastguard Worker TreeNode, 461*6dbdd20aSAndroid Build Coastguard Worker {left: 'Tags'}, 462*6dbdd20aSAndroid Build Coastguard Worker td.tags && 463*6dbdd20aSAndroid Build Coastguard Worker Object.entries(td.tags).map(([key, value]) => { 464*6dbdd20aSAndroid Build Coastguard Worker return m(TreeNode, {left: key, right: value?.toString()}); 465*6dbdd20aSAndroid Build Coastguard Worker }), 466*6dbdd20aSAndroid Build Coastguard Worker ), 467*6dbdd20aSAndroid Build Coastguard Worker ), 468*6dbdd20aSAndroid Build Coastguard Worker ), 469*6dbdd20aSAndroid Build Coastguard Worker ); 470*6dbdd20aSAndroid Build Coastguard Worker} 471