blob: faeda398ad3c43e50a6cd24ed3b89b4da94b0163 [file] [log] [blame]
// Copyright 2023 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/tabs/tab_strip_control_button.h"
#include <utility>
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/views/frame/browser_frame_view.h"
#include "chrome/browser/ui/views/tabs/tab_strip.h"
#include "chrome/browser/ui/views/tabs/tab_strip_controller.h"
#include "chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.h"
#include "chrome/common/chrome_features.h"
#include "third_party/skia/include/core/SkPath.h"
#include "third_party/skia/include/core/SkRRect.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/gfx/vector_icon_types.h"
#include "ui/views/animation/flood_fill_ink_drop_ripple.h"
#include "ui/views/animation/ink_drop.h"
#include "ui/views/animation/ink_drop_highlight.h"
#include "ui/views/animation/ink_drop_state.h"
#include "ui/views/background.h"
#include "ui/views/controls/highlight_path_generator.h"
using std::make_unique;
namespace {
class ControlButtonHighlightPathGenerator
: public views::HighlightPathGenerator {
public:
explicit ControlButtonHighlightPathGenerator(
TabStripControlButton* control_button)
: control_button_(control_button) {}
// HighlightPathGenerator:
SkPath GetHighlightPath(const views::View* view) override {
gfx::Rect rect(view->GetContentsBounds());
SkPath path;
const int corner_radius = control_button_->GetCornerRadius();
const SkScalar left_radius =
control_button_->GetScaledCornerRadius(corner_radius, Edge::kLeft);
const SkScalar right_radius =
control_button_->GetScaledCornerRadius(corner_radius, Edge::kRight);
const SkVector radii[4] = {{left_radius, left_radius},
{right_radius, right_radius},
{right_radius, right_radius},
{left_radius, left_radius}};
return SkPath::RRect(
SkRRect::MakeRectRadii(gfx::RectToSkRect(rect), radii));
}
private:
raw_ptr<TabStripControlButton> control_button_;
};
} // namespace
const int TabStripControlButton::kIconSize = 16;
const gfx::Size TabStripControlButton::kButtonSize{28, 28};
TabStripControlButton::TabStripControlButton(
TabStripController* tab_strip_controller,
PressedCallback callback,
const gfx::VectorIcon& icon,
Edge fixed_flat_edge,
Edge animated_flat_edge)
: TabStripControlButton(tab_strip_controller,
std::move(callback),
icon,
std::u16string(),
fixed_flat_edge,
animated_flat_edge) {}
TabStripControlButton::TabStripControlButton(
TabStripController* tab_strip_controller,
PressedCallback callback,
const std::u16string& text,
Edge fixed_flat_edge,
Edge animated_flat_edge)
: TabStripControlButton(tab_strip_controller,
std::move(callback),
gfx::VectorIcon::EmptyIcon(),
text,
fixed_flat_edge,
animated_flat_edge) {}
TabStripControlButton::TabStripControlButton(
TabStripController* tab_strip_controller,
PressedCallback callback,
const gfx::VectorIcon& icon,
const std::u16string& text,
Edge fixed_flat_edge,
Edge animated_flat_edge)
: views::LabelButton(std::move(callback), text),
icon_(icon),
fixed_flat_edge_(fixed_flat_edge),
animated_flat_edge_(animated_flat_edge),
tab_strip_controller_(tab_strip_controller) {
SetImageCentered(true);
SetEventTargeter(std::make_unique<views::ViewTargeter>(this));
foreground_frame_active_color_id_ = kColorTabForegroundInactiveFrameActive;
foreground_frame_inactive_color_id_ =
kColorNewTabButtonCRForegroundFrameInactive;
background_frame_active_color_id_ = kColorNewTabButtonBackgroundFrameActive;
background_frame_inactive_color_id_ =
kColorNewTabButtonBackgroundFrameInactive;
UpdateIcon();
SetHorizontalAlignment(gfx::ALIGN_CENTER);
views::FocusRing::Get(this)->SetColorId(kColorNewTabButtonFocusRing);
SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
views::InkDrop::Get(this)->SetMode(views::InkDropHost::InkDropMode::ON);
views::InkDrop::Get(this)->SetLayerRegion(views::LayerRegion::kAbove);
views::HighlightPathGenerator::Install(
this, std::make_unique<ControlButtonHighlightPathGenerator>(this));
UpdateInkDrop();
views::FocusRing::Get(this)->SetColorId(kColorNewTabButtonFocusRing);
if (text.size() > 0) {
SetEnabledTextColors(foreground_frame_active_color_id_);
SetText(text);
}
}
void TabStripControlButton::SetForegroundFrameActiveColorId(
ui::ColorId new_color_id) {
foreground_frame_active_color_id_ = new_color_id;
UpdateColors();
}
void TabStripControlButton::SetForegroundFrameInactiveColorId(
ui::ColorId new_color_id) {
foreground_frame_inactive_color_id_ = new_color_id;
UpdateColors();
}
void TabStripControlButton::SetBackgroundFrameActiveColorId(
ui::ColorId new_color_id) {
background_frame_active_color_id_ = new_color_id;
UpdateColors();
}
void TabStripControlButton::SetBackgroundFrameInactiveColorId(
ui::ColorId new_color_id) {
background_frame_inactive_color_id_ = new_color_id;
UpdateColors();
}
void TabStripControlButton::SetVectorIcon(const gfx::VectorIcon& icon) {
icon_ = icon;
UpdateIcon();
}
void TabStripControlButton::SetText(std::u16string_view text) {
label()->SetText(text);
// Required for text to be visible on hover.
// TODO(crbug.com/431015299): Fix text on hover and remove.
label()->SetPaintToLayer();
label()->SetSkipSubpixelRenderingOpacityCheck(true);
label()->layer()->SetFillsBoundsOpaquely(false);
label()->SetSubpixelRenderingEnabled(false);
}
ui::ColorId TabStripControlButton::GetBackgroundColor() {
return (GetWidget() && GetWidget()->ShouldPaintAsActive())
? background_frame_active_color_id_
: background_frame_inactive_color_id_;
}
ui::ColorId TabStripControlButton::GetForegroundColor() {
return (GetWidget() && GetWidget()->ShouldPaintAsActive())
? foreground_frame_active_color_id_
: foreground_frame_inactive_color_id_;
}
void TabStripControlButton::UpdateIcon() {
if (icon_->is_empty()) {
return;
}
const ui::ImageModel icon_image_model = ui::ImageModel::FromVectorIcon(
icon_.get(), GetForegroundColor(), kIconSize);
SetImageModel(views::Button::STATE_NORMAL, icon_image_model);
SetImageModel(views::Button::STATE_HOVERED, icon_image_model);
SetImageModel(views::Button::STATE_PRESSED, icon_image_model);
}
void TabStripControlButton::UpdateInkDrop() {
const auto* const color_provider = GetColorProvider();
if (!color_provider) {
return;
}
CreateToolbarInkdropCallbacks(this, kColorTabStripControlButtonInkDrop,
kColorTabStripControlButtonInkDropRipple);
}
void TabStripControlButton::UpdateColors() {
const auto* const color_provider = GetColorProvider();
if (!color_provider) {
return;
}
SetEnabledTextColors(foreground_frame_active_color_id_);
UpdateBackground();
UpdateInkDrop();
UpdateIcon();
SchedulePaint();
}
void TabStripControlButton::UpdateBackground() {
const auto* const color_provider = GetColorProvider();
if (!color_provider) {
return;
}
const std::optional<int> bg_id = tab_strip_controller_->GetCustomBackgroundId(
BrowserFrameActiveState::kUseCurrent);
// Paint the background as transparent for image based themes.
if (bg_id.has_value() && paint_transparent_for_custom_image_theme_) {
SetBackground(views::CreateSolidBackground(SK_ColorTRANSPARENT));
} else {
const float right_corner_radius =
GetScaledCornerRadius(GetCornerRadius(), Edge::kRight);
const float left_corner_radius =
GetScaledCornerRadius(GetCornerRadius(), Edge::kLeft);
SetBackground(views::CreateBackgroundFromPainter(
views::Painter::CreateSolidRoundRectPainterWithVariableRadius(
color_provider->GetColor(GetBackgroundColor()),
gfx::RoundedCornersF(left_corner_radius, right_corner_radius,
right_corner_radius, left_corner_radius),
GetInsets())));
}
}
int TabStripControlButton::GetCornerRadius() const {
return TabStripControlButton::kButtonSize.width() / 2;
}
int TabStripControlButton::GetFlatCornerRadius() const {
return 0;
}
float TabStripControlButton::GetScaledCornerRadius(float initial_radius,
Edge edge) const {
const int flat_corner_radius = GetFlatCornerRadius();
if (fixed_flat_edge_ == edge) {
return flat_corner_radius;
} else if (animated_flat_edge_ == edge) {
return ((initial_radius - flat_corner_radius) * flat_edge_factor_) +
flat_corner_radius;
} else {
return initial_radius;
}
}
void TabStripControlButton::AddedToWidget() {
paint_as_active_subscription_ =
GetWidget()->RegisterPaintAsActiveChangedCallback(base::BindRepeating(
&TabStripControlButton::UpdateColors, base::Unretained(this)));
UpdateColors();
}
void TabStripControlButton::RemovedFromWidget() {
paint_as_active_subscription_ = {};
}
void TabStripControlButton::OnThemeChanged() {
views::LabelButton::OnThemeChanged();
UpdateColors();
}
bool TabStripControlButton::GetHitTestMask(SkPath* mask) const {
const bool extend_to_top = tab_strip_controller_->IsFrameCondensed();
const SkScalar bottom_radius = GetCornerRadius();
const SkScalar top_radius = extend_to_top ? 0.0f : bottom_radius;
const SkScalar bottom_left_radius =
GetScaledCornerRadius(bottom_radius, Edge::kLeft);
const SkScalar bottom_right_radius =
GetScaledCornerRadius(bottom_radius, Edge::kRight);
const SkScalar top_left_radius =
GetScaledCornerRadius(top_radius, Edge::kLeft);
const SkScalar top_right_radius =
GetScaledCornerRadius(top_radius, Edge::kRight);
const SkVector radii[4] = {{top_left_radius, top_left_radius},
{top_right_radius, top_right_radius},
{bottom_right_radius, bottom_right_radius},
{bottom_left_radius, bottom_left_radius}};
gfx::Rect rect = GetContentsBounds();
if (extend_to_top) {
rect.SetVerticalBounds(0, rect.bottom());
}
*mask = SkPath::RRect(SkRRect::MakeRectRadii(gfx::RectToSkRect(rect), radii));
return true;
}
gfx::Size TabStripControlButton::CalculatePreferredSize(
const views::SizeBounds& available_size) const {
gfx::Size size = TabStripControlButton::kButtonSize;
const auto insets = GetInsets();
size.Enlarge(insets.width(), insets.height());
return size;
}
void TabStripControlButton::NotifyClick(const ui::Event& event) {
LabelButton::NotifyClick(event);
views::InkDrop::Get(this)->GetInkDrop()->AnimateToState(
views::InkDropState::ACTION_TRIGGERED);
}
void TabStripControlButton::SetFlatEdgeFactor(float factor) {
flat_edge_factor_ = factor;
UpdateBackground();
// The ink drop doesn't automatically pick up on rounded corner changes, so
// we need to manually notify it here.
// TODO(crbug.com/332937585): Clean up once this is no longer necessary or
// there is a better API for updating.
views::InkDrop::Get(this)->GetInkDrop()->HostSizeChanged(size());
}
void TabStripControlButton::AnimateToStateForTesting(
views::InkDropState state) {
views::InkDrop::Get(this)->GetInkDrop()->AnimateToState(state);
}
BEGIN_METADATA(TabStripControlButton)
END_METADATA