• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2024 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 m from 'mithril';
16import {canvasSave} from '../../base/canvas_utils';
17import {DisposableStack} from '../../base/disposable_stack';
18import {toHTMLElement} from '../../base/dom_utils';
19import {Rect2D, Size2D} from '../../base/geom';
20import {assertExists} from '../../base/logging';
21import {TimeScale} from '../../base/time_scale';
22import {ZonedInteractionHandler} from '../../base/zoned_interaction_handler';
23import {TraceImpl} from '../../core/trace_impl';
24import {
25  VirtualOverlayCanvas,
26  VirtualOverlayCanvasDrawContext,
27} from '../../widgets/virtual_overlay_canvas';
28import {TRACK_SHELL_WIDTH} from '../css_constants';
29import {NotesPanel} from './notes_panel';
30import {TickmarkPanel} from './tickmark_panel';
31import {TimeAxisPanel} from './time_axis_panel';
32import {TimeSelectionPanel} from './time_selection_panel';
33import {
34  shiftDragPanInteraction,
35  wheelNavigationInteraction,
36} from './timeline_interactions';
37
38export interface TimelineHeaderAttrs {
39  // The trace to use for timeline access et al.
40  readonly trace: TraceImpl;
41
42  // Called when the visible area of the timeline changes size. This is the area
43  // to the right of the header is actually rendered on.
44  onTimelineBoundsChange?(rect: Rect2D): void;
45
46  readonly className?: string;
47}
48
49// TODO(stevegolton): The panel concept has been largely removed. It's just
50// defined here so that we don't have to change the implementation of the
51// various header panels listed here. We should consolidate this in the future.
52interface Panel {
53  readonly height: number;
54  render(): m.Children;
55  renderCanvas(ctx: CanvasRenderingContext2D, size: Size2D): void;
56}
57
58/**
59 * This component defines the header of the timeline and handles it's mouse
60 * interactions.
61 *
62 * The timeline header contains:
63 * - The axis (ticks) and time labels
64 * - The selection bar
65 * - The notes bar
66 * - The tickmark bar (highlights that appear when searching)
67 */
68export class TimelineHeader implements m.ClassComponent<TimelineHeaderAttrs> {
69  private readonly trash = new DisposableStack();
70  private readonly trace: TraceImpl;
71  private readonly panels: ReadonlyArray<Panel>;
72  private interactions?: ZonedInteractionHandler;
73
74  constructor({attrs}: m.Vnode<TimelineHeaderAttrs>) {
75    this.trace = attrs.trace;
76    this.panels = [
77      new TimeAxisPanel(attrs.trace),
78      new TimeSelectionPanel(attrs.trace),
79      new NotesPanel(attrs.trace),
80      new TickmarkPanel(attrs.trace),
81    ];
82  }
83
84  view({attrs}: m.Vnode<TimelineHeaderAttrs>) {
85    return m(
86      '.pf-timeline-header',
87      {className: attrs.className},
88      m(
89        VirtualOverlayCanvas,
90        {
91          onMount: (redrawCanvas) =>
92            attrs.trace.raf.addCanvasRedrawCallback(redrawCanvas),
93          disableCanvasRedrawOnMithrilUpdates: true,
94          onCanvasRedraw: (ctx) => {
95            const rect = new Rect2D({
96              left: TRACK_SHELL_WIDTH,
97              right: ctx.virtualCanvasSize.width,
98              top: 0,
99              bottom: 0,
100            });
101            attrs.onTimelineBoundsChange?.(rect);
102            this.drawCanvas(ctx);
103          },
104        },
105        this.panels.map((p) => p.render()),
106      ),
107    );
108  }
109
110  oncreate({dom}: m.VnodeDOM<TimelineHeaderAttrs>) {
111    const timelineHeaderElement = toHTMLElement(dom);
112    this.interactions = new ZonedInteractionHandler(timelineHeaderElement);
113    this.trash.use(this.interactions);
114  }
115
116  onremove() {
117    this.trash.dispose();
118  }
119
120  private drawCanvas({
121    ctx,
122    virtualCanvasSize,
123  }: VirtualOverlayCanvasDrawContext) {
124    let top = 0;
125    for (const p of this.panels) {
126      using _ = canvasSave(ctx);
127      ctx.translate(0, top);
128      p.renderCanvas(ctx, {width: virtualCanvasSize.width, height: p.height});
129      top += p.height;
130    }
131
132    const timelineRect = new Rect2D({
133      left: TRACK_SHELL_WIDTH,
134      top: 0,
135      right: virtualCanvasSize.width,
136      bottom: virtualCanvasSize.height,
137    });
138
139    // Always grab the latest visible window and create a timescale
140    // out of it.
141    const visibleWindow = this.trace.timeline.visibleWindow;
142    const timescale = new TimeScale(visibleWindow, timelineRect);
143
144    assertExists(this.interactions).update([
145      shiftDragPanInteraction(this.trace, timelineRect, timescale),
146      wheelNavigationInteraction(this.trace, timelineRect, timescale),
147      {
148        // Allow making area selections (no tracks) by dragging on the header
149        // timeline.
150        id: 'area-selection',
151        area: timelineRect,
152        drag: {
153          minDistance: 1,
154          cursorWhileDragging: 'text',
155          onDrag: (e) => {
156            const dragRect = Rect2D.fromPoints(e.dragStart, e.dragCurrent);
157            const timeSpan = timescale
158              .pxSpanToHpTimeSpan(dragRect)
159              .toTimeSpan();
160            this.trace.timeline.selectedSpan = timeSpan;
161          },
162          onDragEnd: (e) => {
163            const dragRect = Rect2D.fromPoints(e.dragStart, e.dragCurrent);
164            const timeSpan = timescale
165              .pxSpanToHpTimeSpan(dragRect)
166              .toTimeSpan();
167            this.trace.selection.selectArea({
168              start: timeSpan.start,
169              end: timeSpan.end,
170              trackUris: [],
171            });
172            this.trace.timeline.selectedSpan = undefined;
173          },
174        },
175      },
176    ]);
177  }
178}
179