Tooltip position always fits within the screen bounds

commit_hash:21a0254d52ab73059956c247cff8a5f801dd6fdf
This commit is contained in:
denlvovich
2024-11-08 16:30:00 +03:00
parent 6f33483856
commit 76d8a75a9d
3 changed files with 78 additions and 59 deletions
@@ -8,7 +8,7 @@ extension BlockTooltip {
constrainedBy bounds: CGRect
) -> CGRect {
let size = block.intrinsicSize
let result = CGRect(
var result = CGRect(
coordinate: targetRect.coordinate(of: position),
ofPosition: position.opposite,
size: size
@@ -16,33 +16,82 @@ extension BlockTooltip {
if result.intersection(bounds) == result {
return result
} else {
result.move(to: bounds)
return result
}
var alternativePosition = position
if result.maxY > bounds.maxY {
alternativePosition = alternativePosition.movingUp
}
if result.minY < bounds.minY {
alternativePosition = alternativePosition.movingDown
}
if result.minX < bounds.minX {
alternativePosition = alternativePosition.movingRight
}
if result.maxX > bounds.maxX {
alternativePosition = alternativePosition.movingLeft
}
let alternativeFrame = CGRect(
coordinate: targetRect.coordinate(of: alternativePosition),
ofPosition: alternativePosition.opposite,
size: size
).offset(by: offset)
return alternativeFrame.intersection(bounds) == alternativeFrame
? alternativeFrame
: result
}
}
extension CGRect {
fileprivate mutating func move(to bounds: CGRect) {
for direction in MoveDirection.allCases {
let (minPos, maxPos) = minAndMaxPos(direction)
let (minBound, maxBound) = bounds.minAndMaxPos(direction)
var newOrigin: CGFloat = origin(direction)
if self.size(direction) > bounds.size(direction) {
switch (minPos, maxPos) {
case let (min, _) where min > minBound:
newOrigin = minBound
case let (_, max) where max < maxBound:
newOrigin -= maxBound - max
default:
break
}
} else {
switch (minPos, maxPos) {
case let (min, _) where min < minBound:
newOrigin = minBound
case let (_, max) where max > maxBound:
newOrigin -= max - maxBound
default:
break
}
}
switch direction {
case .horizontal:
origin.x = newOrigin
case .vertical:
origin.y = newOrigin
}
}
}
private func origin(_ direction: MoveDirection) -> CGFloat {
switch direction {
case .horizontal:
origin.x
case .vertical:
origin.y
}
}
private func size(_ direction: MoveDirection) -> CGFloat {
switch direction {
case .horizontal:
width
case .vertical:
height
}
}
private func minAndMaxPos(
_ direction: MoveDirection
) -> (min: CGFloat, max: CGFloat) {
switch direction {
case .horizontal:
(minX, maxX)
case .vertical:
(minY, maxY)
}
}
fileprivate typealias MoveDirection = ScrollDirection
}
extension CGRect {
fileprivate func coordinate(of position: BlockTooltip.Position) -> CGPoint {
switch position {
@@ -73,38 +122,6 @@ extension BlockTooltip.Position {
case .center: .center
}
}
fileprivate var movingUp: Self {
switch self {
case .topLeft, .left, .bottomLeft: .topLeft
case .top, .center, .bottom: .top
case .topRight, .right, .bottomRight: .topRight
}
}
fileprivate var movingDown: Self {
switch self {
case .topLeft, .left, .bottomLeft: .bottomLeft
case .top, .center, .bottom: .bottom
case .topRight, .right, .bottomRight: .bottomRight
}
}
fileprivate var movingLeft: Self {
switch self {
case .topLeft, .top, .topRight: .topLeft
case .left, .center, .right: .left
case .bottomLeft, .bottom, .bottomRight: .bottomLeft
}
}
fileprivate var movingRight: Self {
switch self {
case .topLeft, .top, .topRight: .topRight
case .left, .center, .right: .right
case .bottomLeft, .bottom, .bottomRight: .bottomRight
}
}
}
extension CGRect {
@@ -1,7 +1,7 @@
import Foundation
@frozen
public enum ScrollDirection: Equatable {
public enum ScrollDirection: Equatable, CaseIterable {
case horizontal
case vertical
@@ -15,8 +15,10 @@ final class BlockTooltipTests: XCTestCase {
private var testCaseOffsets: [CGPoint] {
[
CGPoint(x: 0.0, y: 0.0), // fits
CGPoint(x: 200.0, y: 0.0), // doesn't fit horisontally
CGPoint(x: 0.0, y: 450.0), // doesn't fit vertically
CGPoint(x: tooltipSize.width, y: 0.0), // doesn't fit horisontally
CGPoint(x: -tooltipSize.width, y: 0.0), // doesn't fit horisontally
CGPoint(x: 0.0, y: tooltipSize.height), // doesn't fit vertically
CGPoint(x: 0.0, y: -tooltipSize.height), // doesn't fit vertically
]
}