diff options
author | estade <estade@chromium.org> | 2015-05-03 13:21:26 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-05-03 20:22:01 +0000 |
commit | 4b7d20a22e0c0a06109434005f2d39ac88f51722 (patch) | |
tree | d66b32ea0d2bda85e0b00e5806277d0c8d673ba8 | |
parent | 7d724373197ee4d8b7bc2bc968d9f25333b284fe (diff) | |
download | chromium_src-4b7d20a22e0c0a06109434005f2d39ac88f51722.zip chromium_src-4b7d20a22e0c0a06109434005f2d39ac88f51722.tar.gz chromium_src-4b7d20a22e0c0a06109434005f2d39ac88f51722.tar.bz2 |
Material throbber: use in tabstrip
- move paint code to ui/gfx
- add color and tinting enums in native_theme and ThemeProvider, calculate tinted color in ThemeService
- port tabs to use new throbber
- ash system throbber also gets new throbber
- merge views::Throbber and view::MaterialThrobber and delete some code that wasn't being used (frame_time_ms was always the same, for example)
BUG=473342, 461137
Review URL: https://codereview.chromium.org/1123573002
Cr-Commit-Position: refs/heads/master@{#328079}
20 files changed, 260 insertions, 301 deletions
diff --git a/ash/resources/ash_resources.grd b/ash/resources/ash_resources.grd index 2ff0296..c1eb94e 100644 --- a/ash/resources/ash_resources.grd +++ b/ash/resources/ash_resources.grd @@ -69,8 +69,6 @@ <structure type="chrome_scaled_image" name="IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND" file="common/tray_popup_label_button_normal_background.png" /> <structure type="chrome_scaled_image" name="IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER" file="common/tray_popup_public_account_logout_button_border.png" /> - <structure type="chrome_scaled_image" name="IDR_AURA_CROS_DEFAULT_THROBBER" file="cros/common/default_throbber.png" /> - <structure type="chrome_scaled_image" name="IDR_AURA_WARNING_ICON" file="common/alert_small.png" /> <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_ACCESSIBILITY" file="cros/status/status_accessibility_mode.png" /> diff --git a/ash/resources/default_100_percent/cros/common/default_throbber.png b/ash/resources/default_100_percent/cros/common/default_throbber.png Binary files differdeleted file mode 100644 index 47eeb2c..0000000 --- a/ash/resources/default_100_percent/cros/common/default_throbber.png +++ /dev/null diff --git a/ash/resources/default_200_percent/cros/common/default_throbber.png b/ash/resources/default_200_percent/cros/common/default_throbber.png Binary files differdeleted file mode 100644 index 1538e41..0000000 --- a/ash/resources/default_200_percent/cros/common/default_throbber.png +++ /dev/null diff --git a/ash/system/tray/throbber_view.cc b/ash/system/tray/throbber_view.cc index 90372d1..6ec1f8d 100644 --- a/ash/system/tray/throbber_view.cc +++ b/ash/system/tray/throbber_view.cc @@ -13,16 +13,12 @@ namespace ash { namespace { -// Time in ms per throbber frame. -const int kThrobberFrameMs = 30; - // Duration for showing/hiding animation in milliseconds. const int kThrobberAnimationDurationMs = 200; } // namespace -SystemTrayThrobber::SystemTrayThrobber(int frame_delay_ms) - : views::SmoothedThrobber(frame_delay_ms) { +SystemTrayThrobber::SystemTrayThrobber() : views::SmoothedThrobber() { } SystemTrayThrobber::~SystemTrayThrobber() { @@ -42,9 +38,7 @@ bool SystemTrayThrobber::GetTooltipText(const gfx::Point& p, } ThrobberView::ThrobberView() { - throbber_ = new SystemTrayThrobber(kThrobberFrameMs); - throbber_->SetFrames(ui::ResourceBundle::GetSharedInstance().GetImageNamed( - IDR_AURA_CROS_DEFAULT_THROBBER).ToImageSkia()); + throbber_ = new SystemTrayThrobber(); throbber_->set_stop_delay_ms(kThrobberAnimationDurationMs); AddChildView(throbber_); diff --git a/ash/system/tray/throbber_view.h b/ash/system/tray/throbber_view.h index 08976d6..f09dd8b 100644 --- a/ash/system/tray/throbber_view.h +++ b/ash/system/tray/throbber_view.h @@ -14,7 +14,7 @@ namespace ash { // A SmoothedThrobber with tooltip. class SystemTrayThrobber : public views::SmoothedThrobber { public: - SystemTrayThrobber(int frame_delay_ms); + SystemTrayThrobber(); ~SystemTrayThrobber() override; void SetTooltipText(const base::string16& tooltip_text); diff --git a/chrome/browser/themes/theme_properties.h b/chrome/browser/themes/theme_properties.h index 530315c..f2f9b08 100644 --- a/chrome/browser/themes/theme_properties.h +++ b/chrome/browser/themes/theme_properties.h @@ -88,6 +88,8 @@ class ThemeProperties { COLOR_NTP_SECTION_HEADER_RULE, COLOR_NTP_SECTION_HEADER_RULE_LIGHT, COLOR_NTP_TEXT_LIGHT, + COLOR_THROBBER_SPINNING, + COLOR_THROBBER_WAITING, #if defined(ENABLE_SUPERVISED_USERS) COLOR_SUPERVISED_USER_LABEL, COLOR_SUPERVISED_USER_LABEL_BACKGROUND, diff --git a/chrome/browser/themes/theme_service.cc b/chrome/browser/themes/theme_service.cc index 0594475..8f61303 100644 --- a/chrome/browser/themes/theme_service.cc +++ b/chrome/browser/themes/theme_service.cc @@ -34,6 +34,8 @@ #include "ui/base/layout.h" #include "ui/base/resource/resource_bundle.h" #include "ui/gfx/image/image_skia.h" +#include "ui/native_theme/common_theme.h" +#include "ui/native_theme/native_theme.h" #if defined(ENABLE_EXTENSIONS) #include "extensions/browser/extension_registry_observer.h" @@ -227,6 +229,18 @@ SkColor ThemeService::GetColor(int id) const { return IncreaseLightness(GetColor(Properties::COLOR_NTP_TEXT), 0.86); case Properties::COLOR_NTP_TEXT_LIGHT: return IncreaseLightness(GetColor(Properties::COLOR_NTP_TEXT), 0.40); + case Properties::COLOR_THROBBER_SPINNING: + case Properties::COLOR_THROBBER_WAITING: { + SkColor base_color; + bool found_color = ui::CommonThemeGetSystemColor( + id == Properties::COLOR_THROBBER_SPINNING + ? ui::NativeTheme::kColorId_ThrobberSpinningColor + : ui::NativeTheme::kColorId_ThrobberWaitingColor, + &base_color); + DCHECK(found_color); + color_utils::HSL hsl = GetTint(Properties::TINT_BUTTONS); + return color_utils::HSLShift(base_color, hsl); + } #if defined(ENABLE_SUPERVISED_USERS) case Properties::COLOR_SUPERVISED_USER_LABEL: return color_utils::GetReadableColor( diff --git a/chrome/browser/ui/libgtk2ui/native_theme_gtk2.cc b/chrome/browser/ui/libgtk2ui/native_theme_gtk2.cc index b55352a..fc8ba3b 100644 --- a/chrome/browser/ui/libgtk2ui/native_theme_gtk2.cc +++ b/chrome/browser/ui/libgtk2ui/native_theme_gtk2.cc @@ -405,6 +405,8 @@ GdkColor NativeThemeGtk2::GetSystemGdkColor(ColorId color_id) const { return GetReadableColor(kNegativeTextColor, GetEntryStyle()->base[GTK_STATE_SELECTED]); } + case kColorId_ThrobberSpinningColor: + case kColorId_ThrobberWaitingColor: case kColorId_NumColors: NOTREACHED(); break; diff --git a/chrome/browser/ui/views/autofill/card_unmask_prompt_views.cc b/chrome/browser/ui/views/autofill/card_unmask_prompt_views.cc index 20425fb..c3f45ad 100644 --- a/chrome/browser/ui/views/autofill/card_unmask_prompt_views.cc +++ b/chrome/browser/ui/views/autofill/card_unmask_prompt_views.cc @@ -435,7 +435,7 @@ void CardUnmaskPromptViews::InitIfNecessary() { progress_overlay_->SetVisible(false); AddChildView(progress_overlay_); - progress_throbber_ = new views::MaterialThrobber(); + progress_throbber_ = new views::Throbber(); progress_overlay_->AddChildView(progress_throbber_); progress_label_ = new views::Label(l10n_util::GetStringUTF16( diff --git a/chrome/browser/ui/views/autofill/card_unmask_prompt_views.h b/chrome/browser/ui/views/autofill/card_unmask_prompt_views.h index b272480..ebf8226 100644 --- a/chrome/browser/ui/views/autofill/card_unmask_prompt_views.h +++ b/chrome/browser/ui/views/autofill/card_unmask_prompt_views.h @@ -14,10 +14,10 @@ #include "ui/views/window/dialog_delegate.h" namespace views { +class Checkbox; class ImageView; class Label; -class Checkbox; -class MaterialThrobber; +class Throbber; } namespace autofill { @@ -135,7 +135,7 @@ class CardUnmaskPromptViews : public CardUnmaskPromptView, views::Checkbox* storage_checkbox_; FadeOutView* progress_overlay_; - views::MaterialThrobber* progress_throbber_; + views::Throbber* progress_throbber_; views::Label* progress_label_; gfx::SlideAnimation overlay_animation_; diff --git a/chrome/browser/ui/views/tabs/tab.cc b/chrome/browser/ui/views/tabs/tab.cc index 25df3600..cfaad36 100644 --- a/chrome/browser/ui/views/tabs/tab.cc +++ b/chrome/browser/ui/views/tabs/tab.cc @@ -38,6 +38,7 @@ #include "ui/gfx/favicon_size.h" #include "ui/gfx/geometry/rect_conversions.h" #include "ui/gfx/image/image_skia_operations.h" +#include "ui/gfx/paint_throbber.h" #include "ui/gfx/path.h" #include "ui/gfx/skia_util.h" #include "ui/resources/grit/ui_resources.h" @@ -1332,15 +1333,16 @@ void Tab::PaintIcon(gfx::Canvas* canvas) { if (data().network_state != TabRendererData::NETWORK_STATE_NONE) { // Paint network activity (aka throbber) animation frame. ui::ThemeProvider* tp = GetThemeProvider(); - gfx::ImageSkia frames(*tp->GetImageSkiaNamed( - (data().network_state == TabRendererData::NETWORK_STATE_WAITING) ? - IDR_THROBBER_WAITING : IDR_THROBBER)); - - int icon_size = frames.height(); - int image_offset = loading_animation_frame_ * icon_size; - DrawIconCenter(canvas, frames, image_offset, - icon_size, icon_size, - bounds, false, SkPaint()); + if (data().network_state == TabRendererData::NETWORK_STATE_WAITING) { + gfx::PaintThrobberWaitingForFrame( + canvas, bounds, tp->GetColor(ThemeProperties::COLOR_THROBBER_WAITING), + loading_animation_frame_); + } else { + gfx::PaintThrobberSpinningForFrame( + canvas, bounds, + tp->GetColor(ThemeProperties::COLOR_THROBBER_SPINNING), + loading_animation_frame_); + } } else if (should_display_crashed_favicon_) { // Paint crash favicon. ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); @@ -1364,55 +1366,15 @@ void Tab::AdvanceLoadingAnimation(TabRendererData::NetworkState old_state, // TODO(robliao): Remove ScopedTracker below once crbug.com/461137 is fixed. tracked_objects::ScopedTracker tracking_profile1( FROM_HERE_WITH_EXPLICIT_FUNCTION("461137 Tab::AdvanceLoadingAnimation1")); - static bool initialized = false; - static int loading_animation_frame_count = 0; - static int waiting_animation_frame_count = 0; - static int waiting_to_loading_frame_count_ratio = 0; - if (!initialized) { - // TODO(robliao): Remove ScopedTracker below once crbug.com/461137 is fixed. - tracked_objects::ScopedTracker tracking_profile2( - FROM_HERE_WITH_EXPLICIT_FUNCTION( - "461137 Tab::AdvanceLoadingAnimation2")); - initialized = true; - ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); - gfx::ImageSkia loading_animation(*rb.GetImageSkiaNamed(IDR_THROBBER)); - loading_animation_frame_count = - loading_animation.width() / loading_animation.height(); - gfx::ImageSkia waiting_animation(*rb.GetImageSkiaNamed( - IDR_THROBBER_WAITING)); - waiting_animation_frame_count = - waiting_animation.width() / waiting_animation.height(); - waiting_to_loading_frame_count_ratio = - waiting_animation_frame_count / loading_animation_frame_count; - - base::debug::Alias(&loading_animation_frame_count); - base::debug::Alias(&waiting_animation_frame_count); - CHECK_NE(0, waiting_to_loading_frame_count_ratio) << - "Number of frames in IDR_THROBBER must be equal to or greater " << - "than the number of frames in IDR_THROBBER_WAITING. Please " << - "investigate how this happened and update http://crbug.com/132590, " << - "this is causing crashes in the wild."; - } - - // The waiting animation is the reverse of the loading animation, but at a - // different rate - the following reverses and scales the animation_frame_ - // so that the frame is at an equivalent position when going from one - // animation to the other. - if (state != old_state) { - loading_animation_frame_ = loading_animation_frame_count - - (loading_animation_frame_ / waiting_to_loading_frame_count_ratio); - } if (state == TabRendererData::NETWORK_STATE_WAITING) { - loading_animation_frame_ = (loading_animation_frame_ + 1) % - waiting_animation_frame_count; + ++loading_animation_frame_; // Waiting steps backwards. immersive_loading_step_ = (immersive_loading_step_ - 1 + kImmersiveLoadingStepCount) % kImmersiveLoadingStepCount; } else if (state == TabRendererData::NETWORK_STATE_LOADING) { - loading_animation_frame_ = (loading_animation_frame_ + 1) % - loading_animation_frame_count; + ++loading_animation_frame_; immersive_loading_step_ = (immersive_loading_step_ + 1) % kImmersiveLoadingStepCount; } else { diff --git a/ui/gfx/BUILD.gn b/ui/gfx/BUILD.gn index 51a2b49..c7a7801 100644 --- a/ui/gfx/BUILD.gn +++ b/ui/gfx/BUILD.gn @@ -145,6 +145,8 @@ component("gfx") { "native_widget_types.h", "nine_image_painter.cc", "nine_image_painter.h", + "paint_throbber.cc", + "paint_throbber.h", "path.cc", "path.h", "path_aura.cc", diff --git a/ui/gfx/gfx.gyp b/ui/gfx/gfx.gyp index 97bdb8f..f93151e 100644 --- a/ui/gfx/gfx.gyp +++ b/ui/gfx/gfx.gyp @@ -224,6 +224,8 @@ 'nine_image_painter.cc', 'nine_image_painter.h', 'overlay_transform.h', + 'paint_throbber.cc', + 'paint_throbber.h', 'path.cc', 'path.h', 'path_aura.cc', diff --git a/ui/gfx/paint_throbber.cc b/ui/gfx/paint_throbber.cc new file mode 100644 index 0000000..0495f6a --- /dev/null +++ b/ui/gfx/paint_throbber.cc @@ -0,0 +1,114 @@ +// Copyright (c) 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/paint_throbber.h" + +#include "base/time/time.h" +#include "ui/gfx/animation/tween.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/skia_util.h" + +namespace gfx { + +namespace { + +void PaintArc(Canvas* canvas, + const Rect& bounds, + SkColor color, + SkScalar start_angle, + SkScalar sweep) { + // Inset by half the stroke width to make sure the whole arc is inside + // the visible rect. + SkScalar stroke_width = SkIntToScalar(bounds.width()) / 10.0; + Rect oval = bounds; + int inset = SkScalarCeilToInt(stroke_width / 2.0); + oval.Inset(inset, inset); + + SkPath path; + path.arcTo(RectToSkRect(oval), start_angle, sweep, true); + + SkPaint paint; + paint.setColor(color); + paint.setStrokeCap(SkPaint::kRound_Cap); + paint.setStrokeWidth(stroke_width); + paint.setStyle(SkPaint::kStroke_Style); + paint.setAntiAlias(true); + canvas->DrawPath(path, paint); +} + +} // namespace + +void PaintThrobberSpinning(Canvas* canvas, + const Rect& bounds, SkColor color, const base::TimeDelta& elapsed_time) { + // This is a Skia port of the MD spinner SVG. The |start_angle| rotation + // here corresponds to the 'rotate' animation. + base::TimeDelta rotation_time = base::TimeDelta::FromMilliseconds(1568); + int64_t start_angle = 270 + 360 * elapsed_time / rotation_time; + + // The sweep angle ranges from -|arc_size| to |arc_size| over 1333ms. CSS + // animation timing functions apply in between key frames, so we have to + // break up the |arc_time| into two keyframes (-arc_size to 0, then 0 to + // arc_size). + int64_t arc_size = 270; + base::TimeDelta arc_time = base::TimeDelta::FromMilliseconds(666); + double arc_size_progress = static_cast<double>(elapsed_time.InMicroseconds() % + arc_time.InMicroseconds()) / + arc_time.InMicroseconds(); + // This tween is equivalent to cubic-bezier(0.4, 0.0, 0.2, 1). + double sweep = + arc_size * Tween::CalculateValue(Tween::FAST_OUT_SLOW_IN, + arc_size_progress); + int64_t sweep_keyframe = (elapsed_time / arc_time) % 2; + if (sweep_keyframe == 0) + sweep -= arc_size; + + // This part makes sure the sweep is at least 5 degrees long. Roughly + // equivalent to the "magic constants" in SVG's fillunfill animation. + const double min_sweep_length = 5.0; + if (sweep >= 0.0 && sweep < min_sweep_length) { + start_angle -= (min_sweep_length - sweep); + sweep = min_sweep_length; + } else if (sweep <= 0.0 && sweep > -min_sweep_length) { + start_angle += (-min_sweep_length - sweep); + sweep = -min_sweep_length; + } + + // To keep the sweep smooth, we have an additional rotation after each + // |arc_time| period has elapsed. See SVG's 'rot' animation. + int64_t rot_keyframe = (elapsed_time / (arc_time * 2)) % 4; + PaintArc(canvas, bounds, color, + start_angle + rot_keyframe * arc_size, sweep); +} + +void PaintThrobberSpinningForFrame(Canvas* canvas, + const Rect& bounds, SkColor color, uint32_t frame) { + const uint32_t frame_duration_ms = 30; + PaintThrobberSpinning(canvas, bounds, color, + base::TimeDelta::FromMilliseconds(frame * frame_duration_ms)); +} + +void PaintThrobberWaiting(Canvas* canvas, + const Rect& bounds, SkColor color, const base::TimeDelta& elapsed_time) { + // Calculate start and end points. The angles are counter-clockwise because + // the throbber spins counter-clockwise. The finish angle starts at 12 o'clock + // (90 degrees) and rotates steadily. The start angle trails 180 degrees + // behind, except for the first half revolution, when it stays at 12 o'clock. + base::TimeDelta revolution_time = base::TimeDelta::FromMilliseconds(1320); + int64_t twelve_oclock = 90; + int64_t finish_angle = twelve_oclock + 360 * elapsed_time / revolution_time; + int64_t start_angle = std::max(finish_angle - 180, twelve_oclock); + + // Negate the angles to convert to the clockwise numbers Skia expects. + PaintArc(canvas, bounds, color, -start_angle, -(finish_angle - start_angle)); +} + +void PaintThrobberWaitingForFrame(Canvas* canvas, + const Rect& bounds, SkColor color, uint32_t frame) { + const uint32_t frame_duration_ms = 30; + PaintThrobberWaiting(canvas, bounds, color, + base::TimeDelta::FromMilliseconds(frame * frame_duration_ms)); +} + +} // namespace gfx diff --git a/ui/gfx/paint_throbber.h b/ui/gfx/paint_throbber.h new file mode 100644 index 0000000..497de74 --- /dev/null +++ b/ui/gfx/paint_throbber.h @@ -0,0 +1,38 @@ +// Copyright (c) 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_PAINT_THROBBER_H_ +#define UI_GFX_PAINT_THROBBER_H_ + +#include "base/basictypes.h" +#include "third_party/skia/include/core/SkColor.h" +#include "ui/gfx/gfx_export.h" + +namespace base { +class TimeDelta; +} + +namespace gfx { + +class Canvas; +class Rect; + +// Paints a single frame of the throbber in the "spinning", aka Material, state. +GFX_EXPORT void PaintThrobberSpinning(Canvas* canvas, + const Rect& bounds, SkColor color, const base::TimeDelta& elapsed_time); +// As above, but frame is passed in rather than calculated. Frame duration is +// assumed to be 30ms. +GFX_EXPORT void PaintThrobberSpinningForFrame(Canvas* canvas, + const Rect& bounds, SkColor color, uint32_t frame); + +// Paints a throbber in the "waiting" state. Used when waiting on a network +// response, for example. +GFX_EXPORT void PaintThrobberWaiting(Canvas* canvas, + const Rect& bounds, SkColor color, const base::TimeDelta& elapsed_time); +GFX_EXPORT void PaintThrobberWaitingForFrame(Canvas* canvas, + const Rect& bounds, SkColor color, uint32_t frame); + +} // namespace gfx + +#endif // UI_GFX_PAINT_THROBBER_H_ diff --git a/ui/native_theme/common_theme.cc b/ui/native_theme/common_theme.cc index a7a4c5a..43e5a53 100644 --- a/ui/native_theme/common_theme.cc +++ b/ui/native_theme/common_theme.cc @@ -40,6 +40,9 @@ const SkColor kBlueButtonDisabledColor = SK_ColorWHITE; const SkColor kBlueButtonPressedColor = SK_ColorWHITE; const SkColor kBlueButtonHoverColor = SK_ColorWHITE; const SkColor kBlueButtonShadowColor = SkColorSetRGB(0x53, 0x8C, 0xEA); +// Material spinner/throbber: +const SkColor kThrobberSpinningColor = SkColorSetRGB(0x42, 0x81, 0xF4); +const SkColor kThrobberWaitingColor = SkColorSetRGB(0xA6, 0xA6, 0xA6); } // namespace @@ -106,6 +109,13 @@ bool CommonThemeGetSystemColor(NativeTheme::ColorId color_id, SkColor* color) { case NativeTheme::kColorId_BlueButtonShadowColor: *color = kBlueButtonShadowColor; break; + // Material spinner/throbber + case NativeTheme::kColorId_ThrobberSpinningColor: + *color = kThrobberSpinningColor; + break; + case NativeTheme::kColorId_ThrobberWaitingColor: + *color = kThrobberWaitingColor; + break; default: return false; } diff --git a/ui/native_theme/native_theme.h b/ui/native_theme/native_theme.h index 3f47ad9..cb75406 100644 --- a/ui/native_theme/native_theme.h +++ b/ui/native_theme/native_theme.h @@ -325,6 +325,9 @@ class NATIVE_THEME_EXPORT NativeTheme { kColorId_ResultsTableNegativeHoveredText, kColorId_ResultsTableNegativeSelectedText, // TODO(benrg): move other hardcoded colors here. + // Colors for the material spinner (aka throbber). + kColorId_ThrobberSpinningColor, + kColorId_ThrobberWaitingColor, kColorId_NumColors, }; diff --git a/ui/views/controls/throbber.cc b/ui/views/controls/throbber.cc index b2a26fc..1a8e38c 100644 --- a/ui/views/controls/throbber.cc +++ b/ui/views/controls/throbber.cc @@ -9,16 +9,14 @@ #include "ui/gfx/canvas.h" #include "ui/gfx/image/image.h" #include "ui/gfx/image/image_skia.h" +#include "ui/gfx/paint_throbber.h" +#include "ui/native_theme/common_theme.h" +#include "ui/native_theme/native_theme.h" #include "ui/resources/grit/ui_resources.h" namespace views { -Throbber::Throbber(int frame_time_ms, bool paint_while_stopped) - : paint_while_stopped_(paint_while_stopped), - frames_(NULL), - frame_time_(base::TimeDelta::FromMilliseconds(frame_time_ms)) { - SetFrames(ui::ResourceBundle::GetSharedInstance().GetImageNamed( - IDR_THROBBER).ToImageSkia()); +Throbber::Throbber() : checked_(false), checkmark_(nullptr) { } Throbber::~Throbber() { @@ -30,8 +28,9 @@ void Throbber::Start() { return; start_time_ = base::TimeTicks::Now(); - timer_.Start(FROM_HERE, frame_time_ - base::TimeDelta::FromMilliseconds(10), - this, &Throbber::SchedulePaint); + const int kFrameTimeMs = 30; + timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(kFrameTimeMs), this, + &Throbber::SchedulePaint); SchedulePaint(); // paint right away } @@ -40,35 +39,44 @@ void Throbber::Stop() { return; timer_.Stop(); - SchedulePaint(); // Important if we're not painting while stopped + SchedulePaint(); } -void Throbber::SetFrames(const gfx::ImageSkia* frames) { - frames_ = frames; - DCHECK(frames_->width() > 0 && frames_->height() > 0); - DCHECK(frames_->width() % frames_->height() == 0); - frame_count_ = frames_->width() / frames_->height(); - PreferredSizeChanged(); +void Throbber::SetChecked(bool checked) { + if (checked == checked_) + return; + + checked_ = checked; + SchedulePaint(); } gfx::Size Throbber::GetPreferredSize() const { - return gfx::Size(frames_->height(), frames_->height()); + const int kDefaultDiameter = 16; + return gfx::Size(kDefaultDiameter, kDefaultDiameter); } void Throbber::OnPaint(gfx::Canvas* canvas) { - if (!IsRunning() && !paint_while_stopped_) - return; + if (!IsRunning()) { + if (checked_) { + if (!checkmark_) { + checkmark_ = ui::ResourceBundle::GetSharedInstance() + .GetImageNamed(IDR_CHECKMARK) + .ToImageSkia(); + } - const base::TimeDelta elapsed_time = base::TimeTicks::Now() - start_time_; - const int current_frame = - static_cast<int>(elapsed_time / frame_time_) % frame_count_; + int checkmark_x = (width() - checkmark_->width()) / 2; + int checkmark_y = (height() - checkmark_->height()) / 2; + canvas->DrawImageInt(*checkmark_, checkmark_x, checkmark_y); + } + return; + } - int image_size = frames_->height(); - int image_offset = current_frame * image_size; - canvas->DrawImageInt(*frames_, - image_offset, 0, image_size, image_size, - 0, 0, image_size, image_size, - false); + SkColor color; + bool found_color = ui::CommonThemeGetSystemColor( + ui::NativeTheme::kColorId_ThrobberSpinningColor, &color); + DCHECK(found_color); + base::TimeDelta elapsed_time = base::TimeTicks::Now() - start_time_; + gfx::PaintThrobberSpinning(canvas, GetContentsBounds(), color, elapsed_time); } bool Throbber::IsRunning() const { @@ -83,10 +91,8 @@ static const int kStartDelay = 200; // Delay after work stops before stopping, in milliseconds. static const int kStopDelay = 50; -SmoothedThrobber::SmoothedThrobber(int frame_time_ms) - : Throbber(frame_time_ms, /* paint_while_stopped= */ false), - start_delay_ms_(kStartDelay), - stop_delay_ms_(kStopDelay) { +SmoothedThrobber::SmoothedThrobber() + : start_delay_ms_(kStartDelay), stop_delay_ms_(kStopDelay) { } SmoothedThrobber::~SmoothedThrobber() {} @@ -119,138 +125,4 @@ void SmoothedThrobber::StopDelayOver() { Throbber::Stop(); } -// Material throbber ----------------------------------------------------------- - -// The length of a frame in milliseconds. -// TODO(estade): remove the +10 when the -10 is removed from Throbber::Start(). -static const int kMaterialThrobberFrameTimeMs = 30 + 10; - -MaterialThrobber::MaterialThrobber() : - Throbber(kMaterialThrobberFrameTimeMs, false), - preferred_diameter_(0), - checked_(false), - checkmark_(nullptr) { -} - -MaterialThrobber::~MaterialThrobber() { -} - -void MaterialThrobber::SetChecked(bool checked) { - if (checked == checked_) - return; - - checked_ = checked; - SchedulePaint(); -} - -gfx::Size MaterialThrobber::GetPreferredSize() const { - if (preferred_diameter_ == 0) - return Throbber::GetPreferredSize(); - - return gfx::Size(preferred_diameter_, preferred_diameter_); -} - -int MaterialThrobber::GetHeightForWidth(int w) const { - return w; -} - -void MaterialThrobber::OnPaint(gfx::Canvas* canvas) { - if (!IsRunning()) { - if (checked_) { - if (!checkmark_) { - checkmark_ = ui::ResourceBundle::GetSharedInstance().GetImageNamed( - IDR_CHECKMARK).ToImageSkia(); - } - - int checkmark_x = (width() - checkmark_->width()) / 2; - int checkmark_y = (height() - checkmark_->height()) / 2; - canvas->DrawImageInt(*checkmark_, checkmark_x, checkmark_y); - } - return; - } - - PaintSpinning(canvas); -} - -void MaterialThrobber::PaintSpinning(gfx::Canvas* canvas) { - base::TimeDelta elapsed_time = base::TimeTicks::Now() - start_time(); - - // This is a Skia port of the MD spinner SVG. The |start_angle| rotation - // here corresponds to the 'rotate' animation. - base::TimeDelta rotation_time = base::TimeDelta::FromMilliseconds(1568); - int64_t start_angle = 270 + 360 * elapsed_time / rotation_time; - - // The sweep angle ranges from -|arc_size| to |arc_size| over 1333ms. CSS - // animation timing functions apply in between key frames, so we have to - // break up the |arc_time| into two keyframes (-arc_size to 0, then 0 to - // arc_size). - int64_t arc_size = 270; - base::TimeDelta arc_time = base::TimeDelta::FromMilliseconds(666); - double arc_size_progress = static_cast<double>(elapsed_time.InMicroseconds() % - arc_time.InMicroseconds()) / - arc_time.InMicroseconds(); - // This tween is equivalent to cubic-bezier(0.4, 0.0, 0.2, 1). - double sweep = - arc_size * gfx::Tween::CalculateValue(gfx::Tween::FAST_OUT_SLOW_IN, - arc_size_progress); - int64_t sweep_keyframe = (elapsed_time / arc_time) % 2; - if (sweep_keyframe == 0) - sweep -= arc_size; - - // This part makes sure the sweep is at least 5 degrees long. Roughly - // equivalent to the "magic constants" in SVG's fillunfill animation. - const double min_sweep_length = 5.0; - if (sweep >= 0.0 && sweep < min_sweep_length) { - start_angle -= (min_sweep_length - sweep); - sweep = min_sweep_length; - } else if (sweep <= 0.0 && sweep > -min_sweep_length) { - start_angle += (-min_sweep_length - sweep); - sweep = -min_sweep_length; - } - - // To keep the sweep smooth, we have an additional rotation after each - // |arc_time| period has elapsed. See SVG's 'rot' animation. - int64_t rot_keyframe = (elapsed_time / (arc_time * 2)) % 4; - PaintArc(canvas, start_angle + rot_keyframe * arc_size, sweep); -} - -void MaterialThrobber::PaintWaiting(gfx::Canvas* canvas) { - // Calculate start and end points. The angles are counter-clockwise because - // the throbber spins counter-clockwise. The finish angle starts at 12 o'clock - // (90 degrees) and rotates steadily. The start angle trails 180 degrees - // behind, except for the first half revolution, when it stays at 12 o'clock. - base::TimeDelta revolution_time = base::TimeDelta::FromMilliseconds(1320); - base::TimeDelta elapsed_time = base::TimeTicks::Now() - start_time(); - int64_t twelve_oclock = 90; - int64_t finish_angle = twelve_oclock + 360 * elapsed_time / revolution_time; - int64_t start_angle = std::max(finish_angle - 180, twelve_oclock); - - // Negate the angles to convert to the clockwise numbers Skia expects. - PaintArc(canvas, -start_angle, -(finish_angle - start_angle)); -} - -void MaterialThrobber::PaintArc(gfx::Canvas* canvas, - SkScalar start_angle, - SkScalar sweep) { - gfx::Rect bounds = GetContentsBounds(); - // Inset by half the stroke width to make sure the whole arc is inside - // the visible rect. - SkScalar stroke_width = SkIntToScalar(bounds.width()) / 10.0; - gfx::Rect oval = bounds; - int inset = SkScalarCeilToInt(stroke_width / 2.0); - oval.Inset(inset, inset); - - SkPath path; - path.arcTo(gfx::RectToSkRect(oval), start_angle, sweep, true); - - SkPaint paint; - // TODO(estade): find a place for this color. - paint.setColor(SkColorSetRGB(0x42, 0x85, 0xF4)); - paint.setStrokeCap(SkPaint::kRound_Cap); - paint.setStrokeWidth(stroke_width); - paint.setStyle(SkPaint::kStroke_Style); - paint.setAntiAlias(true); - canvas->DrawPath(path, paint); -} - } // namespace views diff --git a/ui/views/controls/throbber.h b/ui/views/controls/throbber.h index 8c8bb91..c5bed81 100644 --- a/ui/views/controls/throbber.h +++ b/ui/views/controls/throbber.h @@ -21,20 +21,15 @@ namespace views { class VIEWS_EXPORT Throbber : public View { public: - // |frame_time_ms| is the amount of time that should elapse between frames - // (in milliseconds) - // If |paint_while_stopped| is false, this view will be invisible when not - // running. - Throbber(int frame_time_ms, bool paint_while_stopped); - Throbber(int frame_time_ms, bool paint_while_stopped, gfx::ImageSkia* frames); + Throbber(); ~Throbber() override; - // Start and stop the throbber animation + // Start and stop the throbber animation. virtual void Start(); virtual void Stop(); - // Set custom throbber frames. Otherwise IDR_THROBBER is loaded. - void SetFrames(const gfx::ImageSkia* frames); + // Stop spinning and, if checked is true, display a checkmark. + void SetChecked(bool checked); // Overridden from View: gfx::Size GetPreferredSize() const override; @@ -43,16 +38,17 @@ class VIEWS_EXPORT Throbber : public View { protected: // Specifies whether the throbber is currently animating or not bool IsRunning() const; - base::TimeTicks start_time() const { return start_time_; } private: - bool paint_while_stopped_; - int frame_count_; // How many frames we have. base::TimeTicks start_time_; // Time when Start was called. - const gfx::ImageSkia* frames_; // Frames images. - base::TimeDelta frame_time_; // How long one frame is displayed. base::RepeatingTimer<Throbber> timer_; // Used to schedule Run calls. + // Whether or not we should display a checkmark. + bool checked_; + + // The checkmark image. Will be null until it's used (if ever). + const gfx::ImageSkia* checkmark_; + DISALLOW_COPY_AND_ASSIGN(Throbber); }; @@ -62,8 +58,7 @@ class VIEWS_EXPORT Throbber : public View { // a small amount of work time has passed. class VIEWS_EXPORT SmoothedThrobber : public Throbber { public: - explicit SmoothedThrobber(int frame_delay_ms); - SmoothedThrobber(int frame_delay_ms, gfx::ImageSkia* frames); + SmoothedThrobber(); ~SmoothedThrobber() override; void Start() override; @@ -93,49 +88,6 @@ class VIEWS_EXPORT SmoothedThrobber : public Throbber { DISALLOW_COPY_AND_ASSIGN(SmoothedThrobber); }; -// A throbber that follows material design guidelines. This is a specialization -// of Throbber for now, but should probably be folded into/replace Throbber -// eventually. -class VIEWS_EXPORT MaterialThrobber : public Throbber { - public: - MaterialThrobber(); - ~MaterialThrobber() override; - - // Stop spinning and, if checked is true, display a checkmark. - void SetChecked(bool checked); - - void set_preferred_diameter(int diameter) { preferred_diameter_ = diameter; } - - // View implementation. - gfx::Size GetPreferredSize() const override; - int GetHeightForWidth(int w) const override; - void OnPaint(gfx::Canvas* canvas) override; - - private: - // Paints this throbber in the "waiting" state, for example when waiting for - // an initial network response. - void PaintWaiting(gfx::Canvas* canvas); - - // Paints this throbber in its normal state. Corresponds to MD throbber. - void PaintSpinning(gfx::Canvas* canvas); - - // Common painting code for PaintWaiting and PaintSpinning. - void PaintArc(gfx::Canvas* canvas, SkScalar start_angle, SkScalar sweep); - - // The preferred width and height for this view. Zero indicates the size is - // set by the parent class (i.e. matches the size of the pre-material - // sprites). - int preferred_diameter_; - - // Whether or not we should display a checkmark. - bool checked_; - - // The checkmark image. Will be null until it's used (if ever). - const gfx::ImageSkia* checkmark_; - - DISALLOW_COPY_AND_ASSIGN(MaterialThrobber); -}; - } // namespace views #endif // UI_VIEWS_CONTROLS_THROBBER_H_ diff --git a/ui/views/examples/throbber_example.cc b/ui/views/examples/throbber_example.cc index 8582a2b..3333e5b 100644 --- a/ui/views/examples/throbber_example.cc +++ b/ui/views/examples/throbber_example.cc @@ -13,15 +13,10 @@ namespace examples { namespace { -// Time in ms per throbber frame. -const int kThrobberFrameMs = 60; - class ThrobberView : public View { public: - ThrobberView() { - throbber_ = new Throbber(kThrobberFrameMs, false); + ThrobberView() : throbber_(new Throbber()) { AddChildView(throbber_); - throbber_->SetVisible(true); throbber_->Start(); } @@ -30,11 +25,10 @@ class ThrobberView : public View { } void Layout() override { - View* child = child_at(0); - gfx::Size ps = child->GetPreferredSize(); - child->SetBounds((width() - ps.width()) / 2, - (height() - ps.height()) / 2, - ps.width(), ps.height()); + int diameter = 64; + throbber_->SetBounds((width() - diameter) / 2, + (height() - diameter) / 2, + diameter, diameter); SizeToPreferredSize(); } |