mirror of
https://github.com/divkit/divkit.git
synced 2026-06-06 20:07:59 +00:00
Tooltip position always fits within the screen bounds
commit_hash:21a0254d52ab73059956c247cff8a5f801dd6fdf
This commit is contained in:
@@ -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
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user