UnityのGUIで一覧表示

Unityでテーブルレイアウトをやる場合は[GUI.SelectionGrid](http://docs.unity3d.com/Documentation/ScriptReference/GUI.SelectionGrid.html)を使うのがいいかと思いますが、
１行を１つのアイテムに見せるような一覧表示をするのはちょっと難しそうです。

なので、ラベルを組み合わせてテーブルっぽく見せるスクリプトを作ってみました。


```js:ListBox.js
class ListBox {
	static private var clickTimeDelta : float = 1.0;
	private var mouseDownTimeWindow : float;	

	var itemHeight:float = Screen.height / 12;
	var list:List.<System.Object>;
	var callback:Function;
	var top:float;
	var left:float; 
	var width:float; 
	var height:float;
	
	var scrollPosition: Vector2 = Vector2.zero;;
	var selectedInt : int = -1;
	var hoverInt:int = -1;
	
	var columns : String[];	
	var filteredColumns:String[];
	
	var labelTexture:Texture;
	var labelHoverTexture:Texture;
	var labelOnTexture:Texture;
	var labelStyle:GUIStyle;
	var labelStyleCenter:GUIStyle;
	var labelStyleRight:GUIStyle;

	private function refresh(){
		var first = list[0];
		var filteredColumnsArray = new Array();
		if(!first) {
			return;
		}
		var firstDict:Dictionary.<String, System.Object> = first as Dictionary.<String, System.Object>;
		if(firstDict) {
			var keys:Dictionary.KeyCollection.<String, System.Object> = firstDict.Keys;
			if(columns && columns.Length > 0) {
				for(var key in keys){
					for(var key2 in columns){
						if(key2 == key){
							filteredColumnsArray.Add(key);
						}
					}
				}
			} else {
				for(var key in keys){
					filteredColumnsArray.Add(key);
				}
			}
			filteredColumns = filteredColumnsArray.ToBuiltin(String);
		} 
		
		setItemWidth(this.width);
		if(filteredColumns && filteredColumns.Length > 0) {
			setItemWidth(Mathf.Round(this.width / filteredColumns.Length));
		}
	}
	
	private function Initialize(list:List.<System.Object>, callback:Function, top:float, left:float, width:float, height:float){
		labelTexture = Resources.Load("textures/list");
		labelOnTexture = Resources.Load("textures/list_on");
		labelHoverTexture = Resources.Load("textures/list_hover");
		labelStyle = new GUIStyle(GUI.skin.box);
		labelStyle.fontSize = Screen.height / 32;
		
		labelStyle.fixedHeight = itemHeight;
		labelStyle.alignment = TextAnchor.MiddleLeft;
		labelStyle.margin = RectOffset(0, 0, 0, 0);
		labelStyle.padding = RectOffset(16, 16, 0, 0);
		labelStyle.normal.background = labelTexture;

		labelStyleCenter = new GUIStyle(labelStyle);
   		labelStyleCenter.alignment = TextAnchor.MiddleCenter;
   			
   		labelStyleRight = new GUIStyle(labelStyle);
   		labelStyleRight.alignment = TextAnchor.MiddleRight;
   		mouseDownTimeWindow = -1;
   		
   		
		this.list = list;
		this.callback = callback;
		this.top = top;
		this.left = left; 
		this.width = width;
		this.height = height;
		refresh();
	}
	
	private function Initialize(list:List.<System.Object>, callback:Function, top:float, left:float){
		Initialize(list, callback, top, left, Screen.width - (left * 2), Screen.height - (top + itemHeight));
	}
	
	function ListBox(list:List.<System.Object>, callback:Function, top:float, left:float, width:float, height:float){
		Initialize(list, callback, top, left, width, height);
	}
	
	function ListBox(list:List.<System.Object>, callback:Function, top:float, left:float){
		Initialize(list, callback, top, left, Screen.width - (left * 2), Screen.height - (top + itemHeight));
	}
	
	function ListBox(list:List.<System.Object>, callback:Function){
		Initialize(list, callback, itemHeight, itemHeight);
	}
	
	function setItemHeight(_itemHeight:float){
		itemHeight = _itemHeight;
	}
	
	function setList(list:List.<System.Object>){
		this.list = list;
		refresh();
	}
	
	function setColumns(columns:String[]){
		this.columns = columns;
		refresh();
	}
	
	function setItemWidth(width:float) {
		labelStyle.fixedWidth = width;
		labelStyleCenter.fixedWidth = width;
		labelStyleRight.fixedWidth = width;
	}
	
	function setItemTexture(texture:Texture) {
		labelStyle.normal.background = texture;
		labelStyleCenter.normal.background = texture;
		labelStyleRight.normal.background = texture;
	}
	
	function OnGUI() {
		if ( mouseDownTimeWindow > 0 ) {
			mouseDownTimeWindow -= Time.deltaTime;
			if(mouseDownTimeWindow < 0){
				mouseDownTimeWindow = 0;
			}
		}
		var selectStyle:GUIStyle = GUIStyle.none;
   		if(this.list){
   			var oldSelected:int = selectedInt;
   			var currentHeight:float = itemHeight * this.list.Count;
   			var mouseHover = false;
	   		scrollPosition = GUI.BeginScrollView (Rect (top, left, width, height), scrollPosition, Rect (0, 0, width, currentHeight));
				GUILayout.BeginVertical();
				var i:int=0;
				for (var item in this.list){
					if(i == oldSelected){
						setItemTexture(labelOnTexture);
					} else if(i == hoverInt){
						setItemTexture(labelHoverTexture);
					} else {
						setItemTexture(labelTexture);				
					}
					GUILayout.BeginHorizontal();
					var dict:Dictionary.<String, System.Object> = item as Dictionary.<String, System.Object>;
					if(dict){
						var j:int = 0;
						for(var column in filteredColumns) {
							var itemString = "";
							if(dict.ContainsKey(column)){
								itemString = dict[column];
							}
							
							if(j == 0) {
								GUILayout.Label(itemString, labelStyle);
							} else if(j == filteredColumns.Length - 1) {
								GUILayout.Label(itemString, labelStyleRight);
							} else {
								GUILayout.Label(itemString, labelStyleCenter);
							}
							j++;
						}
					} else {
						GUILayout.Label(item as String, labelStyle);
					}
					GUILayout.EndHorizontal();
					var rect:Rect = GUILayoutUtility.GetLastRect();
					var pos:Vector2 = Vector2(Input.mousePosition.x - left, (Screen.height - Input.mousePosition.y) - top);
					if (rect.Contains(pos)) {
						mouseHover = true;
						hoverInt = i;
						if(Input.GetMouseButton(0) || Input.GetMouseButtonUp(0)){
							selectedInt = i;
						} else {
							selectedInt = -1;
						}
					}
					i++;
				}	
				GUILayout.EndVertical();		
			GUI.EndScrollView ();

   			if (selectedInt >= 0) {
   				var mouseOut = ((selectedInt != oldSelected) && (oldSelected >= 0));
				if(mouseOut){
					mouseDownTimeWindow = 0;
					selectedInt = -1;
				} else if(Input.GetMouseButton(0) && mouseDownTimeWindow < 0) {
					mouseDownTimeWindow = clickTimeDelta;
				} else if(!Input.GetMouseButton(0) && (mouseDownTimeWindow > 0)) {
					if(callback){
						callback(list[selectedInt], selectedInt);
					}
					mouseDownTimeWindow = -1;
				} 
			}
			if(!Input.GetMouseButton(0) && mouseDownTimeWindow == 0) {
				mouseDownTimeWindow = -1;
			}
   		}
   	}
}
```

背景画像はこんな感じで
![list.png](https://qiita-image-store.s3.amazonaws.com/0/39143/60cac5ea-74a5-3bdf-e161-7084713073e4.png)　textures/list
![list_hover.png](https://qiita-image-store.s3.amazonaws.com/0/39143/02543d5b-36d0-d0ff-0a89-3b172474edf9.png)　textures/list_hover
![list_on.png](https://qiita-image-store.s3.amazonaws.com/0/39143/7654ff11-ddda-dcc7-ddc0-cd49263ebdc5.png)　textures/list_on

表示させる側のコードはこんな感じです

```js:main.js
var listBox:ListBox;

function Start() {
	listBox = null;
}

function OnGUI(){
	if(!listBox){
		var stringList:List.<System.Object> = new List.<System.Object>();
	
		var item1 = new Dictionary.<String, System.Object>();
		item1['name'] = '布の服';
		item1['price'] = '80G';
		stringList.Add(item1);
		
		var item2 = new Dictionary.<String, System.Object>();
		item2['name'] = '旅人の服';
		item2['price'] = '100G';
		stringList.Add(item2);
		
		var item3 = new Dictionary.<String, System.Object>();
		item3['name'] = '銅の剣';
		item3['price'] = '120G';
		stringList.Add(item3);
		
		var item4 = new Dictionary.<String, System.Object>();
		item4['name'] = '銅の盾';
		item4['price'] = '70G';
		stringList.Add(item4);
		listBox = new ListBox(stringList, function(selected, index){
			Debug.Log("Selected '" + selected + "' at " + index);
		});
	}
	
	if (Event.current.type != EventType.keyDown) {
		listBox.OnGUI();
	}
}
```

実際の表示はこんな感じです。
![ss.png](https://qiita-image-store.s3.amazonaws.com/0/39143/1c915dea-4e2f-4ac8-a4c5-db284c78dd1d.png)


darktablesさんのJSONプラグイン
[MiniJSON.cs](https://gist.github.com/darktable/1411710)と併用することでJSONを一覧表示することもできます。