🎰

Siv3D | ゲーム UI テンプレート集

2020/10/10に公開

1. プログレスバー

# include <Siv3D.hpp> // OpenSiv3D v0.4.3

namespace GameUIToolkit
{
	class ProgressBar
	{
	private:

		RectF m_rect = Rect(0);

		ColorF m_backgroundColor = ColorF(0.25);

		Array<std::pair<double, ColorF>> m_barColors = {
			{ 1.0, ColorF(0.1, 0.8, 0.2) }
		};

		double m_round = 0.0;

		ColorF getBarColor(double progress) const
		{
			ColorF result = m_barColors.front().second;

			for (auto& c : m_barColors)
			{
				if (progress < c.first)
				{
					result = c.second;
				}
				else
				{
					break;
				}
			}

			return result;
		}

	public:

		ProgressBar() = default;

		explicit ProgressBar(const RectF& rect, double round = 0.0)
			: ProgressBar(rect, ColorF(0.25), { { 1.0, ColorF(0.1, 0.8, 0.2) } }, round) {}

		ProgressBar(const RectF& rect, const ColorF& backgroundColor, const ColorF& barColor, double round = 0.0)
			: ProgressBar(rect, backgroundColor, { { 1.0, barColor } }, round) {}

		ProgressBar(const RectF& rect, const ColorF& backgroundColor, const Array<std::pair<double, ColorF>>& barColors, double round = 0.0)
			: m_rect(rect)
			, m_backgroundColor(backgroundColor)
			, m_barColors(barColors)
			, m_round(round)
		{
			m_barColors.sort_by([](const auto& a, const auto& b) { return a.first > b.first; });
		}

		// バーを描く
		const ProgressBar& draw(double value, double maxValue) const
		{
			const double progress = maxValue ? Math::Saturate(value / maxValue) : 1.0;
			const RectF innnerRect = m_rect.stretched(-1);
			const RectF innnerRectBar(innnerRect.pos, innnerRect.w * progress, innnerRect.h);

			if (m_round == 0.0)
			{
				m_rect.draw(m_backgroundColor);
				innnerRectBar.draw(getBarColor(progress));
			}
			else
			{
				m_rect.rounded(m_round).draw(m_backgroundColor);

				if (innnerRectBar.w)
				{
					const Polygon bar = innnerRectBar.asPolygon();
					const RoundRect innnerRoundRect = innnerRect.rounded(m_round - 1);
					const Polygon clip = innnerRoundRect.asPolygon();

					if (const auto g = Geometry2D::And(bar, clip))
					{
						g.front().draw(getBarColor(progress));
					}
				}
			}

			return *this;
		}

		// 中央揃えでテキストを描く
		const ProgressBar& withText(const SDFDrawableText& drawableText, double fontSize, const ColorF& textColor, const Vec2& textPosOffset = Vec2(0, 0)) const
		{
			const Size textSize = drawableText.region(fontSize).size;
			const auto& font = drawableText.font;
			const Point textPos = (m_rect.pos + (m_rect.size - textSize) / 2 + textPosOffset).asPoint();
			Graphics2D::SetSDFParameters(font.pixelRange(), 0.2);
			drawableText.draw(fontSize, textPos, ColorF(0.1));
			Graphics2D::SetSDFParameters(font.pixelRange(), 0.0);
			drawableText.draw(fontSize, textPos, textColor);
			return *this;
		}

		// 左揃えでテキストを描く
		const ProgressBar& withLabel(const SDFDrawableText& drawableText, double fontSize, const ColorF& textColor, double offsetX, const Vec2& textPosOffset = Vec2(0, 0)) const
		{
			const Size textSize = drawableText.region(fontSize).size;
			const auto& font = drawableText.font;
			const Point textPos = (Vec2(m_rect.x + offsetX, m_rect.y + (m_rect.h - textSize.y) / 2) + textPosOffset).asPoint();
			Graphics2D::SetSDFParameters(font.pixelRange(), 0.2);
			drawableText.draw(fontSize, textPos, ColorF(0.1));
			Graphics2D::SetSDFParameters(font.pixelRange(), 0.0);
			drawableText.draw(fontSize, textPos, textColor);
			return *this;
		}
	};
}

