Nobinator
8/11/2017 - 3:20 PM

Custom Handle

using UnityEditor;
using UnityEngine;

//http://answers.unity3d.com/questions/463207/how-do-you-make-a-custom-handle-respond-to-the-mou.html
// Author http://answers.unity3d.com/users/57609/higekun.html
public class MyHandles{
	public enum DragHandleResult{
		none = 0,

		LMBPress,
		LMBClick,
		LMBDoubleClick,
		LMBDrag,
		LMBRelease,

		RMBPress,
		RMBClick,
		RMBDoubleClick,
		RMBDrag,
		RMBRelease
	}
	// Храним хеш строки DragHandleHash в качестве индивидуального ключа
	private static readonly int s_DragHandleHash = "DragHandleHash".GetHashCode();

	private static Vector2 s_DragHandleMouseStart;
	private static Vector2 s_DragHandleMouseCurrent;
	private static Vector3 s_DragHandleWorldStart;
	private static float s_DragHandleClickTime;
	private static int s_DragHandleClickID;
	private static readonly float s_DragHandleDoubleClickInterval = 0.5f; // допустимый интервал между кликами, чтобы сработал doubleClick
	private static bool s_DragHandleHasMoved;

	// externally accessible to get the ID of the most resently processed DragHandle
	public static int lastDragHandleID;

