// Copyright 2017 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_TABS_TAB_TAB_ICON_H_
#define CHROME_BROWSER_UI_VIEWS_TABS_TAB_TAB_ICON_H_

#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/time/time.h"
#include "components/performance_manager/public/features.h"
#include "components/prefs/pref_change_registrar.h"
#include "components/tabs/public/tab_network_state.h"
#include "ui/base/interaction/element_tracker.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/models/image_model.h"
#include "ui/compositor/layer_delegate.h"
#include "ui/gfx/animation/linear_animation.h"
#include "ui/gfx/animation/slide_animation.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/paint_throbber.h"
#include "ui/views/animation/animation_delegate_views.h"
#include "ui/views/view.h"

namespace base {
class TickClock;
}

namespace tabs {
struct TabData;
}

DECLARE_CUSTOM_ELEMENT_EVENT_TYPE(kDiscardAnimationFinishes);

// View that displays the favicon, sad tab, throbber, and attention indicator
// in a tab.
//
// The icon will be drawn in the upper left (upper right for RTL). Normally you
// would lay this out so the top is where you want the icon to be positioned,
// the width is TabIcon::GetIdealWidth(), and the height goes down to the
// bottom of the enclosing view (this is so the crashed tab can animate out of
// the bottom).
class TabIcon : public views::View, public views::AnimationDelegateViews {
  METADATA_HEADER(TabIcon, views::View)

 public:
  // Attention indicator types (use as a bitmask). There is only one visual
  // representation, but the state of each of these is tracked separately and
  // the indicator is shown as long as one is enabled.
  enum class AttentionType {
    kBlockedWebContents = 1 << 0,       // The WebContents is marked as blocked.
    kTabWantsAttentionStatus = 1 << 1,  // Tab::SetTabNeedsAttention() called.
  };

  TabIcon();
  TabIcon(const TabIcon&) = delete;
  TabIcon& operator=(const TabIcon&) = delete;
  ~TabIcon() override;

  // Sets the tab data (network state, favicon, load progress, etc.) that are
  // used to render the tab icon.
  void SetData(const tabs::TabData& data);

  // Sets whether this tab is currently active.
  void SetActiveState(bool is_active);

  // Enables or disables the given attention type. The attention indicator
  // will be shown as long as any of the types are enabled.
  void SetAttention(AttentionType type, bool enabled);

  bool GetShowingLoadingAnimation() const;
  bool GetShowingAttentionIndicator() const;
  bool GetShowingDiscardIndicator() const;

  // Sets whether this object can paint to a layer. When the loading animation
  // is running, painting to a layer saves painting overhead. But if the tab is
  // being painted to some other context than the window, the layered painting
  // won't work.
  void SetCanPaintToLayer(bool can_paint_to_layer);

  // The loading animation only steps when this function is called. The
  // `elapsed_time` parameter is expected to be the same among all tabs in a tab
  // strip in order to keep the throbbers in sync.
  void StepLoadingAnimation(const base::TimeDelta& elapsed_time);

  gfx::ImageSkia GetThemedIconForTesting() { return themed_favicon_; }
  bool GetActiveStateForTesting() { return is_active_tab_; }

  // Called by the parent view when the width available to draw the tab icon
  // changes, to determine whether to increase the discard indicator radius.
  // Not used by vertical tabs since there always is enough width to draw the
  // increased discard indicator radius.
  void ResizeDiscardIndicatorRadiusForWidth(int width);
  void OnDiscardRingTreatmentEnabledChanged();

 protected:
  class CrashAnimation;
  friend CrashAnimation;
  friend class TabTest;
  FRIEND_TEST_ALL_PREFIXES(TabTest, DiscardIndicatorResponsiveness);

  // views::View:
  void OnPaint(gfx::Canvas* canvas) override;
  void OnBoundsChanged(const gfx::Rect& previous_bounds) override;
  void ViewHierarchyChanged(
      const views::ViewHierarchyChangedDetails& details) override;
  void AddedToWidget() override;
  views::PaintInfo::ScaleType GetPaintScaleType() const override;
  void OnThemeChanged() override;

  // views::AnimationDelegateViews:
  void AnimationProgressed(const gfx::Animation* animation) override;
  void AnimationEnded(const gfx::Animation* animation) override;

  // Paints the attention indicator and `favicon_` at the given location.
  void PaintAttentionIndicatorAndIcon(gfx::Canvas* canvas,
                                      const gfx::ImageSkia& icon,
                                      const gfx::Rect& bounds);

  // Paints a dimmed and shrunken favicon surrounded by the discard ring
  void PaintDiscardRingAndIcon(gfx::Canvas* canvas,
                               const gfx::ImageSkia& icon,
                               const gfx::Rect& bounds);

