chromium/chrome/browser/resources/side_panel/read_anything/app_style_updater.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.

import type {CrLitElement} from '//resources/lit/v3_0/lit.rollup.js';

// Constants for styling the app when page zoom changes.
const OVERFLOW_X_TYPICAL = 'hidden';
const OVERFLOW_X_SCROLL = 'scroll';
const MIN_WIDTH_TYPICAL = 'auto';
const MIN_WIDTH_OVERFLOW = 'fit-content';
// Empty state colors.
const EMPTY_STATE_HEADING = 'var(--color-read-anything-foreground';
const EMPTY_STATE_BODY_DARK = 'var(--google-grey-500)';
const EMPTY_STATE_BODY_LIGHT = 'var(--google-grey-700)';
const EMPTY_STATE_BODY_DEFAULT =
    'var(--color-side-panel-card-secondary-foreground)';
// Container colors.
const BACKGROUND_DEFAULT = 'var(--color-sys-base-container-elevated)';
const BACKGROUND_CUSTOM = 'var(--color-read-anything-background';
const FOREGROUND_DEFAULT = 'var(--color-sys-on-surface)';
const FOREGROUND_CUSTOM = 'var(--color-read-anything-foreground';
const TRANSPARENT = 'transparent';
// User text selection colors.
const SELECTION_BACKGROUND_DEFAULT = 'var(--color-text-selection-background)';
const SELECTION_BACKGROUND_CUSTOM = 'var(--color-read-anything-text-selection';
const SELECTION_FOREGROUND_DEFAULT = 'var(--color-text-selection-foreground)';
const SELECTION_FOREGROUND_DARK = 'var(--google-grey-900)';
const SELECTION_FOREGROUND_LIGHT = 'var(--google-grey-800)';
// Read aloud highlight colors.
const HIGHLIGHT_CURRENT =
    'var(--color-read-anything-current-read-aloud-highlight';
const HIGHLIGHT_PREVIOUS_DEFAULT = 'var(--color-sys-on-surface-subtle)';
const HIGHLIGHT_PREVIOUS_CUSTOM =
    'var(--color-read-anything-previous-read-aloud-highlight';
// Link colors.
const LINK_DEFAULT = 'var(--color-read-anything-link-default';
const LINK_VISITED = 'var(--color-read-anything-link-visited';

// Suffixes used in combination with the color vars above to get the color
// values for the current theme.
enum ColorSuffix {
  DEFAULT = '',
  DARK = '-dark',
  LIGHT = '-light',
  YELLOW = '-yellow',
  BLUE = '-blue',
}

// Handles updating the visual styles for the Reading mode content panel.
export class AppStyleUpdater {
  private app_: CrLitElement;

  constructor(app: CrLitElement) {
    this.app_ = app;
  }

  setAllTextStyles() {
    this.setLineSpacing();
    this.setLetterSpacing();
    this.setFont();
    this.setFontSize();
    this.setTheme();
  }

  setLineSpacing() {
    this.setStyle_(
        '--line-height',
        `${
            chrome.readingMode.getLineSpacingValue(
                chrome.readingMode.lineSpacing)}`);
  }

  setLetterSpacing() {
    const letterSpacing = chrome.readingMode.getLetterSpacingValue(
        chrome.readingMode.letterSpacing);
    this.setStyle_('--letter-spacing', letterSpacing + 'em');
  }

  setFontSize() {
    this.setStyle_('--font-size', chrome.readingMode.fontSize + 'em');
  }

  setFont() {
    this.setStyle_(
        '--font-family',
        chrome.readingMode.getValidatedFontName(chrome.readingMode.fontName));
  }

  setHighlight() {
    this.setStyle_(
        '--current-highlight-bg-color',
        this.getCurrentHighlightColor_(this.getCurrentColorSuffix_()));
  }

  resetToolbar() {
    this.setStyle_('--app-overflow-x', OVERFLOW_X_TYPICAL);
    this.setStyle_('--container-min-width', MIN_WIDTH_TYPICAL);
  }

  overflowToolbar(shouldScroll: boolean) {
    this.setStyle_(
        '--app-overflow-x',
        shouldScroll ? OVERFLOW_X_SCROLL : OVERFLOW_X_TYPICAL);
    this.setStyle_(
        // When we scroll, we should allow the container to expand and scroll
        // horizontally.
        '--container-min-width',
        shouldScroll ? MIN_WIDTH_OVERFLOW : MIN_WIDTH_TYPICAL);
  }

