Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix pixel alignment math #312

Merged
merged 1 commit into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed
- Fixed an issue that could cause accessibility focus to shift unexpectedly
- Fixed a screen-pixel alignment issue

### Changed
- Rewrote accessibility code to avoid posting notifications, which causes poor Voice Over performance and odd focus bugs
Expand Down
4 changes: 2 additions & 2 deletions Sources/Internal/FrameProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ final class FrameProvider {
let origin: CGPoint
if distanceFromAdjacentDay < 0 {
let proposedX = adjacentDayFrame.minX - content.horizontalDayMargin - daySize.width
if proposedX > minX || proposedX.isEqual(to: minX, threshold: 1 / scale) {
if proposedX > minX || proposedX.isEqual(to: minX, screenScale: scale) {
origin = CGPoint(x: proposedX, y: adjacentDayFrame.minY)
} else {
origin = CGPoint(
Expand All @@ -239,7 +239,7 @@ final class FrameProvider {
}
} else {
let proposedX = adjacentDayFrame.maxX + content.horizontalDayMargin
if proposedX < maxX || proposedX.isEqual(to: maxX, threshold: 1 / scale) {
if proposedX < maxX || proposedX.isEqual(to: maxX, screenScale: scale) {
origin = CGPoint(x: proposedX, y: adjacentDayFrame.minY)
} else {
origin = CGPoint(x: minX, y: adjacentDayFrame.maxY + content.verticalDayMargin)
Expand Down
22 changes: 11 additions & 11 deletions Sources/Internal/ScreenPixelAlignment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ extension CGFloat {
(self * scale).rounded() / scale
}

/// Tests `self` for approximate equality using the threshold value. For example, 1.48 equals 1.52 if the threshold is 0.05.
/// `threshold` will be treated as a positive value by taking its absolute value.
func isEqual(to rhs: CGFloat, threshold: CGFloat) -> Bool {
abs(self - rhs) <= abs(threshold)
/// Tests `self` for approximate equality, first rounding the operands to be pixel-aligned for a screen with the given
/// `screenScale`. For example, 1.48 equals 1.52 if the `screenScale` is `2`.
func isEqual(to rhs: CGFloat, screenScale: CGFloat) -> Bool {
let lhs = alignedToPixel(forScreenWithScale: screenScale)
let rhs = rhs.alignedToPixel(forScreenWithScale: screenScale)
return lhs == rhs
Comment on lines +27 to +30
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

}
Expand All @@ -35,13 +37,11 @@ extension CGRect {
/// Rounds a `CGRect`'s `origin` and `size` values so that they're aligned on pixel boundaries for a screen with the provided
/// scale.
func alignedToPixels(forScreenWithScale scale: CGFloat) -> CGRect {
let alignedX = minX.alignedToPixel(forScreenWithScale: scale)
let alignedY = minY.alignedToPixel(forScreenWithScale: scale)
return CGRect(
x: alignedX,
y: alignedY,
width: maxX.alignedToPixel(forScreenWithScale: scale) - alignedX,
height: maxY.alignedToPixel(forScreenWithScale: scale) - alignedY)
CGRect(
x: minX.alignedToPixel(forScreenWithScale: scale),
y: minY.alignedToPixel(forScreenWithScale: scale),
width: width.alignedToPixel(forScreenWithScale: scale),
height: height.alignedToPixel(forScreenWithScale: scale))
}

}
Expand Down
6 changes: 3 additions & 3 deletions Tests/FrameProviderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -528,10 +528,10 @@ final class FrameProviderTests: XCTestCase {
height: 34.857142857142854)
.alignedToPixels(forScreenWithScale: 3)
let expectedFrameAfterMiddleDay = CGRect(
x: 187.33333333333334,
x: 187.66666666666666,
y: 374.3333333333333,
width: 34.857142857142854,
height: 34.857142857142854)
width: 35,
height: 35)
.alignedToPixels(forScreenWithScale: 3)
XCTAssert(frameBeforeMiddleDay == expectedFrameBeforeMiddleDay, "Incorrect frame for day.")
XCTAssert(frameAfterMiddleDay == expectedFrameAfterMiddleDay, "Incorrect frame for day.")
Expand Down
20 changes: 7 additions & 13 deletions Tests/ScreenPixelAlignmentTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,15 @@ final class ScreenPixelAlignmentTests: XCTestCase {

func test2xScaleRectAlignment() {
let rect = CGRect(x: 5.299999, y: -19.1994, width: 20.25, height: 0.76)
let expectedRect = CGRect(x: 5.5, y: -19, width: 20, height: 0.5)
let expectedRect = CGRect(x: 5.5, y: -19, width: 20.5, height: 1)
XCTAssert(
rect.alignedToPixels(forScreenWithScale: 2) == expectedRect,
"Incorrect screen pixel alignment")
}

func test3xScaleRectAlignment() {
let rect = CGRect(x: 71.13, y: 71.19, width: 20.25, height: 2)
let expectedRect = CGRect(x: 71, y: 71.33333333333333, width: 20.33333333333333, height: 2)
let expectedRect = CGRect(x: 71, y: 71.33333333333333, width: 20.333333333333332, height: 2)
XCTAssert(
rect.alignedToPixels(forScreenWithScale: 3) == expectedRect,
"Incorrect screen pixel alignment")
Expand All @@ -125,17 +125,11 @@ final class ScreenPixelAlignmentTests: XCTestCase {
// MARK: CGFloat Approximate Comparison Tests

func testApproximateEquality() {
XCTAssert(CGFloat(1.48).isEqual(to: 1.52, threshold: 0.05))
XCTAssert(!CGFloat(1.48).isEqual(to: 1.53, threshold: 0.05))

XCTAssert(CGFloat(1).isEqual(to: 10, threshold: 9))
XCTAssert(!CGFloat(1).isEqual(to: 11, threshold: 9))

XCTAssert(CGFloat(1).isEqual(to: 10, threshold: 9))
XCTAssert(!CGFloat(1).isEqual(to: 11, threshold: 9))

XCTAssert(CGFloat(1.333).isEqual(to: 1.666, threshold: 1 / 3))
XCTAssert(!CGFloat(1.332).isEqual(to: 1.666, threshold: 1 / 3))
XCTAssert(CGFloat(1.48).isEqual(to: 1.52, screenScale: 2))
XCTAssert(!CGFloat(1).isEqual(to: 10, screenScale: 9))
XCTAssert(!CGFloat(1).isEqual(to: 10, screenScale: 9))
XCTAssert(!CGFloat(1).isEqual(to: 9, screenScale: 9))
XCTAssert(!CGFloat(1.333).isEqual(to: 1.666, screenScale: 3))
}

}
Loading