// Copyright (c) 2012 The Chromium Authors. All rights reserved. // 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 #include #include #include "base/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/gfx/canvas.h" #include "ui/gfx/color_palette.h" #include "ui/gfx/color_utils.h" #include "ui/gfx/paint_vector_icon.h" #include "ui/gfx/skia_util.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(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()); // 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::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 Checkbox::CreateDefaultBorder() const { std::unique_ptr border = LabelButton::CreateDefaultBorder(); border->set_insets( LayoutProvider::Get()->GetInsetsMetric(INSETS_CHECKBOX_RADIO_BUTTON)); return border; } void Checkbox::OnThemeChanged() { LabelButton::OnThemeChanged(); UpdateImage(); } SkPath Checkbox::GetFocusRingPath() const { SkPath path; gfx::Rect bounds = image()->GetMirroredContentsBounds(); bounds.Inset(1, 1); path.addRect(RectToSkRect(bounds)); return path; } SkColor Checkbox::GetIconImageColor(int icon_state) const { const SkColor active_color = GetNativeTheme()->GetSystemColor( (icon_state & IconState::CHECKED) ? ui::NativeTheme::kColorId_ButtonCheckedColor : ui::NativeTheme::kColorId_ButtonUncheckedColor); 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