Commit cac4d247 authored by dmazzoni's avatar dmazzoni Committed by Commit bot

Animate the accessibility focus ring and fix some minor visual issues.

The look of the focus ring now closely matches the one drawn by the
ChromeVox content script using CSS, and it smoothly animates from one
location to the next.

BUG=314889

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

Cr-Commit-Position: refs/heads/master@{#296765}
parent 85758c75
......@@ -4,20 +4,40 @@
#include "chrome/browser/chromeos/ui/accessibility_focus_ring.h"
#include "base/logging.h"
namespace chromeos {
// static
AccessibilityFocusRing AccessibilityFocusRing::CreateWithRect(
const gfx::Rect& bounds, int margin) {
// Compute the height of the top and bottom cap.
int cap_height = std::min(bounds.height() / 2, margin * 2);
gfx::Rect top(bounds.x(), bounds.y(),
bounds.width(), bounds.height() / 4);
bounds.width(), cap_height);
gfx::Rect bottom(bounds.x(), bounds.bottom() - cap_height,
bounds.width(), cap_height);
gfx::Rect body(bounds.x(), top.bottom(),
bounds.width(), bounds.height() / 2);
gfx::Rect bottom(bounds.x(), body.bottom(),
bounds.width(), bounds.bottom() - body.bottom());
bounds.width(), bottom.y() - top.bottom());
return CreateWithParagraphShape(top, body, bottom, margin);
}
// static
AccessibilityFocusRing AccessibilityFocusRing::Interpolate(
const AccessibilityFocusRing& r1,
const AccessibilityFocusRing& r2,
double fraction) {
AccessibilityFocusRing dst;
for (int i = 0; i < 36; ++i) {
dst.points[i] = gfx::Point(
r1.points[i].x() * (1 - fraction) + r2.points[i].x() * fraction,
r1.points[i].y() * (1 - fraction) + r2.points[i].y() * fraction);
}
return dst;
}
// static
AccessibilityFocusRing AccessibilityFocusRing::CreateWithParagraphShape(
const gfx::Rect& orig_top_line,
......@@ -28,6 +48,9 @@ AccessibilityFocusRing AccessibilityFocusRing::CreateWithParagraphShape(
gfx::Rect middle = orig_body;
gfx::Rect bottom = orig_bottom_line;
int min_height = std::min(top.height(), bottom.height());
margin = std::min(margin, min_height / 2);
if (top.x() <= middle.x() + 2 * margin) {
top.set_width(top.width() + top.x() - middle.x());
top.set_x(middle.x());
......@@ -108,7 +131,7 @@ AccessibilityFocusRing AccessibilityFocusRing::CreateWithParagraphShape(
gfx::Rect AccessibilityFocusRing::GetBounds() const {
gfx::Point top_left = points[0];
gfx::Point bottom_right = points[0];
for (size_t i = 1; i < 36; i++) {
for (size_t i = 1; i < 36; ++i) {
top_left.SetToMin(points[i]);
bottom_right.SetToMax(points[i]);
}
......
......@@ -72,6 +72,13 @@ struct AccessibilityFocusRing {
static AccessibilityFocusRing CreateWithRect(
const gfx::Rect& bounds, int margin);
// Returns a ring where 0.0 returns r1, 1.0 returns r2, and any number
// in-between interpolates linearly between them.
static AccessibilityFocusRing Interpolate(
const AccessibilityFocusRing& r1,
const AccessibilityFocusRing& r2,
double fraction);
// Construct an AccessibilityFocusRing that outlines a paragraph-shaped
// object.
static AccessibilityFocusRing CreateWithParagraphShape(
......@@ -80,8 +87,9 @@ struct AccessibilityFocusRing {
const gfx::Rect& bottom_line,
int margin);
gfx::Point points[36];
gfx::Rect GetBounds() const;
gfx::Point points[36];
};
} // namespace chromeos
......
......@@ -4,7 +4,11 @@
#include "chrome/browser/chromeos/ui/accessibility_focus_ring_controller.h"
#include "ash/display/display_controller.h"
#include "ash/shell.h"
#include "base/logging.h"
#include "chrome/browser/chromeos/ui/focus_ring_layer.h"
#include "ui/gfx/screen.h"
namespace chromeos {
......@@ -13,7 +17,10 @@ namespace {
// The number of pixels the focus ring is outset from the object it outlines,
// which also determines the border radius of the rounded corners.
// TODO(dmazzoni): take display resolution into account.
const int kAccessibilityFocusRingMargin = 16;
const int kAccessibilityFocusRingMargin = 7;
// Time to transition between one location and the next.
const int kTransitionTimeMilliseconds = 300;
// A Region is an unordered collection of Rects that maintains its
// bounding box. Used in the middle of an algorithm that groups
......@@ -35,7 +42,8 @@ AccessibilityFocusRingController*
return Singleton<AccessibilityFocusRingController>::get();
}
AccessibilityFocusRingController::AccessibilityFocusRingController() {
AccessibilityFocusRingController::AccessibilityFocusRingController()
: compositor_(NULL) {
}
AccessibilityFocusRingController::~AccessibilityFocusRingController() {
......@@ -48,22 +56,57 @@ void AccessibilityFocusRingController::SetFocusRing(
}
void AccessibilityFocusRingController::Update() {
previous_rings_.swap(rings_);
rings_.clear();
RectsToRings(rects_, &rings_);
layers_.resize(rings_.size());
for (size_t i = 0; i < rings_.size(); ++i) {
if (!layers_[i])
layers_[i].reset(new AccessibilityFocusRingLayer(this));
if (i > 0) {
// Focus rings other than the first one don't animate.
layers_[i]->Set(rings_[i]);
continue;
}
if (!main_focus_ring_layer_)
main_focus_ring_layer_.reset(new AccessibilityFocusRingLayer(this));
gfx::Rect bounds = rings_[0].GetBounds();
gfx::Display display =
gfx::Screen::GetNativeScreen()->GetDisplayMatching(bounds);
aura::Window* root_window = ash::Shell::GetInstance()->display_controller()
->GetRootWindowForDisplayId(display.id());
ui::Compositor* compositor = root_window->layer()->GetCompositor();
if (!compositor || root_window != layers_[0]->root_window()) {
layers_[0]->Set(rings_[0]);
if (compositor_ && compositor_->HasAnimationObserver(this)) {
compositor_->RemoveAnimationObserver(this);
compositor_ = NULL;
}
continue;
}
if (!rings_.empty())
main_focus_ring_layer_->Set(rings_[0]);
focus_change_time_ = base::TimeTicks::Now();
if (!compositor->HasAnimationObserver(this)) {
compositor_ = compositor;
compositor_->AddAnimationObserver(this);
}
}
}
void AccessibilityFocusRingController::RectsToRings(
const std::vector<gfx::Rect>& rects,
const std::vector<gfx::Rect>& src_rects,
std::vector<AccessibilityFocusRing>* rings) const {
if (rects.empty())
if (src_rects.empty())
return;
// Give all of the rects a margin.
std::vector<gfx::Rect> rects;
rects.resize(src_rects.size());
for (size_t i = 0; i < src_rects.size(); ++i) {
rects[i] = src_rects[i];
rects[i].Inset(-GetMargin(), -GetMargin());
}
// Split the rects into contiguous regions.
std::vector<Region> regions;
regions.push_back(Region(rects[0]));
......@@ -244,4 +287,33 @@ void AccessibilityFocusRingController::OnDeviceScaleFactorChanged() {
Update();
}
void AccessibilityFocusRingController::OnAnimationStep(
base::TimeTicks timestamp) {
if (rings_.empty())
return;
CHECK(compositor_);
CHECK(!rings_.empty());
CHECK(!layers_.empty());
CHECK(layers_[0]);
base::TimeDelta delta = timestamp - focus_change_time_;
base::TimeDelta transition_time =
base::TimeDelta::FromMilliseconds(kTransitionTimeMilliseconds);
if (delta >= transition_time) {
layers_[0]->Set(rings_[0]);
compositor_->RemoveAnimationObserver(this);
compositor_ = NULL;
return;
}
double fraction = delta.InSecondsF() / transition_time.InSecondsF();
// Ease-in effect.
fraction = pow(fraction, 0.3);
layers_[0]->Set(AccessibilityFocusRing::Interpolate(
previous_rings_[0], rings_[0], fraction));
}
} // namespace chromeos
......@@ -8,14 +8,22 @@
#include "base/basictypes.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/singleton.h"
#include "base/time/time.h"
#include "chrome/browser/chromeos/ui/accessibility_focus_ring_layer.h"
#include "ui/compositor/compositor_animation_observer.h"
#include "ui/gfx/rect.h"
namespace ui {
class Compositor;
}
namespace chromeos {
// AccessibilityFocusRingController manages a custom focus ring (or multiple
// rings) for accessibility.
class AccessibilityFocusRingController : public FocusRingLayerDelegate {
class AccessibilityFocusRingController
: public FocusRingLayerDelegate,
public ui::CompositorAnimationObserver {
public:
// Get the single instance of this class.
static AccessibilityFocusRingController* GetInstance();
......@@ -42,6 +50,9 @@ class AccessibilityFocusRingController : public FocusRingLayerDelegate {
// FocusRingLayerDelegate.
virtual void OnDeviceScaleFactorChanged() OVERRIDE;
// CompositorAnimationObserver.
virtual void OnAnimationStep(base::TimeTicks timestamp) OVERRIDE;
void Update();
AccessibilityFocusRing RingFromSortedRects(
......@@ -54,9 +65,11 @@ class AccessibilityFocusRingController : public FocusRingLayerDelegate {
bool Intersects(const gfx::Rect& r1, const gfx::Rect& r2) const;
std::vector<gfx::Rect> rects_;
std::vector<AccessibilityFocusRing> previous_rings_;
std::vector<AccessibilityFocusRing> rings_;
scoped_ptr<AccessibilityFocusRingLayer> main_focus_ring_layer_;
std::vector<scoped_ptr<AccessibilityFocusRingLayer> > extra_layers_;
std::vector<scoped_ptr<AccessibilityFocusRingLayer> > layers_;
base::TimeTicks focus_change_time_;
ui::Compositor* compositor_;
friend struct DefaultSingletonTraits<AccessibilityFocusRingController>;
......
......@@ -11,7 +11,10 @@ namespace chromeos {
class TestableAccessibilityFocusRingController
: public AccessibilityFocusRingController {
public:
TestableAccessibilityFocusRingController() {}
TestableAccessibilityFocusRingController() {
// By default use an easy round number for testing.
margin_ = 10;
}
virtual ~TestableAccessibilityFocusRingController() {}
void RectsToRings(const std::vector<gfx::Rect>& rects,
......@@ -19,10 +22,12 @@ class TestableAccessibilityFocusRingController
AccessibilityFocusRingController::RectsToRings(rects, rings);
}
// Return an easy round number for testing.
virtual int GetMargin() const OVERRIDE {
return 10;
return margin_;
}
private:
int margin_;
};
class AccessibilityFocusRingControllerTest : public testing::Test {
......@@ -31,6 +36,11 @@ class AccessibilityFocusRingControllerTest : public testing::Test {
virtual ~AccessibilityFocusRingControllerTest() {}
protected:
gfx::Rect AddMargin(gfx::Rect r) {
r.Inset(-controller_.GetMargin(), -controller_.GetMargin());
return r;
}
TestableAccessibilityFocusRingController controller_;
};
......@@ -42,7 +52,7 @@ TEST_F(AccessibilityFocusRingControllerTest, RectsToRingsSimpleBoundsCheck) {
std::vector<AccessibilityFocusRing> rings;
controller_.RectsToRings(rects, &rings);
ASSERT_EQ(1U, rings.size());
ASSERT_EQ(rects[0], rings[0].GetBounds());
ASSERT_EQ(AddMargin(rects[0]), rings[0].GetBounds());
}
TEST_F(AccessibilityFocusRingControllerTest, RectsToRingsVerticalStack) {
......@@ -54,7 +64,7 @@ TEST_F(AccessibilityFocusRingControllerTest, RectsToRingsVerticalStack) {
std::vector<AccessibilityFocusRing> rings;
controller_.RectsToRings(rects, &rings);
ASSERT_EQ(1U, rings.size());
ASSERT_EQ(gfx::Rect(10, 10, 60, 60), rings[0].GetBounds());
ASSERT_EQ(AddMargin(gfx::Rect(10, 10, 60, 60)), rings[0].GetBounds());
}
TEST_F(AccessibilityFocusRingControllerTest, RectsToRingsHorizontalStack) {
......@@ -66,58 +76,58 @@ TEST_F(AccessibilityFocusRingControllerTest, RectsToRingsHorizontalStack) {
std::vector<AccessibilityFocusRing> rings;
controller_.RectsToRings(rects, &rings);
ASSERT_EQ(1U, rings.size());
ASSERT_EQ(gfx::Rect(10, 10, 120, 30), rings[0].GetBounds());
ASSERT_EQ(AddMargin(gfx::Rect(10, 10, 120, 30)), rings[0].GetBounds());
}
TEST_F(AccessibilityFocusRingControllerTest, RectsToRingsParagraphShape) {
// Given a simple paragraph shape, make sure we get something that
// outlines it correctly.
std::vector<gfx::Rect> rects;
rects.push_back(gfx::Rect(0, 0, 200, 100));
rects.push_back(gfx::Rect(0, 100, 600, 300));
rects.push_back(gfx::Rect(400, 400, 200, 100));
rects.push_back(gfx::Rect(10, 10, 180, 80));
rects.push_back(gfx::Rect(10, 110, 580, 280));
rects.push_back(gfx::Rect(410, 410, 180, 80));
std::vector<AccessibilityFocusRing> rings;
controller_.RectsToRings(rects, &rings);
ASSERT_EQ(1U, rings.size());
ASSERT_EQ(gfx::Rect(0, 0, 600, 500), rings[0].GetBounds());
EXPECT_EQ(gfx::Rect(0, 0, 600, 500), rings[0].GetBounds());
const gfx::Point* points = rings[0].points;
ASSERT_EQ(gfx::Point(0, 90), points[0]);
ASSERT_EQ(gfx::Point(0, 10), points[1]);
ASSERT_EQ(gfx::Point(0, 0), points[2]);
ASSERT_EQ(gfx::Point(10, 0), points[3]);
ASSERT_EQ(gfx::Point(190, 0), points[4]);
ASSERT_EQ(gfx::Point(200, 0), points[5]);
ASSERT_EQ(gfx::Point(200, 10), points[6]);
ASSERT_EQ(gfx::Point(200, 90), points[7]);
ASSERT_EQ(gfx::Point(200, 100), points[8]);
ASSERT_EQ(gfx::Point(210, 100), points[9]);
ASSERT_EQ(gfx::Point(590, 100), points[10]);
ASSERT_EQ(gfx::Point(600, 100), points[11]);
ASSERT_EQ(gfx::Point(600, 110), points[12]);
ASSERT_EQ(gfx::Point(600, 390), points[13]);
ASSERT_EQ(gfx::Point(600, 400), points[14]);
ASSERT_EQ(gfx::Point(600, 400), points[15]);
ASSERT_EQ(gfx::Point(600, 400), points[16]);
ASSERT_EQ(gfx::Point(600, 400), points[17]);
ASSERT_EQ(gfx::Point(600, 410), points[18]);
ASSERT_EQ(gfx::Point(600, 490), points[19]);
ASSERT_EQ(gfx::Point(600, 500), points[20]);
ASSERT_EQ(gfx::Point(590, 500), points[21]);
ASSERT_EQ(gfx::Point(410, 500), points[22]);
ASSERT_EQ(gfx::Point(400, 500), points[23]);
ASSERT_EQ(gfx::Point(400, 490), points[24]);
ASSERT_EQ(gfx::Point(400, 410), points[25]);
ASSERT_EQ(gfx::Point(400, 400), points[26]);
ASSERT_EQ(gfx::Point(390, 400), points[27]);
ASSERT_EQ(gfx::Point(10, 400), points[28]);
ASSERT_EQ(gfx::Point(0, 400), points[29]);
ASSERT_EQ(gfx::Point(0, 390), points[30]);
ASSERT_EQ(gfx::Point(0, 110), points[31]);
ASSERT_EQ(gfx::Point(0, 100), points[32]);
ASSERT_EQ(gfx::Point(0, 100), points[33]);
ASSERT_EQ(gfx::Point(0, 100), points[34]);
ASSERT_EQ(gfx::Point(0, 100), points[35]);
EXPECT_EQ(gfx::Point(0, 90), points[0]);
EXPECT_EQ(gfx::Point(0, 10), points[1]);
EXPECT_EQ(gfx::Point(0, 0), points[2]);
EXPECT_EQ(gfx::Point(10, 0), points[3]);
EXPECT_EQ(gfx::Point(190, 0), points[4]);
EXPECT_EQ(gfx::Point(200, 0), points[5]);
EXPECT_EQ(gfx::Point(200, 10), points[6]);
EXPECT_EQ(gfx::Point(200, 90), points[7]);
EXPECT_EQ(gfx::Point(200, 100), points[8]);
EXPECT_EQ(gfx::Point(210, 100), points[9]);
EXPECT_EQ(gfx::Point(590, 100), points[10]);
EXPECT_EQ(gfx::Point(600, 100), points[11]);
EXPECT_EQ(gfx::Point(600, 110), points[12]);
EXPECT_EQ(gfx::Point(600, 390), points[13]);
EXPECT_EQ(gfx::Point(600, 400), points[14]);
EXPECT_EQ(gfx::Point(600, 400), points[15]);
EXPECT_EQ(gfx::Point(600, 400), points[16]);
EXPECT_EQ(gfx::Point(600, 400), points[17]);
EXPECT_EQ(gfx::Point(600, 410), points[18]);
EXPECT_EQ(gfx::Point(600, 490), points[19]);
EXPECT_EQ(gfx::Point(600, 500), points[20]);
EXPECT_EQ(gfx::Point(590, 500), points[21]);
EXPECT_EQ(gfx::Point(410, 500), points[22]);
EXPECT_EQ(gfx::Point(400, 500), points[23]);
EXPECT_EQ(gfx::Point(400, 490), points[24]);
EXPECT_EQ(gfx::Point(400, 410), points[25]);
EXPECT_EQ(gfx::Point(400, 400), points[26]);
EXPECT_EQ(gfx::Point(390, 400), points[27]);
EXPECT_EQ(gfx::Point(10, 400), points[28]);
EXPECT_EQ(gfx::Point(0, 400), points[29]);
EXPECT_EQ(gfx::Point(0, 390), points[30]);
EXPECT_EQ(gfx::Point(0, 110), points[31]);
EXPECT_EQ(gfx::Point(0, 100), points[32]);
EXPECT_EQ(gfx::Point(0, 100), points[33]);
EXPECT_EQ(gfx::Point(0, 100), points[34]);
EXPECT_EQ(gfx::Point(0, 100), points[35]);
}
} // namespace chromeos
......@@ -16,7 +16,7 @@ namespace chromeos {
namespace {
// The number of pixels in the color gradient that fades to transparent.
const int kGradientWidth = 10;
const int kGradientWidth = 6;
// The color of the focus ring. In the future this might be a parameter.
const int kFocusRingColorRed = 247;
......@@ -118,10 +118,11 @@ void AccessibilityFocusRingLayer::OnPaintLayer(gfx::Canvas* canvas) {
paint.setStrokeWidth(2);
SkPath path;
for (int i = 0; i < kGradientWidth; i++) {
const int w = kGradientWidth;
for (int i = 0; i < w; ++i) {
paint.setColor(
SkColorSetARGBMacro(
255 - (255 * i / kGradientWidth),
255 * (w - i) * (w - i) / (w * w),
kFocusRingColorRed, kFocusRingColorGreen, kFocusRingColorBlue));
path = MakePath(ring_, i, offset);
canvas->DrawPath(path, paint);
......
......@@ -40,6 +40,7 @@ class FocusRingLayer : public ui::LayerDelegate {
void Set(aura::Window* root_window, const gfx::Rect& bounds);
ui::Layer* layer() { return layer_.get(); }
aura::Window* root_window() { return root_window_; }
protected:
// Updates |root_window_| and creates |layer_| if it doesn't exist,
......
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