910e62b5创建于 1月15日历史提交
// 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/views/controls/rich_hover_button.h"

#include <memory>
#include <string_view>
#include <utility>

#include "base/strings/strcat.h"
#include "chrome/browser/ui/views/accessibility/non_accessible_image_view.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/chrome_typography.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/models/image_model.h"
#include "ui/base/ui_base_features.h"
#include "ui/compositor/layer.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/animation/ink_drop.h"
#include "ui/views/border.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/table_layout.h"
#include "ui/views/style/typography.h"
#include "ui/views/style/typography_provider.h"
#include "ui/views/view_class_properties.h"

namespace {

std::unique_ptr<views::ImageView> CreateIconView(ui::ImageModel icon) {
  auto view = std::make_unique<NonAccessibleImageView>();
  view->SetImage(std::move(icon));
  // Make sure hovering over the icon also hovers the `RichHoverButton`.
  view->SetCanProcessEventsWithinSubtree(false);
  // Don't cover |icon| when the ink drops are being painted.
  view->SetPaintToLayer();
  view->layer()->SetFillsBoundsOpaquely(false);
  return view;
}

// TODO(crbug.com/355018927): Remove this when we implement in views::Label.
class SubtitleLabelWrapper : public views::View {
  METADATA_HEADER(SubtitleLabelWrapper, views::View)
 public:
  explicit SubtitleLabelWrapper(std::unique_ptr<views::View> title) {
    SetUseDefaultFillLayout(true);
    title_ = AddChildView(std::move(title));
  }

 private:
  // View:
  gfx::Size CalculatePreferredSize(
      const views::SizeBounds& available_size) const override {
    gfx::Size preferred_size = title_->GetPreferredSize(available_size);
    if (!available_size.width().is_bounded()) {
      preferred_size.set_width(title_->GetMinimumSize().width());
    }
    return preferred_size;
  }

  raw_ptr<views::View> title_ = nullptr;
};

BEGIN_METADATA(SubtitleLabelWrapper)
END_METADATA

}  // namespace

RichHoverButton::RichHoverButton() {
  image_container_view()->SetProperty(views::kViewIgnoredByLayoutKey, true);
  label()->SetHandlesTooltips(false);
  label()->SetProperty(views::kViewIgnoredByLayoutKey, true);
  ink_drop_container()->SetProperty(views::kViewIgnoredByLayoutKey, true);

  start_ = children().size();

  views::Builder<RichHoverButton>(this)
      .SetBorder(
          views::CreateEmptyBorder(ChromeLayoutProvider::Get()->GetInsetsMetric(
              ChromeInsetsMetric::INSETS_PAGE_INFO_HOVER_BUTTON)))
      // Main icon placeholder.
      .AddChild(views::Builder<views::View>())
      // Title.
      .AddChild(views::Builder<views::Label>()
                    .CopyAddressTo(&title_)
                    .SetTextContext(views::style::CONTEXT_DIALOG_BODY_TEXT)
                    .SetTextStyle(views::style::STYLE_BODY_3_MEDIUM)
                    .SetHorizontalAlignment(gfx::ALIGN_LEFT)
                    .SetCanProcessEventsWithinSubtree(false))
      // Action icon placeholder.
      .AddChild(views::Builder<views::View>())
      .BuildChildren();

  custom_view_row_start_ = children().size();

  RecreateLayout();
}

RichHoverButton::RichHoverButton(views::Button::PressedCallback callback,
                                 ui::ImageModel icon,
                                 std::u16string_view title_text,
                                 std::u16string_view subtitle_text,
                                 ui::ImageModel action_icon,
                                 ui::ImageModel state_icon)
    : RichHoverButton() {
  SetCallback(std::move(callback));
  SetIcon(std::move(icon));
  SetTitleText(title_text);
  SetStateIcon(std::move(state_icon));
  SetActionIcon(std::move(action_icon));
  SetSubtitleText(subtitle_text);
}

RichHoverButton::~RichHoverButton() = default;

ui::ImageModel RichHoverButton::GetIcon() const {
  return icon_ ? icon_->GetImageModel() : ui::ImageModel();
}

