#include "ui/views/controls/progress_bar.h"
#include <algorithm>
#include <cmath>
#include <memory>
#include <string>
#include "base/check_op.h"
#include "base/i18n/number_formatting.h"
#include "cc/paint/paint_flags.h"
#include "third_party/skia/include/core/SkPath.h"
#include "third_party/skia/include/effects/SkGradientShader.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/color/color_id.h"
#include "ui/color/color_provider.h"
#include "ui/gfx/animation/linear_animation.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/views/widget/widget.h"
namespace views {
namespace {
constexpr int kCornerRadius = 3;
constexpr int kSmallCornerRadius = 1;
void AddPossiblyRoundRectToPath(const gfx::Rect& rectangle,
bool allow_round_corner,
SkPath* path) {
if (!allow_round_corner || rectangle.height() < kSmallCornerRadius) {
path->addRect(gfx::RectToSkRect(rectangle));
} else if (rectangle.height() < kCornerRadius) {
path->addRoundRect(gfx::RectToSkRect(rectangle), kSmallCornerRadius,
kSmallCornerRadius);
} else {
path->addRoundRect(gfx::RectToSkRect(rectangle), kCornerRadius,
kCornerRadius);
}
}
int RoundToPercent(double fractional_value) {
return static_cast<int>(fractional_value * 100);
}
}
ProgressBar::ProgressBar(int preferred_height, bool allow_round_corner)
: preferred_height_(preferred_height),
allow_round_corner_(allow_round_corner) {
SetFlipCanvasOnPaintForRTLUI(true);
SetAccessibilityProperties(ax::mojom::Role::kProgressIndicator);
}
ProgressBar::~ProgressBar() = default;
void ProgressBar::GetAccessibleNodeData(ui::AXNodeData* node_data) {
View::GetAccessibleNodeData(node_data);
if (IsIndeterminate()) {
node_data->RemoveStringAttribute(ax::mojom::StringAttribute::kValue);
} else {
node_data->SetValue(base::FormatPercent(RoundToPercent(current_value_)));
}
}
gfx::Size ProgressBar::CalculatePreferredSize() const {
gfx::Size pref_size(1, preferred_height_);
gfx::Insets insets = GetInsets();
pref_size.Enlarge(insets.width(), insets.height());
return pref_size;
}
void ProgressBar::VisibilityChanged(View* starting_from, bool is_visible) {
MaybeNotifyAccessibilityValueChanged();
}
void ProgressBar::AddedToWidget() {
MaybeNotifyAccessibilityValueChanged();
}
void ProgressBar::OnPaint(gfx::Canvas* canvas) {
if (IsIndeterminate()) {
return OnPaintIndeterminate(canvas);
}
gfx::Rect content_bounds = GetContentsBounds();
SkPath background_path;
AddPossiblyRoundRectToPath(content_bounds, allow_round_corner_,
&background_path);
cc::PaintFlags background_flags;
background_flags.setStyle(cc::PaintFlags::kFill_Style);
background_flags.setAntiAlias(true);
background_flags.setColor(GetBackgroundColor());
canvas->DrawPath(background_path, background_flags);
SkPath slice_path;
const int slice_width = static_cast<int>(
content_bounds.width() * std::min(current_value_, 1.0) + 0.5);
if (slice_width < 1) {
return;
}
gfx::Rect slice_bounds = content_bounds;
slice_bounds.set_width(slice_width);
AddPossiblyRoundRectToPath(slice_bounds, allow_round_corner_, &slice_path);
cc::PaintFlags slice_flags;
slice_flags.setStyle(cc::PaintFlags::kFill_Style);
slice_flags.setAntiAlias(true);
slice_flags.setColor(GetForegroundColor());
canvas->DrawPath(slice_path, slice_flags);
}
double ProgressBar::GetValue() const {
return current_value_;
}
void ProgressBar::SetValue(double value) {
double adjusted_value = (value < 0.0 || value > 1.0) ? -1.0 : value;
if (adjusted_value == current_value_) {
return;
}
current_value_ = adjusted_value;
if (IsIndeterminate()) {
indeterminate_bar_animation_ = std::make_unique<gfx::LinearAnimation>(this);
indeterminate_bar_animation_->SetDuration(base::Seconds(2));
indeterminate_bar_animation_->Start();
} else {
indeterminate_bar_animation_.reset();
OnPropertyChanged(¤t_value_, kPropertyEffectsPaint);
}
MaybeNotifyAccessibilityValueChanged();
}
void ProgressBar::SetPaused(bool is_paused) {
if (is_paused_ == is_paused) {
return;
}
is_paused_ = is_paused;
OnPropertyChanged(&is_paused_, kPropertyEffectsPaint);
}
SkColor ProgressBar::GetForegroundColor() const {
if (foreground_color_) {
return foreground_color_.value();
}
return GetColorProvider()->GetColor(foreground_color_id_.value_or(
GetPaused() ? ui::kColorProgressBarPaused : ui::kColorProgressBar));
}
void ProgressBar::SetForegroundColor(SkColor color) {
if (foreground_color_ == color) {
return;
}
foreground_color_ = color;
foreground_color_id_ = absl::nullopt;
OnPropertyChanged(&foreground_color_, kPropertyEffectsPaint);
}
absl::optional<ui::ColorId> ProgressBar::GetForegroundColorId() const {
return foreground_color_id_;
}
void ProgressBar::SetForegroundColorId(absl::optional<ui::ColorId> color_id) {
if (foreground_color_id_ == color_id) {
return;
}
foreground_color_id_ = color_id;
foreground_color_ = absl::nullopt;
OnPropertyChanged(&foreground_color_id_, kPropertyEffectsPaint);
}
SkColor ProgressBar::GetBackgroundColor() const {
if (background_color_id_) {
return GetColorProvider()->GetColor(background_color_id_.value());
}
return background_color_.value_or(
color_utils::BlendTowardMaxContrast(GetForegroundColor(), 0xCC));
}
void ProgressBar::SetBackgroundColor(SkColor color) {
if (background_color_ == color) {
return;
}
background_color_ = color;
background_color_id_ = absl::nullopt;
OnPropertyChanged(&background_color_, kPropertyEffectsPaint);
}
absl::optional<ui::ColorId> ProgressBar::GetBackgroundColorId() const {
return background_color_id_;
}
void ProgressBar::SetBackgroundColorId(absl::optional<ui::ColorId> color_id) {
if (background_color_id_ == color_id) {
return;
}
background_color_id_ = color_id;
background_color_ = absl::nullopt;
OnPropertyChanged(&background_color_id_, kPropertyEffectsPaint);
}
void ProgressBar::AnimationProgressed(const gfx::Animation* animation) {
DCHECK_EQ(animation, indeterminate_bar_animation_.get());
DCHECK(IsIndeterminate());
SchedulePaint();
}
void ProgressBar::AnimationEnded(const gfx::Animation* animation) {
DCHECK_EQ(animation, indeterminate_bar_animation_.get());
if (IsIndeterminate()) {
indeterminate_bar_animation_->Start();
}
}
bool ProgressBar::IsIndeterminate() {
return current_value_ < 0.0;
}
void ProgressBar::OnPaintIndeterminate(gfx::Canvas* canvas) {
gfx::Rect content_bounds = GetContentsBounds();
SkPath background_path;
AddPossiblyRoundRectToPath(content_bounds, allow_round_corner_,
&background_path);
cc::PaintFlags background_flags;
background_flags.setStyle(cc::PaintFlags::kFill_Style);
background_flags.setAntiAlias(true);
background_flags.setColor(GetBackgroundColor());
canvas->DrawPath(background_path, background_flags);
SkPath slice_path;
double time = indeterminate_bar_animation_->GetCurrentValue();
double bar1_left;
double bar1_width;
double bar2_left;
double bar2_width;
if (time < 0.50) {
bar1_left = time / 2;
bar1_width = time * 1.5;
bar2_left = 0;
bar2_width = 0;
} else if (time < 0.75) {
bar1_left = time * 3 - 1.25;
bar1_width = 0.75 - (time - 0.5) * 3;
bar2_left = 0;
bar2_width = time - 0.5;
} else {
bar1_left = 1;
bar1_width = 0;
bar2_left = (time - 0.75) * 4;
bar2_width = 0.25 - (time - 0.75);
}
int bar1_start_x = std::round(content_bounds.width() * bar1_left);
int bar1_end_x = std::round(content_bounds.width() *
std::min(1.0, bar1_left + bar1_width));
int bar2_start_x = std::round(content_bounds.width() * bar2_left);
int bar2_end_x = std::round(content_bounds.width() *
std::min(1.0, bar2_left + bar2_width));
gfx::Rect slice_bounds = content_bounds;
slice_bounds.set_x(content_bounds.x() + bar1_start_x);
slice_bounds.set_width(bar1_end_x - bar1_start_x);
AddPossiblyRoundRectToPath(slice_bounds, allow_round_corner_, &slice_path);
slice_bounds.set_x(content_bounds.x() + bar2_start_x);
slice_bounds.set_width(bar2_end_x - bar2_start_x);
AddPossiblyRoundRectToPath(slice_bounds, allow_round_corner_, &slice_path);
cc::PaintFlags slice_flags;
slice_flags.setStyle(cc::PaintFlags::kFill_Style);
slice_flags.setAntiAlias(true);
slice_flags.setColor(GetForegroundColor());
canvas->DrawPath(slice_path, slice_flags);
}
void ProgressBar::MaybeNotifyAccessibilityValueChanged() {
if (!GetWidget() || !GetWidget()->IsVisible() ||
RoundToPercent(current_value_) == last_announced_percentage_) {
return;
}
last_announced_percentage_ = RoundToPercent(current_value_);
NotifyAccessibilityEvent(ax::mojom::Event::kValueChanged, true);
}
BEGIN_METADATA(ProgressBar, View)
ADD_PROPERTY_METADATA(SkColor, ForegroundColor, ui::metadata::SkColorConverter)
ADD_PROPERTY_METADATA(SkColor, BackgroundColor, ui::metadata::SkColorConverter)
ADD_PROPERTY_METADATA(absl::optional<ui::ColorId>, ForegroundColorId);
ADD_PROPERTY_METADATA(absl::optional<ui::ColorId>, BackgroundColorId);
ADD_PROPERTY_METADATA(bool, Paused)
END_METADATA
}