Slint のウィンドウサイズの変更を Rust 側で取得する

# はじめに

この記事は [Slint Advent Calendar 2024](https://qiita.com/advent-calendar/2024/slint) 12日目の記事です。

昨日は @gony さんによる [Slintでモーダルダイアログを実現する](https://qiita.com/gony/items/0e6bbbd792b33e49ec1c) という記事でした。

Slint ではまだモーダルはサポートされていないようですが、きっとそのうちされるでしょう。

https://github.com/slint-ui/slint/issues/6607

Windows 限定ではありますが、自前で頑張ってる人もいました。

https://github.com/slint-ui/slint/discussions/6028

---

今日は.slint で生成したウィンドウのサイズの変更を Rust 側で検知する方法です。

# ウィンドウのサイズをプロパティにする

まずは [SlintPad](https://slintpad.com/?snippet=%2F%2F+%E3%82%B5%E3%82%A4%E3%82%BA%E3%82%92%E4%BF%9D%E6%8C%81%E3%81%99%E3%82%8B%E6%A7%8B%E9%80%A0%E4%BD%93%E3%82%92%E5%AE%9A%E7%BE%A9%E3%81%99%E3%82%8B%0Aexport+struct+Size+%7B%0A++++width%3A+length%2C%0A++++height%3A+length%2C%0A%7D%0A%0Aexport+component+AppWindow+inherits+Window+%7B%0A++++%2F%2F+%E3%83%87%E3%83%95%E3%82%A9%E3%83%AB%E3%83%88%E3%81%AE%E5%A4%A7%E3%81%8D%E3%81%95%E3%82%92%E6%8C%87%E5%AE%9A%0A++++preferred-width%3A+1280px%3B%0A++++preferred-height%3A+720px%3B%0A%0A++++%2F%2F+%E3%82%A6%E3%82%A3%E3%83%B3%E3%83%89%E3%82%A6%E3%81%AE%E3%82%B5%E3%82%A4%E3%82%BA%E3%82%92+Size+%E6%A7%8B%E9%80%A0%E4%BD%93%E5%9E%8B%E3%81%AE%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E3%81%A7%E4%BF%9D%E6%8C%81%E3%81%99%E3%82%8B%0A++++out+property%3CSize%3E+window-size%3A+%7B+width%3A+root.width%2C+height%3A+root.height+%7D%3B%0A%0A++++label+%3A%3D+Text+%7B%0A++++++++%2F%2F+%E3%82%A6%E3%82%A3%E3%83%B3%E3%83%89%E3%82%A6%E3%81%AE%E6%A8%AA%E5%B9%85%E3%81%A8%E9%AB%98%E3%81%95%E3%82%92%E8%A1%A8%E7%A4%BA%E3%81%99%E3%82%8B%0A++++++++text%3A+%22%5C%7Broot.window-size.width+%2F+1px%7D+x+%5C%7Broot.window-size.height+%2F+1px%7D%22%3B%0A++++%7D%0A%7D%0A&style=native) で以下のコードを試してみましょう。

```qml
// サイズを保持する構造体を定義する
export struct Size {
    width: length,
    height: length,
}

export component AppWindow inherits Window {
    // デフォルトの大きさを指定
    preferred-width: 1280px;
    preferred-height: 720px;

    // ウィンドウのサイズを Size 構造体型のプロパティで保持する
    out property<Size> window-size: { width: root.width, height: root.height };

    label := Text {
        // ウィンドウの横幅と高さを表示する
        text: "\{root.window-size.width / 1px} x \{root.window-size.height / 1px}";
    }
}
```

サイズ用の構造体 `Size` を定義し、`window-size` というプロパティとその値の関係を宣言しています。

ここではその値を `Text` に表示しています。

[String](https://releases.slint.dev/1.0.1/docs/slint/src/reference/types#strings) 型は文字列中に `\{...}`と書くと、その中を評価して文字に置換してくれます。

なお、`length` 型はそのまま指定すると、
```
Error: Cannot convert length to string. Devide by 1px to convert to a plain number
```
というエラーになるため、指示どおり `1px` で割ることで数値に変換しています。


# ウィンドウのプロパティが変更されたらなにかをする

つぎに、以下のコードを [SlintPad](https://slintpad.com/?snippet=%2F%2F+%E3%82%B5%E3%82%A4%E3%82%BA%E3%82%92%E4%BF%9D%E6%8C%81%E3%81%99%E3%82%8B%E6%A7%8B%E9%80%A0%E4%BD%93%E3%82%92%E5%AE%9A%E7%BE%A9%E3%81%99%E3%82%8B%0Aexport+struct+Size+%7B%0A++++width%3A+length%2C%0A++++height%3A+length%2C%0A%7D%0A%0Aexport+component+AppWindow+inherits+Window+%7B%0A++++%2F%2F+%E3%83%87%E3%83%95%E3%82%A9%E3%83%AB%E3%83%88%E3%81%AE%E5%A4%A7%E3%81%8D%E3%81%95%E3%82%92%E6%8C%87%E5%AE%9A%0A++++preferred-width%3A+1280px%3B%0A++++preferred-height%3A+720px%3B%0A%0A++++%2F%2F+%E3%82%A6%E3%82%A3%E3%83%B3%E3%83%89%E3%82%A6%E3%81%AE%E3%82%B5%E3%82%A4%E3%82%BA%E3%82%92+Size+%E6%A7%8B%E9%80%A0%E4%BD%93%E5%9E%8B%E3%81%AE%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E3%81%A7%E4%BF%9D%E6%8C%81%E3%81%99%E3%82%8B%0A++++out+property%3CSize%3E+window-size%3A+%7B+width%3A+root.width%2C+height%3A+root.height+%7D%3B%0A%0A++++%2F%2F+%E4%B8%8A%E8%A8%98%E3%81%AE%E3%82%A6%E3%82%A3%E3%83%B3%E3%83%89%E3%82%A6%E3%81%AE%E3%82%B5%E3%82%A4%E3%82%BA%E3%81%AB%E5%A4%89%E6%9B%B4%E3%81%8C%E3%81%82%E3%81%A3%E3%81%9F%E3%82%89%E3%80%81%E3%83%A9%E3%83%99%E3%83%AB%E3%81%AB%E3%82%A6%E3%82%A3%E3%83%B3%E3%83%89%E3%82%A6%E3%81%AE%E6%A8%AA%E5%B9%85%E3%81%A8%E9%AB%98%E3%81%95%E3%82%92%E8%A1%A8%E7%A4%BA%E3%81%99%E3%82%8B%0A++++changed+window-size+%3D%3E+%7B+label.text+%3D+%22%5C%7Broot.window-size.width+%2F+1px%7D+x+%5C%7Broot.window-size.height+%2F+1px%7D%22%3B+%7D%0A%0A++++label+%3A%3D+Text+%7B%7D%0A%7D%0A&style=native) で試してみます。

```qml
// サイズを保持する構造体を定義する
export struct Size {
    width: length,
    height: length,
}

export component AppWindow inherits Window {
    // デフォルトの大きさを指定
    preferred-width: 1280px;
    preferred-height: 720px;

    // ウィンドウのサイズを Size 構造体型のプロパティで保持する
    out property<Size> window-size: { width: root.width, height: root.height };

    // 上記のウィンドウのサイズに変更があったら、ラベルにウィンドウの横幅と高さを表示する
    changed window-size => { label.text = "\{root.window-size.width / 1px} x \{root.window-size.height / 1px}"; }

    label := Text {}
}
```

動作は以前と同じですが、プロパティバインディングではなく、Slint 1.8.0 で導入されたばかりの [プロパティの変更ハンドラ](https://docs.slint.dev/latest/docs/slint/src/language/syntax/properties#change-callbacks) で表示を更新しています。


### コールバックで通知する

.slint 側でコールバックを定義し、ウィンドウサイズが変更されたら呼ぶようにし、Rust 側でそれを受けて処理をするようにします。

[SlintPad](https://slintpad.com/?snippet=%2F%2F+%E3%82%B5%E3%82%A4%E3%82%BA%E3%82%92%E4%BF%9D%E6%8C%81%E3%81%99%E3%82%8B%E6%A7%8B%E9%80%A0%E4%BD%93%E3%82%92%E5%AE%9A%E7%BE%A9%E3%81%99%E3%82%8B%0Aexport+struct+Size+%7B%0A++++width%3A+length%2C%0A++++height%3A+length%2C%0A%7D%0A%0Aexport+component+AppWindow+inherits+Window+%7B%0A++++%2F%2F+%E3%83%87%E3%83%95%E3%82%A9%E3%83%AB%E3%83%88%E3%81%AE%E5%A4%A7%E3%81%8D%E3%81%95%E3%82%92%E6%8C%87%E5%AE%9A%0A++++preferred-width%3A+1280px%3B%0A++++preferred-height%3A+720px%3B%0A%0A++++%2F%2F+%E3%82%A6%E3%82%A3%E3%83%B3%E3%83%89%E3%82%A6%E3%81%AE%E3%82%B5%E3%82%A4%E3%82%BA%E3%82%92+Size+%E6%A7%8B%E9%80%A0%E4%BD%93%E5%9E%8B%E3%81%AE%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E3%81%A7%E4%BF%9D%E6%8C%81%E3%81%99%E3%82%8B%0A++++out+property%3CSize%3E+window-size%3A+%7B+width%3A+root.width%2C+height%3A+root.height+%7D%3B%0A%0A++++%2F%2F+%E3%82%A6%E3%82%A3%E3%83%B3%E3%83%89%E3%82%A6%E3%81%AE%E3%82%B5%E3%82%A4%E3%82%BA%E3%81%8C%E5%A4%89%E6%9B%B4%E3%81%95%E3%82%8C%E3%81%9F%E9%9A%9B%E3%81%AE%E3%82%B3%E3%83%BC%E3%83%AB%E3%83%90%E3%83%83%E3%82%AF%0A++++callback+window-size-changed%3B%0A%0A++++%2F%2F+%E4%B8%8A%E8%A8%98%E3%81%AE%E3%82%A6%E3%82%A3%E3%83%B3%E3%83%89%E3%82%A6%E3%81%AE%E3%82%B5%E3%82%A4%E3%82%BA%E3%81%AB%E5%A4%89%E6%9B%B4%E3%81%8C%E3%81%82%E3%81%A3%E3%81%9F%E3%82%89%E3%80%81%E3%82%B3%E3%83%BC%E3%83%AB%E3%83%90%E3%83%83%E3%82%AF%E3%82%92%E5%91%BC%E3%81%B6%0A++++changed+window-size+%3D%3E+%7B+window-size-changed%28%29%3B+%7D%0A%7D%0A&style=native) で以下のコードを試します。

```qml
// サイズを保持する構造体を定義する
export struct Size {
    width: length,
    height: length,
}

export component AppWindow inherits Window {
    // デフォルトの大きさを指定
    preferred-width: 1280px;
    preferred-height: 720px;

    // ウィンドウのサイズを Size 構造体型のプロパティで保持する
    out property<Size> window-size: { width: root.width, height: root.height };

    // ウィンドウのサイズが変更された際のコールバック
    callback window-size-changed;

    // 上記のウィンドウのサイズに変更があったら、コールバックを呼ぶ
    changed window-size => { window-size-changed(); }
}
```

今回は、.slint 上には表示されません。 main.rs 側で変更を検知し、処理を行います。

```rust
use std::error::Error;

slint::include_modules!();

fn main() -> Result<(), Box<dyn Error>> {
    let ui = AppWindow::new()?;

    // ウィンドウの初期サイズをプロパティから取得する
    let size = ui.get_window_size();
    println!("Initial Window Size: {}x{}", size.width, size.height);

    let ui_handle = ui.as_weak().unwrap();
    // ウィンドウサイズが変更されたら処理を行う
    ui.on_window_size_changed(move || {
        let size = ui_handle.get_window_size();
        // 標準出力に出す
        println!("Window Size Changed: {}x{}", size.width, size.height);
    });

    ui.run()?;

    Ok(())
}
```

ソースコードの一式を以下で公開したので、手元で試したい人は参考にしてください。

https://github.com/task-jp/slint-window-size/tree/main


# おわりに

今回は、.slint で作成したウィンドウのサイズとその変更を、バックエンドの Rust 側で取得や検知する方法を調査しました。

明日は @hermit4 さんがによる [Slint言語入門(4) 構文について(続2)](https://qiita.com/hermit4/items/8e4de709b831e294f8d1) です。お楽しみに！