Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "ui/display/display_layout.h" | 5 #include "ui/display/display_layout.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 #include <map> | |
| 8 #include <set> | 9 #include <set> |
| 9 #include <sstream> | 10 #include <sstream> |
| 10 | 11 |
| 11 #include "base/logging.h" | 12 #include "base/logging.h" |
| 12 #include "base/strings/string_number_conversions.h" | 13 #include "base/strings/string_number_conversions.h" |
| 13 #include "base/strings/stringprintf.h" | 14 #include "base/strings/stringprintf.h" |
| 14 #include "base/values.h" | 15 #include "base/values.h" |
| 15 #include "ui/display/display.h" | 16 #include "ui/display/display.h" |
| 16 #include "ui/gfx/geometry/insets.h" | 17 #include "ui/gfx/geometry/insets.h" |
| 17 | 18 |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 29 // to change this value in case to support even larger displays. | 30 // to change this value in case to support even larger displays. |
| 30 const int kMaxValidOffset = 10000; | 31 const int kMaxValidOffset = 10000; |
| 31 | 32 |
| 32 bool IsIdInList(int64_t id, const DisplayIdList& list) { | 33 bool IsIdInList(int64_t id, const DisplayIdList& list) { |
| 33 const auto iter = | 34 const auto iter = |
| 34 std::find_if(list.begin(), list.end(), | 35 std::find_if(list.begin(), list.end(), |
| 35 [id](int64_t display_id) { return display_id == id; }); | 36 [id](int64_t display_id) { return display_id == id; }); |
| 36 return iter != list.end(); | 37 return iter != list.end(); |
| 37 } | 38 } |
| 38 | 39 |
| 40 // Extracts the displays IDs list from the displays list. | |
| 41 DisplayIdList DisplayListToDisplayIdList(const Displays& displays) { | |
| 42 DisplayIdList list; | |
| 43 for (const auto& display : displays) | |
| 44 list.emplace_back(display.id()); | |
| 45 | |
| 46 return list; | |
| 47 } | |
| 48 | |
| 49 // Ruturns nullptr if display with |id| is not found. | |
| 39 Display* FindDisplayById(Displays* display_list, int64_t id) { | 50 Display* FindDisplayById(Displays* display_list, int64_t id) { |
| 40 auto iter = | 51 auto iter = |
| 41 std::find_if(display_list->begin(), display_list->end(), | 52 std::find_if(display_list->begin(), display_list->end(), |
| 42 [id](const Display& display) { return display.id() == id; }); | 53 [id](const Display& display) { return display.id() == id; }); |
| 43 return &(*iter); | 54 return iter == display_list->end() ? nullptr : &(*iter); |
| 55 } | |
| 56 | |
| 57 // Returns the tree depth of the display with ID |display_id| from the tree root | |
| 58 // (i.e. from the primary display). | |
| 59 int GetDisplayTreeDepth( | |
| 60 int64_t display_id, | |
| 61 int64_t primary_id, | |
| 62 const std::map<int64_t, int64_t>& display_to_parent_ids_map) { | |
| 63 int64_t current_id = display_id; | |
| 64 int depth = 0; | |
| 65 const int kMaxDepth = 100; // Avoid layouts with cycles. | |
| 66 while (current_id != primary_id && depth < kMaxDepth) { | |
| 67 ++depth; | |
| 68 current_id = display_to_parent_ids_map.at(current_id); | |
| 69 } | |
| 70 | |
| 71 return depth; | |
| 72 } | |
| 73 | |
| 74 // Returns true if the child and parent displays are sharing a border that | |
| 75 // matches the child's relative position to its parent. | |
| 76 bool AreDisplaysTouching(const Display& child_display, | |
| 77 const Display& parent_display, | |
| 78 DisplayPlacement::Position child_position) { | |
| 79 const gfx::Rect& a_bounds = child_display.bounds(); | |
| 80 const gfx::Rect& b_bounds = parent_display.bounds(); | |
| 81 | |
| 82 if (child_position == DisplayPlacement::TOP || | |
| 83 child_position == DisplayPlacement::BOTTOM) { | |
| 84 const int rb = std::min(a_bounds.bottom(), b_bounds.bottom()); | |
| 85 const int ry = std::max(a_bounds.y(), b_bounds.y()); | |
| 86 return rb == ry; | |
| 87 } | |
| 88 | |
| 89 const int rx = std::max(a_bounds.x(), b_bounds.x()); | |
| 90 const int rr = std::min(a_bounds.right(), b_bounds.right()); | |
| 91 return rr == rx; | |
| 92 } | |
| 93 | |
| 94 // After the layout has been applied to the |display_list| and any possible | |
| 95 // overlaps have been fixed, this function is called to update the offsets in | |
| 96 // the |placement_list|. | |
| 97 void UpdateOffsets(Displays* display_list, | |
| 98 std::vector<DisplayPlacement>* placement_list) { | |
| 99 for (DisplayPlacement& placement : *placement_list) { | |
| 100 const Display* child_display = | |
| 101 FindDisplayById(display_list, placement.display_id); | |
| 102 const Display* parent_display = | |
| 103 FindDisplayById(display_list, placement.parent_display_id); | |
| 104 | |
| 105 if (!child_display || !parent_display) | |
| 106 continue; | |
| 107 | |
| 108 const gfx::Rect& child_bounds = child_display->bounds(); | |
| 109 const gfx::Rect& parent_bounds = parent_display->bounds(); | |
| 110 | |
| 111 if (placement.position == DisplayPlacement::TOP || | |
| 112 placement.position == DisplayPlacement::BOTTOM) { | |
| 113 placement.offset = child_bounds.x() - parent_bounds.x(); | |
| 114 } else { | |
| 115 placement.offset = child_bounds.y() - parent_bounds.y(); | |
| 116 } | |
| 117 } | |
| 118 } | |
| 119 | |
| 120 // Reparents |target_display| to |last_intersecting_source_display| if it's not | |
| 121 // touching with its current parent. | |
| 122 void MaybeReparentTargetDisplay( | |
| 123 int last_offset_x, | |
| 124 int last_offset_y, | |
| 125 const Display* last_intersecting_source_display, | |
| 126 const Display* target_display, | |
| 127 std::map<int64_t, int64_t>* display_to_parent_ids_map, | |
| 128 Displays* display_list, | |
| 129 std::vector<DisplayPlacement>* placement_list) { | |
| 130 // A de-intersection was performed. | |
| 131 // The offset target display may have moved such that it no longer touches | |
| 132 // its parent. Reparent if necessary. | |
| 133 const int64_t parent_display_id = | |
| 134 display_to_parent_ids_map->at(target_display->id()); | |
| 135 if (parent_display_id == last_intersecting_source_display->id()) { | |
| 136 // It was just de-intersected with the source display in such a way that | |
| 137 // they're touching, and the source display is its parent. So no need to | |
| 138 // do any reparenting. | |
| 139 return; | |
| 140 } | |
| 141 | |
| 142 Display* parent_display = FindDisplayById(display_list, parent_display_id); | |
| 143 DCHECK(parent_display); | |
| 144 | |
| 145 auto target_display_placement_itr = | |
| 146 std::find_if(placement_list->begin(), placement_list->end(), | |
| 147 [&target_display](const DisplayPlacement& p) { | |
| 148 return p.display_id == target_display->id(); | |
| 149 }); | |
| 150 DCHECK(target_display_placement_itr != placement_list->end()); | |
| 151 DisplayPlacement& target_display_placement = *target_display_placement_itr; | |
| 152 if (AreDisplaysTouching(*target_display, *parent_display, | |
| 153 target_display_placement.position)) { | |
| 154 return; | |
| 155 } | |
| 156 | |
| 157 // Reparent the target to source and update the position. No need to | |
| 158 // update the offset here as it will be done later when UpdateOffsets() | |
| 159 // is called. | |
| 160 target_display_placement.parent_display_id = | |
| 161 last_intersecting_source_display->id(); | |
| 162 // Update the map. | |
| 163 (*display_to_parent_ids_map)[target_display->id()] = | |
| 164 last_intersecting_source_display->id(); | |
| 165 | |
| 166 if (last_offset_x) { | |
| 167 target_display_placement.position = | |
| 168 last_offset_x > 0 ? DisplayPlacement::RIGHT : DisplayPlacement::LEFT; | |
| 169 } else { | |
| 170 target_display_placement.position = | |
| 171 last_offset_y > 0 ? DisplayPlacement::BOTTOM : DisplayPlacement::TOP; | |
| 172 } | |
| 173 } | |
| 174 | |
| 175 // Offsets |display| by the provided |x| and |y| values. | |
| 176 void OffsetDisplay(Display* display, int x, int y) { | |
| 177 gfx::Point new_origin = display->bounds().origin(); | |
| 178 new_origin.Offset(x, y); | |
| 179 gfx::Insets insets = display->GetWorkAreaInsets(); | |
| 180 display->set_bounds(gfx::Rect(new_origin, display->bounds().size())); | |
| 181 display->UpdateWorkAreaFromInsets(insets); | |
| 182 } | |
| 183 | |
| 184 // Calculates the amount of offset along the X axis for the target display with | |
| 185 // |target_bounds| to de-intersect with the source display with |source_bounds|. | |
| 186 // This function assumes both displays already intersects. | |
|
stevenjb
2017/01/31 23:40:55
intersect
afakhry
2017/01/31 23:56:16
Done.
| |
| 187 int CalculateOffsetX(const gfx::Rect& source_bounds, | |
| 188 const gfx::Rect& target_bounds) { | |
| 189 if (target_bounds.x() >= 0) { | |
| 190 // Target display moves along the +ve X direction. | |
| 191 return source_bounds.right() - target_bounds.x(); | |
| 192 } | |
| 193 | |
| 194 // Target display moves along the -ve X direction. | |
| 195 return -(target_bounds.right() - source_bounds.x()); | |
| 196 } | |
| 197 | |
| 198 // Calculates the amount of offset along the Y axis for the target display with | |
| 199 // |target_bounds| to de-intersect with the source display with |source_bounds|. | |
| 200 // This function assumes both displays already intersects. | |
|
stevenjb
2017/01/31 23:40:55
nit: You could probably share a single comment for
afakhry
2017/01/31 23:56:16
Done.
| |
| 201 int CalculateOffsetY(const gfx::Rect& source_bounds, | |
| 202 const gfx::Rect& target_bounds) { | |
| 203 if (target_bounds.y() >= 0) { | |
| 204 // Target display moves along the +ve Y direction. | |
| 205 return source_bounds.bottom() - target_bounds.y(); | |
| 206 } | |
| 207 | |
| 208 // Target display moves along the -ve Y direction. | |
| 209 return -(target_bounds.bottom() - source_bounds.y()); | |
| 210 } | |
| 211 | |
| 212 // Fixes any overlapping displays and reparents displays if necessary. | |
| 213 void DeIntersectDisplays(int64_t primary_id, | |
| 214 Displays* display_list, | |
| 215 std::vector<DisplayPlacement>* placement_list, | |
| 216 std::set<int64_t>* updated_displays) { | |
| 217 std::map<int64_t, int64_t> display_to_parent_ids_map; | |
| 218 for (const DisplayPlacement& placement : *placement_list) { | |
| 219 display_to_parent_ids_map.insert( | |
| 220 std::make_pair(placement.display_id, placement.parent_display_id)); | |
| 221 } | |
| 222 | |
| 223 std::vector<Display*> sorted_displays; | |
| 224 for (Display& display : *display_list) | |
| 225 sorted_displays.push_back(&display); | |
| 226 | |
| 227 // Sort the displays first by their depth in the display hierarchy tree, and | |
| 228 // then by distance of their top left points from the origin. This way we | |
| 229 // process the displays starting at the root (the primary display), in the | |
| 230 // order of their decendence spanning out from the primary display. | |
| 231 std::sort(sorted_displays.begin(), sorted_displays.end(), [&](Display* d1, | |
| 232 Display* d2) { | |
| 233 const int d1_depth = | |
| 234 GetDisplayTreeDepth(d1->id(), primary_id, display_to_parent_ids_map); | |
| 235 const int d2_depth = | |
| 236 GetDisplayTreeDepth(d2->id(), primary_id, display_to_parent_ids_map); | |
| 237 | |
| 238 if (d1_depth != d2_depth) | |
| 239 return d1_depth < d2_depth; | |
| 240 | |
| 241 return d1->bounds().OffsetFromOrigin().LengthSquared() < | |
| 242 d2->bounds().OffsetFromOrigin().LengthSquared(); | |
| 243 }); | |
| 244 // This must result in the primary display being at the front of the list. | |
| 245 DCHECK_EQ(sorted_displays.front()->id(), primary_id); | |
| 246 | |
| 247 for (int i = 1; i < static_cast<int>(sorted_displays.size()); ++i) { | |
| 248 Display* target_display = sorted_displays[i]; | |
| 249 const Display* last_intersecting_source_display = nullptr; | |
| 250 int last_offset_x = 0; | |
| 251 int last_offset_y = 0; | |
| 252 for (int j = i - 1; j >= 0; --j) { | |
| 253 const Display* source_display = sorted_displays[j]; | |
| 254 const gfx::Rect source_bounds = source_display->bounds(); | |
| 255 const gfx::Rect target_bounds = target_display->bounds(); | |
| 256 | |
| 257 gfx::Rect intersection = source_bounds; | |
| 258 intersection.Intersect(target_bounds); | |
| 259 | |
| 260 if (intersection.IsEmpty()) | |
| 261 continue; | |
| 262 | |
| 263 // Calculate offsets along both X and Y axes such that either can remove | |
| 264 // the overlap, but choose and apply the smaller offset. This way we have | |
| 265 // more predictable results. | |
| 266 int offset_x = 0; | |
| 267 int offset_y = 0; | |
| 268 if (intersection.width()) | |
| 269 offset_x = CalculateOffsetX(source_bounds, target_bounds); | |
| 270 if (intersection.height()) | |
| 271 offset_y = CalculateOffsetY(source_bounds, target_bounds); | |
| 272 | |
| 273 if (offset_x == 0 && offset_y == 0) | |
| 274 continue; | |
| 275 | |
| 276 // Choose the smaller offset. | |
| 277 if (std::abs(offset_x) <= std::abs(offset_y)) | |
| 278 offset_y = 0; | |
| 279 else | |
| 280 offset_x = 0; | |
| 281 | |
| 282 OffsetDisplay(target_display, offset_x, offset_y); | |
| 283 updated_displays->insert(target_display->id()); | |
| 284 | |
| 285 // The most recent performed de-intersection data. | |
| 286 last_intersecting_source_display = source_display; | |
| 287 last_offset_x = offset_x; | |
| 288 last_offset_y = offset_y; | |
| 289 } | |
| 290 | |
| 291 if (!last_intersecting_source_display) | |
| 292 continue; | |
| 293 | |
| 294 MaybeReparentTargetDisplay(last_offset_x, last_offset_y, | |
| 295 last_intersecting_source_display, target_display, | |
| 296 &display_to_parent_ids_map, display_list, | |
| 297 placement_list); | |
| 298 } | |
| 299 | |
| 300 // The offsets might have changed and we must update them. | |
| 301 UpdateOffsets(display_list, placement_list); | |
| 44 } | 302 } |
| 45 | 303 |
| 46 } // namespace | 304 } // namespace |
| 47 | 305 |
| 48 //////////////////////////////////////////////////////////////////////////////// | 306 //////////////////////////////////////////////////////////////////////////////// |
| 49 // DisplayPlacement | 307 // DisplayPlacement |
| 50 | 308 |
| 51 DisplayPlacement::DisplayPlacement() | 309 DisplayPlacement::DisplayPlacement() |
| 52 : DisplayPlacement(kInvalidDisplayId, | 310 : DisplayPlacement(kInvalidDisplayId, |
| 53 kInvalidDisplayId, | 311 kInvalidDisplayId, |
| (...skipping 121 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 175 //////////////////////////////////////////////////////////////////////////////// | 433 //////////////////////////////////////////////////////////////////////////////// |
| 176 // DisplayLayout | 434 // DisplayLayout |
| 177 | 435 |
| 178 DisplayLayout::DisplayLayout() | 436 DisplayLayout::DisplayLayout() |
| 179 : mirrored(false), default_unified(true), primary_id(kInvalidDisplayId) {} | 437 : mirrored(false), default_unified(true), primary_id(kInvalidDisplayId) {} |
| 180 | 438 |
| 181 DisplayLayout::~DisplayLayout() {} | 439 DisplayLayout::~DisplayLayout() {} |
| 182 | 440 |
| 183 void DisplayLayout::ApplyToDisplayList(Displays* display_list, | 441 void DisplayLayout::ApplyToDisplayList(Displays* display_list, |
| 184 std::vector<int64_t>* updated_ids, | 442 std::vector<int64_t>* updated_ids, |
| 185 int minimum_offset_overlap) const { | 443 int minimum_offset_overlap) { |
| 444 if (placement_list.empty()) | |
| 445 return; | |
| 446 | |
| 447 if (!DisplayLayout::Validate(DisplayListToDisplayIdList(*display_list), | |
| 448 *this)) { | |
| 449 // Prevent invalid and non-relevant display layouts. | |
| 450 return; | |
| 451 } | |
| 452 | |
| 186 // Layout from primary, then dependent displays. | 453 // Layout from primary, then dependent displays. |
| 187 std::set<int64_t> parents; | 454 std::set<int64_t> parents; |
| 455 std::set<int64_t> updated_displays; | |
| 188 parents.insert(primary_id); | 456 parents.insert(primary_id); |
| 189 while (parents.size()) { | 457 while (parents.size()) { |
| 190 int64_t parent_id = *parents.begin(); | 458 int64_t parent_id = *parents.begin(); |
| 191 parents.erase(parent_id); | 459 parents.erase(parent_id); |
| 192 for (const DisplayPlacement& placement : placement_list) { | 460 for (const DisplayPlacement& placement : placement_list) { |
| 193 if (placement.parent_display_id == parent_id) { | 461 if (placement.parent_display_id == parent_id) { |
| 194 if (ApplyDisplayPlacement(placement, display_list, | 462 if (ApplyDisplayPlacement(placement, display_list, |
| 195 minimum_offset_overlap) && | 463 minimum_offset_overlap)) { |
| 196 updated_ids) { | 464 updated_displays.insert(placement.display_id); |
| 197 updated_ids->push_back(placement.display_id); | |
| 198 } | 465 } |
| 199 parents.insert(placement.display_id); | 466 parents.insert(placement.display_id); |
| 200 } | 467 } |
| 201 } | 468 } |
| 202 } | 469 } |
| 470 | |
| 471 // Now that all the placements have been applied, we must detect and fix any | |
| 472 // overlapping displays. | |
| 473 DeIntersectDisplays(primary_id, display_list, &placement_list, | |
| 474 &updated_displays); | |
| 475 | |
| 476 if (updated_ids) { | |
| 477 updated_ids->insert(updated_ids->begin(), updated_displays.begin(), | |
| 478 updated_displays.end()); | |
| 479 } | |
| 203 } | 480 } |
| 204 | 481 |
| 205 // static | 482 // static |
| 206 bool DisplayLayout::Validate(const DisplayIdList& list, | 483 bool DisplayLayout::Validate(const DisplayIdList& list, |
| 207 const DisplayLayout& layout) { | 484 const DisplayLayout& layout) { |
| 208 // The primary display should be in the list. | 485 // The primary display should be in the list. |
| 209 DCHECK(IsIdInList(layout.primary_id, list)); | 486 if (!IsIdInList(layout.primary_id, list)) { |
| 487 LOG(ERROR) << "The primary id: " << layout.primary_id | |
| 488 << " is not in the id list."; | |
| 489 return false; | |
| 490 } | |
| 210 | 491 |
| 211 // Unified mode, or mirror mode switched from unified mode, | 492 // Unified mode, or mirror mode switched from unified mode, |
| 212 // may not have the placement yet. | 493 // may not have the placement yet. |
| 213 if (layout.placement_list.size() == 0u) | 494 if (layout.placement_list.size() == 0u) |
| 214 return true; | 495 return true; |
| 215 | 496 |
| 216 bool has_primary_as_parent = false; | 497 bool has_primary_as_parent = false; |
| 217 int64_t prev_id = std::numeric_limits<int64_t>::min(); | 498 int64_t prev_id = std::numeric_limits<int64_t>::min(); |
| 218 for (const auto& placement : layout.placement_list) { | 499 for (const auto& placement : layout.placement_list) { |
| 219 // Placements are sorted by display_id. | 500 // Placements are sorted by display_id. |
| (...skipping 136 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 356 | 637 |
| 357 gfx::Insets insets = target_display->GetWorkAreaInsets(); | 638 gfx::Insets insets = target_display->GetWorkAreaInsets(); |
| 358 target_display->set_bounds( | 639 target_display->set_bounds( |
| 359 gfx::Rect(new_target_origin, target_bounds.size())); | 640 gfx::Rect(new_target_origin, target_bounds.size())); |
| 360 target_display->UpdateWorkAreaFromInsets(insets); | 641 target_display->UpdateWorkAreaFromInsets(insets); |
| 361 | 642 |
| 362 return old_bounds != target_display->bounds(); | 643 return old_bounds != target_display->bounds(); |
| 363 } | 644 } |
| 364 | 645 |
| 365 } // namespace display | 646 } // namespace display |
| OLD | NEW |