  // Paint either the indeterimate throbber or progress indicator according to
  // current tab state.
  void PaintLoadingAnimation(gfx::Canvas* canvas, gfx::Rect bounds);

  // Gets either the crashed icon or favicon to be rendered for the tab.
  gfx::ImageSkia GetIconToPaint();

  // Paint the favicon if it's available.
  void MaybePaintFavicon(gfx::Canvas* canvas,
                         const gfx::ImageSkia& icon,
                         const gfx::Rect& bounds);
  bool GetNonDefaultFavicon() const;

  // Sets the icon.
  void SetIcon(const ui::ImageModel& icon, bool should_themify_favicon);

  // Start or stops the favicon fade animation for discard tabs
  void SetDiscarded(bool show_discard_status);

  // For certain types of tabs the loading animation is not desired so the
  // caller can set inhibit_loading_animation to true. When false, the loading
  // animation state will be derived from the network state.
  void SetNetworkState(tabs::TabNetworkState network_state);

  // Sets whether the tab should paint as crashed or not.
  void SetCrashed(bool crashed);
  bool GetCrashed() const;

  // Creates or destroys the throbber according to the current animation state
  // and whether a layer can be used. Unlike the base TabIcon layer (which
  // merely reduces composition overhead), this compositor-driven throbber
  // completely offloads the animation rotation to the compositor thread,
  // allowing the UI thread to remain idle during page loads.
  void UpdateThrobber();

  // Updates the themed favicon if necessary.
  void UpdateThemedFavicon();

  // The view used to render the compositor-driven throbber. When active, this
  // child view's layer handles the rotation via a transform animation,
  // bypassing the need for periodic UI-thread repaints.
  raw_ptr<views::View> throbber_view_ = nullptr;

  raw_ptr<const base::TickClock> clock_;

  ui::ImageModel favicon_;
  bool should_themify_favicon_ = false;
  tabs::TabNetworkState network_state_ = tabs::TabNetworkState::kNone;
  bool crashed_ = false;
  int attention_types_ = 0;  // Bitmask of AttentionType.

  // Value from last call to SetNetworkState. When true, the network loading
  // animation will not be shown.
  bool inhibit_loading_animation_ = false;

  // The point in time when the tab icon was first painted in the loading state.
  base::TimeTicks loading_animation_start_time_;

  // When the favicon_ has theming applied to it, the themed version will be
  // cached here. If this isNull(), then there is no theming and favicon_
  // should be used.
  gfx::ImageSkia themed_favicon_;

  // May be different than is_crashed when the crashed icon is animating in.
  bool should_display_crashed_favicon_ = false;

  // Drawn when should_display_crashed_favicon_ is set. Created lazily.
  gfx::ImageSkia crashed_icon_;

  // The fraction the icon is hidden by for the crashed tab animation.
  // When this is 0 it will be drawn at the normal location, and when this is 1
  // it will be drawn off the bottom.
  double hiding_fraction_ = 0.0;

  // Animation used when the favicon grows or shrinks in size. `Show` will
  // represent the favicon growing to full size, while `Hide` will represent the
  // favicon shrinking which happens when the loading spinner or discard
  // indicator is present.
  gfx::SlideAnimation favicon_size_animation_;

  // Animation used when a tab is discarded so the favicon will partially
  // fade out
  gfx::LinearAnimation tab_discard_animation_;

  // The discard indicator will be shown only if the tab is discarded and the
  // discard ring treatment pref is enabled. Keep track of both of the component
  // booleans, in order to determine if the discard indicator is shown/unshown
  // due to a change in the discard status or a change to the pref, because
  // we don't want to animate the discard ring in the latter case.
  bool is_discarded_ = false;
  bool discard_ring_treatment_enabled_ = true;
  bool was_discard_indicator_shown_ = false;

  // Crash animation (in place of favicon). Lazily created since most of the
  // time it will be unneeded.
  std::unique_ptr<CrashAnimation> crash_animation_;

  bool can_paint_to_layer_ = false;

  bool has_tab_data_ = false;

  bool is_active_tab_ = false;

  bool is_monochrome_favicon_ = false;

  // If there is enough space, increased the size of the discard ring beyond the
  // default favicon size, by this amount.
  static constexpr int kIncreasedDiscardIndicatorRadiusDp = 2;

  // The amount to increase the size of the discard ring by.
  int increased_discard_indicator_radius_ = kIncreasedDiscardIndicatorRadiusDp;

  PrefChangeRegistrar local_state_registrar_;
};

#endif  // CHROME_BROWSER_UI_VIEWS_TABS_TAB_TAB_ICON_H_
