File: paint_throbber.cc

package info (click to toggle)
chromium 138.0.7204.183-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 6,071,908 kB
  • sloc: cpp: 34,937,088; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,953; asm: 946,768; xml: 739,971; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,806; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (147 lines) | stat: -rw-r--r-- 5,608 bytes parent folder | download | duplicates (5)
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/gfx/paint_throbber.h"

#include <algorithm>

#include "base/numerics/safe_conversions.h"
#include "base/time/time.h"
#include "cc/paint/paint_flags.h"
#include "third_party/skia/include/core/SkPath.h"
#include "ui/gfx/animation/tween.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/skia_conversions.h"

namespace gfx {

namespace {

// The maximum size of the "spinning" state arc, in degrees.
constexpr int64_t kMaxArcSize = 270;

// The amount of time it takes to grow the "spinning" arc from 0 to 270 degrees.
constexpr auto kArcTime = base::Seconds(2.0 / 3.0);

// The amount of time it takes for the "spinning" throbber to make a full
// rotation.
constexpr auto kRotationTime = base::Milliseconds(1568);

void PaintArc(Canvas* canvas,
              const Rect& bounds,
              SkColor color,
              SkScalar start_angle,
              SkScalar sweep,
              std::optional<SkScalar> stroke_width) {
  if (!stroke_width) {
    // Stroke width depends on size.
    // . For size < 28:          3 - (28 - size) / 16
    // . For 28 <= size:         (8 + size) / 12
    stroke_width = bounds.width() < 28
                       ? 3.0 - SkIntToScalar(28 - bounds.width()) / 16.0
                       : SkIntToScalar(bounds.width() + 8) / 12.0;
  }
  Rect oval = bounds;
  // Inset by half the stroke width to make sure the whole arc is inside
  // the visible rect.
  const int inset = SkScalarCeilToInt(*stroke_width / 2.0);
  oval.Inset(inset);

  SkPath path;
  path.arcTo(RectToSkRect(oval), start_angle, sweep, true);

  cc::PaintFlags flags;
  flags.setColor(color);
  flags.setStrokeCap(cc::PaintFlags::kRound_Cap);
  flags.setStrokeWidth(*stroke_width);
  flags.setStyle(cc::PaintFlags::kStroke_Style);
  flags.setAntiAlias(true);
  canvas->DrawPath(path, flags);
}

// This is a Skia port of the MD spinner SVG. The |start_angle| rotation
// here corresponds to the 'rotate' animation.
ThrobberSpinningState CalculateThrobberSpinningStateWithStartAngle(
    base::TimeDelta elapsed_time,
    int64_t start_angle,
    const int64_t sweep_keyframe_offset = 0) {
  // The sweep angle ranges from -270 to 270 over 1333ms. CSS
  // animation timing functions apply in between key frames, so we have to
  // break up the 1333ms into two keyframes (-270 to 0, then 0 to 270).
  const double elapsed_ratio =
      (elapsed_time / kArcTime) + sweep_keyframe_offset;
  const int64_t sweep_frame = base::ClampFloor<int64_t>(elapsed_ratio);
  const double arc_progress = elapsed_ratio - sweep_frame;
  // This tween is equivalent to cubic-bezier(0.4, 0.0, 0.2, 1).
  double sweep = kMaxArcSize *
                 Tween::CalculateValue(Tween::FAST_OUT_SLOW_IN, arc_progress);
  if (sweep_frame % 2 == 0) {
    sweep -= kMaxArcSize;
  }

  // This part makes sure the sweep is at least 5 degrees long. Roughly
  // equivalent to the "magic constants" in SVG's fillunfill animation.
  constexpr double kMinSweepLength = 5.0;
  if (sweep >= 0.0 && sweep < kMinSweepLength) {
    start_angle -= (kMinSweepLength - sweep);
    sweep = kMinSweepLength;
  } else if (sweep <= 0.0 && sweep > -kMinSweepLength) {
    start_angle += (-kMinSweepLength - sweep);
    sweep = -kMinSweepLength;
  }

  // To keep the sweep smooth, we have an additional rotation after each
  // arc period has elapsed. See SVG's 'rot' animation.
  const int64_t rot_keyframe = (sweep_frame / 2) % 4;
  start_angle = start_angle + rot_keyframe * kMaxArcSize;
  return ThrobberSpinningState{
      .start_angle = static_cast<SkScalar>(start_angle),
      .sweep_angle = static_cast<SkScalar>(sweep)};
}

void PaintThrobberSpinningWithState(Canvas* canvas,
                                    const Rect& bounds,
                                    SkColor color,
                                    const ThrobberSpinningState& state,
                                    std::optional<SkScalar> stroke_width) {
  PaintArc(canvas, bounds, color, state.start_angle, state.sweep_angle,
           stroke_width);
}
}  // namespace

ThrobberSpinningState CalculateThrobberSpinningState(
    base::TimeDelta elapsed_time,
    const int64_t sweep_keyframe_offset) {
  const int64_t start_angle =
      270 + base::ClampRound<int64_t>(elapsed_time / kRotationTime * 360);
  return CalculateThrobberSpinningStateWithStartAngle(elapsed_time, start_angle,
                                                      sweep_keyframe_offset);
}

void PaintThrobberSpinning(Canvas* canvas,
                           const Rect& bounds,
                           SkColor color,
                           const base::TimeDelta& elapsed_time,
                           std::optional<SkScalar> stroke_width) {
  const ThrobberSpinningState state =
      CalculateThrobberSpinningState(elapsed_time);
  PaintThrobberSpinningWithState(canvas, bounds, color, state, stroke_width);
}

void PaintThrobberSpinningWithSweepEasedIn(
    Canvas* canvas,
    const Rect& bounds,
    SkColor color,
    const base::TimeDelta& elapsed_time,
    std::optional<SkScalar> stroke_width) {
  // The second keyframe of the spinning animation is when the arc is
  // minimized, compared to the first keyframe, where it is at its maximum.
  const ThrobberSpinningState state =
      CalculateThrobberSpinningState(elapsed_time, 1);
  PaintThrobberSpinningWithState(canvas, bounds, color, state, stroke_width);
}

}  // namespace gfx