910e62b5创建于 1月15日历史提交
// Copyright 2011 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef UI_VIEWS_CONTROLS_BUTTON_BUTTON_H_
#define UI_VIEWS_CONTROLS_BUTTON_BUTTON_H_

#include <memory>
#include <optional>
#include <utility>
#include <variant>

#include "base/callback_list.h"
#include "base/functional/bind.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/safety_checks.h"
#include "base/memory/weak_ptr.h"
#include "build/build_config.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/actions/actions.h"
#include "ui/base/metadata/metadata_types.h"
#include "ui/base/mojom/menu_source_type.mojom-forward.h"
#include "ui/events/event_constants.h"
#include "ui/gfx/animation/throb_animation.h"
#include "ui/native_theme/native_theme.h"
#include "ui/views/animation/animation_delegate_views.h"
#include "ui/views/animation/ink_drop_host.h"
#include "ui/views/animation/ink_drop_state.h"
#include "ui/views/controls/button/button_controller_delegate.h"
#include "ui/views/metadata/view_factory.h"
#include "ui/views/painter.h"
#include "ui/views/view.h"

namespace ui {
class Event;
}  // namespace ui

namespace views {

namespace test {
class ButtonTestApi;
}

class Button;
class ButtonController;

// A View representing a button. A Button is focusable by default and will
// be part of the focus chain.
class VIEWS_EXPORT Button : public View, public AnimationDelegateViews {
  METADATA_HEADER(Button, View)

  // TODO(crbug.com/451373711): Remove this macro once the bug gets fixed.
  ADVANCED_MEMORY_SAFETY_CHECKS();

 public:
  // Button states for various button sub-types.
  enum ButtonState {
    STATE_NORMAL = 0,
    STATE_HOVERED,
    STATE_PRESSED,
    STATE_DISABLED,
    STATE_COUNT,
  };

  // An enum describing the events on which a button should be clicked for a
  // given key event.
  enum class KeyClickAction {
    kOnKeyPress,
    kOnKeyRelease,
    kNone,
  };

  // TODO(cyan): Consider having Button implement ButtonControllerDelegate.
  class VIEWS_EXPORT DefaultButtonControllerDelegate
      : public ButtonControllerDelegate {
   public:
    explicit DefaultButtonControllerDelegate(Button* button);

    DefaultButtonControllerDelegate(const DefaultButtonControllerDelegate&) =
        delete;
    DefaultButtonControllerDelegate& operator=(
        const DefaultButtonControllerDelegate&) = delete;

    ~DefaultButtonControllerDelegate() override;

    // views::ButtonControllerDelegate:
    void RequestFocusFromEvent() override;
    void NotifyClick(const ui::Event& event) override;
    void OnClickCanceled(const ui::Event& event) override;
    bool IsTriggerableEvent(const ui::Event& event) override;
    bool ShouldEnterPushedState(const ui::Event& event) override;
    bool ShouldEnterHoveredState() override;
    InkDrop* GetInkDrop() override;
    int GetDragOperations(const gfx::Point& press_pt) override;
    bool InDrag() override;
  };

  // PressedCallback wraps a one-arg callback type with multiple constructors to
  // allow callers to specify a OnceClosure or RepeatingClosure if they don't
  // care about the callback arg.
  //
  // TODO(crbug.com/41348855): Re-evaluate if this class can/should be converted
  // to a type alias + various helpers or overloads to support the
  // RepeatingClosure case.
  class VIEWS_EXPORT PressedCallback {
   public:
    using Callback = base::RepeatingCallback<void(const ui::Event& event)>;

    // Allow providing callbacks that expect either zero or one args, since many
    // callers don't care about the argument and can avoid adapter functions
    // this way.
    PressedCallback(base::OnceClosure closure);       // NOLINT
    PressedCallback(Callback callback = Callback());  // NOLINT
    PressedCallback(base::RepeatingClosure closure);  // NOLINT
    PressedCallback(PressedCallback&&);
    PressedCallback& operator=(PressedCallback&&);
    ~PressedCallback();

    // Returns true if `callback_` holds a non-null callback, regardless if the
    // callback is once or repeating.
    explicit operator bool() const;

    // Precondition:
    // `operator bool()` must be true (i.e. callback_ must be a non-null
    // callback).
    //
    // Postcondition:
    // If `callback_` holds a `base::OnceClosure`, `operator bool()` will be
    // false.
    void Run(const ui::Event& event);