  setTheme() {
    const colorSuffix = this.getCurrentColorSuffix_();
    this.setStyle_('--background-color', this.getBackgroundColor_(colorSuffix));
    this.setStyle_('--foreground-color', this.getForegroundColor_(colorSuffix));
    this.setStyle_('--selection-color', this.getSelectionColor_(colorSuffix));
    this.setStyle_(
        '--current-highlight-bg-color',
        this.getCurrentHighlightColor_(colorSuffix));
    this.setStyle_(
        '--previous-highlight-color',
        this.getPreviousHighlightColor_(colorSuffix));
    this.setStyle_(
        '--sp-empty-state-heading-color',
        EMPTY_STATE_HEADING + `${colorSuffix})`);
    this.setStyle_(
        '--sp-empty-state-body-color',
        this.getEmptyStateBodyColor_(colorSuffix));
    this.setStyle_('--link-color', LINK_DEFAULT + `${colorSuffix})`);
    this.setStyle_('--visited-link-color', LINK_VISITED + `${colorSuffix})`);

    document.documentElement.style.setProperty(
        '--selection-color', this.getSelectionColor_(colorSuffix));
    document.documentElement.style.setProperty(
        '--selection-text-color', this.getSelectionTextColor_(colorSuffix));
  }

  private setStyle_(key: string, val: string) {
    this.app_.style.setProperty(key, val);
  }

  private getCurrentColorSuffix_(): ColorSuffix {
    switch (chrome.readingMode.colorTheme) {
      case chrome.readingMode.lightTheme:
        return ColorSuffix.LIGHT;
      case chrome.readingMode.darkTheme:
        return ColorSuffix.DARK;
      case chrome.readingMode.yellowTheme:
        return ColorSuffix.YELLOW;
      case chrome.readingMode.blueTheme:
        return ColorSuffix.BLUE;
      default:
        return ColorSuffix.DEFAULT;
    }
  }

  private getEmptyStateBodyColor_(colorSuffix: ColorSuffix): string {
    switch (colorSuffix) {
      case ColorSuffix.DEFAULT:
        return EMPTY_STATE_BODY_DEFAULT;
      case ColorSuffix.DARK:
        return EMPTY_STATE_BODY_DARK;
      default:
        return EMPTY_STATE_BODY_LIGHT;
    }
  }

  private getCurrentHighlightColor_(colorSuffix: ColorSuffix): string {
    if (!chrome.readingMode.isHighlightOn()) {
      return TRANSPARENT;
    }
    if (colorSuffix === ColorSuffix.DEFAULT) {
      return SELECTION_BACKGROUND_DEFAULT;
    }
    return HIGHLIGHT_CURRENT + `${colorSuffix})`;
  }

  private getPreviousHighlightColor_(colorSuffix: ColorSuffix): string {
    return (colorSuffix === ColorSuffix.DEFAULT) ?
        HIGHLIGHT_PREVIOUS_DEFAULT :
        (HIGHLIGHT_PREVIOUS_CUSTOM + `${colorSuffix})`);
  }

  private getBackgroundColor_(colorSuffix: ColorSuffix): string {
    return (colorSuffix === ColorSuffix.DEFAULT) ?
        BACKGROUND_DEFAULT :
        (BACKGROUND_CUSTOM + `${colorSuffix})`);
  }

  private getForegroundColor_(colorSuffix: ColorSuffix): string {
    return (colorSuffix === ColorSuffix.DEFAULT) ?
        FOREGROUND_DEFAULT :
        (FOREGROUND_CUSTOM + `${colorSuffix})`);
  }

  private getSelectionColor_(colorSuffix: ColorSuffix): string {
    return (colorSuffix === ColorSuffix.DEFAULT) ?
        SELECTION_BACKGROUND_DEFAULT :
        (SELECTION_BACKGROUND_CUSTOM + `${colorSuffix})`);
  }

  private getSelectionTextColor_(colorSuffix: ColorSuffix): string {
    if (colorSuffix === ColorSuffix.DEFAULT) {
      return SELECTION_FOREGROUND_DEFAULT;
    }

    return (window.matchMedia('(prefers-color-scheme: dark)').matches) ?
        SELECTION_FOREGROUND_DARK :
        SELECTION_FOREGROUND_LIGHT;
  }
}