blob: 218e222255037ece8fe82da0193ba1803ffdd7c5 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/color/omnibox_color_mixer.h"
#include "base/feature_list.h"
#include "base/strings/string_number_conversions.h"
#include "chrome/browser/ui/color/chrome_color_id.h"
#include "chrome/browser/ui/color/chrome_color_provider_utils.h"
#include "components/omnibox/common/omnibox_features.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/base/ui_base_features.h"
#include "ui/color/color_id.h"
#include "ui/color/color_mixer.h"
#include "ui/color/color_provider.h"
#include "ui/color/color_provider_key.h"
#include "ui/color/color_recipe.h"
#include "ui/color/color_transform.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/color_utils.h"
namespace {
// The contrast for omnibox colors in high contrast mode.
constexpr float kOmniboxHighContrastRatio = 6.0f;
// Apply updates to the Omnibox text color tokens per GM3 spec.
void ApplyGM3OmniboxTextColor(ui::ColorMixer& mixer,
const ui::ColorProviderKey& key) {
if (!omnibox::IsOmniboxCr23CustomizeGuardedFeatureEnabled(
omnibox::kOmniboxSteadyStateTextColor)) {
return;
}
mixer[kColorOmniboxText] = {ui::kColorSysOnSurface};
mixer[kColorOmniboxTextDimmed] = {ui::kColorSysOnSurfaceSubtle};
mixer[kColorOmniboxSelectionBackground] = {ui::kColorSysStateTextHighlight};
mixer[kColorOmniboxSelectionForeground] = {ui::kColorSysStateOnTextHighlight};
// In high-contrast mode, text colors have selected variants. This is because
// the selected suggestion has a high-contrast background, so when the
// unselected text needs to be near-white, the selected text needs to be
// near-black (or vice versa). Though there are bugs where some of the views
// apply the selected variants to the 1st suggestion instead of the selected
// suggestion. Regardless,for now, CR23 does not apply in high-contrast mode,
// so it's safe to use the unselected colors.
// TODO(manukh): Figure out correct colors when launching CR23 for
// high-contrast.
mixer[kColorOmniboxResultsTextSelected] = {kColorOmniboxText};
mixer[kColorOmniboxResultsUrlSelected] = {kColorOmniboxResultsUrl};
// These affect finance answers; e.g. 'goog stock'.
// TODO(crbug.com/1465985): These don't seem to apply anymore, at least on
// desktop. Check with UX if we still care to color finance answers, and
// what those colors should in CR23.
mixer[kColorOmniboxResultsTextNegativeSelected] = {
kColorOmniboxResultsTextNegative};
mixer[kColorOmniboxResultsTextPositiveSelected] = {
kColorOmniboxResultsTextPositive};
mixer[kColorOmniboxResultsTextSecondarySelected] = {
kColorOmniboxResultsTextSecondary};
}
void ApplyCR2023OmniboxIconColors(ui::ColorMixer& mixer,
const ui::ColorProviderKey& key) {
if (!omnibox::IsOmniboxCr23CustomizeGuardedFeatureEnabled(
omnibox::kOmniboxCR23SteadyStateIcons)) {
return;
}
mixer[kColorPageActionIconHover] = {ui::kColorSysStateHoverOnSubtle};
mixer[kColorPageInfoBackground] = {ui::kColorSysBaseContainerElevated};
mixer[kColorPageInfoIconHover] = {ui::kColorSysStateHoverDimBlendProtection};
mixer[kColorPageInfoIconPressed] = {ui::kColorSysStateRippleNeutralOnSubtle};
mixer[kColorPageActionIcon] = {ui::kColorSysOnSurfaceSubtle};
// Security chip.
mixer[kColorOmniboxSecurityChipDangerousBackground] = {ui::kColorSysError};
mixer[kColorOmniboxSecurityChipText] = {ui::kColorSysOnError};
mixer[kColorOmniboxSecurityChipInkDropHover] = {
ui::kColorSysStateHoverOnProminent};
mixer[kColorOmniboxSecurityChipInkDropRipple] = {
ui::kColorSysStateRippleNeutralOnProminent};
}
// Apply updates to the Omnibox "expanded state" color tokens per CR2023 spec.
void ApplyCR2023OmniboxExpandedStateColors(ui::ColorMixer& mixer,
const ui::ColorProviderKey& key) {
if (!omnibox::IsOmniboxCr23CustomizeGuardedFeatureEnabled(
omnibox::kExpandedStateColors)) {
return;
}
// Update focus bar color.
mixer[kColorOmniboxResultsFocusIndicator] = {ui::kColorSysStateFocusRing};
// Update omnibox popup background color.
mixer[kColorOmniboxResultsBackground] = {ui::kColorSysBase};
// Update suggestion hover fill colors.
mixer[kColorOmniboxResultsBackgroundHovered] = ui::GetResultingPaintColor(
ui::kColorSysStateHoverOnSubtle, kColorOmniboxResultsBackground);
mixer[kColorOmniboxResultsBackgroundSelected] = {
kColorOmniboxResultsBackgroundHovered};
// Update URL link color.
mixer[kColorOmniboxResultsUrl] = {ui::kColorSysPrimary};
// Update keyword mode icon & text color.
mixer[kColorOmniboxKeywordSelected] = {kColorOmniboxResultsUrl};
// Update keyword mode separator color.
mixer[kColorOmniboxKeywordSeparator] = {ui::kColorSysTonalOutline};
// Update suggest text and separator dim color.
mixer[kColorOmniboxResultsTextDimmed] = {ui::kColorSysOnSurfaceSubtle};
mixer[kColorOmniboxResultsTextDimmedSelected] = {
kColorOmniboxResultsTextDimmed};
// Update suggestion vector icon color.
mixer[kColorOmniboxResultsIcon] = {ui::kColorSysOnSurfaceSubtle};
mixer[kColorOmniboxResultsIconSelected] = {kColorOmniboxResultsIcon};
// Update chip colors.
mixer[kColorOmniboxResultsButtonBorder] = {kColorOmniboxKeywordSeparator};
mixer[kColorOmniboxResultsButtonIcon] = {kColorOmniboxResultsUrl};
mixer[kColorOmniboxResultsButtonIconSelected] = {
kColorOmniboxResultsButtonIcon};
mixer[kColorOmniboxResultsButtonInkDrop] = {ui::kColorSysStateHoverOnSubtle};
mixer[kColorOmniboxResultsButtonInkDropSelected] = {
ui::kColorSysStateRippleNeutralOnSubtle};
// Update starter pack icon color.
mixer[kColorOmniboxResultsStarterPackIcon] = {ui::kColorSysPrimary};
}
// Apply updates to the Omnibox color tokens per CR2023 guidelines.
void ApplyOmniboxCR2023Colors(ui::ColorMixer& mixer,
const ui::ColorProviderKey& key) {
// Do not apply CR2023 Omnibox colors to clients using high-contrast
// mode or a custom theme.
// TODO(khalidpeer): Roll out CR2023 color updates for high-contrast clients.
// TODO(khalidpeer): Roll out CR2023 color updates for themed clients.
if (ShouldApplyHighContrastColors(key) || key.custom_theme) {
return;
}
ApplyGM3OmniboxTextColor(mixer, key);
ApplyCR2023OmniboxExpandedStateColors(mixer, key);
ApplyCR2023OmniboxIconColors(mixer, key);
}
} // namespace
void AddOmniboxColorMixer(ui::ColorProvider* provider,
const ui::ColorProviderKey& key) {
ui::ColorMixer& mixer = provider->AddMixer();
const bool high_contrast_custom_handling = ShouldApplyHighContrastColors(key);
const float contrast_ratio = high_contrast_custom_handling
? kOmniboxHighContrastRatio
: color_utils::kMinimumReadableContrastRatio;
// Selected colors will use inverted base colors in high contrast mode.
const auto selected_background_color =
high_contrast_custom_handling
? ui::ContrastInvert(kColorToolbarBackgroundSubtleEmphasis)
: kColorToolbarBackgroundSubtleEmphasis;
const auto selected_text_color = high_contrast_custom_handling
? ui::ContrastInvert(kColorOmniboxText)
: kColorOmniboxText;
// Location bar colors.
mixer[kColorLocationBarClearAllButtonIcon] =
ui::DeriveDefaultIconColor(kColorOmniboxText);
mixer[kColorLocationBarClearAllButtonIconDisabled] = ui::SetAlpha(
kColorLocationBarClearAllButtonIcon, gfx::kDisabledControlAlpha);
// Omnibox background colors.
mixer[kColorToolbarBackgroundSubtleEmphasis] = ui::SelectBasedOnDarkInput(
kColorToolbar, gfx::kGoogleGrey900, gfx::kGoogleGrey100);
mixer[kColorToolbarBackgroundSubtleEmphasisHovered] =
ui::BlendTowardMaxContrast(kColorToolbarBackgroundSubtleEmphasis, 0x0A);
// Omnibox text colors.
mixer[kColorOmniboxText] =
ui::GetColorWithMaxContrast(kColorToolbarBackgroundSubtleEmphasis);
mixer[kColorOmniboxResultsTextSelected] = {selected_text_color};
mixer[kColorOmniboxKeywordSelected] =
ui::SelectBasedOnDarkInput(kColorToolbarBackgroundSubtleEmphasis,
gfx::kGoogleGrey100, kColorOmniboxResultsUrl);
mixer[kColorOmniboxKeywordSeparator] = {kColorOmniboxText};
// Omnibox highlight colors.
mixer[kColorOmniboxSelectionBackground] = {
ui::kColorTextfieldSelectionBackground};
mixer[kColorOmniboxSelectionForeground] = {
ui::kColorTextfieldSelectionForeground};
// Bubble outline colors.
mixer[kColorOmniboxBubbleOutline] = ui::SelectBasedOnDarkInput(
kColorToolbarBackgroundSubtleEmphasis, gfx::kGoogleGrey100,
SkColorSetA(gfx::kGoogleGrey900, 0x24));
mixer[kColorOmniboxBubbleOutlineExperimentalKeywordMode] = {
kColorOmniboxKeywordSelected};
// Results background, button, and focus colors.
mixer[kColorOmniboxResultsBackground] =
ui::GetColorWithMaxContrast(kColorOmniboxText);
mixer[kColorOmniboxResultsBackgroundHovered] = ui::BlendTowardMaxContrast(
kColorOmniboxResultsBackground, gfx::kGoogleGreyAlpha200);
mixer[kColorOmniboxResultsBackgroundSelected] = ui::BlendTowardMaxContrast(
ui::GetColorWithMaxContrast(kColorOmniboxResultsTextSelected),
gfx::kGoogleGreyAlpha200);
mixer[kColorOmniboxResultsButtonBorder] = ui::BlendTowardMaxContrast(
kColorToolbarBackgroundSubtleEmphasis, gfx::kGoogleGreyAlpha400);
mixer[kColorOmniboxResultsButtonIcon] = {kColorOmniboxResultsIcon};
mixer[kColorOmniboxResultsButtonIconSelected] = {
kColorOmniboxResultsIconSelected};
mixer[kColorOmniboxResultsButtonInkDrop] =
ui::GetColorWithMaxContrast(kColorOmniboxResultsBackgroundHovered);
mixer[kColorOmniboxResultsButtonInkDropSelected] =
ui::GetColorWithMaxContrast(kColorOmniboxResultsBackgroundSelected);
mixer[kColorOmniboxResultsFocusIndicator] = ui::PickGoogleColor(
ui::kColorFocusableBorderFocused, kColorOmniboxResultsBackgroundSelected,
color_utils::kMinimumVisibleContrastRatio);
// Results icon colors.
{
const auto results_icon = [contrast_ratio](ui::ColorId text_id,
ui::ColorId background_id) {
return ui::BlendForMinContrast(ui::DeriveDefaultIconColor(text_id),
background_id, absl::nullopt,
contrast_ratio);
};
mixer[kColorOmniboxResultsIcon] =
results_icon(kColorOmniboxText, kColorOmniboxResultsBackground);
mixer[kColorOmniboxResultsIconSelected] =
results_icon(kColorOmniboxResultsTextSelected,
kColorOmniboxResultsBackgroundSelected);
mixer[kColorOmniboxResultsStarterPackIcon] = ui::BlendForMinContrast(
gfx::kGoogleBlue600, kColorOmniboxResultsBackground, absl::nullopt,
color_utils::kMinimumVisibleContrastRatio);
}
// Dimmed text colors.
{
const auto blend_with_clamped_contrast =
[contrast_ratio](ui::ColorId foreground_id, ui::ColorId background_id) {
return ui::BlendForMinContrast(
foreground_id, foreground_id,
ui::BlendForMinContrast(background_id, background_id,
absl::nullopt, contrast_ratio),
contrast_ratio);
};
mixer[kColorOmniboxResultsTextDimmed] = blend_with_clamped_contrast(
kColorOmniboxText, kColorOmniboxResultsBackgroundHovered);
mixer[kColorOmniboxResultsTextDimmedSelected] =
blend_with_clamped_contrast(kColorOmniboxResultsTextSelected,
kColorOmniboxResultsBackgroundSelected);
mixer[kColorOmniboxTextDimmed] = blend_with_clamped_contrast(
kColorOmniboxText, kColorToolbarBackgroundSubtleEmphasisHovered);
}
// Other results text colors.
{
const auto negative_color = [contrast_ratio](
ui::ColorId background,
ui::ColorTransform dark_selector) {
return ui::BlendForMinContrast(
// Like kColorAlertHighSeverity, but toggled on `dark_selector`.
ui::SelectBasedOnDarkInput(dark_selector, gfx::kGoogleRed300,
gfx::kGoogleRed600),
background, absl::nullopt, contrast_ratio);
};
const auto positive_color = [contrast_ratio](
ui::ColorId background,
ui::ColorTransform dark_selector) {
return ui::BlendForMinContrast(
// Like kColorAlertLowSeverity, but toggled on `dark_selector`.
ui::SelectBasedOnDarkInput(dark_selector, gfx::kGoogleGreen300,
gfx::kGoogleGreen700),
background, absl::nullopt, contrast_ratio);
};
const auto secondary_color = [contrast_ratio](
ui::ColorId background,
ui::ColorTransform dark_selector) {
return ui::BlendForMinContrast(
// Like kColorDisabledForeground, but toggled on `dark_selector`.
ui::BlendForMinContrast(
gfx::kGoogleGrey600,
ui::SelectBasedOnDarkInput(dark_selector,
SkColorSetRGB(0x29, 0x2A, 0x2D),
SK_ColorWHITE),
ui::SelectBasedOnDarkInput(dark_selector, gfx::kGoogleGrey200,
gfx::kGoogleGrey900)),
background, absl::nullopt, contrast_ratio);
};
const auto url_color = [contrast_ratio](ui::ColorId background,
ui::ColorTransform dark_selector) {
return ui::BlendForMinContrast(
gfx::kGoogleBlue500, background,
ui::SelectBasedOnDarkInput(dark_selector, gfx::kGoogleBlue050,
gfx::kGoogleBlue900),
contrast_ratio);
};
mixer[kColorOmniboxResultsTextNegative] =
negative_color(kColorOmniboxResultsBackgroundHovered,
kColorToolbarBackgroundSubtleEmphasis);
mixer[kColorOmniboxResultsTextNegativeSelected] = negative_color(
kColorOmniboxResultsBackgroundSelected, selected_background_color);
mixer[kColorOmniboxResultsTextPositive] =
positive_color(kColorOmniboxResultsBackgroundHovered,
kColorToolbarBackgroundSubtleEmphasis);
mixer[kColorOmniboxResultsTextPositiveSelected] = positive_color(
kColorOmniboxResultsBackgroundSelected, selected_background_color);
mixer[kColorOmniboxResultsTextSecondary] =
secondary_color(kColorOmniboxResultsBackgroundHovered,
kColorToolbarBackgroundSubtleEmphasis);
mixer[kColorOmniboxResultsTextSecondarySelected] = secondary_color(
kColorOmniboxResultsBackgroundSelected, selected_background_color);
mixer[kColorOmniboxResultsUrl] =
url_color(kColorOmniboxResultsBackgroundHovered,
kColorToolbarBackgroundSubtleEmphasis);
mixer[kColorOmniboxResultsUrlSelected] = url_color(
kColorOmniboxResultsBackgroundSelected, selected_background_color);
}
// Security chip colors.
{
const auto security_chip_color = [contrast_ratio](SkColor dark_input,
SkColor light_input) {
return ui::BlendForMinContrast(
ui::SelectBasedOnDarkInput(kColorToolbarBackgroundSubtleEmphasis,
dark_input, light_input),
kColorToolbarBackgroundSubtleEmphasisHovered, absl::nullopt,
contrast_ratio);
};
mixer[kColorOmniboxSecurityChipDangerous] =
security_chip_color(gfx::kGoogleRed300, gfx::kGoogleRed600);
// TODO(weili): consider directly deriving from the omnibox text color such
// as using
// security_chip_color(ui::DeriveDefaultIconColor(kColorOmniboxText)).
mixer[kColorOmniboxSecurityChipSecure] =
security_chip_color(gfx::kGoogleGrey500, gfx::kGoogleGrey700);
mixer[kColorOmniboxSecurityChipDefault] = {kColorOmniboxSecurityChipSecure};
mixer[kColorOmniboxSecurityChipDangerousBackground] =
ui::SelectBasedOnDarkInput(kColorOmniboxResultsBackground,
gfx::kGoogleRed300, gfx::kGoogleRed800);
mixer[kColorOmniboxSecurityChipText] = ui::SelectBasedOnDarkInput(
kColorOmniboxSecurityChipDangerousBackground,
ui::GetColorWithMaxContrast(
kColorOmniboxSecurityChipDangerousBackground),
gfx::kGoogleRed800);
mixer[kColorOmniboxSecurityChipInkDropHover] = {
ui::SetAlpha(kColorOmniboxSecurityChipText, std::ceil(0.10f * 255.0f))};
mixer[kColorOmniboxSecurityChipInkDropRipple] = {
ui::SetAlpha(kColorOmniboxSecurityChipText, std::ceil(0.16f * 255.0f))};
}
// TODO(manukh): `kColorOmniboxResultsIconGM3Background` is unused currently,
// but if we decide to revisit it, we should use tokens instead of rgb's.
mixer[kColorOmniboxResultsIconGM3Background] = ui::SelectBasedOnDarkInput(
kColorToolbar, SkColorSetRGB(48, 48, 48), SkColorSetRGB(242, 242, 242));
mixer[kColorOmniboxAnswerIconGM3Background] = {ui::kColorSysTonalContainer};
mixer[kColorOmniboxAnswerIconGM3Foreground] = {ui::kColorSysOnTonalContainer};
// location bar icon colors.
mixer[kColorPageInfoBackground] = {kColorToolbar};
mixer[kColorPageInfoBackgroundTonal] = {ui::kColorSysTonalContainer};
// Literal constants are `kOmniboxOpacityHovered` and
// `kOmniboxOpacitySelected`. This is so that we can more cleanly use the
// colors in the inkdrop instead of handling themes and non-themes separately
// in-code as they have different opacity requirements.
mixer[kColorPageInfoIconHover] = {
ui::SetAlpha(kColorOmniboxText, std::ceil(0.10f * 255.0f))};
mixer[kColorPageInfoIconPressed] = {
ui::SetAlpha(kColorOmniboxText, std::ceil(0.16f * 255.0f))};
mixer[kColorPageActionIconHover] = {kColorPageInfoIconHover};
mixer[kColorPageActionIcon] = {kColorOmniboxResultsIcon};
// Override omnibox colors per CR2023 spec.
ApplyOmniboxCR2023Colors(mixer, key);
}