   private:
    std::variant<base::OnceClosure, base::RepeatingClosure, Callback> callback_;
  };

  // This is used to ensure that multiple overlapping elements anchored on this
  // button correctly handle highlighting.
  class VIEWS_EXPORT ScopedAnchorHighlight {
   public:
    explicit ScopedAnchorHighlight(base::WeakPtr<Button> button);
    ~ScopedAnchorHighlight();

    ScopedAnchorHighlight(ScopedAnchorHighlight&&);
    ScopedAnchorHighlight& operator=(ScopedAnchorHighlight&&);

   private:
    base::WeakPtr<Button> button_;
  };

  static constexpr ButtonState kButtonStates[STATE_COUNT] = {
      ButtonState::STATE_NORMAL, ButtonState::STATE_HOVERED,
      ButtonState::STATE_PRESSED, ButtonState::STATE_DISABLED};

  Button(const Button&) = delete;
  Button& operator=(const Button&) = delete;

  ~Button() override;

  static const Button* AsButton(const View* view);
  static Button* AsButton(View* view);

  static ButtonState GetButtonStateFrom(ui::NativeTheme::State state);

  // Tag is now a property. These accessors are deprecated. Use GetTag() and
  // SetTag() below or even better, use SetID()/GetID() from the ancestor.
  int tag() const { return tag_; }
  void set_tag(int tag) { tag_ = tag; }

  virtual void SetCallback(PressedCallback callback);

  void AdjustAccessibleName(std::u16string& new_name,
                            ax::mojom::NameFrom& name_from) override;

  // Button uses the tooltip text in `AdjustAccessibleName` to provide an
  // alternative accessible name if there is no existing accessible name.
  // However, some button subclasses have a custom locally cached tooltip text
  // that should be used instead. Views that follow this pattern should override
  // this method to provide an alternative accessible name if they cache a
  // custom tooltip text that is different from the one cached in View.
  virtual std::u16string GetAlternativeAccessibleName() const;

  // Get/sets the current display state of the button.
  ButtonState GetState() const;
  // Clients passing in STATE_DISABLED should consider calling
  // SetEnabled(false) instead because the enabled flag can affect other things
  // like event dispatching, focus traversals, etc. Calling SetEnabled(false)
  // will also set the state of |this| to STATE_DISABLED.
  void SetState(ButtonState state);

  // Set how long the hover animation will last for.
  void SetAnimationDuration(base::TimeDelta duration);

  void SetTriggerableEventFlags(int triggerable_event_flags);
  int GetTriggerableEventFlags() const;

  // Sets whether |RequestFocus| should be invoked on a mouse press. The default
  // is false.
  void SetRequestFocusOnPress(bool value);
  bool GetRequestFocusOnPress() const;

  // See description above field.
  void SetAnimateOnStateChange(bool value);
  bool GetAnimateOnStateChange() const;

  void SetHideInkDropWhenShowingContextMenu(bool value);
  bool GetHideInkDropWhenShowingContextMenu() const;

  void SetShowInkDropWhenHotTracked(bool value);
  bool GetShowInkDropWhenHotTracked() const;

  void SetHasInkDropActionOnClick(bool value);
  bool GetHasInkDropActionOnClick() const;

  void SetInstallFocusRingOnFocus(bool install_focus_ring_on_focus);
  bool GetInstallFocusRingOnFocus() const;

  void SetHotTracked(bool is_hot_tracked);
  bool IsHotTracked() const;

  // TODO(crbug.com/40801855): These property accessors and tag_ field should be
  // removed and use SetID()/GetID from the ancestor View class.
  void SetTag(int value);
  int GetTag() const;

  void SetFocusPainter(std::unique_ptr<Painter> focus_painter);

  // Highlights the ink drop for the button.
  void SetHighlighted(bool highlighted);

  // Menus, bubbles, and IPH should call this when they anchor. This ensures
  // that highlighting is handled correctly with multiple anchored elements.
  // TODO(crbug.com/40262104): Migrate callers of SetHighlighted to this
  // function, where appropriate.
  ScopedAnchorHighlight AddAnchorHighlight();

  base::CallbackListSubscription AddStateChangedCallback(
      PropertyChangedCallback callback);
  base::CallbackListSubscription AddAnchorCountChangedCallback(
      base::RepeatingCallback<void(size_t)> callback);

