1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and affiliates.
    
  3.  *
    
  4.  * This source code is licensed under the MIT license found in the
    
  5.  * LICENSE file in the root directory of this source tree.
    
  6.  *
    
  7.  * @flow
    
  8.  */
    
  9. 
    
  10. import type {Rect, Size} from '../view-base';
    
  11. 
    
  12. import {
    
  13.   durationToWidth,
    
  14.   positioningScaleFactor,
    
  15.   positionToTimestamp,
    
  16.   timestampToPosition,
    
  17. } from './utils/positioning';
    
  18. import {
    
  19.   View,
    
  20.   Surface,
    
  21.   rectIntersectsRect,
    
  22.   intersectionOfRects,
    
  23. } from '../view-base';
    
  24. import {
    
  25.   COLORS,
    
  26.   INTERVAL_TIMES,
    
  27.   LABEL_SIZE,
    
  28.   FONT_SIZE,
    
  29.   MARKER_HEIGHT,
    
  30.   MARKER_TEXT_PADDING,
    
  31.   MARKER_TICK_HEIGHT,
    
  32.   MIN_INTERVAL_SIZE_PX,
    
  33.   BORDER_SIZE,
    
  34. } from './constants';
    
  35. 
    
  36. const HEADER_HEIGHT_FIXED = MARKER_HEIGHT + BORDER_SIZE;
    
  37. const LABEL_FIXED_WIDTH = LABEL_SIZE + BORDER_SIZE;
    
  38. 
    
  39. export class TimeAxisMarkersView extends View {
    
  40.   _totalDuration: number;
    
  41.   _intrinsicSize: Size;
    
  42. 
    
  43.   constructor(surface: Surface, frame: Rect, totalDuration: number) {
    
  44.     super(surface, frame);
    
  45.     this._totalDuration = totalDuration;
    
  46.     this._intrinsicSize = {
    
  47.       width: this._totalDuration,
    
  48.       height: HEADER_HEIGHT_FIXED,
    
  49.     };
    
  50.   }
    
  51. 
    
  52.   desiredSize(): Size {
    
  53.     return this._intrinsicSize;
    
  54.   }
    
  55. 
    
  56.   // Time mark intervals vary based on the current zoom range and the time it represents.
    
  57.   // In Chrome, these seem to range from 70-140 pixels wide.
    
  58.   // Time wise, they represent intervals of e.g. 1s, 500ms, 200ms, 100ms, 50ms, 20ms.
    
  59.   // Based on zoom, we should determine which amount to actually show.
    
  60.   _getTimeTickInterval(scaleFactor: number): number {
    
  61.     for (let i = 0; i < INTERVAL_TIMES.length; i++) {
    
  62.       const currentInterval = INTERVAL_TIMES[i];
    
  63.       const intervalWidth = durationToWidth(currentInterval, scaleFactor);
    
  64.       if (intervalWidth > MIN_INTERVAL_SIZE_PX) {
    
  65.         return currentInterval;
    
  66.       }
    
  67.     }
    
  68.     return INTERVAL_TIMES[0];
    
  69.   }
    
  70. 
    
  71.   draw(context: CanvasRenderingContext2D) {
    
  72.     const {frame, _intrinsicSize, visibleArea} = this;
    
  73.     const clippedFrame = {
    
  74.       origin: frame.origin,
    
  75.       size: {
    
  76.         width: frame.size.width,
    
  77.         height: _intrinsicSize.height,
    
  78.       },
    
  79.     };
    
  80.     const drawableRect = intersectionOfRects(clippedFrame, visibleArea);
    
  81. 
    
  82.     // Clear background
    
  83.     context.fillStyle = COLORS.BACKGROUND;
    
  84.     context.fillRect(
    
  85.       drawableRect.origin.x,
    
  86.       drawableRect.origin.y,
    
  87.       drawableRect.size.width,
    
  88.       drawableRect.size.height,
    
  89.     );
    
  90. 
    
  91.     const scaleFactor = positioningScaleFactor(
    
  92.       _intrinsicSize.width,
    
  93.       clippedFrame,
    
  94.     );
    
  95.     const interval = this._getTimeTickInterval(scaleFactor);
    
  96.     const firstIntervalTimestamp =
    
  97.       Math.ceil(
    
  98.         positionToTimestamp(
    
  99.           drawableRect.origin.x - LABEL_FIXED_WIDTH,
    
  100.           scaleFactor,
    
  101.           clippedFrame,
    
  102.         ) / interval,
    
  103.       ) * interval;
    
  104. 
    
  105.     for (
    
  106.       let markerTimestamp = firstIntervalTimestamp;
    
  107.       true;
    
  108.       markerTimestamp += interval
    
  109.     ) {
    
  110.       if (markerTimestamp <= 0) {
    
  111.         continue; // Timestamps < are probably a bug; markers at 0 are ugly.
    
  112.       }
    
  113. 
    
  114.       const x = timestampToPosition(markerTimestamp, scaleFactor, clippedFrame);
    
  115.       if (x > drawableRect.origin.x + drawableRect.size.width) {
    
  116.         break; // Not in view
    
  117.       }
    
  118. 
    
  119.       const markerLabel = Math.round(markerTimestamp);
    
  120. 
    
  121.       context.fillStyle = COLORS.PRIORITY_BORDER;
    
  122.       context.fillRect(
    
  123.         x,
    
  124.         drawableRect.origin.y + MARKER_HEIGHT - MARKER_TICK_HEIGHT,
    
  125.         BORDER_SIZE,
    
  126.         MARKER_TICK_HEIGHT,
    
  127.       );
    
  128. 
    
  129.       context.fillStyle = COLORS.TIME_MARKER_LABEL;
    
  130.       context.textAlign = 'right';
    
  131.       context.textBaseline = 'middle';
    
  132.       context.font = `${FONT_SIZE}px sans-serif`;
    
  133.       context.fillText(
    
  134.         `${markerLabel}ms`,
    
  135.         x - MARKER_TEXT_PADDING,
    
  136.         MARKER_HEIGHT / 2,
    
  137.       );
    
  138.     }
    
  139. 
    
  140.     // Render bottom border.
    
  141.     // Propose border rect, check if intersects with `rect`, draw intersection.
    
  142.     const borderFrame: Rect = {
    
  143.       origin: {
    
  144.         x: clippedFrame.origin.x,
    
  145.         y: clippedFrame.origin.y + clippedFrame.size.height - BORDER_SIZE,
    
  146.       },
    
  147.       size: {
    
  148.         width: clippedFrame.size.width,
    
  149.         height: BORDER_SIZE,
    
  150.       },
    
  151.     };
    
  152.     if (rectIntersectsRect(borderFrame, visibleArea)) {
    
  153.       const borderDrawableRect = intersectionOfRects(borderFrame, visibleArea);
    
  154.       context.fillStyle = COLORS.PRIORITY_BORDER;
    
  155.       context.fillRect(
    
  156.         borderDrawableRect.origin.x,
    
  157.         borderDrawableRect.origin.y,
    
  158.         borderDrawableRect.size.width,
    
  159.         borderDrawableRect.size.height,
    
  160.       );
    
  161.     }
    
  162.   }
    
  163. }