@@ -14,6 +14,7 @@ public final class AppKitBackend: AppBackend {
1414 public typealias Widget = NSView
1515 public typealias Menu = NSMenu
1616 public typealias Alert = NSAlert
17+ public typealias Path = NSBezierPath
1718
1819 public let defaultTableRowContentHeight = 20
1920 public let defaultTableCellVerticalPadding = 4
@@ -1143,6 +1144,171 @@ public final class AppKitBackend: AppBackend {
11431144 tapGestureTarget. longPressHandler = action
11441145 }
11451146 }
1147+
1148+ final class NSBezierPathView : NSView {
1149+ var path : NSBezierPath !
1150+ var fillColor : NSColor = . clear
1151+ var strokeColor : NSColor = . clear
1152+
1153+ override func draw( _ dirtyRect: NSRect ) {
1154+ fillColor. set ( )
1155+ path. fill ( )
1156+ strokeColor. set ( )
1157+ path. stroke ( )
1158+ }
1159+ }
1160+
1161+ public func createPathWidget( ) -> NSView {
1162+ NSBezierPathView ( )
1163+ }
1164+
1165+ public func createPath( ) -> Path {
1166+ NSBezierPath ( )
1167+ }
1168+
1169+ func applyStrokeStyle( _ strokeStyle: StrokeStyle , to path: NSBezierPath ) {
1170+ path. lineWidth = CGFloat ( strokeStyle. width)
1171+
1172+ path. lineCapStyle =
1173+ switch strokeStyle. cap {
1174+ case . butt:
1175+ . butt
1176+ case . round:
1177+ . round
1178+ case . square:
1179+ . square
1180+ }
1181+
1182+ switch strokeStyle. join {
1183+ case . miter( let limit) :
1184+ path. lineJoinStyle = . miter
1185+ path. miterLimit = CGFloat ( limit)
1186+ case . round:
1187+ path. lineJoinStyle = . round
1188+ case . bevel:
1189+ path. lineJoinStyle = . bevel
1190+ }
1191+ }
1192+
1193+ public func updatePath( _ path: Path , _ source: SwiftCrossUI . Path , pointsChanged: Bool ) {
1194+ applyStrokeStyle ( source. strokeStyle, to: path)
1195+
1196+ if pointsChanged {
1197+ path. removeAllPoints ( )
1198+
1199+ for action in source. actions {
1200+ switch action {
1201+ case . moveTo( let point) :
1202+ path. move ( to: NSPoint ( x: point. x, y: point. y) )
1203+ case . lineTo( let point) :
1204+ if path. isEmpty {
1205+ path. move ( to: . zero)
1206+ }
1207+ path. line ( to: NSPoint ( x: point. x, y: point. y) )
1208+ case . quadCurve( let control, let end) :
1209+ if path. isEmpty {
1210+ path. move ( to: . zero)
1211+ }
1212+
1213+ if #available( macOS 14 , * ) {
1214+ // Use the native quadratic curve function
1215+ path. curve (
1216+ to: NSPoint ( x: end. x, y: end. y) ,
1217+ controlPoint: NSPoint ( x: control. x, y: control. y)
1218+ )
1219+ } else {
1220+ let start = path. currentPoint
1221+ // Build a cubic curve that follows the same path as the quadratic
1222+ path. curve (
1223+ to: NSPoint ( x: end. x, y: end. y) ,
1224+ controlPoint1: NSPoint (
1225+ x: ( start. x + 2.0 * control. x) / 3.0 ,
1226+ y: ( start. y + 2.0 * control. y) / 3.0
1227+ ) ,
1228+ controlPoint2: NSPoint (
1229+ x: ( 2.0 * control. x + end. x) / 3.0 ,
1230+ y: ( 2.0 * control. y + end. y) / 3.0
1231+ )
1232+ )
1233+ }
1234+ case . cubicCurve( let control1, let control2, let end) :
1235+ if path. isEmpty {
1236+ path. move ( to: . zero)
1237+ }
1238+
1239+ path. curve (
1240+ to: NSPoint ( x: end. x, y: end. y) ,
1241+ controlPoint1: NSPoint ( x: control1. x, y: control1. y) ,
1242+ controlPoint2: NSPoint ( x: control2. x, y: control2. y)
1243+ )
1244+ case . rectangle( let rect) :
1245+ path. appendRect (
1246+ NSRect (
1247+ origin: NSPoint ( x: rect. x, y: rect. y) ,
1248+ size: NSSize (
1249+ width: CGFloat ( rect. width) ,
1250+ height: CGFloat ( rect. height)
1251+ )
1252+ )
1253+ )
1254+ case . circle( let center, let radius) :
1255+ path. appendOval (
1256+ in: NSRect (
1257+ origin: NSPoint ( x: center. x - radius, y: center. y - radius) ,
1258+ size: NSSize (
1259+ width: CGFloat ( radius) * 2.0 ,
1260+ height: CGFloat ( radius) * 2.0
1261+ )
1262+ )
1263+ )
1264+ case . arc(
1265+ let center,
1266+ let radius,
1267+ let startAngle,
1268+ let endAngle,
1269+ let clockwise
1270+ ) :
1271+ path. appendArc (
1272+ withCenter: NSPoint ( x: center. x, y: center. y) ,
1273+ radius: CGFloat ( radius) ,
1274+ startAngle: CGFloat ( startAngle) ,
1275+ endAngle: CGFloat ( endAngle) ,
1276+ clockwise: clockwise
1277+ )
1278+ case . transform( let transform) :
1279+ path. transform (
1280+ using: Foundation . AffineTransform (
1281+ m11: CGFloat ( transform. linearTransform. x) ,
1282+ m12: CGFloat ( transform. linearTransform. z) ,
1283+ m21: CGFloat ( transform. linearTransform. y) ,
1284+ m22: CGFloat ( transform. linearTransform. w) ,
1285+ tX: CGFloat ( transform. translation. x) ,
1286+ tY: CGFloat ( transform. translation. y)
1287+ )
1288+ )
1289+ }
1290+ }
1291+ }
1292+ }
1293+
1294+ public func renderPath(
1295+ _ path: Path ,
1296+ container: Widget ,
1297+ strokeColor: Color ,
1298+ fillColor: Color ,
1299+ overrideStrokeStyle: StrokeStyle ?
1300+ ) {
1301+ if let overrideStrokeStyle {
1302+ applyStrokeStyle ( overrideStrokeStyle, to: path)
1303+ }
1304+
1305+ let widget = container as! NSBezierPathView
1306+ widget. path = path
1307+ widget. strokeColor = strokeColor. nsColor
1308+ widget. fillColor = fillColor. nsColor
1309+
1310+ widget. setNeedsDisplay ( widget. bounds)
1311+ }
11461312}
11471313
11481314final class NSCustomTapGestureTarget : NSView {
0 commit comments