910e62b5创建于 1月15日历史提交
// 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 {
  const gfx::Point center =
      image_container_view()->GetMirroredBounds().CenterPoint();

  return SkPath::Circle(center.x(), center.y(), kFocusRingRadius);
}

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