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

ash / system / progress_indicator / progress_ring_indeterminate_animation.cc [blame]

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

#include "ash/system/progress_indicator/progress_ring_indeterminate_animation.h"

#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/gfx/paint_throbber.h"

namespace ash {
namespace {

// This animation is cyclic and uses `start_time()` rather than the animation
// fraction to update animatable properties. That being the case, this value
// doesn't really matter but is chosen to be on the order of minutes to minimize
// overhead that may occur during cyclic animation restart.
constexpr base::TimeDelta kAnimationDuration = base::Minutes(1);

// Helpers ---------------------------------------------------------------------

// Converts the specified `angle_in_degrees` to a position in the range [0, 1].
float ConvertFromAngleToPosition(float angle_in_degrees) {
  DCHECK_GE(angle_in_degrees, 0.f);
  float position = angle_in_degrees / 360.f;
  if (position >= 1.f) {
    // NOTE: The `position` should be wrapped around the interval [0, 1], not be
    // clamped. This is accomplished by removing the integral component.
    position -= static_cast<int>(position);
  }
  return position;
}

}  // namespace

// ProgressRingIndeterminateAnimation ------------------------------------------

ProgressRingIndeterminateAnimation::ProgressRingIndeterminateAnimation()
    : ProgressRingAnimation(Type::kIndeterminate,
                            kAnimationDuration,
                            /*is_cyclic=*/true) {}

ProgressRingIndeterminateAnimation::~ProgressRingIndeterminateAnimation() =
    default;

void ProgressRingIndeterminateAnimation::UpdateAnimatableProperties(
    double fraction,
    float* start_position,
    float* end_position,
    float* outer_ring_opacity) {
  base::TimeDelta elapsed_time = base::TimeTicks::Now() - start_time();

  // Since `elapsed_time` is used rather than the animation `fraction`, it is
  // necessary to manually account for animation duration scaling.
  const float duration_multiplier =
      ui::ScopedAnimationDurationScaleMode::duration_multiplier();
  if (duration_multiplier != 0.f)
    elapsed_time /= duration_multiplier;

  const gfx::ThrobberSpinningState state =
      gfx::CalculateThrobberSpinningState(elapsed_time);

  // The spinning throbber offsets its `start_angle` by `270.f` degrees to
  // account for the fact that `SkPath` treats zero degrees as being aligned
  // with the positive x-axis. Undo that offset.
  float start_angle = state.start_angle - 270.f;
  *start_position = ConvertFromAngleToPosition(start_angle);

  // Add `360.f` degrees to ensure that `end_angle` is positive given that
  // `sweep_angle` may be negative if the spinning throbber is sweeping counter
  // clockwise. Adding `360.f` degrees does not change the calculated position
  // since it will be wrapped around the interval [0, 1].
  float end_angle = start_angle + 360.f + state.sweep_angle;
  *end_position = ConvertFromAngleToPosition(end_angle);

  // If `sweep_angle` is negative, the spinning throbber is sweeping counter
  // clockwise. In this case, swap `start_position` and `end_position`.
  if (state.sweep_angle < 0.f)
    std::swap(*start_position, *end_position);
}

}  // namespace ash