void RichHoverButton::SetIcon(ui::ImageModel icon) {
  SetIconMember(icon_, start_, std::move(icon), true);
}

std::u16string_view RichHoverButton::GetTitleText() const {
  return title_->GetText();
}

void RichHoverButton::SetTitleText(std::u16string_view title_text) {
  title_->SetText(std::u16string(title_text));
  UpdateAccessibleName();
}

ui::ImageModel RichHoverButton::GetStateIcon() const {
  return state_icon_ ? state_icon_->GetImageModel() : ui::ImageModel();
}

void RichHoverButton::SetStateIcon(ui::ImageModel state_icon) {
  SetIconMember(state_icon_, start_ + 2, std::move(state_icon), false);
}

ui::ImageModel RichHoverButton::GetActionIcon() const {
  return action_icon_ ? action_icon_->GetImageModel() : ui::ImageModel();
}

void RichHoverButton::SetActionIcon(ui::ImageModel action_icon) {
  SetIconMember(action_icon_, start_ + (state_icon_ ? 3 : 2),
                std::move(action_icon), true);
}

std::u16string_view RichHoverButton::GetSubtitleText() const {
  return subtitle_ ? subtitle_->GetText() : std::u16string_view();
}

void RichHoverButton::SetSubtitleText(std::u16string_view subtitle_text) {
  if (subtitle_text.empty()) {
    subtitle_ = nullptr;
    for (const auto& v : subtitle_row_views_) {
      RemoveChildViewT(v);
    }
    subtitle_row_views_.clear();
  } else {
    if (subtitle_row_views_.empty()) {
      subtitle_row_views_.push_back(AddChildView(
          std::make_unique<views::View>()));  // Skip main icon column.
      auto subtitle = std::make_unique<views::Label>();
      subtitle_ = subtitle.get();
      subtitle_row_views_.push_back(AddChildView(
          std::make_unique<SubtitleLabelWrapper>(std::move(subtitle))));
      subtitle_->SetTextStyle(views::style::STYLE_BODY_5);
      subtitle_->SetEnabledColor(ui::kColorLabelForegroundSecondary);
      subtitle_->SetMultiLine(subtitle_multiline_);
      subtitle_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
      subtitle_->SetAutoColorReadabilityEnabled(false);
      base::Extend(subtitle_row_views_, AddFillerViews(children().size()));
    }
    subtitle_->SetText(std::u16string(subtitle_text));
  }
  RecreateLayout();
  UpdateAccessibleName();
}

bool RichHoverButton::GetSubtitleMultiline() const {
  return subtitle_multiline_;
}

void RichHoverButton::SetSubtitleMultiline(bool subtitle_multiline) {
  subtitle_multiline_ = subtitle_multiline;
  if (subtitle_) {
    subtitle_->SetMultiLine(subtitle_multiline);
  }
}

void RichHoverButton::SetTitleTextStyleAndColor(int style,
                                                ui::ColorId color_id) {
  title_->SetTextStyle(style);
  title_->SetEnabledColor(color_id);
}

void RichHoverButton::SetSubtitleTextStyleAndColor(int style,
                                                   ui::ColorId color_id) {
  if (subtitle_) {
    subtitle_->SetTextStyle(style);
    subtitle_->SetEnabledColor(color_id);
  }
}

void RichHoverButton::OnBoundsChanged(const gfx::Rect& previous_bounds) {
  return Button::OnBoundsChanged(previous_bounds);
}

views::View* RichHoverButton::GetTooltipHandlerForPoint(
    const gfx::Point& point) {
  return Button::GetTooltipHandlerForPoint(point);
}

gfx::Size RichHoverButton::CalculatePreferredSize(
    const views::SizeBounds& available_size) const {
  return Button::CalculatePreferredSize(available_size);
}