void Main()
{
	Window::Resize(1280, 720);
	Scene::SetBackground(ColorF(0.5, 0.4, 0.6));
	
	const SDFFont font1(40, Typeface::Bold);
	const SDFFont font2(32, U"example/font/LogoTypeGothic/LogoTypeGothic.otf", FontStyle::Bold);

	// 進度に応じたバーの表示色のカスタマイズ
	const Array<std::pair<double, ColorF>> barColors = {
		{ 0.15, ColorF(0.8, 0.2, 0.0) }, // 15 % 未満は赤
		{ 0.5, ColorF(0.8, 0.6, 0.1) }, // 50 % 未満はオレンジ
		{ 1.0, ColorF(0.1, 0.8, 0.2) }, // それ以外は緑
	};

	// バーの背景色
	constexpr ColorF barBackgroundColor(0.2, 0.8);
	
	// テキストの色
	constexpr ColorF textColor(1.0, 0.95, 0.9);

	while (System::Update())
	{
		const int32 hp = static_cast<int32>(Periodic::Triangle0_1(12s) * 4800);

		GameUIToolkit::ProgressBar(Rect(40, 40, 320, 25), ColorF(0.5), ColorF(0.1, 0.8, 0.2))
			.draw(15, 100);
		GameUIToolkit::ProgressBar(Rect(40, 80, 320, 25), ColorF(0.5), ColorF(0.1, 0.8, 0.2))
			.draw(40, 100);
		GameUIToolkit::ProgressBar(Rect(40, 120, 320, 25), ColorF(0.5), ColorF(0.1, 0.8, 0.2))
			.draw(90, 100);

		GameUIToolkit::ProgressBar(Rect(40, 180, 320, 25), barBackgroundColor, barColors)
			.draw(600, 4800);
		GameUIToolkit::ProgressBar(Rect(40, 220, 320, 25), barBackgroundColor, barColors)
			.draw(2000, 4800);
		GameUIToolkit::ProgressBar(Rect(40, 260, 320, 25), barBackgroundColor, barColors)
			.draw(3200, 4800);

		GameUIToolkit::ProgressBar(Rect(440, 40, 320, 25), barBackgroundColor, ColorF(0.1, 0.8, 0.2))
			.draw(15, 100)
			.withText(font1(U"15%"), 20, textColor);
		GameUIToolkit::ProgressBar(Rect(440, 80, 320, 25), barBackgroundColor, ColorF(0.1, 0.8, 0.2))
			.draw(40, 100)
			.withText(font1(U"40%"), 20, textColor);
		GameUIToolkit::ProgressBar(Rect(440, 120, 320, 25), barBackgroundColor, ColorF(0.1, 0.8, 0.2))
			.draw(90, 100)
			.withText(font1(U"90%"), 20, textColor);

		GameUIToolkit::ProgressBar(Rect(440, 180, 320, 25), barBackgroundColor, barColors)
			.draw(600, 4800)
			.withText(font1(U"600/4800"), 24, textColor);
		GameUIToolkit::ProgressBar(Rect(440, 220, 320, 25), barBackgroundColor, barColors)
			.draw(2000, 4800)
			.withText(font1(U"2000/4800"), 24, textColor);
		GameUIToolkit::ProgressBar(Rect(440, 260, 320, 25), barBackgroundColor, barColors)
			.draw(hp, 4800)
			.withText(font1(U"{}/4800"_fmt(hp)), 24, textColor);

		GameUIToolkit::ProgressBar(Rect(840, 40, 320, 25), barBackgroundColor, ColorF(0.2, 0.9, 0.3), 12.5)
			.draw(1, 100)
			.withText(font2(U"1%"), 20, textColor, Vec2(0, 1));
		GameUIToolkit::ProgressBar(Rect(840, 80, 320, 25), barBackgroundColor, ColorF(0.2, 0.9, 0.3), 12.5)
			.draw(80, 100)
			.withText(font2(U"80%"), 20, textColor, Vec2(0, 1));
		GameUIToolkit::ProgressBar(Rect(840, 120, 320, 25), barBackgroundColor, ColorF(0.2, 0.9, 0.3), 12.5)
			.draw(99, 100)
			.withText(font2(U"99%"), 20, textColor, Vec2(0, 1));

		GameUIToolkit::ProgressBar(Rect(840, 180, 320, 25), barBackgroundColor, barColors, 12.5)
			.draw(600, 4800)
			.withText(font2(U"600/4800"), 25, textColor, Vec2(0, 1));
		GameUIToolkit::ProgressBar(Rect(840, 220, 320, 25), barBackgroundColor, barColors, 12.5)
			.draw(2000, 4800)
			.withText(font2(U"2000/4800"), 25, textColor, Vec2(0, 1));
		GameUIToolkit::ProgressBar(Rect(840, 260, 320, 25), barBackgroundColor, barColors, 12.5)
			.draw(hp, 4800)
			.withText(font2(U"{}/4800"_fmt(hp)), 25, textColor, Vec2(0, 1));

		GameUIToolkit::ProgressBar(Rect(120, 320, 320, 25), barBackgroundColor, barColors)
			.draw(600, 4800)
			.withText(font1(U"600/4800"), 24, textColor)
			.withLabel(font1(U"Lv.50"), 24, textColor, -80);
		GameUIToolkit::ProgressBar(Rect(120, 360, 320, 25), barBackgroundColor, barColors)
			.draw(2000, 4800)
			.withText(font1(U"2000/4800"), 24, textColor)
			.withLabel(font1(U"Lv.50"), 24, textColor, -80);
		GameUIToolkit::ProgressBar(Rect(120, 400, 320, 25), barBackgroundColor, barColors)
			.draw(hp, 4800)
			.withText(font1(U"{}/4800"_fmt(hp)), 24, textColor)
			.withLabel(font1(U"Lv.50"), 24, textColor, -80);

		GameUIToolkit::ProgressBar(Rect(660, 327, 320, 12), barBackgroundColor, barColors)
			.draw(600, 4800)
			.withLabel(font2(U"600/4800"), 14, textColor, 335)
			.withLabel(font2(U"Lv.50"), 25, textColor, -100, Vec2(0, 1));
		GameUIToolkit::ProgressBar(Rect(660, 367, 320, 12), barBackgroundColor, barColors)
			.draw(2000, 4800)
			.withLabel(font2(U"2000/4800"), 14, textColor, 335)
			.withLabel(font2(U"Lv.50"), 25, textColor, -100, Vec2(0, 1));
		GameUIToolkit::ProgressBar(Rect(660, 407, 320, 12), barBackgroundColor, barColors)
			.draw(hp, 4800)
			.withLabel(font2(U"{}/4800"_fmt(hp)), 14, textColor, 335)
			.withLabel(font2(U"Lv.50"), 25, textColor, -100, Vec2(0, 1));

		GameUIToolkit::ProgressBar(Rect(100, 460, 320, 15), barBackgroundColor, barColors)
			.draw(600, 4800)
			.withText(font1(U"600/4800"), 16, textColor)
			.withLabel(font1(U"Lv.50"), 16, textColor, -60);
		GameUIToolkit::ProgressBar(Rect(100, 500, 320, 15), barBackgroundColor, barColors)
			.draw(2000, 4800)
			.withText(font1(U"2000/4800"), 16, textColor)
			.withLabel(font1(U"Lv.50"), 16, textColor, -60);
		GameUIToolkit::ProgressBar(Rect(100, 540, 320, 15), barBackgroundColor, barColors)
			.draw(hp, 4800)
			.withText(font1(U"{}/4800"_fmt(hp)), 16, textColor)
			.withLabel(font1(U"Lv.50"), 16, textColor, -60);

		GameUIToolkit::ProgressBar(Rect(660, 467, 480, 12), barBackgroundColor, barColors, 6)
			.draw(600, 4800)
			.withText(font2(U"600/4800"), 14, textColor)
			.withLabel(font2(U"Lv.50"), 25, textColor, -100, Vec2(0, 1));
		GameUIToolkit::ProgressBar(Rect(660, 507, 480, 12), barBackgroundColor, barColors, 6)
			.draw(2000, 4800)
			.withText(font2(U"2000/4800"), 14, textColor)
			.withLabel(font2(U"Lv.50"), 25, textColor, -100, Vec2(0, 1));
		GameUIToolkit::ProgressBar(Rect(660, 547, 480, 12), barBackgroundColor, barColors, 6)
			.draw(hp, 4800)
			.withText(font2(U"{}/4800"_fmt(hp)), 14, textColor)
			.withLabel(font2(U"Lv.50"), 25, textColor, -100, Vec2(0, 1));
	}
}

