blob: 94c1d6614879676c50a6e1244799cc2ecb43aeb4 [file] [log] [blame]
// Copyright 2024 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/quick_insert/views/quick_insert_item_view.h"
#include <memory>
#include <utility>
#include "ash/quick_insert/views/quick_insert_focus_indicator.h"
#include "ash/quick_insert/views/quick_insert_submenu_controller.h"
#include "ash/style/style_util.h"
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/time/time.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/color/color_provider.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/views/animation/ink_drop.h"
#include "ui/views/animation/ink_drop_host.h"
#include "ui/views/background.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/focus_ring.h"
#include "ui/views/view_utils.h"
namespace ash {
namespace {
// How much to clip the focused QuickInsertItemView by. Inset by at least the
// default focus ring inset so the clipping is actually visible, then clip by
// the actual desired amount. It would be better to absolute value the halo
// inset here, but to keep this constexpr, also multiply by -1 to keep it
// positive.
constexpr float kPseudoFocusClipInset =
views::FocusRing::kDefaultHaloInset * -1.0f + 2.0f;
constexpr auto kQuickInsertItemFocusIndicatorMargins = gfx::Insets::VH(6, 0);
} // namespace
QuickInsertItemView::QuickInsertItemView(
SelectItemCallback select_item_callback,
FocusIndicatorStyle focus_indicator_style)
: views::Button(select_item_callback),
select_item_callback_(select_item_callback),
focus_indicator_style_(focus_indicator_style) {
switch (focus_indicator_style_) {
case FocusIndicatorStyle::kFocusRingWithInsetGap:
[[fallthrough]];
case FocusIndicatorStyle::kFocusRing:
StyleUtil::SetUpFocusRingForView(this);
views::FocusRing::Get(this)->SetHasFocusPredicate(
base::BindRepeating([](const View* view) {
const auto* v = views::AsViewClass<QuickInsertItemView>(view);
CHECK(v);
return (v->HasFocus() ||
v->GetItemState() ==
QuickInsertItemView::ItemState::kPseudoFocused);
}));
break;
case FocusIndicatorStyle::kFocusBar:
// Disable default focus ring to use a custom focus indicator.
SetInstallFocusRingOnFocus(false);
break;
}
}
QuickInsertItemView::~QuickInsertItemView() = default;
void QuickInsertItemView::StateChanged(ButtonState old_state) {
UpdateBackground();
}
void QuickInsertItemView::PaintButtonContents(gfx::Canvas* canvas) {
views::Button::PaintButtonContents(canvas);
if (focus_indicator_style_ == FocusIndicatorStyle::kFocusBar &&
item_state_ == ItemState::kPseudoFocused) {
PaintQuickInsertFocusIndicator(
canvas, gfx::Point(0, kQuickInsertItemFocusIndicatorMargins.top()),
height() - kQuickInsertItemFocusIndicatorMargins.height(),
GetColorProvider()->GetColor(cros_tokens::kCrosSysFocusRing));
}
}
void QuickInsertItemView::SelectItem() {
select_item_callback_.Run();
}
void QuickInsertItemView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
UpdateClipPathForFocusRingWithInsetGap();
}
void QuickInsertItemView::OnMouseEntered(const ui::MouseEvent& event) {
if (submenu_controller_ != nullptr) {
submenu_controller_->Close();
}
}
void QuickInsertItemView::SetCornerRadius(int corner_radius) {
if (corner_radius_ == corner_radius) {
return;
}
corner_radius_ = corner_radius;
StyleUtil::InstallRoundedCornerHighlightPathGenerator(
this, gfx::RoundedCornersF(corner_radius_));
UpdateBackground();
}
QuickInsertSubmenuController* QuickInsertItemView::GetSubmenuController() {
return submenu_controller_;
}
void QuickInsertItemView::SetSubmenuController(
QuickInsertSubmenuController* submenu_controller) {
submenu_controller_ = submenu_controller;
}
QuickInsertItemView::ItemState QuickInsertItemView::GetItemState() const {
return item_state_;
}
void QuickInsertItemView::SetItemState(ItemState item_state) {
if (item_state_ == item_state) {
return;
}
item_state_ = item_state;
UpdateBackground();
switch (focus_indicator_style_) {
case FocusIndicatorStyle::kFocusRingWithInsetGap:
UpdateClipPathForFocusRingWithInsetGap();
[[fallthrough]];
case FocusIndicatorStyle::kFocusRing:
views::FocusRing::Get(this)->SchedulePaint();
break;
case FocusIndicatorStyle::kFocusBar:
SchedulePaint();
break;
}
}
void QuickInsertItemView::UpdateClipPathForFocusRingWithInsetGap() {
if (focus_indicator_style_ != FocusIndicatorStyle::kFocusRingWithInsetGap) {
return;
}
SkPath clip_path;
if (item_state_ == ItemState::kPseudoFocused) {
gfx::RectF inset_bounds(GetLocalBounds());
const SkScalar radius =
SkIntToScalar(corner_radius_ - kPseudoFocusClipInset);
inset_bounds.Inset(kPseudoFocusClipInset);
clip_path.addRoundRect(gfx::RectFToSkRect(inset_bounds), radius, radius);
}
SetClipPath(clip_path);
}
void QuickInsertItemView::UpdateBackground() {
if (GetState() == views::Button::ButtonState::STATE_HOVERED ||
item_state_ == QuickInsertItemView::ItemState::kPseudoFocused) {
SetBackground(views::CreateRoundedRectBackground(
cros_tokens::kCrosSysHoverOnSubtle, corner_radius_));
} else {
SetBackground(nullptr);
}
}
BEGIN_METADATA(QuickInsertItemView)
END_METADATA
} // namespace ash