UIレイアウトエンジンYogaをOpenSiv3Dへ導入してみた
Siv3D Advent Calendar 2023の23日目の記事です。
たまたまUnityのUI Toolkitを調べていたらそこにYogaが使われている[1]ことを知ったので、仕組みの勉強ついでにSiv3Dにも組み込んでみました。
Yogaとは
Facebookが作っている、オープンソースのレイアウトエンジンです。
Facebook系のフレームワークであるReact Native, ComponentKit, Lithoのレイアウト機能の実装部分を担っており、コア部分は C++ で実装されています。
ちなみに、ホームページにはPlaygroundがあるので実際に触って試してみることができます。
Siv3Dへ組み込み
ソースコードをGitHubに公開しました。
コンパイルにはvcpkgをインストールする必要があります。Yogaもvcpkgでパッケージ化されているのですが、そのYogaのバージョンが古すぎるうえに、READMEに
Yoga is additionally part of the vcpkg collection of ports maintained by Microsoft and community contributors. If the version is out of date, please create an issue or pull request on the vcpkg repository.
意訳:vcpkg上のバージョンが古かったらIssueかPull Requestを送ってね!
と書かれていていたので今回リポジトリにはsubmoduleで紐づけています。
さて、実装したのは主に4つです。
-
Widget
: 灰色の四角が表示されるシンプルなウィジェット -
Label
: 色付きテキストが表示されるウィジェット -
LayoutTree
: レイアウトを計算,処理するツリー -
WidgetTreeEditor
: ウィジェットを編集するエディタ
使い方は次のとおりです。
-
Widget
,Label
を組み合わせて作りたいUIを作成 - 作成したUIから、
LayoutTree
を作成する -
LayoutTree::calculateLayout
でレイアウトを計算 -
Widget::draw()
で描画
サンプル1:プログラムからUI生成
#include <Siv3D.hpp> // Siv3D v0.6.13
#include "Widget.hpp"
#include "Label.hpp"
#include "LayoutTree.hpp"
void Main()
{
Scene::SetBackground(Palette::White);
Window::SetStyle(WindowStyle::Sizable);
// UIを作成
auto rootWidget = std::make_shared<Widget>();
// パディングを30pxに設定
rootWidget->style().setPadding(facebook::yoga::Edge::All, facebook::yoga::StyleLength::points(30));
// FlexDirectionをRow(水平方向)に設定
rootWidget->style().setFlexDirection(facebook::yoga::FlexDirection::Row);
// 折返しを有効にする
rootWidget->style().setFlexWrap(facebook::yoga::Wrap::Wrap);
// 0~9のサイズが変化する子要素を作成
for (int i : Iota(10))
{
auto childWidget = std::make_shared<Widget>();
auto label = std::make_shared<Label>(String{ U'0' + i }, Palette::Black);
// labelを中央に配置
childWidget->style().setJustifyContent(facebook::yoga::Justify::Center);
childWidget->style().setAlignItems(facebook::yoga::Align::Center);
// childWidgetのサイズを少しずつ大きくする
auto size = facebook::yoga::StyleLength::points(50 + 10 * i);
childWidget->style().setDimension(facebook::yoga::Dimension::Width, size);
childWidget->style().setDimension(facebook::yoga::Dimension::Height, size);
childWidget->children.emplace_back(std::move(label));
rootWidget->children.emplace_back(std::move(childWidget));
}
// UIからLayoutTreeを構築
LayoutTree tree{ rootWidget };
while (System::Update())
{
// レイアウトを計算
tree.calculateLayout(Scene::Size());
// ウィジェットを描画
rootWidget->draw();
}
}
WidgetTreeEditor
でUI編集
サンプル2:#include <Siv3D.hpp> // Siv3D v0.6.13
#include "imgui_impl_s3d/DearImGuiAddon.hpp"
#include "Widget.hpp"
#include "Label.hpp"
#include "LayoutTree.hpp"
#include "WidgetTreeEditor.hpp"
void Main()
{
Addon::Register<DearImGuiAddon>(U"ImGui");
Scene::SetBackground(Palette::White);
Window::SetStyle(WindowStyle::Sizable);
// 空のUIを作成
auto rootWidget = std::make_shared<Widget>();
// UIからLayoutTreeを構築
LayoutTree tree{ rootWidget };
// UIを編集するエディタ
WidgetTreeEditor editor{ rootWidget };
while (System::Update())
{
// レイアウトを計算
tree.calculateLayout(Scene::Size());
// ウィジェットを描画
rootWidget->draw();
// UIを編集
if (editor.update())
{
// 変更があったらLayoutTreeを再構築
tree.construct(rootWidget);
}
}
}
Yogaコード解読
公式ドキュメントにAPIの解説があると心強いですが、レイアウトの挙動に関する情報のみだったので諦めてソースコードを読んでいくことに。それでもコメントで丁寧なAPIに関する説明がなされていたので、想像よりは楽でした。
構成
Yogaのコア部分はC++ですが、フロントは様々な言語(Java,JSなど)で実装されています。
コア部分はC言語の関数群でラップされており、C言語の関数を呼び出せる言語であれば容易に実装ができるようになっているようです。
Javaの関数例
次に、Yogaを構成する主なコンポーネントについて紹介します。
YGNode
レイアウトの木構造を構成するのに必要なノードです。
- ノード種別 (
Default
orText
) - 親子関係
- スタイル (
YGNodeStyle
) - レイアウトの計算結果 (
YGNodeLayout
) - コンテキスト (自由に設定可能な
void
ポインタ) - コールバック関数
...などが保存されています。
YGNode.h
YGNodeLayout.h
YGNodeStyle
ノードのスタイルに関する情報が入っており、この情報を元にレイアウトが決定されます。
YGNodeStyle.h
YGNodeCalculateLayout
YGNodeStyle
から与えられた情報をもとに、各ノードの位置とサイズを計算する関数です。
availableWidth
とavailableHeight
に表示領域のサイズ(例:ウィンドウのサイズ)を与えると、例えば画面いっぱいに表示したいときにこのサイズが使われます。
YGNodeCalculateLayout (YGNode.h)
おわりに
やっぱり実際にいじってみるのは良いですね。おかげでUI Toolkitに対する深い理解が得られました。[2] 今後はUIのレイアウトをファイルに保存できるようにしたり、YogaのPlaygroundのようにWebでUIを編集できるようにしたいなと思っています。
また、コードリーディングにおいては先人の解説記事も参考にしました (感謝...!)
-
https://docs.unity3d.com/ja/2023.2/Manual/UIE-LayoutEngine.html ↩︎
-
代償として実装にかかる時間を浪費したのはさておき ↩︎
Discussion