2. プログレスバー (カスタム形状)

# include <Siv3D.hpp> // OpenSiv3D v0.4.3

namespace GameUIToolkit
{
	class CustomShapeProgressBar
	{
	private:

		Polygon m_shape;

		Polygon m_shrinkedShape;

		ColorF m_backgroundColor = ColorF(0.25);

		Array<std::pair<double, ColorF>> m_barColors = {
			{ 1.0, ColorF(0.1, 0.8, 0.2) }
		};

		double m_shearedX = 0.0;

		ColorF getBarColor(double progress) const
		{
			ColorF result = m_barColors.front().second;

			for (auto& c : m_barColors)
			{
				if (progress < c.first)
				{
					result = c.second;
				}
				else
				{
					break;
				}
			}

			return result;
		}

	public:

		CustomShapeProgressBar() = default;

		explicit CustomShapeProgressBar(const Polygon& polygon, double shearedX = 0.0)
			: CustomShapeProgressBar(polygon, ColorF(0.25), { { 1.0, ColorF(0.1, 0.8, 0.2) } }, shearedX) {}

		CustomShapeProgressBar(const Polygon& polygon, const ColorF& backgroundColor, const ColorF& barColor, double shearedX = 0.0)
			: CustomShapeProgressBar(polygon, backgroundColor, { { 1.0, barColor } }, shearedX) {}

