blob: b797ccb9382c56803964b104501428c0c4c5920d [file] [log] [blame]
Avi Drissman3e1a26c2022-09-15 20:26:031// Copyright 2016 The Chromium Authors
[email protected]93c7e5622013-06-28 15:12:062// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
kylechar731f85f92016-12-01 20:50:465#include "ui/display/display_layout.h"
[email protected]93c7e5622013-06-28 15:12:066
Peter Kastinge10dd2e52025-01-28 00:26:127#include <algorithm>
afakhry6c80a8752017-02-01 01:38:048#include <map>
robliao4567a8672016-04-12 16:45:399#include <set>
oshimaf571c4a2016-02-24 18:51:0510#include <sstream>
Helmut Januschka36619c12024-04-24 14:33:1911#include <string_view>
kylechar6e8ea592017-03-30 15:27:5712#include <unordered_map>
Andrew Rayskiy0b0a1482024-02-27 16:07:5613#include <vector>
oshima95d499b2016-02-10 03:49:5614
David Dorwin3f503b82022-04-20 04:07:0315#include "base/check.h"
16#include "base/check_op.h"
Mitsuru Oshimad6890ba2022-04-14 06:58:3317#include "base/containers/contains.h"
[email protected]93c7e5622013-06-28 15:12:0618#include "base/logging.h"
[email protected]93c7e5622013-06-28 15:12:0619#include "base/strings/string_number_conversions.h"
[email protected]93c7e5622013-06-28 15:12:0620#include "base/values.h"
Jeroen Dhollander7c83e092023-11-03 17:59:3221#include "components/device_event_log/device_event_log.h"
oshimaef5fb892016-04-25 20:44:0722#include "ui/display/display.h"
Mitsuru Oshimadd6758c2022-04-19 06:06:0923#include "ui/display/util/display_util.h"
robliao4567a8672016-04-12 16:45:3924#include "ui/gfx/geometry/insets.h"
Steven Bennetts9c10bc42018-04-02 17:02:0425#include "ui/gfx/geometry/point.h"
26#include "ui/gfx/geometry/rect.h"
[email protected]93c7e5622013-06-28 15:12:0627
robliaoc0dfd6b2016-04-07 21:33:5628namespace display {
kylechar731f85f92016-12-01 20:50:4629namespace {
[email protected]93c7e5622013-06-28 15:12:0630
robliao8c912b532016-03-21 21:34:1531// DisplayPlacement Positions
32const char kTop[] = "top";
33const char kRight[] = "right";
34const char kBottom[] = "bottom";
35const char kLeft[] = "left";
36const char kUnknown[] = "unknown";
37
[email protected]93c7e5622013-06-28 15:12:0638// The maximum value for 'offset' in DisplayLayout in case of outliers. Need
39// to change this value in case to support even larger displays.
40const int kMaxValidOffset = 10000;
41
kylechar6e8ea592017-03-30 15:27:5742bool ComparePlacements(const DisplayPlacement& d1, const DisplayPlacement& d2) {
afakhryd3d4ea42017-04-14 02:01:4343 return CompareDisplayIds(d1.display_id, d2.display_id);
kylechar6e8ea592017-03-30 15:27:5744}
45
afakhry6c80a8752017-02-01 01:38:0446// Extracts the displays IDs list from the displays list.
47DisplayIdList 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 Wolfers0783e122023-01-30 15:55:4155// Returns nullptr if display with |id| is not found.
msw5e048a72016-09-07 18:55:3056Display* FindDisplayById(Displays* display_list, int64_t id) {
Peter Kastinge10dd2e52025-01-28 00:26:1257 auto iter = std::ranges::find(*display_list, id, &Display::id);
afakhry6c80a8752017-02-01 01:38:0458 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).
63int 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;
afakhrya0bad9612017-02-01 22:45:1872 auto iter = display_to_parent_ids_map.find(current_id);
73 if (iter == display_to_parent_ids_map.end())
Andrew Wolfers0783e122023-01-30 15:55:4174 return kMaxDepth; // Let detached displays go to the end.
afakhrya0bad9612017-02-01 22:45:1875
76 current_id = iter->second;
afakhry6c80a8752017-02-01 01:38:0477 }
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.
84bool 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
afakhrya0bad9612017-02-01 22:45:18104// the |placement_list|, and make sure the placement list is sorted by display
105// IDs.
106void UpdatePlacementList(Displays* display_list,
107 std::vector<DisplayPlacement>* placement_list) {
kylechar6e8ea592017-03-30 15:27:57108 std::sort(placement_list->begin(), placement_list->end(), ComparePlacements);
afakhrya0bad9612017-02-01 22:45:18109
afakhry6c80a8752017-02-01 01:38:04110 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
afakhrya0bad9612017-02-01 22:45:18132// 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.
afakhry6c80a8752017-02-01 01:38:04135void 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.
afakhrya0bad9612017-02-01 22:45:18146 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 Kastinge10dd2e52025-01-28 00:26:12160 auto target_display_placement_itr = std::ranges::find(
Peter Kasting862ac0f2022-09-19 15:28:14161 *placement_list, target_display->id(), &DisplayPlacement::display_id);
Daniel Cheng4d54f0a2025-05-26 22:59:12162 CHECK(target_display_placement_itr != placement_list->end());
afakhrya0bad9612017-02-01 22:45:18163 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();
afakhry6c80a8752017-02-01 01:38:04174 }
175
afakhrya0bad9612017-02-01 22:45:18176 DCHECK(target_display_placement);
afakhry6c80a8752017-02-01 01:38:04177
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.
afakhrya0bad9612017-02-01 22:45:18181 target_display_placement->parent_display_id =
afakhry6c80a8752017-02-01 01:38:04182 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) {
afakhrya0bad9612017-02-01 22:45:18188 target_display_placement->position =
afakhry6c80a8752017-02-01 01:38:04189 last_offset_x > 0 ? DisplayPlacement::RIGHT : DisplayPlacement::LEFT;
190 } else {
afakhrya0bad9612017-02-01 22:45:18191 target_display_placement->position =
afakhry6c80a8752017-02-01 01:38:04192 last_offset_y > 0 ? DisplayPlacement::BOTTOM : DisplayPlacement::TOP;
193 }
194}
195
196// Offsets |display| by the provided |x| and |y| values.
197void 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.
209int 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}
219int 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.
231void 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 Wolfers0783e122023-01-30 15:55:41248 // order of their descendence spanning out from the primary display.
afakhry6c80a8752017-02-01 01:38:04249 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
afakhrya0bad9612017-02-01 22:45:18259 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();
afakhry6c80a8752017-02-01 01:38:04266 });
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
afakhrya0bad9612017-02-01 22:45:18270 for (size_t i = 1; i < sorted_displays.size(); ++i) {
afakhry6c80a8752017-02-01 01:38:04271 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;
afakhrya0bad9612017-02-01 22:45:18275 for (size_t j = 0; j < i; ++j) {
afakhry6c80a8752017-02-01 01:38:04276 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
afakhrya0bad9612017-02-01 22:45:18323 // New placements might have been added and offsets might have changed and we
324 // must update them.
325 UpdatePlacementList(display_list, placement_list);
robliao4567a8672016-04-12 16:45:39326}
327
Steven Bennetts9c10bc42018-04-02 17:02:04328// 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.
332bool 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]93c7e5622013-06-28 15:12:06345} // namespace
346
347////////////////////////////////////////////////////////////////////////////////
oshima95d499b2016-02-10 03:49:56348// DisplayPlacement
oshimaf571c4a2016-02-24 18:51:05349
350DisplayPlacement::DisplayPlacement()
kylechar7bfba9892016-11-21 20:44:03351 : DisplayPlacement(kInvalidDisplayId,
352 kInvalidDisplayId,
robliaoec8055562016-05-11 22:55:40353 DisplayPlacement::RIGHT,
354 0,
355 DisplayPlacement::TOP_LEFT) {}
oshimaf571c4a2016-02-24 18:51:05356
robliaoec8055562016-05-11 22:55:40357DisplayPlacement::DisplayPlacement(Position position, int offset)
kylechar7bfba9892016-11-21 20:44:03358 : DisplayPlacement(kInvalidDisplayId,
359 kInvalidDisplayId,
robliaoec8055562016-05-11 22:55:40360 position,
361 offset,
362 DisplayPlacement::TOP_LEFT) {}
363
364DisplayPlacement::DisplayPlacement(Position position,
365 int offset,
366 OffsetReference offset_reference)
kylechar7bfba9892016-11-21 20:44:03367 : DisplayPlacement(kInvalidDisplayId,
368 kInvalidDisplayId,
robliaoec8055562016-05-11 22:55:40369 position,
370 offset,
371 offset_reference) {}
372
373DisplayPlacement::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]93c7e5622013-06-28 15:12:06383 DCHECK_LE(TOP, position);
384 DCHECK_GE(LEFT, position);
[email protected]93c7e5622013-06-28 15:12:06385 // 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 Kastingb6e09f512021-07-08 05:33:53393DisplayPlacement::DisplayPlacement(const DisplayPlacement&) = default;
394
395DisplayPlacement& DisplayPlacement::operator=(const DisplayPlacement&) =
396 default;
robliaodf372032016-03-23 00:42:34397
oshima95d499b2016-02-10 03:49:56398DisplayPlacement& DisplayPlacement::Swap() {
[email protected]93c7e5622013-06-28 15:12:06399 switch (position) {
400 case TOP:
oshima95d499b2016-02-10 03:49:56401 position = BOTTOM;
[email protected]93c7e5622013-06-28 15:12:06402 break;
403 case BOTTOM:
oshima95d499b2016-02-10 03:49:56404 position = TOP;
[email protected]93c7e5622013-06-28 15:12:06405 break;
406 case RIGHT:
oshima95d499b2016-02-10 03:49:56407 position = LEFT;
[email protected]93c7e5622013-06-28 15:12:06408 break;
409 case LEFT:
oshima95d499b2016-02-10 03:49:56410 position = RIGHT;
[email protected]93c7e5622013-06-28 15:12:06411 break;
412 }
oshima95d499b2016-02-10 03:49:56413 offset = -offset;
oshima61af6e12016-02-12 01:00:52414 std::swap(display_id, parent_display_id);
oshima95d499b2016-02-10 03:49:56415 return *this;
[email protected]93c7e5622013-06-28 15:12:06416}
417
oshima95d499b2016-02-10 03:49:56418std::string DisplayPlacement::ToString() const {
oshimaf571c4a2016-02-24 18:51:05419 std::stringstream s;
kylechar7bfba9892016-11-21 20:44:03420 if (display_id != kInvalidDisplayId)
oshimaf571c4a2016-02-24 18:51:05421 s << "id=" << display_id << ", ";
kylechar7bfba9892016-11-21 20:44:03422 if (parent_display_id != kInvalidDisplayId)
oshimaf571c4a2016-02-24 18:51:05423 s << "parent=" << parent_display_id << ", ";
robliao8c912b532016-03-21 21:34:15424 s << PositionToString(position) << ", ";
oshimaf571c4a2016-02-24 18:51:05425 s << offset;
426 return s.str();
427}
428
429// static
robliao8c912b532016-03-21 21:34:15430std::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 Januschka36619c12024-04-24 14:33:19445bool DisplayPlacement::StringToPosition(std::string_view string,
robliao8c912b532016-03-21 21:34:15446 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;
oshima95d499b2016-02-10 03:49:56470}
471
472////////////////////////////////////////////////////////////////////////////////
473// DisplayLayout
474
oshima61af6e12016-02-12 01:00:52475DisplayLayout::DisplayLayout()
Weidong Guo15abd312017-12-21 17:35:01476 : default_unified(true), primary_id(kInvalidDisplayId) {}
oshima95d499b2016-02-10 03:49:56477
478DisplayLayout::~DisplayLayout() {}
479
msw5e048a72016-09-07 18:55:30480void DisplayLayout::ApplyToDisplayList(Displays* display_list,
robliao4567a8672016-04-12 16:45:39481 std::vector<int64_t>* updated_ids,
afakhry6c80a8752017-02-01 01:38:04482 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 Oshima9b4bfa82025-04-17 23:49:21489 LOG(ERROR) << "Invalid Display Layout";
afakhry6c80a8752017-02-01 01:38:04490 return;
491 }
492
robliao4567a8672016-04-12 16:45:39493 // Layout from primary, then dependent displays.
494 std::set<int64_t> parents;
afakhry6c80a8752017-02-01 01:38:04495 std::set<int64_t> updated_displays;
robliao4567a8672016-04-12 16:45:39496 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) {
kylechar731f85f92016-12-01 20:50:46502 if (ApplyDisplayPlacement(placement, display_list,
afakhry6c80a8752017-02-01 01:38:04503 minimum_offset_overlap)) {
504 updated_displays.insert(placement.display_id);
robliao4567a8672016-04-12 16:45:39505 }
506 parents.insert(placement.display_id);
507 }
508 }
509 }
afakhry6c80a8752017-02-01 01:38:04510
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 }
robliao4567a8672016-04-12 16:45:39520}
521
[email protected]93c7e5622013-06-28 15:12:06522// static
oshimaf571c4a2016-02-24 18:51:05523bool DisplayLayout::Validate(const DisplayIdList& list,
524 const DisplayLayout& layout) {
525 // The primary display should be in the list.
Peter Kasting862ac0f2022-09-19 15:28:14526 if (!base::Contains(list, layout.primary_id)) {
Jeroen Dhollander7c83e092023-11-03 17:59:32527 DISPLAY_LOG(ERROR) << "The primary id: " << layout.primary_id
528 << " is not in the id list.";
afakhry6c80a8752017-02-01 01:38:04529 return false;
530 }
oshimaf571c4a2016-02-24 18:51:05531
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;
afakhryd3d4ea42017-04-14 02:01:43538 // The placement list must be sorted by the first 8 bits of the display IDs.
Mitsuru Oshima9b4bfa82025-04-17 23:49:21539#if BUILDFLAG(IS_CHROMEOS)
afakhryd3d4ea42017-04-14 02:01:43540 int64_t prev_id = std::numeric_limits<int8_t>::min();
Mitsuru Oshima9b4bfa82025-04-17 23:49:21541#endif // BUILDFLAG(IS_CHROMEOS)
robliaodf372032016-03-23 00:42:34542 for (const auto& placement : layout.placement_list) {
Mitsuru Oshima9b4bfa82025-04-17 23:49:21543#if BUILDFLAG(IS_CHROMEOS)
544 // Placements are sorted by display_id on ChromeOS.
afakhryd3d4ea42017-04-14 02:01:43545 if (prev_id >= (placement.display_id & 0xFF)) {
Jeroen Dhollander7c83e092023-11-03 17:59:32546 DISPLAY_LOG(ERROR) << "PlacementList must be sorted by first 8 bits of"
547 << " display_id ";
oshimaf571c4a2016-02-24 18:51:05548 return false;
549 }
afakhryd3d4ea42017-04-14 02:01:43550 prev_id = (placement.display_id & 0xFF);
Mitsuru Oshima9b4bfa82025-04-17 23:49:21551#endif // BUILDFLAG(IS_CHROMEOS)
kylechar7bfba9892016-11-21 20:44:03552 if (placement.display_id == kInvalidDisplayId) {
Jeroen Dhollander7c83e092023-11-03 17:59:32553 DISPLAY_LOG(ERROR) << "display_id is not initialized";
oshimaf571c4a2016-02-24 18:51:05554 return false;
555 }
kylechar7bfba9892016-11-21 20:44:03556 if (placement.parent_display_id == kInvalidDisplayId) {
Jeroen Dhollander7c83e092023-11-03 17:59:32557 DISPLAY_LOG(ERROR) << "display_parent_id is not initialized";
oshimaf571c4a2016-02-24 18:51:05558 return false;
559 }
robliaodf372032016-03-23 00:42:34560 if (placement.display_id == placement.parent_display_id) {
Jeroen Dhollander7c83e092023-11-03 17:59:32561 DISPLAY_LOG(ERROR) << "display_id must not be same as parent_display_id";
oshimaf571c4a2016-02-24 18:51:05562 return false;
563 }
Peter Kasting862ac0f2022-09-19 15:28:14564 if (!base::Contains(list, placement.display_id)) {
Jeroen Dhollander7c83e092023-11-03 17:59:32565 DISPLAY_LOG(ERROR) << "display_id is not in the id list:"
566 << placement.ToString();
oshimaf571c4a2016-02-24 18:51:05567 return false;
568 }
569
Peter Kasting862ac0f2022-09-19 15:28:14570 if (!base::Contains(list, placement.parent_display_id)) {
Jeroen Dhollander7c83e092023-11-03 17:59:32571 DISPLAY_LOG(ERROR) << "parent_display_id is not in the id list:"
572 << placement.ToString();
oshimaf571c4a2016-02-24 18:51:05573 return false;
574 }
robliaodf372032016-03-23 00:42:34575 has_primary_as_parent |= layout.primary_id == placement.parent_display_id;
oshimaf571c4a2016-02-24 18:51:05576 }
577 if (!has_primary_as_parent)
Jeroen Dhollander7c83e092023-11-03 17:59:32578 DISPLAY_LOG(ERROR)
579 << "At least, one placement must have the primary as a parent.";
oshimaf571c4a2016-02-24 18:51:05580 return has_primary_as_parent;
581}
582
danakj25c52c32016-04-12 21:51:08583std::unique_ptr<DisplayLayout> DisplayLayout::Copy() const {
584 std::unique_ptr<DisplayLayout> copy(new DisplayLayout);
robliaodf372032016-03-23 00:42:34585 for (const auto& placement : placement_list)
586 copy->placement_list.push_back(placement);
oshimaf571c4a2016-02-24 18:51:05587 copy->default_unified = default_unified;
588 copy->primary_id = primary_id;
589 return copy;
590}
591
kylechar6e8ea592017-03-30 15:27:57592void 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
oshimaf571c4a2016-02-24 18:51:05613bool DisplayLayout::HasSamePlacementList(const DisplayLayout& layout) const {
kylechar1d6d5d22017-02-03 14:34:49614 return placement_list == layout.placement_list;
oshimaf571c4a2016-02-24 18:51:05615}
616
Mitsuru Oshimad6890ba2022-04-14 06:58:33617void DisplayLayout::RemoveDisplayPlacements(const DisplayIdList& list) {
Andrew Rayskiy0b0a1482024-02-27 16:07:56618 std::erase_if(placement_list, [&list](const DisplayPlacement& placement) {
K. M. Merajul Arefin2de2f7d2023-12-12 16:19:45619 return base::Contains(list, placement.display_id);
620 });
Mitsuru Oshimad6890ba2022-04-14 06:58:33621 for (DisplayPlacement& placement : placement_list) {
622 if (base::Contains(list, placement.parent_display_id))
623 placement.parent_display_id = primary_id;
624 }
625}
626
oshimaf571c4a2016-02-24 18:51:05627std::string DisplayLayout::ToString() const {
628 std::stringstream s;
629 s << "primary=" << primary_id;
oshimaf571c4a2016-02-24 18:51:05630 if (default_unified)
631 s << ", default_unified";
632 bool added = false;
robliaodf372032016-03-23 00:42:34633 for (const auto& placement : placement_list) {
oshimaf571c4a2016-02-24 18:51:05634 s << (added ? "),(" : " [(");
robliaodf372032016-03-23 00:42:34635 s << placement.ToString();
oshimaf571c4a2016-02-24 18:51:05636 added = true;
637 }
638 if (added)
639 s << ")]";
640 return s.str();
[email protected]93c7e5622013-06-28 15:12:06641}
642
robliaodf372032016-03-23 00:42:34643DisplayPlacement DisplayLayout::FindPlacementById(int64_t display_id) const {
Peter Kastinge10dd2e52025-01-28 00:26:12644 const auto iter = std::ranges::find(placement_list, display_id,
645 &DisplayPlacement::display_id);
robliaodf372032016-03-23 00:42:34646 return (iter == placement_list.end()) ? DisplayPlacement()
647 : DisplayPlacement(*iter);
stevenjb209a17752016-02-29 23:53:33648}
649
Steven Bennetts9c10bc42018-04-02 17:02:04650// 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 Wolfers0783e122023-01-30 15:55:41678// The rectangle shares an edge with the reference's bottom edge, but its
Steven Bennetts9c10bc42018-04-02 17:02:04679// center point is in the left area.
680
681// static
682DisplayPlacement 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
robliao4567a8672016-04-12 16:45:39741// static
742bool DisplayLayout::ApplyDisplayPlacement(const DisplayPlacement& placement,
msw5e048a72016-09-07 18:55:30743 Displays* display_list,
robliao4567a8672016-04-12 16:45:39744 int minimum_offset_overlap) {
kylechar79bed53c2016-09-01 13:02:31745 const Display& parent_display =
robliao4567a8672016-04-12 16:45:39746 *FindDisplayById(display_list, placement.parent_display_id);
747 DCHECK(parent_display.is_valid());
kylechar79bed53c2016-09-01 13:02:31748 Display* target_display = FindDisplayById(display_list, placement.display_id);
robliao4567a8672016-04-12 16:45:39749 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) {
robliaoec8055562016-05-11 22:55:40763 if (placement.offset_reference == DisplayPlacement::BOTTOM_RIGHT)
764 offset = parent_bounds.width() - offset - target_bounds.width();
765
kylechar731f85f92016-12-01 20:50:46766 offset = std::min(offset, parent_bounds.width() - minimum_offset_overlap);
767 offset = std::max(offset, -target_bounds.width() + minimum_offset_overlap);
robliao4567a8672016-04-12 16:45:39768 } else {
robliaoec8055562016-05-11 22:55:40769 if (placement.offset_reference == DisplayPlacement::BOTTOM_RIGHT)
770 offset = parent_bounds.height() - offset - target_bounds.height();
771
kylechar731f85f92016-12-01 20:50:46772 offset = std::min(offset, parent_bounds.height() - minimum_offset_overlap);
773 offset = std::max(offset, -target_bounds.height() + minimum_offset_overlap);
robliao4567a8672016-04-12 16:45:39774 }
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
robliaoc0dfd6b2016-04-07 21:33:56798} // namespace display