context: /* Copyright 2013-2014 Daikon Forge */
  using UnityEngine;
  
  using System;
  using System.Linq;
  using System.Reflection;
  using System.Collections;
  using System.Collections.Generic;
  
  /// <summary>
  /// The dfGUIManager class is responsible for compiling all control rendering 
  /// data into a Mesh and rendering that Mesh to the screen. This class is the 
  /// primary workhorse of the DF-GUI library and in conjunction with <see cref="dfControl"/>
  /// forms the core of this library's functionality.
  /// </summary>
  [Serializable]
  [ExecuteInEditMode]
  [RequireComponent( typeof( BoxCollider ) )]
  [RequireComponent( typeof( dfInputManager ) )]
  [AddComponentMenu( "Daikon Forge/User Interface/GUI Manager" )]
  public class dfGUIManager : MonoBehaviour, IDFControlHost, IComparable<dfGUIManager>
  {
  
  	#region Callback and event definitions
  
  	[dfEventCategory( "Modal Dialog" )]
  	public delegate void ModalPoppedCallback( dfControl control );
  
  	[dfEventCategory( "Global Callbacks" )]
  	public delegate void RenderCallback( dfGUIManager manager );
  
  	/// <summary>
  	/// Called before a dfGUIManager instance begins rendering the user interface
  	/// </summary>
  	public static event RenderCallback BeforeRender;
  
  	/// <summary>
  	/// Called after a dfGUIManager instance has finished rendering the user interface
  	/// </summary>
  	public static event RenderCallback AfterRender;
  
  	#endregion
  
  	#region Serialized protected members
  
  	[SerializeField]
  	protected float uiScale = 1f;
  
  	[SerializeField]
  	protected bool uiScaleLegacy = true;
  
  	[SerializeField]
  	protected dfInputManager inputManager;
  
  	[SerializeField]
  	protected int fixedWidth = -1;
  
  	[SerializeField]
  	protected int fixedHeight = 600;
  
  	[SerializeField]
  	protected dfAtlas atlas;
  
  	[SerializeField]
  	protected dfFontBase defaultFont;
  
  	[SerializeField]
  	protected bool mergeMaterials = false;
  
  	[SerializeField]
  	protected bool pixelPerfectMode = true;
  
  	[SerializeField]
  	protected Camera renderCamera = null;
  
  	[SerializeField]
  	protected bool generateNormals = false;
  
  	[SerializeField]
  	protected bool consumeMouseEvents = false;
  
  	[SerializeField]
  	protected bool overrideCamera = false;
  
  	[SerializeField]
  	protected int renderQueueBase = 3000;
  
  	[SerializeField]
  	public List<dfDesignGuide> guides = new List<dfDesignGuide>();
  
  	#endregion
  
  	#region Static fields
  
  	/// <summary>
  	/// Keeps track of all active dfGUIManager instances
  	/// </summary>
  	private static List<dfGUIManager> activeInstances = new List<dfGUIManager>();
  
  	/// <summary>
  	/// Global reference to the control that currently has input focus
  	/// </summary>
  	private static dfControl activeControl = null;
  
  	/// <summary>
  	/// Used to maintain a stack of "modal" controls
  	/// </summary>
  	private static Stack<ModalControlReference> modalControlStack = new Stack<ModalControlReference>();
  
  	#endregion
  
  	#region Private non-serialized fields
  
  	private dfGUICamera guiCamera;
  	private Mesh[] renderMesh;
  	private MeshFilter renderFilter;
  	private MeshRenderer meshRenderer;
  	private int activeRenderMesh = 0;
  	private int cachedChildCount = 0;
  	private bool isDirty;
  	private bool abortRendering;
  	private Vector2 cachedScreenSize;
  	private Vector3[] corners = new Vector3[ 4 ];
  
  	private dfList<Rect> occluders = new dfList<Rect>( 256 );
  
  	private Stack<dfTriangleClippingRegion> clipStack = new Stack<dfTriangleClippingRegion>();
  	private static dfRenderData masterBuffer = new dfRenderData( 4096 );
  	private dfList<dfRenderData> drawCallBuffers = new dfList<dfRenderData>();
  	private dfList<dfRenderGroup> renderGroups = new dfList<dfRenderGroup>();
  	private List<int> submeshes = new List<int>();
  	private int drawCallCount = 0;
  	private Vector2 uiOffset = Vector2.zero;
  	private static Plane[] clippingPlanes;
  
  	private dfList<int> drawCallIndices = new dfList<int>();
  	private dfList<dfControl> controlsRendered = new dfList<dfControl>();
  
  	private bool shutdownInProcess = false;
  	private int suspendCount = 0;
  
  	#endregion
  
  	#region Public properties
  
  	public static IEnumerable<dfGUIManager> ActiveManagers { get { return activeInstances; } }
  
  	/// <summary>Returns the total number of draw calls required to render this <see cref="dfGUIManager"/> instance during the last frame </summary>
  	public int TotalDrawCalls { get; private set; }
  
  	/// <summary>Returns the total number of triangles this <see cref="dfGUIManager"/> instance rendered during the last frame </summary>
  	public int TotalTriangles { get; private set; }
  
  	/// <summary>Returns the total number of controls this <see cref="dfGUIManager"/> instance rendered during the last frame </summary>
  	public int NumControlsRendered { get; private set; }
  
  	/// <summary>Returns the total number of frames this <see cref="dfGUIManager"/> instance has rendered </summary>
  	public int FramesRendered { get; private set; }
  
  	/// <summary> Returns the list of controls actually rendered in the last render pass</summary>
  	public IList<dfControl> ControlsRendered { get { return controlsRendered; } }
  
  	/// <summary> Returns a list of indices into the ControlsRendered collection where each draw call started </summary>
  	public IList<int> DrawCallStartIndices { get { return drawCallIndices; } }
  
  	/// <summary>
  	/// Gets or sets the base value that will be used for the <a href="http://docs.unity3d.com/Documentation/ScriptReference/Material-renderQueue.html" target="_blank">Render Queue</a>
  	/// </summary>
  	public int RenderQueueBase
  	{
  		get { return this.renderQueueBase; }
  		set
  		{
  			if( value != this.renderQueueBase )
  			{
  				this.renderQueueBase = value;
  				RefreshAll();
  			}
  		}
  	}
  
  	/// <summary>
  	/// Returns a reference to the <see cref="dfControl"/> instance that currently has input focus
  	/// </summary>
  	public static dfControl ActiveControl { get { return activeControl; } }
  
  	/// <summary>
  	/// Gets or sets the multiplier by which the entire UI will be scaled
  	/// </summary>
  	public float UIScale
  	{
  		get { return this.uiScale; }
  		set
  		{
  			if( !Mathf.Approximately( value, this.uiScale ) )
  			{
  				this.uiScale = value;
  				onResolutionChanged();
  			}
  		}
  	}
  
  	/// <summary>
  	/// Gets or sets whether the UIScale property will use Legacy Mode (scales entire UI)
  	/// </summary>
  	public bool UIScaleLegacyMode
  	{
  		get { return this.uiScaleLegacy; }
  		set
  		{
  			if( value != uiScaleLegacy )
  			{
  				this.uiScaleLegacy = value;
  				onResolutionChanged();
  			}
  		}
  	}
  
  	/// <summary>
  	/// Gets or sets the amount and direction of "offset" for the entire
  	/// user interface. Useful for "panning" or implementing "shake".
  	/// </summary>
  	public Vector2 UIOffset
  	{
  		get { return this.uiOffset; }
  		set
  		{
  			if( !Vector2.Equals( this.uiOffset, value ) )
  			{
  				this.uiOffset = value;
  				Invalidate();
  			}
  		}
  	}
  
  	/// <summary>
  	/// Returns the <see cref="UnityEngine.Camera"/> that is used to render 
  	/// the <see cref="dfGUIManager"/> and all of its controls
  	/// </summary>
  	public Camera RenderCamera
  	{
  		get { return renderCamera; }
  		set
  		{
  			if( !object.ReferenceEquals( renderCamera, value ) )
  			{
  
  				this.renderCamera = value;
  				Invalidate();
  
  				if( value != null && value.gameObject.GetComponent<dfGUICamera>() == null )
  				{
  					value.gameObject.AddComponent<dfGUICamera>();
  				}
  
  				if( this.inputManager != null )
  				{
  					this.inputManager.RenderCamera = value;
  				}
  
  			}
  		}
  	}
  
  	/// <summary>
  	/// Gets/Sets a value indicating whether the GUIManager should attempt
  	/// to consolidate drawcalls that use the same Material instance. This 
  	/// can reduce the number of draw calls in some cases, but depending on 
  	/// scene complexity may also affect control render order.
  	/// </summary>
  	public bool MergeMaterials
  	{
  		get { return this.mergeMaterials; }
  		set
  		{
  			if( value != this.mergeMaterials )
  			{
  				this.mergeMaterials = value;
  				invalidateAllControls();
  			}
  		}
  	}
  
  	/// <summary>
  	/// Gets or sets whether normals and tangents will be generated for the 
  	/// rendered output, which is needed by some shaders. Defaults to FALSE.
  	/// </summary>
  	public bool GenerateNormals
  	{
  		get { return this.generateNormals; }
  		set
  		{
  			if( value != this.generateNormals )
  			{
  
  				this.generateNormals = value;
  
  				if( this.renderMesh != null )
  				{
  					renderMesh[ 0 ].Clear();
  					renderMesh[ 1 ].Clear();
  				}
  
  				dfRenderData.FlushObjectPool();
  				invalidateAllControls();
  
  			}
  		}
  	}
  
  	/// <summary>
  	/// Gets/Sets a value indicating whether controls should be resized at
  	/// runtime to retain the same pixel dimensions as design time. If this
  	/// value is set to TRUE, controls will always remain at the same pixel
  	/// resolution regardless of the resolution of the game. If set to FALSE,
  	/// controls will be scaled to fit the target resolution.
  	/// </summary>
  	public bool PixelPerfectMode
  	{
  		get { return this.pixelPerfectMode; }
  		set
  		{
  			if( value != this.pixelPerfectMode )
  			{
  				this.pixelPerfectMode = value;
  				onResolutionChanged();
  				Invalidate();
  			}
  		}
  	}
  
  	/// <summary>
  	/// The default <see cref="dfAtlas">Texture Atlas</see> containing the images used
  	/// to render controls in this <see cref="dfGUIManager"/>. New controls added to the
  	/// scene will use this Atlas by default.
  	/// </summary>
  	public dfAtlas DefaultAtlas
  	{
  		get { return atlas; }
  		set
  		{
  			if( !dfAtlas.Equals( value, atlas ) )
  			{
  				this.atlas = value;
  				invalidateAllControls();
  			}
  		}
  	}
  
  	/// <summary>
  	/// The default <see cref="dfFont">Bitmapped Font</see> that will be assigned
  	/// to new controls added to the scene
  	/// </summary>
  	public dfFontBase DefaultFont
  	{
  		get { return defaultFont; }
  		set
  		{
  			if( value != this.defaultFont )
  			{
  				this.defaultFont = value;
  				invalidateAllControls();
  			}
  		}
  	}
  
  	/// <summary>
  	/// Returns the width of the target screen size
  	/// </summary>
  	public int FixedWidth
  	{
  		get { return this.fixedWidth; }
  		set
  		{
  			if( value != this.fixedWidth )
  			{
  				this.fixedWidth = value;
  				onResolutionChanged();
  			}
  		}
  	}
  
  	/// <summary>
  	/// Gets/Sets the height of the target screen size.
  	/// </summary>
  	public int FixedHeight
  	{
  		get { return this.fixedHeight; }
  		set
  		{
  			if( value != this.fixedHeight )
  			{
  				var previousValue = this.fixedHeight;
  				this.fixedHeight = value;
  
  				onResolutionChanged( previousValue, value );
  
  			}
  		}
  	}
  
  	/// <summary>
  	/// Gets or sets whether mouse events generated on an active control
  	/// will be "consumed" (unavailable for other in-game processing)
  	/// </summary>
  	public bool ConsumeMouseEvents
  	{
  		get { return this.consumeMouseEvents; }
  		set { this.consumeMouseEvents = value; }
  	}
  
  	/// <summary>
  	/// Gets or sets whether user scripts will override the RenderCamera's
  	/// settins. If set to TRUE, then user scripts will be responsible for
  	/// all camera settings on the UI camera
  	/// </summary>
  	public bool OverrideCamera
  	{
  		get { return this.overrideCamera; }
  		set { this.overrideCamera = value; }
  	}
  
  	#endregion
  
  	#region Unity events
  
  	public void OnApplicationQuit()
  	{
  		shutdownInProcess = true;
  	}
  
  	public void OnGUI()
  	{
  
  		if( overrideCamera || !consumeMouseEvents || !Application.isPlaying || occluders == null )
  			return;
  
  		// This code prevents "click through" by iterating 
  		// through the list of rendered control positions
  		// and determining if the mouse or touch currently
  		// overlaps. If it does overlap, then a GUI.Box()
  		// occluder is rendered and the current event is 
  		// consumed.
  
  		// NOTE: This does not account for controls that are only partially "clipped", as the
  		// clipped portion will still block mouse clicks due to the fact that DFGUI does not
  		// currently clip the control exclusion rectangles, and doing so is likely to have a
  		// nontrivial impact on per-frame performance for low-powered mobile devices.
  		
  		// NOTE: Using GUI drawing functions to block mouse input is pathologically slow
  		// on Windows Phone 8 (and potentially other mobile devices), resulting in a significant 
  		// reduction in frame rates. Unfortunately, Unity does not give *any other way* to block 
  		// the built-in SendMouseEvents processing, so there is no other option. You can either 
  		// block mouse input processing from sending the built-in OnMouseXXX events, or have better 
  		// framerates. You apparently cannot have both.
  
  		var mousePosition = Input.mousePosition;
  		mousePosition.y = Screen.height - mousePosition.y;
  
  		if( modalControlStack.Count > 0 )
  		{
  			// If there is a modal control/window being displayed, block 
  			// mouse/touch input for the entire screen.
  			GUI.Box( new Rect( 0, 0, Screen.width, Screen.height ), GUIContent.none, GUIStyle.none );
  		}
  		else
  		{
  
  			Rect lastRect = new Rect();
  
  			// Block mouse/touch input for each screen area where a control was rendered
  			for( int i = 0; i < occluders.Count; i++ )
  			{
  
  				var occluder = occluders[ i ];
  
  				if( Event.current.isMouse && occluder.Contains( mousePosition ) )
  					Event.current.Use();
  				else if( !lastRect.Contains( occluder ) )
  					GUI.Box( occluder, GUIContent.none, GUIStyle.none );
  
  				lastRect = occluder;
  
  			}
  		}
  
  		// NOTE: Not using inputManager.touchInputSource here because we're only concerned
  		// with Unity's built-in touch processing when attempting to account for "click-through".
  		if( Event.current.isMouse && Input.touchCount > 0 )
  		{
  			var touchCount = Input.touchCount;
  			for( var i = 0; i < touchCount; i++ )
  			{
  				var touch = Input.GetTouch( i );
  				if( touch.phase == TouchPhase.Began )
  				{
  
  					var touchPosition = touch.position;
  					touchPosition.y = Screen.height - touchPosition.y;
  
  					if( occluders.Any( x => x.Contains( touchPosition ) ) )
  					{
  						Event.current.Use();
  						break;
  					}
  
  				}
  			}
  		}
  
  	}
  
  	/// <summary>
  	/// Awake is called by the Unity engine when the script instance is being loaded.
  	/// </summary>
  	public void Awake()
  	{
  
  		// Clean up any render data that might have been allocated on a previous level
  		dfRenderData.FlushObjectPool();
  
  	}
  
  	/// <summary>
  	/// This function is called by the Unity engine when the object becomes enabled and current.
  	/// </summary>
  	public void OnEnable()
  	{
  
  		var sceneCameras = Camera.allCameras;
  		for( var i = 0; i < sceneCameras.Length; i++ )
  		{
  
  			// Get rid of Unity's extremely annoying tendency to print errors
  			// about being unable to call SendMouseEventXXX because the event
  			// signatures don't match. Whose idea was that, anyways? Sheesh.
  			sceneCameras[ i ].eventMask &= ~( 1 << gameObject.layer );
  
  		}
  
  		// Ensure that we have a reference to the MeshRenderer
  		if( this.meshRenderer == null )
  		{
  			initialize();
  		}
  
  		// Explicitly disabling the layout stage improves performance, most notably on
  		// very slow mobile platforms
  		this.useGUILayout = !this.ConsumeMouseEvents;
  
  		activeInstances.Add( this );
  
  		FramesRendered = 0;
  		TotalDrawCalls = 0;
  		TotalTriangles = 0;
  
  		if( meshRenderer != null )
  		{
  			meshRenderer.enabled = true;
  		}
  
  		inputManager = GetComponent<dfInputManager>() ?? gameObject.AddComponent<dfInputManager>();
  		inputManager.RenderCamera = this.RenderCamera;
  
  		FramesRendered = 0;
  
  		if( meshRenderer != null )
  		{
  			meshRenderer.enabled = true;
  		}
  
  		if( Application.isPlaying )
  		{
  			onResolutionChanged();
  		}
  
  		Invalidate();
  
  	}
  
  	/// <summary>
  	/// This function is called by the Unity engine when the control becomes 
  	/// disabled or inactive.
  	/// </summary>
  	public void OnDisable()
  	{
  
  		activeInstances.Remove( this );
  
  		if( meshRenderer != null )
  		{
  			meshRenderer.enabled = false;
  		}
  
  		resetDrawCalls();
  
  	}
  
  	public void OnDestroy()
  	{
  
  		if( activeInstances.Count == 0 )
  		{
  			dfMaterialCache.Clear();
  		}
  
  		if( renderMesh == null || renderFilter == null )
  			return;
  
  		renderFilter.sharedMesh = null;
  
  		DestroyImmediate( renderMesh[ 0 ] );
  		DestroyImmediate( renderMesh[ 1 ] );
  
  		renderMesh = null;
  
  	}
  
  	/// <summary>
  	/// Start is called by the Unity engine before any of the <see cref="Update"/> 
  	/// methods is called for the first time
  	/// </summary>
  	public void Start()
  	{
  
  		var sceneCameras = Camera.allCameras;
  		for( var i = 0; i < sceneCameras.Length; i++ )
  		{
  
  			// Get rid of Unity's extremely annoying tendency to print errors
  			// about being unable to call SendMouseEventXXX because the event
  			// signatures don't match. Whose idea was that, anyways? Sheesh.
  			sceneCameras[ i ].eventMask &= ~( 1 << gameObject.layer );
  
  		}
  
  	}
  
  	/// <summary>
  	/// Called by the Unity engine every frame if the control component is enabled
  	/// </summary>
  	public void Update()
  	{
  
  		// Sort the active instances by RenderQueueBase value
  		activeInstances.Sort();
  
  		if( this.renderCamera == null || !enabled )
  		{
  			if( meshRenderer != null )
  			{
  				meshRenderer.enabled = false;
  			}
  			return;
  		}
  
  		if( this.renderMesh == null || this.renderMesh.Length == 0 )
  		{
  
  			initialize();
  
  			// Gets rid of annoying flash when recompiling in the Editor 
  			// but we don't actually want to refresh this early otherwise
  			if( Application.isEditor && !Application.isPlaying )
  			{
  				Render();
  			}
  
  		}
  
  		if( cachedChildCount != transform.childCount )
  		{
  			cachedChildCount = transform.childCount;
  			Invalidate();
  		}
  
  		// If the screen size has changed since we last checked we need to let all
  		// controls know about the new screen size so that they can reposition or 
  		// resize themselves, etc.
  		var currentScreenSize = GetScreenSize();
  		if( ( currentScreenSize - cachedScreenSize ).sqrMagnitude > float.Epsilon )
  		{
  			onResolutionChanged( cachedScreenSize, currentScreenSize );
  			cachedScreenSize = currentScreenSize;
  		}
  
  		// NOTE: Experimental - can we now remove the asinine "scene is always dirty" bit?
  #if UNITY_EDITOR && EXPERIMENTAL
  		// HACK: The following code makes sure that the UI is aways updated while
  		// in design mode, and is a workaround for a Unity quirk where the scene's
  		// materials are reset in some specific situations, which causes the unchanged
  		// UI to render with elements out of order. This quirk is harmless in the 
  		// sense that your UI will still render correctly when the application is 
  		// playing, but makes the design-time experience somewhat distracting.
  		//
  		// NOTE: This can cause the scene in the editor to always be flagged as 
  		// unsaved. If this bothers you, you can comment out the following code, 
  		// but will then have to contend with the issue described above.
  		//
  		if( !Application.isPlaying )
  		{
  			// Setting isDirty to TRUE signals the GUIManager to redraw
  			// the user interface on the next LateUpdate pass
  			isDirty = true;
  		}
  #endif
  
  	}
  
  	/// <summary>
  	/// Called by the Unity engine every frame (after <see cref="Update"/>) if
  	/// the control component is enabled
  	/// </summary>
  	public void LateUpdate()
  	{
  
  		if( this.renderMesh == null || this.renderMesh.Length == 0 )
  		{
  			initialize();
  		}
  
  		if( !Application.isPlaying )
  		{
  
  #if UNITY_EDITOR
  			// Needed for proper raycasting in Editor viewport, which doesn't 
  			// update with the same frequency as the runtime application
  			updateRenderOrder();
  #endif
  
  			var boxCollider = this.collider as BoxCollider;
  			if( boxCollider != null )
  			{
  				var size = this.GetScreenSize() * PixelsToUnits();
  				boxCollider.center = Vector3.zero;
  				boxCollider.size = size;
  			}
  
  		}
  
  		// The first active dfGUIManager instance is responsible for rendering
  		// all dfGUIManager instances. 
  		if( activeInstances[ 0 ] == this )
  		{
  
  			// Rebuild all dynamic font atlases (if needed) before rendering
  			dfFontManager.RebuildDynamicFonts();
  
  			var updateMaterials = false;
  
  			for( int i = 0; i < activeInstances.Count; i++ )
  			{
  
  				var instance = activeInstances[ i ];
  
  				if( instance.isDirty && instance.suspendCount <= 0 )
  				{
  
  					updateMaterials = true;
  
  					instance.abortRendering = false;
  					instance.isDirty = false;
  					instance.Render();
  
  				}
  
  			}
  
  			if( updateMaterials )
  			{
  
  				// Reset the material cache before rendering
  				dfMaterialCache.Reset();
  
  				updateDrawCalls();
  
  				for( int i = 0; i < activeInstances.Count; i++ )
  				{
  					activeInstances[ i ].updateDrawCalls();
  				}
  
  			}
  
  		}
  
  	}
  
  #if UNITY_EDITOR
  
  	[HideInInspector]
  	private bool isVisibleToSceneCamera()
  	{
  
  		var selected = UnityEditor.Selection.activeGameObject;
  		if( selected != null && selected.transform.IsChildOf( this.transform ) )
  			return true;
  
  		var bounds = this.collider.bounds;
  
  		var sceneCameras = UnityEditor.SceneView.GetAllSceneCameras();
  		for( int i = 0; i < sceneCameras.Length; i++ )
  		{
  			var frustum = GeometryUtility.CalculateFrustumPlanes( sceneCameras[ i ] );
  			if( GeometryUtility.TestPlanesAABB( frustum, bounds ) )
  			{
  				UnityEditor.SceneView.currentDrawingSceneView.Repaint();
  				return true;
  			}
  		}
  
  		return false;
  
  	}
  
  	[HideInInspector]
  	public void OnDrawGizmos()
  	{
  
  		if( meshRenderer != null )
  		{
  			var debugShowMesh = UnityEditor.EditorPrefs.GetBool( "dfGUIManager.ShowMesh", false );
  			UnityEditor.EditorUtility.SetSelectedWireframeHidden( meshRenderer, !debugShowMesh );
  		}
  
  		collider.hideFlags = HideFlags.HideInInspector;
  
  		// Calculate the screen size in "pixels"
  		var screenSize = GetScreenSize() * PixelsToUnits();
  
  		// Rendering a clear cube allows the user to click on the control
  		// in the Unity Editor Scene Manager
  		Gizmos.color = Color.clear;
  		var back = transform.forward * 0.02f;
  		Gizmos.DrawCube( transform.position + back, screenSize + Vector2.one * 150 * PixelsToUnits() );
  
  		// Render the outline of the view
  		Gizmos.color = new UnityEngine.Color( 0, 1, 0, 0.3f );
  		var corners = GetCorners();
  		for( int i = 0; i < corners.Length; i++ )
  		{
  			Gizmos.DrawLine( corners[ i ], corners[ ( i + 1 ) % corners.Length ] );
  		}
  
  		#region Show "Safe Area"
  
  		var showSafeArea = UnityEditor.EditorPrefs.GetBool( "ShowSafeArea", false );
  		if( showSafeArea )
  		{
  
  			var safeAreaMargin = UnityEditor.EditorPrefs.GetFloat( "SafeAreaMargin", 10f ) * 0.01f;
  
  			// Scale corners
  			var scale = 1f - safeAreaMargin;
  			var center = corners[ 0 ] + ( corners[ 2 ] - corners[ 0 ] ) * 0.5f;
  			for( int i = 0; i < corners.Length; i++ )
  			{
  				corners[ i ] = Vector3.Lerp( center, corners[ i ], scale );
  			}
  
  			Gizmos.color = new UnityEngine.Color( 0, 1, 0, 0.5f );
  			for( int i = 0; i < corners.Length; i++ )
  			{
  				Gizmos.DrawLine( corners[ i ], corners[ ( i + 1 ) % corners.Length ] );
  			}
  
  		}
  
  		#endregion
  
  		// If viewing the Game tab with gizmos on, there will be no 
  		// currentDrawingSceneView, so skip the rest of this function
  		if( UnityEditor.SceneView.currentDrawingSceneView == null )
  			return;
  
  		if( UnityEditor.EditorPrefs.GetBool( "dfGUIManager.ShowRulers", true ) )
  		{
  			drawRulers();
  		}
  
  		if( UnityEditor.EditorPrefs.GetBool( "dfGUIManager.ShowGrid", false ) )
  		{
  			drawGrid();
  		}
  
  		if( !Application.isPlaying && UnityEditor.EditorPrefs.GetBool( "dfGUIManager.ShowGuides", true ) )
  		{
  			drawDesignGuides();
  		}
  
  	}
  
  	[HideInInspector]
  	public void OnDrawGizmosSelected()
  	{
  
  		try
  		{
  			if( !UnityEditor.Selection.activeGameObject.transform.IsChildOf( this.transform ) )
  			{
  				return;
  			}
  		}
  		catch
  		{
  			return;
  		}
  
  		// Render the outline of the view
  		Gizmos.color = UnityEngine.Color.green;
  		var worldCorners = GetCorners();
  		for( var i = 0; i < worldCorners.Length; i++ )
  		{
  			Gizmos.DrawLine( worldCorners[ i ], worldCorners[ ( i + 1 ) % worldCorners.Length ] );
  		}
  
  	}
  
  	private void drawDesignGuides()
  	{
  
  		if( !UnityEditor.SceneView.currentDrawingSceneView.orthographic )
  			return;
  
  		var isSelected = UnityEditor.Selection.activeGameObject == this.gameObject;
  
  		var worldCorners = GetCorners();
  
  		for( var i = 0; i < guides.Count; i++ )
  		{
  
  			var color = Color.magenta;
  			if( UnityEditor.Selection.activeGameObject != this.gameObject )
  				color.a = 0.5f;
  			else
  				color.a = 0.7f;
  
  			Vector3 pos1;
  			Vector3 pos2;
  
  			var guide = guides[ i ];
  			if( guide.orientation == dfControlOrientation.Vertical )
  			{
  				pos1 = Vector3.Lerp( worldCorners[ 0 ], worldCorners[ 1 ], (float)guide.position / (float)FixedWidth );
  				pos2 = Vector3.Lerp( worldCorners[ 3 ], worldCorners[ 2 ], (float)guide.position / (float)FixedWidth );
  			}
  			else
  			{
  				pos1 = Vector3.Lerp( worldCorners[ 0 ], worldCorners[ 3 ], (float)guide.position / (float)FixedHeight );
  				pos2 = Vector3.Lerp( worldCorners[ 1 ], worldCorners[ 2 ], (float)guide.position / (float)FixedHeight );
  			}
  
  			var screenPos1 = UnityEditor.HandleUtility.WorldToGUIPoint( pos1 );
  			var screenPos2 = UnityEditor.HandleUtility.WorldToGUIPoint( pos2 );
  			if( isSelected && distanceFromLine( screenPos1, screenPos2, Event.current.mousePosition ) < 5 )
  			{
  				color.a = 1f;
  			}
  
  			Gizmos.color = color;
  			Gizmos.DrawLine( pos1, pos2 );
  
  		}
  
  	}
  
  	private static float distanceFromLine( Vector3 start, Vector3 end, Vector3 test )
  	{
  
  		var v = start - end;
  		var w = test - end;
  
  		var c1 = Vector3.Dot( w, v );
  		if( c1 <= 0 )
  			return Vector3.Distance( test, end );
  
  		var c2 = Vector3.Dot( v, v );
  		if( c2 <= c1 )
  			return Vector3.Distance( test, start );
  
  		var b = c1 / c2;
  		var pb = end + b * v;
  
  		return Vector3.Distance( test, pb );
  
  	}
  
  	private void drawGrid()
  	{
  
  		if( !UnityEditor.SceneView.currentDrawingSceneView.orthographic )
  			return;
  
  		var worldCorners = GetCorners();
  
  		const int SHOW_GRID_THRESHOLD = 512;
  
  		// If the on-screen view is too zoomed out, don't show the rulers
  		var screenUL = UnityEditor.HandleUtility.WorldToGUIPoint( worldCorners[ 0 ] );
  		var screenLR = UnityEditor.HandleUtility.WorldToGUIPoint( worldCorners[ 2 ] );
  		var screenSize = Vector3.Distance( screenUL, screenLR );
  		if( screenSize < SHOW_GRID_THRESHOLD )
  			return;
  
  		Gizmos.color = new UnityEngine.Color( 0, 1, 0, 0.075f );
  
  		var gridSize = UnityEditor.EditorPrefs.GetInt( "dfGUIManager.GridSize", 20 );
  		if( gridSize < 5 )
  			return;
  
  		for( var x = 0; x < FixedWidth; x += gridSize )
  		{
  
  			var pos1 = Vector3.Lerp( worldCorners[ 0 ], worldCorners[ 1 ], (float)x / (float)FixedWidth );
  			var pos2 = Vector3.Lerp( worldCorners[ 3 ], worldCorners[ 2 ], (float)x / (float)FixedWidth );
  
  			Gizmos.DrawLine( pos1, pos2 );
  
  		}
  
  		for( var y = 0; y < FixedHeight; y += gridSize )
  		{
  
  			var pos1 = Vector3.Lerp( worldCorners[ 0 ], worldCorners[ 3 ], (float)y / (float)FixedHeight );
  			var pos2 = Vector3.Lerp( worldCorners[ 1 ], worldCorners[ 2 ], (float)y / (float)FixedHeight );
  
  			Gizmos.DrawLine( pos1, pos2 );
  
  		}
  
  	}
  
  	private void drawRulers()
  	{
  
  		if( !UnityEditor.SceneView.currentDrawingSceneView.orthographic )
  			return;
  
  		var worldCorners = GetCorners();
  
  		const int SHOW_RULERS_THRESHOLD = 128;
  		const int SHOW_SMALL_THRESHOLD = 200;
  		const int INCREASE_LINESIZE_THRESHOLD = 768;
  		const int SHOW_TICKS_THRESHOLD = 1280;
  
  		// If the on-screen view is too zoomed out, don't show the rulers
  		var screenUL = UnityEditor.HandleUtility.WorldToGUIPoint( worldCorners[ 0 ] );
  		var screenLL = UnityEditor.HandleUtility.WorldToGUIPoint( worldCorners[ 3 ] );
  		var screenSize = Vector3.Distance( screenUL, screenLL );
  		if( screenSize < SHOW_RULERS_THRESHOLD )
  			return;
  
  		Gizmos.color = new UnityEngine.Color( 0, 1, 0, 0.5f );
  
  		var lerpMult = Mathf.Lerp( 3, 1, INCREASE_LINESIZE_THRESHOLD / screenSize );
  		var lineSize = Mathf.Lerp( 20f, 5f, SHOW_SMALL_THRESHOLD / screenSize ) / Vector3.Distance( screenUL, screenLL ) * lerpMult;
  
  
gt: 		var up = Vector3.up * lineSize;