		CustomShapeProgressBar(const Polygon& polygon, const ColorF& backgroundColor, const Array<std::pair<double, ColorF>>& barColors, double shearedX = 0.0)
			: m_shape(polygon)
			, m_shrinkedShape(polygon.calculateBuffer(-1))
			, m_backgroundColor(backgroundColor)
			, m_barColors(barColors)
			, m_shearedX(shearedX)
		{
			m_barColors.sort_by([](const auto& a, const auto& b) { return a.first > b.first; });
		}

		// バーを描く
		const CustomShapeProgressBar& draw(double value, double maxValue) const
		{
			const double progress = maxValue ? Math::Saturate(value / maxValue) : 1.0;
			const RectF innnerRect = m_shrinkedShape.boundingRect();
			const RectF innnerRectBar(innnerRect.pos, innnerRect.w * progress, innnerRect.h);

			if (m_shearedX == 0.0)
			{
				m_shape.draw(m_backgroundColor);

				if (innnerRectBar.w)
				{
					const Polygon bar = innnerRectBar.asPolygon();

					if (const auto g = Geometry2D::And(bar, m_shrinkedShape))
					{
						g.front().draw(getBarColor(progress));
					}
				}
			}
			else
			{
				const Vec2 center = m_shape.boundingRect().center();
				Transformer2D tr(Mat3x2::Translate(-center).shearedX(m_shearedX).translated(center));

				m_shape.draw(m_backgroundColor);

				if (innnerRectBar.w)
				{
					const Polygon bar = innnerRectBar.asPolygon();

					if (const auto g = Geometry2D::And(bar, m_shrinkedShape))
					{
						g.front().draw(getBarColor(progress));
					}
				}
			}

			return *this;
		}

		// 中央揃えでテキストを描く
		const CustomShapeProgressBar& withText(const SDFDrawableText& drawableText, double fontSize, const ColorF& textColor, const Vec2& textPosOffset = Vec2(0, 0)) const
		{
			const Size textSize = drawableText.region(fontSize).size;
			const auto& font = drawableText.font;
			const auto& rect = m_shape.boundingRect();
			const Point textPos = (rect.pos + (rect.size - textSize) / 2 + textPosOffset).asPoint();
			Graphics2D::SetSDFParameters(font.pixelRange(), 0.2);
			drawableText.draw(fontSize, textPos, ColorF(0.1));
			Graphics2D::SetSDFParameters(font.pixelRange(), 0.0);
			drawableText.draw(fontSize, textPos, textColor);
			return *this;
		}