	// 
	public static Vector3 DragHandle(Vector3 position, float handleSize, Handles.CapFunction capFunc, Color colorSelected,
		out DragHandleResult result){
		
		// Используем хеш ключ для получения индивидуального ключа контроля
		var id = GUIUtility.GetControlID(s_DragHandleHash, FocusType.Passive);
		// Переменная для хранения id, доступная извне (public)
		lastDragHandleID = id;

		//???//
		var screenPosition = Handles.matrix.MultiplyPoint(position);
		// Сохраняем действующую матрицу в отдельную переменную, чтобы после отработки всего кода вернуть Handles матрицу в прежнее состояние
		var cachedMatrix = Handles.matrix;

		result = DragHandleResult.none;

		// Проверяем на события 
		switch(Event.current.GetTypeForControl(id)){
			// Мышь нажата
			case EventType.MouseDown:
				/*
				HandleUtility.nearestControl по видимому возвращает id ближайшего контроллера
				
				public static int nearestControl{
					get { return (double) HandleUtility.s_NearestDistance > 5.0 ? 0 : HandleUtility.s_NearestControl;}
					set { HandleUtility.s_NearestControl = value; }
				}
				
				public static void AddControl(int controlId, float distance){
					if ((double) distance < (double) HandleUtility.s_CustomPickDistance && (double) distance > 5.0)
						distance = 5f;
					if ((double) distance > (double) HandleUtility.s_NearestDistance)
						return;
					HandleUtility.s_NearestDistance = distance;
					HandleUtility.s_NearestControl = controlId; 	<<< !
				}
				
				---
				
				Event.current.button == 0 < left mouse button click
				Event.current.button == 1 < right mouse button click
				*/
				if(HandleUtility.nearestControl == id && (Event.current.button == 0 || Event.current.button == 1)){
					/*
					Записывая текущий контроллер как hot мы позволяем только ему перехватывать события мыши
					*/
					GUIUtility.hotControl = id;
					s_DragHandleMouseCurrent = s_DragHandleMouseStart = Event.current.mousePosition;
					s_DragHandleWorldStart = position;
					s_DragHandleHasMoved = false;
					// использование Use позволяет как бы обнулить событие, что непозволит другим элементам среагировать на него
					// http://answers.unity3d.com/questions/971262/when-to-actually-use-eventuse.html
					Event.current.Use();
					
					/* Когда ведешь контроллер к краю экрана эта строчка позволяет курсору мыши перескочить на другой край и продолжать двигаться
						Типа как если взять rotation tool и двигать мышь постоянно влево
					 */

					EditorGUIUtility.SetWantsMouseJumping(1);

					// Записываем состояние коонтроллера в DragHandleResult
					if(Event.current.button == 0)
						result = DragHandleResult.LMBPress;
					else if(Event.current.button == 1)
						result = DragHandleResult.RMBPress;
				}
				break;
			// Мышь отпущена
			case EventType.MouseUp:
				if(GUIUtility.hotControl == id && (Event.current.button == 0 || Event.current.button == 1)){
					// Отключаем горячесть контроллера
					GUIUtility.hotControl = 0;
					Event.current.Use();
					// Отключаем фичу с перескоками
					EditorGUIUtility.SetWantsMouseJumping(0);

					if(Event.current.button == 0)
						result = DragHandleResult.LMBRelease;
					else if(Event.current.button == 1)
						result = DragHandleResult.RMBRelease;
					// Мышь не сдвинулась
					if(Event.current.mousePosition == s_DragHandleMouseStart){
						// Если текущий id клика идентичный предыдущему и интервал между кликами не высок то doubleClick = true;
						var doubleClick = s_DragHandleClickID == id &&
						                  Time.realtimeSinceStartup - s_DragHandleClickTime < s_DragHandleDoubleClickInterval;

						s_DragHandleClickID = id; // Храним id клика
						s_DragHandleClickTime = Time.realtimeSinceStartup; // Храним время клика

						if(Event.current.button == 0)
							result = doubleClick ? DragHandleResult.LMBDoubleClick : DragHandleResult.LMBClick;
						else if(Event.current.button == 1)
							result = doubleClick ? DragHandleResult.RMBDoubleClick : DragHandleResult.RMBClick;
					}
				}
				break;

			case EventType.MouseDrag:
				// Если текущий контроллер активен
				if(GUIUtility.hotControl == id){
					// Рассчет новойц позиции мыши
					s_DragHandleMouseCurrent += new Vector2(Event.current.delta.x, -Event.current.delta.y);
					
					//???// Handles.matrix.MultiplyPoint
					// Рассчет текущего смещения мыши относительно s_DragHandleMouseStart 
					var position2 = Camera.current.WorldToScreenPoint(Handles.matrix.MultiplyPoint(s_DragHandleWorldStart))
					                + (Vector3) (s_DragHandleMouseCurrent - s_DragHandleMouseStart);
					// Перевод в мировые координаты
					position = Handles.matrix.inverse.MultiplyPoint(Camera.current.ScreenToWorldPoint(position2));

					// Если камера смотрит ровно по направлению Vector3.forward или - Vector3.forward
					if(Camera.current.transform.forward == Vector3.forward || Camera.current.transform.forward == -Vector3.forward)
						position.z = s_DragHandleWorldStart.z; // То не изменяем позицию по z
					// Такая же система
					if(Camera.current.transform.forward == Vector3.up || Camera.current.transform.forward == -Vector3.up)
						position.y = s_DragHandleWorldStart.y;
					if(Camera.current.transform.forward == Vector3.right || Camera.current.transform.forward == -Vector3.right)
						position.x = s_DragHandleWorldStart.x;

					if(Event.current.button == 0)
						result = DragHandleResult.LMBDrag;
					else if(Event.current.button == 1)
						result = DragHandleResult.RMBDrag;

					s_DragHandleHasMoved = true;
			
					// Сообщаем GUI, что контроллер изменил состояние входных данных
					GUI.changed = true;
					Event.current.Use();
				}
				break;

			case EventType.Repaint:
				var currentColour = Handles.color;
				if(id == GUIUtility.hotControl && s_DragHandleHasMoved)
					Handles.color = colorSelected;

				Handles.matrix = Matrix4x4.identity;
				capFunc(id, screenPosition, Quaternion.identity, handleSize, EventType.Repaint);
				Handles.matrix = cachedMatrix;

				Handles.color = currentColour;
				break;

			case EventType.Layout:
				Handles.matrix = Matrix4x4.identity;
				// Добавление контроллера
				// Тут не берется во внимание какой cap стоит и тут всегда дистанция считается через DistanceToCircle 
				// Вообще такие дела с id и AddControl делаются в CapFunction
				//HandleUtility.AddControl(id, HandleUtility.DistanceToCircle(screenPosition, handleSize));
				capFunc(id, screenPosition, Quaternion.identity, handleSize, EventType.Layout);
				Handles.matrix = cachedMatrix;
				break;
		}

		return position;
	}
	
	
	
}