// 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