blob: 019c7fa0989fdfb92b5435ce4d44e0f5fcdf2116 [file] [log] [blame]
// Copyright 2023 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_ACTIONS_ACTIONS_H_
#define UI_ACTIONS_ACTIONS_H_
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "base/callback_list.h"
#include "base/component_export.h"
#include "base/containers/flat_map.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "ui/actions/action_id.h"
#include "ui/actions/action_utils.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/base/class_property.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/models/image_model.h"
#include "ui/events/event.h"
namespace actions {
class ActionItem;
using ActionListVector = std::vector<std::unique_ptr<ActionItem>>;
using ActionItemVector = std::vector<raw_ptr<ActionItem, VectorExperimental>>;
class COMPONENT_EXPORT(ACTIONS) ActionList {
public:
class Delegate {
public:
virtual ~Delegate() = default;
virtual void ActionListChanged() = 0;
};
explicit ActionList(Delegate* delegate);
~ActionList();
const ActionListVector& children() const { return children_; }
bool empty() const { return children_.empty(); }
ActionItem* AddAction(std::unique_ptr<ActionItem> action_item);
std::unique_ptr<ActionItem> RemoveAction(ActionItem* action_item);
// Clear the action list vector
void Reset();
private:
ActionListVector children_;
raw_ptr<Delegate> delegate_;
};
class COMPONENT_EXPORT(ACTIONS) BaseAction
: public ui::metadata::MetaDataProvider,
public ActionList::Delegate,
public ui::PropertyHandler {
public:
METADATA_HEADER_BASE(BaseAction);
BaseAction();
BaseAction(const BaseAction&) = delete;
BaseAction& operator=(const BaseAction&) = delete;
~BaseAction() override;
BaseAction* GetParent() const;
ActionItem* AddChild(std::unique_ptr<ActionItem> action_item);
std::unique_ptr<ActionItem> RemoveChild(ActionItem* action_item);
const ActionList& GetChildren() const { return children_; }
void ResetActionList();
protected:
void ActionListChanged() override;
private:
raw_ptr<BaseAction> parent_ = nullptr;
ActionList children_{this};
};
// Class returned from ActionItem::BeginUpdate() in order to allow a "batch"
// update of the ActionItem state without triggering ActionChanged callbacks
// for each state change. Will trigger one update once the instance goes out of
// scope, assuming any changes were actually made.
class COMPONENT_EXPORT(ACTIONS) ScopedActionUpdate {
public:
explicit ScopedActionUpdate(ActionItem* action_item);
ScopedActionUpdate(ScopedActionUpdate&& scoped_action_update);
ScopedActionUpdate& operator=(ScopedActionUpdate&& scoped_action_update);
~ScopedActionUpdate();
private:
raw_ptr<ActionItem> action_item_;
};
// Context object designed to allow any class property to be attached to it.
// This allows invoking the action with any additional contextual information
// without requiring the action item itself have any knowledge of that
// information.
class COMPONENT_EXPORT(ACTIONS) ActionInvocationContext
: public ui::PropertyHandler {
public:
ActionInvocationContext();
ActionInvocationContext(ActionInvocationContext&&);
ActionInvocationContext& operator=(ActionInvocationContext&&);
~ActionInvocationContext() override;
class COMPONENT_EXPORT(ACTIONS) ContextBuilder {
public:
ContextBuilder(ContextBuilder&&);
ContextBuilder& operator=(ContextBuilder&&);
~ContextBuilder();
template <typename T>
ContextBuilder&& SetProperty(const ui::ClassProperty<T>* property,
ui::metadata::ArgType<T> value) && {
context_->SetProperty(property, value);
return std::move(*this);
}
[[nodiscard]] ActionInvocationContext Build() &&;
private:
friend class ActionInvocationContext;
ContextBuilder();
std::unique_ptr<ActionInvocationContext> context_ =
std::make_unique<ActionInvocationContext>();
};
static ContextBuilder Builder();
};
template <typename BuilderT, typename ActionItemClass>
class BaseActionItemBuilderT {
public:
using ChildList = std::vector<std::unique_ptr<BuilderT>>;
using ActionChangedCallback = ui::metadata::PropertyChangedCallback;
using InvokeActionCallback =
base::RepeatingCallback<void(ActionItem*, ActionInvocationContext)>;
BaseActionItemBuilderT() {
action_item_ = std::make_unique<ActionItemClass>();
}
explicit BaseActionItemBuilderT(InvokeActionCallback callback) {
action_item_ = std::make_unique<ActionItemClass>(std::move(callback));
}
BaseActionItemBuilderT(BaseActionItemBuilderT&&) = default;
BaseActionItemBuilderT& operator=(BaseActionItemBuilderT&&) = default;
~BaseActionItemBuilderT() = default;
// Build an action.
static BuilderT Builder(InvokeActionCallback callback) {
return BuilderT(std::move(callback));
}
static BuilderT Builder() { return BuilderT(); }
BuilderT& AddChild(BuilderT&& child_item) & {
children_.emplace_back(child_item.Release());
return static_cast<BuilderT&>(*this);
}
BuilderT&& AddChild(BuilderT&& child_item) && {
return std::move(this->AddChild(std::move(child_item)));
}
template <typename Child, typename... Types>
BuilderT& AddChildren(Child&& child, Types&&... args) & {
return AddChildrenImpl(&child, &args...);
}
template <typename Child, typename... Types>
BuilderT&& AddChildren(Child&& child, Types&&... args) && {
return std::move(this->AddChildrenImpl(&child, &args...));
}
template <typename ActionPtr>
BuilderT& CopyAddressTo(ActionPtr* action_address) & {
*action_address = action_item_.get();
return static_cast<BuilderT&>(*this);
}
template <typename ActionPtr>
BuilderT&& CopyAddressTo(ActionPtr* action_address) && {
return std::move(this->CopyAddressTo(action_address));
}
template <typename Action>
BuilderT& CopyWeakPtrTo(base::WeakPtr<Action>* weak_ptr) & {
*weak_ptr = action_item_->GetAsWeakPtr();
return static_cast<BuilderT&>(*this);
}
template <typename Action>
BuilderT&& CopyWeakPtrTo(base::WeakPtr<Action>* weak_ptr) && {
return std::move(this->CopyWeakPtrTo(weak_ptr));
}
template <typename T>
BuilderT& SetProperty(const ui::ClassProperty<T>* property,
ui::metadata::ArgType<T> value) & {
action_item_->SetProperty(property, value);
return static_cast<BuilderT&>(*this);
}
template <typename T>
BuilderT&& SetProperty(const ui::ClassProperty<T>* property,
ui::metadata::ArgType<T> value) && {
return std::move(this->SetProperty(property, value));
}
BuilderT& SetAccessibleName(const std::u16string accessible_name) & {
action_item_->SetAccessibleName(accessible_name);
return static_cast<BuilderT&>(*this);
}
BuilderT&& SetAccessibleName(const std::u16string accessible_name) && {
return std::move(this->SetAccessibleName(accessible_name));
}
BuilderT& SetActionId(std::optional<ActionId> action_id) & {
action_item_->SetActionId(action_id);
return static_cast<BuilderT&>(*this);
}
BuilderT&& SetActionId(std::optional<ActionId> action_id) && {
return std::move(this->SetActionId(action_id));
}
BuilderT& SetAccelerator(ui::Accelerator accelerator) & {
action_item_->SetAccelerator(accelerator);
return static_cast<BuilderT&>(*this);
}
BuilderT&& SetAccelerator(ui::Accelerator accelerator) && {
return std::move(this->SetAccelerator(accelerator));
}
BuilderT& SetChecked(bool checked) & {
action_item_->SetChecked(checked);
return static_cast<BuilderT&>(*this);
}
BuilderT&& SetChecked(bool checked) && {
return std::move(this->SetChecked(checked));
}
BuilderT& SetEnabled(bool enabled) & {
action_item_->SetEnabled(enabled);
return static_cast<BuilderT&>(*this);
}
BuilderT&& SetEnabled(bool enabled) && {
return std::move(this->SetEnabled(enabled));
}
BuilderT& SetGroupId(std::optional<int> group_id) & {
action_item_->SetGroupId(group_id);
return static_cast<BuilderT&>(*this);
}
BuilderT&& SetGroupId(std::optional<int> group_id) && {
return std::move(this->SetGroupId(group_id));
}
BuilderT& SetImage(const ui::ImageModel& image) & {
action_item_->SetImage(image);
return static_cast<BuilderT&>(*this);
}
BuilderT&& SetImage(const ui::ImageModel& image) && {
return std::move(this->SetImage(image));
}
BuilderT& SetText(std::u16string_view text) & {
action_item_->SetText(text);
return static_cast<BuilderT&>(*this);
}
BuilderT&& SetText(std::u16string_view text) && {
return std::move(this->SetText(text));
}
BuilderT& SetTooltipText(std::u16string_view tooltip) & {
action_item_->SetTooltipText(tooltip);
return static_cast<BuilderT&>(*this);
}
BuilderT&& SetTooltipText(std::u16string_view tooltip) && {
return std::move(this->SetTooltipText(tooltip));
}
BuilderT& SetVisible(bool visible) & {
action_item_->SetVisible(visible);
return static_cast<BuilderT&>(*this);
}
BuilderT&& SetVisible(bool visible) && {
return std::move(this->SetVisible(visible));
}
BuilderT& SetInvokeActionCallback(InvokeActionCallback callback) & {
action_item_->SetInvokeActionCallback(std::move(callback));
return static_cast<BuilderT&>(*this);
}
BuilderT&& SetInvokeActionCallback(InvokeActionCallback callback) && {
return std::move(this->SetInvokeActionCallback(std::move(callback)));
}
BuilderT& SetIsShowingBubble(bool showing_bubble) & {
action_item_->SetIsShowingBubble(showing_bubble);
return static_cast<BuilderT&>(*this);
}
BuilderT&& SetIsShowingBubble(bool showing_bubble) && {
return std::move(this->SetIsShowingBubble(showing_bubble));
}
[[nodiscard]] std::unique_ptr<ActionItemClass> Build() && {
CreateChildren();
return std::move(action_item_);
}
protected:
template <typename... Args>
BuilderT& AddChildrenImpl(Args*... args) & {
std::vector<BuilderT*> children = {args...};
for (auto* child : children) {
children_.emplace_back(child->Release());
}
return static_cast<BuilderT&>(*this);
}
void CreateChildren() {
for (auto& child : children_) {
action_item_->AddChild(std::move(*child).Build());
}
}
[[nodiscard]] std::unique_ptr<BuilderT> Release() {
return std::make_unique<BuilderT>(std::move(static_cast<BuilderT&>(*this)));
}
// Owned and meaningful during the Builder building process. Its
// ownership will be transferred out upon Build() call.
std::unique_ptr<ActionItemClass> action_item_;
ChildList children_;
};
class COMPONENT_EXPORT(ACTIONS) ActionItem : public BaseAction {
METADATA_HEADER(ActionItem, BaseAction)
public:
using ActionChangedCallback = ui::metadata::PropertyChangedCallback;
using InvokeActionCallback =
base::RepeatingCallback<void(ActionItem*, ActionInvocationContext)>;
class COMPONENT_EXPORT(ACTIONS) ActionItemBuilder
: public BaseActionItemBuilderT<ActionItemBuilder, ActionItem> {
// TODO: possibly construct a Core class of
// ctors to avoid writing this in every derived
// class.
using BaseActionItemBuilderT::BaseActionItemBuilderT;
};
ActionItem();
explicit ActionItem(InvokeActionCallback callback);
ActionItem(const ActionItem&) = delete;
ActionItem& operator=(const ActionItem&) = delete;
~ActionItem() override;
// Build an action.
static ActionItemBuilder Builder() { return ActionItemBuilder(); }
static ActionItemBuilder Builder(InvokeActionCallback callback) {
return ActionItemBuilder(std::move(callback));
}
// Configure action states and attributes.
std::u16string_view GetAccessibleName() const;
void SetAccessibleName(std::u16string_view accessible_name);
std::optional<ActionId> GetActionId() const;
void SetActionId(std::optional<ActionId> action_id);
ui::Accelerator GetAccelerator() const;
void SetAccelerator(ui::Accelerator accelerator);
bool GetChecked() const;
void SetChecked(bool checked);
bool GetEnabled() const;
void SetEnabled(bool enabled);
std::optional<int> GetGroupId() const;
void SetGroupId(std::optional<int> group_id);
const ui::ImageModel& GetImage() const;
void SetImage(const ui::ImageModel& image);
std::u16string_view GetText() const;
void SetText(std::u16string_view text);
std::u16string_view GetTooltipText() const;
void SetTooltipText(std::u16string_view tooltip);
bool GetVisible() const;
void SetVisible(bool visible);
void SetInvokeActionCallback(InvokeActionCallback callback);
bool GetIsShowingBubble() const;
void SetIsShowingBubble(bool showing_bubble);
[[nodiscard]] base::CallbackListSubscription AddActionChangedCallback(
ActionChangedCallback callback);
// Alternative terms used to identify this action. Used for search indexing.
void AddSynonyms(std::initializer_list<std::u16string> synonyms);
// Do a "batch" update of the ActionItem state without triggering
// ActionChanged callbacks for each state change.
[[nodiscard]] ScopedActionUpdate BeginUpdate();
// ui::PropertyHandler:
void AfterPropertyChange(const void* key, int64_t old_value) override;
// Invoke an action.
void InvokeAction(
ActionInvocationContext context = ActionInvocationContext());
// Get action metrics.
int GetInvokeCount() const;
std::optional<base::TimeTicks> GetLastInvokeTime() const;
base::WeakPtr<ActionItem> GetAsWeakPtr();
protected:
// ActionList::Delegate override.
void ActionListChanged() override;
void ActionItemChanged();
private:
friend class ScopedActionUpdate;
void EndUpdate();
using Synonyms = std::vector<std::u16string>;
// When `updating_` > 0, calling ActionItemChanged() will only record whether
// is item was updated in `updated_`. Once `updating_` returns to 0 and
// `updated_` = true, the ActionChanged callbacks will trigger.
int updating_ = 0;
bool updated_ = false;
std::u16string accessible_name_;
std::optional<ActionId> action_id_;
ui::Accelerator accelerator_;
bool checked_ = false;
bool enabled_ = true;
std::optional<int> group_id_;
bool visible_ = true;
std::u16string text_;
std::u16string tooltip_;
ui::ImageModel image_;
Synonyms synonyms_;
InvokeActionCallback callback_;
int invoke_count_ = 0;
std::optional<base::TimeTicks> last_invoke_time_;
// Represents whether this action is currently showing associated ephemeral
// UI. Pinned action buttons which execute on mouse release won't execute if
// `is_showing_bubble_` was true on mouse press. Used to avoid immediately
// re-triggering actions when mouse press was intended to dismiss their
// ephemeral UI.
// TODO(b/361251892): Rename this to appropriately reflect bubbles that do not
// close on deactivate.
bool is_showing_bubble_ = false;
base::WeakPtrFactory<ActionItem> weak_ptr_factory_{this};
};
// TODO(crbug.com/375261318): Make it so that this ActionItem descendant can
// also be subclassed along with the builder.
// A subclass of ActionItem that has an additional image that reflects the
// current state of the action.
class COMPONENT_EXPORT(ACTIONS) StatefulImageActionItem : public ActionItem {
METADATA_HEADER(StatefulImageActionItem, ActionItem)
public:
using ActionItem::ActionItem;
~StatefulImageActionItem() override;
class StatefulImageActionItemBuilder
: public BaseActionItemBuilderT<StatefulImageActionItemBuilder,
StatefulImageActionItem> {
public:
using BaseActionItemBuilderT::BaseActionItemBuilderT;
StatefulImageActionItemBuilder& SetStatefulImage(
const ui::ImageModel& image) & {
action_item_->SetStatefulImage(image);
return *this;
}
StatefulImageActionItemBuilder&& SetStatefulImage(
const ui::ImageModel& image) && {
return std::move(this->SetStatefulImage(image));
}
};
static StatefulImageActionItemBuilder Builder() {
return StatefulImageActionItemBuilder();
}
static StatefulImageActionItemBuilder Builder(InvokeActionCallback callback) {
return StatefulImageActionItemBuilder(std::move(callback));
}
const ui::ImageModel& GetStatefulImage() const;
void SetStatefulImage(const ui::ImageModel& image);
private:
ui::ImageModel stateful_image_;
};
class COMPONENT_EXPORT(ACTIONS) ActionManager
: public ui::metadata::MetaDataProvider {
public:
METADATA_HEADER_BASE(ActionManager);
using ActionItemInitializerList =
base::RepeatingCallbackList<void(ActionManager*)>;
ActionManager(const ActionManager&) = delete;
ActionManager& operator=(const ActionManager&) = delete;
static ActionManager& Get();
static ActionManager& GetForTesting();
static void ResetForTesting();
void IndexActions();
ActionItem* FindAction(std::u16string term, ActionItem* scope = nullptr);
ActionItem* FindAction(ActionId action_id, ActionItem* scope = nullptr);
ActionItem* FindAction(const ui::KeyEvent& key_event,
ActionItem* scope = nullptr);
void GetActions(ActionItemVector& items, ActionItem* scope = nullptr);
ActionItem* AddAction(std::unique_ptr<ActionItem> action_item);
std::unique_ptr<ActionItem> RemoveAction(ActionItem* action_item);
template <typename Action, typename... Types>
void AddActions(Action&& action, Types&&... args) & {
AddActionsImpl(&action, &args...);
}
// Clears the actions stored in `root_action_parent_`.
void ResetActions();
// Resets the current `initializer_list_`.
void ResetActionItemInitializerList();
// Appends `initializer` to the end of the current `initializer_list_`. If the
// initializers have already been run or actions have already been added to
// the manager, the initializer will be run immediately.
[[nodiscard]] base::CallbackListSubscription AppendActionItemInitializer(
ActionItemInitializerList::CallbackType initializer);
protected:
ActionManager();
~ActionManager() override;
private:
template <typename... Args>
void AddActionsImpl(Args*... args) {
std::vector<std::unique_ptr<ActionItem>*> actions = {args...};
for (auto* action : actions) {
AddAction(std::move(*action));
}
}
ActionItem* FindActionImpl(ActionId action_id, const ActionList& list);
void GetActionsImpl(ActionItem* item, ActionItemVector& items);
// Holds the chain of ActionManager initializer callbacks.
std::unique_ptr<ActionItemInitializerList> initializer_list_;
// All "root" actions are parented to this action.
BaseAction root_action_parent_;
};
class COMPONENT_EXPORT(ACTIONS) ActionIdMap {
public:
using ActionIdToStringMap = base::flat_map<ActionId, std::string>;
using StringToActionIdMap = base::flat_map<std::string, ActionId>;
ActionIdMap(const ActionIdMap&) = delete;
ActionIdMap& operator=(const ActionIdMap&) = delete;
// Searches existing maps for the given ActionId and returns the corresponding
// string if found, otherwise returns an empty string.
static std::optional<std::string> ActionIdToString(const ActionId action_id);
// Searches existing maps for the given string and returns the corresponding
// ActionId if found, otherwise returns kActionsEnd.
static std::optional<ActionId> StringToActionId(
const std::string action_id_string);
static std::vector<std::optional<std::string>> ActionIdsToStrings(
std::vector<ActionId> action_ids);
static std::vector<std::optional<ActionId>> StringsToActionIds(
std::vector<std::string> action_id_strings);
static void AddActionIdToStringMappings(ActionIdToStringMap map);
static void AddStringToActionIdMappings(StringToActionIdMap map);
// The second element in the pair is set to true if a new ActionId is
// created, or false if an ActionId with the given name already exists.
static std::pair<ActionId, bool> CreateActionId(
const std::string& action_name);
static void ResetMapsForTesting();
private:
// Merges `map2` into `map1`.
template <typename T, typename U>
static void MergeMaps(base::flat_map<T, U>& map1, base::flat_map<T, U>& map2);
static std::optional<ActionIdToStringMap>& GetGlobalActionIdToStringMap();
static std::optional<StringToActionIdMap>& GetGlobalStringToActionIdMap();
static ActionIdToStringMap& GetActionIdToStringMap();
static StringToActionIdMap& GetStringToActionIdMap();
};
COMPONENT_EXPORT(ACTIONS)
extern const ui::ClassProperty<
std::underlying_type_t<ActionPinnableState>>* const kActionItemPinnableKey;
} // namespace actions
#endif // UI_ACTIONS_ACTIONS_H_