Hello everybody,
I'm trying to make a native looking GUI for MacOS (using Cocoa api). Is there any NIM bindings to Cocoa or any other way to make native GUI for Mac?
Thanks
I've tried nimx one of the first, it works fine with OSX but is far from native :(
Actually threre is one wrapper: https://github.com/SSPkrolik/nim-native-dialogs, it emits Objective-C commands directly to Cocoa API, as I understand. It works perfect but only for two things: file open and save windows. Just thinking if it is possible to wrap all Cocoa API such direct way?
Actually its just amazing, why so obvious task like UI for mac
Because it’s difficult, and a large task considering the API surface area.
Also, IMHO a large proportion of the OSS/hacker community either use Linux ... or if on a Mac don’t really care about apps other than Terminal and a web browser ... or else have shitty taste in UI/UX and can’t see the benefit of a well-designed native UI over some cross-platform thing.
The reality is you could write apps in Obj-C in 1996 and you still can today.
Fair enough. The tooling for that is/was ridiculous though, compared to Delphi.
But to keep this discussion more productive: If you were to write a Nim based UI library targetting the native OSX widgets, how would you do it, today?
And if you need "beautifull UI" - you can't build it with anything other than the platform native tools, no compromises like "UI wrapper in language X".
That’s not true. If by “platform native tools” you mean Interface Builder, it’s not required, and has in fact become less useful over time as UIs stopped relying on absolute scales/positions.
The only things you need to build a fully native Mac or iOS UI are the ability to call into Objective-C and to subclass certain Objective-C classes. Nim can do the former, but I’m not sure about the latter.
var windowToolkitKind = WindowToolkitKind.Darwin
{.passL: "-framework AppKit".}
type NSOpenPanel {.importobjc: "NSOpenPanel*", header: "<AppKit/AppKit.h>", incompleteStruct.} = object
proc newOpenPanel: NSOpenPanel {.importobjc: "NSOpenPanel openPanel", nodecl.}
- template wrapObjModalCode(body: untyped) =
- {.emit: """ NSAutoreleasePool* pool = [NSAutoreleasePool new]; NSWindow* keyWindow = [NSApp keyWindow]; NSOpenGLContext* glCtx = [[NSOpenGLContext currentContext] retain]; """.} body {.emit: """ [pool drain]; [glCtx makeCurrentContext]; [glCtx release]; [keyWindow makeKeyAndOrderFront: nil]; """.}
- proc callDialogFileOpen*(title: string, buttons: seq[DialogButtonInfo] = @[]): string =
- wrapObjModalCode:
- let dialog = newOpenPanel() let ctitle : cstring = title var cres: cstring {.emit: """ [dialog setCanChooseFiles:YES]; dialog.title = [NSString stringWithUTF8String: ctitle];
- if ([dialog runModal] == NSOKButton && dialog.URLs.count > 0) {
- cres = [dialog.URLs objectAtIndex: 0].path.UTF8String;
} """.}
- if not cres.isNil:
- result = $cres
unfortunately it is only for this panel...
Somewhat relevant. darwin package is supposed to be a complete collection of macos/ios frameworks, which it is not at the moment, but new bindings can be easily declared like in this NSView. Advantage of this way (instead of nim's builtin importobjc) is that there's no header dependencies, the files can be compiled with either C or C++ backend, and objc compiler is not required.
Subclassing objc/swift in nim is not implemented yet, but eventually will be if there's enough interest.
Subclassing objc/swift in nim is not implemented yet, but eventually will be if there's enough interest.
I'm not sure how Nim/Obj-C interop works, but if it's difficult to emit an @interface and @implementation at compile-time, an alternative is to create the class at runtime using the C API in <objc/objc-runtime.h>. It's fairly easy to do; the trickiest part probably is that the methods need to be provided in the form of C functions that have self and _cmd parameters prepended.
I have some experience using this API, and I'm glad to offer advice if it's desired.
I've been plaing with 'objc_runtime' this weekend, and wanted to show how I created a subclass of NSObject with methods, property, and iVar.
Idea came from this`StackOverflow post https://stackoverflow.com/questions/7819092/how-can-i-add-properties-to-an-object-at-runtime/7834787#7834787`
import objc_runtime
import darwin / foundation
from math import log2
type
propertyArray = array[3, objc_property_attribute_t]
var
prop_type = objc_property_attribute_t(name: "T", value: "@\"NSString\"")
ownership = objc_property_attribute_t(name: "C", value: "")
backingivar = objc_property_attribute_t(name: "V",value: "_privateName")
let attrs:propertyArray = [prop_type, ownership, backingivar]
objcr:
# **********************************************
# ***** DYNAMIC CLASS CREATION BEGINS HERE *****
# **********************************************
proc nameGetter(self: ID, cmd: SEL ): NSString =
var ivar = getIvar(cast[Class]([SomeClass class]), "_privateName")
result = cast[NSString](getIvar(self, ivar))
proc nameSetter(self: ID, cmd: SEL, newName: NSString ) =
var ivar = getIvar(cast[Class]([SomeClass class]), "_privateName")
setIvar(self, ivar, [newName.ID copy])
# *****************************
# ***** SUBCLASS NSOBJECT *****
# *****************************
let SomeClass = allocateClassPair(getClass("NSObject"),"SomeClass",0)
# *******************************************************************
# ***** ivars can only be added PRIOR to registering the class! *****
# *******************************************************************
discard addIvar(SomeClass,"_privateName",sizeof(ID),log2(sizeof(ID).float).int,"@")
# *****************************
# ***** REGISTER SUBCLASS *****
# *****************************
registerClassPair(SomeClass)
# *****************************************************************************
# ***** Properties/Methods can only be added AFTER registering the class! *****
# *****************************************************************************
discard addProperty(cast[Class]([SomeClass class]), "name", attrs)
discard addMethod(getClass("SomeClass"),registerName("name"), cast[IMP](nameGetter), "@@:")
discard addMethod(getClass("SomeClass"),registerName("setName:"), cast[IMP](nameSetter), "v@:@")
# ********************************************
# ***** DYNAMIC CLASS CREATION ENDS HERE *****
# ********************************************
proc main() =
var o = [SomeClass new]
[o setName: @"Steve"]
NSLog("%@",[o name])
[o setName: @"Jobs"]
NSLog("%@",[o name])
if isMainModule:
main()
AIR.
"It doesn't help that Apple keeps changing everything. " Your sentiments are shared by other language creators such as Marc Hoffman (REM Objects) co-creator of the polyglot compiler, "Elements" and the Oxygene language (Pascal-based CLI language). Although not specific to GUI development, the apparent frustration remains..
Swift 5.5: again big pile of changes …. sigh