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 <string>
9
10#include "base/logging.h"
11#include "third_party/skia/include/core/SkPaint.h"
12#include "third_party/skia/include/core/SkXfermode.h"
13#include "third_party/skia/include/effects/SkGradientShader.h"
14#include "ui/accessibility/ax_view_state.h"
15#include "ui/gfx/canvas.h"
16
17namespace {
18
19// Progress bar's border width.
20const int kBorderWidth = 1;
21
22// Corner radius for the progress bar's border.
23const int kCornerRadius = 2;
24
25// The width of the highlight at the right of the progress bar.
26const int kHighlightWidth = 18;
27
28const SkColor kBackgroundColor = SkColorSetRGB(230, 230, 230);
29const SkColor kBackgroundBorderColor = SkColorSetRGB(208, 208, 208);
30const SkColor kBarBorderColor = SkColorSetRGB(65, 137, 237);
31const SkColor kBarTopColor = SkColorSetRGB(110, 188, 249);
32const SkColor kBarColorStart = SkColorSetRGB(86, 167, 247);
33const SkColor kBarColorEnd = SkColorSetRGB(76, 148, 245);
34const SkColor kBarHighlightEnd = SkColorSetRGB(114, 206, 251);
35const SkColor kDisabledBarBorderColor = SkColorSetRGB(191, 191, 191);
36const SkColor kDisabledBarColorStart = SkColorSetRGB(224, 224, 224);
37const SkColor kDisabledBarColorEnd = SkColorSetRGB(212, 212, 212);
38
39void AddRoundRectPathWithPadding(int x, int y,
40                                 int w, int h,
41                                 int corner_radius,
42                                 SkScalar padding,
43                                 SkPath* path) {
44  DCHECK(path);
45  SkRect rect;
46  rect.set(
47      SkIntToScalar(x) + padding, SkIntToScalar(y) + padding,
48      SkIntToScalar(x + w) - padding, SkIntToScalar(y + h) - padding);
49  path->addRoundRect(
50      rect,
51      SkIntToScalar(corner_radius) - padding,
52      SkIntToScalar(corner_radius) - padding);
53}
54
55void AddRoundRectPath(int x, int y,
56                      int w, int h,
57                      int corner_radius,
58                      SkPath* path) {
59  AddRoundRectPathWithPadding(x, y, w, h, corner_radius, SK_ScalarHalf, path);
60}
61
62void FillRoundRect(gfx::Canvas* canvas,
63                   int x, int y,
64                   int w, int h,
65                   int corner_radius,
66                   const SkColor colors[],
67                   const SkScalar points[],
68                   int count,
69                   bool gradient_horizontal) {
70  SkPath path;
71  AddRoundRectPath(x, y, w, h, corner_radius, &path);
72  SkPaint paint;
73  paint.setStyle(SkPaint::kFill_Style);
74  paint.setFlags(SkPaint::kAntiAlias_Flag);
75
76  SkPoint p[2];
77  p[0].iset(x, y);
78  if (gradient_horizontal) {
79    p[1].iset(x + w, y);
80  } else {
81    p[1].iset(x, y + h);
82  }
83  skia::RefPtr<SkShader> s = skia::AdoptRef(SkGradientShader::CreateLinear(
84      p, colors, points, count, SkShader::kClamp_TileMode));
85  paint.setShader(s.get());
86
87  canvas->DrawPath(path, paint);
88}
89
90void FillRoundRect(gfx::Canvas* canvas,
91                   int x, int y,
92                   int w, int h,
93                   int corner_radius,
94                   SkColor gradient_start_color,
95                   SkColor gradient_end_color,
96                   bool gradient_horizontal) {
97  if (gradient_start_color != gradient_end_color) {
98    SkColor colors[2] = { gradient_start_color, gradient_end_color };
99    FillRoundRect(canvas, x, y, w, h, corner_radius,
100                  colors, NULL, 2, gradient_horizontal);
101  } else {
102    SkPath path;
103    AddRoundRectPath(x, y, w, h, corner_radius, &path);
104    SkPaint paint;
105    paint.setStyle(SkPaint::kFill_Style);
106    paint.setFlags(SkPaint::kAntiAlias_Flag);
107    paint.setColor(gradient_start_color);
108    canvas->DrawPath(path, paint);
109  }
110}
111
112void StrokeRoundRect(gfx::Canvas* canvas,
113                     int x, int y,
114                     int w, int h,
115                     int corner_radius,
116                     SkColor stroke_color,
117                     int stroke_width) {
118  SkPath path;
119  AddRoundRectPath(x, y, w, h, corner_radius, &path);
120  SkPaint paint;
121  paint.setShader(NULL);
122  paint.setColor(stroke_color);
123  paint.setStyle(SkPaint::kStroke_Style);
124  paint.setFlags(SkPaint::kAntiAlias_Flag);
125  paint.setStrokeWidth(SkIntToScalar(stroke_width));
126  canvas->DrawPath(path, paint);
127}
128
129}  // namespace
130
131namespace views {
132
133// static
134const char ProgressBar::kViewClassName[] = "ProgressBar";
135
136ProgressBar::ProgressBar()
137    : min_display_value_(0.0),
138      max_display_value_(1.0),
139      current_value_(0.0) {
140}
141
142ProgressBar::~ProgressBar() {
143}
144
145double ProgressBar::GetNormalizedValue() const {
146  const double capped_value = std::min(
147      std::max(current_value_, min_display_value_), max_display_value_);
148  return (capped_value - min_display_value_) /
149      (max_display_value_ - min_display_value_);
150}
151
152void ProgressBar::SetDisplayRange(double min_display_value,
153                                  double max_display_value) {
154  if (min_display_value != min_display_value_ ||
155      max_display_value != max_display_value_) {
156    DCHECK(min_display_value < max_display_value);
157    min_display_value_ = min_display_value;
158    max_display_value_ = max_display_value;
159    SchedulePaint();
160  }
161}
162
163void ProgressBar::SetValue(double value) {
164  if (value != current_value_) {
165    current_value_ = value;
166    SchedulePaint();
167  }
168}
169
170void ProgressBar::SetTooltipText(const base::string16& tooltip_text) {
171  tooltip_text_ = tooltip_text;
172}
173
174bool ProgressBar::GetTooltipText(const gfx::Point& p,
175                                 base::string16* tooltip) const {
176  DCHECK(tooltip);
177  *tooltip = tooltip_text_;
178  return !tooltip_text_.empty();
179}
180
181void ProgressBar::GetAccessibleState(ui::AXViewState* state) {
182  state->role = ui::AX_ROLE_PROGRESS_INDICATOR;
183  state->AddStateFlag(ui::AX_STATE_READ_ONLY);
184}
185
186gfx::Size ProgressBar::GetPreferredSize() const {
187  gfx::Size pref_size(100, 11);
188  gfx::Insets insets = GetInsets();
189  pref_size.Enlarge(insets.width(), insets.height());
190  return pref_size;
191}
192
193const char* ProgressBar::GetClassName() const {
194  return kViewClassName;
195}
196
197void ProgressBar::OnPaint(gfx::Canvas* canvas) {
198  gfx::Rect content_bounds = GetContentsBounds();
199  int bar_left = content_bounds.x();
200  int bar_top = content_bounds.y();
201  int bar_width = content_bounds.width();
202  int bar_height = content_bounds.height();
203
204  const int progress_width =
205      static_cast<int>(bar_width * GetNormalizedValue() + 0.5);
206
207  // Draw background.
208  FillRoundRect(canvas,
209                bar_left, bar_top, bar_width, bar_height,
210                kCornerRadius,
211                kBackgroundColor, kBackgroundColor,
212                false);
213  StrokeRoundRect(canvas,
214                  bar_left, bar_top,
215                  bar_width, bar_height,
216                  kCornerRadius,
217                  kBackgroundBorderColor,
218                  kBorderWidth);
219
220  if (progress_width > 1) {
221    // Draw inner if wide enough.
222    if (progress_width > kBorderWidth * 2) {
223      canvas->Save();
224
225      SkPath inner_path;
226      AddRoundRectPathWithPadding(
227          bar_left, bar_top, progress_width, bar_height,
228          kCornerRadius,
229          0,
230          &inner_path);
231      canvas->ClipPath(inner_path, false);
232
233      const SkColor bar_colors[] = {
234        kBarTopColor,
235        kBarTopColor,
236        kBarColorStart,
237        kBarColorEnd,
238        kBarColorEnd,
239      };
240      // We want a thin 1-pixel line for kBarTopColor.
241      SkScalar scalar_height = SkIntToScalar(bar_height);
242      SkScalar highlight_width = SkScalarDiv(SK_Scalar1, scalar_height);
243      SkScalar border_width = SkScalarDiv(SkIntToScalar(kBorderWidth),
244                                          scalar_height);
245      const SkScalar bar_points[] = {
246        0,
247        border_width,
248        border_width + highlight_width,
249        SK_Scalar1 - border_width,
250        SK_Scalar1,
251      };
252
253      const SkColor disabled_bar_colors[] = {
254        kDisabledBarColorStart,
255        kDisabledBarColorStart,
256        kDisabledBarColorEnd,
257        kDisabledBarColorEnd,
258      };
259
260      const SkScalar disabled_bar_points[] = {
261        0,
262        border_width,
263        SK_Scalar1 - border_width,
264        SK_Scalar1
265      };
266
267      // Do not start from (kBorderWidth, kBorderWidth) because it makes gaps
268      // between the inner and the border.
269      FillRoundRect(canvas,
270                    bar_left, bar_top,
271                    progress_width, bar_height,
272                    kCornerRadius,
273                    enabled() ? bar_colors : disabled_bar_colors,
274                    enabled() ? bar_points : disabled_bar_points,
275                    enabled() ? arraysize(bar_colors) :
276                        arraysize(disabled_bar_colors),
277                    false);
278
279      if (enabled()) {
280        // Draw the highlight to the right.
281        const SkColor highlight_colors[] = {
282          SkColorSetA(kBarHighlightEnd, 0),
283          kBarHighlightEnd,
284          kBarHighlightEnd,
285        };
286        const SkScalar highlight_points[] = {
287          0,
288          SK_Scalar1 - SkScalarDiv(SkIntToScalar(kBorderWidth), scalar_height),
289          SK_Scalar1,
290        };
291        SkPaint paint;
292        paint.setStyle(SkPaint::kFill_Style);
293        paint.setFlags(SkPaint::kAntiAlias_Flag);
294
295        SkPoint p[2];
296        int highlight_left =
297            std::max(0, progress_width - kHighlightWidth - kBorderWidth);
298        p[0].iset(highlight_left, 0);
299        p[1].iset(progress_width, 0);
300        skia::RefPtr<SkShader> s =
301            skia::AdoptRef(SkGradientShader::CreateLinear(
302                p, highlight_colors, highlight_points,
303                arraysize(highlight_colors), SkShader::kClamp_TileMode));
304        paint.setShader(s.get());
305        paint.setXfermodeMode(SkXfermode::kSrcOver_Mode);
306        canvas->DrawRect(gfx::Rect(highlight_left, 0,
307                                   kHighlightWidth + kBorderWidth, bar_height),
308                         paint);
309      }
310
311      canvas->Restore();
312    }
313
314    // Draw bar stroke
315    StrokeRoundRect(canvas,
316                    bar_left, bar_top, progress_width, bar_height,
317                    kCornerRadius,
318                    enabled() ? kBarBorderColor : kDisabledBarBorderColor,
319                    kBorderWidth);
320  }
321}
322
323}  // namespace views
324