chromium/ash/assistant/ui/base/assistant_button.cc

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

#include "ash/assistant/ui/base/assistant_button.h"

#include "ash/assistant/model/assistant_ui_model.h"
#include "ash/assistant/ui/base/assistant_button_listener.h"
#include "ash/assistant/util/histogram_util.h"
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/assistant/controller/assistant_ui_controller.h"
#include "ash/style/ash_color_id.h"
#include "ash/style/style_util.h"
#include "base/functional/bind.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/color/color_provider.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/animation/ink_drop.h"
#include "ui/views/controls/highlight_path_generator.h"
#include "ui/views/view.h"

namespace ash {

namespace {

// Appearance.
constexpr int kFocusRingStrokeWidth = 2;
constexpr int kInkDropInset = 2;

}  // namespace

// AssistantButton::InitParams -------------------------------------------------

AssistantButton::InitParams::InitParams() = default;
AssistantButton::InitParams::InitParams(InitParams&&) = default;
AssistantButton::InitParams::~InitParams() = default;

// AssistantButton -------------------------------------------------------------

AssistantButton::AssistantButton(AssistantButtonListener* listener,
                                 AssistantButtonId button_id)
    : views::ImageButton(base::BindRepeating(&AssistantButton::OnButtonPressed,
                                             base::Unretained(this))),
      listener_(listener),
      id_(button_id) {
  // Avoid drawing default focus ring and draw customized focus instead.
  SetInstallFocusRingOnFocus(false);
  SetFocusBehavior(FocusBehavior::ALWAYS);

  // Inkdrop only on click.
  views::InkDrop::Get(this)->SetMode(views::InkDropHost::InkDropMode::ON);
  SetHasInkDropActionOnClick(true);
  views::InkDrop::UseInkDropForFloodFillRipple(views::InkDrop::Get(this),
                                               /*highlight_on_hover=*/false);
  views::InstallCircleHighlightPathGenerator(this, gfx::Insets(kInkDropInset));

  // Image.
  SetFlipCanvasOnPaintForRTLUI(false);
  SetImageHorizontalAlignment(views::ImageButton::ALIGN_CENTER);
  SetImageVerticalAlignment(views::ImageButton::ALIGN_MIDDLE);
}

AssistantButton::~AssistantButton() = default;

// static
std::unique_ptr<AssistantButton> AssistantButton::Create(
    AssistantButtonListener* listener,
    const gfx::VectorIcon& icon,
    AssistantButtonId button_id,
    InitParams params) {
  DCHECK_GT(params.size_in_dip, 0);
  DCHECK_GT(params.icon_size_in_dip, 0);
  DCHECK(params.accessible_name_id.has_value());

  auto button = std::make_unique<AssistantButton>(listener, button_id);
  button->GetViewAccessibility().SetName(
      l10n_util::GetStringUTF16(params.accessible_name_id.value()));

  if (params.tooltip_id) {
    button->SetTooltipText(
        l10n_util::GetStringUTF16(params.tooltip_id.value()));
  }

  button->SetPreferredSize(gfx::Size(params.size_in_dip, params.size_in_dip));

  gfx::IconDescription icon_description(icon, params.icon_size_in_dip,
                                        gfx::kPlaceholderColor);

  if (params.icon_color_type.has_value()) {
    // If we have an `icon_color_type`, that color needs to be resolved in
    // OnThemeChanged(). Since we can't do anything else now, just set the data
    // and return the button.
    button->icon_color_type_ = params.icon_color_type.value();
    // We cannot copy IconDescription as copy assignment operator of
    // IconDescription is deleted since it has a non-static reference member,
    // icon.
    button->icon_description_.emplace(icon_description);
    return button;
  }

  button->SetImageModel(
      views::Button::STATE_NORMAL,
      ui::ImageModel::FromVectorIcon(*icon_description.icon, params.icon_color,
                                     icon_description.dip_size));
  return button;
}

void AssistantButton::OnBlur() {
  views::ImageButton::OnBlur();
  SchedulePaint();
}

void AssistantButton::OnBoundsChanged(const gfx::Rect& previous_bounds) {
  // Note that the current assumption is that button bounds are square.
  DCHECK_EQ(width(), height());
}

void AssistantButton::OnFocus() {
  views::ImageButton::OnFocus();
  SchedulePaint();
}

void AssistantButton::OnPaintBackground(gfx::Canvas* canvas) {
  // Hide focus ring when keyboard traversal is not enabled.
  // This is specifically applicable to tablet mode when
  // keyboard traversal may be off.
  const bool hide_focus_ring_when_not_keyboard_traversal =
      !AssistantUiController::Get()->GetModel()->keyboard_traversal_mode();
  const bool should_show_focus_ring =
      HasFocus() && !hide_focus_ring_when_not_keyboard_traversal;

  if (should_show_focus_ring) {
    cc::PaintFlags circle_flags;
    circle_flags.setAntiAlias(true);
    circle_flags.setColor(
        GetColorProvider()->GetColor(cros_tokens::kFocusRingColor));
    circle_flags.setStyle(cc::PaintFlags::kStroke_Style);
    circle_flags.setStrokeWidth(kFocusRingStrokeWidth);
    canvas->DrawCircle(GetLocalBounds().CenterPoint(),
                       width() / 2 - kFocusRingStrokeWidth, circle_flags);
  }
}

void AssistantButton::OnThemeChanged() {
  views::View::OnThemeChanged();

  // Updates inkdrop color and opacity.
  auto* ink_drop = views::InkDrop::Get(this);
  ink_drop->SetBaseColor(
      GetColorProvider()->GetColor(kColorAshInkDropOpaqueColor));
  ink_drop->SetVisibleOpacity(StyleUtil::GetInkDropOpacity());

  if (!icon_color_type_.has_value() || !icon_description_.has_value())
    return;

  // This might be the first time the image is rendered since `icon_color_type_`
  // may not resolvable until now.
  icon_description_->color = GetColorProvider()->GetColor(*icon_color_type_);
  SetImageModel(views::Button::STATE_NORMAL,
                ui::ImageModel::FromVectorIcon(*icon_description_->icon,
                                               icon_description_->color,
                                               icon_description_->dip_size));
}

void AssistantButton::OnButtonPressed() {
  assistant::util::IncrementAssistantButtonClickCount(id_);
  listener_->OnButtonPressed(id_);
}

BEGIN_METADATA(AssistantButton)
END_METADATA

}  // namespace ash