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"
9 #include "base/logging.h"
10 #include "chrome/browser/chromeos/ui/focus_ring_layer.h"
11 #include "ui/gfx/screen.h"
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.
29 explicit Region(gfx::Rect initial_rect
) {
30 bounds
= initial_rect
;
31 rects
.push_back(initial_rect
);
34 std::vector
<gfx::Rect
> rects
;
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
) {
58 void AccessibilityFocusRingController::Update() {
59 previous_rings_
.swap(rings_
);
61 RectsToRings(rects_
, &rings_
);
62 layers_
.resize(rings_
.size());
63 for (size_t i
= 0; i
< rings_
.size(); ++i
) {
65 layers_
[i
] = new AccessibilityFocusRingLayer(this);
68 // Focus rings other than the first one don't animate.
69 layers_
[i
]->Set(rings_
[i
]);
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;
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())
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
) {
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
]);
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.
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
);
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".
166 // +---------------------+---+---+
168 // +--------+---------------+----+
170 // +--------+---------------+--+
172 // +---------+-----------------+
180 // +---------------------+-------+
186 // +---------+-------------------+
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"
193 void AccessibilityFocusRingController::SplitIntoParagraphShape(
194 const std::vector
<gfx::Rect
>& rects
,
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;
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;
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
);
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());
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());
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() {
291 void AccessibilityFocusRingController::OnAnimationStep(
292 base::TimeTicks timestamp
) {
297 CHECK(!rings_
.empty());
298 CHECK(!layers_
.empty());
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;
317 double fraction
= delta
.InSecondsF() / transition_time
.InSecondsF();
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