Go to file
2023-10-13 12:07:34 -04:00
cocoa Added instruction to compile 'appdelegate.m' file 2023-10-13 12:07:34 -04:00
doc Update 'doc/NSFunctions.md' 2021-07-29 21:34:52 -04:00
examples Corrected warnings about implicit 'cstring' conversions 2023-10-13 12:02:47 -04:00
images Updated README and uploaded screenshots 2021-07-24 18:05:36 -04:00
tests Configured nimble to build/install the bundle binary 2021-07-27 22:26:45 -04:00
.gitignore Updated Exclusions 2021-08-08 15:32:21 -04:00
bundle.nim Rearraigned to address issue with Nimble 2021-08-08 15:33:13 -04:00
cocoa_lib.nim Added NSStatusBar (systray) support 2021-08-15 19:13:08 -04:00
cocoa.nim Changed 'bind' function to 'bindWidget' to eliminate conflict with 'bind' from the 'socket' library 2023-10-12 13:52:49 -04:00
cocoa.nimble Changed to use cocoa_lib.nim to create libcocoa.a library file 2021-08-08 17:51:47 -04:00
LICENSE Updated LICENSE file 2021-07-18 03:14:20 -04:00
README.md - updated README example 2023-09-24 21:09:13 -04:00

NimCocoa

NimCocoa is an experimental implementation of a Native GUI for the Nim programming language running on macOS.

Rather than rely on low level calls to objc_msgsend and it's variants, it uses actual Objective-C code modules I created that are wrapped using C-Style functions, which Nim can then use.

At the moment, the following GUI objects are available:

NSWindow NSMenu NSSavedialog NSSlider
NSButton NSTextfield NSMessagebox NSListbox NSContainer (Groupbox)
NSLabel NSCheckbox NSOpendialog NSTextedit NSTableView
NSCombobox NSDialog NSLine NSRadioButton

Preliminary documentation for GUI objects is available in the doc folder and in the wiki.

Here is an example of what coding with NimCocoa looks like:

import Cocoa
import json, os, plists, times

var mainWin: ID

var lblFile, txtFile, btnFile, lblAuthor, txtAuthor, lblApp, 
    txtApp, lblImage, txtImage, btnImage, line1, lblVersion, 
    txtVersion, lblIdent, txtIdent, line2, chkLaunch, btnExec,
    btnQuit: ID

const
  width:cint = 800
  height:cint = 310
  winStyle = NSWindowStyleMaskTitled or NSWindowStyleMaskClosable or NSWindowStyleMaskMiniaturizable

proc getExecutable(sender: ID) {.cdecl.} =
  let fName = newOpenDialog(mainWin, "")
  if fName.len > 0:
    txtFile.text = fName

proc getImage(sender: ID) {.cdecl.} =
  let fName = newOpenDialog(mainWin, "icns")
  if fName.len > 0:
    txtImage.text = fName
  
proc quit(sender: ID) {.cdecl.} =
  Cocoa_Quit(mainWin)

proc createAppBundle(sender: ID) {.cdecl.} =
  let dt = now()

  let appAuthor = $txtAuthor.text
  let appName = $txtFile.text
  let iconFile = $txtImage.text
  let bundleName = $txtApp.text
  let bundleIdentifier = $txtIdent.text
  let appVersion = $txtVersion.text
  let appInfo = appVersion & " Created by " & appAuthor & " on " & dt.format("MM-dd-yyyy")
  let appCopyRight = "Copyright" & dt.format(" yyyy ") & appAuthor & ". All rights reserved."
  let appBundle = bundleName & ".app"

  var pl = %*
    { "CFBundlePackageType" : "APPL",
      "CFBundleInfoDictionaryVersion" : "6.0",
      "CFBundleName" : bundleName,
      "CFBundleExecutable" : bundleName,
      "CFBundleIconFile" : extractFilename(iconFile) ,
      "CFBundleIdentifier" : bundleIdentifier ,
      "CFBundleVersion" : appVersion ,
      "CFBundleGetInfoString" : appInfo,
      "CFBundleShortVersionString" : appVersion ,
      "NSHumanReadableCopyright" : appCopyRight ,
      "NSPrincipalClass" : "NSApplication" ,
      "NSMainNibFile" : "MainMenu" 
    }

  createDir(appBundle & "/Contents/MacOS")
  createDir(appBundle & "/Contents/Resources")
  createDir(appBundle & "/Contents/Frameworks")

  if appName.fileExists:
    appName.copyFileWithPermissions(appBundle & "/Contents/MacOS/" & bundleName)

    if iconFile.fileExists:
      iconFile.copyFileWithPermissions(appBundle & "/Contents/Resources/" & extractFileName(iconFile))

    if "Credits.rtf".fileExists:
      "Credits.rtf".copyFileWithPermissions(appBundle & "/Contents/Resources/Credits.rtf")

        
    pl.writePlist(appBundle & "/Contents/Info.plist")

    discard execShellCmd("touch " & appBundle)

    if chkLaunch.state == 1:
      discard execShellCmd("open " & appBundle)


proc main() =

  Cocoa_Init()

  mainWin = newWindow("macOS Application Bundler", width, height, winStyle)

  lblFile = newLabel(mainWin, "Select Executable",30, 20, 120, 25)
  txtFile = newTextField(mainWin, "", 160, 20, 500, 25)
  btnFile = newButton(mainWin, "Load", 680, 20, 100, 25, getExecutable)
  txtFile.anchor=akWidth; btnFile.anchor=akRight

  lblAuthor = newLabel(mainWin, "Author Name", 30, 60, 120, 25)
  txtAuthor = newTextField(mainWin, "", 160, 60, 500, 25)
  txtAuthor.anchor=akWidth

  lblApp = newLabel(mainWin, "Application Name", 30, 100, 120, 25)
  txtApp = newTextField(mainWin, "", 160, 100, 170, 25)
  txtApp.anchor=akWidth

  lblImage = newLabel(mainWin, "Select Icon File", 30, 200, 120, 25)
  txtImage = newTextField(mainWin, "", 160, 200, 500, 25)
  btnImage = newButton(mainWin, "Load", 680, 200, 100, 25, getImage)
  txtImage.anchor=akWidth; btnImage.anchor=akRight

  line1 = newSeparator(mainWin, 30, 140, 750)

  lblVersion = newLabel(mainWin, "Application Version", 350, 100, 130, 25)
  txtVersion = newTextField(mainWin, "", 490, 100, 170, 25)
  txtVersion.anchor=akRight; lblVersion.anchor=akRight

  lblIdent = newLabel(mainWin, "Bundle Identifier", 30, 160, 120, 25)
  txtIdent = newTextField(mainWin, "", 160, 160, 500, 25)
  # btnCredits = newButton(mainWin, "Load", 680, 160, 100, 25, nil)
  txtIdent.anchor=akWidth

  line2 = newSeparator(mainWin, 20, 240, 750)

  chkLaunch = newCheckBox(mainWin, "Launch Application?", 390, 270, 150, 25)
  btnExec = newButton(mainWin, "🟢 Execute", 680, 270, 100, 25, createAppBundle)
  chkLaunch.anchor=akLeft + akRight + akBottom; btnExec.anchor=akRight + akBottom

  btnQuit = newButton(mainWin, "🔴 Quit", 565, 270, 100, 25, quit)

  Cocoa_Run(mainWin)
  

if isMainModule:
  main()

Which results in the following:

Another screenshot:

Same app resized, showing how objects flow based on their associated 'anchor' setting:

I am working on documenting the available objects/functions as well as examples.

Stay Tuned!