| // Copyright 2013 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_BUTTON_H_ |
| #define CHROME_BROWSER_UI_VIEWS_TOOLBAR_TOOLBAR_BUTTON_H_ |
| |
| #include <memory> |
| #include <optional> |
| |
| #include "base/functional/bind.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/raw_ptr_exclusion.h" |
| #include "chrome/browser/ui/views/chrome_views_export.h" |
| #include "ui/base/interaction/element_identifier.h" |
| #include "ui/base/metadata/metadata_header_macros.h" |
| #include "ui/base/models/menu_model.h" |
| #include "ui/base/mojom/menu_source_type.mojom-forward.h" |
| #include "ui/base/pointer/touch_ui_controller.h" |
| #include "ui/base/theme_provider.h" |
| #include "ui/base/ui_base_features.h" |
| #include "ui/gfx/animation/animation_delegate.h" |
| #include "ui/gfx/animation/slide_animation.h" |
| #include "ui/gfx/geometry/point.h" |
| #include "ui/gfx/vector_icon_types.h" |
| #include "ui/views/context_menu_controller.h" |
| #include "ui/views/controls/button/label_button.h" |
| #include "ui/views/controls/highlight_path_generator.h" |
| #include "ui/views/metadata/view_factory.h" |
| |
| class TabStripModel; |
| |
| namespace test { |
| class ToolbarButtonTestApi; |
| } |
| |
| namespace ui { |
| class MenuModel; |
| } |
| |
| namespace views { |
| class MenuModelAdapter; |
| class MenuRunner; |
| } // namespace views |
| |
| // This class provides basic drawing and mouse-over behavior for buttons |
| // appearing in the toolbar. |
| class ToolbarButton : public views::LabelButton, |
| public views::ContextMenuController { |
| METADATA_HEADER(ToolbarButton, views::LabelButton) |
| |
| public: |
| enum class Edge { |
| kLeft = 0, |
| kRight, |
| }; |
| |
| // More convenient form of the ctor below, when |model| and |tab_strip_model| |
| // are both nullptr. |
| explicit ToolbarButton(PressedCallback callback = PressedCallback()); |
| |
| // |tab_strip_model| must outlive this class. |
| // |model| can be null if no menu is to be shown. |
| // |tab_strip_model| is only needed if showing the menu with |model| requires |
| // an active tab. There may be no active tab in |tab_strip_model| during |
| // shutdown. |
| ToolbarButton(PressedCallback callback, |
| std::unique_ptr<ui::MenuModel> model, |
| TabStripModel* tab_strip_model, |
| bool trigger_menu_on_long_press = true); |
| ToolbarButton(const ToolbarButton&) = delete; |
| ToolbarButton& operator=(const ToolbarButton&) = delete; |
| ~ToolbarButton() override; |
| |
| // Highlights the button by setting the label to given |highlight_text|, using |
| // tinting it using the |hightlight_color| if set. The highlight is displayed |
| // using an animation. If some highlight is already set, it shows the new |
| // highlight directly without any animation. To clear the previous highlight |
| // (also using an animation), call this function with both parameters empty. |
| void SetHighlight(const std::u16string& highlight_text, |
| std::optional<SkColor> highlight_color); |
| |
| // Sets the leading margin when the browser is maximized and updates layout to |
| // make the focus rectangle centered. |
| void SetLeadingMargin(int margin); |
| |
| // Sets the trailing margin when the browser is maximized and updates layout |
| // to make the focus rectangle centered. |
| void SetTrailingMargin(int margin); |
| |
| // Methods for handling ButtonDropDown-style menus. |
| void ClearPendingMenu(); |
| bool IsMenuShowing() const; |
| |
| // Sets the button's vector icon. This icon uses the default colors and are |
| // automatically updated on theme changes. |
| void SetVectorIcon(const gfx::VectorIcon& icon); |
| |
| // Sets an icon and touch-mode icon for this button. |
| // TODO(pbos): Investigate if touch-mode icons are just different-size ones |
| // that can be folded into the same .icon file as different CANVAS_DIMENSIONS. |
| void SetVectorIcons(const gfx::VectorIcon& icon, |
| const gfx::VectorIcon& touch_icon); |
| |
| // Updates the images using the given icon and the default colors returned by |
| // GetForegroundColor(). |
| void UpdateIconsWithStandardColors(const gfx::VectorIcon& icon); |
| |
| // Updates the icon images and colors as necessary. Should be called any time |
| // icon state changes, e.g. in response to theme or touch mode changes. |
| virtual void UpdateIcon(); |
| |
| // Returns the button's corner radius for `edge`. |
| virtual float GetCornerRadiusFor(ToolbarButton::Edge edge) const; |
| |
| // Gets/Sets |layout_insets_|, see comment there. |
| std::optional<gfx::Insets> GetLayoutInsets() const; |
| void SetLayoutInsets(const std::optional<gfx::Insets>& insets); |
| |
| // Sets |layout_inset_delta_|, see comment there. |
| void SetLayoutInsetDelta(const gfx::Insets& insets); |
| |
| // views::LabelButton: |
| void OnBoundsChanged(const gfx::Rect& previous_bounds) override; |
| void OnThemeChanged() override; |
| gfx::Rect GetAnchorBoundsInScreen() const override; |
| bool OnMousePressed(const ui::MouseEvent& event) override; |
| bool OnMouseDragged(const ui::MouseEvent& event) override; |
| void OnMouseReleased(const ui::MouseEvent& event) override; |
| // Showing the drop down results in a MouseCaptureLost, we need to ignore it. |
| void OnMouseCaptureLost() override; |
| void OnMouseExited(const ui::MouseEvent& event) override; |
| void OnGestureEvent(ui::GestureEvent* event) override; |
| std::unique_ptr<views::ActionViewInterface> GetActionViewInterface() override; |
| |
| // When IPH is showing we suppress the tooltip text. This means that we must |
| // provide an alternative accessible name, when this is the case. This is |
| // because `Button::AdjustAccessibleName` will use the tooltip text when the |
| // accessible name is empty, and if the tooltip text is also empty then the |
| // button will have no accessible name. |
| std::u16string GetAlternativeAccessibleName() const override; |
| |
| // views::ContextMenuController: |
| void ShowContextMenuForViewImpl( |
| View* source, |
| const gfx::Point& point, |
| ui::mojom::MenuSourceType source_type) override; |
| |
| // ui::PropertyHandler: |
| void AfterPropertyChange(const void* key, int64_t old_value) override; |
| |
| ui::MenuModel* menu_model() { return model_.get(); } |
| |
| void set_menu_identifier(ui::ElementIdentifier menu_identifier) { |
| menu_identifier_ = menu_identifier; |
| } |
| ui::ElementIdentifier menu_identifier() const { return menu_identifier_; } |
| |
| bool GetVectorIconsHasValueForTesting() { return vector_icons_.has_value(); } |
| |
| protected: |
| struct VectorIcons { |
| // RAW_PTR_EXCLUSION: Never allocated by PartitionAlloc (always points to a |
| // global), so there is no benefit to using a raw_ptr, only cost. |
| RAW_PTR_EXCLUSION const gfx::VectorIcon& icon; |
| RAW_PTR_EXCLUSION const gfx::VectorIcon& touch_icon; |
| }; |
| |
| // Returns if menu should be shown. Override this to change default behavior. |
| virtual bool ShouldShowMenu(); |
| |
| // Returns if the button inkdrop should persist after the user interacts with |
| // IPH for the button. Override this to change default behavior. |
| // TODO(crbug.com/40258442): Investigate if this is still needed and if so how |
| // it can be applied to all Buttons rather than just ToolbarButtons. |
| virtual bool ShouldShowInkdropAfterIphInteraction(); |
| |
| // Function to show the dropdown menu. |
| virtual void ShowDropDownMenu(ui::mojom::MenuSourceType source_type); |
| |
| // Shows the given `menu_model` anchored to this button. |
| void ShowMenuForModel(ui::mojom::MenuSourceType source_type, |
| ui::MenuModel* menu_model); |
| |
| // Updates the button's background and border. |
| virtual void UpdateColorsAndInsets(); |
| |
| // Returns the standard toolbar button foreground color for the given state. |
| // This color is typically used for the icon and text of toolbar buttons. |
| virtual SkColor GetForegroundColor(ButtonState state) const; |
| |
| // Returns the icon size of the toolbar button |
| virtual int GetIconSize() const; |
| |
| // Retuns true if a non-empty border should be painted. |
| virtual bool ShouldPaintBorder() const; |
| |
| // Retuns true if the background highlight color should be blended |
| // with the toolbar color. |
| virtual bool ShouldBlendHighlightColor() const; |
| |
| // Returns whether to directly use the highlight as background instead |
| // of blending it with the toolbar colors. |
| // TODO(shibalik): remove this method after fixing for profile button. |
| virtual bool ShouldDirectlyUseHighlightAsBackground() const; |
| |
| // Virtual method to explicitly set the highlighted text color instead of the |
| // default behavior of the HighlightColorAnimation. |
| virtual std::optional<SkColor> GetHighlightTextColor() const; |
| |
| // Virtual method to explicitly set the highlighted border color instead of |
| // the default behavior of the HighlightColorAnimation. |
| virtual std::optional<SkColor> GetHighlightBorderColor() const; |
| |
| const std::optional<VectorIcons>& GetVectorIcons() const { |
| return vector_icons_; |
| } |
| |
| // Sets the spacing on the outer side of the label (not the side where the |
| // image is). The spacing is applied only when the label is non-empty. |
| void SetLabelSideSpacing(int spacing); |
| |
| // Returns the target insets according to the button's layout. |
| const gfx::Insets GetTargetInsets() const; |
| |
| // Returns the target size according to the current button's size and insets. |
| const gfx::Size GetTargetSize() const; |
| |
| // Returns the button's rounded corner radius based on its size. |
| int GetRoundedCornerRadius() const; |
| |
| // Updates the images using the given icons and specific colors. |
| void UpdateIconsWithColors(const gfx::VectorIcon& icon, |
| SkColor normal_color, |
| SkColor hovered_color, |
| SkColor pressed_color, |
| SkColor disabled_color); |
| |
| static constexpr int kDefaultIconSize = 16; |
| static constexpr int kDefaultIconSizeChromeRefresh = 20; |
| static constexpr int kDefaultTouchableIconSize = 24; |
| |
| private: |
| friend test::ToolbarButtonTestApi; |
| |
| class HighlightColorAnimation : gfx::AnimationDelegate { |
| public: |
| explicit HighlightColorAnimation(ToolbarButton* parent); |
| HighlightColorAnimation(const HighlightColorAnimation&) = delete; |
| HighlightColorAnimation& operator=(const HighlightColorAnimation&) = delete; |
| ~HighlightColorAnimation() override; |
| |
| // Starts a fade-in animation using the provided |highlight color| or using |
| // a default color if not set. |
| void Show(std::optional<SkColor> highlight_color); |
| |
| // Starts a fade-out animation. A no-op if the fade-out animation is |
| // currently in progress or not shown. |
| void Hide(); |
| |
| // Returns current text / border / background / ink-drop base color based on |
| // current |highlight_color_| and on the current animation state (which |
| // influences the alpha channel). Returns no value if there is no such color |
| // and we should use the default text color / paint no border / paint no |
| // background / use the default ink-drop base color. |
| std::optional<SkColor> GetTextColor() const; |
| std::optional<SkColor> GetBorderColor() const; |
| std::optional<SkColor> GetBackgroundColor() const; |
| std::optional<SkColor> GetInkDropBaseColor() const; |
| |
| void AnimationEnded(const gfx::Animation* animation) override; |
| void AnimationProgressed(const gfx::Animation* animation) override; |
| |
| private: |
| friend test::ToolbarButtonTestApi; |
| |
| // Returns whether the animation is currently shown. Note that this returns |
| // true even after calling Hide() until the fade-out animation finishes. |
| bool IsShown() const; |
| |
| void ClearHighlightColor(); |
| |
| const raw_ptr<ToolbarButton> parent_; |
| |
| // A highlight color is used to signal special states. When set this color |
| // is used as a base for background, text, border and ink drops. When not |
| // set, uses the default ToolbarButton ink drop. |
| std::optional<SkColor> highlight_color_; |
| |
| // Animation for showing the highlight color (in border, text, and |
| // background) when it becomes non-empty and hiding it when it becomes empty |
| // again. |
| gfx::SlideAnimation highlight_color_animation_; |
| }; |
| |
| void TouchUiChanged(); |
| |
| // Clears the current highlight, i.e. it sets the label to an empty string and |
| // clears the highlight color. If there was a non-empty highlight, previously, |
| // it hides the current highlight using an animation. Otherwise, it is a |
| // no-op. |
| void ClearHighlight(); |
| |
| // Callback for MenuModelAdapter. |
| void OnMenuClosed(); |
| |
| // views::LabelButton: |
| // This is private to avoid a foot-shooter. Callers should use SetHighlight() |
| // instead which sets an optional color as well. |
| void SetText(std::u16string_view text) override; |
| |
| // Sets the in product help promo. Called after the kHasInProductHelpPromoKey |
| // property changes. When this button has an in product help promo, the button |
| // gets a blue highlight that pulses. |
| void SetHasInProductHelpPromo(bool has_in_product_help_promo); |
| |
| // The model that populates the attached menu. |
| std::unique_ptr<ui::MenuModel> model_; |
| |
| const raw_ptr<TabStripModel> tab_strip_model_; |
| |
| // Indicates if menu is currently showing. |
| bool menu_showing_ = false; |
| |
| // Whether the menu should be shown when there is a long mouse press or a drag |
| // event. |
| const bool trigger_menu_on_long_press_; |
| |
| // Determines whether to highlight the button for in-product help. |
| // TODO(crbug.com/40258442): Remove this member after issue is addressed. |
| bool has_in_product_help_promo_ = false; |
| |
| // Y position of mouse when left mouse button is pressed. |
| int y_position_on_lbuttondown_ = 0; |
| |
| // The model adapter for the drop down menu. |
| std::unique_ptr<views::MenuModelAdapter> menu_model_adapter_; |
| |
| // Menu runner to display drop down menu. |
| std::unique_ptr<views::MenuRunner> menu_runner_; |
| |
| // Optional identifier for the menu when it runs. |
| ui::ElementIdentifier menu_identifier_; |
| |
| // Layout insets to use. This is used when the ToolbarButton is not actually |
| // hosted inside the toolbar. If not supplied, |
| // |GetLayoutInsets(TOOLBAR_BUTTON)| is used instead which is not appropriate |
| // outside the toolbar. |
| std::optional<gfx::Insets> layout_insets_; |
| |
| // Delta from regular toolbar-button insets. This is necessary for buttons |
| // that use smaller or larger icons than regular ToolbarButton instances. |
| // CastToolbarButton for instance uses larger insets for touchable mode to |
| // match the expected touchable UI. |
| gfx::Insets layout_inset_delta_; |
| |
| // Used to ensure the button remains highlighted while the menu is active. |
| std::optional<Button::ScopedAnchorHighlight> menu_anchor_higlight_; |
| |
| // Vector icons for the ToolbarButton. The icon is chosen based on touch-ui. |
| // Reacts to theme changes using default colors. |
| std::optional<VectorIcons> vector_icons_; |
| |
| // Class responsible for animating highlight color (calling a callback on |
| // |this| to refresh UI). |
| HighlightColorAnimation highlight_color_animation_; |
| |
| // Suppress tooltip when IPH is showing. |
| std::u16string suppressed_tooltip_text_; |
| |
| base::CallbackListSubscription subscription_ = |
| ui::TouchUiController::Get()->RegisterCallback( |
| base::BindRepeating(&ToolbarButton::TouchUiChanged, |
| base::Unretained(this))); |
| |
| // A factory for tasks that show the dropdown context menu for the button. |
| base::WeakPtrFactory<ToolbarButton> show_menu_factory_{this}; |
| }; |
| |
| class ToolbarButtonActionViewInterface |
| : public views::LabelButtonActionViewInterface { |
| public: |
| explicit ToolbarButtonActionViewInterface(ToolbarButton* action_view); |
| ~ToolbarButtonActionViewInterface() override = default; |
| |
| // LabelButtonActionViewInterface: |
| void ActionItemChangedImpl(actions::ActionItem* action_item) override; |
| |
| private: |
| raw_ptr<ToolbarButton> action_view_; |
| }; |
| |
| BEGIN_VIEW_BUILDER(CHROME_VIEWS_EXPORT, ToolbarButton, views::LabelButton) |
| VIEW_BUILDER_PROPERTY(std::optional<gfx::Insets>, LayoutInsets) |
| END_VIEW_BUILDER |
| |
| DEFINE_VIEW_BUILDER(CHROME_VIEWS_EXPORT, ToolbarButton) |
| |
| #endif // CHROME_BROWSER_UI_VIEWS_TOOLBAR_TOOLBAR_BUTTON_H_ |