  // Overridden from View:
  bool OnMousePressed(const ui::MouseEvent& event) override;
  bool OnMouseDragged(const ui::MouseEvent& event) override;
  void OnMouseReleased(const ui::MouseEvent& event) override;
  void OnMouseCaptureLost() override;
  void OnMouseEntered(const ui::MouseEvent& event) override;
  void OnMouseExited(const ui::MouseEvent& event) override;
  void OnMouseMoved(const ui::MouseEvent& event) override;
  bool OnKeyPressed(const ui::KeyEvent& event) override;
  bool OnKeyReleased(const ui::KeyEvent& event) override;
  void OnGestureEvent(ui::GestureEvent* event) override;
  bool AcceleratorPressed(const ui::Accelerator& accelerator) override;
  bool SkipDefaultKeyEventProcessing(const ui::KeyEvent& event) override;
  void ShowContextMenu(const gfx::Point& p,
                       ui::mojom::MenuSourceType source_type) override;
  void OnDragDone() override;
  // Instead of overriding this, subclasses that want custom painting should use
  // PaintButtonContents.
  void OnPaint(gfx::Canvas* canvas) final;
  void VisibilityChanged(View* starting_from, bool is_visible) override;
  void ViewHierarchyChanged(
      const ViewHierarchyChangedDetails& details) override;
  void OnFocus() override;
  void OnBlur() override;
  std::unique_ptr<ActionViewInterface> GetActionViewInterface() override;

  // Overridden from views::AnimationDelegateViews:
  void AnimationProgressed(const gfx::Animation* animation) override;

  // Returns the click action for the given key event.
  // Subclasses may override this method to support default actions for key
  // events.
  // TODO(cyan): Move this into the ButtonController.
  virtual KeyClickAction GetKeyClickActionForEvent(const ui::KeyEvent& event);

  ButtonController* button_controller() const {
    return button_controller_.get();
  }

  void SetButtonController(std::unique_ptr<ButtonController> button_controller);

  gfx::Point GetMenuPosition() const;

  View* ink_drop_view() const { return ink_drop_view_; }
  void SetInkDropView(View* view);

 protected:
  explicit Button(PressedCallback callback = PressedCallback());

  // Called when the button has been clicked or tapped and should request focus
  // if necessary.
  virtual void RequestFocusFromEvent();

  // Cause the button to notify the listener that a click occurred.
  virtual void NotifyClick(const ui::Event& event);

  // Called when a button gets released without triggering an action.
  // Note: This is only wired up for mouse button events and not gesture
  // events.
  virtual void OnClickCanceled(const ui::Event& event);

  // Called when the tooltip is set.
  virtual void OnSetTooltipText(const std::u16string& tooltip_text);

  void OnTooltipTextChanged(const std::u16string& old_tooltip_text) override;

  // Invoked from SetState() when SetState() is passed a value that differs from
  // the current node_data. Button's implementation of StateChanged() does
  // nothing; this method is provided for subclasses that wish to do something
  // on state changes.
  virtual void StateChanged(ButtonState old_state);

  // Returns true if the event is one that can trigger notifying the listener.
  // This implementation returns true if the left mouse button is down.
  // TODO(cyan): Remove this method and move the implementation into
  // ButtonController.
  virtual bool IsTriggerableEvent(const ui::Event& event);

  // Returns true if the ink drop should be updated by Button when
  // OnClickCanceled() is called. This method is provided for subclasses.
  // If the method is overriden and returns false, the subclass is responsible
  // will be responsible for updating the ink drop.
  virtual bool ShouldUpdateInkDropOnClickCanceled() const;

  // Returns true if the button should become pressed when the user
  // holds the mouse down over the button. For this implementation,
  // we simply return IsTriggerableEvent(event).
  virtual bool ShouldEnterPushedState(const ui::Event& event);

  // Override to paint custom button contents. Any background or border set on
  // the view will be painted before this is called and |focus_painter_| will be
  // painted afterwards.
  virtual void PaintButtonContents(gfx::Canvas* canvas);

  // Returns true if the button should enter hovered state; that is, if the
  // mouse is over the button, and no other window has capture (which would
  // prevent the button from receiving MouseExited events and updating its
  // node_data). This does not take into account enabled node_data.
  bool ShouldEnterHoveredState();

  const gfx::ThrobAnimation& hover_animation() const {
    return hover_animation_;
  }

  // Getter used by metadata only.
  const PressedCallback& GetCallback() const { return callback_; }

  base::WeakPtr<Button> GetWeakPtr();

  virtual void OnEnabledChanged();

  virtual void UpdateAccessibleCheckedState();

