blob: dec8eca1a68cf3eef6a2dfc6b9daae58879c934a [file] [log] [blame]
// Copyright (c) 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/views/tabs/tab_hover_card_bubble_view.h"
#include <algorithm>
#include <memory>
#include <string>
#include "base/containers/mru_cache.h"
#include "base/cxx17_backports.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_macros.h"
#include "build/build_config.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/themes/theme_properties.h"
#include "chrome/browser/ui/tabs/tab_renderer_data.h"
#include "chrome/browser/ui/tabs/tab_style.h"
#include "chrome/browser/ui/thumbnails/thumbnail_image.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/chrome_typography.h"
#include "chrome/browser/ui/views/tabs/tab.h"
#include "chrome/browser/ui/views/tabs/tab_hover_card_controller.h"
#include "chrome/grit/generated_resources.h"
#include "components/url_formatter/url_formatter.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/theme_provider.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/animation/linear_animation.h"
#include "ui/gfx/animation/slide_animation.h"
#include "ui/gfx/animation/tween.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/gfx/text_constants.h"
#include "ui/native_theme/native_theme.h"
#include "ui/resources/grit/ui_resources.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/background.h"
#include "ui/views/border.h"
#include "ui/views/bubble/bubble_frame_view.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/layout/layout_provider.h"
#include "ui/views/style/typography.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/widget/widget.h"
#if defined(OS_WIN)
#include "ui/base/win/shell.h"
#endif
namespace {
// Maximum number of lines that a title label occupies.
constexpr int kHoverCardTitleMaxLines = 2;
constexpr int kHorizontalMargin = 18;
constexpr int kVerticalMargin = 10;
constexpr int kFootnoteVerticalMargin = 8;
constexpr gfx::Insets kTitleMargins(kVerticalMargin, kHorizontalMargin);
constexpr gfx::Insets kAlertMargins(kFootnoteVerticalMargin, kHorizontalMargin);
bool CustomShadowsSupported() {
#if defined(OS_WIN)
return ui::win::IsAeroGlassEnabled();
#else
return true;
#endif
}
std::unique_ptr<views::Label> CreateAlertView(const TabAlertState& state) {
auto alert_state_label = std::make_unique<views::Label>(
std::u16string(), views::style::CONTEXT_DIALOG_BODY_TEXT,
views::style::STYLE_PRIMARY);
alert_state_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
alert_state_label->SetMultiLine(true);
alert_state_label->SetVisible(true);
alert_state_label->SetText(chrome::GetTabAlertStateText(state));
return alert_state_label;
}
// Calculates an appropriate size to display a preview image in the hover card.
// For the vast majority of images, the |preferred_size| is used, but extremely
// tall or wide images use the image size instead, centering in the available
// space.
gfx::Size GetPreviewImageSize(gfx::Size preview_size,
gfx::Size preferred_size) {
DCHECK(!preferred_size.IsEmpty());
if (preview_size.IsEmpty())
return preview_size;
const float preview_aspect_ratio =
static_cast<float>(preview_size.width()) / preview_size.height();
const float preferred_aspect_ratio =
static_cast<float>(preferred_size.width()) / preferred_size.height();
const float ratio = preview_aspect_ratio / preferred_aspect_ratio;
// Images between 2/3 and 3/2 of the target aspect ratio use the preferred
// size, stretching the image. Only images outside this range get centered.
// Since this is a corner case most users will never see, the specific cutoffs
// just need to be reasonable and don't need to be precise values (that is,
// there is no "correct" value; if the results are not aesthetic they can be
// tuned).
constexpr float kMinStretchRatio = 0.667f;
constexpr float kMaxStretchRatio = 1.5f;
if (ratio >= kMinStretchRatio && ratio <= kMaxStretchRatio)
return preferred_size;
return preview_size;
}
bool UseAlternateHoverCardFormat() {
static const int use_alternate_format =
base::GetFieldTrialParamByFeatureAsInt(
features::kTabHoverCardImages, features::kTabHoverCardAlternateFormat,
0);
return use_alternate_format != 0;
}
// Label that renders its background in a solid color. Placed in front of a
// normal label either by being later in the draw order or on a layer, it can
// be used to animate a fade-out.
class SolidLabel : public views::Label {
public:
METADATA_HEADER(SolidLabel);
using Label::Label;
SolidLabel() = default;
~SolidLabel() override = default;
protected:
// views::Label:
void OnPaintBackground(gfx::Canvas* canvas) override {
canvas->DrawColor(GetBackgroundColor());
}
};
BEGIN_METADATA(SolidLabel, views::Label)
END_METADATA
} // namespace
// This view overlays and fades out an old version of the text of a label,
// while displaying the new text underneath. It is used to fade out the old
// value of the title and domain labels on the hover card when the tab switches
// or the tab title changes.
class TabHoverCardBubbleView::FadeLabel : public views::View {
public:
FadeLabel(int context, int num_lines) {
primary_label_ = AddChildView(std::make_unique<views::Label>(
std::u16string(), context, views::style::STYLE_PRIMARY));
primary_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
primary_label_->SetVerticalAlignment(gfx::ALIGN_TOP);
primary_label_->SetMultiLine(num_lines > 1);
if (num_lines > 1)
primary_label_->SetMaxLines(num_lines);
label_fading_out_ = AddChildView(std::make_unique<SolidLabel>(
std::u16string(), context, views::style::STYLE_PRIMARY));
label_fading_out_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
label_fading_out_->SetVerticalAlignment(gfx::ALIGN_TOP);
label_fading_out_->SetMultiLine(num_lines > 1);
if (num_lines > 1)
label_fading_out_->SetMaxLines(num_lines);
label_fading_out_->GetViewAccessibility().OverrideIsIgnored(true);
SetLayoutManager(std::make_unique<views::FillLayout>());
}
~FadeLabel() override = default;
void SetText(std::u16string text, absl::optional<bool> is_filename) {
if (was_filename_.has_value())
SetMultilineParams(label_fading_out_, was_filename_.value());
label_fading_out_->SetText(primary_label_->GetText());
if (is_filename.has_value())
SetMultilineParams(primary_label_, is_filename.value());
was_filename_ = is_filename;
primary_label_->SetText(text);
}
// Sets the fade-out of the label as |percent| in the range [0, 1]. Since
// FadeLabel is designed to mask new text with the old and then fade away, the
// higher the percentage the less opaque the label.
void SetFade(double percent) {
percent_ = std::min(1.0, percent);
if (percent_ == 1.0)
label_fading_out_->SetText(std::u16string());
const SkAlpha alpha = base::saturated_cast<SkAlpha>(
std::numeric_limits<SkAlpha>::max() * (1.0 - percent_));
label_fading_out_->SetBackgroundColor(
SkColorSetA(label_fading_out_->GetBackgroundColor(), alpha));
label_fading_out_->SetEnabledColor(
SkColorSetA(label_fading_out_->GetEnabledColor(), alpha));
}
std::u16string GetText() const { return primary_label_->GetText(); }
protected:
// views::View:
gfx::Size GetMaximumSize() const override {
return gfx::Tween::SizeValueBetween(percent_,
label_fading_out_->GetPreferredSize(),
primary_label_->GetPreferredSize());
}
gfx::Size CalculatePreferredSize() const override {
return primary_label_->GetPreferredSize();
}
gfx::Size GetMinimumSize() const override {
return primary_label_->GetMinimumSize();
}
int GetHeightForWidth(int width) const override {
return primary_label_->GetHeightForWidth(width);
}
private:
static void SetMultilineParams(views::Label* label, bool is_filename) {
if (is_filename) {
label->SetMultiLine(false);
label->SetElideBehavior(gfx::ELIDE_MIDDLE);
} else {
label->SetElideBehavior(gfx::ELIDE_TAIL);
label->SetMultiLine(true);
}
}
views::Label* primary_label_;
SolidLabel* label_fading_out_;
absl::optional<bool> was_filename_;
double percent_ = 1.0;
};
// Represents the preview image on the hover card. Allows for a new image to be
// faded in over the old image.
class TabHoverCardBubbleView::ThumbnailView
: public views::View,
public views::AnimationDelegateViews {
public:
// Specifies which (if any) of the corners of the preview image will be
// rounded. See SetRoundedCorners() below for more information.
enum class RoundedCorners { kNone, kTopCorners, kBottomCorners };
explicit ThumbnailView(TabHoverCardBubbleView* bubble_view)
: AnimationDelegateViews(this),
bubble_view_(bubble_view),
image_transition_animation_(this) {
constexpr base::TimeDelta kImageTransitionDuration =
kHoverCardSlideDuration;
image_transition_animation_.SetDuration(kImageTransitionDuration);
// Set a reasonable preview size so that ThumbnailView() is given an
// appropriate amount of space in the layout.
target_tab_image_ = AddChildView(CreateImageView());
image_fading_out_ = AddChildView(CreateImageView());
image_fading_out_->SetPaintToLayer();
image_fading_out_->layer()->SetOpacity(0.0f);
SetLayoutManager(std::make_unique<views::FillLayout>());
}
// Sets the appropriate rounded corners for the preview image, for platforms
// where layers must be explicitly clipped (because they are not clipped by
// the widget). Set `rounded_corners` to kTopCorners if the preview image is
// the topmost view in the widget (including header); set kBottomCorners if
// the preview is the bottom-most view (including footer). If neither, use
// kNone.
void SetRoundedCorners(RoundedCorners rounded_corners, float radius) {
gfx::RoundedCornersF corners;
switch (rounded_corners) {
case RoundedCorners::kNone:
corners = {0, 0, 0, 0};
break;
case RoundedCorners::kTopCorners:
corners = {radius, radius, 0, 0};
break;
case RoundedCorners::kBottomCorners:
corners = {0, 0, radius, radius};
break;
}
image_fading_out_->layer()->SetRoundedCornerRadius(corners);
}
// Sets the new preview image. The old image will be faded out.
void SetTargetTabImage(gfx::ImageSkia preview_image) {
StartFadeOut();
SetImage(target_tab_image_, preview_image, ImageType::kThumbnail);
image_type_ = ImageType::kThumbnail;
}
// Clears the preview image and replaces it with a placeholder image. The old
// image will be faded out.
void SetPlaceholderImage() {
if (image_type_ == ImageType::kPlaceholder)
return;
// Theme provider may be null if there is no associated widget. In that case
// there is nothing to render, and we can't get theme default colors to
// render with anyway, so bail out.
const ui::ThemeProvider* const theme_provider = GetThemeProvider();
if (!theme_provider)
return;
StartFadeOut();
// Check the no-preview color and size to see if it needs to be
// regenerated. DPI or theme change can cause a regeneration.
const SkColor foreground_color = theme_provider->GetColor(
ThemeProperties::COLOR_HOVER_CARD_NO_PREVIEW_FOREGROUND);
// Set the no-preview placeholder image. All sizes are in DIPs.
// gfx::CreateVectorIcon() caches its result so there's no need to store
// images here; if a particular size/color combination has already been
// requested it will be low-cost to request it again.
constexpr gfx::Size kNoPreviewImageSize{64, 64};
const gfx::ImageSkia no_preview_image = gfx::CreateVectorIcon(
kGlobeIcon, kNoPreviewImageSize.width(), foreground_color);
SetImage(target_tab_image_, no_preview_image, ImageType::kPlaceholder);
image_type_ = ImageType::kPlaceholder;
}
void ClearImage() {
if (image_type_ == ImageType::kNone)
return;
StartFadeOut();
SetImage(target_tab_image_, gfx::ImageSkia(), ImageType::kNone);
image_type_ = ImageType::kNone;
}
void SetWaitingForImage() {
if (image_type_ == ImageType::kNone) {
image_type_ = ImageType::kNoneButWaiting;
InvalidateLayout();
}
}
private:
enum class ImageType { kNone, kNoneButWaiting, kPlaceholder, kThumbnail };
// Creates an image view with the appropriate default properties.
static std::unique_ptr<views::ImageView> CreateImageView() {
auto image_view = std::make_unique<views::ImageView>();
image_view->SetHorizontalAlignment(views::ImageView::Alignment::kCenter);
return image_view;
}
// Sets `image` on `image_view_`, configuring the image appropriately based
// on whether it's a placeholder or not.
void SetImage(views::ImageView* image_view,
gfx::ImageSkia image,
ImageType image_type) {
image_view->SetImage(image);
switch (image_type) {
case ImageType::kNone:
case ImageType::kNoneButWaiting:
image_view->SetBackground(
views::CreateSolidBackground(bubble_view_->color()));
break;
case ImageType::kPlaceholder:
image_view->SetVerticalAlignment(views::ImageView::Alignment::kCenter);
image_view->SetImageSize(image.size());
image_view->SetBackground(views::CreateSolidBackground(
image_view->GetThemeProvider()->GetColor(
ThemeProperties::COLOR_HOVER_CARD_NO_PREVIEW_BACKGROUND)));
break;
case ImageType::kThumbnail:
image_view->SetVerticalAlignment(views::ImageView::Alignment::kLeading);
image_view->SetImageSize(
GetPreviewImageSize(image.size(), TabStyle::GetPreviewImageSize()));
image_view->SetBackground(nullptr);
break;
}
}
// views::View:
gfx::Size GetMinimumSize() const override { return gfx::Size(); }
gfx::Size CalculatePreferredSize() const override {
return image_type_ == ImageType::kNone ? gfx::Size()
: TabStyle::GetPreviewImageSize();
}
gfx::Size GetMaximumSize() const override {
return TabStyle::GetPreviewImageSize();
}
// views::AnimationDelegateViews:
void AnimationProgressed(const gfx::Animation* animation) override {
image_fading_out_->layer()->SetOpacity(1.0 - animation->GetCurrentValue());
}
void AnimationEnded(const gfx::Animation* animation) override {
image_fading_out_->layer()->SetOpacity(0.0f);
SetImage(image_fading_out_, gfx::ImageSkia(), ImageType::kNone);
}
void AnimationCanceled(const gfx::Animation* animation) override {
AnimationEnded(animation);
}
// Begins fading out the existing image, which it reads from
// `target_tab_image_`. Does a smart three-way crossfade if an image is
// already fading out.
void StartFadeOut() {
if (!GetVisible())
return;
if (!GetPreviewImageCrossfadeStart().has_value())
return;
gfx::ImageSkia old_image = target_tab_image_->GetImage();
if (image_transition_animation_.is_animating()) {
// If we're already animating and we've barely faded out the previous old
// image, keep fading out the old one and just swap the new one
// underneath.
const double current_value =
image_transition_animation_.GetCurrentValue();
if (current_value <= 0.5)
return;
// Currently we have:
// - old preview at `current_value` opacity
// - previous old preview at 1.0 - `current_value` opacity
// We will discard the previous old preview and move the old preview into
// the occluding view. However, since the opacity of this view is
// inversely proportional to animation progress, to keep the same opacity
// (while we swap the new image in behind) we have to rewind the
// animation.
image_transition_animation_.SetCurrentValue(1.0 - current_value);
SetImage(image_fading_out_, old_image, image_type_);
AnimationProgressed(&image_transition_animation_);
} else {
SetImage(image_fading_out_, old_image, image_type_);
image_fading_out_->layer()->SetOpacity(1.0f);
image_transition_animation_.Start();
}
}
TabHoverCardBubbleView* const bubble_view_;
// Displays the image that we are trying to display for the target/current
// tab. Placed under `image_fading_out_` so that it is revealed as the
// previous image fades out.
views::ImageView* target_tab_image_ = nullptr;
// Displays the previous image as it's fading out. Rendered over
// `target_tab_image_` and has its alpha animated from 1 to 0.
views::ImageView* image_fading_out_ = nullptr;
// Provides a smooth fade out for `image_fading_out_`. We do not use a
// LayerAnimation because we need to rewind the transparency at various
// times (it's not necessarily a single smooth animation).
gfx::LinearAnimation image_transition_animation_;
// Records what type of image `target_tab_image_` is showing. Used to
// configure `image_fading_out_` when the target image becomes the previous
// image and fades out.
ImageType image_type_ = ImageType::kNone;
};
// static
constexpr base::TimeDelta TabHoverCardBubbleView::kHoverCardSlideDuration;
TabHoverCardBubbleView::TabHoverCardBubbleView(Tab* tab)
: BubbleDialogDelegateView(tab,
views::BubbleBorder::TOP_LEFT,
views::BubbleBorder::STANDARD_SHADOW) {
if (CustomShadowsSupported()) {
corner_radius_ = ChromeLayoutProvider::Get()->GetCornerRadiusMetric(
views::Emphasis::kHigh);
}
SetButtons(ui::DIALOG_BUTTON_NONE);
// We'll do all of our own layout inside the bubble, so no need to inset this
// view inside the client view.
set_margins(gfx::Insets());
// Set so that when hovering over a tab in a inactive window that window will
// not become active. Setting this to false creates the need to explicitly
// hide the hovercard on press, touch, and keyboard events.
SetCanActivate(false);
#if defined(OS_MAC)
set_accept_events(false);
#endif
// Set so that the tab hover card is not focus traversable when keyboard
// navigating through the tab strip.
set_focus_traversable_from_anchor_view(false);
title_label_ = AddChildView(std::make_unique<FadeLabel>(
CONTEXT_TAB_HOVER_CARD_TITLE, kHoverCardTitleMaxLines));
domain_label_ = AddChildView(
std::make_unique<FadeLabel>(views::style::CONTEXT_DIALOG_BODY_TEXT, 1));
if (TabHoverCardController::AreHoverCardImagesEnabled()) {
if (UseAlternateHoverCardFormat()) {
thumbnail_view_ =
AddChildViewAt(std::make_unique<ThumbnailView>(this), 0);
thumbnail_view_->SetRoundedCorners(
ThumbnailView::RoundedCorners::kTopCorners,
corner_radius_.value_or(0));
} else {
thumbnail_view_ = AddChildView(std::make_unique<ThumbnailView>(this));
thumbnail_view_->SetRoundedCorners(
ThumbnailView::RoundedCorners::kBottomCorners,
corner_radius_.value_or(0));
}
}
// Set up layout.
views::FlexLayout* const layout =
SetLayoutManager(std::make_unique<views::FlexLayout>());
layout->SetOrientation(views::LayoutOrientation::kVertical);
layout->SetMainAxisAlignment(views::LayoutAlignment::kStart);
layout->SetCrossAxisAlignment(views::LayoutAlignment::kStretch);
layout->SetCollapseMargins(true);
// In some browser types (e.g. ChromeOS terminal app) we hide the domain
// label. In those cases, we need to adjust the bottom margin of the title
// element because it is no longer above another text element and needs a
// bottom margin.
const bool show_domain = tab->controller()->ShowDomainInHoverCards();
gfx::Insets title_margins = kTitleMargins;
domain_label_->SetVisible(show_domain);
if (show_domain) {
title_margins.set_bottom(0);
domain_label_->SetProperty(
views::kMarginsKey,
gfx::Insets(0, kHorizontalMargin, kVerticalMargin, kHorizontalMargin));
}
title_label_->SetProperty(views::kMarginsKey, title_margins);
title_label_->SetProperty(
views::kFlexBehaviorKey,
views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToMinimum,
views::MaximumFlexSizeRule::kScaleToMaximum)
.WithOrder(2));
if (thumbnail_view_) {
thumbnail_view_->SetProperty(
views::kFlexBehaviorKey,
views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToMinimum,
views::MaximumFlexSizeRule::kScaleToMaximum)
.WithOrder(1));
}
// Set up widget.
views::BubbleDialogDelegateView::CreateBubble(this);
set_adjust_if_offscreen(true);
GetBubbleFrameView()->SetFootnoteMargins(kAlertMargins);
GetBubbleFrameView()->SetPreferredArrowAdjustment(
views::BubbleFrameView::PreferredArrowAdjustment::kOffset);
GetBubbleFrameView()->set_hit_test_transparent(true);
if (using_rounded_corners())
GetBubbleFrameView()->SetCornerRadius(corner_radius_.value());
// Placeholder image should be used when there is no image data for the
// given tab. Otherwise don't flash the placeholder while we wait for the
// existing thumbnail to be decompressed.
//
// Note that this code has to go after CreateBubble() above, since setting up
// the placeholder image and background color require a ThemeProvider, which
// is only available once this View has been added to its widget.
if (thumbnail_view_ && !tab->data().thumbnail->has_data() &&
!tab->IsActive()) {
thumbnail_view_->SetPlaceholderImage();
}
// Start in the fully "faded-in" position so that whatever text we initially
// display is visible.
SetTextFade(1.0);
}
TabHoverCardBubbleView::~TabHoverCardBubbleView() = default;
ax::mojom::Role TabHoverCardBubbleView::GetAccessibleWindowRole() {
// Override the role so that hover cards are not read when they appear because
// tabs handle accessibility text.
return ax::mojom::Role::kNone;
}
void TabHoverCardBubbleView::UpdateCardContent(const Tab* tab) {
// Preview image is never visible for the active tab.
if (thumbnail_view_) {
if (tab->IsActive())
thumbnail_view_->ClearImage();
else
thumbnail_view_->SetWaitingForImage();
}
std::u16string title;
absl::optional<TabAlertState> old_alert_state = alert_state_;
GURL domain_url;
// Use committed URL to determine if no page has yet loaded, since the title
// can be blank for some web pages.
if (tab->data().last_committed_url.is_empty()) {
domain_url = tab->data().visible_url;
title = tab->data().IsCrashed()
? l10n_util::GetStringUTF16(IDS_HOVER_CARD_CRASHED_TITLE)
: l10n_util::GetStringUTF16(IDS_TAB_LOADING_TITLE);
alert_state_ = absl::nullopt;
} else {
domain_url = tab->data().last_committed_url;
title = tab->data().title;
alert_state_ = Tab::GetAlertStateToShow(tab->data().alert_state);
}
std::u16string domain;
bool is_filename = false;
if (domain_url.SchemeIsFile()) {
is_filename = true;
domain = l10n_util::GetStringUTF16(IDS_HOVER_CARD_FILE_URL_SOURCE);
} else {
if (domain_url.SchemeIsBlob()) {
domain = l10n_util::GetStringUTF16(IDS_HOVER_CARD_BLOB_URL_SOURCE);
} else {
domain = url_formatter::FormatUrl(
domain_url,
url_formatter::kFormatUrlOmitDefaults |
url_formatter::kFormatUrlOmitHTTPS |
url_formatter::kFormatUrlOmitTrivialSubdomains |
url_formatter::kFormatUrlTrimAfterHost,
net::UnescapeRule::NORMAL, nullptr, nullptr, nullptr);
}
}
title_label_->SetText(title, is_filename);
domain_label_->SetText(domain, absl::nullopt);
const bool alternate_layout = UseAlternateHoverCardFormat();
if (alert_state_ != old_alert_state) {
std::unique_ptr<views::Label> alert_label =
alert_state_.has_value() ? CreateAlertView(*alert_state_) : nullptr;
if (alternate_layout) {
if (alert_label) {
// Simulate the same look as the footnote view.
// TODO(dfried): should we add this as a variation of
// FootnoteContainerView? Currently it's only used here.
alert_label->SetBackground(
views::CreateSolidBackground(GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_BubbleFooterBackground)));
alert_label->SetBorder(views::CreatePaddedBorder(
views::CreateSolidSidedBorder(
0, 0, 1, 0,
GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_FootnoteContainerBorder)),
kAlertMargins));
}
GetBubbleFrameView()->SetHeaderView(std::move(alert_label));
} else {
GetBubbleFrameView()->SetFootnoteView(std::move(alert_label));
}
if (thumbnail_view_) {
// We only clip the corners of the fade image when there isn't a header
// or footer.
ThumbnailView::RoundedCorners corners =
ThumbnailView::RoundedCorners::kNone;
if (!alert_state_.has_value()) {
corners = alternate_layout
? ThumbnailView::RoundedCorners::kTopCorners
: ThumbnailView::RoundedCorners::kBottomCorners;
}
thumbnail_view_->SetRoundedCorners(corners, corner_radius_.value_or(0));
}
}
}
void TabHoverCardBubbleView::SetTextFade(double percent) {
title_label_->SetFade(percent);
domain_label_->SetFade(percent);
}
void TabHoverCardBubbleView::SetTargetTabImage(gfx::ImageSkia preview_image) {
DCHECK(thumbnail_view_)
<< "This method should only be called when preview images are enabled.";
thumbnail_view_->SetTargetTabImage(preview_image);
}
void TabHoverCardBubbleView::SetPlaceholderImage() {
DCHECK(thumbnail_view_)
<< "This method should only be called when preview images are enabled.";
thumbnail_view_->SetPlaceholderImage();
}
std::u16string TabHoverCardBubbleView::GetTitleTextForTesting() const {
return title_label_->GetText();
}
std::u16string TabHoverCardBubbleView::GetDomainTextForTesting() const {
return domain_label_->GetText();
}
// static
absl::optional<double> TabHoverCardBubbleView::GetPreviewImageCrossfadeStart() {
static const double start_percent = base::GetFieldTrialParamByFeatureAsDouble(
features::kTabHoverCardImages,
features::kTabHoverCardImagesCrossfadePreviewAtParameterName, -1.0);
return start_percent >= 0.0
? absl::make_optional(base::clamp(start_percent, 0.0, 1.0))
: absl::nullopt;
}
gfx::Size TabHoverCardBubbleView::CalculatePreferredSize() const {
gfx::Size preferred_size = GetLayoutManager()->GetPreferredSize(this);
preferred_size.set_width(TabStyle::GetPreviewImageSize().width());
DCHECK(!preferred_size.IsEmpty());
return preferred_size;
}
void TabHoverCardBubbleView::OnThemeChanged() {
BubbleDialogDelegateView::OnThemeChanged();
// Bubble closes if the theme changes to the point where the border has to be
// regenerated. See crbug.com/1140256
if (using_rounded_corners() != CustomShadowsSupported()) {
GetWidget()->Close();
return;
}
}
BEGIN_METADATA(TabHoverCardBubbleView, views::BubbleDialogDelegateView)
END_METADATA