Index: chrome/browser/chromeos/ui/accessibility_focus_ring_controller.cc |
diff --git a/chrome/browser/chromeos/ui/accessibility_focus_ring_controller.cc b/chrome/browser/chromeos/ui/accessibility_focus_ring_controller.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..aaca4c5199bbd39b983223edf8f44c68596c5b8c |
--- /dev/null |
+++ b/chrome/browser/chromeos/ui/accessibility_focus_ring_controller.cc |
@@ -0,0 +1,247 @@ |
+// Copyright 2014 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 "chrome/browser/chromeos/ui/accessibility_focus_ring_controller.h" |
+ |
+#include "chrome/browser/chromeos/ui/focus_ring_layer.h" |
+ |
+namespace chromeos { |
+ |
+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; |
+ |
+// A Region is an unordered collection of Rects that maintains its |
+// bounding box. Used in the middle of an algorithm that groups |
+// adjacent and overlapping rects. |
+struct Region { |
+ explicit Region(gfx::Rect initial_rect) { |
+ bounds = initial_rect; |
+ rects.push_back(initial_rect); |
+ } |
+ gfx::Rect bounds; |
+ std::vector<gfx::Rect> rects; |
+}; |
+ |
+} // namespace |
+ |
+// static |
+AccessibilityFocusRingController* |
+ AccessibilityFocusRingController::GetInstance() { |
+ return Singleton<AccessibilityFocusRingController>::get(); |
+} |
+ |
+AccessibilityFocusRingController::AccessibilityFocusRingController() { |
+} |
+ |
+AccessibilityFocusRingController::~AccessibilityFocusRingController() { |
+} |
+ |
+void AccessibilityFocusRingController::SetFocusRing( |
+ const std::vector<gfx::Rect>& rects) { |
+ rects_ = rects; |
+ Update(); |
+} |
+ |
+void AccessibilityFocusRingController::Update() { |
+ rings_.clear(); |
+ RectsToRings(rects_, &rings_); |
+ |
+ if (!main_focus_ring_layer_) |
+ main_focus_ring_layer_.reset(new AccessibilityFocusRingLayer(this)); |
+ |
+ if (!rings_.empty()) |
+ main_focus_ring_layer_->Set(rings_[0]); |
+} |
+ |
+void AccessibilityFocusRingController::RectsToRings( |
+ const std::vector<gfx::Rect>& rects, |
+ std::vector<AccessibilityFocusRing>* rings) const { |
+ if (rects.empty()) |
+ return; |
+ |
+ // Split the rects into contiguous regions. |
+ std::vector<Region> regions; |
+ regions.push_back(Region(rects[0])); |
+ for (size_t i = 1; i < rects.size(); ++i) { |
+ bool found = false; |
+ for (size_t j = 0; j < regions.size(); ++j) { |
+ if (Intersects(rects[i], regions[j].bounds)) { |
+ regions[j].rects.push_back(rects[i]); |
+ regions[j].bounds.Union(rects[i]); |
+ found = true; |
+ } |
+ } |
+ if (!found) { |
+ regions.push_back(Region(rects[i])); |
+ } |
+ } |
+ |
+ // Keep merging regions that intersect. |
+ // TODO(dmazzoni): reduce the worst-case complexity! This appears like |
+ // it could be O(n^3), make sure it's not in practice. |
+ bool merged; |
+ do { |
+ merged = false; |
+ for (size_t i = 0; i < regions.size() - 1 && !merged; ++i) { |
+ for (size_t j = i + 1; j < regions.size() && !merged; ++j) { |
+ if (Intersects(regions[i].bounds, regions[j].bounds)) { |
+ regions[i].rects.insert(regions[i].rects.end(), |
+ regions[j].rects.begin(), |
+ regions[j].rects.end()); |
+ regions[i].bounds.Union(regions[j].bounds); |
+ regions.erase(regions.begin() + j); |
+ merged = true; |
+ } |
+ } |
+ } |
+ } while (merged); |
+ |
+ for (size_t i = 0; i < regions.size(); ++i) { |
+ std::sort(regions[i].rects.begin(), regions[i].rects.end()); |
+ rings->push_back(RingFromSortedRects(regions[i].rects)); |
+ } |
+} |
+ |
+int AccessibilityFocusRingController::GetMargin() const { |
+ return kAccessibilityFocusRingMargin; |
+} |
+ |
+// Given a vector of rects that all overlap, already sorted from top to bottom |
+// and left to right, split them into three shapes covering the top, middle, |
+// and bottom of a "paragraph shape". |
+// |
+// Input: |
+// |
+// +---+---+ |
+// | 1 | 2 | |
+// +---------------------+---+---+ |
+// | 3 | |
+// +--------+---------------+----+ |
+// | 4 | 5 | |
+// +--------+---------------+--+ |
+// | 6 | |
+// +---------+-----------------+ |
+// | 7 | |
+// +---------+ |
+// |
+// Output: |
+// |
+// +-------+ |
+// | Top | |
+// +---------------------+-------+ |
+// | | |
+// | | |
+// | Middle | |
+// | | |
+// | | |
+// +---------+-------------------+ |
+// | Bottom | |
+// +---------+ |
+// |
+// When there's no clear "top" or "bottom" segment, split the overall rect |
+// evenly so that some of the area still fits into the "top" and "bottom" |
+// segments. |
+void AccessibilityFocusRingController::SplitIntoParagraphShape( |
+ const std::vector<gfx::Rect>& rects, |
+ gfx::Rect* top, |
+ gfx::Rect* middle, |
+ gfx::Rect* bottom) const { |
+ size_t n = rects.size(); |
+ |
+ // Figure out how many rects belong in the top portion. |
+ gfx::Rect top_rect = rects[0]; |
+ int top_middle = (top_rect.y() + top_rect.bottom()) / 2; |
+ size_t top_count = 1; |
+ while (top_count < n && rects[top_count].y() < top_middle) { |
+ top_rect.Union(rects[top_count]); |
+ top_middle = (top_rect.y() + top_rect.bottom()) / 2; |
+ top_count++; |
+ } |
+ |
+ // Figure out how many rects belong in the bottom portion. |
+ gfx::Rect bottom_rect = rects[n - 1]; |
+ int bottom_middle = (bottom_rect.y() + bottom_rect.bottom()) / 2; |
+ size_t bottom_count = std::min(static_cast<size_t>(1), n - top_count); |
+ while (bottom_count + top_count < n && |
+ rects[n - bottom_count - 1].bottom() > bottom_middle) { |
+ bottom_rect.Union(rects[n - bottom_count - 1]); |
+ bottom_middle = (bottom_rect.y() + bottom_rect.bottom()) / 2; |
+ bottom_count++; |
+ } |
+ |
+ // Whatever's left goes to the middle rect, but if there's no middle or |
+ // bottom rect, split the existing rects evenly to make one. |
+ gfx::Rect middle_rect; |
+ if (top_count + bottom_count < n) { |
+ middle_rect = rects[top_count]; |
+ for (size_t i = top_count + 1; i < n - bottom_count; i++) |
+ middle_rect.Union(rects[i]); |
+ } else if (bottom_count > 0) { |
+ gfx::Rect enclosing_rect = top_rect; |
+ enclosing_rect.Union(bottom_rect); |
+ int middle_top = (top_rect.y() + top_rect.bottom() * 2) / 3; |
+ int middle_bottom = (bottom_rect.y() * 2 + bottom_rect.bottom()) / 3; |
+ top_rect.set_height(middle_top - top_rect.y()); |
+ bottom_rect.set_height(bottom_rect.bottom() - middle_bottom); |
+ bottom_rect.set_y(middle_bottom); |
+ middle_rect = gfx::Rect(enclosing_rect.x(), middle_top, |
+ enclosing_rect.width(), middle_bottom - middle_top); |
+ } else { |
+ int middle_top = (top_rect.y() * 2 + top_rect.bottom()) / 3; |
+ int middle_bottom = (top_rect.y() + top_rect.bottom() * 2) / 3; |
+ middle_rect = gfx::Rect(top_rect.x(), middle_top, |
+ top_rect.width(), middle_bottom - middle_top); |
+ bottom_rect = gfx::Rect( |
+ top_rect.x(), middle_bottom, |
+ top_rect.width(), top_rect.bottom() - middle_bottom); |
+ top_rect.set_height(middle_top - top_rect.y()); |
+ } |
+ |
+ if (middle_rect.y() > top_rect.bottom()) { |
+ middle_rect.set_height( |
+ middle_rect.height() + middle_rect.y() - top_rect.bottom()); |
+ middle_rect.set_y(top_rect.bottom()); |
+ } |
+ |
+ if (middle_rect.bottom() < bottom_rect.y()) { |
+ middle_rect.set_height(bottom_rect.y() - middle_rect.y()); |
+ } |
+ |
+ *top = top_rect; |
+ *middle = middle_rect; |
+ *bottom = bottom_rect; |
+} |
+ |
+AccessibilityFocusRing AccessibilityFocusRingController::RingFromSortedRects( |
+ const std::vector<gfx::Rect>& rects) const { |
+ if (rects.size() == 1) |
+ return AccessibilityFocusRing::CreateWithRect(rects[0], GetMargin()); |
+ |
+ gfx::Rect top; |
+ gfx::Rect middle; |
+ gfx::Rect bottom; |
+ SplitIntoParagraphShape(rects, &top, &middle, &bottom); |
+ |
+ return AccessibilityFocusRing::CreateWithParagraphShape( |
+ top, middle, bottom, GetMargin()); |
+} |
+ |
+bool AccessibilityFocusRingController::Intersects( |
+ const gfx::Rect& r1, const gfx::Rect& r2) const { |
+ int slop = GetMargin(); |
+ return (r2.x() <= r1.right() + slop && |
+ r2.right() >= r1.x() - slop && |
+ r2.y() <= r1.bottom() + slop && |
+ r2.bottom() >= r1.y() - slop); |
+} |
+ |
+void AccessibilityFocusRingController::OnDeviceScaleFactorChanged() { |
+ Update(); |
+} |
+ |
+} // namespace chromeos |