パッケージ arsd-official : simpledisplay の使い方

# はじめに
[D言語](https://dlang.org/)のポピュラーな[DUBパッケージ](https://code.dlang.org/)の中に[arsd-official](https://code.dlang.org/packages/arsd-official)があります。
この記事では、arsd-officialのサブパッケージ[simpledisplay](https://github.com/adamdruppe/arsd/blob/master/simpledisplay.d)の使い方を紹介します。

# 開発環境
* Windows 10
* [DMD](https://dlang.org/download.html) v2.099.0
* [DUB](https://github.com/dlang/dub) version 1.28.0
* [arsd-official](https://code.dlang.org/packages/arsd-official) Version 10.7.0

# その１－ウィンドウ画面作成
[simpledisplay](https://github.com/adamdruppe/arsd/blob/master/simpledisplay.d)は、ウィンドウ画面の作成や表示処理が簡単に実装できるような機能が提供されています。
まずは、ウィンドウ作成に必要な最小限のソースコード実装例です。
[この記事](https://qiita.com/simd_nyan/items/428372ef698a586cd01d)を参考に[Single-file packages](https://dub.pm/advanced_usage.html)で実装しました。

```D:sample1.d
/+ dub.sdl:
	name "sample1"
	dependency "arsd-official:simpledisplay" version="~>10.7.0"
+/
// dub run --single sample1.d
import arsd.simpledisplay;
void main() {
	auto window = new SimpleWindow(500, 500, "Sample1");
	window.eventLoop(0);
}
```

これだけのソースコードでウィンドウの作成とイベント処理ができます。
`SimpleWindow`でウィンドウを作成します。ウィンドウの幅、高さ、タイトルを引数にセットしています。
`eventLoop`はウィンドウのイベント処理を行います。
引数に`0`のみをセットした場合、`simpledisplay`が用意したデフォルトのイベント処理を行います。

以下はコンパイルと実行結果です。

```text:コマンドプロンプト
D:\Dev> dub run --single sample1.d
parsePackageRecipe dub.sdl
Fetching arsd-official 10.7.0 (getting selected version)...
Performing "debug" build using D:\Dev\dmd2\windows\bin64\dmd.exe for x86_64.
arsd-official:color_base 10.7.0: building configuration "library"...
arsd-official:simpledisplay 10.7.0: building configuration "normal"...
sample1 ~master: building configuration "application"...
Linking...
Running sample1.exe
```
ウィンドウの最小化、最大化、サイズ変更、閉じるなどのイベントは`simpledisplay`が処理してくれています。
![sample1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675901/3c959d11-f1a8-0ad4-f11b-75bb37e0f29c.png)

# その２－イベント処理の実装
`eventLoop`を使って、イベント処理を実装します。
[simpledisplay](https://github.com/adamdruppe/arsd/blob/master/simpledisplay.d)のソースコード内のサンプルコードを参考に実装しました。
他にもサンプルコードが紹介されているので、興味がある方はソースコードを参照してください。

```D:sample2.d
/+ dub.sdl:
	name "sample2"
	dependency "arsd-official:simpledisplay" version="~>10.7.0"
+/
// dub run --single sample2.d
import std.conv;
import arsd.simpledisplay;

void main() {
	auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d");
	int y = 0;
	void addLine(string text) {
		auto painter = window.draw();
		if(y + painter.fontHeight >= window.height) {
			painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight);
			y -= painter.fontHeight;
		}
		painter.outlineColor = Color.green;
		painter.fillColor = Color.black;
		painter.drawRectangle(Point(0, y), window.width, painter.fontHeight);
		painter.outlineColor = Color.white;
		painter.drawText(Point(10, y), text);
		y += painter.fontHeight;
	}
	window.eventLoop(1000,
		(){
			addLine("Timer went off!");
		},
		(KeyEvent event){
			addLine(to!string(event));
		},
		(MouseEvent event){
			addLine(to!string(event));
		},
		(dchar ch){
			addLine(to!string(ch));
		}
	);
}
```

`eventLoop`内の`(){～}`は、タイマーイベント処理です。
`eventLoop`の第１引数に正数をセットすると、その時間間隔ごとにタイマーイベントが発生します。単位はミリ秒です。

`(KeyEvent event){～}`は、キーボード操作のイベント処理です。
キーを押した場合、キーを離した場合にイベントが発生します。

`(MouseEvent event){～}`は、マウス操作のイベント処理です。
ウィンドウ上でマウスを動かした場合、クリックした場合にイベントが発生します。

` (dchar ch){～}`では、キーボードから入力された文字ごとの処理を実装します。

以下はコンパイルと実行結果です。

```text:コマンドプロンプト
D:\Dev> dub run --single sample2.d
parsePackageRecipe dub.sdl
Performing "debug" build using D:\Dev\dmd2\windows\bin64\dmd.exe for x86_64.
arsd-official:color_base 10.7.0: target for configuration "library" is up to date.
arsd-official:simpledisplay 10.7.0: target for configuration "normal" is up to date.
sample2 ~master: building configuration "application"...
Linking...
To force a rebuild of up-to-date targets, run again with --force.
Running sample2.exe
```

どのイベントでも`addLine`関数が呼び出されています。
`addLine`関数内の`window.draw()`で、ウィンドウの描画処理に必要な`painter`(`struct ScreenPainter`)を取得しています。
`ScreenPainter`のデストラクタが呼び出される際にウィンドウの表示を更新する仕組みとのことです。

以下の実行結果を補足します。

* 1000ミリ秒ごとに`Timer went off!`が出力されています。
* キーボードの`a`を押した際、離した際に`KeyEvent`が発生しています。
* ` (dchar ch){～}`で引数`ch`に、押したキーである`a`がセットされて呼び出されています。
* マウス操作のイベント`MouseEvent`も発生しています。
マウス動かした際に`MouseEvent(motion, ～`、クリックした際に`MouseEvent(buttonPressed, ～`と`MouseEvent(buttonReleased, ～`が発生しています。

![sample2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675901/3ea4029d-bad0-5702-a615-1c0c834ebaaa.png)

# その３－OS固有のイベント処理の実装
その２では、タイマー、キーボード、マウスイベントの処理方法を紹介しました。
その３では、その他のウィンドウイベントの処理実装例になります。
また、`arsd-official`は汎用的なパッケージため、`Windows`や`X11`のいずれでも使用可能ですが、OS固有のイベントを処理できる仕組みも用意されています。

```D:sample3.d
/+ dub.sdl:
	name "sample3"
	dependency	"arsd-official:simpledisplay" version="~>10.7.0"
	dependency	"arsd-official:image_files"   version="~>10.7.0"
+/
// dub run --single sample3.d
import core.sys.windows.windows;
import std.conv;
import arsd.image;
import arsd.simpledisplay;

void main()
{
	auto window = new SimpleWindow(Size(500, 500), "Arsd Viewer");
	MemoryImage image;
	void draw(int width, int height){
		auto painter = window.draw();
		painter.clear();
		if ( image !is null ){
			if ( (cast(double)width) / image.width > (cast(double)height) / image.height ){
				width  = image.width  * height / image.height;
			} else {
				height = image.height * width  / image.width;
			}
			TrueColorImage winImage = image.imageResize(width, height);
			painter.drawImage(Point(0, 0), Image.fromMemoryImage(winImage));
		}
	}
	window.windowResized = (int width, int height){
		draw(width, height);
	};
	window.handleNativeEvent =
		delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int r){
			switch ( msg ){
				case WM_DROPFILES:
					const MAX_SIZE = 256;
					wchar[] fileName = new wchar[MAX_SIZE];
					int nSize = DragQueryFile(cast(HDROP)wParam, 0, fileName.ptr, cast(int)(fileName.length));
					try {
						image = loadImageFromFile(fileName[0 .. nSize].to!string);
					} catch (Exception e){
					}
					draw(window.width, window.height);
					r = 1;
					return ( 0 );
				default:
			}
			return ( 1 );
		};
	draw(window.width, window.height);
	window.eventLoop(0);
}
```

ウィンドウの描画イベントを処理するために`draw`関数を用意しました。
`draw`関数では、`MemoryImage image`に格納された画像情報をウィンドウ画面に表示します。

`window.windowResized`は、ウィンドウのサイズを変更した際に発生するイベントです。`draw`関数を呼び出すように実装しました。

`window.handleNativeEvent`で、OS固有イベントの処理方法を実装します。
今回は、ファイルをドラッグ＆ドロップした際(`Windows`の`WM_DROPFILES`イベント)に、ファイルを`MemoryImage`に読み込み、`draw`関数を呼び出します。
`handleNativeEvent`を実装する際の注意点として、自分で処理したイベントは戻り値`0`、自分で処理しないイベントは戻り値`1`を返すことです。
戻り値`1`を返すことで、`simpledisplay`が用意したデフォルトのイベント処理を行います。
戻り値`0`を返すとイベント処理を何も行わないため、ご注意ください。

以下はコンパイルと実行結果です。

```text:コマンドプロンプト
D:\Dev> dub run --single sample3.d
parsePackageRecipe dub.sdl
Performing "debug" build using D:\Dev\dmd2\windows\bin64\dmd.exe for x86_64.
arsd-official:color_base 10.7.0: target for configuration "library" is up to date.
arsd-official:bmp 10.7.0: target for configuration "library" is up to date.
arsd-official:imageresize 10.7.0: target for configuration "library" is up to date.
arsd-official:jpeg 10.7.0: target for configuration "library" is up to date.
arsd-official:png 10.7.0: target for configuration "library" is up to date.
arsd-official:svg 10.7.0: target for configuration "library" is up to date.
arsd-official:image_files 10.7.0: target for configuration "library" is up to date.
arsd-official:simpledisplay 10.7.0: target for configuration "normal" is up to date.
sample3 ~master: building configuration "application"...
Linking...
To force a rebuild of up-to-date targets, run again with --force.
Running sample3.exe
```

以下は、ファイル（Qiitaのロゴ画像）をウィンドウにドラッグ＆ドロップした際の結果です。
ウィンドウサイズを変えると、ロゴマークが拡大縮小表示されます。

![sample3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675901/75ace79c-f754-4f49-ee27-b92918a0998a.png)

# ウィンドウイベント一覧(Windows用)
用意されている`WindowS`用のウィンドウイベントをまとめました。
`X11`用のウィンドウイベントなど、さらに詳細を知りたい方は、ソースコードを参照してください。

|イベント関数|説明|
|:--|:--|
|void delegate(KeyEvent ke) handleKeyEvent|キーボード操作イベント|
|void delegate(dchar c) handleCharEvent|キーボードで押した文字処理イベント|
|void delegate() handlePulse|タイマーイベント|
|void delegate(bool) onFocusChange|ウィンドウのフォーカス有効無効イベント|
|void delegate() onClosing|ウィンドウを閉じるイベント|
|void delegate() onDestroyed|ウィンドウのリソース開放イベント|
|void delegate(MouseEvent) handleMouseEvent|マウス操作イベント|
|void delegate() paintingFinished|ウィンドウ描画終了時イベント|
|void delegate(int width, int height) windowResized|ウィンドウサイズ変更イベント|

# 更新履歴

* 2021.1.9 初回投稿
* 2022.3.26 開発環境のバージョンを更新。ソースコードを更新。