		// 左揃えでテキストを描く
		const CustomShapeProgressBar& withLabel(const SDFDrawableText& drawableText, double fontSize, const ColorF& textColor, double offsetX, const Vec2& textPosOffset = Vec2(0, 0)) const
		{
			const Size textSize = drawableText.region(fontSize).size;
			const auto& font = drawableText.font;
			const auto& rect = m_shape.boundingRect();
			const Point textPos = (Vec2(rect.x + offsetX, rect.y + (rect.h - textSize.y) / 2) + textPosOffset).asPoint();
			Graphics2D::SetSDFParameters(font.pixelRange(), 0.2);
			drawableText.draw(fontSize, textPos, ColorF(0.1));
			Graphics2D::SetSDFParameters(font.pixelRange(), 0.0);
			drawableText.draw(fontSize, textPos, textColor);
			return *this;
		}
	};
}

void Main()
{
	Window::Resize(1280, 720);
	Scene::SetBackground(ColorF(0.5, 0.4, 0.6));

	const SDFFont font(32, U"example/font/LogoTypeGothic/LogoTypeGothic.otf", FontStyle::Bold);

	// 進度に応じたバーの表示色のカスタマイズ
	const Array<std::pair<double, ColorF>> barColors = {
		{ 0.15, ColorF(0.8, 0.2, 0.0) }, // 15 % 未満は赤
		{ 0.5, ColorF(0.8, 0.6, 0.1) }, // 50 % 未満はオレンジ
		{ 1.0, ColorF(0.1, 0.8, 0.2) }, // それ以外は緑
	};

	// バーの背景色
	constexpr ColorF barBackgroundColor(0.2, 0.8);
	
	// テキストの色
	constexpr ColorF textColor(1.0, 0.95, 0.9);

	// 六角形
	const Polygon shape1({ {0, 12}, {12, 0}, {308, 0}, {320, 12}, {308, 24}, {12, 24} });

	// だんだん大きく
	const Polygon shape2({ {0, 10}, {320, 0}, {320, 24}, {0, 24} });

	// ラベルの場所を細く
	const Polygon shape3({ {-6, 0}, {314, 0}, {314, 16}, {160, 16}, {160, 24}, {6, 24} });

	// 右端だけ丸く
	const Polygon shape4 = Rect(320, 24).rounded(0, 12, 12, 0);

	// 六角形(細長)
	const Polygon shape5({ {0, 6}, {6, 0}, {614, 0}, {620, 6}, {614, 12}, {6, 12} });

	while (System::Update())
	{
		const int32 hp = static_cast<int32>(Periodic::Triangle0_1(12s) * 4800);

		GameUIToolkit::CustomShapeProgressBar(Rect(40, 40, 320, 25).asPolygon(), ColorF(0.5), ColorF(0.1, 0.8, 0.2))
			.draw(600, 4800);
		GameUIToolkit::CustomShapeProgressBar(Rect(40, 120, 320, 25).stretched(-12, 0).asPolygon(), ColorF(0.5), ColorF(0.1, 0.8, 0.2), 1.0)
			.draw(1200, 4800);
		GameUIToolkit::CustomShapeProgressBar(shape1.movedBy(40, 200), ColorF(0.5), ColorF(0.1, 0.8, 0.2))
			.draw(1800, 4800);
		GameUIToolkit::CustomShapeProgressBar(shape2.movedBy(40, 280), ColorF(0.5), ColorF(0.1, 0.8, 0.2))
			.draw(2400, 4800);
		GameUIToolkit::CustomShapeProgressBar(shape3.movedBy(40, 360), ColorF(0.5), ColorF(0.1, 0.8, 0.2), 0.5)
			.draw(3000, 4800);
		GameUIToolkit::CustomShapeProgressBar(shape4.movedBy(40, 440), ColorF(0.5), ColorF(0.1, 0.8, 0.2))
			.draw(3400, 4800);

		GameUIToolkit::CustomShapeProgressBar(Rect(410, 40, 320, 25).asPolygon(), barBackgroundColor, barColors)
			.draw(hp, 4800)
			.withText(font(U"{}/4800"_fmt(hp)), 20, textColor, Vec2(0, 1));
		GameUIToolkit::CustomShapeProgressBar(Rect(410, 120, 320, 25).stretched(-12, 0).asPolygon(), barBackgroundColor, barColors, 1.0)
			.draw(hp, 4800)
			.withText(font(U"{}/4800"_fmt(hp)), 20, textColor, Vec2(0, 1));
		GameUIToolkit::CustomShapeProgressBar(shape1.movedBy(410, 200), barBackgroundColor, barColors)
			.draw(hp, 4800)
			.withText(font(U"{}/4800"_fmt(hp)), 20, textColor, Vec2(0, 1));
		GameUIToolkit::CustomShapeProgressBar(shape2.movedBy(410, 280), barBackgroundColor, barColors)
			.draw(hp, 4800)
			.withLabel(font(U"{}/4800"_fmt(hp)), 20, textColor, 180, Vec2(0, 2));
		GameUIToolkit::CustomShapeProgressBar(shape3.movedBy(410, 360), barBackgroundColor, barColors, 0.5)
			.draw(hp, 4800)
			.withLabel(font(U"{}/4800"_fmt(hp)), 20, textColor, 180, Vec2(0, 15));
		GameUIToolkit::CustomShapeProgressBar(shape4.movedBy(410, 440), barBackgroundColor, barColors)
			.draw(hp, 4800)
			.withText(font(U"{}/4800"_fmt(hp)), 20, textColor, Vec2(0, 1));

		GameUIToolkit::CustomShapeProgressBar(Rect(840, 40, 320, 25).asPolygon(), barBackgroundColor, barColors)
			.draw(hp, 4800)
			.withLabel(font(U"{}/4800"_fmt(hp)), 14, textColor, 330)
			.withLabel(font(U"Lv.50"), 25, textColor, -85, Vec2(0, 1));
		GameUIToolkit::CustomShapeProgressBar(Rect(840, 120, 320, 25).stretched(-12, 0).asPolygon(), barBackgroundColor, barColors, 1.0)
			.draw(hp, 4800)
			.withLabel(font(U"{}/4800"_fmt(hp)), 14, textColor, 330 - 12)
			.withLabel(font(U"Lv.50"), 25, textColor, -85 - 12, Vec2(0, 1));
		GameUIToolkit::CustomShapeProgressBar(shape1.movedBy(840, 200), barBackgroundColor, barColors)
			.draw(hp, 4800)
			.withLabel(font(U"{}/4800"_fmt(hp)), 14, textColor, 330)
			.withLabel(font(U"Lv.50"), 25, textColor, -85, Vec2(0, 1));
		GameUIToolkit::CustomShapeProgressBar(shape2.movedBy(840, 280), barBackgroundColor, barColors)
			.draw(hp, 4800)
			.withLabel(font(U"{}/4800"_fmt(hp)), 14, textColor, 330)
			.withLabel(font(U"Lv.50"), 25, textColor, -85, Vec2(0, 1));
		GameUIToolkit::CustomShapeProgressBar(shape3.movedBy(840, 360), barBackgroundColor, barColors, 0.5)
			.draw(hp, 4800)
			.withLabel(font(U"{}/4800"_fmt(hp)), 20, textColor, 180, Vec2(0, 15))
			.withLabel(font(U"Lv.50"), 25, textColor, -85 + 6, Vec2(0, 1));
		GameUIToolkit::CustomShapeProgressBar(shape4.movedBy(840, 440), barBackgroundColor, barColors)
			.draw(hp, 4800)
			.withLabel(font(U"{}/4800"_fmt(hp)), 14, textColor, 330)
			.withLabel(font(U"Lv.50"), 25, textColor, -85, Vec2(0, 1));

		GameUIToolkit::CustomShapeProgressBar(shape5.movedBy(320, 540), barBackgroundColor, barColors)
			.draw(hp, 4800)
			.withText(font(U"{}/4800"_fmt(hp)), 17, textColor, Vec2(0, 1))
			.withLabel(font(U"Lv.50"), 25, textColor, -100, Vec2(0, 1));
	}
}

