ExtensionSyncService: listen for relevant changes instead of being explicitly called...
[chromium-blink-merge.git] / chrome / browser / chromeos / ui / accessibility_focus_ring_controller.cc
blob02bd975dc05fb9e90f468b5b7ee0b11e80b46a09
1 // Copyright 2014 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.
5 #include "chrome/browser/chromeos/ui/accessibility_focus_ring_controller.h"
7 #include "ash/display/window_tree_host_manager.h"
8 #include "ash/shell.h"
9 #include "base/logging.h"
10 #include "chrome/browser/chromeos/ui/focus_ring_layer.h"
11 #include "ui/gfx/screen.h"
13 namespace chromeos {
15 namespace {
17 // The number of pixels the focus ring is outset from the object it outlines,
18 // which also determines the border radius of the rounded corners.
19 // TODO(dmazzoni): take display resolution into account.
20 const int kAccessibilityFocusRingMargin = 7;
22 // Time to transition between one location and the next.
23 const int kTransitionTimeMilliseconds = 300;
25 // A Region is an unordered collection of Rects that maintains its
26 // bounding box. Used in the middle of an algorithm that groups
27 // adjacent and overlapping rects.
28 struct Region {
29 explicit Region(gfx::Rect initial_rect) {
30 bounds = initial_rect;
31 rects.push_back(initial_rect);
33 gfx::Rect bounds;
34 std::vector<gfx::Rect> rects;
37 } // namespace
39 // static
40 AccessibilityFocusRingController*
41 AccessibilityFocusRingController::GetInstance() {
42 return Singleton<AccessibilityFocusRingController>::get();
45 AccessibilityFocusRingController::AccessibilityFocusRingController()
46 : compositor_(nullptr) {
49 AccessibilityFocusRingController::~AccessibilityFocusRingController() {
52 void AccessibilityFocusRingController::SetFocusRing(
53 const std::vector<gfx::Rect>& rects) {
54 rects_ = rects;
55 Update();
58 void AccessibilityFocusRingController::Update() {
59 previous_rings_.swap(rings_);
60 rings_.clear();
61 RectsToRings(rects_, &rings_);
62 layers_.resize(rings_.size());
63 for (size_t i = 0; i < rings_.size(); ++i) {
64 if (!layers_[i])
65 layers_[i] = new AccessibilityFocusRingLayer(this);
67 if (i > 0) {
68 // Focus rings other than the first one don't animate.
69 layers_[i]->Set(rings_[i]);
70 continue;
73 gfx::Rect bounds = rings_[0].GetBounds();
74 gfx::Display display =
75 gfx::Screen::GetNativeScreen()->GetDisplayMatching(bounds);
76 aura::Window* root_window = ash::Shell::GetInstance()
77 ->window_tree_host_manager()
78 ->GetRootWindowForDisplayId(display.id());
79 ui::Compositor* compositor = root_window->layer()->GetCompositor();
80 if (!compositor || root_window != layers_[0]->root_window()) {
81 layers_[0]->Set(rings_[0]);
82 if (compositor_ && compositor_->HasAnimationObserver(this)) {
83 compositor_->RemoveAnimationObserver(this);
84 compositor_ = nullptr;
86 continue;
89 focus_change_time_ = base::TimeTicks::Now();
90 if (!compositor->HasAnimationObserver(this)) {
91 compositor_ = compositor;
92 compositor_->AddAnimationObserver(this);
97 void AccessibilityFocusRingController::RectsToRings(
98 const std::vector<gfx::Rect>& src_rects,
99 std::vector<AccessibilityFocusRing>* rings) const {
100 if (src_rects.empty())
101 return;
103 // Give all of the rects a margin.
104 std::vector<gfx::Rect> rects;
105 rects.resize(src_rects.size());
106 for (size_t i = 0; i < src_rects.size(); ++i) {
107 rects[i] = src_rects[i];
108 rects[i].Inset(-GetMargin(), -GetMargin());
111 // Split the rects into contiguous regions.
112 std::vector<Region> regions;
113 regions.push_back(Region(rects[0]));
114 for (size_t i = 1; i < rects.size(); ++i) {
115 bool found = false;
116 for (size_t j = 0; j < regions.size(); ++j) {
117 if (Intersects(rects[i], regions[j].bounds)) {
118 regions[j].rects.push_back(rects[i]);
119 regions[j].bounds.Union(rects[i]);
120 found = true;
123 if (!found) {
124 regions.push_back(Region(rects[i]));
128 // Keep merging regions that intersect.
129 // TODO(dmazzoni): reduce the worst-case complexity! This appears like
130 // it could be O(n^3), make sure it's not in practice.
131 bool merged;
132 do {
133 merged = false;
134 for (size_t i = 0; i < regions.size() - 1 && !merged; ++i) {
135 for (size_t j = i + 1; j < regions.size() && !merged; ++j) {
136 if (Intersects(regions[i].bounds, regions[j].bounds)) {
137 regions[i].rects.insert(regions[i].rects.end(),
138 regions[j].rects.begin(),
139 regions[j].rects.end());
140 regions[i].bounds.Union(regions[j].bounds);
141 regions.erase(regions.begin() + j);
142 merged = true;
146 } while (merged);
148 for (size_t i = 0; i < regions.size(); ++i) {
149 std::sort(regions[i].rects.begin(), regions[i].rects.end());
150 rings->push_back(RingFromSortedRects(regions[i].rects));
154 int AccessibilityFocusRingController::GetMargin() const {
155 return kAccessibilityFocusRingMargin;
158 // Given a vector of rects that all overlap, already sorted from top to bottom
159 // and left to right, split them into three shapes covering the top, middle,
160 // and bottom of a "paragraph shape".
162 // Input:
164 // +---+---+
165 // | 1 | 2 |
166 // +---------------------+---+---+
167 // | 3 |
168 // +--------+---------------+----+
169 // | 4 | 5 |
170 // +--------+---------------+--+
171 // | 6 |
172 // +---------+-----------------+
173 // | 7 |
174 // +---------+
176 // Output:
178 // +-------+
179 // | Top |
180 // +---------------------+-------+
181 // | |
182 // | |
183 // | Middle |
184 // | |
185 // | |
186 // +---------+-------------------+
187 // | Bottom |
188 // +---------+
190 // When there's no clear "top" or "bottom" segment, split the overall rect
191 // evenly so that some of the area still fits into the "top" and "bottom"
192 // segments.
193 void AccessibilityFocusRingController::SplitIntoParagraphShape(
194 const std::vector<gfx::Rect>& rects,
195 gfx::Rect* top,
196 gfx::Rect* middle,
197 gfx::Rect* bottom) const {
198 size_t n = rects.size();
200 // Figure out how many rects belong in the top portion.
201 gfx::Rect top_rect = rects[0];
202 int top_middle = (top_rect.y() + top_rect.bottom()) / 2;
203 size_t top_count = 1;
204 while (top_count < n && rects[top_count].y() < top_middle) {
205 top_rect.Union(rects[top_count]);
206 top_middle = (top_rect.y() + top_rect.bottom()) / 2;
207 top_count++;
210 // Figure out how many rects belong in the bottom portion.
211 gfx::Rect bottom_rect = rects[n - 1];
212 int bottom_middle = (bottom_rect.y() + bottom_rect.bottom()) / 2;
213 size_t bottom_count = std::min(static_cast<size_t>(1), n - top_count);
214 while (bottom_count + top_count < n &&
215 rects[n - bottom_count - 1].bottom() > bottom_middle) {
216 bottom_rect.Union(rects[n - bottom_count - 1]);
217 bottom_middle = (bottom_rect.y() + bottom_rect.bottom()) / 2;
218 bottom_count++;
221 // Whatever's left goes to the middle rect, but if there's no middle or
222 // bottom rect, split the existing rects evenly to make one.
223 gfx::Rect middle_rect;
224 if (top_count + bottom_count < n) {
225 middle_rect = rects[top_count];
226 for (size_t i = top_count + 1; i < n - bottom_count; i++)
227 middle_rect.Union(rects[i]);
228 } else if (bottom_count > 0) {
229 gfx::Rect enclosing_rect = top_rect;
230 enclosing_rect.Union(bottom_rect);
231 int middle_top = (top_rect.y() + top_rect.bottom() * 2) / 3;
232 int middle_bottom = (bottom_rect.y() * 2 + bottom_rect.bottom()) / 3;
233 top_rect.set_height(middle_top - top_rect.y());
234 bottom_rect.set_height(bottom_rect.bottom() - middle_bottom);
235 bottom_rect.set_y(middle_bottom);
236 middle_rect = gfx::Rect(enclosing_rect.x(), middle_top,
237 enclosing_rect.width(), middle_bottom - middle_top);
238 } else {
239 int middle_top = (top_rect.y() * 2 + top_rect.bottom()) / 3;
240 int middle_bottom = (top_rect.y() + top_rect.bottom() * 2) / 3;
241 middle_rect = gfx::Rect(top_rect.x(), middle_top,
242 top_rect.width(), middle_bottom - middle_top);
243 bottom_rect = gfx::Rect(
244 top_rect.x(), middle_bottom,
245 top_rect.width(), top_rect.bottom() - middle_bottom);
246 top_rect.set_height(middle_top - top_rect.y());
249 if (middle_rect.y() > top_rect.bottom()) {
250 middle_rect.set_height(
251 middle_rect.height() + middle_rect.y() - top_rect.bottom());
252 middle_rect.set_y(top_rect.bottom());
255 if (middle_rect.bottom() < bottom_rect.y()) {
256 middle_rect.set_height(bottom_rect.y() - middle_rect.y());
259 *top = top_rect;
260 *middle = middle_rect;
261 *bottom = bottom_rect;
264 AccessibilityFocusRing AccessibilityFocusRingController::RingFromSortedRects(
265 const std::vector<gfx::Rect>& rects) const {
266 if (rects.size() == 1)
267 return AccessibilityFocusRing::CreateWithRect(rects[0], GetMargin());
269 gfx::Rect top;
270 gfx::Rect middle;
271 gfx::Rect bottom;
272 SplitIntoParagraphShape(rects, &top, &middle, &bottom);
274 return AccessibilityFocusRing::CreateWithParagraphShape(
275 top, middle, bottom, GetMargin());
278 bool AccessibilityFocusRingController::Intersects(
279 const gfx::Rect& r1, const gfx::Rect& r2) const {
280 int slop = GetMargin();
281 return (r2.x() <= r1.right() + slop &&
282 r2.right() >= r1.x() - slop &&
283 r2.y() <= r1.bottom() + slop &&
284 r2.bottom() >= r1.y() - slop);
287 void AccessibilityFocusRingController::OnDeviceScaleFactorChanged() {
288 Update();
291 void AccessibilityFocusRingController::OnAnimationStep(
292 base::TimeTicks timestamp) {
293 if (rings_.empty())
294 return;
296 CHECK(compositor_);
297 CHECK(!rings_.empty());
298 CHECK(!layers_.empty());
299 CHECK(layers_[0]);
301 // It's quite possible for the first 1 or 2 animation frames to be
302 // for a timestamp that's earlier than the time we received the
303 // focus change, so we just treat those as a delta of zero.
304 if (timestamp < focus_change_time_)
305 timestamp = focus_change_time_;
307 base::TimeDelta delta = timestamp - focus_change_time_;
308 base::TimeDelta transition_time =
309 base::TimeDelta::FromMilliseconds(kTransitionTimeMilliseconds);
310 if (delta >= transition_time) {
311 layers_[0]->Set(rings_[0]);
312 compositor_->RemoveAnimationObserver(this);
313 compositor_ = nullptr;
314 return;
317 double fraction = delta.InSecondsF() / transition_time.InSecondsF();
319 // Ease-in effect.
320 fraction = pow(fraction, 0.3);
322 layers_[0]->Set(AccessibilityFocusRing::Interpolate(
323 previous_rings_[0], rings_[0], fraction));
326 void AccessibilityFocusRingController::OnCompositingShuttingDown(
327 ui::Compositor* compositor) {
328 DCHECK_EQ(compositor_, compositor);
329 compositor_->RemoveAnimationObserver(this);
330 compositor_ = nullptr;
333 } // namespace chromeos