軽量ブラウザ Ultralight を OpenSiv3D で動かす

![20191225-225953-457.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100367/11531f3a-db25-09f3-8ffa-d59a310695ce.png)


# Ultralight とは
https://ultralig.ht/

Ultralight とはゲームやデスクトップアプリなどに **HTML UI を組み込むための C++ ライブラリ**です。
HTML 描画が主目的のため Web ブラウザとしての機能は少なめ（動画や音の再生に非対応）ですが、その代わりに**組み込みの容易さと軽さ**を特徴としています。
使用ライセンスは 非商用利用 or 年間10万ドル未満の商用利用 に限り無料で使うことができます。
ブラウザ部分は WebKit の実装をベースにしており、レンダラは OpenGL, DirectX, Metal をサポートしています。


# 使い方
Ultralight の DirectX 版の実装を OpenSiv3D に組み込んでみました。

https://github.com/agehama/OpenSiv3D/tree/ultralight
ここからクローンして `OpenSiv3D/WindowsDesktop/OpenSiv3D.sln` をビルドすれば動かすことができます。（Windows 限定）


## 例1. URL を読み込んで描画する

```cpp:WebSample1.cpp
#include <Siv3D.hpp> // OpenSiv3D v0.4.2
#include "BrowserRect.hpp"

void Main()
{
	Window::Resize(1280, 720);
	Scene::SetBackground(ColorF(0.8, 0.9, 1.0));

	BrowserRect browser(Scene::Rect().stretched(-50));
	browser.view()->LoadURL("https://qiita.com/advent-calendar/2019/siv3d");

	while (System::Update())
	{
		browser.update();
		browser.drawRounded(10.0);
	}
}
```

`BrowserRect::view()` は Ultralight のビューにアクセスするための関数で、ここから URL や HTML の読み込みなどを行うことができます。
`BrowserRect::update()` でマウス入力などブラウザの更新処理と内部のテクスチャ更新を行います。
`BrowserRect::draw()` で内部のテクスチャを画面に表示します。`drawRounded()` は角丸版です。

![20191225-000908-341.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100367/4300ceaf-81bc-71b3-69f2-a9395152fa0c.png)


## 例2. JavaScript から C++ の関数を呼ぶ
JavaScript から C++ の関数を呼び出すには `JSGlobalObject()` に呼びたい関数を事前に登録しておくことで JavaScript からグローバル関数として呼び出すことができるようになります。
逆に C++ から JavaScript を呼び出すには `JSEval()` 関数に JavaScript の式を渡すことで呼べます。

```cpp:WebSample2.cpp
#include <Siv3D.hpp> // OpenSiv3D v0.4.2
#include "BrowserRect.hpp"

class Text
{
public:
	Text(const String& str, const Vec2& pos):
		str(str), pos(pos)
	{}
	String str;
	Vec2 pos;
};

class Listener : public ultralight::LoadListener
{
public:
	void DropText(const ultralight::JSObject& thisObject, const ultralight::JSArgs& args)
	{
		char* str = static_cast<ultralight::String>(args[0].ToString()).utf8().data();
		texts.emplace_back(Unicode::FromUTF8(str), RandomVec2({ 0,Scene::Width() }, 0));
	}

	void OnDOMReady(ultralight::View* caller) override
	{
		ultralight::SetJSContext(caller->js_context());
		ultralight::JSObject global = ultralight::JSGlobalObject();

		using ultralight::JSCallback;
		global["DropText"] = BindJSCallback(&Listener::DropText);
	}

	Array<Text> texts;
};

void Main()
{
	Window::Resize(1280, 720);
	Scene::SetBackground(ColorF(0.8, 0.9, 1.0));
	Font font(50, Typeface::Heavy);

	BrowserRect browser(Scene::Rect().stretched(-100));

	Listener listener;
	browser.view()->set_load_listener(&listener);

	browser.view()->LoadHTML(R"(
<html>
<style type="text/css">
body { font-family: sans-serif; text-align: center; }
</style>
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
<body>
<form class="w3-container w3-card-4" action="/action_page.php">
  <h2 class="w3-text-blue">OpenSiv3D Web Sample</h2>
  <p>input some characters</p>
  <p><input id="textBox" class="w3-input w3-border" name="text1" type="text" value=""></p>
  <p><button class="w3-btn w3-blue w3-xxxlarge" onclick="DropText(document.getElementById('textBox').value);">Go!</button></p>
</form>
</body>
</html>
)");

	while (System::Update())
	{
		browser.update();
		browser.draw();

		for (auto& text : listener.texts)
		{
			text.pos += Vec2(0, 0.5);
			font(text.str).draw(text.pos, RandomHSV({ 0,360 }, { 1,1 }, { 1,1 }));
		}
	}
}

```

テキストを入力してボタンを押すと上からテキストが点滅しながら降ってくるサンプルです。
（描画が微妙にバグっているのとテキスト入力があまりスムーズにいかないバグがありますがおそらくどちらも雑に組み込んでしまったのが原因です…）

![20191225-213703-764.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100367/817c459b-4b52-d3b9-9b88-1ab8f4db564b.png)


Ultralight の使い方の詳細な説明は https://docs.ultralig.ht/docs にあります。

## まとめ
現在の Ultralight はまだ CSS などの互換性が甘いためどうしても描画の粗が気になってしまいます。
しかしこれでも v1.0.0 に比べるとかなり良くなった方で、日々開発も進んでいるようなので今後に期待ですね。

いずれはこの記事のようにゲーム中の GUI 実装の選択肢としても使えるようになると嬉しいですね。
[Nintendo Switchの中ではReactが動いてる！Nintendo eShop開発秘話を聞いてきた - HTML5Experts.jp](https://html5experts.jp/shumpei-shiraishi/24538/)