// Copyright 2011 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_GFX_ANIMATION_ANIMATION_H_
#define UI_GFX_ANIMATION_ANIMATION_H_

#include <optional>

#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/time/time.h"
#include "build/buildflag.h"
#include "ui/gfx/animation/animation_container_element.h"
#include "ui/gfx/animation/animation_export.h"

namespace gfx {
class Rect;
}

namespace gfx {

class AnimationContainer;
class AnimationDelegate;
class AnimationTestApi;

// Base class used in implementing animations. You only need use this class if
// you're implementing a new animation type, otherwise you'll likely want one of
// LinearAnimation, SlideAnimation, ThrobAnimation or MultiAnimation.
//
// To subclass override Step, which is invoked as the animation progresses and
// GetCurrentValue() to return the value appropriate to the animation.
class ANIMATION_EXPORT Animation : public AnimationContainerElement {
 public:
  // Used with SetRichAnimationRenderMode() to force enable/disable rich
  // animations during tests.
  enum class RichAnimationRenderMode {
    PLATFORM,
    FORCE_ENABLED,
    FORCE_DISABLED
  };

  explicit Animation(base::TimeDelta timer_interval);

  Animation(const Animation&) = delete;
  Animation& operator=(const Animation&) = delete;

  ~Animation() override;

  // Starts the animation. Does nothing if the animation is already running.
  void Start();

  // Stops the animation. Does nothing if the animation is not running.
  void Stop();

  // Gets the value for the current state, according to the animation
  // curve in use.
  virtual double GetCurrentValue() const = 0;

  // Convenience for returning a value between |start| and |target| based on
  // the current value. This is (target - start) * GetCurrentValue() + start.
  double CurrentValueBetween(double start, double target) const;
  int CurrentValueBetween(int start, int target) const;
  gfx::Rect CurrentValueBetween(const gfx::Rect& start_bounds,
                                const gfx::Rect& target_bounds) const;

  // Sets the delegate.
  void set_delegate(AnimationDelegate* delegate) { delegate_ = delegate; }

  // Sets the container used to manage the timer. A value of NULL results in
  // creating a new AnimationContainer.
  void SetContainer(AnimationContainer* container);

  bool is_animating() const { return is_animating_; }

  base::TimeDelta timer_interval() const { return timer_interval_; }

  // Returns true if rich animations should be rendered.
  // Looks at session type (e.g. remote desktop) and accessibility settings
  // to give guidance for heavy animations such as "start download" arrow.
  static bool ShouldRenderRichAnimation();

  // Returns the duration a rich animation should use given the nominal
  // `duration`. The return value is either `duration` or zero, depending on
  // whether rich animations are currently enabled. NOTE: Since rich animations
  // can be enabled and disabled dynamically, it is not appropriate to store the
  // result of this call long-term; callers should re-set the animation duration
  // before each animation run.
  //
  // TODO(pkasting): It may be clearer and safer to have an "is rich animation"
  // bit on animations, and have the animation system adjust the duration
  // internally automatically, e.g. in `AnimationStarted()` in each subclass.
  static base::TimeDelta RichAnimationDuration(base::TimeDelta duration);

  // Determines on a per-platform basis whether scroll animations (e.g. produced
  // by home/end key) should be enabled. Should only be called from the browser
  // process.
  static bool ScrollAnimationsEnabledBySystem();

  // Determines whether the user desires reduced motion based on platform APIs.
  // Should only be called from the browser process, on the UI thread.
  static bool PrefersReducedMotion();
  static void UpdatePrefersReducedMotion();
#if BUILDFLAG(IS_CHROMEOS)
  // This should only be used by the ChromeOS Accessibility system.
  static void SetPrefersReducedMotionForA11y(bool prefers_reduced_motion);
#endif  // BUILDFLAG(IS_CHROMEOS)
  static void SetPrefersReducedMotionForTesting(bool prefers_reduced_motion) {
    prefers_reduced_motion_ = prefers_reduced_motion;
  }

 protected:
  // Invoked from Start to allow subclasses to prepare for the animation.
  virtual void AnimationStarted() {}

  // Invoked from Stop after we're removed from the container but before the
  // delegate has been invoked.
  virtual void AnimationStopped() {}

  // Invoked from stop to determine if cancel should be invoked. If this returns
  // true the delegate is notified the animation was canceled, otherwise the
  // delegate is notified the animation stopped.
  virtual bool ShouldSendCanceledFromStop();

  AnimationContainer* container() { return container_.get(); }
  base::TimeTicks start_time() const { return start_time_; }
  AnimationDelegate* delegate() { return delegate_; }

  // AnimationContainer::Element overrides
  void SetStartTime(base::TimeTicks start_time) override;
  void Step(base::TimeTicks time_now) override = 0;
  base::TimeDelta GetTimerInterval() const override;

 private:
  friend class AnimationTestApi;

  static bool ShouldRenderRichAnimationImpl();

  // The mode in which to render rich animations.
  static RichAnimationRenderMode rich_animation_rendering_mode_;

  // Interval for the animation.
  const base::TimeDelta timer_interval_;

  // If true we're running.
  bool is_animating_;

  // Our delegate; may be null.
  raw_ptr<AnimationDelegate, DanglingUntriaged> delegate_;

  // Container we're in. If non-null we're animating.
  scoped_refptr<AnimationContainer> container_;

  // Time we started at.
  base::TimeTicks start_time_;

  // Obtaining the PrefersReducedMotion system setting can be expensive, so it
  // is cached in this boolean. A value of `std::nullopt` indicates that its
  // value has not been queried from the platform yet.
  static std::optional<bool> prefers_reduced_motion_;

#if BUILDFLAG(IS_WIN)
  // On Windows, the `prefers_reduced_motion_` system setting is largely
  // dictated by a platform call to SystemParametersInfo(), which can fail, and
  // thus, `prefers_reduced_motion_` has a very slightly different semantic to
  // ShouldRenderRichAnimationImpl(). This keeps track of Win32 API calls made
  // to read the SPI parameter `SPI_GETCLIENTAREAANIMATION`, Windows'
  // system-wide 'reduced-animation' setting, so it is known whether we have its
  // value, since `prefers_reduced_motion_` defaults to false, allowing the use
  // of its cached value in most cases. A value of `std::nullopt` indicates that
  // a call to SystemParametersInfo() has not been made yet. It is updated on
  // startup, as well as each time a WM_SETTINGCHANGE MSG is broadcasted for
  // animations (as happens each time that setting is modified).
  static std::optional<bool> has_reduced_motion_platform_parameter_;
#endif
};

}  // namespace gfx

#endif  // UI_GFX_ANIMATION_ANIMATION_H_
