blob: f4a6cf30e32e4441dd50ac270576116f0760066e [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/radio_button.h"
#include <algorithm>
#include "base/auto_reset.h"
#include "base/check.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_enums.mojom.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/events/event_utils.h"
#include "ui/gfx/canvas.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/cascading_property.h"
#include "ui/views/controls/focus_ring.h"
#include "ui/views/resources/grit/views_resources.h"
#include "ui/views/vector_icons.h"
#include "ui/views/view_utils.h"
#include "ui/views/widget/widget.h"
namespace views {
namespace {
constexpr int kFocusRingRadius = 16;
constexpr int kRadioButtonIconDipSizeCr2023 = 20;
} // namespace
RadioButton::RadioButton(const std::u16string& label, int group_id)
: Checkbox(label) {
SetGroup(group_id);
views::FocusRing::Get(this)->SetOutsetFocusRingDisabled(true);
GetViewAccessibility().SetRole(ax::mojom::Role::kRadioButton);
}
RadioButton::~RadioButton() = default;
View* RadioButton::GetSelectedViewForGroup(int group) {
Views views;
GetViewsInGroupFromParent(group, &views);
const auto i = std::ranges::find_if(views, [](const views::View* view) {
// Why don't we check the runtime type like is done in SetChecked()?
return static_cast<const RadioButton*>(view)->GetChecked();
});
return (i == views.cend()) ? nullptr : *i;
}
bool RadioButton::HandleAccessibleAction(const ui::AXActionData& action_data) {
if (action_data.action == ax::mojom::Action::kFocus) {
if (GetViewAccessibility().IsAccessibilityFocusable()) {
base::AutoReset<bool> reset(&select_on_focus_, false);
RequestFocus();
return true;
}
}
return Checkbox::HandleAccessibleAction(action_data);
}
bool RadioButton::IsGroupFocusTraversable() const {
// When focusing a radio button with tab/shift+tab, only the selected button
// from the group should be focused.
return false;
}
void RadioButton::OnFocus() {
Checkbox::OnFocus();
if (select_on_focus_) {
SetChecked(true);
}
}
void RadioButton::OnThemeChanged() {
Checkbox::OnThemeChanged();
SchedulePaint();
}
void RadioButton::RequestFocusFromEvent() {
Checkbox::RequestFocusFromEvent();
// Take focus only if another radio button in the group has focus.
Views views;
GetViewsInGroupFromParent(GetGroup(), &views);
if (std::ranges::any_of(views, [](View* v) { return v->HasFocus(); })) {
RequestFocus();
}
}
void RadioButton::NotifyClick(const ui::Event& event) {
// Set the checked state to true only if we are unchecked, since we can't
// be toggled on and off like a checkbox.
if (!GetChecked()) {
SetChecked(true);
}
LabelButton::NotifyClick(event);
}
ui::NativeTheme::Part RadioButton::GetThemePart() const {
return ui::NativeTheme::kRadio;
}
void RadioButton::SetChecked(bool checked) {
if (checked == RadioButton::GetChecked()) {
return;
}
if (checked) {
// We can't start from the root view because other parts of the UI might use
// radio buttons with the same group. This can happen when re-using the same
// component or even if views want to use the group for a different purpose.
Views other;
GetViewsInGroupFromParent(GetGroup(), &other);
for (views::View* peer : other) {
if (peer != this) {
DCHECK(IsViewClass<RadioButton>(peer))
<< "radio-button-nt has same group as non radio-button-nt views.";
static_cast<RadioButton*>(peer)->SetChecked(false);
}
}
}
Checkbox::SetChecked(checked);
}
const gfx::VectorIcon& RadioButton::GetVectorIcon() const {
return GetChecked() ? kRadioButtonActiveIcon : kRadioButtonNormalIcon;
}
gfx::ImageSkia RadioButton::GetImage(ButtonState for_state) const {
return gfx::CreateVectorIcon(GetVectorIcon(), kRadioButtonIconDipSizeCr2023,
GetIconImageColor(GetIconState(for_state)));
}
SkPath RadioButton::GetFocusRingPath() const {
SkPath path;
const gfx::Point center =
image_container_view()->GetMirroredBounds().CenterPoint();
path.addCircle(center.x(), center.y(), kFocusRingRadius);
return path;
}
void RadioButton::GetViewsInGroupFromParent(int group, Views* views) {
if (parent()) {
if (View* parent_group_view = GetCascadingRadioGroupView(parent())) {
parent_group_view->GetViewsInGroup(group, views);
} else {
parent()->GetViewsInGroup(group, views);
}
}
}
BEGIN_METADATA(RadioButton)
END_METADATA
} // namespace views