GNUStepSwiftBridge & CairoGraphics
前回の GNUStepSwiftBridge を利用して、CairoGraphics を行いました。
実行結果
起動時
メニューアイテム(AItem)をクリック時
InfoPanel の表示
前回からの変更点
Resources ファイルの .build/debug ディレクトリへのコピー
前回はResouces を手動で コピーしていましたが、これをプログラム内で行うようにしました。FileManager を使用します。
CheckResource.swift
// The Swift Programming Language // https://docs.swift.org/swift-book // check Resources Files import Foundation func CheckResource() { let fileManager = FileManager.default let currentPath = fileManager.currentDirectoryPath let filePath:String = currentPath + "/Sources/Resources" let copyPath:String = ".build/debug/Resources" let exist = fileManager.fileExists(atPath: copyPath) if !exist { do { try fileManager.copyItem(atPath: filePath, toPath: copyPath) } catch { print("can not copy") } } }
AppKit ライブラリ
前回はAppKit ライブラリを application project と同じフォルダに置いていましたが、これをAppKit 単体のライブラリにしました。
方法は以下の通り。
1 mkdir AppKit 2 cd AppKit swift package init --type library 3 copy AppKitGNUStep , FoundationGNUStep, libobjc2 , ObjCSwiftInterop in Sources dir copy Headers in AppKit dir 4 edit Sources/AppKit/AppKit.swift copy Foundation.swift , SwiftForGNUStep.swift 5 edit Package.swift // swift-tools-version: 5.8 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "AppKit", products: [ // Products define the executables and libraries a package produces, making them visible to other packages. .library(name: "libobjc", targets: ["libobjc2"]), .library(name: "AppKitGNUStep", targets: ["AppKitGNUStep"]), .library(name: "FoundationGNUStep", targets: ["FoundationGNUStep"]), .library(name: "ObjCSwiftInterop", targets: ["ObjCSwiftInterop"]), .library( name: "AppKit", targets: ["AppKit"]), ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. // Targets can depend on other targets in this package and products from dependencies. .target(name: "ObjCSwiftInterop"), .target( name: "AppKit", dependencies: ["libobjc2", "AppKitGNUStep", "ObjCSwiftInterop", "FoundationGNUStep"]), .systemLibrary(name: "libobjc2"), .systemLibrary(name: "AppKitGNUStep"), .systemLibrary(name: "FoundationGNUStep"), .testTarget( name: "AppKitTests", dependencies: ["AppKit"]), ] ) 5 set git info in AppKit dir git init git add . git commit -m "Initial Commit" git tag 1.0.0
application (AppCG) ディレクトリ配置
AppKit (上記ライブラリ) AppCG Sources CCairo module.modulemap Resources MainMenu.gorm Info-gnustep.plist ( tiff files ) main.swift AppDelegate.swift Image.swift CGContext.swift CheckResource.swift Package.swift
MainMenu.gorm
Gorm を使って編集します。
1 InfoPanel の表示
メニューの InfoPanel を NSFirst に接続
target から orderFrontStandardInfoPanel: を選択
2 メニューアイテムの追加
パレットウィンドウのメニューから、 Item をドラッグ
CCairo
module.modulemap
module CCairo [system] { module CairoXlib { header "/usr/include/cairo/cairo-xlib.h" } module Cairo { header "/usr/include/cairo/cairo.h" } link "cairo" export * }
Package.swift
// swift-tools-version: 5.8 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "AppCG", dependencies: [ .package(url: "../AppKit", from: Version(1,0,0)) ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. // Targets can depend on other targets in this package and products from dependencies. .executableTarget( name: "AppCG", dependencies: ["AppKit", "CCairo"], path: "Sources" ), .systemLibrary( name: "CCairo"), ] )
main.swift
// The Swift Programming Language // https://docs.swift.org/swift-book import AppKitGNUStep import ObjCSwiftInterop import Foundation // check ResourcesFile CheckResource() // main let fileManager = FileManager.default let delegate = AppDelegate() let napClass = objc_getClass("NSApplication") var sharedApp = forSwift_objcSendMessage(&napClass!.pointee, sel_registerName("sharedApplication")) print("Just created NSApplication") let v = forSwift_objcSendMessage1ID(&sharedApp!.pointee, sel_getUid("setDelegate:"), delegate._nsobjptr!) print("made it!") NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv.pointee)
AppDelegate.swift
// The Swift Programming Language // https://docs.swift.org/swift-book import FoundationGNUStep import ObjCSwiftInterop import AppKit import CCairo.Cairo let width = 640 let height = 480 var drawFlag = 1 func draw(_ context: CGContext, _ width: Float, _ height: Float) -> Void { // background context.setColor(1, 1, 1, 1) context.addRect(CGRect(x:0, y:0, width: Double(width), height: Double(height))) context.fillPath() switch (drawFlag) { case 1: // Draw random rects (some stroked, some filled) for i in 0...19 { if (i % 2 == 0) { context.setColor( Double.random(in: 0...1), Double.random(in: 0...1), Double.random(in: 0...1), Double.random(in: 0...1)) context.addRect(CGRect( x:Double.random(in: 0...1)*Double(width), y:Double.random(in: 0...1)*Double(height), width:Double.random(in: 0...1)*Double(width), height:Double.random(in: 0...1)*Double(height))) context.fillPath() } else { context.lineWidth = Float.random(in: 0...10) + 2 context.setColor( Double.random(in: 0...1), Double.random(in: 0...1), Double.random(in: 0...1), Double.random(in: 0...1)) context.addRect(CGRect( x:Double.random(in: 0...1)*Double(width), y:Double.random(in: 0...1)*Double(height), width:Double.random(in: 0...1)*Double(width), height:Double.random(in: 0...1)*Double(height))) context.strokePath() } } case 2: // Draw random circles (some stroked, some filled) for i in 0...19 { context.addArc( center: CGPoint(x:Double.random(in: 0...1)*Double(width), y:Double.random(in: 0...1)*Double(height)), radius: Float.random(in: 0...1)*((width>height) ? Float(height) : Float(width)), startAngle: 0, endAngle: 2*Float.pi, clockwise: false) if (i % 2 == 0) { context.setColor( Double.random(in: 0...1), Double.random(in: 0...1), Double.random(in: 0...1), Double.random(in: 0...1)) context.fillPath() } else { context.lineWidth = Float.random(in: 0...10) + 2 context.setColor( Double.random(in: 0...1), Double.random(in: 0...1), Double.random(in: 0...1), Double.random(in: 0...1)) context.strokePath() } } default: break } } func display() { let cs = cairo_image_surface_create( CAIRO_FORMAT_ARGB32, Int32(width), Int32(height) ) let c = CGContext(surface: cs, width: width, height: height) // draw draw(c, Float(width), Float(height)) cairo_surface_write_to_png( cs, "test.png" ) } class AppDelegate: NSApplicationDelegate { lazy var window = NSWindow(CGRect(x: 150, y: 380, width: 500, height: 450)) lazy var imageView = NSImageView() lazy var view = UIView() override func applicationDidFinishLaunching(notification: Any?) { print("\nget mainMenu") let menu = forSwift_objcSendMessage(&sharedApp!.pointee, sel_registerName("mainMenu")) // menu: add Action print("item at index") var aindex : Int = 1 print("item index:", aindex) let aindexNSInt: UnsafeMutablePointer<objc_object> = unsafeBitCast(aindex, to: UnsafeMutablePointer<objc_object>.self) let aitem = forSwift_objcSendMessage1ID(&menu!.pointee, sel_getUid("itemAtIndex:"), aindexNSInt) print("index \(aindex) item:", aitem) let aitem2 = objc_convertToSwift_NSObject(value: aitem) as? NSString let aitemtitle = forSwift_objcSendMessage(&aitem!.pointee, sel_registerName("title")) let aitemtitle2 = objc_convertToSwift_NSObject(value: aitemtitle) as? NSString print("index \(aindex) item:", aitemtitle2!.string) let my_actionIMP: @convention(block) () -> () = { print("print from menu") drawFlag = 2 display() let currentPath = fileManager.currentDirectoryPath let filePath:String = currentPath + "/test.png" let image2 = Image.imageWithPath(filePath) self.imageView.setImage(image2!) } let types = "@" let imp = imp_implementationWithBlock(unsafeBitCast(my_actionIMP, to: id.self)) class_addMethod(object_getClass(&aitem!.pointee), sel_registerName("my_action:"),imp, types) _ = forSwift_objcSendMessage1ID(&aitem!.pointee, sel_registerName("setTarget:"), aitem!) let sel = sel_getUid("my_action:") _ = forSwift_objcSendMessage1SEL(&aitem!.pointee, sel_registerName("setAction:"), sel!) // print("end menu\n") display() let currentPath = fileManager.currentDirectoryPath let filePath:String = currentPath + "/test.png" let image = Image.imageWithPath(filePath) window.orderFront(sender: self) window.setTitle(NSString(string: "Image Test")) view.frame = .init(x: 0, y: 0, width: 500, height: 450) imageView.frame = .init(x: 0, y: 0, width: 500, height: 450) imageView.setImage(image!) view.addSubview(imageView) window.setContentView(view) } }
Image.swift
// The Swift Programming Language // https://docs.swift.org/swift-book import FoundationGNUStep import ObjCSwiftInterop import AppKit class Image { static func imageWithPath(_ path: String) -> NSImage? { print("file path:", path) let nsfilePath = NSString(string: path) var nsImageClass = objc_getClass("NSImage") var initializedObject = forSwift_objcSendMessage(&nsImageClass!.pointee, sel_getUid("alloc")) var vimage = forSwift_objcSendMessage1(&initializedObject!.pointee, sel_getUid("initWithContentsOfFile:"), nsfilePath._nsobjptr) //print("vimage type:", type(of: vimage)) let v = NSImage(nsobjptr: &vimage!.pointee) return v } }
CGContext.swift
// The Swift Programming Language // https://docs.swift.org/swift-book // // CGContext.swift // CairoGraphics // import ObjCSwiftInterop import CCairo.Cairo open class CGContext { public var surface: Optional<OpaquePointer> public var context: Optional<OpaquePointer> public internal(set) var height: Int = 0 public internal(set) var width: Int = 0 public init(surface: Optional<OpaquePointer>, width: Int, height: Int) { self.context = cairo_create(surface) self.surface = surface self.width = width self.height = height } } public extension CGContext { func flush() { cairo_surface_flush(surface) } } public extension CGContext { func beginPath() { cairo_new_path(context) } func closePath() { cairo_close_path(context) } var currentPointOfPath: CGPoint { var x: Double = .zero var y: Double = .zero cairo_get_current_point(context, &x, &y) return CGPoint(x: x, y: y) } var boundingBoxOfPath: CGRect { var x1: Double = .zero var y1: Double = .zero var x2: Double = .zero var y2: Double = .zero cairo_path_extents(context, &x1, &y1, &x2, &y2) if x1.isZero && y1.isZero && x2.isZero && y2.isZero { //return .null return CGRect(x: x1, y: y1, width: x2, height: y2) } else { return CGRect(x: min(x1, x2), y: min(y1, y2), width: max(x1, x2) - min(x1, x2), height: max(y1, y2) - min(y1, y2)) } } } public extension CGContext { func move(to point: CGPoint) { cairo_move_to(context, Double(point.x), Double(point.y)) } func addLine(to point: CGPoint) { cairo_line_to(context, Double(point.x), Double(point.y)) } func addRect(_ rect: CGRect) { cairo_rectangle(context, Double(rect.x), Double(rect.y), Double(rect.width), Double(rect.height)) } func addCurve(to end: CGPoint, control1: CGPoint, control2: CGPoint) { cairo_curve_to(context, Double(control1.x), Double(control1.y), Double(control2.x), Double(control2.y), Double(end.x), Double(end.y)) } func addQuadCurve(to end: CGPoint, control: CGPoint) { let current = currentPointOfPath let control1 = CGPoint(x: (current.x / 3.0) + (2.0 * control.x / 3.0), y: (current.y / 3.0) + (2.0 * control.y / 3.0)) let control2 = CGPoint(x: (2.0 * control.x / 3.0) + (end.x / 3.0), y: (2.0 * control.y / 3.0) + (end.y / 3.0)) addCurve(to: end, control1: control1, control2: control2) } func addLines(between points: [CGPoint]) { if points.count == 0 { return } move(to: points[0]) for i in 1..<points.count { addLine(to: points[i]) } } func addRects(_ rects: [CGRect]) { for rect in rects { addRect(rect) } } func addArc(center: CGPoint, radius: Float, startAngle: Float, endAngle: Float, clockwise: Bool) { if clockwise { cairo_arc_negative(context, Double(center.x), Double(center.y), Double(radius), Double(startAngle), Double(endAngle)) } else { cairo_arc(context, Double(center.x), Double(center.y), Double(radius), Double(startAngle), Double(endAngle)) } } } public extension CGContext { func fillPath() { cairo_fill(context) } func clip() { cairo_clip(context) } func resetClip() { cairo_reset_clip(context) } func strokePath() { cairo_stroke(context) } } public extension CGContext { func fill(_ rect: CGRect) { beginPath() addRect(rect) closePath() } func stroke(_ rect: CGRect) { beginPath() addRect(rect) closePath() strokePath() } } public extension CGContext { func setColor(_ r:Double, _ g:Double, _ b:Double, _ a:Double) { cairo_set_source_rgba(context, r, g, b, a); } var lineWidth: Float { get { return Float(cairo_get_line_width(context)) } set { cairo_set_line_width(context, Double(newValue)) } } }