OLD | NEW |
(Empty) | |
| 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. |
| 4 |
| 5 #include "chrome/browser/chromeos/ui/accessibility_focus_ring_controller.h" |
| 6 |
| 7 #include "chrome/browser/chromeos/ui/focus_ring_layer.h" |
| 8 |
| 9 namespace chromeos { |
| 10 |
| 11 namespace { |
| 12 |
| 13 // The number of pixels the focus ring is outset from the object it outlines, |
| 14 // which also determines the border radius of the rounded corners. |
| 15 // TODO(dmazzoni): take display resolution into account. |
| 16 const int kAccessibilityFocusRingMargin = 16; |
| 17 |
| 18 // A Region is an unordered collection of Rects that maintains its |
| 19 // bounding box. Used in the middle of an algorithm that groups |
| 20 // adjacent and overlapping rects. |
| 21 struct Region { |
| 22 explicit Region(gfx::Rect initial_rect) { |
| 23 bounds = initial_rect; |
| 24 rects.push_back(initial_rect); |
| 25 } |
| 26 gfx::Rect bounds; |
| 27 std::vector<gfx::Rect> rects; |
| 28 }; |
| 29 |
| 30 } // namespace |
| 31 |
| 32 // static |
| 33 AccessibilityFocusRingController* |
| 34 AccessibilityFocusRingController::GetInstance() { |
| 35 return Singleton<AccessibilityFocusRingController>::get(); |
| 36 } |
| 37 |
| 38 AccessibilityFocusRingController::AccessibilityFocusRingController() { |
| 39 } |
| 40 |
| 41 AccessibilityFocusRingController::~AccessibilityFocusRingController() { |
| 42 } |
| 43 |
| 44 void AccessibilityFocusRingController::SetFocusRing( |
| 45 const std::vector<gfx::Rect>& rects) { |
| 46 rects_ = rects; |
| 47 Update(); |
| 48 } |
| 49 |
| 50 void AccessibilityFocusRingController::Update() { |
| 51 rings_.clear(); |
| 52 RectsToRings(rects_, &rings_); |
| 53 |
| 54 if (!main_focus_ring_layer_) |
| 55 main_focus_ring_layer_.reset(new AccessibilityFocusRingLayer(this)); |
| 56 |
| 57 if (!rings_.empty()) |
| 58 main_focus_ring_layer_->Set(rings_[0]); |
| 59 } |
| 60 |
| 61 void AccessibilityFocusRingController::RectsToRings( |
| 62 const std::vector<gfx::Rect>& rects, |
| 63 std::vector<AccessibilityFocusRing>* rings) const { |
| 64 if (rects.empty()) |
| 65 return; |
| 66 |
| 67 // Split the rects into contiguous regions. |
| 68 std::vector<Region> regions; |
| 69 regions.push_back(Region(rects[0])); |
| 70 for (size_t i = 1; i < rects.size(); ++i) { |
| 71 bool found = false; |
| 72 for (size_t j = 0; j < regions.size(); ++j) { |
| 73 if (Intersects(rects[i], regions[j].bounds)) { |
| 74 regions[j].rects.push_back(rects[i]); |
| 75 regions[j].bounds.Union(rects[i]); |
| 76 found = true; |
| 77 } |
| 78 } |
| 79 if (!found) { |
| 80 regions.push_back(Region(rects[i])); |
| 81 } |
| 82 } |
| 83 |
| 84 // Keep merging regions that intersect. |
| 85 // TODO(dmazzoni): reduce the worst-case complexity! This appears like |
| 86 // it could be O(n^3), make sure it's not in practice. |
| 87 bool merged; |
| 88 do { |
| 89 merged = false; |
| 90 for (size_t i = 0; i < regions.size() - 1 && !merged; ++i) { |
| 91 for (size_t j = i + 1; j < regions.size() && !merged; ++j) { |
| 92 if (Intersects(regions[i].bounds, regions[j].bounds)) { |
| 93 regions[i].rects.insert(regions[i].rects.end(), |
| 94 regions[j].rects.begin(), |
| 95 regions[j].rects.end()); |
| 96 regions[i].bounds.Union(regions[j].bounds); |
| 97 regions.erase(regions.begin() + j); |
| 98 merged = true; |
| 99 } |
| 100 } |
| 101 } |
| 102 } while (merged); |
| 103 |
| 104 for (size_t i = 0; i < regions.size(); ++i) { |
| 105 std::sort(regions[i].rects.begin(), regions[i].rects.end()); |
| 106 rings->push_back(RingFromSortedRects(regions[i].rects)); |
| 107 } |
| 108 } |
| 109 |
| 110 int AccessibilityFocusRingController::GetMargin() const { |
| 111 return kAccessibilityFocusRingMargin; |
| 112 } |
| 113 |
| 114 // Given a vector of rects that all overlap, already sorted from top to bottom |
| 115 // and left to right, split them into three shapes covering the top, middle, |
| 116 // and bottom of a "paragraph shape". |
| 117 // |
| 118 // Input: |
| 119 // |
| 120 // +---+---+ |
| 121 // | 1 | 2 | |
| 122 // +---------------------+---+---+ |
| 123 // | 3 | |
| 124 // +--------+---------------+----+ |
| 125 // | 4 | 5 | |
| 126 // +--------+---------------+--+ |
| 127 // | 6 | |
| 128 // +---------+-----------------+ |
| 129 // | 7 | |
| 130 // +---------+ |
| 131 // |
| 132 // Output: |
| 133 // |
| 134 // +-------+ |
| 135 // | Top | |
| 136 // +---------------------+-------+ |
| 137 // | | |
| 138 // | | |
| 139 // | Middle | |
| 140 // | | |
| 141 // | | |
| 142 // +---------+-------------------+ |
| 143 // | Bottom | |
| 144 // +---------+ |
| 145 // |
| 146 // When there's no clear "top" or "bottom" segment, split the overall rect |
| 147 // evenly so that some of the area still fits into the "top" and "bottom" |
| 148 // segments. |
| 149 void AccessibilityFocusRingController::SplitIntoParagraphShape( |
| 150 const std::vector<gfx::Rect>& rects, |
| 151 gfx::Rect* top, |
| 152 gfx::Rect* middle, |
| 153 gfx::Rect* bottom) const { |
| 154 size_t n = rects.size(); |
| 155 |
| 156 // Figure out how many rects belong in the top portion. |
| 157 gfx::Rect top_rect = rects[0]; |
| 158 int top_middle = (top_rect.y() + top_rect.bottom()) / 2; |
| 159 size_t top_count = 1; |
| 160 while (top_count < n && rects[top_count].y() < top_middle) { |
| 161 top_rect.Union(rects[top_count]); |
| 162 top_middle = (top_rect.y() + top_rect.bottom()) / 2; |
| 163 top_count++; |
| 164 } |
| 165 |
| 166 // Figure out how many rects belong in the bottom portion. |
| 167 gfx::Rect bottom_rect = rects[n - 1]; |
| 168 int bottom_middle = (bottom_rect.y() + bottom_rect.bottom()) / 2; |
| 169 size_t bottom_count = std::min(static_cast<size_t>(1), n - top_count); |
| 170 while (bottom_count + top_count < n && |
| 171 rects[n - bottom_count - 1].bottom() > bottom_middle) { |
| 172 bottom_rect.Union(rects[n - bottom_count - 1]); |
| 173 bottom_middle = (bottom_rect.y() + bottom_rect.bottom()) / 2; |
| 174 bottom_count++; |
| 175 } |
| 176 |
| 177 // Whatever's left goes to the middle rect, but if there's no middle or |
| 178 // bottom rect, split the existing rects evenly to make one. |
| 179 gfx::Rect middle_rect; |
| 180 if (top_count + bottom_count < n) { |
| 181 middle_rect = rects[top_count]; |
| 182 for (size_t i = top_count + 1; i < n - bottom_count; i++) |
| 183 middle_rect.Union(rects[i]); |
| 184 } else if (bottom_count > 0) { |
| 185 gfx::Rect enclosing_rect = top_rect; |
| 186 enclosing_rect.Union(bottom_rect); |
| 187 int middle_top = (top_rect.y() + top_rect.bottom() * 2) / 3; |
| 188 int middle_bottom = (bottom_rect.y() * 2 + bottom_rect.bottom()) / 3; |
| 189 top_rect.set_height(middle_top - top_rect.y()); |
| 190 bottom_rect.set_height(bottom_rect.bottom() - middle_bottom); |
| 191 bottom_rect.set_y(middle_bottom); |
| 192 middle_rect = gfx::Rect(enclosing_rect.x(), middle_top, |
| 193 enclosing_rect.width(), middle_bottom - middle_top); |
| 194 } else { |
| 195 int middle_top = (top_rect.y() * 2 + top_rect.bottom()) / 3; |
| 196 int middle_bottom = (top_rect.y() + top_rect.bottom() * 2) / 3; |
| 197 middle_rect = gfx::Rect(top_rect.x(), middle_top, |
| 198 top_rect.width(), middle_bottom - middle_top); |
| 199 bottom_rect = gfx::Rect( |
| 200 top_rect.x(), middle_bottom, |
| 201 top_rect.width(), top_rect.bottom() - middle_bottom); |
| 202 top_rect.set_height(middle_top - top_rect.y()); |
| 203 } |
| 204 |
| 205 if (middle_rect.y() > top_rect.bottom()) { |
| 206 middle_rect.set_height( |
| 207 middle_rect.height() + middle_rect.y() - top_rect.bottom()); |
| 208 middle_rect.set_y(top_rect.bottom()); |
| 209 } |
| 210 |
| 211 if (middle_rect.bottom() < bottom_rect.y()) { |
| 212 middle_rect.set_height(bottom_rect.y() - middle_rect.y()); |
| 213 } |
| 214 |
| 215 *top = top_rect; |
| 216 *middle = middle_rect; |
| 217 *bottom = bottom_rect; |
| 218 } |
| 219 |
| 220 AccessibilityFocusRing AccessibilityFocusRingController::RingFromSortedRects( |
| 221 const std::vector<gfx::Rect>& rects) const { |
| 222 if (rects.size() == 1) |
| 223 return AccessibilityFocusRing::CreateWithRect(rects[0], GetMargin()); |
| 224 |
| 225 gfx::Rect top; |
| 226 gfx::Rect middle; |
| 227 gfx::Rect bottom; |
| 228 SplitIntoParagraphShape(rects, &top, &middle, &bottom); |
| 229 |
| 230 return AccessibilityFocusRing::CreateWithParagraphShape( |
| 231 top, middle, bottom, GetMargin()); |
| 232 } |
| 233 |
| 234 bool AccessibilityFocusRingController::Intersects( |
| 235 const gfx::Rect& r1, const gfx::Rect& r2) const { |
| 236 int slop = GetMargin(); |
| 237 return (r2.x() <= r1.right() + slop && |
| 238 r2.right() >= r1.x() - slop && |
| 239 r2.y() <= r1.bottom() + slop && |
| 240 r2.bottom() >= r1.y() - slop); |
| 241 } |
| 242 |
| 243 void AccessibilityFocusRingController::OnDeviceScaleFactorChanged() { |
| 244 Update(); |
| 245 } |
| 246 |
| 247 } // namespace chromeos |
OLD | NEW |