chromium/ash/system/tray/tray_background_view.h

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef ASH_SYSTEM_TRAY_TRAY_BACKGROUND_VIEW_H_
#define ASH_SYSTEM_TRAY_TRAY_BACKGROUND_VIEW_H_

#include <memory>

#include "ash/ash_export.h"
#include "ash/constants/tray_background_view_catalog.h"
#include "ash/system/model/virtual_keyboard_model.h"
#include "ash/system/tray/tray_bubble_view.h"
#include "ash/system/user/login_status.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/timer/timer.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/views/context_menu_controller.h"

namespace ui {
class Event;
enum MenuSourceType;
}  // namespace ui

namespace views {
class AnimationAbortHandle;
class MenuRunner;
class View;
}  // namespace views

namespace ash {
class Shelf;
class TrayContainer;

// Base class for some children of StatusAreaWidget. This class handles setting
// and animating the background when the Launcher is shown/hidden. Note that
// events targeting a `TrayBackgroundView`'s view hierarchy are ignored while
// the `TrayBackgroundView`'s hide animation is running.
class ASH_EXPORT TrayBackgroundView : public views::Button,
                                      public views::ContextMenuController,
                                      public TrayBubbleView::Delegate,
                                      public VirtualKeyboardModel::Observer {
  METADATA_HEADER(TrayBackgroundView, views::Button)

 public:

  // Inherit from this class to be notified of events that happen for a specific
  // `TrayBackgroundView`.
  class Observer : public base::CheckedObserver {
   public:
    // Called when the `TrayBackgroundView`'s preferred visibility changes.
    // `visible_preferred` is the new preferred visibility.
    virtual void OnVisiblePreferredChanged(bool visible_preferred) = 0;
  };

  // Records TrayBackgroundView.Pressed metrics for all types of trays. All
  // nested buttons inside `TrayBackgroundView` should use this delegate if they
  // want to record the TrayBackgroundView.Pressed metric.
  class TrayButtonControllerDelegate
      : public Button::DefaultButtonControllerDelegate {
   public:
    TrayButtonControllerDelegate(views::Button* button,
                                 TrayBackgroundViewCatalogName catalog_name);
    void NotifyClick(const ui::Event& event) override;

   private:
    // The catalog name, used to record metrics on feature integrations.
    TrayBackgroundViewCatalogName catalog_name_;
  };

  enum RoundedCornerBehavior {
    kNotRounded,
    kStartRounded,
    kEndRounded,
    kAllRounded
  };

  TrayBackgroundView(Shelf* shelf,
                     const TrayBackgroundViewCatalogName catalog_name,
                     RoundedCornerBehavior corner_behavior = kAllRounded);
  TrayBackgroundView(const TrayBackgroundView&) = delete;
  TrayBackgroundView& operator=(const TrayBackgroundView&) = delete;
  ~TrayBackgroundView() override;

  void AddTrayBackgroundViewObserver(Observer* observer);
  void RemoveTrayBackgroundViewObserver(Observer* observer);

  // Called after the tray has been added to the widget containing it.
  virtual void Initialize();

  // Initializes animations for the bubble. This contains only a fade out
  // animation that hides `bubble_widget` when it becomes invisible.
  static void InitializeBubbleAnimations(views::Widget* bubble_widget);

  // views::Button:
  void OnThemeChanged() override;

  // VirtualKeyboardModel::Observer:
  void OnVirtualKeyboardVisibilityChanged() override;

  // Returns the associated tray bubble view, if one exists. Otherwise returns
  // nullptr.
  virtual TrayBubbleView* GetBubbleView();

  // Returns the associated tray bubble widget, if a bubble exists. Otherwise
  // returns nullptr.
  virtual views::Widget* GetBubbleWidget() const;

  // Returns a lock that prevents window activation from closing bubbles.
  [[nodiscard]] static base::ScopedClosureRunner
  DisableCloseBubbleOnWindowActivated();

  // Whether a window activation change should close bubbles.
  static bool ShouldCloseBubbleOnWindowActivated();

  // Closes the associated tray bubble view if it exists and is currently
  // showing.
  virtual void CloseBubbleInternal() {}

  // Shows the associated tray bubble if one exists.
  virtual void ShowBubble();

  // Calculates the ideal bounds that this view should have depending on the
  // constraints.
  virtual void CalculateTargetBounds();

  // Makes this view's bounds and layout match its calculated target bounds.
  virtual void UpdateLayout();

  // Called to update the tray button after the login status changes.
  virtual void UpdateAfterLoginStatusChange();

  // Called whenever the lock state changes. `locked` represents the current
  // lock state.
  void UpdateAfterLockStateChange(bool locked);

  // Called whenever the status area's collapse state changes.
  virtual void UpdateAfterStatusAreaCollapseChange();

  // Called when the anchor (tray or bubble) may have moved or changed.
  virtual void AnchorUpdated() {}

  // Called from GetAccessibleNodeData, must return a valid accessible name.
  virtual std::u16string GetAccessibleNameForTray() = 0;

  // Called when a locale change is detected. It should reload any strings the
  // view may be using. Note that the locale is not expected to change after the
  // user logs in.
  virtual void HandleLocaleChange() = 0;

  // Hides the bubble associated with |bubble_view|. Called when the widget
  // is closed.
  virtual void HideBubbleWithView(const TrayBubbleView* bubble_view) = 0;

  // Called by the bubble wrapper when a click event occurs outside the bubble.
  // May close the bubble.
  virtual void ClickedOutsideBubble(const ui::LocatedEvent& event) = 0;

  // Returns true if tray bubble view is cached when hidden
  virtual bool CacheBubbleViewForHide() const;

  // Updates the background layer.
  virtual void UpdateBackground();

  // For Jelly: updates the color of either the icon or the label of this view
  // based on the active state specified by `is_active`.
  virtual void UpdateTrayItemColor(bool is_active) = 0;

  // Calls `CloseBubbleInternal` which is implemented by each child tray view.
  // The focusing behavior is handled in this method.
  void CloseBubble();

  // Gets the anchor for bubbles, which is tray_container().
  views::View* GetBubbleAnchor() const;

  // Gets additional insets for positioning bubbles relative to
  // tray_container().
  gfx::Insets GetBubbleAnchorInsets() const;

  // Returns the container window for the bubble (on the proper display).
  aura::Window* GetBubbleWindowContainer();

  // Helper function that calculates background bounds relative to local bounds
  // based on background insets returned from GetBackgroundInsets().
  gfx::Rect GetBackgroundBounds() const;

  // Sets whether the tray item should be shown by default (e.g. it is
  // activated). The effective visibility of the tray item is determined by the
  // current state of the status tray (i.e. whether the virtual keyboard is
  // showing or if it is collapsed).
  virtual void SetVisiblePreferred(bool visible_preferred);
  bool visible_preferred() const { return visible_preferred_; }

  // Disables bounce in and fade in animation. The animation will remain
  // disabled until the returned scoped closure runner is run.
  [[nodiscard]] base::ScopedClosureRunner DisableShowAnimation();

  // Registers a client's request to use custom visibility animations. The
  // custom animation must be executed by the client; `TrayBackgroundView` does
  // not run any custom animations (it will simply do nothing rather than run
  // the default visibility animations). Custom animations will be used until
  // the returned scoped closure runner is run.
  [[nodiscard]] base::ScopedClosureRunner SetUseCustomVisibilityAnimations();

  // Returns true if the view is showing a context menu.
  bool IsShowingMenu() const;

  // Set the rounded corner behavior for this tray item.
  void SetRoundedCornerBehavior(RoundedCornerBehavior corner_behavior);

  // Returns the corners based on the `corner_behavior_`;
  gfx::RoundedCornersF GetRoundedCorners();

  // Returns a weak pointer to this instance.
  base::WeakPtr<TrayBackgroundView> GetWeakPtr();

  // Checks if we should show bounce in or fade in animation.
  bool IsShowAnimationEnabled();

  // Callbacks for Animations
  void OnHideAnimationStarted();
  void OnAnimationAborted();
  virtual void OnAnimationEnded();

  void SetIsActive(bool is_active);
  bool is_active() const { return is_active_; }

  TrayContainer* tray_container() const { return tray_container_; }
  Shelf* shelf() { return shelf_; }
  TrayBackgroundViewCatalogName catalog_name() const { return catalog_name_; }

  // Updates the visibility of this tray's separator.
  void set_separator_visibility(bool visible) { separator_visible_ = visible; }

  // Sets whether to show the view when the status area is collapsed.
  void set_show_when_collapsed(bool show_when_collapsed) {
    show_when_collapsed_ = show_when_collapsed;
  }

  // Sets whether changes in lock state should cause this tray's bubble to close
  // if it is currently open.
  void set_should_close_bubble_on_lock_state_change(
      bool should_close_bubble_on_lock_state_change) {
    should_close_bubble_on_lock_state_change_ =
        should_close_bubble_on_lock_state_change;
  }

 protected:
  // views::Button:
  void OnBoundsChanged(const gfx::Rect& previous_bounds) override;
  bool ShouldEnterPushedState(const ui::Event& event) override;
  views::PaintInfo::ScaleType GetPaintScaleType() const override;

  virtual void OnShouldShowAnimationChanged(bool should_animate) {}

  // Specifies the menu that appears when this tray is right-clicked if
  // `SetContextMenuEnabled(true)` has been called. Default implementation
  // returns a nullptr, in which case no context menu is shown.
  virtual std::unique_ptr<ui::SimpleMenuModel> CreateContextMenuModel();

  // After hide animation is finished/aborted/removed, we will need to do an
  // update to the view's visibility and the view's status area widget state.
  virtual void OnVisibilityAnimationFinished(bool should_log_visible_pod_count,
                                             bool aborted);

  // Used to start and stop pulse animation on tray button.
  void StartPulseAnimation();
  void StopPulseAnimation();

  // Used to bounce in animation on tray button.
  void BounceInAnimation();

  void SetContextMenuEnabled(bool should_enable_menu) {
    set_context_menu_controller(should_enable_menu ? this : nullptr);
  }

  // Logs the tray's visibility UMA.
  void TrackVisibilityUMA(bool visible_preferred) const;

  void set_show_with_virtual_keyboard(bool show_with_virtual_keyboard) {
    show_with_virtual_keyboard_ = show_with_virtual_keyboard;
  }

  void set_use_bounce_in_animation(bool use_bounce_in_animation) {
    use_bounce_in_animation_ = use_bounce_in_animation;
  }

 private:
  class TrayWidgetObserver;
  class TrayBackgroundViewSessionChangeHandler;
  friend class StatusAreaWidgetTest;

  void StartVisibilityAnimation(bool visible);

  // Updates status area widget by calling `UpdateCollapseState()` and
  // `LogVisiblePodCountMetric()`.
  void UpdateStatusArea(bool should_log_visible_pod_count);

  // views::ContextMenuController:
  void ShowContextMenuForViewImpl(views::View* source,
                                  const gfx::Point& point,
                                  ui::MenuSourceType source_type) override;

  // views::View:
  void AboutToRequestFocusFromTabTraversal(bool reverse) override;
  void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
  void ChildPreferredSizeChanged(views::View* child) override;
  // In some cases, we expect the layer's visibility to be set to false right
  // away when the layer is replaced. See
  // `OverviewButtonTrayTest.HideAnimationAlwaysCompletesOnDelete` test as an
  // example. We use `::wm::RecreateLayers(root_window)` to create fresh layers
  // for the window. If we don't override this method, the old layer and its
  // child layers will still be there until all the animation finished.
  std::unique_ptr<ui::Layer> RecreateLayer() override;

  // Applies transformations to the `layer()` to animate the view when
  // `SetVisible(false)` is called.
  void FadeInAnimation();
  void HideAnimation();

  // Helper function that calculates background insets relative to local bounds.
  gfx::Insets GetBackgroundInsets() const;

  // Returns the effective visibility of the tray item based on the current
  // state.
  bool GetEffectiveVisibility();

  // Checks if we should use custom visibility animations.
  bool ShouldUseCustomVisibilityAnimations() const;

  // For Material Next: Updates the background color based on active state.
  void UpdateBackgroundColor(bool active);

  // Add and remove ripple_layer_ from parent.
  void AddRippleLayer();
  void RemoveRippleLayer();

  // Start playing the pulse animation on button. ONLY called in
  // `StartPulseAnimation()` when all layers are ready.
  void PlayPulseAnimation();
  void StartPulseAnimationCoolDownTimer();

  // The shelf containing the system tray for this view.
  raw_ptr<Shelf> shelf_;

  // The catalog name, used to record metrics on feature integrations.
  const TrayBackgroundViewCatalogName catalog_name_;

  // Convenience pointer to the contents view.
  raw_ptr<TrayContainer> tray_container_;

  // A separate layer for ripple aimation.
  std::unique_ptr<ui::Layer> ripple_layer_;
  // The handle to abort ripple and pulse animation.
  std::unique_ptr<views::AnimationAbortHandle>
      ripple_and_pulse_animation_abort_handle_;
  base::OneShotTimer pulse_animation_cool_down_timer_;

  // Determines if the view is active. This changes how  the ink drop ripples
  // behave.
  bool is_active_;

  // Visibility of this tray's separator which is a line of 1x32px and 4px to
  // right of tray.
  bool separator_visible_;

  // During virtual keyboard is shown, visibility changes to TrayBackgroundView
  // are ignored. In such case, preferred visibility is reflected after the
  // virtual keyboard is hidden.
  bool visible_preferred_;

  // If true, the view always shows up when virtual keyboard is visible.
  bool show_with_virtual_keyboard_;

  // If true, the view is visible when the status area is collapsed.
  bool show_when_collapsed_;

  bool use_bounce_in_animation_ = true;
  bool is_starting_animation_ = false;

  // Number of active requests to disable the bounce-in and fade-in animation.
  size_t disable_show_animation_count_ = 0;

  // Number of active requests to use custom visibility animations.
  size_t use_custom_visibility_animation_count_ = 0;

  // The shape of this tray which is only applied to the horizontal tray.
  // Defaults to `kAllRounded`.
  RoundedCornerBehavior corner_behavior_;

  std::unique_ptr<TrayWidgetObserver> widget_observer_;
  std::unique_ptr<TrayBackgroundViewSessionChangeHandler> handler_;
  std::unique_ptr<ui::SimpleMenuModel> context_menu_model_;
  std::unique_ptr<views::MenuRunner> context_menu_runner_;

  // Whether changes in lock state should cause this tray's bubble to close if
  // it is currently open.
  bool should_close_bubble_on_lock_state_change_;

  base::ObserverList<Observer> observers_;

  base::WeakPtrFactory<TrayBackgroundView> weak_factory_{this};
};

}  // namespace ash

#endif  // ASH_SYSTEM_TRAY_TRAY_BACKGROUND_VIEW_H_