blob: 82b3224762d8ac02e80dde9c93ec1cac1dec4ec6 [file] [log] [blame]
// 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/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/controls/focus_ring.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)
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_