3. タイル型のボタン

# include <Siv3D.hpp> // OpenSiv3D v0.4.3

namespace GameUIToolkit
{
	class TileButton
	{
	private:

		static constexpr double Border = 3.0;

		TextureRegion m_icon;

		int32 m_iconSize = 0;

		Font m_font;

		String m_text;

		RectF m_rect;

		Transition m_transitionPressed{ 0.09s, 0.12s };
		
		ColorF m_color1, m_color2, m_color3, m_color4;

		bool m_pressed = false;

		void drawTile(double t) const
		{
			m_rect.draw(m_color1.lerp(m_color2, t));
			m_rect.stretched(Math::Lerp(-Border, 0, t))
				.drawFrame(0.1, Math::Lerp(1, 3, t), m_color3.lerp(m_color4, t));
		}

		void drawIcon(double padding, double t) const
		{
			const Point iconPos = Vec2(m_rect.x + (m_rect.w - m_icon.size.x) / 2,
				m_rect.y + Border + padding + (m_iconSize - m_icon.size.y) / 2.0).asPoint();
			m_icon.draw(iconPos, m_color2.lerp(m_color1, t));
		}

		void drawLabel(double padding, double t) const
		{
			const Size textSize = m_font(m_text).region().size;
			const Point textPos = Vec2(m_rect.x + (m_rect.w - textSize.x) / 2,
				m_rect.y + Border + padding + m_iconSize + padding).asPoint();
			m_font(m_text).draw(textPos, m_color2.lerp(m_color1, t));
		}

