blob: 4ac6a447c7f2013fd8bbcd5043515631ebf9708a [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/views/controls/button/checkbox.h"
#include <stddef.h>
#include <memory>
#include <utility>
#include <variant>
#include "base/functional/bind.h"
#include "third_party/skia/include/core/SkPath.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/ui_base_features.h"
#include "ui/color/color_id.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/geometry/size_f.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/native_theme/native_theme.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/animation/ink_drop.h"
#include "ui/views/animation/ink_drop_host.h"
#include "ui/views/animation/ink_drop_impl.h"
#include "ui/views/animation/ink_drop_painted_layer_delegates.h"
#include "ui/views/animation/ink_drop_ripple.h"
#include "ui/views/controls/button/button_controller.h"
#include "ui/views/controls/button/label_button_border.h"
#include "ui/views/controls/focus_ring.h"
#include "ui/views/controls/highlight_path_generator.h"
#include "ui/views/layout/layout_provider.h"
#include "ui/views/painter.h"
#include "ui/views/resources/grit/views_resources.h"
#include "ui/views/style/platform_style.h"
#include "ui/views/style/typography.h"
#include "ui/views/vector_icons.h"
namespace views {
namespace {
constexpr gfx::Size kCheckboxInkDropSize = gfx::Size(24, 24);
constexpr float kCheckboxIconDipSize = 16;
constexpr int kCheckboxIconCornerRadius = 2;
} // namespace
class Checkbox::FocusRingHighlightPathGenerator
: public views::HighlightPathGenerator {
public:
SkPath GetHighlightPath(const views::View* view) override {
SkPath path;
auto* checkbox = static_cast<const views::Checkbox*>(view);
if (checkbox->image_container_view()->bounds().IsEmpty()) {
return path;
}
return checkbox->GetFocusRingPath();
}
};
Checkbox::Checkbox(const std::u16string& label,
PressedCallback callback,
int button_context)
: LabelButton(std::move(callback), label, button_context) {
SetImageCentered(false);
SetHorizontalAlignment(gfx::ALIGN_LEFT);
SetRequestFocusOnPress(false);
InkDrop::Get(this)->SetMode(views::InkDropHost::InkDropMode::ON);
SetHasInkDropActionOnClick(true);
InkDrop::UseInkDropWithoutAutoHighlight(InkDrop::Get(this),
/*highlight_on_hover=*/false);
InkDrop::Get(this)->SetCreateRippleCallback(base::BindRepeating(
[](Checkbox* host) {
// The "small" size is 21dp, the large size is 1.33 * 21dp = 28dp.
return InkDrop::Get(host)->CreateSquareRipple(
host->image_container_view()
->GetMirroredContentsBounds()
.CenterPoint(),
gfx::Size(21, 21));
},
this));
InkDrop::Get(this)->SetBaseColorCallback(base::BindRepeating(
[](Checkbox* host) {
// Usually ink-drop ripples match the text color. Checkboxes use the
// color of the unchecked, enabled icon.
return host->GetIconImageColor(IconState::ENABLED);
},
this));
// Limit the checkbox height to match the legacy appearance.
const gfx::Size preferred_size(LabelButton::CalculatePreferredSize({}));
SetMinSize(gfx::Size(0, preferred_size.height() + 4));
// Checkboxes always have a focus ring, even when the platform otherwise
// doesn't generally use them for buttons.
SetInstallFocusRingOnFocus(true);
FocusRing::Get(this)->SetPathGenerator(
std::make_unique<FocusRingHighlightPathGenerator>());
// Avoid the default ink-drop mask to allow the ripple effect to extend beyond
// the checkbox view (otherwise it gets clipped which looks weird).
views::InstallEmptyHighlightPathGenerator(this);
InkDrop::Install(image_container_view(),
std::make_unique<InkDropHost>(image_container_view()));
SetInkDropView(image_container_view());
InkDrop::Get(image_container_view())->SetMode(InkDropHost::InkDropMode::ON);
// Allow ImageView to capture mouse events in order for InkDrop effects to
// trigger.
image_container_view()->SetCanProcessEventsWithinSubtree(true);
// Avoid the default ink-drop mask to allow the InkDrop effect to extend
// beyond the image view (otherwise it gets clipped which looks weird).
views::InstallEmptyHighlightPathGenerator(image_container_view());
InkDrop::Get(image_container_view())
->SetCreateHighlightCallback(base::BindRepeating(
[](View* host) {
int radius =
InkDropHost::GetLargeSize(kCheckboxInkDropSize).width() / 2;
return std::make_unique<views::InkDropHighlight>(
gfx::PointF(host->GetContentsBounds().CenterPoint()),
std::make_unique<CircleLayerDelegate>(
views::InkDrop::Get(host)->GetBaseColor(), radius));
},
image_container_view()));
InkDrop::Get(image_container_view())
->SetCreateRippleCallback(base::BindRepeating(
[](View* host) {
return InkDrop::Get(host)->CreateSquareRipple(
host->GetContentsBounds().CenterPoint(), kCheckboxInkDropSize);
},
image_container_view()));
// Usually ink-drop ripples match the text color. Checkboxes use the
// color of the unchecked, enabled icon.
InkDrop::Get(image_container_view())
->SetBaseColor(ui::kColorCheckboxForegroundUnchecked);
GetViewAccessibility().SetRole(ax::mojom::Role::kCheckBox);
SetAndUpdateAccessibleDefaultActionVerb();
UpdateAccessibleCheckedState();
}
Checkbox::~Checkbox() = default;
void Checkbox::SetChecked(bool checked) {
if (GetChecked() == checked) {
return;
}
checked_ = checked;
UpdateImage();
OnPropertyChanged(&checked_, kPropertyEffectsNone);
NotifyViewControllerCallback();
SetAndUpdateAccessibleDefaultActionVerb();
UpdateAccessibleCheckedState();
}
bool Checkbox::GetChecked() const {
return checked_;
}
base::CallbackListSubscription Checkbox::AddCheckedChangedCallback(
PropertyChangedCallback callback) {
return AddPropertyChangedCallback(&checked_, callback);
}
void Checkbox::SetMultiLine(bool multi_line) {
if (GetMultiLine() == multi_line) {
return;
}
label()->SetMultiLine(multi_line);
// TODO(pkasting): Remove this and forward callback subscriptions to the
// underlying label property when Label is converted to properties.
OnPropertyChanged(this, kPropertyEffectsNone);
}
bool Checkbox::GetMultiLine() const {
return label()->GetMultiLine();
}
void Checkbox::SetCheckedIconImageColor(SkColor color) {
checked_icon_image_color_ = color;
}
gfx::ImageSkia Checkbox::GetImage(ButtonState for_state) const {
const int icon_state = GetIconState(for_state);
const SkColor container_color = GetIconImageColor(icon_state);
if (GetChecked()) {
const gfx::ImageSkia check_icon = gfx::CreateVectorIcon(
GetVectorIcon(), kCheckboxIconDipSize, GetIconCheckColor(icon_state));
return gfx::ImageSkiaOperations::CreateImageWithRoundRectBackground(
gfx::SizeF(kCheckboxIconDipSize, kCheckboxIconDipSize),
kCheckboxIconCornerRadius, container_color, check_icon);
}
return gfx::CreateVectorIcon(GetVectorIcon(), kCheckboxIconDipSize,
container_color);
}
std::unique_ptr<LabelButtonBorder> Checkbox::CreateDefaultBorder() const {
std::unique_ptr<LabelButtonBorder> border =
LabelButton::CreateDefaultBorder();
border->set_insets(
LayoutProvider::Get()->GetInsetsMetric(INSETS_CHECKBOX_RADIO_BUTTON));
return border;
}
std::unique_ptr<ActionViewInterface> Checkbox::GetActionViewInterface() {
return std::make_unique<CheckboxActionViewInterface>(this);
}
void Checkbox::UpdateAccessibleCheckedState() {
GetViewAccessibility().SetCheckedState(GetChecked()
? ax::mojom::CheckedState::kTrue
: ax::mojom::CheckedState::kFalse);
}
void Checkbox::OnThemeChanged() {
LabelButton::OnThemeChanged();
}
SkPath Checkbox::GetFocusRingPath() const {
gfx::Rect bounds = image_container_view()->GetMirroredContentsBounds();
return SkPath::Rect(RectToSkRect(bounds));
}
SkColor Checkbox::GetIconImageColor(int icon_state) const {
if (icon_state & IconState::CHECKED) {
return GetColorProvider()->GetColor(
(icon_state & IconState::ENABLED)
? ui::kColorCheckboxContainer
: ui::kColorCheckboxContainerDisabled);
}
return GetColorProvider()->GetColor((icon_state & IconState::ENABLED)
? ui::kColorCheckboxOutline
: ui::kColorCheckboxOutlineDisabled);
}
SkColor Checkbox::GetIconCheckColor(int icon_state) const {
DCHECK(GetChecked());
// Use the overridden checked icon image color instead if set.
if (checked_icon_image_color_.has_value()) {
return checked_icon_image_color_.value();
}
return GetColorProvider()->GetColor((icon_state & IconState::ENABLED)
? ui::kColorCheckboxCheck
: ui::kColorCheckboxCheckDisabled);
}
const gfx::VectorIcon& Checkbox::GetVectorIcon() const {
return GetChecked() ? kCheckboxCheckCr2023Icon : kCheckboxNormalCr2023Icon;
}
int Checkbox::GetIconState(ButtonState for_state) const {
int icon_state = 0;
if (GetChecked()) {
icon_state |= IconState::CHECKED;
}
if (for_state != STATE_DISABLED) {
icon_state |= IconState::ENABLED;
}
return icon_state;
}
void Checkbox::NotifyClick(const ui::Event& event) {
SetChecked(!GetChecked());
LabelButton::NotifyClick(event);
}
ui::NativeTheme::Part Checkbox::GetThemePart() const {
return ui::NativeTheme::kCheckbox;
}
void Checkbox::GetExtraParams(ui::NativeTheme::ExtraParams* params) const {
LabelButton::GetExtraParams(params);
std::get<ui::NativeTheme::ButtonExtraParams>(*params).checked = GetChecked();
}
void Checkbox::SetAndUpdateAccessibleDefaultActionVerb() {
SetDefaultActionVerb(checked_ ? ax::mojom::DefaultActionVerb::kUncheck
: ax::mojom::DefaultActionVerb::kCheck);
UpdateAccessibleDefaultActionVerb();
}
CheckboxActionViewInterface::CheckboxActionViewInterface(Checkbox* action_view)
: LabelButtonActionViewInterface(action_view), action_view_(action_view) {}
void CheckboxActionViewInterface::ActionItemChangedImpl(
actions::ActionItem* action_item) {
LabelButtonActionViewInterface::ActionItemChangedImpl(action_item);
action_view_->SetChecked(action_item->GetChecked());
}
void CheckboxActionViewInterface::OnViewChangedImpl(
actions::ActionItem* action_item) {
LabelButtonActionViewInterface::OnViewChangedImpl(action_item);
// The checked property is tied together for all checkboxes that are linked to
// the same ActionItem.
action_item->SetChecked(action_view_->GetChecked());
}
BEGIN_METADATA(Checkbox)
ADD_PROPERTY_METADATA(bool, Checked)
ADD_PROPERTY_METADATA(bool, MultiLine)
END_METADATA
} // namespace views