Skip to content

Commit 003d9d7

Browse files
committed
AppKitBackend: Fix Path construction (flipped coord system)
Also fixed path re-rendering. Before these changes we only set needsDisplay for the region within the bounds of the path, but we have to set it for the whole view cause otherwise any out-of-bounds parts of that path don't get re-rendered at all.
1 parent 07fbdbb commit 003d9d7

File tree

6 files changed

+64
-18
lines changed

6 files changed

+64
-18
lines changed

Sources/AppKitBackend/AppKitBackend.swift

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1303,18 +1303,29 @@ public final class AppKitBackend: AppBackend {
13031303
public func updatePath(
13041304
_ path: Path,
13051305
_ source: SwiftCrossUI.Path,
1306+
bounds: SwiftCrossUI.Path.Rect,
13061307
pointsChanged: Bool,
13071308
environment: EnvironmentValues
13081309
) {
13091310
applyStrokeStyle(source.strokeStyle, to: path)
13101311

13111312
if pointsChanged {
13121313
path.removeAllPoints()
1313-
applyActions(source.actions, to: path)
1314+
applyActions(
1315+
source.actions,
1316+
to: path,
1317+
bounds: bounds,
1318+
applyCoordinateSystemCorrection: true
1319+
)
13141320
}
13151321
}
13161322

1317-
func applyActions(_ actions: [SwiftCrossUI.Path.Action], to path: NSBezierPath) {
1323+
func applyActions(
1324+
_ actions: [SwiftCrossUI.Path.Action],
1325+
to path: NSBezierPath,
1326+
bounds: SwiftCrossUI.Path.Rect,
1327+
applyCoordinateSystemCorrection: Bool
1328+
) {
13181329
for action in actions {
13191330
switch action {
13201331
case .moveTo(let point):
@@ -1392,25 +1403,43 @@ public final class AppKitBackend: AppBackend {
13921403
radius: CGFloat(radius),
13931404
startAngle: CGFloat(startAngle * 180.0 / .pi),
13941405
endAngle: CGFloat(endAngle * 180.0 / .pi),
1395-
clockwise: clockwise
1406+
// Due to being in a flipped coordinate system (before the
1407+
// correction gets applied), we have to reverse all arcs.
1408+
clockwise: !clockwise
13961409
)
13971410
case .transform(let transform):
1398-
path.transform(
1399-
using: Foundation.AffineTransform(
1400-
m11: CGFloat(transform.linearTransform.x),
1401-
m12: CGFloat(transform.linearTransform.z),
1402-
m21: CGFloat(transform.linearTransform.y),
1403-
m22: CGFloat(transform.linearTransform.w),
1404-
tX: CGFloat(transform.translation.x),
1405-
tY: CGFloat(transform.translation.y)
1406-
)
1411+
let affineTransform = Foundation.AffineTransform(
1412+
m11: CGFloat(transform.linearTransform.x),
1413+
m12: CGFloat(transform.linearTransform.z),
1414+
m21: CGFloat(transform.linearTransform.y),
1415+
m22: CGFloat(transform.linearTransform.w),
1416+
tX: CGFloat(transform.translation.x),
1417+
tY: CGFloat(transform.translation.y)
14071418
)
1419+
path.transform(using: affineTransform)
14081420
case .subpath(let subpathActions):
14091421
let subpath = NSBezierPath()
1410-
applyActions(subpathActions, to: subpath)
1422+
// We don't apply the coordinate system correction to the subpath,
1423+
// we only want to apply it to the whole path once we're done.
1424+
applyActions(
1425+
subpathActions,
1426+
to: subpath,
1427+
bounds: bounds,
1428+
applyCoordinateSystemCorrection: false
1429+
)
14111430
path.append(subpath)
14121431
}
14131432
}
1433+
1434+
if applyCoordinateSystemCorrection {
1435+
// AppKit's coordinate system has a flipped Y axis so we have to correct for that
1436+
// once we've constructed the whole path.
1437+
var coordinateSystemCorrection = Foundation.AffineTransform(scaleByX: 1, byY: -1)
1438+
coordinateSystemCorrection.append(
1439+
Foundation.AffineTransform(translationByX: 0, byY: bounds.maxY + bounds.y)
1440+
)
1441+
path.transform(using: coordinateSystemCorrection)
1442+
}
14141443
}
14151444

14161445
public func renderPath(
@@ -1429,7 +1458,7 @@ public final class AppKitBackend: AppBackend {
14291458
widget.strokeColor = strokeColor.nsColor
14301459
widget.fillColor = fillColor.nsColor
14311460

1432-
widget.setNeedsDisplay(widget.bounds)
1461+
widget.needsDisplay = true
14331462
}
14341463
}
14351464

Sources/SwiftCrossUI/Backend/AppBackend.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,11 +587,16 @@ public protocol AppBackend {
587587
/// - Parameters:
588588
/// - path: The path to be updated.
589589
/// - source: The source to copy the path from.
590+
/// - bounds: The bounds that the path is getting rendered in. This gets
591+
/// passed to backends because AppKit uses a different coordinate system
592+
/// (with a flipped y axis) and therefore needs to perform coordinate
593+
/// conversions.
590594
/// - pointsChanged: If `false`, the ``Path/actions`` of the source have not changed.
591595
/// - environment: The environment of the path.
592596
func updatePath(
593597
_ path: Path,
594598
_ source: SwiftCrossUI.Path,
599+
bounds: SwiftCrossUI.Path.Rect,
595600
pointsChanged: Bool,
596601
environment: EnvironmentValues
597602
)

Sources/SwiftCrossUI/Views/Shapes/Shape.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,13 @@ extension Shape {
9090
let storage = children as! ShapeStorage
9191
let size = size(fitting: proposedSize)
9292

93-
let path = path(
94-
in: Path.Rect(x: 0.0, y: 0.0, width: Double(size.size.x), height: Double(size.size.y))
93+
let bounds = Path.Rect(
94+
x: 0.0,
95+
y: 0.0,
96+
width: Double(size.size.x),
97+
height: Double(size.size.y)
9598
)
99+
let path = path(in: bounds)
96100

97101
let pointsChanged = storage.oldPath?.actions != path.actions
98102
storage.oldPath = path
@@ -101,6 +105,7 @@ extension Shape {
101105
backend.updatePath(
102106
backendPath,
103107
path,
108+
bounds: bounds,
104109
pointsChanged: pointsChanged,
105110
environment: environment
106111
)

Sources/SwiftCrossUI/Views/Shapes/StyledShape.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,13 @@ extension StyledShape {
6161
let storage = children as! ShapeStorage
6262
let size = size(fitting: proposedSize)
6363

64-
let path = path(
65-
in: Path.Rect(x: 0.0, y: 0.0, width: Double(size.size.x), height: Double(size.size.y))
64+
let bounds = Path.Rect(
65+
x: 0.0,
66+
y: 0.0,
67+
width: Double(size.size.x),
68+
height: Double(size.size.y)
6669
)
70+
let path = path(in: bounds)
6771

6872
let pointsChanged = storage.oldPath?.actions != path.actions
6973
storage.oldPath = path
@@ -72,6 +76,7 @@ extension StyledShape {
7276
backend.updatePath(
7377
backendPath,
7478
path,
79+
bounds: bounds,
7580
pointsChanged: pointsChanged,
7681
environment: environment
7782
)

Sources/UIKitBackend/UIKitBackend+Path.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ extension UIKitBackend {
4949
public func updatePath(
5050
_ path: UIBezierPath,
5151
_ source: SwiftCrossUI.Path,
52+
bounds: SwiftCrossUI.Path.Rect,
5253
pointsChanged: Bool,
5354
environment: EnvironmentValues
5455
) {

Sources/WinUIBackend/WinUIBackend.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,6 +1307,7 @@ public final class WinUIBackend: AppBackend {
13071307
public func updatePath(
13081308
_ path: Path,
13091309
_ source: SwiftCrossUI.Path,
1310+
bounds: SwiftCrossUI.Path.Rect,
13101311
pointsChanged: Bool,
13111312
environment: EnvironmentValues
13121313
) {

0 commit comments

Comments
 (0)