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