Avi Drissman | 3e1a26c | 2022-09-15 20:26:03 | [diff] [blame] | 1 | // Copyright 2016 The Chromium Authors |
[email protected] | 93c7e562 | 2013-06-28 15:12:06 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
kylechar | 731f85f9 | 2016-12-01 20:50:46 | [diff] [blame] | 5 | #include "ui/display/display_layout.h" |
[email protected] | 93c7e562 | 2013-06-28 15:12:06 | [diff] [blame] | 6 | |
Peter Kasting | e10dd2e5 | 2025-01-28 00:26:12 | [diff] [blame] | 7 | #include <algorithm> |
afakhry | 6c80a875 | 2017-02-01 01:38:04 | [diff] [blame] | 8 | #include <map> |
robliao | 4567a867 | 2016-04-12 16:45:39 | [diff] [blame] | 9 | #include <set> |
oshima | f571c4a | 2016-02-24 18:51:05 | [diff] [blame] | 10 | #include <sstream> |
Helmut Januschka | 36619c1 | 2024-04-24 14:33:19 | [diff] [blame] | 11 | #include <string_view> |
kylechar | 6e8ea59 | 2017-03-30 15:27:57 | [diff] [blame] | 12 | #include <unordered_map> |
Andrew Rayskiy | 0b0a148 | 2024-02-27 16:07:56 | [diff] [blame] | 13 | #include <vector> |
oshima | 95d499b | 2016-02-10 03:49:56 | [diff] [blame] | 14 | |
David Dorwin | 3f503b8 | 2022-04-20 04:07:03 | [diff] [blame] | 15 | #include "base/check.h" |
| 16 | #include "base/check_op.h" |
Mitsuru Oshima | d6890ba | 2022-04-14 06:58:33 | [diff] [blame] | 17 | #include "base/containers/contains.h" |
[email protected] | 93c7e562 | 2013-06-28 15:12:06 | [diff] [blame] | 18 | #include "base/logging.h" |
[email protected] | 93c7e562 | 2013-06-28 15:12:06 | [diff] [blame] | 19 | #include "base/strings/string_number_conversions.h" |
[email protected] | 93c7e562 | 2013-06-28 15:12:06 | [diff] [blame] | 20 | #include "base/values.h" |
Jeroen Dhollander | 7c83e09 | 2023-11-03 17:59:32 | [diff] [blame] | 21 | #include "components/device_event_log/device_event_log.h" |
oshima | ef5fb89 | 2016-04-25 20:44:07 | [diff] [blame] | 22 | #include "ui/display/display.h" |
Mitsuru Oshima | dd6758c | 2022-04-19 06:06:09 | [diff] [blame] | 23 | #include "ui/display/util/display_util.h" |
robliao | 4567a867 | 2016-04-12 16:45:39 | [diff] [blame] | 24 | #include "ui/gfx/geometry/insets.h" |
Steven Bennetts | 9c10bc4 | 2018-04-02 17:02:04 | [diff] [blame] | 25 | #include "ui/gfx/geometry/point.h" |
| 26 | #include "ui/gfx/geometry/rect.h" |
[email protected] | 93c7e562 | 2013-06-28 15:12:06 | [diff] [blame] | 27 | |
robliao | c0dfd6b | 2016-04-07 21:33:56 | [diff] [blame] | 28 | namespace display { |
kylechar | 731f85f9 | 2016-12-01 20:50:46 | [diff] [blame] | 29 | namespace { |
[email protected] | 93c7e562 | 2013-06-28 15:12:06 | [diff] [blame] | 30 | |
robliao | 8c912b53 | 2016-03-21 21:34:15 | [diff] [blame] | 31 | // DisplayPlacement Positions |
| 32 | const char kTop[] = "top"; |
| 33 | const char kRight[] = "right"; |
| 34 | const char kBottom[] = "bottom"; |
| 35 | const char kLeft[] = "left"; |
| 36 | const char kUnknown[] = "unknown"; |
| 37 | |
[email protected] | 93c7e562 | 2013-06-28 15:12:06 | [diff] [blame] | 38 | // The maximum value for 'offset' in DisplayLayout in case of outliers. Need |
| 39 | // to change this value in case to support even larger displays. |
| 40 | const int kMaxValidOffset = 10000; |
| 41 | |
kylechar | 6e8ea59 | 2017-03-30 15:27:57 | [diff] [blame] | 42 | bool ComparePlacements(const DisplayPlacement& d1, const DisplayPlacement& d2) { |
afakhry | d3d4ea4 | 2017-04-14 02:01:43 | [diff] [blame] | 43 | return CompareDisplayIds(d1.display_id, d2.display_id); |
kylechar | 6e8ea59 | 2017-03-30 15:27:57 | [diff] [blame] | 44 | } |
| 45 | |
afakhry | 6c80a875 | 2017-02-01 01:38:04 | [diff] [blame] | 46 | // Extracts the displays IDs list from the displays list. |
| 47 | DisplayIdList DisplayListToDisplayIdList(const Displays& displays) { |
| 48 | DisplayIdList list; |
| 49 | for (const auto& display : displays) |
| 50 | list.emplace_back(display.id()); |
| 51 | |
| 52 | return list; |
| 53 | } |
| 54 | |
Andrew Wolfers | 0783e12 | 2023-01-30 15:55:41 | [diff] [blame] | 55 | // Returns nullptr if display with |id| is not found. |
msw | 5e048a7 | 2016-09-07 18:55:30 | [diff] [blame] | 56 | Display* FindDisplayById(Displays* display_list, int64_t id) { |
Peter Kasting | e10dd2e5 | 2025-01-28 00:26:12 | [diff] [blame] | 57 | auto iter = std::ranges::find(*display_list, id, &Display::id); |
afakhry | 6c80a875 | 2017-02-01 01:38:04 | [diff] [blame] | 58 | return iter == display_list->end() ? nullptr : &(*iter); |
| 59 | } |
| 60 | |
| 61 | // Returns the tree depth of the display with ID |display_id| from the tree root |
| 62 | // (i.e. from the primary display). |
| 63 | int GetDisplayTreeDepth( |
| 64 | int64_t display_id, |
| 65 | int64_t primary_id, |
| 66 | const std::map<int64_t, int64_t>& display_to_parent_ids_map) { |
| 67 | int64_t current_id = display_id; |
| 68 | int depth = 0; |
| 69 | const int kMaxDepth = 100; // Avoid layouts with cycles. |
| 70 | while (current_id != primary_id && depth < kMaxDepth) { |
| 71 | ++depth; |
afakhry | a0bad961 | 2017-02-01 22:45:18 | [diff] [blame] | 72 | auto iter = display_to_parent_ids_map.find(current_id); |
| 73 | if (iter == display_to_parent_ids_map.end()) |
Andrew Wolfers | 0783e12 | 2023-01-30 15:55:41 | [diff] [blame] | 74 | return kMaxDepth; // Let detached displays go to the end. |
afakhry | a0bad961 | 2017-02-01 22:45:18 | [diff] [blame] | 75 | |
| 76 | current_id = iter->second; |
afakhry | 6c80a875 | 2017-02-01 01:38:04 | [diff] [blame] | 77 | } |
| 78 | |
| 79 | return depth; |
| 80 | } |
| 81 | |
| 82 | // Returns true if the child and parent displays are sharing a border that |
| 83 | // matches the child's relative position to its parent. |
| 84 | bool AreDisplaysTouching(const Display& child_display, |
| 85 | const Display& parent_display, |
| 86 | DisplayPlacement::Position child_position) { |
| 87 | const gfx::Rect& a_bounds = child_display.bounds(); |
| 88 | const gfx::Rect& b_bounds = parent_display.bounds(); |
| 89 | |
| 90 | if (child_position == DisplayPlacement::TOP || |
| 91 | child_position == DisplayPlacement::BOTTOM) { |
| 92 | const int rb = std::min(a_bounds.bottom(), b_bounds.bottom()); |
| 93 | const int ry = std::max(a_bounds.y(), b_bounds.y()); |
| 94 | return rb == ry; |
| 95 | } |
| 96 | |
| 97 | const int rx = std::max(a_bounds.x(), b_bounds.x()); |
| 98 | const int rr = std::min(a_bounds.right(), b_bounds.right()); |
| 99 | return rr == rx; |
| 100 | } |
| 101 | |
| 102 | // After the layout has been applied to the |display_list| and any possible |
| 103 | // overlaps have been fixed, this function is called to update the offsets in |
afakhry | a0bad961 | 2017-02-01 22:45:18 | [diff] [blame] | 104 | // the |placement_list|, and make sure the placement list is sorted by display |
| 105 | // IDs. |
| 106 | void UpdatePlacementList(Displays* display_list, |
| 107 | std::vector<DisplayPlacement>* placement_list) { |
kylechar | 6e8ea59 | 2017-03-30 15:27:57 | [diff] [blame] | 108 | std::sort(placement_list->begin(), placement_list->end(), ComparePlacements); |
afakhry | a0bad961 | 2017-02-01 22:45:18 | [diff] [blame] | 109 | |
afakhry | 6c80a875 | 2017-02-01 01:38:04 | [diff] [blame] | 110 | for (DisplayPlacement& placement : *placement_list) { |
| 111 | const Display* child_display = |
| 112 | FindDisplayById(display_list, placement.display_id); |
| 113 | const Display* parent_display = |
| 114 | FindDisplayById(display_list, placement.parent_display_id); |
| 115 | |
| 116 | if (!child_display || !parent_display) |
| 117 | continue; |
| 118 | |
| 119 | const gfx::Rect& child_bounds = child_display->bounds(); |
| 120 | const gfx::Rect& parent_bounds = parent_display->bounds(); |
| 121 | |
| 122 | if (placement.position == DisplayPlacement::TOP || |
| 123 | placement.position == DisplayPlacement::BOTTOM) { |
| 124 | placement.offset = child_bounds.x() - parent_bounds.x(); |
| 125 | } else { |
| 126 | placement.offset = child_bounds.y() - parent_bounds.y(); |
| 127 | } |
| 128 | } |
| 129 | } |
| 130 | |
| 131 | // Reparents |target_display| to |last_intersecting_source_display| if it's not |
afakhry | a0bad961 | 2017-02-01 22:45:18 | [diff] [blame] | 132 | // touching with its current parent. It also handles the case if |
| 133 | // |target_display| is detached, it then reparents it to the last intersecting |
| 134 | // display. |
afakhry | 6c80a875 | 2017-02-01 01:38:04 | [diff] [blame] | 135 | void MaybeReparentTargetDisplay( |
| 136 | int last_offset_x, |
| 137 | int last_offset_y, |
| 138 | const Display* last_intersecting_source_display, |
| 139 | const Display* target_display, |
| 140 | std::map<int64_t, int64_t>* display_to_parent_ids_map, |
| 141 | Displays* display_list, |
| 142 | std::vector<DisplayPlacement>* placement_list) { |
| 143 | // A de-intersection was performed. |
| 144 | // The offset target display may have moved such that it no longer touches |
| 145 | // its parent. Reparent if necessary. |
afakhry | a0bad961 | 2017-02-01 22:45:18 | [diff] [blame] | 146 | DisplayPlacement* target_display_placement = nullptr; |
| 147 | auto iter = display_to_parent_ids_map->find(target_display->id()); |
| 148 | if (iter != display_to_parent_ids_map->end()) { |
| 149 | const int64_t parent_display_id = iter->second; |
| 150 | if (parent_display_id == last_intersecting_source_display->id()) { |
| 151 | // It was just de-intersected with the source display in such a way that |
| 152 | // they're touching, and the source display is its parent. So no need to |
| 153 | // do any reparenting. |
| 154 | return; |
| 155 | } |
| 156 | |
| 157 | Display* parent_display = FindDisplayById(display_list, parent_display_id); |
| 158 | DCHECK(parent_display); |
| 159 | |
Peter Kasting | e10dd2e5 | 2025-01-28 00:26:12 | [diff] [blame] | 160 | auto target_display_placement_itr = std::ranges::find( |
Peter Kasting | 862ac0f | 2022-09-19 15:28:14 | [diff] [blame] | 161 | *placement_list, target_display->id(), &DisplayPlacement::display_id); |
Daniel Cheng | 4d54f0a | 2025-05-26 22:59:12 | [diff] [blame] | 162 | CHECK(target_display_placement_itr != placement_list->end()); |
afakhry | a0bad961 | 2017-02-01 22:45:18 | [diff] [blame] | 163 | target_display_placement = &(*target_display_placement_itr); |
| 164 | if (AreDisplaysTouching(*target_display, *parent_display, |
| 165 | target_display_placement->position)) { |
| 166 | return; |
| 167 | } |
| 168 | } else { |
| 169 | // It's a detached display with no parent. Add a new placement for it. |
| 170 | DisplayPlacement new_placement; |
| 171 | new_placement.display_id = target_display->id(); |
| 172 | placement_list->emplace_back(new_placement); |
| 173 | target_display_placement = &placement_list->back(); |
afakhry | 6c80a875 | 2017-02-01 01:38:04 | [diff] [blame] | 174 | } |
| 175 | |
afakhry | a0bad961 | 2017-02-01 22:45:18 | [diff] [blame] | 176 | DCHECK(target_display_placement); |
afakhry | 6c80a875 | 2017-02-01 01:38:04 | [diff] [blame] | 177 | |
| 178 | // Reparent the target to source and update the position. No need to |
| 179 | // update the offset here as it will be done later when UpdateOffsets() |
| 180 | // is called. |
afakhry | a0bad961 | 2017-02-01 22:45:18 | [diff] [blame] | 181 | target_display_placement->parent_display_id = |
afakhry | 6c80a875 | 2017-02-01 01:38:04 | [diff] [blame] | 182 | last_intersecting_source_display->id(); |
| 183 | // Update the map. |
| 184 | (*display_to_parent_ids_map)[target_display->id()] = |
| 185 | last_intersecting_source_display->id(); |
| 186 | |
| 187 | if (last_offset_x) { |
afakhry | a0bad961 | 2017-02-01 22:45:18 | [diff] [blame] | 188 | target_display_placement->position = |
afakhry | 6c80a875 | 2017-02-01 01:38:04 | [diff] [blame] | 189 | last_offset_x > 0 ? DisplayPlacement::RIGHT : DisplayPlacement::LEFT; |
| 190 | } else { |
afakhry | a0bad961 | 2017-02-01 22:45:18 | [diff] [blame] | 191 | target_display_placement->position = |
afakhry | 6c80a875 | 2017-02-01 01:38:04 | [diff] [blame] | 192 | last_offset_y > 0 ? DisplayPlacement::BOTTOM : DisplayPlacement::TOP; |
| 193 | } |
| 194 | } |
| 195 | |
| 196 | // Offsets |display| by the provided |x| and |y| values. |
| 197 | void OffsetDisplay(Display* display, int x, int y) { |
| 198 | gfx::Point new_origin = display->bounds().origin(); |
| 199 | new_origin.Offset(x, y); |
| 200 | gfx::Insets insets = display->GetWorkAreaInsets(); |
| 201 | display->set_bounds(gfx::Rect(new_origin, display->bounds().size())); |
| 202 | display->UpdateWorkAreaFromInsets(insets); |
| 203 | } |
| 204 | |
| 205 | // Calculates the amount of offset along the X or Y axes for the target display |
| 206 | // with |target_bounds| to de-intersect with the source display with |
| 207 | // |source_bounds|. |
| 208 | // These functions assume both displays already intersect. |
| 209 | int CalculateOffsetX(const gfx::Rect& source_bounds, |
| 210 | const gfx::Rect& target_bounds) { |
| 211 | if (target_bounds.x() >= 0) { |
| 212 | // Target display moves along the +ve X direction. |
| 213 | return source_bounds.right() - target_bounds.x(); |
| 214 | } |
| 215 | |
| 216 | // Target display moves along the -ve X direction. |
| 217 | return -(target_bounds.right() - source_bounds.x()); |
| 218 | } |
| 219 | int CalculateOffsetY(const gfx::Rect& source_bounds, |
| 220 | const gfx::Rect& target_bounds) { |
| 221 | if (target_bounds.y() >= 0) { |
| 222 | // Target display moves along the +ve Y direction. |
| 223 | return source_bounds.bottom() - target_bounds.y(); |
| 224 | } |
| 225 | |
| 226 | // Target display moves along the -ve Y direction. |
| 227 | return -(target_bounds.bottom() - source_bounds.y()); |
| 228 | } |
| 229 | |
| 230 | // Fixes any overlapping displays and reparents displays if necessary. |
| 231 | void DeIntersectDisplays(int64_t primary_id, |
| 232 | Displays* display_list, |
| 233 | std::vector<DisplayPlacement>* placement_list, |
| 234 | std::set<int64_t>* updated_displays) { |
| 235 | std::map<int64_t, int64_t> display_to_parent_ids_map; |
| 236 | for (const DisplayPlacement& placement : *placement_list) { |
| 237 | display_to_parent_ids_map.insert( |
| 238 | std::make_pair(placement.display_id, placement.parent_display_id)); |
| 239 | } |
| 240 | |
| 241 | std::vector<Display*> sorted_displays; |
| 242 | for (Display& display : *display_list) |
| 243 | sorted_displays.push_back(&display); |
| 244 | |
| 245 | // Sort the displays first by their depth in the display hierarchy tree, and |
| 246 | // then by distance of their top left points from the origin. This way we |
| 247 | // process the displays starting at the root (the primary display), in the |
Andrew Wolfers | 0783e12 | 2023-01-30 15:55:41 | [diff] [blame] | 248 | // order of their descendence spanning out from the primary display. |
afakhry | 6c80a875 | 2017-02-01 01:38:04 | [diff] [blame] | 249 | std::sort(sorted_displays.begin(), sorted_displays.end(), [&](Display* d1, |
| 250 | Display* d2) { |
| 251 | const int d1_depth = |
| 252 | GetDisplayTreeDepth(d1->id(), primary_id, display_to_parent_ids_map); |
| 253 | const int d2_depth = |
| 254 | GetDisplayTreeDepth(d2->id(), primary_id, display_to_parent_ids_map); |
| 255 | |
| 256 | if (d1_depth != d2_depth) |
| 257 | return d1_depth < d2_depth; |
| 258 | |
afakhry | a0bad961 | 2017-02-01 22:45:18 | [diff] [blame] | 259 | const int64_t d1_distance = d1->bounds().OffsetFromOrigin().LengthSquared(); |
| 260 | const int64_t d2_distance = d2->bounds().OffsetFromOrigin().LengthSquared(); |
| 261 | |
| 262 | if (d1_distance != d2_distance) |
| 263 | return d1_distance < d2_distance; |
| 264 | |
| 265 | return d1->id() < d2->id(); |
afakhry | 6c80a875 | 2017-02-01 01:38:04 | [diff] [blame] | 266 | }); |
| 267 | // This must result in the primary display being at the front of the list. |
| 268 | DCHECK_EQ(sorted_displays.front()->id(), primary_id); |
| 269 | |
afakhry | a0bad961 | 2017-02-01 22:45:18 | [diff] [blame] | 270 | for (size_t i = 1; i < sorted_displays.size(); ++i) { |
afakhry | 6c80a875 | 2017-02-01 01:38:04 | [diff] [blame] | 271 | Display* target_display = sorted_displays[i]; |
| 272 | const Display* last_intersecting_source_display = nullptr; |
| 273 | int last_offset_x = 0; |
| 274 | int last_offset_y = 0; |
afakhry | a0bad961 | 2017-02-01 22:45:18 | [diff] [blame] | 275 | for (size_t j = 0; j < i; ++j) { |
afakhry | 6c80a875 | 2017-02-01 01:38:04 | [diff] [blame] | 276 | const Display* source_display = sorted_displays[j]; |
| 277 | const gfx::Rect source_bounds = source_display->bounds(); |
| 278 | const gfx::Rect target_bounds = target_display->bounds(); |
| 279 | |
| 280 | gfx::Rect intersection = source_bounds; |
| 281 | intersection.Intersect(target_bounds); |
| 282 | |
| 283 | if (intersection.IsEmpty()) |
| 284 | continue; |
| 285 | |
| 286 | // Calculate offsets along both X and Y axes such that either can remove |
| 287 | // the overlap, but choose and apply the smaller offset. This way we have |
| 288 | // more predictable results. |
| 289 | int offset_x = 0; |
| 290 | int offset_y = 0; |
| 291 | if (intersection.width()) |
| 292 | offset_x = CalculateOffsetX(source_bounds, target_bounds); |
| 293 | if (intersection.height()) |
| 294 | offset_y = CalculateOffsetY(source_bounds, target_bounds); |
| 295 | |
| 296 | if (offset_x == 0 && offset_y == 0) |
| 297 | continue; |
| 298 | |
| 299 | // Choose the smaller offset. |
| 300 | if (std::abs(offset_x) <= std::abs(offset_y)) |
| 301 | offset_y = 0; |
| 302 | else |
| 303 | offset_x = 0; |
| 304 | |
| 305 | OffsetDisplay(target_display, offset_x, offset_y); |
| 306 | updated_displays->insert(target_display->id()); |
| 307 | |
| 308 | // The most recent performed de-intersection data. |
| 309 | last_intersecting_source_display = source_display; |
| 310 | last_offset_x = offset_x; |
| 311 | last_offset_y = offset_y; |
| 312 | } |
| 313 | |
| 314 | if (!last_intersecting_source_display) |
| 315 | continue; |
| 316 | |
| 317 | MaybeReparentTargetDisplay(last_offset_x, last_offset_y, |
| 318 | last_intersecting_source_display, target_display, |
| 319 | &display_to_parent_ids_map, display_list, |
| 320 | placement_list); |
| 321 | } |
| 322 | |
afakhry | a0bad961 | 2017-02-01 22:45:18 | [diff] [blame] | 323 | // New placements might have been added and offsets might have changed and we |
| 324 | // must update them. |
| 325 | UpdatePlacementList(display_list, placement_list); |
robliao | 4567a867 | 2016-04-12 16:45:39 | [diff] [blame] | 326 | } |
| 327 | |
Steven Bennetts | 9c10bc4 | 2018-04-02 17:02:04 | [diff] [blame] | 328 | // Checks if the given point is over the radius vector described by its end |
| 329 | // point |vector|. The point is over a vector if it's on its positive (left) |
| 330 | // side. The method sees a point on the same line as the vector as being over |
| 331 | // the vector. |
| 332 | bool IsPointOverRadiusVector(const gfx::Point& point, |
| 333 | const gfx::Point& vector) { |
| 334 | // |point| is left of |vector| if its radius vector's scalar product with a |
| 335 | // vector orthogonal (and facing the positive side) to |vector| is positive. |
| 336 | // |
| 337 | // An orthogonal vector of (a, b) is (b, -a), as the scalar product of these |
| 338 | // two is 0. |
| 339 | // So, (x, y) is over (a, b) if x * b + y * (-a) >= 0, which is equivalent to |
| 340 | // x * b >= y * a. |
| 341 | return static_cast<int64_t>(point.x()) * static_cast<int64_t>(vector.y()) >= |
| 342 | static_cast<int64_t>(point.y()) * static_cast<int64_t>(vector.x()); |
| 343 | } |
| 344 | |
[email protected] | 93c7e562 | 2013-06-28 15:12:06 | [diff] [blame] | 345 | } // namespace |
| 346 | |
| 347 | //////////////////////////////////////////////////////////////////////////////// |
oshima | 95d499b | 2016-02-10 03:49:56 | [diff] [blame] | 348 | // DisplayPlacement |
oshima | f571c4a | 2016-02-24 18:51:05 | [diff] [blame] | 349 | |
| 350 | DisplayPlacement::DisplayPlacement() |
kylechar | 7bfba989 | 2016-11-21 20:44:03 | [diff] [blame] | 351 | : DisplayPlacement(kInvalidDisplayId, |
| 352 | kInvalidDisplayId, |
robliao | ec805556 | 2016-05-11 22:55:40 | [diff] [blame] | 353 | DisplayPlacement::RIGHT, |
| 354 | 0, |
| 355 | DisplayPlacement::TOP_LEFT) {} |
oshima | f571c4a | 2016-02-24 18:51:05 | [diff] [blame] | 356 | |
robliao | ec805556 | 2016-05-11 22:55:40 | [diff] [blame] | 357 | DisplayPlacement::DisplayPlacement(Position position, int offset) |
kylechar | 7bfba989 | 2016-11-21 20:44:03 | [diff] [blame] | 358 | : DisplayPlacement(kInvalidDisplayId, |
| 359 | kInvalidDisplayId, |
robliao | ec805556 | 2016-05-11 22:55:40 | [diff] [blame] | 360 | position, |
| 361 | offset, |
| 362 | DisplayPlacement::TOP_LEFT) {} |
| 363 | |
| 364 | DisplayPlacement::DisplayPlacement(Position position, |
| 365 | int offset, |
| 366 | OffsetReference offset_reference) |
kylechar | 7bfba989 | 2016-11-21 20:44:03 | [diff] [blame] | 367 | : DisplayPlacement(kInvalidDisplayId, |
| 368 | kInvalidDisplayId, |
robliao | ec805556 | 2016-05-11 22:55:40 | [diff] [blame] | 369 | position, |
| 370 | offset, |
| 371 | offset_reference) {} |
| 372 | |
| 373 | DisplayPlacement::DisplayPlacement(int64_t display_id, |
| 374 | int64_t parent_display_id, |
| 375 | Position position, |
| 376 | int offset, |
| 377 | OffsetReference offset_reference) |
| 378 | : display_id(display_id), |
| 379 | parent_display_id(parent_display_id), |
| 380 | position(position), |
| 381 | offset(offset), |
| 382 | offset_reference(offset_reference) { |
[email protected] | 93c7e562 | 2013-06-28 15:12:06 | [diff] [blame] | 383 | DCHECK_LE(TOP, position); |
| 384 | DCHECK_GE(LEFT, position); |
[email protected] | 93c7e562 | 2013-06-28 15:12:06 | [diff] [blame] | 385 | // Set the default value to |position| in case position is invalid. DCHECKs |
| 386 | // above doesn't stop in Release builds. |
| 387 | if (TOP > position || LEFT < position) |
| 388 | this->position = RIGHT; |
| 389 | |
| 390 | DCHECK_GE(kMaxValidOffset, abs(offset)); |
| 391 | } |
| 392 | |
Peter Kasting | b6e09f51 | 2021-07-08 05:33:53 | [diff] [blame] | 393 | DisplayPlacement::DisplayPlacement(const DisplayPlacement&) = default; |
| 394 | |
| 395 | DisplayPlacement& DisplayPlacement::operator=(const DisplayPlacement&) = |
| 396 | default; |
robliao | df37203 | 2016-03-23 00:42:34 | [diff] [blame] | 397 | |
oshima | 95d499b | 2016-02-10 03:49:56 | [diff] [blame] | 398 | DisplayPlacement& DisplayPlacement::Swap() { |
[email protected] | 93c7e562 | 2013-06-28 15:12:06 | [diff] [blame] | 399 | switch (position) { |
| 400 | case TOP: |
oshima | 95d499b | 2016-02-10 03:49:56 | [diff] [blame] | 401 | position = BOTTOM; |
[email protected] | 93c7e562 | 2013-06-28 15:12:06 | [diff] [blame] | 402 | break; |
| 403 | case BOTTOM: |
oshima | 95d499b | 2016-02-10 03:49:56 | [diff] [blame] | 404 | position = TOP; |
[email protected] | 93c7e562 | 2013-06-28 15:12:06 | [diff] [blame] | 405 | break; |
| 406 | case RIGHT: |
oshima | 95d499b | 2016-02-10 03:49:56 | [diff] [blame] | 407 | position = LEFT; |
[email protected] | 93c7e562 | 2013-06-28 15:12:06 | [diff] [blame] | 408 | break; |
| 409 | case LEFT: |
oshima | 95d499b | 2016-02-10 03:49:56 | [diff] [blame] | 410 | position = RIGHT; |
[email protected] | 93c7e562 | 2013-06-28 15:12:06 | [diff] [blame] | 411 | break; |
| 412 | } |
oshima | 95d499b | 2016-02-10 03:49:56 | [diff] [blame] | 413 | offset = -offset; |
oshima | 61af6e1 | 2016-02-12 01:00:52 | [diff] [blame] | 414 | std::swap(display_id, parent_display_id); |
oshima | 95d499b | 2016-02-10 03:49:56 | [diff] [blame] | 415 | return *this; |
[email protected] | 93c7e562 | 2013-06-28 15:12:06 | [diff] [blame] | 416 | } |
| 417 | |
oshima | 95d499b | 2016-02-10 03:49:56 | [diff] [blame] | 418 | std::string DisplayPlacement::ToString() const { |
oshima | f571c4a | 2016-02-24 18:51:05 | [diff] [blame] | 419 | std::stringstream s; |
kylechar | 7bfba989 | 2016-11-21 20:44:03 | [diff] [blame] | 420 | if (display_id != kInvalidDisplayId) |
oshima | f571c4a | 2016-02-24 18:51:05 | [diff] [blame] | 421 | s << "id=" << display_id << ", "; |
kylechar | 7bfba989 | 2016-11-21 20:44:03 | [diff] [blame] | 422 | if (parent_display_id != kInvalidDisplayId) |
oshima | f571c4a | 2016-02-24 18:51:05 | [diff] [blame] | 423 | s << "parent=" << parent_display_id << ", "; |
robliao | 8c912b53 | 2016-03-21 21:34:15 | [diff] [blame] | 424 | s << PositionToString(position) << ", "; |
oshima | f571c4a | 2016-02-24 18:51:05 | [diff] [blame] | 425 | s << offset; |
| 426 | return s.str(); |
| 427 | } |
| 428 | |
| 429 | // static |
robliao | 8c912b53 | 2016-03-21 21:34:15 | [diff] [blame] | 430 | std::string DisplayPlacement::PositionToString(Position position) { |
| 431 | switch (position) { |
| 432 | case TOP: |
| 433 | return kTop; |
| 434 | case RIGHT: |
| 435 | return kRight; |
| 436 | case BOTTOM: |
| 437 | return kBottom; |
| 438 | case LEFT: |
| 439 | return kLeft; |
| 440 | } |
| 441 | return kUnknown; |
| 442 | } |
| 443 | |
| 444 | // static |
Helmut Januschka | 36619c1 | 2024-04-24 14:33:19 | [diff] [blame] | 445 | bool DisplayPlacement::StringToPosition(std::string_view string, |
robliao | 8c912b53 | 2016-03-21 21:34:15 | [diff] [blame] | 446 | Position* position) { |
| 447 | if (string == kTop) { |
| 448 | *position = TOP; |
| 449 | return true; |
| 450 | } |
| 451 | |
| 452 | if (string == kRight) { |
| 453 | *position = RIGHT; |
| 454 | return true; |
| 455 | } |
| 456 | |
| 457 | if (string == kBottom) { |
| 458 | *position = BOTTOM; |
| 459 | return true; |
| 460 | } |
| 461 | |
| 462 | if (string == kLeft) { |
| 463 | *position = LEFT; |
| 464 | return true; |
| 465 | } |
| 466 | |
| 467 | LOG(ERROR) << "Invalid position value:" << string; |
| 468 | |
| 469 | return false; |
oshima | 95d499b | 2016-02-10 03:49:56 | [diff] [blame] | 470 | } |
| 471 | |
| 472 | //////////////////////////////////////////////////////////////////////////////// |
| 473 | // DisplayLayout |
| 474 | |
oshima | 61af6e1 | 2016-02-12 01:00:52 | [diff] [blame] | 475 | DisplayLayout::DisplayLayout() |
Weidong Guo | 15abd31 | 2017-12-21 17:35:01 | [diff] [blame] | 476 | : default_unified(true), primary_id(kInvalidDisplayId) {} |
oshima | 95d499b | 2016-02-10 03:49:56 | [diff] [blame] | 477 | |
| 478 | DisplayLayout::~DisplayLayout() {} |
| 479 | |
msw | 5e048a7 | 2016-09-07 18:55:30 | [diff] [blame] | 480 | void DisplayLayout::ApplyToDisplayList(Displays* display_list, |
robliao | 4567a867 | 2016-04-12 16:45:39 | [diff] [blame] | 481 | std::vector<int64_t>* updated_ids, |
afakhry | 6c80a875 | 2017-02-01 01:38:04 | [diff] [blame] | 482 | int minimum_offset_overlap) { |
| 483 | if (placement_list.empty()) |
| 484 | return; |
| 485 | |
| 486 | if (!DisplayLayout::Validate(DisplayListToDisplayIdList(*display_list), |
| 487 | *this)) { |
| 488 | // Prevent invalid and non-relevant display layouts. |
Mitsuru Oshima | 9b4bfa8 | 2025-04-17 23:49:21 | [diff] [blame] | 489 | LOG(ERROR) << "Invalid Display Layout"; |
afakhry | 6c80a875 | 2017-02-01 01:38:04 | [diff] [blame] | 490 | return; |
| 491 | } |
| 492 | |
robliao | 4567a867 | 2016-04-12 16:45:39 | [diff] [blame] | 493 | // Layout from primary, then dependent displays. |
| 494 | std::set<int64_t> parents; |
afakhry | 6c80a875 | 2017-02-01 01:38:04 | [diff] [blame] | 495 | std::set<int64_t> updated_displays; |
robliao | 4567a867 | 2016-04-12 16:45:39 | [diff] [blame] | 496 | parents.insert(primary_id); |
| 497 | while (parents.size()) { |
| 498 | int64_t parent_id = *parents.begin(); |
| 499 | parents.erase(parent_id); |
| 500 | for (const DisplayPlacement& placement : placement_list) { |
| 501 | if (placement.parent_display_id == parent_id) { |
kylechar | 731f85f9 | 2016-12-01 20:50:46 | [diff] [blame] | 502 | if (ApplyDisplayPlacement(placement, display_list, |
afakhry | 6c80a875 | 2017-02-01 01:38:04 | [diff] [blame] | 503 | minimum_offset_overlap)) { |
| 504 | updated_displays.insert(placement.display_id); |
robliao | 4567a867 | 2016-04-12 16:45:39 | [diff] [blame] | 505 | } |
| 506 | parents.insert(placement.display_id); |
| 507 | } |
| 508 | } |
| 509 | } |
afakhry | 6c80a875 | 2017-02-01 01:38:04 | [diff] [blame] | 510 | |
| 511 | // Now that all the placements have been applied, we must detect and fix any |
| 512 | // overlapping displays. |
| 513 | DeIntersectDisplays(primary_id, display_list, &placement_list, |
| 514 | &updated_displays); |
| 515 | |
| 516 | if (updated_ids) { |
| 517 | updated_ids->insert(updated_ids->begin(), updated_displays.begin(), |
| 518 | updated_displays.end()); |
| 519 | } |
robliao | 4567a867 | 2016-04-12 16:45:39 | [diff] [blame] | 520 | } |
| 521 | |
[email protected] | 93c7e562 | 2013-06-28 15:12:06 | [diff] [blame] | 522 | // static |
oshima | f571c4a | 2016-02-24 18:51:05 | [diff] [blame] | 523 | bool DisplayLayout::Validate(const DisplayIdList& list, |
| 524 | const DisplayLayout& layout) { |
| 525 | // The primary display should be in the list. |
Peter Kasting | 862ac0f | 2022-09-19 15:28:14 | [diff] [blame] | 526 | if (!base::Contains(list, layout.primary_id)) { |
Jeroen Dhollander | 7c83e09 | 2023-11-03 17:59:32 | [diff] [blame] | 527 | DISPLAY_LOG(ERROR) << "The primary id: " << layout.primary_id |
| 528 | << " is not in the id list."; |
afakhry | 6c80a875 | 2017-02-01 01:38:04 | [diff] [blame] | 529 | return false; |
| 530 | } |
oshima | f571c4a | 2016-02-24 18:51:05 | [diff] [blame] | 531 | |
| 532 | // Unified mode, or mirror mode switched from unified mode, |
| 533 | // may not have the placement yet. |
| 534 | if (layout.placement_list.size() == 0u) |
| 535 | return true; |
| 536 | |
| 537 | bool has_primary_as_parent = false; |
afakhry | d3d4ea4 | 2017-04-14 02:01:43 | [diff] [blame] | 538 | // The placement list must be sorted by the first 8 bits of the display IDs. |
Mitsuru Oshima | 9b4bfa8 | 2025-04-17 23:49:21 | [diff] [blame] | 539 | #if BUILDFLAG(IS_CHROMEOS) |
afakhry | d3d4ea4 | 2017-04-14 02:01:43 | [diff] [blame] | 540 | int64_t prev_id = std::numeric_limits<int8_t>::min(); |
Mitsuru Oshima | 9b4bfa8 | 2025-04-17 23:49:21 | [diff] [blame] | 541 | #endif // BUILDFLAG(IS_CHROMEOS) |
robliao | df37203 | 2016-03-23 00:42:34 | [diff] [blame] | 542 | for (const auto& placement : layout.placement_list) { |
Mitsuru Oshima | 9b4bfa8 | 2025-04-17 23:49:21 | [diff] [blame] | 543 | #if BUILDFLAG(IS_CHROMEOS) |
| 544 | // Placements are sorted by display_id on ChromeOS. |
afakhry | d3d4ea4 | 2017-04-14 02:01:43 | [diff] [blame] | 545 | if (prev_id >= (placement.display_id & 0xFF)) { |
Jeroen Dhollander | 7c83e09 | 2023-11-03 17:59:32 | [diff] [blame] | 546 | DISPLAY_LOG(ERROR) << "PlacementList must be sorted by first 8 bits of" |
| 547 | << " display_id "; |
oshima | f571c4a | 2016-02-24 18:51:05 | [diff] [blame] | 548 | return false; |
| 549 | } |
afakhry | d3d4ea4 | 2017-04-14 02:01:43 | [diff] [blame] | 550 | prev_id = (placement.display_id & 0xFF); |
Mitsuru Oshima | 9b4bfa8 | 2025-04-17 23:49:21 | [diff] [blame] | 551 | #endif // BUILDFLAG(IS_CHROMEOS) |
kylechar | 7bfba989 | 2016-11-21 20:44:03 | [diff] [blame] | 552 | if (placement.display_id == kInvalidDisplayId) { |
Jeroen Dhollander | 7c83e09 | 2023-11-03 17:59:32 | [diff] [blame] | 553 | DISPLAY_LOG(ERROR) << "display_id is not initialized"; |
oshima | f571c4a | 2016-02-24 18:51:05 | [diff] [blame] | 554 | return false; |
| 555 | } |
kylechar | 7bfba989 | 2016-11-21 20:44:03 | [diff] [blame] | 556 | if (placement.parent_display_id == kInvalidDisplayId) { |
Jeroen Dhollander | 7c83e09 | 2023-11-03 17:59:32 | [diff] [blame] | 557 | DISPLAY_LOG(ERROR) << "display_parent_id is not initialized"; |
oshima | f571c4a | 2016-02-24 18:51:05 | [diff] [blame] | 558 | return false; |
| 559 | } |
robliao | df37203 | 2016-03-23 00:42:34 | [diff] [blame] | 560 | if (placement.display_id == placement.parent_display_id) { |
Jeroen Dhollander | 7c83e09 | 2023-11-03 17:59:32 | [diff] [blame] | 561 | DISPLAY_LOG(ERROR) << "display_id must not be same as parent_display_id"; |
oshima | f571c4a | 2016-02-24 18:51:05 | [diff] [blame] | 562 | return false; |
| 563 | } |
Peter Kasting | 862ac0f | 2022-09-19 15:28:14 | [diff] [blame] | 564 | if (!base::Contains(list, placement.display_id)) { |
Jeroen Dhollander | 7c83e09 | 2023-11-03 17:59:32 | [diff] [blame] | 565 | DISPLAY_LOG(ERROR) << "display_id is not in the id list:" |
| 566 | << placement.ToString(); |
oshima | f571c4a | 2016-02-24 18:51:05 | [diff] [blame] | 567 | return false; |
| 568 | } |
| 569 | |
Peter Kasting | 862ac0f | 2022-09-19 15:28:14 | [diff] [blame] | 570 | if (!base::Contains(list, placement.parent_display_id)) { |
Jeroen Dhollander | 7c83e09 | 2023-11-03 17:59:32 | [diff] [blame] | 571 | DISPLAY_LOG(ERROR) << "parent_display_id is not in the id list:" |
| 572 | << placement.ToString(); |
oshima | f571c4a | 2016-02-24 18:51:05 | [diff] [blame] | 573 | return false; |
| 574 | } |
robliao | df37203 | 2016-03-23 00:42:34 | [diff] [blame] | 575 | has_primary_as_parent |= layout.primary_id == placement.parent_display_id; |
oshima | f571c4a | 2016-02-24 18:51:05 | [diff] [blame] | 576 | } |
| 577 | if (!has_primary_as_parent) |
Jeroen Dhollander | 7c83e09 | 2023-11-03 17:59:32 | [diff] [blame] | 578 | DISPLAY_LOG(ERROR) |
| 579 | << "At least, one placement must have the primary as a parent."; |
oshima | f571c4a | 2016-02-24 18:51:05 | [diff] [blame] | 580 | return has_primary_as_parent; |
| 581 | } |
| 582 | |
danakj | 25c52c3 | 2016-04-12 21:51:08 | [diff] [blame] | 583 | std::unique_ptr<DisplayLayout> DisplayLayout::Copy() const { |
| 584 | std::unique_ptr<DisplayLayout> copy(new DisplayLayout); |
robliao | df37203 | 2016-03-23 00:42:34 | [diff] [blame] | 585 | for (const auto& placement : placement_list) |
| 586 | copy->placement_list.push_back(placement); |
oshima | f571c4a | 2016-02-24 18:51:05 | [diff] [blame] | 587 | copy->default_unified = default_unified; |
| 588 | copy->primary_id = primary_id; |
| 589 | return copy; |
| 590 | } |
| 591 | |
kylechar | 6e8ea59 | 2017-03-30 15:27:57 | [diff] [blame] | 592 | void DisplayLayout::SwapPrimaryDisplay(int64_t new_primary_id) { |
| 593 | if (primary_id == new_primary_id) |
| 594 | return; |
| 595 | |
| 596 | // Build a map of the *original* |display_id| for each placement. |
| 597 | std::unordered_map<int64_t, DisplayPlacement*> id_to_placement; |
| 598 | for (auto& placement : placement_list) |
| 599 | id_to_placement[placement.display_id] = &placement; |
| 600 | |
| 601 | // Swap placements so that |new_primary_id| is the display that placements are |
| 602 | // anchored on and set |primary_id|. |
| 603 | int64_t swap_display_id = new_primary_id; |
| 604 | while (swap_display_id != primary_id) { |
| 605 | DisplayPlacement* placement = id_to_placement.at(swap_display_id); |
| 606 | swap_display_id = placement->parent_display_id; |
| 607 | placement->Swap(); |
| 608 | } |
| 609 | std::sort(placement_list.begin(), placement_list.end(), ComparePlacements); |
| 610 | primary_id = new_primary_id; |
| 611 | } |
| 612 | |
oshima | f571c4a | 2016-02-24 18:51:05 | [diff] [blame] | 613 | bool DisplayLayout::HasSamePlacementList(const DisplayLayout& layout) const { |
kylechar | 1d6d5d2 | 2017-02-03 14:34:49 | [diff] [blame] | 614 | return placement_list == layout.placement_list; |
oshima | f571c4a | 2016-02-24 18:51:05 | [diff] [blame] | 615 | } |
| 616 | |
Mitsuru Oshima | d6890ba | 2022-04-14 06:58:33 | [diff] [blame] | 617 | void DisplayLayout::RemoveDisplayPlacements(const DisplayIdList& list) { |
Andrew Rayskiy | 0b0a148 | 2024-02-27 16:07:56 | [diff] [blame] | 618 | std::erase_if(placement_list, [&list](const DisplayPlacement& placement) { |
K. M. Merajul Arefin | 2de2f7d | 2023-12-12 16:19:45 | [diff] [blame] | 619 | return base::Contains(list, placement.display_id); |
| 620 | }); |
Mitsuru Oshima | d6890ba | 2022-04-14 06:58:33 | [diff] [blame] | 621 | for (DisplayPlacement& placement : placement_list) { |
| 622 | if (base::Contains(list, placement.parent_display_id)) |
| 623 | placement.parent_display_id = primary_id; |
| 624 | } |
| 625 | } |
| 626 | |
oshima | f571c4a | 2016-02-24 18:51:05 | [diff] [blame] | 627 | std::string DisplayLayout::ToString() const { |
| 628 | std::stringstream s; |
| 629 | s << "primary=" << primary_id; |
oshima | f571c4a | 2016-02-24 18:51:05 | [diff] [blame] | 630 | if (default_unified) |
| 631 | s << ", default_unified"; |
| 632 | bool added = false; |
robliao | df37203 | 2016-03-23 00:42:34 | [diff] [blame] | 633 | for (const auto& placement : placement_list) { |
oshima | f571c4a | 2016-02-24 18:51:05 | [diff] [blame] | 634 | s << (added ? "),(" : " [("); |
robliao | df37203 | 2016-03-23 00:42:34 | [diff] [blame] | 635 | s << placement.ToString(); |
oshima | f571c4a | 2016-02-24 18:51:05 | [diff] [blame] | 636 | added = true; |
| 637 | } |
| 638 | if (added) |
| 639 | s << ")]"; |
| 640 | return s.str(); |
[email protected] | 93c7e562 | 2013-06-28 15:12:06 | [diff] [blame] | 641 | } |
| 642 | |
robliao | df37203 | 2016-03-23 00:42:34 | [diff] [blame] | 643 | DisplayPlacement DisplayLayout::FindPlacementById(int64_t display_id) const { |
Peter Kasting | e10dd2e5 | 2025-01-28 00:26:12 | [diff] [blame] | 644 | const auto iter = std::ranges::find(placement_list, display_id, |
| 645 | &DisplayPlacement::display_id); |
robliao | df37203 | 2016-03-23 00:42:34 | [diff] [blame] | 646 | return (iter == placement_list.end()) ? DisplayPlacement() |
| 647 | : DisplayPlacement(*iter); |
stevenjb | 209a1775 | 2016-02-29 23:53:33 | [diff] [blame] | 648 | } |
| 649 | |
Steven Bennetts | 9c10bc4 | 2018-04-02 17:02:04 | [diff] [blame] | 650 | // Creates a display::DisplayPlacement value for |rectangle| relative to the |
| 651 | // |reference| rectangle. |
| 652 | // The layout consists of two values: |
| 653 | // - position: Whether the rectangle is positioned left, right, over or under |
| 654 | // the reference. |
| 655 | // - offset: The rectangle's offset from the reference origin along the axis |
| 656 | // opposite the position direction (if the rectangle is left or right along |
| 657 | // y-axis, otherwise along x-axis). |
| 658 | // The rectangle's position is calculated by dividing the space in areas defined |
| 659 | // by the |reference|'s diagonals and finding the area |rectangle|'s center |
| 660 | // point belongs. If the |rectangle| in the calculated layout does not share a |
| 661 | // part of the bounds with the |reference|, the |rectangle| position in set to |
| 662 | // the more suitable neighboring position (e.g. if |rectangle| is completely |
| 663 | // over the |reference| top bound, it will be set to TOP) and the layout is |
| 664 | // recalculated with the new position. This is to handle the case where the |
| 665 | // rectangle shares an edge with the reference, but it's center is not in the |
| 666 | // same area as the reference's edge, e.g. |
| 667 | // |
| 668 | // +---------------------+ |
| 669 | // | | |
| 670 | // | REFERENCE | |
| 671 | // | | |
| 672 | // | | |
| 673 | // +---------------------+ |
| 674 | // +-------------------------------------------------+ |
| 675 | // | RECTANGLE x | |
| 676 | // +-------------------------------------------------+ |
| 677 | // |
Andrew Wolfers | 0783e12 | 2023-01-30 15:55:41 | [diff] [blame] | 678 | // The rectangle shares an edge with the reference's bottom edge, but its |
Steven Bennetts | 9c10bc4 | 2018-04-02 17:02:04 | [diff] [blame] | 679 | // center point is in the left area. |
| 680 | |
| 681 | // static |
| 682 | DisplayPlacement DisplayLayout::CreatePlacementForRectangles( |
| 683 | const gfx::Rect& reference, |
| 684 | const gfx::Rect& rectangle) { |
| 685 | // Translate coordinate system so origin is in the reference's top left point |
| 686 | // (so the reference's down-diagonal vector starts in the (0, 0)) and scale it |
| 687 | // up by two (to avoid division when calculating the rectangle's center |
| 688 | // point). |
| 689 | gfx::Point center(2 * (rectangle.x() - reference.x()) + rectangle.width(), |
| 690 | 2 * (rectangle.y() - reference.y()) + rectangle.height()); |
| 691 | gfx::Point down_diag(2 * reference.width(), 2 * reference.height()); |
| 692 | |
| 693 | bool is_top_right = IsPointOverRadiusVector(center, down_diag); |
| 694 | |
| 695 | // Translate the coordinate system again, so the bottom right point of the |
| 696 | // reference is origin (so the reference's up-diagonal starts at (0, 0)). |
| 697 | // Note that the coordinate system is scaled by 2. |
| 698 | center.Offset(0, -2 * reference.height()); |
| 699 | // Choose the vector orientation so the points on the diagonal are considered |
| 700 | // to be left. |
| 701 | gfx::Point up_diag(-2 * reference.width(), 2 * reference.height()); |
| 702 | |
| 703 | bool is_bottom_right = IsPointOverRadiusVector(center, up_diag); |
| 704 | |
| 705 | DisplayPlacement::Position position; |
| 706 | if (is_top_right) { |
| 707 | position = |
| 708 | is_bottom_right ? DisplayPlacement::RIGHT : DisplayPlacement::TOP; |
| 709 | } else { |
| 710 | position = |
| 711 | is_bottom_right ? DisplayPlacement::BOTTOM : DisplayPlacement::LEFT; |
| 712 | } |
| 713 | |
| 714 | // If the rectangle with the calculated position would not have common side |
| 715 | // with the reference, try to position it so it shares another edge with the |
| 716 | // reference. |
| 717 | if (is_top_right == is_bottom_right) { |
| 718 | if (rectangle.y() > reference.bottom()) { |
| 719 | // The rectangle is left or right, but completely under the reference. |
| 720 | position = DisplayPlacement::BOTTOM; |
| 721 | } else if (rectangle.bottom() < reference.y()) { |
| 722 | // The rectangle is left or right, but completely over the reference. |
| 723 | position = DisplayPlacement::TOP; |
| 724 | } |
| 725 | } else { |
| 726 | if (rectangle.x() > reference.right()) { |
| 727 | // The rectangle is over or under, but completely right of the reference. |
| 728 | position = DisplayPlacement::RIGHT; |
| 729 | } else if (rectangle.right() < reference.x()) { |
| 730 | // The rectangle is over or under, but completely left of the reference. |
| 731 | position = DisplayPlacement::LEFT; |
| 732 | } |
| 733 | } |
| 734 | int offset = (position == DisplayPlacement::LEFT || |
| 735 | position == DisplayPlacement::RIGHT) |
| 736 | ? rectangle.y() |
| 737 | : rectangle.x(); |
| 738 | return DisplayPlacement(position, offset); |
| 739 | } |
| 740 | |
robliao | 4567a867 | 2016-04-12 16:45:39 | [diff] [blame] | 741 | // static |
| 742 | bool DisplayLayout::ApplyDisplayPlacement(const DisplayPlacement& placement, |
msw | 5e048a7 | 2016-09-07 18:55:30 | [diff] [blame] | 743 | Displays* display_list, |
robliao | 4567a867 | 2016-04-12 16:45:39 | [diff] [blame] | 744 | int minimum_offset_overlap) { |
kylechar | 79bed53c | 2016-09-01 13:02:31 | [diff] [blame] | 745 | const Display& parent_display = |
robliao | 4567a867 | 2016-04-12 16:45:39 | [diff] [blame] | 746 | *FindDisplayById(display_list, placement.parent_display_id); |
| 747 | DCHECK(parent_display.is_valid()); |
kylechar | 79bed53c | 2016-09-01 13:02:31 | [diff] [blame] | 748 | Display* target_display = FindDisplayById(display_list, placement.display_id); |
robliao | 4567a867 | 2016-04-12 16:45:39 | [diff] [blame] | 749 | gfx::Rect old_bounds(target_display->bounds()); |
| 750 | DCHECK(target_display); |
| 751 | |
| 752 | const gfx::Rect& parent_bounds = parent_display.bounds(); |
| 753 | const gfx::Rect& target_bounds = target_display->bounds(); |
| 754 | gfx::Point new_target_origin = parent_bounds.origin(); |
| 755 | |
| 756 | DisplayPlacement::Position position = placement.position; |
| 757 | |
| 758 | // Ignore the offset in case the target display doesn't share edges with |
| 759 | // the parent display. |
| 760 | int offset = placement.offset; |
| 761 | if (position == DisplayPlacement::TOP || |
| 762 | position == DisplayPlacement::BOTTOM) { |
robliao | ec805556 | 2016-05-11 22:55:40 | [diff] [blame] | 763 | if (placement.offset_reference == DisplayPlacement::BOTTOM_RIGHT) |
| 764 | offset = parent_bounds.width() - offset - target_bounds.width(); |
| 765 | |
kylechar | 731f85f9 | 2016-12-01 20:50:46 | [diff] [blame] | 766 | offset = std::min(offset, parent_bounds.width() - minimum_offset_overlap); |
| 767 | offset = std::max(offset, -target_bounds.width() + minimum_offset_overlap); |
robliao | 4567a867 | 2016-04-12 16:45:39 | [diff] [blame] | 768 | } else { |
robliao | ec805556 | 2016-05-11 22:55:40 | [diff] [blame] | 769 | if (placement.offset_reference == DisplayPlacement::BOTTOM_RIGHT) |
| 770 | offset = parent_bounds.height() - offset - target_bounds.height(); |
| 771 | |
kylechar | 731f85f9 | 2016-12-01 20:50:46 | [diff] [blame] | 772 | offset = std::min(offset, parent_bounds.height() - minimum_offset_overlap); |
| 773 | offset = std::max(offset, -target_bounds.height() + minimum_offset_overlap); |
robliao | 4567a867 | 2016-04-12 16:45:39 | [diff] [blame] | 774 | } |
| 775 | switch (position) { |
| 776 | case DisplayPlacement::TOP: |
| 777 | new_target_origin.Offset(offset, -target_bounds.height()); |
| 778 | break; |
| 779 | case DisplayPlacement::RIGHT: |
| 780 | new_target_origin.Offset(parent_bounds.width(), offset); |
| 781 | break; |
| 782 | case DisplayPlacement::BOTTOM: |
| 783 | new_target_origin.Offset(offset, parent_bounds.height()); |
| 784 | break; |
| 785 | case DisplayPlacement::LEFT: |
| 786 | new_target_origin.Offset(-target_bounds.width(), offset); |
| 787 | break; |
| 788 | } |
| 789 | |
| 790 | gfx::Insets insets = target_display->GetWorkAreaInsets(); |
| 791 | target_display->set_bounds( |
| 792 | gfx::Rect(new_target_origin, target_bounds.size())); |
| 793 | target_display->UpdateWorkAreaFromInsets(insets); |
| 794 | |
| 795 | return old_bounds != target_display->bounds(); |
| 796 | } |
| 797 | |
robliao | c0dfd6b | 2016-04-07 21:33:56 | [diff] [blame] | 798 | } // namespace display |