  // Sets the |default_action_verb_| for accessibility. Subclasses may
  // call this method to set their specific default action verb.
  void SetDefaultActionVerb(ax::mojom::DefaultActionVerb verb);
  // Called whenever the state impacting default action verb changes.
  void UpdateAccessibleDefaultActionVerb();

 private:
  friend class test::ButtonTestApi;
  friend class ScopedAnchorHighlight;
  FRIEND_TEST_ALL_PREFIXES(BlueButtonTest, Border);

  void ReleaseAnchorHighlight();

  // The button's listener. Notified when clicked.
  PressedCallback callback_;

  // Callbacks called when the anchor count changes.
  base::RepeatingCallbackList<void(size_t)> anchor_count_changed_callbacks_;

  // The id tag associated with this button. Used to disambiguate buttons.
  // TODO(pbos): See if this can be removed, e.g. by replacing with SetID().
  int tag_ = -1;

  ButtonState state_ = STATE_NORMAL;

  gfx::ThrobAnimation hover_animation_{this};

  // Should we animate when the state changes?
  bool animate_on_state_change_ = false;

  // Mouse event flags which can trigger button actions.
  int triggerable_event_flags_ = ui::EF_LEFT_MOUSE_BUTTON;

  // See description above setter.
  bool request_focus_on_press_ = false;

  // True when a button click should trigger an animation action on
  // ink_drop_delegate().
  bool has_ink_drop_action_on_click_ = false;

  // When true, the ink drop ripple and hover will be hidden prior to showing
  // the context menu.
  bool hide_ink_drop_when_showing_context_menu_ = true;

  // When true, the ink drop ripple will be shown when setting state to hot
  // tracked with SetHotTracked().
  bool show_ink_drop_when_hot_tracked_ = false;

  // |ink_drop_view_| is generally the button, but can be overridden for special
  // cases (e.g. Checkbox) where the InkDrop may be more appropriately installed
  // on a child view of the button.
  raw_ptr<View> ink_drop_view_ = this;

  std::unique_ptr<Painter> focus_painter_;

  // ButtonController is responsible for handling events sent to the Button and
  // related state changes from the events.
  // TODO(cyan): Make sure all state changes are handled within
  // ButtonController.
  std::unique_ptr<ButtonController> button_controller_;

  base::CallbackListSubscription enabled_changed_subscription_{
      AddEnabledInViewsSubtreeChangedCallback(
          base::BindRepeating(&Button::OnEnabledChanged,
                              base::Unretained(this)))};

  size_t anchor_count_ = 0;

  ax::mojom::DefaultActionVerb default_action_verb_ =
      ax::mojom::DefaultActionVerb::kPress;

  base::WeakPtrFactory<Button> weak_ptr_factory_{this};
};

class VIEWS_EXPORT ButtonActionViewInterface : public BaseActionViewInterface {
 public:
  explicit ButtonActionViewInterface(Button* action_view);
  ~ButtonActionViewInterface() override = default;

  // BaseActionViewInterface:
  void ActionItemChangedImpl(actions::ActionItem* action_item) override;
  void LinkActionInvocationToView(
      base::RepeatingClosure invoke_action_callback) override;

 private:
  raw_ptr<Button> action_view_;
};

BEGIN_VIEW_BUILDER(VIEWS_EXPORT, Button, View)
VIEW_BUILDER_PROPERTY(Button::PressedCallback, Callback)
VIEW_BUILDER_PROPERTY(base::TimeDelta, AnimationDuration)
VIEW_BUILDER_PROPERTY(bool, AnimateOnStateChange)
VIEW_BUILDER_PROPERTY(bool, HasInkDropActionOnClick)
VIEW_BUILDER_PROPERTY(bool, HideInkDropWhenShowingContextMenu)
VIEW_BUILDER_PROPERTY(bool, InstallFocusRingOnFocus)
VIEW_BUILDER_PROPERTY(bool, RequestFocusOnPress)
VIEW_BUILDER_PROPERTY(Button::ButtonState, State)
VIEW_BUILDER_PROPERTY(int, Tag)
VIEW_BUILDER_PROPERTY(std::u16string, TooltipText)
VIEW_BUILDER_PROPERTY(int, TriggerableEventFlags)
END_VIEW_BUILDER

}  // namespace views

DEFINE_VIEW_BUILDER(VIEWS_EXPORT, Button)

#endif  // UI_VIEWS_CONTROLS_BUTTON_BUTTON_H_