OpenSiv3DでImGuiを使う

# 追記

OpenSiv3D用のimgui_implの実装が公開されています。
https://twitter.com/telyaino/status/1209112739110514689

# はじめに

OpenSiv3Dで簡単にGUIを作りたいと思ったことはありませんか？
この記事ではImGuiというGUIライブラリを組み込む方法を解説します。

nuklearというGUIライブラリを使った例は、既に記事が書かれていますのでここで紹介しておきます。
[OpenSiv3D+nuklearでGUIを実現する](https://qiita.com/ta_dragon/items/f0448cebb8d669a5ab93)

# ImGuiとは
https://github.com/ocornut/imgui
詳しくはリポジトリを見てもらうのが早いですが、簡潔にまとめると、直感的なコードでGUIを構築できる、マルチプラットフォームなGUIライブラリです。

# 環境
この記事の内容は以下のバージョンを元に解説しています。

- OpenSiv3D 0.3.1
- ImGui 1.66

# 準備
1. [OpenSiv3D](https://github.com/Siv3D/OpenSiv3D)をクローンする。
1. `Dependencies`フォルダーに、要求されているバージョンのboostを配置する。
1. [ImGui](https://github.com/ocornut/imgui/releases)をダウンロードする。

**ImGuiのフォルダからそのまま参照する場合、プロジェクトに含めて、インクルードディレクトリに指定すれば、以下の手順は必要ありません。**

1. `include/ThirdParty`に`imgui`フォルダーを作成する。
1. 以下のファイルを`include/ThirdParty/imgui`にコピーする。 
 - imgui.h
 - imgui_internal.h
 - imconfig.h
 - misc/cpp/imgui_stdlib.h[^1]

1. `include/ThirdParty`に`imgui`フィルターを作成する。
1. `include/ThirdParty/imgui`フィルターに`include/ThirdParty/imgui`フォルダーの中身を全て追加する。

[^1]: InputTextでstd::stringが使えるようになります。

1. `src/ThirdParty`に`imgui`フォルダーを作成する。
1. 以下のファイルを`src/ThirdParty/imgui`にコピーする
 - imgui.cpp
 - imgui_demo.cpp
 - imgui_draw.cpp
 - imgui_widgets.cpp
 - imstb_rectpack.h
 - imstb_textedit.h
 - imstb_truetype.h
 - examples/imgui_impl_dx11.cpp
 - examples/imgui_impl_dx11.h
 - examples/imgui_impl_win32.cpp
 - examples/imgui_impl_win32.h
 - misc/cpp/imgui_stdlib.cpp[^1]

1. `src/ThirdParty`に`imgui`フィルターを作成する。
1. `src/ThirdParty/imgui`フィルターに`src/ThirdParty/imgui`フォルダーの中身を全て追加する。

1. 以下のソースのインクルードディレクトリを修正する。
 - imgui.cpp
 - imgui_demo.cpp
 - imgui_draw.cpp
 - imgui_impl_dx11.cpp
 - imgui_impl_win32.cpp
 - imgui_stdlib.cpp
 - imgui_widgets.cpp

　`#include "imgui.h"` → `#include <imgui/imgui.h>`
　`#include "imgui_internal.h"` → `#include <imgui/imgui_internal.h>`
　`#include "imgui_stdlib.h"` → `#include <imgui/imgui_stdlib.h>`

# 実装

d3dcompilerをリンクする。
**直近のコミットで本家に追記されたため、今後のリリースではこの手順は必要無いと思われます。**
https://github.com/ocornut/imgui/commit/8d58055a5433e1227ebb5e3b8dba80bb00cc239a

```diff:imgui_impl_dx11.cpp
	#include <stdio.h>
	#include <d3d11.h>
	#include <d3dcompiler.h>

+	#pragma comment(lib, "d3dcompiler")
```


初期化とフレーム開始の処理を追記する。

```diff:src/Siv3D/System/CSystem_Windows.cpp
	# include <Siv3D/Cursor.hpp>
	# include <Siv3D/Logger.hpp>

+	#include <imgui/imgui.h>
+	#include <imgui/imgui_impl_win32.h>
+	#include <imgui/imgui_impl_dx11.h>
+	#include "../Graphics/D3D11/CGraphics_D3D11.hpp"
```
```diff:src/Siv3D/System/CSystem_Windows.cpp
		if (!Siv3DEngine::GetAsset()->init())
		{
			return false;
		}

+		ImGui::CreateContext();
+		ImGui_ImplWin32_Init(Siv3DEngine::GetWindow()->getHandle());
+		CGraphics_D3D11* graphics = dynamic_cast<CGraphics_D3D11*>(Siv3DEngine::GetGraphics());
+		ImGui_ImplDX11_Init(graphics->getDevice(), graphics->getContext());

		m_setupState = SetupState::Initialized;

		LOG_INFO(U"✅ Siv3D engine setup completed");

		return true;
	}

	bool CSystem_Windows::update(bool clearGraphics)
	{
		if (!m_updateSucceeded)
		{
			return false;
		}

		m_updateSucceeded = false;

		if (m_setupState == SetupState::Initialized)
		{
			Siv3DEngine::GetWindow()->show(true);

+			ImGui_ImplDX11_NewFrame();
+			ImGui_ImplWin32_NewFrame();
+			ImGui::NewFrame();

			m_setupState = SetupState::Displayed;

			Siv3DEngine::GetCursor()->applyStyleImmediately(CursorStyle::Default);
		}

```
```diff:src/Siv3D/System/CSystem_Windows.cpp
		if (!Siv3DEngine::GetProfiler()->beginFrame())
		{
			return false;
		}

+		ImGui_ImplDX11_NewFrame();
+		ImGui_ImplWin32_NewFrame();
+		ImGui::NewFrame();

		const bool onDeviceChange = m_onDeviceChange.exchange(false);

```

フレーム終了(レンダリング)の処理を追記する。

```diff:src/Siv3D/Grapgics/D3D11/CGraphics_D3D11.cpp
	# include "../../Siv3DEngine.hpp"
	# include "CGraphics_D3D11.hpp"

+	#include <imgui/imgui.h>
+	#include <imgui/imgui_impl_dx11.h>
```
```diff:src/Siv3D/Grapgics/D3D11/CGraphics_D3D11.cpp
	bool CGraphics_D3D11::flush(bool clearGraphics)
	{
		m_renderer2D->flush(clearGraphics);

+		ImGui::Render();
+		ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());

		m_renderTarget->resolve();

		return true;
	}
```

アプリ終了時の処理を追記する。

```diff:src/Siv3D/Siv3DEngine.cpp
	# include "Script/IScript.hpp"
	# include "Asset/IAsset.hpp"

+	#include <imgui/imgui.h>
+	#include <imgui/imgui_impl_win32.h>
+	#include <imgui/imgui_impl_dx11.h>
```
```diff:src/Siv3D/Siv3DEngine.cpp
	Siv3DEngine::~Siv3DEngine()
	{
+		ImGui_ImplDX11_Shutdown();
+		ImGui_ImplWin32_Shutdown();
+		ImGui::DestroyContext();

		m_asset.release();
		m_script.release();
```

ウィンドウプロシージャを編集する。
ImGuiにウィンドウメッセージを渡し、Siv3Dのテキスト入力処理を削除する。(コメントアウトで構いません)

```diff:src/Siv3D/Window/WindowProc.cpp
	# include "../TextInput/ITextInput.hpp"
	# include "../TextInput/CTextInput_Windows.hpp"

+	#include <imgui/imgui.h>
+	#include <imgui/imgui_impl_win32.h>

+	IMGUI_IMPL_API LRESULT  ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

namespace s3d
{
	namespace detail
	{
		LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
		{

+			if (ImGui_ImplWin32_WndProcHandler(hWnd, message, wParam, lParam)) {
+				return 1;
+			}
			
-			if (auto textinput = dynamic_cast<CTextInput_Windows*>(Siv3DEngine::GetTextInput()))
-			{
-				if (textinput->process(message, wParam, &lParam))
-				{
-					return 0;
-				}
-			}
```
```diff:src/Siv3D/Window/WindowProc.cpp
	case WM_CHAR:
	{
-		Siv3DEngine::GetTextInput()->pushChar(static_cast<uint32>(wParam));

		return 0;
	}
	case WM_UNICHAR:
	{
		if (wParam == UNICODE_NOCHAR)
		{
			return true;
		}

-		Siv3DEngine::GetTextInput()->pushChar(static_cast<uint32>(wParam));

		return 0;
	}
```

# ライブラリを使う
まずは通常通り、OpenSiv3Dのプロジェクトを作成します。
その後、インクルードディレクトリやライブラリディレクトリを、ここでクローンしたディレクトリに変更してください。

## サンプル
### デモウィンドウの表示
```cpp:Main.cpp
# include <Siv3D.hpp> // OpenSiv3D v0.3.1
# include <imgui/imgui.h>

void Main()
{
	Graphics::SetBackground(ColorF(0.8, 0.9, 1.0));

	while (System::Update())
	{
		ImGui::ShowDemoWindow();
	}
}
```
### 日本語入力
任意のフォントファイルをフォルダに配置してから実行してください。（この例ではmplus-1p-regular.ttfを配置しています）

範囲の指定に
`io.Fonts->GetGlyphRangesJapanese()`
等がありますが、以下のように全範囲を指定しても問題無いようです。

```cpp:Main.cpp
# include <Siv3D.hpp> // OpenSiv3D v0.3.1
# include <imgui/imgui.h>
# include <imgui/imgui_stdlib.h>

void Main()
{
	Graphics::SetBackground(ColorF(0.8, 0.9, 1.0));

	ImGuiIO& io = ImGui::GetIO();
	static const ImWchar ranges[] = { 0x0020, 0xFFFF, 0 };
	io.Fonts->AddFontFromFileTTF("mplus-1p-regular.ttf", 20.0f, NULL, &ranges[0]);

	std::string text;

	while (System::Update())
	{
		ImGui::InputText(u8"テキスト", &text);
	}
}
```

# 既知の不具合
変換候補がキャレットの位置ではない場所に表示される。