	public:

		TileButton() = default;

		// アルファ透過した画像からアイコンを作成
		TileButton(const FilePath& path, int32 iconSize, const Font& font, StringView text, const RectF& rect, const ColorF& color1, const ColorF& color2, const ColorF& color3, const ColorF& color4)
			: TileButton(Texture(path, TextureDesc::Mipped), iconSize, font, text, rect, color1, color2, color3, color4) {}

		// Image からアイコンを作成
		TileButton(const Image& image, int32 iconSize, const Font& font, StringView text, const RectF& rect, const ColorF& color1, const ColorF& color2, const ColorF& color3, const ColorF& color4)
			: TileButton(Texture(image, TextureDesc::Mipped), iconSize, font, text, rect, color1, color2, color3, color4) {}

		// Icon コードからアイコンを作成
		TileButton(uint32 iconCode, int32 iconSize, const Font& font, StringView text, const RectF& rect, const ColorF& color1, const ColorF& color2, const ColorF& color3, const ColorF& color4)
			: TileButton(Texture(Icon::CreateImage(iconCode, iconSize), TextureDesc::Mipped), iconSize, font, text, rect, color1, color2, color3, color4) {}

		// Texture からアイコンを作成
		TileButton(const TextureRegion& textureRegion, int32 iconSize, const Font& font, StringView text, const RectF& rect, const ColorF& color1, const ColorF& color2, const ColorF& color3, const ColorF& color4)
			: m_icon(textureRegion)
			, m_iconSize(iconSize)
			, m_font(font)
			, m_text(text)
			, m_rect(rect) 
			, m_color1(color1)
			, m_color2(color2)
			, m_color3(color3)
			, m_color4(color4) {}

		bool update()
		{
			const bool mouseOver = m_rect.mouseOver();
			bool pushed = false;

			if (mouseOver)
			{
				Cursor::RequestStyle(CursorStyle::Hand);
			}

			if (!m_pressed)
			{
				if (m_rect.leftClicked())
				{
					m_pressed = true;
				}
			}
			else
			{
				if (m_rect.leftReleased())
				{
					m_pressed = false;
					pushed = true;
				}
				else if (!m_rect.mouseOver())
				{
					m_pressed = false;
				}
			}

			m_transitionPressed.update(m_pressed);

			return pushed;
		}

		void draw() const
		{
			const double padding = (m_rect.h - m_iconSize - m_font.ascent() - (Border * 2)) / 3.0;
			const double t = m_transitionPressed.value();
			const Transformer2D tr(Mat3x2::Scale((1 + t * 0.06), m_rect.center()));
			drawTile(t);
			drawIcon(padding, t);
			drawLabel(padding, t);
		}
	};
}


