Commit 90104a67 authored by estade's avatar estade Committed by Commit bot

material throbber, take 2

Due to a misunderstanding on my part, the initial implementation represented the "waiting" state, which is used while waiting for a network response in a Chrome tab. Most throbbers should use the "normal"/"spinning" state. This ports the CSS animations in MD_spinner_blue.svg to skia commands, and uses it in the card unmasking dialog.

After some investigation, I've realized that very few places in Chrome actually use the Throbber class. Tabs, for example, draw directly onto a canvas. So the next step will be to refactor this functionality into a ThrobberPainter class so tabs can use it. The reason I am not already doing this is I want to verify with the designers that this version looks good before I work on bringing it to more of Chrome.

BUG=259556, 473342

Review URL: https://codereview.chromium.org/1098853005

Cr-Commit-Position: refs/heads/master@{#327105}
parent 016a5fdb
......@@ -5,6 +5,7 @@
#include "ui/views/controls/throbber.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/animation/tween.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia.h"
......@@ -168,14 +169,52 @@ void MaterialThrobber::OnPaint(gfx::Canvas* canvas) {
return;
}
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);
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
......@@ -186,10 +225,23 @@ void MaterialThrobber::OnPaint(gfx::Canvas* canvas) {
int64_t finish_angle = twelve_oclock + 360 * elapsed_time / revolution_time;
int64_t start_angle = std::max(finish_angle - 180, twelve_oclock);
SkPath path;
// Negate the angles to convert to the clockwise numbers Skia expects.
path.arcTo(gfx::RectToSkRect(oval), -start_angle,
-(finish_angle - start_angle), true);
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.
......
......@@ -112,6 +112,16 @@ class VIEWS_EXPORT MaterialThrobber : public Throbber {
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).
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment