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.
4
5 #include "ui/views/controls/progress_bar.h"
6
7 #include <algorithm>
8 #include <cmath>
9 #include <memory>
10 #include <string>
11
12 #include "base/logging.h"
13 #include "base/macros.h"
14 #include "cc/paint/paint_flags.h"
15 #include "third_party/skia/include/core/SkPath.h"
16 #include "third_party/skia/include/effects/SkGradientShader.h"
17 #include "ui/accessibility/ax_enums.mojom.h"
18 #include "ui/accessibility/ax_node_data.h"
19 #include "ui/gfx/animation/linear_animation.h"
20 #include "ui/gfx/canvas.h"
21 #include "ui/gfx/color_utils.h"
22 #include "ui/native_theme/native_theme.h"
23 #include "ui/views/metadata/metadata_impl_macros.h"
24
25 namespace views {
26
27 namespace {
28
29 // In DP, the amount to round the corners of the progress bar (both bg and
30 // fg, aka slice).
31 constexpr int kCornerRadius = 3;
32
33 // Adds a rectangle to the path. The corners will be rounded if there is room.
AddPossiblyRoundRectToPath(const gfx::Rect & rectangle,bool allow_round_corner,SkPath * path)34 void AddPossiblyRoundRectToPath(const gfx::Rect& rectangle,
35 bool allow_round_corner,
36 SkPath* path) {
37 if (!allow_round_corner || rectangle.height() < kCornerRadius) {
38 path->addRect(gfx::RectToSkRect(rectangle));
39 } else {
40 path->addRoundRect(gfx::RectToSkRect(rectangle), kCornerRadius,
41 kCornerRadius);
42 }
43 }
44
45 } // namespace
46
ProgressBar(int preferred_height,bool allow_round_corner)47 ProgressBar::ProgressBar(int preferred_height, bool allow_round_corner)
48 : preferred_height_(preferred_height),
49 allow_round_corner_(allow_round_corner) {
50 EnableCanvasFlippingForRTLUI(true);
51 }
52
53 ProgressBar::~ProgressBar() = default;
54
GetAccessibleNodeData(ui::AXNodeData * node_data)55 void ProgressBar::GetAccessibleNodeData(ui::AXNodeData* node_data) {
56 node_data->role = ax::mojom::Role::kProgressIndicator;
57 }
58
CalculatePreferredSize() const59 gfx::Size ProgressBar::CalculatePreferredSize() const {
60 // The width will typically be ignored.
61 gfx::Size pref_size(1, preferred_height_);
62 gfx::Insets insets = GetInsets();
63 pref_size.Enlarge(insets.width(), insets.height());
64 return pref_size;
65 }
66
OnPaint(gfx::Canvas * canvas)67 void ProgressBar::OnPaint(gfx::Canvas* canvas) {
68 if (IsIndeterminate())
69 return OnPaintIndeterminate(canvas);
70
71 gfx::Rect content_bounds = GetContentsBounds();
72
73 // Draw background.
74 SkPath background_path;
75 AddPossiblyRoundRectToPath(content_bounds, allow_round_corner_,
76 &background_path);
77 cc::PaintFlags background_flags;
78 background_flags.setStyle(cc::PaintFlags::kFill_Style);
79 background_flags.setAntiAlias(true);
80 background_flags.setColor(GetBackgroundColor());
81 canvas->DrawPath(background_path, background_flags);
82
83 // Draw slice.
84 SkPath slice_path;
85 const int slice_width = static_cast<int>(
86 content_bounds.width() * std::min(current_value_, 1.0) + 0.5);
87 if (slice_width < 1)
88 return;
89
90 gfx::Rect slice_bounds = content_bounds;
91 slice_bounds.set_width(slice_width);
92 AddPossiblyRoundRectToPath(slice_bounds, allow_round_corner_, &slice_path);
93
94 cc::PaintFlags slice_flags;
95 slice_flags.setStyle(cc::PaintFlags::kFill_Style);
96 slice_flags.setAntiAlias(true);
97 slice_flags.setColor(GetForegroundColor());
98 canvas->DrawPath(slice_path, slice_flags);
99 }
100
GetValue() const101 double ProgressBar::GetValue() const {
102 return current_value_;
103 }
104
SetValue(double value)105 void ProgressBar::SetValue(double value) {
106 double adjusted_value = (value < 0.0 || value > 1.0) ? -1.0 : value;
107
108 if (adjusted_value == current_value_)
109 return;
110
111 current_value_ = adjusted_value;
112 if (IsIndeterminate()) {
113 indeterminate_bar_animation_ = std::make_unique<gfx::LinearAnimation>(this);
114 indeterminate_bar_animation_->SetDuration(base::TimeDelta::FromSeconds(2));
115 indeterminate_bar_animation_->Start();
116 } else {
117 indeterminate_bar_animation_.reset();
118 OnPropertyChanged(¤t_value_, kPropertyEffectsPaint);
119 }
120 }
121
GetForegroundColor() const122 SkColor ProgressBar::GetForegroundColor() const {
123 if (foreground_color_)
124 return foreground_color_.value();
125
126 return GetNativeTheme()->GetSystemColor(
127 ui::NativeTheme::kColorId_ProminentButtonColor);
128 }
129
SetForegroundColor(SkColor color)130 void ProgressBar::SetForegroundColor(SkColor color) {
131 if (foreground_color_ == color)
132 return;
133
134 foreground_color_ = color;
135 OnPropertyChanged(&foreground_color_, kPropertyEffectsPaint);
136 }
137
GetBackgroundColor() const138 SkColor ProgressBar::GetBackgroundColor() const {
139 return background_color_.value_or(
140 color_utils::BlendTowardMaxContrast(GetForegroundColor(), 0xCC));
141 }
142
SetBackgroundColor(SkColor color)143 void ProgressBar::SetBackgroundColor(SkColor color) {
144 if (background_color_ == color)
145 return;
146
147 background_color_ = color;
148 OnPropertyChanged(&background_color_, kPropertyEffectsPaint);
149 }
150
AnimationProgressed(const gfx::Animation * animation)151 void ProgressBar::AnimationProgressed(const gfx::Animation* animation) {
152 DCHECK_EQ(animation, indeterminate_bar_animation_.get());
153 DCHECK(IsIndeterminate());
154 SchedulePaint();
155 }
156
AnimationEnded(const gfx::Animation * animation)157 void ProgressBar::AnimationEnded(const gfx::Animation* animation) {
158 DCHECK_EQ(animation, indeterminate_bar_animation_.get());
159 // Restarts animation.
160 if (IsIndeterminate())
161 indeterminate_bar_animation_->Start();
162 }
163
IsIndeterminate()164 bool ProgressBar::IsIndeterminate() {
165 return current_value_ < 0.0;
166 }
167
OnPaintIndeterminate(gfx::Canvas * canvas)168 void ProgressBar::OnPaintIndeterminate(gfx::Canvas* canvas) {
169 gfx::Rect content_bounds = GetContentsBounds();
170
171 // Draw background.
172 SkPath background_path;
173 AddPossiblyRoundRectToPath(content_bounds, allow_round_corner_,
174 &background_path);
175 cc::PaintFlags background_flags;
176 background_flags.setStyle(cc::PaintFlags::kFill_Style);
177 background_flags.setAntiAlias(true);
178 background_flags.setColor(GetBackgroundColor());
179 canvas->DrawPath(background_path, background_flags);
180
181 // Draw slice.
182 SkPath slice_path;
183 double time = indeterminate_bar_animation_->GetCurrentValue();
184
185 // The animation spec corresponds to the material design lite's parameter.
186 // (cf. https://github.com/google/material-design-lite/)
187 double bar1_left;
188 double bar1_width;
189 double bar2_left;
190 double bar2_width;
191 if (time < 0.50) {
192 bar1_left = time / 2;
193 bar1_width = time * 1.5;
194 bar2_left = 0;
195 bar2_width = 0;
196 } else if (time < 0.75) {
197 bar1_left = time * 3 - 1.25;
198 bar1_width = 0.75 - (time - 0.5) * 3;
199 bar2_left = 0;
200 bar2_width = time - 0.5;
201 } else {
202 bar1_left = 1;
203 bar1_width = 0;
204 bar2_left = (time - 0.75) * 4;
205 bar2_width = 0.25 - (time - 0.75);
206 }
207
208 int bar1_start_x = std::round(content_bounds.width() * bar1_left);
209 int bar1_end_x = std::round(content_bounds.width() *
210 std::min(1.0, bar1_left + bar1_width));
211 int bar2_start_x = std::round(content_bounds.width() * bar2_left);
212 int bar2_end_x = std::round(content_bounds.width() *
213 std::min(1.0, bar2_left + bar2_width));
214
215 gfx::Rect slice_bounds = content_bounds;
216 slice_bounds.set_x(content_bounds.x() + bar1_start_x);
217 slice_bounds.set_width(bar1_end_x - bar1_start_x);
218 AddPossiblyRoundRectToPath(slice_bounds, allow_round_corner_, &slice_path);
219 slice_bounds.set_x(content_bounds.x() + bar2_start_x);
220 slice_bounds.set_width(bar2_end_x - bar2_start_x);
221 AddPossiblyRoundRectToPath(slice_bounds, allow_round_corner_, &slice_path);
222
223 cc::PaintFlags slice_flags;
224 slice_flags.setStyle(cc::PaintFlags::kFill_Style);
225 slice_flags.setAntiAlias(true);
226 slice_flags.setColor(GetForegroundColor());
227 canvas->DrawPath(slice_path, slice_flags);
228 }
229
230 BEGIN_METADATA(ProgressBar)
231 METADATA_PARENT_CLASS(View)
232 ADD_PROPERTY_METADATA(ProgressBar, SkColor, ForegroundColor)
233 ADD_PROPERTY_METADATA(ProgressBar, SkColor, BackgroundColor)
234 END_METADATA()
235
236 } // namespace views
237