void RichHoverButton::SetIconMember(raw_ptr<views::ImageView>& icon_member,
                                    size_t child_index,
                                    ui::ImageModel icon,
                                    bool use_placeholder) {
  if (icon.IsEmpty()) {
    if (icon_member) {
      icon_member = nullptr;
      RemoveChildViewT(children()[child_index]);
      if (use_placeholder) {
        AddChildViewAt(std::make_unique<views::View>(), child_index);
      } else {
        RecreateLayout();
      }
    }
  } else if (!icon_member) {
    if (use_placeholder) {
      RemoveChildViewT(children()[child_index]);
    }
    icon_member = AddChildViewAt(CreateIconView(std::move(icon)), child_index);
    if (!use_placeholder) {
      RecreateLayout();
    }
  } else {
    icon_member->SetImage(std::move(icon));
  }
}

void RichHoverButton::RecreateLayout() {
  const int icon_label_spacing = ChromeLayoutProvider::Get()->GetDistanceMetric(
      DISTANCE_RICH_HOVER_BUTTON_ICON_HORIZONTAL);
  views::TableLayout* table_layout =
      SetLayoutManager(std::make_unique<views::TableLayout>());
  table_layout
      // Column for main image.
      ->AddColumn(views::LayoutAlignment::kCenter,
                  views::LayoutAlignment::kCenter,
                  views::TableLayout::kFixedSize,
                  views::TableLayout::ColumnSize::kUsePreferred, 0, 0)
      .AddPaddingColumn(views::TableLayout::kFixedSize, icon_label_spacing)
      // Column for title.
      .AddColumn(views::LayoutAlignment::kStretch,
                 views::LayoutAlignment::kCenter, 1.0f,
                 views::TableLayout::ColumnSize::kUsePreferred, 0, 0);
  if (state_icon_) {
    table_layout
        // Column for state icon.
        ->AddPaddingColumn(views::TableLayout::kFixedSize, icon_label_spacing)
        .AddColumn(views::LayoutAlignment::kCenter,
                   views::LayoutAlignment::kCenter,
                   views::TableLayout::kFixedSize,
                   views::TableLayout::ColumnSize::kFixed, 16, 0);
  }
  table_layout
      // Column for action icon.
      ->AddPaddingColumn(views::TableLayout::kFixedSize, icon_label_spacing)
      .AddColumn(views::LayoutAlignment::kCenter,
                 views::LayoutAlignment::kCenter,
                 views::TableLayout::kFixedSize,
                 views::TableLayout::ColumnSize::kFixed, 16, 0)
      .AddRows(1, views::TableLayout::kFixedSize,
               // Force row to have sufficient height for full line-height of
               // the title.
               views::TypographyProvider::Get().GetLineHeight(
                   views::style::CONTEXT_DIALOG_BODY_TEXT,
                   views::style::STYLE_PRIMARY));
  if (!custom_view_row_views_.empty()) {
    // Row for custom view.
    table_layout->AddRows(1, views::TableLayout::kFixedSize);
  }
  if (!subtitle_row_views_.empty()) {
    // Row for subtitle.
    table_layout->AddRows(1, views::TableLayout::kFixedSize);
  }
}

void RichHoverButton::UpdateAccessibleName() {
  const std::u16string_view title_text = GetTitleText();
  const std::u16string_view subtitle_text = GetSubtitleText();
  HoverButton::GetViewAccessibility().SetName(
      subtitle_text.empty() ? std::u16string(title_text)
                            : base::StrCat({title_text, u"\n", subtitle_text}));
}

std::vector<raw_ptr<views::View>> RichHoverButton::AddFillerViews(
    size_t start) {
  std::vector<raw_ptr<views::View>> vec;
  if (state_icon_) {
    vec.push_back(AddChildViewAt(std::make_unique<views::View>(), start++));
  }
  vec.push_back(AddChildViewAt(std::make_unique<views::View>(), start));
  return vec;
}

BEGIN_METADATA(RichHoverButton)
ADD_PROPERTY_METADATA(ui::ImageModel, Icon)
ADD_PROPERTY_METADATA(std::u16string_view, TitleText)
ADD_PROPERTY_METADATA(ui::ImageModel, StateIcon)
ADD_PROPERTY_METADATA(ui::ImageModel, ActionIcon)
ADD_PROPERTY_METADATA(std::u16string_view, SubtitleText)
ADD_PROPERTY_METADATA(bool, SubtitleMultiline)
END_METADATA