• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.connecteddevice.display
18 
19 import android.graphics.RectF
20 import kotlin.math.hypot
21 
22 // Unfortunately, in the world of IEEE 32-bit floats, A + X - X is not always == A
23 // For example: A = 1075.4271f
24 //              C = 1249.2203f
25 // For example: - A - 173.79326f = - C
26 // However:     - C + A = - 173.79321f
27 // So we need to keep track of how the movingDisplay block is attaching to otherDisplays throughout
28 // the calculations below. We cannot use the rect.left with its width as a proxy for rect.right. We
29 // have to save the "inner" or attached side and use the width or height to calculate the "external"
30 // side.
31 
32 /** A potential X position for the display to clamp at. */
33 private class XCoor(
34     val left : Float, val right : Float,
35 
36     /**
37      * If present, the position of the display being attached to. If absent, indicates the X
38      * position is derived from the exact drag position.
39      */
40     val attaching : RectF?,
41 )
42 
43 /** A potential Y position for the display to clamp at. */
44 private class YCoor(
45     val top : Float, val bottom : Float,
46 
47     /**
48      * If present, the position of the display being attached to. If absent, indicates the Y
49      * position is derived from the exact drag position.
50      */
51     val attaching : RectF?,
52 )
53 
54 /**
55  * Finds the optimal clamp position assuming the user has dragged the block to `movingDisplay`.
56  *
57  * @param otherDisplays positions of the stationary displays (every one not being dragged)
58  * @param movingDisplay the position the user is current holding the block during a drag
59  *
60  * @return the clamp position as a RectF, whose dimensions will match that of `movingDisplay`
61  */
62 fun clampPosition(otherDisplays : Iterable<RectF>, movingDisplay : RectF) : RectF {
63     val xCoors = otherDisplays.flatMap {
64         listOf(
65             // Attaching to left edge of `it`
66             XCoor(it.left - movingDisplay.width(), it.left, it),
67             // Attaching to right edge of `it`
68             XCoor(it.right, it.right + movingDisplay.width(), it),
69         )
70     }.plusElement(XCoor(movingDisplay.left, movingDisplay.right, null))
71 
72     val yCoors = otherDisplays.flatMap {
73         listOf(
74             // Attaching to the top edge of `it`
75             YCoor(it.top - movingDisplay.height(), it.top, it),
76             // Attaching to the bottom edge of `it`
77             YCoor(it.bottom, it.bottom + movingDisplay.height(), it),
78         )
79     }.plusElement(YCoor(movingDisplay.top, movingDisplay.bottom, null))
80 
81     class Cand(val x : XCoor, val y : YCoor)
82 
83     val candidateGrid = xCoors.flatMap { x -> yCoors.map { y -> Cand(x, y) }}
84     val hasAttachInRange = candidateGrid.filter {
85         if (it.x.attaching != null) {
86             // Attaching to a vertical (left or right) edge. The y range of dragging and
87             // stationary blocks must overlap.
88             it.y.top <= it.x.attaching.bottom && it.y.bottom >= it.x.attaching.top
89         } else if (it.y.attaching != null) {
90             // Attaching to a horizontal (top or bottom) edge. The x range of dragging and
91             // stationary blocks must overlap.
92             it.x.left <= it.y.attaching.right && it.x.right >= it.y.attaching.left
93         } else {
94             // Not attaching to another display's edge at all, so not a valid clamp position.
95             false
96         }
97     }
98     // Clamp positions closest to the user's drag position are best. Sort by increasing distance
99     // from it, so the best will be first.
100     val prioritized = hasAttachInRange.sortedBy {
101         hypot(it.x.left - movingDisplay.left, it.y.top - movingDisplay.top)
102     }
103     val notIntersectingAny = prioritized.asSequence()
104         .map { RectF(it.x.left, it.y.top, it.x.right, it.y.bottom) }
105         .filter { p -> otherDisplays.all { !RectF.intersects(p, it) } }
106 
107     // Note we return a copy of `movingDisplay` if there is no valid clamp position, which will only
108     // happen if `otherDisplays` is empty or has no valid rectangles. It may not be wise to rely on
109     // this behavior.
110     return notIntersectingAny.firstOrNull() ?: RectF(movingDisplay)
111 }
112