blob: 8728290cdc75ff376952effdb71c0dc2b06a4c3d [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 "base/functional/bind.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/skia_conversions.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_impl.h"
#include "ui/views/animation/ink_drop_ripple.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 {
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()->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()->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);
}
Checkbox::~Checkbox() = default;
void Checkbox::SetChecked(bool checked) {
if (GetChecked() == checked)
return;
checked_ = checked;
NotifyAccessibilityEvent(ax::mojom::Event::kCheckedStateChanged, true);
UpdateImage();
OnPropertyChanged(&checked_, kPropertyEffectsNone);
}
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::SetAssociatedLabel(View* labelling_view) {
DCHECK(labelling_view);
GetViewAccessibility().OverrideLabelledBy(labelling_view);
ui::AXNodeData node_data;
labelling_view->GetAccessibleNodeData(&node_data);
// Labelled-by relations are not common practice in native UI, so we also
// set the checkbox accessible name for ATs which don't support that.
// TODO(aleventhal) automatically handle setting the name from the related
// label in ViewAccessibility and have it update the name if the text of the
// associated label changes.
SetAccessibleName(
node_data.GetString16Attribute(ax::mojom::StringAttribute::kName));
}
void Checkbox::SetCheckedIconImageColor(SkColor color) {
checked_icon_image_color_ = color;
}
void Checkbox::GetAccessibleNodeData(ui::AXNodeData* node_data) {
LabelButton::GetAccessibleNodeData(node_data);
node_data->role = ax::mojom::Role::kCheckBox;
const ax::mojom::CheckedState checked_state =
GetChecked() ? ax::mojom::CheckedState::kTrue
: ax::mojom::CheckedState::kFalse;
node_data->SetCheckedState(checked_state);
if (GetEnabled()) {
node_data->SetDefaultActionVerb(GetChecked()
? ax::mojom::DefaultActionVerb::kUncheck
: ax::mojom::DefaultActionVerb::kCheck);
}
}
gfx::ImageSkia Checkbox::GetImage(ButtonState for_state) const {
int icon_state = 0;
if (GetChecked())
icon_state |= IconState::CHECKED;
if (for_state != STATE_DISABLED)
icon_state |= IconState::ENABLED;
return gfx::CreateVectorIcon(GetVectorIcon(), 16,
GetIconImageColor(icon_state));
}
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;
}
void Checkbox::OnThemeChanged() {
LabelButton::OnThemeChanged();
}
SkPath Checkbox::GetFocusRingPath() const {
SkPath path;
gfx::Rect bounds = image()->GetMirroredContentsBounds();
bounds.Inset(1);
path.addRect(RectToSkRect(bounds));
return path;
}
SkColor Checkbox::GetIconImageColor(int icon_state) const {
SkColor active_color = GetColorProvider()->GetColor(
(icon_state & IconState::CHECKED) ? ui::kColorButtonForegroundChecked
: ui::kColorButtonForegroundUnchecked);
// TODO(crbug.com/1394575): Remove block and update the above ColorIds
if (features::IsChromeRefresh2023()) {
active_color = GetColorProvider()->GetColor(
(icon_state & IconState::CHECKED) ? ui::kColorAlertHighSeverity
: ui::kColorAlertMediumSeverity);
}
// Use the overridden checked icon image color instead if set.
if (icon_state & IconState::CHECKED && checked_icon_image_color_.has_value())
active_color = checked_icon_image_color_.value();
return (icon_state & IconState::ENABLED)
? active_color
: color_utils::BlendTowardMaxContrast(active_color,
gfx::kDisabledControlAlpha);
}
const gfx::VectorIcon& Checkbox::GetVectorIcon() const {
return GetChecked() ? kCheckboxActiveIcon : kCheckboxNormalIcon;
}
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);
params->button.checked = GetChecked();
}
BEGIN_METADATA(Checkbox, LabelButton)
ADD_PROPERTY_METADATA(bool, Checked)
ADD_PROPERTY_METADATA(bool, MultiLine)
END_METADATA
} // namespace views