chromium/chrome/browser/resources/lens/overlay/selection_utils.ts

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview Utility file for storing shared types and helper functions for
 * the different selection components.
 */

// The number of pixels the pointer needs to move before being considered a drag
export const DRAG_THRESHOLD = 5;

export enum DragFeature {
  NONE = 0,
  TEXT = 1,
  MANUAL_REGION = 2,
  POST_SELECTION = 3,
}

export enum GestureState {
  // No gesture is currently happening.
  NOT_STARTED = 0,
  // A gesture is starting, indicated by a pointerdown event.
  STARTING = 1,
  // A drag is currently happening, indicated by pointer moving far enough away
  // from the initial gesture position.
  DRAGGING = 2,
  // A drag is finished, indicated by a pointerup event.
  FINISHED = 3,
}

export enum CursorType {
  DEFAULT = 0,
  POINTER = 1,
  CROSSHAIR = 2,
  TEXT = 3,
}

// Specifies which feature is requesting to control the Shimmer. Features are
// ordered by priority, meaning requesters with higher enum values can take
// control from lower value requesters, but not vice versa. For example, if
// CURSOR is the requester, and a new focus region gets called for SEGMENTATION,
// the focus region request will be executed. But if CURSOR sends a focus region
// request while SEGMENTATION has control, the request will be ignored.
export enum ShimmerControlRequester {
  NONE = 0,
  CURSOR = 1,
  POST_SELECTION = 2,
  SEGMENTATION = 3,
  MANUAL_REGION = 4,
}

// Region sent to OverlayShimmerCanvasElement to focus the shimmer on.
// The numbers should be normalized to the image dimensions, between 0 and 1.
export interface OverlayShimmerFocusedRegion {
  top: number;
  left: number;
  width: number;
  height: number;
  requester: ShimmerControlRequester;
}

// Request to unfocus a region and relinquish control from the given requester.
export interface OverlayShimmerUnfocusRegion {
  requester: ShimmerControlRequester;
}

export interface GestureEvent {
  // The state of this event.
  state: GestureState;
  // The x coordinate (pixel value) this gesture started at.
  startX: number;
  // The y coordinate (pixel value) this gesture started at.
  startY: number;
  // The x coordinate (pixel value) this gesture is currently at.
  clientX: number;
  // The y coordinate (pixel value) this gesture is currently at.
  clientY: number;
}

/** A simple interface representing a point. */
export interface Point {
  x: number;
  y: number;
}

// Returns an empty GestureEvent
export function emptyGestureEvent(): GestureEvent {
  return {
    state: GestureState.NOT_STARTED,
    startX: 0,
    startY: 0,
    clientX: 0,
    clientY: 0,
  };
}

/**
 * Helper function to dispatch event to focus the shimmer on a region. This
 * should be used instead of directly dispatching the event, so if
 * implementation changes, it can be easily changed across the codebase.
 */
export function focusShimmerOnRegion(
    dispatchEl: HTMLElement, top: number, left: number, width: number,
    height: number, requester: ShimmerControlRequester) {
  dispatchEl.dispatchEvent(
      new CustomEvent<OverlayShimmerFocusedRegion>('focus-region', {
        bubbles: true,
        composed: true,
        detail: {
          top,
          left,
          width,
          height,
          requester,
        },
      }));
}

/**
 * Helper function to dispatch event to unfocus the shimmer. This should be used
 * instead of directly dispatching the event, so if implementation changes, it
 * can be easily changed across the codebase.
 */
export function unfocusShimmer(
    dispatchEl: HTMLElement, requester: ShimmerControlRequester) {
  dispatchEl.dispatchEvent(
      new CustomEvent<OverlayShimmerUnfocusRegion>('unfocus-region', {
        bubbles: true,
        composed: true,
        detail: {requester},
      }));
}

// Converts the clientX and clientY to be relative to the given parent bounds
// instead of the viewport. If the event is out of the parent bounds, returns
// the closest point to those bounds.
export function getRelativeCoordinate(
    coord: Point, parentBounds: DOMRect): Point {
  return {
    x: Math.max(0, Math.min(coord.x, parentBounds.right) - parentBounds.left),
    y: Math.max(0, Math.min(coord.y, parentBounds.bottom) - parentBounds.top),
  };
}