1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "ui/views/controls/throbber.h"
7 #include "base/time/time.h"
8 #include "ui/base/resource/resource_bundle.h"
9 #include "ui/gfx/canvas.h"
10 #include "ui/gfx/image/image.h"
11 #include "ui/gfx/image/image_skia.h"
12 #include "ui/resources/grit/ui_resources.h"
15 using base::TimeDelta
;
19 Throbber::Throbber(int frame_time_ms
,
20 bool paint_while_stopped
)
22 paint_while_stopped_(paint_while_stopped
),
24 frame_time_(TimeDelta::FromMilliseconds(frame_time_ms
)) {
25 SetFrames(ui::ResourceBundle::GetSharedInstance().GetImageNamed(
26 IDR_THROBBER
).ToImageSkia());
29 Throbber::~Throbber() {
33 void Throbber::Start() {
37 start_time_
= Time::Now();
39 timer_
.Start(FROM_HERE
, frame_time_
- TimeDelta::FromMilliseconds(10),
40 this, &Throbber::Run
);
44 SchedulePaint(); // paint right away
47 void Throbber::Stop() {
54 SchedulePaint(); // Important if we're not painting while stopped
57 void Throbber::SetFrames(const gfx::ImageSkia
* frames
) {
59 DCHECK(frames_
->width() > 0 && frames_
->height() > 0);
60 DCHECK(frames_
->width() % frames_
->height() == 0);
61 frame_count_
= frames_
->width() / frames_
->height();
62 PreferredSizeChanged();
65 void Throbber::Run() {
71 gfx::Size
Throbber::GetPreferredSize() const {
72 return gfx::Size(frames_
->height(), frames_
->height());
75 void Throbber::OnPaint(gfx::Canvas
* canvas
) {
76 if (!running_
&& !paint_while_stopped_
)
79 const TimeDelta elapsed_time
= Time::Now() - start_time_
;
80 const int current_frame
=
81 static_cast<int>(elapsed_time
/ frame_time_
) % frame_count_
;
83 int image_size
= frames_
->height();
84 int image_offset
= current_frame
* image_size
;
85 canvas
->DrawImageInt(*frames_
,
86 image_offset
, 0, image_size
, image_size
,
87 0, 0, image_size
, image_size
,
93 // Smoothed throbber ---------------------------------------------------------
96 // Delay after work starts before starting throbber, in milliseconds.
97 static const int kStartDelay
= 200;
99 // Delay after work stops before stopping, in milliseconds.
100 static const int kStopDelay
= 50;
103 SmoothedThrobber::SmoothedThrobber(int frame_time_ms
)
104 : Throbber(frame_time_ms
, /* paint_while_stopped= */ false),
105 start_delay_ms_(kStartDelay
),
106 stop_delay_ms_(kStopDelay
) {
109 SmoothedThrobber::~SmoothedThrobber() {}
111 void SmoothedThrobber::Start() {
114 if (!running() && !start_timer_
.IsRunning()) {
115 start_timer_
.Start(FROM_HERE
, TimeDelta::FromMilliseconds(start_delay_ms_
),
116 this, &SmoothedThrobber::StartDelayOver
);
120 void SmoothedThrobber::StartDelayOver() {
124 void SmoothedThrobber::Stop() {
129 stop_timer_
.Start(FROM_HERE
, TimeDelta::FromMilliseconds(stop_delay_ms_
),
130 this, &SmoothedThrobber::StopDelayOver
);
133 void SmoothedThrobber::StopDelayOver() {
137 // Material throbber -----------------------------------------------------------
139 // The length of a frame in milliseconds.
140 // TODO(estade): remove the +10 when the -10 is removed from Throbber::Start().
141 static const int kMaterialThrobberFrameTimeMs
= 30 + 10;
143 MaterialThrobber::MaterialThrobber() :
144 Throbber(kMaterialThrobberFrameTimeMs
, false),
145 preferred_diameter_(0),
147 checkmark_(nullptr) {
150 MaterialThrobber::~MaterialThrobber() {
153 void MaterialThrobber::SetChecked(bool checked
) {
154 if (checked
== checked_
)
161 gfx::Size
MaterialThrobber::GetPreferredSize() const {
162 if (preferred_diameter_
== 0)
163 return Throbber::GetPreferredSize();
165 return gfx::Size(preferred_diameter_
, preferred_diameter_
);
168 int MaterialThrobber::GetHeightForWidth(int w
) const {
172 void MaterialThrobber::OnPaint(gfx::Canvas
* canvas
) {
176 checkmark_
= ui::ResourceBundle::GetSharedInstance().GetImageNamed(
177 IDR_CHECKMARK
).ToImageSkia();
180 int checkmark_x
= (width() - checkmark_
->width()) / 2;
181 int checkmark_y
= (height() - checkmark_
->height()) / 2;
182 canvas
->DrawImageInt(*checkmark_
, checkmark_x
, checkmark_y
);
187 gfx::Rect bounds
= GetContentsBounds();
188 // Inset by half the stroke width to make sure the whole arc is inside
190 SkScalar stroke_width
= SkIntToScalar(bounds
.width()) / 10.0;
191 gfx::Rect oval
= bounds
;
192 int inset
= SkScalarCeilToInt(stroke_width
/ 2.0);
193 oval
.Inset(inset
, inset
);
195 // Calculate start and end points. The angles are counter-clockwise because
196 // the throbber spins counter-clockwise. The finish angle starts at 12 o'clock
197 // (90 degrees) and rotates steadily. The start angle trails 180 degrees
198 // behind, except for the first half revolution, when it stays at 12 o'clock.
199 base::TimeDelta revolution_time
= base::TimeDelta::FromMilliseconds(1320);
200 base::TimeDelta elapsed_time
= base::Time::Now() - start_time();
201 int64_t twelve_oclock
= 90;
202 int64_t finish_angle
= twelve_oclock
+ 360 * elapsed_time
/ revolution_time
;
203 int64_t start_angle
= std::max(finish_angle
- 180, twelve_oclock
);
206 // Negate the angles to convert to the clockwise numbers Skia expects.
207 path
.arcTo(gfx::RectToSkRect(oval
), -start_angle
,
208 -(finish_angle
- start_angle
), true);
211 // TODO(estade): find a place for this color.
212 paint
.setColor(SkColorSetRGB(0x42, 0x85, 0xF4));
213 paint
.setStrokeCap(SkPaint::kRound_Cap
);
214 paint
.setStrokeWidth(stroke_width
);
215 paint
.setStyle(SkPaint::kStroke_Style
);
216 paint
.setAntiAlias(true);
217 canvas
->DrawPath(path
, paint
);