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(&current_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