@@ -534,7 +534,30 @@ public struct Path: Equatable, LosslessStringConvertible, @unchecked Sendable {
534534 /// If `eoFill` is true, this method uses the even-odd rule to define which
535535 /// points are inside the path. Otherwise, it uses the non-zero rule.
536536 public func contains( _ p: CGPoint , eoFill: Bool = false ) -> Bool {
537- _openSwiftUIUnimplementedFailure ( )
537+ switch storage {
538+ case . empty:
539+ return false
540+ case let . rect( rect) :
541+ return rect. contains ( p)
542+ case let . ellipse( rect) :
543+ return ellipseContains ( in: rect, contains: p)
544+ case let . roundedRect( rect) :
545+ #if canImport(Darwin)
546+ return rect. contains ( p)
547+ #else
548+ // FIXME
549+ let path = retainRBPath ( )
550+ defer { path. release ( ) }
551+ return path. contains ( point: p, eoFill: false )
552+ #endif
553+ case . stroked, . trimmed:
554+ _openSwiftUIUnreachableCode ( )
555+ case let . path( box) :
556+ let path = box. retainRBPath ( )
557+ // TBA
558+ defer { path. release ( ) }
559+ return path. contains ( point: p, eoFill: eoFill)
560+ }
538561 }
539562
540563 package func contains(
@@ -552,7 +575,12 @@ public struct Path: Equatable, LosslessStringConvertible, @unchecked Sendable {
552575 eoFill: Bool = false ,
553576 origin: CGPoint = . zero
554577 ) -> BitVector64 {
555- _openSwiftUIUnimplementedFailure ( )
578+ // FIXME:
579+ // The RenderBox shim currently declares the batched mask result as Bool.
580+ // Use scalar containment to preserve every bit until that ABI is corrected.
581+ points. mapBool {
582+ contains ( CGPoint ( x: $0. x - origin. x, y: $0. y - origin. y) , eoFill: eoFill)
583+ }
556584 }
557585
558586 /// An element of a path.
@@ -587,7 +615,28 @@ public struct Path: Equatable, LosslessStringConvertible, @unchecked Sendable {
587615
588616 /// Calls `body` with each element in the path.
589617 public func forEach( _ body: ( Path . Element ) -> Void ) {
590- _openSwiftUIUnimplementedFailure ( )
618+ #if canImport(CoreGraphics)
619+ // FIXME: Use RB API instead of CG API
620+ cgPath. applyWithBlock { element in
621+ let element = element. pointee
622+ switch element. type {
623+ case . moveToPoint:
624+ body ( . move( to: element. points [ 0 ] ) )
625+ case . addLineToPoint:
626+ body ( . line( to: element. points [ 0 ] ) )
627+ case . addQuadCurveToPoint:
628+ body ( . quadCurve( to: element. points [ 1 ] , control: element. points [ 0 ] ) )
629+ case . addCurveToPoint:
630+ body ( . curve( to: element. points [ 2 ] , control1: element. points [ 0 ] , control2: element. points [ 1 ] ) )
631+ case . closeSubpath:
632+ body ( . closeSubpath)
633+ @unknown default :
634+ break
635+ }
636+ }
637+ #else
638+ _openSwiftUIPlatformUnimplementedFailure ( )
639+ #endif
591640 }
592641
593642 /// Returns a stroked copy of the path using `style` to define how the
@@ -814,7 +863,18 @@ extension Path {
814863 _ path: Path ,
815864 transform: CGAffineTransform = . identity
816865 ) {
817- _openSwiftUIUnimplementedFailure ( )
866+ // FIXME: Use RB instead of CG
867+ if case . empty = storage {
868+ self = path. applying ( transform)
869+ } else if !path. isEmpty {
870+ #if canImport(CoreGraphics)
871+ let result = cgPath. mutableCopy ( ) !
872+ result. addPath ( path. cgPath, transform: transform)
873+ self = Path ( result)
874+ #else
875+ _openSwiftUIPlatformUnimplementedFailure ( )
876+ #endif
877+ }
818878 }
819879
820880 public var currentPoint : CGPoint ? {
@@ -875,7 +935,20 @@ extension Path {
875935 }
876936
877937 package mutating func formTrivialUnion( _ path: Path ) {
878- _openSwiftUIUnimplementedWarning ( )
938+ // TBA
939+ guard !path. isEmpty else {
940+ return
941+ }
942+ if let lhs = roundedRect ( ) , let rhs = path. roundedRect ( ) {
943+ if lhs. contains ( rhs) {
944+ return
945+ }
946+ if rhs. contains ( lhs) {
947+ self = path
948+ return
949+ }
950+ }
951+ addPath ( path)
879952 }
880953
881954 public func applying( _ transform: CGAffineTransform ) -> Path {
@@ -891,8 +964,16 @@ extension Path {
891964 return Path ( ellipseIn: rect. applying ( transform) )
892965 case let . roundedRect( fixedRoundedRect) where transform. isRectilinear:
893966 return Path ( storage: . roundedRect( fixedRoundedRect. applying ( transform) ) )
967+ case . stroked, . trimmed:
968+ _openSwiftUIUnreachableCode ( )
894969 default :
895- _openSwiftUIUnimplementedFailure ( )
970+ // TBA
971+ #if canImport(CoreGraphics)
972+ var transform = transform
973+ return Path ( cgPath. copy ( using: & transform) !)
974+ #else
975+ _openSwiftUIPlatformUnimplementedFailure ( )
976+ #endif
896977 }
897978 }
898979
@@ -904,12 +985,109 @@ extension Path {
904985 }
905986
906987 func mapPoints( _ body: ( inout [ CGPoint ] ) -> ( ) ) -> Path {
907- // TODO
908- _openSwiftUIUnimplementedWarning ( )
909- return self
988+ // TBA
989+ switch storage {
990+ case . empty:
991+ return self
992+ case let . rect( rect) :
993+ var points = rect. cornerPoints
994+ body ( & points)
995+ if let rect = CGRect ( exactCornerPoints: points) {
996+ return Path ( rect)
997+ }
998+ #if canImport(CoreGraphics)
999+ let result = CGMutablePath ( )
1000+ result. move ( to: points [ 0 ] )
1001+ for point in points [ 1 ..< 4 ] {
1002+ result. addLine ( to: point)
1003+ }
1004+ result. closeSubpath ( )
1005+ return Path ( result)
1006+ #else
1007+ _openSwiftUIPlatformUnimplementedFailure ( )
1008+ #endif
1009+ case let . ellipse( rect) :
1010+ var points = rect. cornerPoints
1011+ body ( & points)
1012+ if let mapped = CGRect ( exactCornerPoints: points) ,
1013+ mapped. width == rect. width,
1014+ mapped. height == rect. height {
1015+ return Path ( ellipseIn: mapped)
1016+ }
1017+ case let . roundedRect( rect) :
1018+ var points = rect. rect. cornerPoints
1019+ body ( & points)
1020+ if let mapped = CGRect ( exactCornerPoints: points) ,
1021+ mapped. width == rect. rect. width,
1022+ mapped. height == rect. rect. height {
1023+ return Path ( storage: . roundedRect( FixedRoundedRect (
1024+ mapped,
1025+ cornerSize: rect. cornerSize,
1026+ style: rect. style
1027+ ) ) )
1028+ }
1029+ case . stroked, . trimmed:
1030+ _openSwiftUIUnreachableCode ( )
1031+ case . path:
1032+ break
1033+ }
1034+ #if canImport(CoreGraphics)
1035+ var points : [ CGPoint ] = [ ]
1036+ forEach { element in
1037+ switch element {
1038+ case let . move( point) , let . line( point) :
1039+ points. append ( point)
1040+ case let . quadCurve( point, control) :
1041+ points. append ( contentsOf: [ point, control] )
1042+ case let . curve( point, control1, control2) :
1043+ points. append ( contentsOf: [ point, control1, control2] )
1044+ case . closeSubpath:
1045+ break
1046+ }
1047+ }
1048+ body ( & points)
1049+ let result = CGMutablePath ( )
1050+ var index = 0
1051+ forEach { element in
1052+ switch element {
1053+ case . move:
1054+ result. move ( to: points [ index] )
1055+ index += 1
1056+ case . line:
1057+ result. addLine ( to: points [ index] )
1058+ index += 1
1059+ case . quadCurve:
1060+ result. addQuadCurve ( to: points [ index] , control: points [ index + 1 ] )
1061+ index += 2
1062+ case . curve:
1063+ result. addCurve ( to: points [ index] , control1: points [ index + 1 ] , control2: points [ index + 2 ] )
1064+ index += 3
1065+ case . closeSubpath:
1066+ result. closeSubpath ( )
1067+ }
1068+ }
1069+ return Path ( result)
1070+ #else
1071+ _openSwiftUIPlatformUnimplementedFailure ( )
1072+ #endif
9101073 }
9111074}
9121075
1076+ private func ellipseContains( in rect: CGRect , contains point: CGPoint ) -> Bool {
1077+ var rect = rect
1078+ var point = point
1079+ if rect. width != rect. height {
1080+ let scale = rect. height / rect. width
1081+ rect. origin. x *= scale
1082+ rect. size. width *= scale
1083+ point. x *= scale
1084+ }
1085+ let dx = point. x - ( rect. origin. x + rect. width / 2 )
1086+ let dy = point. y - ( rect. origin. y + rect. height / 2 )
1087+ let radius = rect. width / 2
1088+ return dx * dx + dy * dy < radius * radius
1089+ }
1090+
9131091// MARK: - RenderBox
9141092
9151093private let temporaryPathCallbacks : UnsafePointer < ORBPath . Callbacks > = {
0 commit comments