void Main()
{
	Window::Resize(1280, 720);
	const Font font1(21, U"example/font/LogoTypeGothic/LogoTypeGothic.otf", FontStyle::Bold);
	const Font font2(14, U"example/font/LogoTypeGothic/LogoTypeGothic.otf", FontStyle::Bold);
	const Font font3(24, Typeface::Heavy);

	constexpr int32 iconSize1 = 65;
	constexpr int32 iconSize2 = 40;
	constexpr int32 iconSize3 = 60;
	constexpr ColorF c1(0.3, 0.2, 0.0);
	constexpr ColorF c2(1.0, 0.95, 0.75);
	constexpr ColorF c3(1.0, 0.4);
	constexpr ColorF c4(1.0, 0.8, 0.4);

	Array<GameUIToolkit::TileButton> buttons = {
		{ 0xf279, iconSize1, font1, U"マップ", Rect(40, 40, 130), c1, c2, c3, c4 },
		{ 0xf14e, iconSize1, font1, U"イベント", Rect(180, 40, 130), c1, c2, c3, c4 },
		// knapsack.png は https://game-icons.net/1x1/lorc/knapsack.html からダウンロード
		{ Image(Palette::White, U"knapsack.png").scaled(iconSize1, iconSize1), iconSize1, font1, U"バッグ", Rect(320, 40, 130), c1, c2, c3, c4 },
		{ 0xf02d, iconSize1, font1, U"冒険の記録", Rect(460, 40, 130), c1, c2, c3, c4 },
		{ 0xf091, iconSize1, font1, U"実績", Rect(600, 40, 130), c1, c2, c3, c4 },
		{ 0xf013, iconSize1, font1, U"設定", Rect(740, 40, 130), c1, c2, c3, c4 },

		{ 0xf279, iconSize2, font2, U"マップ", Rect(40, 200, 80), c1, c2, c3, c4 },
		{ 0xf14e, iconSize2, font2, U"イベント", Rect(130, 200, 80), c1, c2, c3, c4 },
		{ Image(Palette::White, U"knapsack.png").scaled(iconSize2, iconSize2), iconSize2, font2, U"バッグ", Rect(220, 200, 80), c1, c2, c3, c4 },
		{ 0xf02d, iconSize2, font2, U"冒険の記録", Rect(310, 200, 80), c1, c2, c3, c4 },
		{ 0xf091, iconSize2, font2, U"実績", Rect(400, 200, 80), c1, c2, c3, c4 },
		{ 0xf013, iconSize2, font2, U"設定", Rect(490, 200, 80), c1, c2, c3, c4 },

		{ 0xf2e7, iconSize3, font3, U"メニュー", Rect(40, 360, 150, 120), HSV(25, 1, 0.8), Palette::White, c3, HSV(25, 0.5, 1) },
		{ 0xf7fb, iconSize3, font3, U"具材", Rect(200, 360, 150, 120), HSV(75, 1, 0.8), Palette::White, c3, HSV(75, 0.5, 1) },
		{ 0xf816, iconSize3, font3, U"調味料", Rect(360, 360, 150, 120), HSV(125, 1, 0.8), Palette::White, c3, HSV(125, 0.5, 1) },
		{ 0xf07a, iconSize3, font3, U"仕入れ", Rect(520, 360, 150, 120), HSV(175, 1, 0.8), Palette::White, c3, HSV(175, 0.5, 1) },
		{ 0xf517, iconSize3, font3, U"設備", Rect(680, 360, 150, 120), HSV(225, 1, 0.8), Palette::White, c3, HSV(225, 0.5, 1) },
		{ 0xf0a1, iconSize3, font3, U"宣伝", Rect(840, 360, 150, 120), HSV(275, 1, 0.8), Palette::White, c3, HSV(275, 0.5, 1) },
		{ 0xf201, iconSize3, font3, U"売り上げ", Rect(1000, 360, 150, 120), HSV(325, 1, 0.8), Palette::White, c3, HSV(325, 0.5, 1) },
	};

	while (System::Update())
	{
		Rect(0, 0, Scene::Width(), 320).draw(ColorF(0.8, 0.7, 0.6));
		Rect(0, 320, Scene::Width(), 200).draw(ColorF(0.3));

		for (auto& button : buttons)
		{
			button.update();
		}

		for (const auto& button : buttons)
		{
			button.draw();
		}
	}
}

Discussion