Siv3Dで自前のスクロール機能を実装する

Siv3D Advent Calendar 2016 21日目の記事です。
***
#概要
Siv3Dでスクロール機能の実装をしてみます。ここで言うスクロール機能とは、スマホなんかじゃおなじみの「画面をスライドさせると画面がその方向にスクロールする」ような機能です。今回は「右クリックしたままドラッグすると画面をスクロールさせる」ことにします。また、せっかくなのでマウスホイールの回転で画面の拡大率も変わるような機能も実装してみます。

ちなみに、Siv3D付属のHamFramework中にもスクロール機能が実装できるような2Dカメラ用のクラスが用意されていますが、ここで紹介するものはより自然なUIとなるように自分なりに実装したものとなります。2Dカメラ操作についてはこちらの記事で詳しく紹介されています。
[Siv3D での 2D カメラ操作をサポートする Camera2D ](http://qiita.com/hamukun8686/items/95471335b8a0fa1d877f)

#実装コード

```cpp:Main.cpp
# include <Siv3D.hpp>

void Main()
{
	Vec2 absolutePosition = Vec2(0, 0);
	double expansion = 1.0;

	Vec2 Place = Vec2(200,200);
	bool isPressed = false;

	const Font font(30);

	while (System::Update())
	{
		const RectF rectf(absolutePosition + Place*expansion, 120 * expansion, 120 * expansion);
		if (rectf.leftClicked) {
			isPressed = true;
		}
		if (Input::MouseL.released) {
			isPressed = false;
		}
		if (isPressed) {
			Place += Mouse::Delta() / expansion;
		}

		rectf.draw(isPressed ? Palette::Red : Palette::Gray);
		font(Place).draw(absolutePosition + (Place+Vec2(0,-60))*expansion, Palette::Orange);

		//スクロール
		if (Input::MouseR.pressed)
		{
			const Point delta = Mouse::Delta();
			absolutePosition += delta;
		}

		// 拡大
		const int wheel = Mouse::Wheel();
		if (wheel == 1) {
			if (expansion >= 0.25) { expansion = expansion / 2; absolutePosition = (absolutePosition + Window::Center()) / 2; }
		}
		else if (wheel == -1) {
			if (expansion <= 8) { expansion = expansion * 2; absolutePosition = absolutePosition * 2 - Window::Center(); }
		}

		PutText(L" ×{}"_fmt, expansion);
	}
}
```

![q6.gif](https://qiita-image-store.s3.amazonaws.com/0/127031/570f1776-2e01-a577-cbc4-151a5bfd8cf0.gif)

#解説
##絶対位置を示す変数absolutePosition
今回実装したUIの、簡潔な解説イラストがこちらです。
![qq1.png](https://qiita-image-store.s3.amazonaws.com/0/127031/108011d3-5e4b-3dc0-7101-6aa11202a544.png)

メインウインドウ画面を動かす(ように見せる)ことで、スクロール機能を実装させました。実際には画面は固定されており、図形の描写位置の方を更新していくことであたかもスクロールしているかのようにしています。
例えば、この状態から右クリックしたまま左にドラッグするとAbsolutePositionのx座標成分がマイナスされ、結果として描写する図形が左に移動してくれます。

##拡大率を示す変数expansion
拡大率を制御するための変数expansionを定義しました。拡大率が2倍であれば図形の見た目の大きさも2倍に描写しなければいけません。またabsolutePositionとの兼ね合いもあり、拡大率を変更した前と後とで中心位置がずれないようにします。
![qq2.png](https://qiita-image-store.s3.amazonaws.com/0/127031/573018cd-b148-e976-592b-21ff183d9004.png)

イラストだけだと少し分かりづらいですが、expansionによって図形が拡大・縮小され、AbsolutePositionによって図形が引っ張られます。

##描写

描写は気を付けなければいけません。上述したabsolutePositionとexpansionを上手く機能させるために、以下のように描写の引数を指定します。

- 描写位置は、もともとの位置にexpansionを掛けたものにabsolutePositionを足す
- 描写するサイズは、もともとのサイズにexpansionを掛ける

**例**
`const RectF rectf(absolutePosition + Place*expansion, 120 * expansion, 120 * expansion);`

#応用
##テクスチャ
図形だけでなく、テクスチャにも応用することができます。

```cpp:Main.cpp
# include <Siv3D.hpp>

void Main()
{
	Vec2 absolutePosition = Vec2(0, 0);
	double expansion = 1.0;

	Vec2 Place = Vec2(200,200);
	const Texture texture(L"sumple.png");

	while (System::Update())
	{
		texture.resize(200*expansion, 200*expansion).draw(absolutePosition + Place*expansion);

		//座標変換
		if (Input::MouseR.pressed)
		{
			const Point delta = Mouse::Delta();
			absolutePosition += delta;
		}

		// 拡大率
		const int wheel = Mouse::Wheel();
		if (wheel == 1) {
			if (expansion >= 0.25) { expansion = expansion / 2; absolutePosition = (absolutePosition + Window::Center()) / 2; }
		}
		else if (wheel == -1) {
			if (expansion <= 8) { expansion = expansion * 2; absolutePosition = absolutePosition * 2 - Window::Center(); }
		}

		PutText(L" ×{}"_fmt, expansion);
	}
}
```

![1111111111.gif](https://qiita-image-store.s3.amazonaws.com/0/127031/8013f988-35f0-fa5d-862a-7bcab3efaaf3.gif)

#まとめ

- スクロール機能の実装ができた。
- 身の回りに浸透しているUIだから、大衆受けするアプリケーションも作りやすいかも。
- 描写の引数など改善の余地あり。今のままだとメモリに優しくないかな？


なんか、あまりに物足りなさすぎる気が、、、当初はこのUIを使ったチューリング完全なヴィジュアルプログラミング言語とか作ろうと思ってたのですが、時間が足りなかったということでまた後日個別に上げようかなと思います～。

***
明日の記事は @Mitsugoro32 さんです。
よろしくお願いします！