1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317
|
// 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 CHROME_BROWSER_UI_VIEWS_TOOLBAR_TOOLBAR_CONTROLLER_H_
#define CHROME_BROWSER_UI_VIEWS_TOOLBAR_TOOLBAR_CONTROLLER_H_
#include <variant>
#include <vector>
#include "base/callback_list.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "chrome/browser/ui/browser_element_identifiers.h"
#include "chrome/browser/ui/toolbar/pinned_toolbar/pinned_toolbar_actions_model.h"
#include "chrome/browser/ui/views/toolbar/overflow_button.h"
#include "chrome/browser/ui/views/toolbar/toolbar_button.h"
#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
#include "ui/actions/action_id.h"
#include "ui/views/controls/menu/menu_item_view.h"
#include "ui/views/layout/flex_layout_types.h"
#include "ui/views/layout/proposed_layout.h"
#include "ui/views/view.h"
class Browser;
// Manages toolbar elements' visibility using flex rules. This also owns the
// overflow menu and the logic to generate the menu model. It also listens to
// action item changes and updates the menu as required.
class ToolbarController : public views::MenuDelegate,
public ui::SimpleMenuModel::Delegate,
public PinnedToolbarActionsModel::Observer {
public:
// Manages action-based pinned toolbar elements.
class PinnedActionsDelegate {
public:
virtual actions::ActionItem* GetActionItemFor(actions::ActionId id) = 0;
// Returns true if the corresponding element is hidden.
virtual bool IsOverflowed(actions::ActionId id) = 0;
virtual views::View* GetContainerView() = 0;
// Return true if any buttons should overflow given available size.
virtual bool ShouldAnyButtonsOverflow(gfx::Size available_size) const = 0;
// Returns the ordered list of pinned ActionIds.
virtual const std::vector<actions::ActionId>& PinnedActionIds() const = 0;
protected:
virtual ~PinnedActionsDelegate() = default;
};
// Data structure to store information specifically used to support
// ui::ElementIdentifier as element reference.
struct ElementIdInfo {
explicit ElementIdInfo(ui::ElementIdentifier overflow_identifier,
int menu_text_id,
raw_ptr<const gfx::VectorIcon> menu_icon,
ui::ElementIdentifier activate_identifier,
std::optional<ui::ElementIdentifier>
observed_identifier = std::nullopt);
// The identifier of toolbar element that potentially overflows.
ui::ElementIdentifier overflow_identifier;
// Menu text when the element is overflow to the overflow menu. For
// ActionId-based elements this value is supplied when constructing action
// items.
int menu_text_id;
// Menu item icon. nullptr if this menu item has no icon.
raw_ptr<const gfx::VectorIcon> menu_icon = nullptr;
// The toolbar button to be activated with menu text pressed. This is not
// necessarily the same as the element that overflows. E.g. when the
// overflowed element is kToolbarExtensionsContainerElementId the
// `activate_identifier` should be kExtensionsMenuButtonElementId.
ui::ElementIdentifier activate_identifier;
// Pop out button when `observed_identifier` is shown. End pop out when it's
// hidden.
std::optional<ui::ElementIdentifier> observed_identifier;
};
// Data structure to store information of responsive elements. Supports both
// ui::ElementIdentifier and ActionId as element reference.
struct ResponsiveElementInfo {
// Overflow menu structure:
// -------------------
// | Forward |
// |-----------------|
// | Home | -> section end
// |=================| -> potential separator
// | Reading list |
// |-----------------|
// | Bookmarks | -> section end
// |=================| -> potential separator
// | Labs |
// |-----------------|
// | Cast |
// |-----------------|
// | Media controls |
// |-----------------|
// | Downloads | -> section end
// |=================| -> potential separator
// | Profile |
// |-----------------|
explicit ResponsiveElementInfo(
std::variant<ElementIdInfo, actions::ActionId> overflow_id,
bool is_section_end = false);
ResponsiveElementInfo(const ResponsiveElementInfo&);
~ResponsiveElementInfo();
// The toolbar element that potentially overflows.
std::variant<ElementIdInfo, actions::ActionId> overflow_id;
// True if current element is a section end in overflow menu structure.
bool is_section_end = false;
};
ToolbarController(
const std::vector<ResponsiveElementInfo>& responsive_elements,
const std::vector<ui::ElementIdentifier>& elements_in_overflow_order,
int element_flex_order_start,
views::View* toolbar_container_view,
OverflowButton* overflow_button,
PinnedActionsDelegate* PinnedActionsDelegate,
PinnedToolbarActionsModel* pinned_toolbar_actions_model);
ToolbarController(const ToolbarController&) = delete;
ToolbarController& operator=(const ToolbarController&) = delete;
~ToolbarController() override;
// Handler to pop out `identifier` when `observed_identier` is shown and end
// the pop out when it's hidden. For example, a toolbar button needs to pop
// out when a bubble is anchored to it.
class PopOutHandler {
public:
PopOutHandler(ToolbarController* controller,
ui::ElementContext context,
ui::ElementIdentifier identifier,
ui::ElementIdentifier observed_identifier);
PopOutHandler(const PopOutHandler&) = delete;
PopOutHandler& operator=(const PopOutHandler&) = delete;
virtual ~PopOutHandler();
private:
// Called when element with `observed_identifier` is shown.
void OnElementShown(ui::TrackedElement* element);
// Called when element with `observed_identifier` is hidden.
void OnElementHidden(ui::TrackedElement* element);
const raw_ptr<ToolbarController> controller_;
const ui::ElementIdentifier identifier_;
const ui::ElementIdentifier observed_identifier_;
base::CallbackListSubscription shown_subscription_;
base::CallbackListSubscription hidden_subscription_;
};
// Data structure to store the state of the responsive element. It's used for
// pop out/end pop out.
struct PopOutState {
PopOutState();
PopOutState(const PopOutState&) = delete;
PopOutState& operator=(const PopOutState&) = delete;
~PopOutState();
// The original FlexSpecification.
std::optional<views::FlexSpecification> original_spec;
// The responsive FlexSpecification modified by ToolbarController.
views::FlexSpecification responsive_spec;
// Whether the element is current popped out.
bool is_popped_out = false;
std::unique_ptr<PopOutHandler> handler;
};
// PinnedToolbarActionsModel::Observer
void OnActionsChanged() override;
// Return the default responsive elements list in the toolbar.
static std::vector<ResponsiveElementInfo> GetDefaultResponsiveElements(
Browser* browser);
// Return the element list in desired overflow order. The list should contain
// only the immediate children of toolbar i.e. those managed by
// `toolbar_container_view_` layout manager. For those inside a child
// container (e.g. PinnedToolbarActionsContainer) of `toolbar_container_view_`
// they should have their own overflow order.
static std::vector<ui::ElementIdentifier> GetDefaultOverflowOrder();
// Return the action name from element identifier. Return empty if not found.
static std::string GetActionNameFromElementIdentifier(
std::variant<ui::ElementIdentifier, actions::ActionId> identifier);
// Force the UI element with the identifier to show. Return whether the action
// is successful.
virtual bool PopOut(ui::ElementIdentifier identifier);
// End forcing the UI element with the identifier to show. Return whether the
// action is successful.
virtual bool EndPopOut(ui::ElementIdentifier identifier);
// Returns true if any overflow-able elements are hidden when
// `toolbar_container_view_` is set to `size`. This excludes the overflow
// button itself from the calculation, providing a much more accurate idea of
// whether overflow would happen. Because of this, however, it must fully
// recalculate the layout which could be expensive; call this method as little
// as possible.
bool ShouldShowOverflowButton(gfx::Size size);
// Return true if any buttons overflow.
bool InOverflowMode() const;
OverflowButton* overflow_button() { return overflow_button_; }
const base::flat_map<ui::ElementIdentifier, std::unique_ptr<PopOutState>>&
pop_out_state_for_testing() const {
return pop_out_state_;
}
// Create the overflow menu model for hidden buttons.
std::unique_ptr<ui::SimpleMenuModel> CreateOverflowMenuModel();
// Generate menu text from the responsive element.
virtual std::u16string GetMenuText(
const ResponsiveElementInfo& element_info) const;
// Get menu icon from the responsive element.
std::optional<ui::ImageModel> GetMenuIcon(
const ResponsiveElementInfo& element_info) const;
// Utility that recursively searches for a view with `id` from `view`.
static views::View* FindToolbarElementWithId(views::View* view,
ui::ElementIdentifier id);
// Shows the overflow menu that is anchored to the `overflow_button_`.
void ShowMenu();
bool IsMenuRunning() const;
const views::MenuItemView* root_menu_item() const {
return root_menu_item_.get();
}
const ui::SimpleMenuModel* menu_model_for_testing() const {
return menu_model_.get();
}
private:
friend class ToolbarControllerUiTest;
friend class ToolbarControllerUnitTest;
// Returns currently hidden elements.
std::vector<const ResponsiveElementInfo*> GetOverflowedElements();
// Check if element has overflowed. Check the visibility in proposed_layout if
// provided.
bool IsOverflowed(
const ResponsiveElementInfo& element,
const views::ProposedLayout* proposed_layout = nullptr) const;
void PopulateMenu(views::MenuItemView* parent);
void CloseMenu();
// Adds the status indicator to all the menu items and makes it visible if
// needed.
void ShowStatusIndicator();
// Listens to changes in `action_item` and updates the visibility of the
// status indicator.
void ActionItemChanged(actions::ActionItem* action_item);
// ui::SimpleMenuModel::Delegate:
void ExecuteCommand(int command_id, int event_flags) override;
bool IsCommandIdEnabled(int command_id) const override;
// The toolbar elements managed by this controller.
// Actions are kept in order by observing the PinnedToolbarActionsModel.
// To facilitate menu creation elements order should match overflow
// menu top to bottom.
std::vector<ResponsiveElementInfo> responsive_elements_;
// Returns responsive_elements_ but with the Actions in the correct order,
// as defined by the pinned_actions_delegate_
std::vector<ResponsiveElementInfo> GetResponsiveElementsWithOrderedActions()
const;
std::vector<base::CallbackListSubscription> action_changed_subscription_;
// The starting flex order assigned to the last overflowed element in
// `responsive_elements_`.
const int element_flex_order_start_;
// Reference to ToolbarView::container_view_. Must outlive `this`.
const raw_ptr<views::View> toolbar_container_view_;
// The button with a chevron icon that indicates at least one element in
// `responsive_elements_` overflows. Owned by `toolbar_container_view_`.
raw_ptr<OverflowButton> overflow_button_;
std::unique_ptr<views::MenuRunner> menu_runner_;
std::unique_ptr<ui::SimpleMenuModel> menu_model_;
raw_ptr<views::MenuItemView> root_menu_item_ = nullptr;
const raw_ptr<PinnedActionsDelegate> pinned_actions_delegate_;
const raw_ptr<PinnedToolbarActionsModel> pinned_actions_model_;
// A map to save the original and modified FlexSpecification of responsive
// elements that need to pop out. Set when ToolbarController is initialized.
base::flat_map<ui::ElementIdentifier, std::unique_ptr<PopOutState>>
pop_out_state_;
};
#endif // CHROME_BROWSER_UI_VIEWS_TOOLBAR_TOOLBAR_CONTROLLER_H_
|