chromium/chromeos/ui/frame/caption_buttons/frame_center_button.cc

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

#include "chromeos/ui/frame/caption_buttons/frame_center_button.h"

#include <algorithm>
#include <utility>

#include "base/i18n/rtl.h"
#include "base/numerics/safe_conversions.h"
#include "chromeos/constants/chromeos_features.h"
#include "chromeos/ui/vector_icons/vector_icons.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/hit_test.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/font_list.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/gfx/text_constants.h"
#include "ui/gfx/text_utils.h"
#include "ui/strings/grit/ui_strings.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/animation/ink_drop.h"
#include "ui/views/widget/widget.h"
#include "ui/views/window/caption_button_layout_constants.h"
#include "ui/views/window/custom_frame_view.h"
#include "ui/views/window/frame_caption_button.h"

namespace chromeos {

namespace {

// The margin between the contents inside the button if several of them are set.
constexpr int kMarginBetweenContents = 3;

constexpr int kLeadingMargin = 12;
constexpr int kLeadingMarginText = 8;
constexpr int kLeadingMarginSubIcon = 6;
constexpr int kTailingMargin = 10;

constexpr float kDefaultHighlightOpacityForLight = 0.12f;
constexpr float kDefaultHighlightOpacityForDark = 0.20f;

}  // namespace

FrameCenterButton::FrameCenterButton(PressedCallback callback)
    : FrameCaptionButton(std::move(callback),
                         views::CAPTION_BUTTON_ICON_CENTER,
                         HTMENU) {
  GetViewAccessibility().SetName(
      l10n_util::GetStringUTF16(IDS_APP_ACCNAME_CENTER));
  background_color_changed_subscription_ = AddBackgroundColorChangedCallback(
      base::BindRepeating(&FrameCenterButton::OnBackgroundColorChanged,
                          base::Unretained(this)));
}

FrameCenterButton::~FrameCenterButton() = default;

gfx::Size FrameCenterButton::GetMinimumSize() const {
  gfx::Size size = GetPreferredSize({0, 0});
  // Similar to CalculatePreferredSize(), but allow the text width to be zero.
  size.set_width((sub_icon_image_
                      ? base::ClampCeil(icon_image().width() / 2.0f) +
                            kMarginBetweenContents +
                            base::ClampCeil(sub_icon_image_->width() / 2.0f)
                      : 0) +
                 views::GetCaptionButtonWidth());
  return size;
}

void FrameCenterButton::SetSubImage(const gfx::VectorIcon& icon_definition) {
  gfx::ImageSkia new_icon_image = gfx::CreateVectorIcon(
      icon_definition, GetButtonColor(GetBackgroundColor()));
  if (sub_icon_image_ &&
      new_icon_image.BackedBySameObjectAs(*sub_icon_image_)) {
    return;
  }
  sub_icon_definition_ = &icon_definition;
  sub_icon_image_ = new_icon_image;

  if (parent())
    parent()->InvalidateLayout();
}

void FrameCenterButton::SetText(std::optional<std::u16string> text) {
  if (text_ && text_->text() == text)
    return;

  if (!text) {
    if (text_)
      text_.reset();
    return;
  }

  if (!text_) {
    std::unique_ptr<gfx::RenderText> render_text =
        gfx::RenderText::CreateRenderText();
    render_text->SetFontList(gfx::FontList({"Google Sans", "Roboto"},
                                           gfx::Font::FontStyle::NORMAL, 13,
                                           gfx::Font::Weight::NORMAL));
    render_text->SetHorizontalAlignment(gfx::ALIGN_CENTER);
    render_text->SetVerticalAlignment(gfx::ALIGN_MIDDLE);
    text_ = std::move(render_text);
  }
  text_->SetText(*std::move(text));

  if (parent())
    parent()->InvalidateLayout();
}

// |--------------[FrameCaptionButton width]--------------|
// | (i) |  (ii)  | (iii)  | (iv) |  (v)   | (vi) | (vii) |
// | primary icon | margin | text | margin |   sub icon   |
//
// (i) The left semicircle (views::kCaptionButtonWidth / 2)
// (ii) The right semicircle of the primary icon (icon_image().width() / 2)
// (iii) The margin between the primary icon and the text if set
//       (kMarginBetweenContents)
// (iv) The text if set (text_->GetStringSize().width())
// (v) The margin between the text and the sub icon (kMarginBetweenContents)
// (vi) The left semicircle of the sub icon (sub_icon_image_->width() / 2)
// (vii) The right semicircle of the sub icon (views::kCaptionButtonWidth / 2)
gfx::Size FrameCenterButton::CalculatePreferredSize(
    const views::SizeBounds& available_size) const {
  gfx::Size size = views::View::CalculatePreferredSize(available_size);

  size.set_width(
      (text_ ? text_->GetStringSize().width() + kLeadingMarginText : 0) +
      (sub_icon_image_ ? sub_icon_image_->width() + kLeadingMarginSubIcon : 0) +
      kLeadingMargin + kTailingMargin + icon_image().width());
  return size;
}

void FrameCenterButton::DrawHighlight(gfx::Canvas* canvas,
                                      cc::PaintFlags flags) {
  const gfx::Size ink_drop_size = GetInkDropSize();
  int centered_origin_x = (width() - ink_drop_size.width()) / 2;
  int centered_origin_y = (height() - ink_drop_size.height()) / 2;
  canvas->DrawRoundRect(
      gfx::Rect(centered_origin_x, centered_origin_y, ink_drop_size.width(),
                ink_drop_size.height()),
      GetInkDropCornerRadius(), flags);
}

void FrameCenterButton::DrawIconContents(gfx::Canvas* canvas,
                                         gfx::ImageSkia image,
                                         int x,
                                         int y,
                                         cc::PaintFlags flags) {
  std::optional<gfx::ImageSkia> left_icon = icon_image();
  std::optional<gfx::ImageSkia> right_icon = sub_icon_image_;
  const bool is_rtl = base::i18n::IsRTL();
  if (is_rtl) {
    std::swap(left_icon, right_icon);
  }

  // We want to have default highlight to make the button prominent.
  cc::PaintFlags button_bg_flags;
  button_bg_flags.setColor(views::InkDrop::Get(this)->GetBaseColor());
  button_bg_flags.setAlphaf(color_utils::IsDark(GetBackgroundColor())
                                ? kDefaultHighlightOpacityForDark
                                : kDefaultHighlightOpacityForLight);
  button_bg_flags.setAntiAlias(true);
  DrawHighlight(canvas, button_bg_flags);

  int offset = is_rtl ? kTailingMargin : kLeadingMargin;
  if (left_icon) {
    canvas->DrawImageInt(*left_icon, offset,
                         (height() - left_icon->height()) / 2, flags);
    offset += left_icon->width();
  }

  if (text_) {
    offset += is_rtl ? kLeadingMarginSubIcon : kLeadingMarginText;
    const int max_text_width =
        width() - kLeadingMargin - kTailingMargin - icon_image().width() -
        (sub_icon_image_ ? kLeadingMarginSubIcon + sub_icon_image_->width()
                         : 0);
    const gfx::Rect text_bounds =
        gfx::Rect(offset, (height() - text_->GetStringSize().height()) / 2,
                  std::min(text_->GetStringSize().width(), max_text_width),
                  text_->GetStringSize().height());
    text_->SetDisplayRect(text_bounds);
    text_->SetColor(SkColorSetA(GetButtonColor(GetBackgroundColor()),
                                flags.getAlphaf() * SK_AlphaOPAQUE));
    text_->Draw(canvas);
    offset += text_bounds.width();
  }

  if (right_icon) {
    offset += is_rtl ? kLeadingMarginText : kLeadingMarginSubIcon;
    canvas->DrawImageInt(*right_icon, offset,
                         (height() - right_icon->height()) / 2, flags);
  }

  return;
}

gfx::Size FrameCenterButton::GetInkDropSize() const {
  return gfx::Size(width(), 2 * GetInkDropCornerRadius());
}

void FrameCenterButton::OnBackgroundColorChanged() {
  if (sub_icon_definition_)
    SetSubImage(*sub_icon_definition_);
  if (text_)
    text_->SetColor(GetButtonColor(GetBackgroundColor()));
}

BEGIN_METADATA(FrameCenterButton)
END_METADATA

}  // namespace chromeos