Slint言語入門(4) 構文について(続2)

# はじめに
 [スリントアドベントカレンダー](https://qiita.com/advent-calendar/2024/slint)も無事に埋まって１３日の金曜日を迎えました。昔はホラー映画のタイトルになるような縁起が悪いみたいな話もあったのですけど、そういえば最近そういう話きかなくなりましたね。え、知らない？…そうですか。

　昨日は、@task_jpさんによる「[Slint のウィンドウサイズの変更を Rust 側で取得する](https://qiita.com/task_jp/items/75759eac712c7a16c45c)」でした。Slint言語入門ではビジネスロジック側の話はできるだけ省いてしまっているので、気になる人はぜひ確認してみてください。

## 本日のお題
　Slint言語入門と称して、いずれ技術同人誌を書くためのたたき台作成中４日目です。そういえば、Slint向け同人誌用のキャラデザとかも誰かにお願いしなきゃだなぁ。Slintも、Rustも割と更新や機能追加が早いのでどこまでをいつ出すのかも問題ですけど。

- [Slint言語入門(1) 概要](https://qiita.com/hermit4/items/81cd36d65f33178085e6)
- [Slint言語入門(2) 構文について](https://qiita.com/hermit4/items/3128daed1d0605e05ff9)
- [Slint言語入門(3) 構文について(続)](https://qiita.com/hermit4/items/53f71549087a7d51ed15)

# Slint言語入門(4)

## その他の構文
### コメント
　コメントの表記はCスタイルで、行コメントとブロックコメントがあります。

 ```qml:
 // 行コメント。行末までコメント
 /* ブロックコメント
    閉じられるまで
 　　コメント */
 /* ブロックコメントは 
     /* 閉じられるまで */ 
    コメント扱いとなります */
```

Qiitaだとうまくパースされていませんが、上記の例はすべてコメントとなります。

### アニメーション
　animateキーワードを使って色の変化や位置、サイズの変化などをアニメーションできます。

```qml:
export component Example inherits Window {
    preferred-width: 100px;
    preferred-height: 100px;

    background: area.pressed ? blue : red;
    animate background {
        duration: 250ms;
    }

    area := TouchArea {}
}
```

　上記の例では、クリック状態で色が変化しますが250msの間隔でアニメーション的に変動するようになっています。

  animate指定には、以下のプロパティが用意されています。
  - delay : アニメーション開始までの待機時間
  - duration : アニメーションが完了するまでにかかる時間
  - iteration-count : アニメーションの実行回数。負数を指定すると無限に一項します
  - easing : 時間経過に伴う変動率
      - linear
      - ease-in-quad
      - ease-out-quad
      - ease-in-out-quad
      - ease
      - ease-in
      - ease-out
      - ease-in-out
      - ease-in-quart
      - ease-out-quart
      - ease-in-out-quart
      - ease-in-quint
      - ease-out-quint
      - ease-in-out-quint
      - ease-in-expo
      - ease-out-expo
      - ease-in-out-expo
      - ease-in-sine
      - ease-out-sine
      - ease-in-out-sine
      - ease-in-back
      - ease-out-back
      - ease-in-out-back
      - ease-in-circ
      - ease-out-circ
      - ease-in-out-circ
      - ease-in-elastic
      - ease-out-elastic
      - ease-in-out-elastic
      - ease-in-bounce
      - ease-out-bounce
      - ease-in-out-bounce
      - cubic-bezier(a, b, c, d)
 
　QMLとSlintの比較記事では、アニメーションは変動率くらいしか設定できなくてQMLに及ばないと記載しましたが、実は多くの変動率が用意されていました。お詫びして訂正します。
　どのように変動するかは[easings.net](https://easings.net/)が参考になります。本当はfor文でループしながらSlintPadで動くサンプル例を作ってみようと思ったのですが、easingって型じゃないんですね。配列にしてanimateのプロパティに渡す方法がわからなかった。バックエンド側使えばなんとかなりそうなんですが、Slint言語だけだとfor文諦めて羅列するしかなくて、無駄にコードが長くなりそうだったので今回は諦めました。まぁ、多分そのうちしれっとできるようになったりする気がしますが。

## 状態定義
　statesキーワードを利用すると特定の条件で成立する状態を定義し、プロパティ変更をまとめて管理することが可能になります。

```qml:
export component Example inherits Window {
    preferred-width: 100px;
    preferred-height: 100px;
    default-font-size: 24px;

    label := Text { }
    ta := TouchArea {
        clicked => {
            active = !active;
        }
    }
    property <bool> active: true;
    states [
        active when active && !ta.has-hover: {
            label.text: "Active";
            root.background: blue;
        }
        active-hover when active && ta.has-hover: {
            label.text: "Active\nHover";
            root.background: green;
        }
        inactive when !active: {
            label.text: "Inactive";
            root.background: gray;
        }
    ]
}
```
　本家のドキュメントのサンプルそのままですが、ta(TouchArea)のhas-hoverと、activeプロパティの値に応じて状態が変化し、その状態によりlabel.txtとroot.backgroundが設定されるという実装になっています。
　このコードをSlintPadに入れると、active状態、activeでマウスカーソルが合うとactive-hover、クリックでactive状態がfalseになるとinactiveになります。

### 状態変化
　in, outキーワードを使うと状態の変化時にアニメーションをつけることも可能です。

 ```qml:
 export component Example inherits Window {
    preferred-width: 100px;
    preferred-height: 100px;
    default-font-size: 24px;

    label := Text { }
    ta := TouchArea {
        clicked => {
            active = !active;
        }
    }
    property <bool> active: true;
    states [
        active when active && !ta.has-hover: {
            label.text: "Active";
            root.background: blue;
        }
        active-hover when active && ta.has-hover: {
            label.text: "Active\nHover";
            root.background: green;
        }
        inactive when !active: {
            label.text: "Inactive";
            background: gray;
            in {
                animate background { duration: 800ms; }
            }
            out {
                animate background { duration: 800ms; }
            }
        }
    ]
}
 ```
　active化、非active化のタイミングで背景色の変動がアニメーションするようにしてみました。本ドキュメント執筆時点では、SlintPadの問題なのかSlint側に不具合があるのか、ドキュメントに記載のあるようなターゲット指定や"*"指定などがきちんと動作していないようです。

## グローバルシングルトン
　プロパティやコールバックについて、コンポーネントやエレメントツリー内で定義できるとしてきましたが、globalキーワードを使ってグローバルシングルトンを宣言し、モジュール内で共用することが可能です。

　たとえば、以下のように配色を一括で管理するなどの用途に利用できます。
```qml:
global Palette  {
    in-out property<color> primary: blue;
    in-out property<color> secondary: green;
}

export component Example inherits Rectangle {
    background: Palette.primary;
    border-color: Palette.secondary;
    border-width: 2px;
}
```
　グローバルシングルトンをexportすれば、他のモジュールやバックエンド（ビジネスロジック）側からもアクセス可能になります。

 ```qml: app.slint
export global Logic  {
    in-out property <int> the-value;
    pure callback magic-operation(int) -> int;
}
   :
 ```
　ビジネスロジックとSlint言語の合わせ込みは、カレンダーの昨日の記事や、Python-Slintに関する記事などを参考にしていただければわかりますが、基本的にインスタンス化したコンポーネントを経由してアクセスします。
　ここでは割愛しますが、このような状況のため、現状ではグローバルシングルトンへのビジネスロジック側からのアクセスはちょっと迂遠になります。その対策として、双方向バインディング構文(<=>)でビジネスロジック側に公開するコンポーネントへリンクして公開するという手段も用意されています。

 ```qml:
global Logic  {
    in-out property <int> the-value;
    pure callback magic-operation(int) -> int;
}

component SomeComponent inherits Text {
    // use the global in any component
    text: "The magic value is:" + Logic.magic-operation(42);
}

export component MainWindow inherits Window {
    // MainWindowのインスタンスを使ってグローバルシングルトンが利用できるようになる
    in-out property the-value <=> Logic.the-value;
    pure callback magic-operation <=> Logic.magic-operation;

    SomeComponent {}
}
 ```
 ## モジュール構文
　Slint言語ではファイルをモジュールという単位としており、内部のコンポーネント・グローバルシングルトンなどをexportすることで別モジュールからimportできることは既に説明しました。このimport/export構文は以下のような記述が可能です。

 ```qml:import
import { export1 } from "module.slint";
import { export1, export2 } from "module.slint";
import { export1 as alias1 } from "module.slint";
import { export1, export2 as alias2, /* ... */ } from "module.slint";
 ```

　上記は、module.slintでexportされたシンボルをインポートしています。必要に応じて別名でインポートすることも可能です。

```qml:export
// Export declarations
export component MyButton inherits Rectangle { /* ... */ }

// Export lists
component MySwitch inherits Rectangle { /* ... */ }
export { MySwitch }
export { MySwitch as Alias1, MyButton as Alias2 }

// 他のモジュールのシンボルを再エクスポートも可能
export { MyCheckBox, MyButton as OtherButton } from "other_module.slint";

// すべてのシンボルを再エクスポートすることも可能（ファイルごとに１度きりですが）
export * from "other_module.slint";
```
　例ではcomponentの前にexportをつけてきましたが、別だしでまとめてexport宣言も可能です。

 ```qml:
component ButtonHelper inherits Rectangle {
    // ...
}

component Button inherits Rectangle {
    // ...
    ButtonHelper {
        // ...
    }
}

export { Button }
 ```

　また、外部に公開する際、内部実装を変更することなく、別名で公開することも可能になっています。

 ```qml:
component Button inherits Rectangle {
    // ...
}

export { Button as ColorButton }
 ```

　外部のモジュールからインポートしたグローバルシンボルを別名でエクスポートする例も公式ドキュメントに記載されていますが、正直やりすぎると探すのに苦労しかねないので程々にしましょう。

 ```qml:
import { Logic as MathLogic } from "math.slint";
export { MathLogic } 
 ```

　なお、fromのあとのモジュールファイルパスは、基本的に相対パスになっています。モジュールファイルを格納するフォルダを分割してコンポーネントライブラリとし、Slintコンパイラに名前とパスを引き渡すことでパスのハードコードを避ける手法もあるのですが、利用するビジネスロジック側言語の話もしないとならないので、ビジネスロジック側との合わせ込み周りで説明したいなと思います。


## フォーカスと入力
　TextInputなど特定のエレメントはマウスやタッチパネル、キーボードなどの入力を受け付けます。これらのイベントを受け入れるにはフォーカスが必要です。フォーカスがあるかはhas-focusプロパティで確認できます。
  これらのエレメントは、focus()を呼び出せば、手動でフォーカスをアクティブにできます。

```qml:
import { Button } from "std-widgets.slint";

export component App inherits Window {
    VerticalLayout {
        alignment: start;
        Button {
            text: "press me";
            clicked => { input.focus(); }
        }
        input := TextInput {
            text: "I am a text input field";
        }
    }
}
```

　同様にclear-focus()を呼び出せば手動でフォーカスをクリアできます。

```qml
import { Button } from "std-widgets.slint";

export component App inherits Window {
    VerticalLayout {
        alignment: start;
        Button {
            text: "press me";
            clicked => { input.clear-focus(); }
        }
        input := TextInput {
            text: "I am a text input field";
        }
    }
}
```

　フォーカスをクリア後、別のエレメントがフォーカスされるまではウィンドウへのキー入力は破棄されます。フォーカスは、focus()呼び出し、マウスなどのクリック、Tabを押してフォーカス可能なエレメントが発見された時、エレメントにフォーカスが移ります。

  また、コンポーネントのforward-focusプロパティを使うと、コンポーネントのフォーカスの転送対象エレメントを設定できます。
```qml:
import { Button } from "std-widgets.slint";

component LabeledInput inherits GridLayout {
    forward-focus: input;
    Row {
        Text {
            text: "Input Label:";
        }
        input := TextInput {}
    }
}

export component App inherits Window {
    GridLayout {
        Button {
            text: "press me";
            clicked => { label.focus(); }
        }
        label := LabeledInput {
        }
    }
}
```

　このウィンドウでボタンをクリックすると、LabeldInputにフォーカスを設定しますが、forward-focusの指定によりフォーカスがTextInputに移ります。

　Windowエレメントのforward-focusプロパティを使うと、ウィンドウが最初にフォーカスを受け取った時にフォーカスされる対象を設定できます。


# まとめ
　本日は、Slint言語入門として残りの雑多な構文について取りまとめました。言語組込み機能（エレメントやコールバック、関数など）や標準ウィジェットの解説が残っていますが、解説ばかりだと飽きるので、次回はちょっと手を動かす何かをやっておきたいと思います。

　明日は、@task_jpさんが何か書いてくださるそうです。期待してお待ちしましょう。