🦉

Nimでデスクトップアプリを作る

2023/01/03に公開約3,100字

はじめに

解説を挟みつつ、Nimでデスクトップアプリを作っていきます。

環境

  • MacOS Ventura 13.0.1
  • Nim1.6.6

GUI

owlkettleというGUIライブラリを使って作っていきます。
https://zenn.dev/neo/scraps/456c7a54325314

サンプルアプリ

owlkettleのguiマクロを使うと、宣言的UIを構築できます。
試しに、次のようなコードを書いてみます。

import
  pkg/owlkettle

viewable App:
  counter: int

method view(app: AppState): Widget =
  result = gui:
    Window:
      title = "Counter"
      defaultSize = (200, 60)

      Box(orient = OrientX, margin = 12, spacing = 6):
        Label(text = $app.counter)
        Button {.expand: false.}:
          text = "+"
          style = {ButtonSuggested}
          proc clicked() =
            app.counter += 1

brew(gui(App()))

実行するとこんなウィンドウが生成されました。

+ボタンを押すと左の数字が増えていく、簡単なアプリケーションです。コードを少しずつ解説します。

viewable

owlkettleでは、最初にviewableマクロを使って、Viewableという抽象的なオブジェクトを定義します。この例では、Appが定義されています。Viewableには、アプリの中で変化する値をフィールドとして格納しておき、ここでは、int型のcounterを定義しています。

viewable App:
  counter: int

view

次にviewメソッドを定義していきます。ここで、viewApp型ではなくAppState型を引数に取ることに注意します。AppStateは、Appを定義したときに同時に定義されているオブジェクトです。
viewableでとあるXを定義するとき、自動的にXStateが定義されます。XStateは、Xと同じフィールドをもちます。また、viewの返り値はWidget型です。

proc view(app: AppState): Widget =
  ...

gui, Window

guiマクロでは、ウィジェットと呼ばれるオブジェクトを入れ子構造で記述します。各ウィジェットにはフィールドがあり、Widget(field = value), もしくはウィジェットのブロック内でfield = valueのように設定できます。まずはウィンドウを表示するために、Windowウィジェットを配置します。フィールドとしてtitledefaultSizeをもっています。

result = gui:
  Window:
    title = "Counter"
    defaultSize = (200, 60)

Box

次に、数字とボタンを表示しますが、その前にBoxというウィジェットを配置します。通常のウィジェットは入れ子として配置できる子ウィジェットが1つなのに対し、このウィジェットは複数の子ウィジェットをもつことができ、ウィジェットを複数並べて表示するときに使います。orientフィールドには定数OrientXOrientYが設定でき、前者で左から横に、後者で上から縦に並べて表示します。ここではOrientXにします。フィールドmarginは外側、spacingは内側の余白を設定します。

result = gui:
  Window:
    ...
    Box(orient = OrientX, margin = 12, spacing = 6):
      ...

Label, Button

テキストとボタンを表示します。Labelウィジェットでテキストを表示できます。textフィールドに、appのフィールドcounterを文字列化したものを設定すると、counterが更新されたとき勝手に表示も更新されます。
Buttonウィジェットを使いボタンを表示します。Button{.expand: false.}というプラグマがついています。詳しい説明は割愛しますが、これは親ウィジェットであるBoxAdderというものの一種で、このプラグマをつけると無駄な余白をなくして表示できます。textにはボタンに表示する文字を、styleにはButtonStyle型の組み合わせをsetで設定します。Button内ブロックに定義されているclicked()は、イベントと呼ばれるものの一種です。ウィジェットには、フィールドの他に、ユーザーがアクションを起こしたときに発火するイベントという関数を設定できるものがあり、Buttonには、そのボタンをクリックしたときに発火するclicked()が用意されています。ここでは、app.counterをインクリメントする処理が書かれています。

result = gui:
  Window:
    ...
    Box:
      Label(text = $app.counter)
      Button {.expand: false.}:
        text = "+"
        style = {ButtonSuggested}
        proc clicked() =
          app.counter += 1

brew

最後に、定義したAppview()を元にウィンドウを組み立てて表示します。

brew(gui(App()))

作ってみた

owlkettleを使って、パスワードを生成するアプリを作ってみました。
https://github.com/Glasses-Neo/passGen

おわりに

Nim製GUIライブラリの選択肢は他にもありますが、その中でも記述しやすく、ドキュメントが比較的充実していることや、クロスプラットフォームに対応している点で優れていると思います。ぜひ使ってみてください!

GitHubで編集を提案

Discussion

ログインするとコメントできます