NimでElectron（デスクトップアプリ）

連続で忙しい日にアドベントカレンダー入れるんじゃなかった泣
と思いながらGodotでNimを使ってゲームを作ろうかと思ったのですが
すでに似た記事があったためNim + Electronに変更します 笑

# 概要

#### 実行環境

・M1 MacbookAir（2020）
・OS macOS BigSur 11.5.2
・メモリ 16GB

#### ディレクトリ構成

```
├── index.html
├── main.js ・・・コンパイル結果
├── main.nim
└── package.json
```

```json:package.json
{                                                                                                                                                                                 
  "name": "electron-quick-start",
  "version": "1.0.0",
  "description": "A minimal Electron application",
  "main": "main.js",
  "scripts": {
    "start": "electron ."
  },  
  "repository": "https://github.com/electron/electron-quick-start",
  "keywords": [
    "Electron",
    "quick",
    "start",
    "tutorial",
    "demo"
  ],  
  "author": "GitHub",
  "license": "CC0-1.0",
  "devDependencies": {
    "electron": "^12.0.1"
  }
}
```

####　実行の手順

main.nimに処理を記述して以下でjsにコンパイルしてnpm startで実行できます。

```shell
$ npm install
$ nim js -d:node -o:main.js main.nim

# アプリ起動
$ npm start
```

# NimでのElectron記述

### jsライブラリのimport

まずelectronがjs製なため、jsの関数やイベントをロードする必要があります。
ここら辺は脳死で書いてしまっていいかと思います。
（動くまでに結構時間がかかりました...）

```Nim:main.nim
import jsffi

{.emit: """ 
const electron = require('electron')
const path = require('path')
const url = require('url')
const net = require('net');
""".}

var
  electron {. importc, nodecl .}: JsObject
  path {. importc, nodecl .}: JsObject
  url {. importc, nodecl .}: JsObject
  console {. importc, nodecl .}: JsObject
  process {.importc, nodecl.}: JsObject
  net {.importc, nodecl.}: JsObject
  dirname {.importc: "__dirname", nodecl.}: JsObject
```

### ウィンドウの起動設定

```Nim:main.nim
let app = electron.app
var mainWindow: JsObject

proc createWindow() =
  # ウィンドウサイズを指定してjsnewを使ってウィンドウを作成
  mainWindow = jsnew electron.BrowserWindow(JsObject{width: 800, height: 600})
  mainWindow.loadURL(url.format(JsObject{
    pathname: dirname.to(cstring) & "/index.html",
    protocol: "file".cstring,
    slashes: true
  })) 

  mainWindow.on("closed", proc () =
    mainWindow = nil 
  )
```

#### イベントハンドラ

##### ready（起動時）
```Nim:main.nim
app.on("ready", createWindow)
```

##### window-all-closed（Windowが全部閉じられた時）
```Nim:main.nim
app.on("window-all-closed", proc () =
  if process.platform.to(cstring) != "darwin":
　　　　　　　　# 終了処理
    app.quit()
)
```
ここでdarwinとしているのはos判定なので、
NimのOSプラットフォームを見て必要であれば追加してください。

##### バックグラウンドからアクティブにされた時
```Nim:main.nim
app.on("activate", proc() =
  if mainWindow == nil:
    createWindow()
)
```

activateにcreateWindowを書く必要があるのは
macOSなどでバックグラウンド起動時にアイコンから起動で
ウィンドウを再生成する場合があるためらしいです。

以下、main.nimの全文です。

```Nim:main.nim
import jsffi

{.emit: """
const electron = require('electron')
const path = require('path')
const url = require('url')
const net = require('net');
""".}

var
  electron {. importc, nodecl .}: JsObject
  path {. importc, nodecl .}: JsObject
  url {. importc, nodecl .}: JsObject
  console {. importc, nodecl .}: JsObject
  process {.importc, nodecl.}: JsObject
  net {.importc, nodecl.}: JsObject
  dirname {.importc: "__dirname", nodecl.}: JsObject

let app = electron.app
var mainWindow: JsObject

proc createWindow() =
  mainWindow = jsnew electron.BrowserWindow(JsObject{width: 800, height: 600})
  mainWindow.loadURL(url.format(JsObject{
    pathname: dirname.to(cstring) & "/index.html",
    protocol: "file".cstring,
    slashes: true
  }))

  mainWindow.on("closed", proc () =
    mainWindow = nil
  )

app.on("ready", createWindow)

app.on("window-all-closed", proc () =
  if process.platform.to(cstring) != "darwin":
    app.quit()
)

app.on("activate", proc() =
  if mainWindow == nil:
    createWindow()
)
```