This commit is contained in:
2021-06-13 10:28:03 +02:00
parent eb70603c85
commit df2d24cbd3
7487 changed files with 943244 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
using System;
using UnityEngine;
using UnityEditor.U2D.Path.GUIFramework;
namespace UnityEditor.U2D.Path
{
public class CreatePointAction : ClickAction
{
private Control m_PointControl;
public Func<IGUIState, Vector2, Vector3> guiToWorld;
public Action<int, Vector3> onCreatePoint;
public CreatePointAction(Control pointControl, Control edgeControl) : base(edgeControl, 0, false)
{
m_PointControl = pointControl;
}
protected override void OnTrigger(IGUIState guiState)
{
base.OnTrigger(guiState);
var index = hoveredControl.layoutData.index;
var position = GetMousePositionWorld(guiState);
if (onCreatePoint != null)
onCreatePoint(index, position);
guiState.nearestControl = m_PointControl.ID;
var data = m_PointControl.layoutData;
data.index = index + 1;
data.position = position;
data.distance = 0f;
m_PointControl.layoutData = data;
guiState.changed = true;
}
private Vector3 GetMousePositionWorld(IGUIState guiState)
{
if (guiToWorld != null)
return guiToWorld(guiState, guiState.mousePosition);
return guiState.GUIToWorld(guiState.mousePosition, hoveredControl.layoutData.forward, hoveredControl.layoutData.position);
}
}
}

View File

@@ -0,0 +1,118 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditor.U2D.Path.GUIFramework;
namespace UnityEditor.U2D.Path
{
public class Drawer : IDrawer
{
internal class Styles
{
public readonly GUIStyle pointNormalStyle;
public readonly GUIStyle pointHoveredStyle;
public readonly GUIStyle pointSelectedStyle;
public readonly GUIStyle pointPreviewStyle;
public readonly GUIStyle tangentNormalStyle;
public readonly GUIStyle tangentHoveredStyle;
public Styles()
{
var pointNormal = AssetDatabase.LoadAssetAtPath<Texture2D>("Packages/com.unity.2d.path/Editor/Handles/Path/pointNormal.png");
var pointHovered = AssetDatabase.LoadAssetAtPath<Texture2D>("Packages/com.unity.2d.path/Editor/Handles/Path/pointHovered.png");
var pointSelected = AssetDatabase.LoadAssetAtPath<Texture2D>("Packages/com.unity.2d.path/Editor/Handles/Path/pointSelected.png");
var pointPreview = AssetDatabase.LoadAssetAtPath<Texture2D>("Packages/com.unity.2d.path/Editor/Handles/Path/pointPreview.png");
var tangentNormal = AssetDatabase.LoadAssetAtPath<Texture2D>("Packages/com.unity.2d.path/Editor/Handles/Path/tangentNormal.png");
pointNormalStyle = CreateStyle(AssetDatabase.LoadAssetAtPath<Texture2D>("Packages/com.unity.2d.path/Editor/Handles/Path/pointNormal.png"), Vector2.one * 12f);
pointHoveredStyle = CreateStyle(AssetDatabase.LoadAssetAtPath<Texture2D>("Packages/com.unity.2d.path/Editor/Handles/Path/pointHovered.png"), Vector2.one * 12f);
pointSelectedStyle = CreateStyle(AssetDatabase.LoadAssetAtPath<Texture2D>("Packages/com.unity.2d.path/Editor/Handles/Path/pointSelected.png"), Vector2.one * 12f);
pointPreviewStyle = CreateStyle(AssetDatabase.LoadAssetAtPath<Texture2D>("Packages/com.unity.2d.path/Editor/Handles/Path/pointPreview.png"), Vector2.one * 12f);
tangentNormalStyle = CreateStyle(AssetDatabase.LoadAssetAtPath<Texture2D>("Packages/com.unity.2d.path/Editor/Handles/Path/tangentNormal.png"), Vector2.one * 8f);
tangentHoveredStyle = CreateStyle(AssetDatabase.LoadAssetAtPath<Texture2D>("Packages/com.unity.2d.path/Editor/Handles/Path/pointHovered.png"), Vector2.one * 10f);
}
private GUIStyle CreateStyle(Texture2D texture, Vector2 size)
{
var guiStyle = new GUIStyle();
guiStyle.normal.background = texture;
guiStyle.fixedWidth = size.x;
guiStyle.fixedHeight = size.y;
return guiStyle;
}
}
private IGUIState m_GUIState = new GUIState();
private Styles m_Styles;
private Styles styles
{
get
{
if (m_Styles == null)
m_Styles = new Styles();
return m_Styles;
}
}
public void DrawCreatePointPreview(Vector3 position)
{
DrawGUIStyleCap(0, position, Quaternion.identity, m_GUIState.GetHandleSize(position), styles.pointPreviewStyle);
}
public void DrawPoint(Vector3 position)
{
DrawGUIStyleCap(0, position, Quaternion.identity, m_GUIState.GetHandleSize(position), styles.pointNormalStyle);
}
public void DrawPointHovered(Vector3 position)
{
DrawGUIStyleCap(0, position, Quaternion.identity, m_GUIState.GetHandleSize(position), styles.pointHoveredStyle);
}
public void DrawPointSelected(Vector3 position)
{
DrawGUIStyleCap(0, position, Quaternion.identity, m_GUIState.GetHandleSize(position), styles.pointSelectedStyle);
}
public void DrawLine(Vector3 p1, Vector3 p2, float width, Color color)
{
Handles.color = color;
Handles.DrawAAPolyLine(width, new Vector3[] { p1, p2 });
}
public void DrawBezier(Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4, float width, Color color)
{
Handles.color = color;
Handles.DrawBezier(p1, p4, p2, p3, color, null, width);
}
public void DrawTangent(Vector3 position, Vector3 tangent)
{
DrawLine(position, tangent, 3f, Color.yellow);
DrawGUIStyleCap(0, tangent, Quaternion.identity, m_GUIState.GetHandleSize(tangent), styles.tangentNormalStyle);
}
private void DrawGUIStyleCap(int controlID, Vector3 position, Quaternion rotation, float size, GUIStyle guiStyle)
{
if (Camera.current && Vector3.Dot(position - Camera.current.transform.position, Camera.current.transform.forward) < 0f)
return;
Handles.BeginGUI();
guiStyle.Draw(GetGUIStyleRect(guiStyle, position), GUIContent.none, controlID);
Handles.EndGUI();
}
private Rect GetGUIStyleRect(GUIStyle style, Vector3 position)
{
Vector2 vector = HandleUtility.WorldToGUIPoint(position);
float fixedWidth = style.fixedWidth;
float fixedHeight = style.fixedHeight;
return new Rect(vector.x - fixedWidth / 2f, vector.y - fixedHeight / 2f, fixedWidth, fixedHeight);
}
}
}

View File

@@ -0,0 +1,81 @@
using System;
using UnityEngine;
namespace UnityEditor.U2D.Path.GUIFramework
{
/// <summary>
/// Represents an Action to process when the user clicks a particular mouse button a certain number of times.
/// </summary>
public class ClickAction : HoveredControlAction
{
private int m_Button;
private bool m_UseEvent;
/// <summary>
/// The number of button clicks required to satisfy the trigger condition
/// </summary>
public int clickCount = 1;
/// <summary>
/// The Action to execute when the user satisfies the trigger condition.
/// </summary>
public Action<IGUIState, Control> onClick;
private int m_ClickCounter = 0;
/// <summary>
/// Initializes and returns an instance of ClickAction
/// </summary>
/// <param name="control">Current control</param>
/// <param name="button">The mouse button to check for.</param>
/// <param name="useEvent">Whether to Use the current event after the trigger condition has been met.</param>
public ClickAction(Control control, int button, bool useEvent = true) : base(control)
{
m_Button = button;
m_UseEvent = useEvent;
}
/// <summary>
/// Checks to see if the trigger condition has been met or not.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
/// <returns>Returns `true` if the trigger condition has been met. Otherwise, returns false.</returns>
protected override bool GetTriggerContidtion(IGUIState guiState)
{
if (guiState.mouseButton == m_Button && guiState.eventType == EventType.MouseDown)
{
if (guiState.clickCount == 1)
m_ClickCounter = 0;
++m_ClickCounter;
if (m_ClickCounter == clickCount)
return true;
}
return false;
}
/// <summary>
/// Calls the methods in its invocation list when the trigger conditions are met.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
protected override void OnTrigger(IGUIState guiState)
{
base.OnTrigger(guiState);
if (onClick != null)
onClick(guiState, hoveredControl);
if (m_UseEvent)
guiState.UseEvent();
}
/// <summary>
/// Checks to see if the finish condition has been met or not. For a ClickAction, this is always `true`.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
/// <returns>Returns `true`.</returns>
protected override bool GetFinishContidtion(IGUIState guiState)
{
return true;
}
}
}

View File

@@ -0,0 +1,70 @@
using System;
using UnityEngine;
namespace UnityEditor.U2D.Path.GUIFramework
{
/// <summary>
/// Represents an Action to process when the custom editor validates a command.
/// </summary>
public class CommandAction : GUIAction
{
private string m_CommandName;
/// <summary>
/// The Action to execute.
/// </summary>
public Action<IGUIState> onCommand;
/// <summary>
/// Initializes and returns an instance of CommandAction
/// </summary>
/// <param name="commandName">The name of the command. When the custom editor validates a command with this name, it triggers the action.</param>
public CommandAction(string commandName)
{
m_CommandName = commandName;
}
/// <summary>
/// Checks to see if the trigger condition has been met or not.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
/// <returns>Returns `true` if the trigger condition has been met. Otherwise, returns `false`.</returns>
protected override bool GetTriggerContidtion(IGUIState guiState)
{
if (guiState.eventType == EventType.ValidateCommand && guiState.commandName == m_CommandName)
{
guiState.UseEvent();
return true;
}
return false;
}
/// <summary>
/// Checks to see if the finish condition has been met or not.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
/// <returns>Returns `true` if the trigger condition is finished. Otherwise, returns `false`.</returns>
protected override bool GetFinishContidtion(IGUIState guiState)
{
if (guiState.eventType == EventType.ExecuteCommand && guiState.commandName == m_CommandName)
{
guiState.UseEvent();
return true;
}
return false;
}
/// <summary>
/// Calls the methods in its invocation list when the finish condition is met.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
protected override void OnFinish(IGUIState guiState)
{
if (onCommand != null)
onCommand(guiState);
}
}
}

View File

@@ -0,0 +1,250 @@
using System;
using UnityEngine;
namespace UnityEditor.U2D.Path.GUIFramework
{
/// <summary>
/// Represents a UI control in a custom editor.
/// </summary>
public abstract class Control
{
private string m_Name;
private int m_NameHashCode;
private int m_ID;
private LayoutData m_LayoutData;
private int m_ActionID = -1;
private LayoutData m_HotLayoutData;
/// <summary>
/// The name of the control.
/// </summary>
public string name
{
get { return m_Name; }
}
/// <summary>
/// The control ID. The GUI uses this to identify the control.
/// </summary>
public int ID
{
get { return m_ID; }
}
/// <summary>
/// The action ID.
/// </summary>
public int actionID
{
get { return m_ActionID; }
}
/// <summary>
/// The control's layout data. This contains information about the control's position and orientation.
/// </summary>
public LayoutData layoutData
{
get { return m_LayoutData; }
set { m_LayoutData = value; }
}
/// <summary>
/// The control's hot layout data
/// </summary>
public LayoutData hotLayoutData
{
get { return m_HotLayoutData; }
}
/// <summary>
/// Initializes and returns an instance of Control
/// </summary>
/// <param name="name">The name of the control</param>
public Control(string name)
{
m_Name = name;
m_NameHashCode = name.GetHashCode();
}
/// <summary>
/// Gets the control from the guiState.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
public void GetControl(IGUIState guiState)
{
m_ID = guiState.GetControlID(m_NameHashCode, FocusType.Passive);
}
internal void SetActionID(int actionID)
{
m_ActionID = actionID;
m_HotLayoutData = m_LayoutData;
}
/// <summary>
/// Begins the layout for this control. A call to EndLayout must always follow a call to this function.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
public void BeginLayout(IGUIState guiState)
{
Debug.Assert(guiState.eventType == EventType.Layout);
m_LayoutData = OnBeginLayout(LayoutData.zero, guiState);
}
/// <summary>
/// Gets the control's layout data from the guiState.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
public void Layout(IGUIState guiState)
{
Debug.Assert(guiState.eventType == EventType.Layout);
for (var i = 0; i < GetCount(); ++i)
{
if (guiState.hotControl == actionID && hotLayoutData.index == i)
continue;
var layoutData = new LayoutData()
{
index = i,
position = GetPosition(guiState, i),
distance = GetDistance(guiState, i),
forward = GetForward(guiState, i),
up = GetUp(guiState, i),
right = GetRight(guiState, i),
userData = GetUserData(guiState, i)
};
m_LayoutData = LayoutData.Nearest(m_LayoutData, layoutData);
}
}
/// <summary>
/// Ends the layout for this control. This function must always follow a call to BeginLayout().
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
public void EndLayout(IGUIState guiState)
{
Debug.Assert(guiState.eventType == EventType.Layout);
OnEndLayout(guiState);
}
/// <summary>
/// Repaints the control.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
public void Repaint(IGUIState guiState)
{
for (var i = 0; i < GetCount(); ++i)
OnRepaint(guiState, i);
}
/// <summary>
/// Called when the control begins its layout.
/// </summary>
/// <param name="data">The layout data.</param>
/// <param name="guiState">The current state of the custom editor.</param>
/// <returns>Returns the layout data to use.</returns>
protected virtual LayoutData OnBeginLayout(LayoutData data, IGUIState guiState)
{
return data;
}
/// <summary>
/// Called when the control ends its layout.
/// /// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
protected virtual void OnEndLayout(IGUIState guiState)
{
}
/// <summary>
/// Called when the control repaints its contents.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
/// <param name="index">The index.</param>
protected virtual void OnRepaint(IGUIState guiState, int index)
{
}
/// <summary>
/// Gets the number of sub-controllers.
/// </summary>
/// <remarks>
/// By default, this is `1`. If you implement your own controller and want to use multiple sub-controllers within it, you can override this function to declare how to count the sub-controllers.
/// </remarks>
/// <returns>Returns the number of sub-controllers. If you do not override this function, this returns 1.</returns>
protected virtual int GetCount()
{
return 1;
}
/// <summary>
/// Gets the position of the control.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
/// <param name="index">The index.</param>
/// <returns>Returns Vector3.zero.</returns>
protected virtual Vector3 GetPosition(IGUIState guiState, int index)
{
return Vector3.zero;
}
/// <summary>
/// Gets the forward vector of the control.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
/// <param name="index">The index.</param>
/// <returns>Returns Vector3.forward.</returns>
protected virtual Vector3 GetForward(IGUIState guiState, int index)
{
return Vector3.forward;
}
/// <summary>
/// Gets the up vector of the control.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
/// <param name="index">The index.</param>
/// <returns>Returns Vector3.up,</returns>
protected virtual Vector3 GetUp(IGUIState guiState, int index)
{
return Vector3.up;
}
/// <summary>
/// Gets the right vector of the control.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
/// <param name="index">The index.</param>
/// <returns>Returns Vector3.right.</returns>
protected virtual Vector3 GetRight(IGUIState guiState, int index)
{
return Vector3.right;
}
/// <summary>
/// Gets the distance from the Scene view camera to the control.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
/// <param name="index">The index.</param>
/// <returns>Returns layoutData.distance.</returns>
protected virtual float GetDistance(IGUIState guiState, int index)
{
return layoutData.distance;
}
/// <summary>
/// Gets the control's user data.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
/// <param name="index">The index.</param>
/// <returns>Returns `null`.</returns>
protected virtual object GetUserData(IGUIState guiState, int index)
{
return null;
}
}
}

View File

@@ -0,0 +1,39 @@
using System;
using UnityEngine;
namespace UnityEditor.U2D.Path.GUIFramework
{
/// <summary>
/// Represents the default implementation of a control.
/// </summary>
public abstract class DefaultControl : Control
{
/// <summary>
/// Default kPickDistance == 5.0f
/// </summary>
public static readonly float kPickDistance = 5f;
/// <summary>
/// Initializes and returns an instance of DefaultControl
/// </summary>
/// <param name="name">The name of the default control.</param>
public DefaultControl(string name) : base(name)
{
}
/// <summary>
/// Overrides the Control.OnBeginLayout function.
/// </summary>
/// <remarks>
/// Sets the LayoutData.distance to DefaultControl.kPickDistance.
/// </remarks>
/// <param name="data">The layout data.</param>
/// <param name="guiState">The current state of the custom editor.</param>
/// <returns>Returns the modified layout data.</returns>
protected override LayoutData OnBeginLayout(LayoutData data, IGUIState guiState)
{
data.distance = kPickDistance;
return data;
}
}
}

View File

@@ -0,0 +1,184 @@
using System;
using UnityEngine;
namespace UnityEditor.U2D.Path.GUIFramework
{
/// <summary>
/// Represents an action that is tied to a GUI element.
/// </summary>
public abstract class GUIAction
{
private int m_ID = -1;
/// <summary>
/// Func for GetEnable
/// </summary>
public Func<IGUIState, GUIAction, bool> enable;
/// <summary>
/// Func for EnabledRepaint
/// </summary>
public Func<IGUIState, GUIAction, bool> enableRepaint;
/// <summary>
/// Func for repaintOnMouseMove
/// </summary>
public Func<IGUIState, GUIAction, bool> repaintOnMouseMove;
/// <summary>
/// Action for OnPreRepaint
/// </summary>
public Action<IGUIState, GUIAction> onPreRepaint;
/// <summary>
/// Func for OnRepaint
/// </summary>
public Action<IGUIState, GUIAction> onRepaint;
/// <summary>
/// The action ID.
/// </summary>
public int ID
{
get { return m_ID; }
}
/// <summary>
/// Calls the methods in its invocation list when Unity draws this GUIAction's GUI.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
public void OnGUI(IGUIState guiState)
{
m_ID = guiState.GetControlID(GetType().GetHashCode(), FocusType.Passive);
if (guiState.hotControl == 0 && IsEnabled(guiState) && CanTrigger(guiState) && GetTriggerContidtion(guiState))
{
guiState.hotControl = ID;
OnTrigger(guiState);
}
if (guiState.hotControl == ID)
{
if (GetFinishContidtion(guiState))
{
OnFinish(guiState);
guiState.hotControl = 0;
}
else
{
OnPerform(guiState);
}
}
if (guiState.eventType == EventType.Repaint && IsRepaintEnabled(guiState))
Repaint(guiState);
}
/// <summary>
/// Checks whether the GUIAction is enabled.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
/// <returns>Returns `true` if the GUIAction is enabled in the custom editor. Otherwise, returns `false`.</returns>
public bool IsEnabled(IGUIState guiState)
{
if (enable != null)
return enable(guiState, this);
return true;
}
/// <summary>
/// Checks whether the GUIAction should repaint.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
/// <returns>Returns `true` if the GUIAction should repaint. Otherwise, returns `false`.</returns>
public bool IsRepaintEnabled(IGUIState guiState)
{
if (!IsEnabled(guiState))
return false;
if (enableRepaint != null)
return enableRepaint(guiState, this);
return true;
}
/// <summary>
/// Preprocessing that occurs before the GUI repaints.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
public void PreRepaint(IGUIState guiState)
{
Debug.Assert(guiState.eventType == EventType.Repaint);
if (IsEnabled(guiState) && onPreRepaint != null)
onPreRepaint(guiState, this);
}
/// <summary>
/// Calls the methods in its invocation list when repainting the GUI.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
private void Repaint(IGUIState guiState)
{
Debug.Assert(guiState.eventType == EventType.Repaint);
if (onRepaint != null)
onRepaint(guiState, this);
}
/// <summary>
/// Checks whether the GUI should repaint if the mouse moves over it.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
/// <returns>Returns `true` if the GUI should repaint if the moves moves over it. Otherwise, returns `false`.</returns>
internal bool IsRepaintOnMouseMoveEnabled(IGUIState guiState)
{
if (!IsEnabled(guiState) || !IsRepaintEnabled(guiState))
return false;
if (repaintOnMouseMove != null)
return repaintOnMouseMove(guiState, this);
return false;
}
/// <summary>
/// Determines whether the finish condition has been met.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
/// <returns>Returns `true` if finish condition has been met. Otherwise, returns `false`.</returns>
protected abstract bool GetFinishContidtion(IGUIState guiState);
/// <summary>
/// Determines whether the trigger condition has been met.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
/// <returns>Returns `true` if finish condition has been met. Otherwise, returns `false`.</returns>
protected abstract bool GetTriggerContidtion(IGUIState guiState);
/// <summary>
/// Determines whether the GUIAction can trigger.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
/// <returns>Always returns `true`.</returns>
protected virtual bool CanTrigger(IGUIState guiState) { return true; }
/// <summary>
/// Calls the methods in its invocation list when triggered.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
protected virtual void OnTrigger(IGUIState guiState)
{
}
/// <summary>
/// Calls the methods in its invocation list when performed.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
protected virtual void OnPerform(IGUIState guiState)
{
}
/// <summary>
/// Calls the methods in its invocation list when finished.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
protected virtual void OnFinish(IGUIState guiState)
{
}
}
}

View File

@@ -0,0 +1,245 @@
using UnityEngine;
using UnityEditor;
namespace UnityEditor.U2D.Path.GUIFramework
{
/// <summary>
/// An implementation of an IGUIState that represents a generic GUI state.
/// </summary>
public class GUIState : IGUIState
{
private Handles.CapFunction nullCap = (int c, Vector3 p , Quaternion r, float s, EventType ev) => {};
/// <summary>
/// The current mouse position.
/// </summary>
public Vector2 mousePosition
{
get { return Event.current.mousePosition; }
}
/// <summary>
/// The currently pressed button.
/// </summary>
public int mouseButton
{
get { return Event.current.button; }
}
/// <summary>
/// The current number of mouse clicks.
/// </summary>
public int clickCount
{
get { return Event.current.clickCount; }
set { Event.current.clickCount = Mathf.Max(0, value); }
}
/// <summary>
/// Indicates whether the shift key is pressed.
/// </summary>
public bool isShiftDown
{
get { return Event.current.shift; }
}
/// <summary>
/// Indicates whether the alt key is pressed.
/// </summary>
public bool isAltDown
{
get { return Event.current.alt; }
}
/// <summary>
/// Indicates whether the action key is pressed.
/// </summary>
public bool isActionKeyDown
{
get { return EditorGUI.actionKey; }
}
/// <summary>
/// The KeyCode of the currently pressed key.
/// </summary>
public KeyCode keyCode
{
get { return Event.current.keyCode; }
}
/// <summary>
/// The type of the current event.
/// </summary>
public EventType eventType
{
get { return Event.current.type; }
}
/// <summary>
/// The name of the current event's command.
/// </summary>
public string commandName
{
get { return Event.current.commandName; }
}
/// <summary>
/// The closest control to the event.
/// </summary>
public int nearestControl
{
get { return HandleUtility.nearestControl; }
set { HandleUtility.nearestControl = value; }
}
/// <summary>
/// Hot Control
/// </summary>
public int hotControl
{
get { return GUIUtility.hotControl; }
set { GUIUtility.hotControl = value; }
}
/// <summary>
/// Indicates whether the GUI has changed.
/// </summary>
public bool changed
{
get { return GUI.changed; }
set { GUI.changed = value; }
}
/// <summary>
/// Gets the ID of a nested control by a hint and focus type.
/// </summary>
/// <param name="hint">The hint this function uses to identify the control ID.</param>
/// <param name="focusType">The focus Type</param>
/// <returns>Returns the ID of the control that matches the hint and focus type.</returns>
public int GetControlID(int hint, FocusType focusType)
{
return GUIUtility.GetControlID(hint, focusType);
}
/// <summary>
/// Adds a control to the GUIState.
/// </summary>
/// <param name="controlID">The ID of the control to add.</param>
/// <param name="distance">The distance from the camera to the control.</param>
public void AddControl(int controlID, float distance)
{
HandleUtility.AddControl(controlID, distance);
}
/// <summary>
/// Checks whether a slider value has changed.
/// </summary>
/// <param name="id">The ID of the slider to check.</param>
/// <param name="sliderData">The slider's data.</param>
/// <param name="newPosition">The new position of the slider.</param>
/// <returns>Returns `true` if the slider has changed. Otherwise, returns `false`.</returns>
public bool Slider(int id, SliderData sliderData, out Vector3 newPosition)
{
if (mouseButton == 0 && eventType == EventType.MouseDown)
{
hotControl = 0;
nearestControl = id;
}
EditorGUI.BeginChangeCheck();
newPosition = Handles.Slider2D(id, sliderData.position, sliderData.forward, sliderData.right, sliderData.up, 1f, nullCap, Vector2.zero);
return EditorGUI.EndChangeCheck();
}
/// <summary>
/// Uses the current event.
/// </summary>
public void UseEvent()
{
Event.current.Use();
}
/// <summary>
/// Repaints the GUI.
/// </summary>
public void Repaint()
{
HandleUtility.Repaint();
}
/// <summary>
/// Checks if the current camera is valid.
/// </summary>
/// <returns>Returns `true` if the current camera is not null. Otherwise, returns `false`.</returns>
public bool HasCurrentCamera()
{
return Camera.current != null;
}
/// <summary>
/// Gets the size of the handle.
/// </summary>
/// <param name="position">The position of the handle.</param>
/// <returns>Returns the size of the handle.</returns>
public float GetHandleSize(Vector3 position)
{
var scale = HasCurrentCamera() ? 0.01f : 0.05f;
return HandleUtility.GetHandleSize(position) * scale;
}
/// <summary>
/// Measures the GUI-space distance between two points of a segment.
/// </summary>
/// <param name="p1">The first point.</param>
/// <param name="p2">The seconde point.</param>
/// <returns>Returns the GUI-space distance between p1 and p2.</returns>
public float DistanceToSegment(Vector3 p1, Vector3 p2)
{
p1 = HandleUtility.WorldToGUIPoint(p1);
p2 = HandleUtility.WorldToGUIPoint(p2);
return HandleUtility.DistancePointToLineSegment(Event.current.mousePosition, p1, p2);
}
/// <summary>
/// Measures the distance to a circle.
/// </summary>
/// <param name="center">The center of the circle.</param>
/// <param name="radius">The radius of the circle.</param>
/// <returns>Returns the distance to a circle with the specified center and radius.</returns>
public float DistanceToCircle(Vector3 center, float radius)
{
return HandleUtility.DistanceToCircle(center, radius);
}
/// <summary>
/// Transforms a GUI-space position into world space.
/// </summary>
/// <param name="guiPosition">The GUI position</param>
/// <param name="planeNormal">The plane normal.</param>
/// <param name="planePos">The plane position.</param>
/// <returns>Returns the world-space position of `guiPosition`.</returns>
public Vector3 GUIToWorld(Vector2 guiPosition, Vector3 planeNormal, Vector3 planePos)
{
Vector3 worldPos = Handles.inverseMatrix.MultiplyPoint(guiPosition);
if (Camera.current)
{
Ray ray = HandleUtility.GUIPointToWorldRay(guiPosition);
planeNormal = Handles.matrix.MultiplyVector(planeNormal);
planePos = Handles.matrix.MultiplyPoint(planePos);
Plane plane = new Plane(planeNormal, planePos);
float distance = 0f;
if (plane.Raycast(ray, out distance))
{
worldPos = Handles.inverseMatrix.MultiplyPoint(ray.GetPoint(distance));
}
}
return worldPos;
}
}
}

View File

@@ -0,0 +1,154 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.U2D.Path.GUIFramework
{
/// <summary>
/// Represents a system of GUI elements and controls.
/// </summary>
public class GUISystem
{
private readonly int kControlIDCheckHashCode = "ControlIDCheckHashCode".GetHashCode();
private List<Control> m_Controls = new List<Control>();
private List<GUIAction> m_Actions = new List<GUIAction>();
private IGUIState m_GUIState;
private int m_PrevNearestControl = -1;
private LayoutData m_PrevNearestLayoutData = LayoutData.zero;
private int m_ControlIDCheck = -1;
/// <summary>
/// Initializes and returns an instance of GUISystem
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
public GUISystem(IGUIState guiState)
{
m_GUIState = guiState;
}
/// <summary>
/// Adds a control to the internal list of controls.
/// </summary>
/// <param name="control">The control to add.</param>
public void AddControl(Control control)
{
if (control == null)
throw new NullReferenceException("Control is null");
m_Controls.Add(control);
}
/// <summary>
/// Removes a control from the internal list of controls.
/// </summary>
/// <param name="control">The control to remove.</param>
public void RemoveControl(Control control)
{
m_Controls.Remove(control);
}
/// <summary>
/// Adds an action to the internal list of actions.
/// </summary>
/// <param name="action">The action to add.</param>
public void AddAction(GUIAction action)
{
if (action == null)
throw new NullReferenceException("Action is null");
m_Actions.Add(action);
}
/// <summary>
/// Removes an action from the internal list of actions.
/// </summary>
/// <param name="action">The action to remove.</param>
public void RemoveAction(GUIAction action)
{
m_Actions.Remove(action);
}
/// <summary>
/// Calls the methods in its invocation list when Unity draws this GUISystems's GUI.
/// </summary>
public void OnGUI()
{
var controlIDCheck = m_GUIState.GetControlID(kControlIDCheckHashCode, FocusType.Passive);
if (m_GUIState.eventType == EventType.Layout)
m_ControlIDCheck = controlIDCheck;
else if (m_GUIState.eventType != EventType.Used && m_ControlIDCheck != controlIDCheck)
Debug.LogWarning("GetControlID at event " + m_GUIState.eventType + " returns a controlID different from the one in Layout event");
var nearestLayoutData = LayoutData.zero;
foreach (var control in m_Controls)
control.GetControl(m_GUIState);
if (m_GUIState.eventType == EventType.Layout)
{
foreach (var control in m_Controls)
control.BeginLayout(m_GUIState);
foreach (var control in m_Controls)
{
control.Layout(m_GUIState);
nearestLayoutData = LayoutData.Nearest(nearestLayoutData, control.layoutData);
}
foreach (var control in m_Controls)
m_GUIState.AddControl(control.ID, control.layoutData.distance);
foreach (var control in m_Controls)
control.EndLayout(m_GUIState);
if (m_PrevNearestControl == m_GUIState.nearestControl)
{
if (nearestLayoutData.index != m_PrevNearestLayoutData.index)
m_GUIState.Repaint();
}
else
{
m_PrevNearestControl = m_GUIState.nearestControl;
m_GUIState.Repaint();
}
m_PrevNearestLayoutData = nearestLayoutData;
}
if (m_GUIState.eventType == EventType.Repaint)
{
foreach (var action in m_Actions)
if (action.IsRepaintEnabled(m_GUIState))
action.PreRepaint(m_GUIState);
foreach (var control in m_Controls)
control.Repaint(m_GUIState);
}
var repaintOnMouseMove = false;
foreach (var action in m_Actions)
{
if (IsMouseMoveEvent())
repaintOnMouseMove |= action.IsRepaintOnMouseMoveEnabled(m_GUIState);
action.OnGUI(m_GUIState);
}
if (repaintOnMouseMove)
m_GUIState.Repaint();
}
/// <summary>
/// Calls the methods in its invocation list when the mouse moves.
/// </summary>
/// <returns>Returns `true` if the mouse moved. Otherwise, returns `false`.</returns>
private bool IsMouseMoveEvent()
{
return m_GUIState.eventType == EventType.MouseMove || m_GUIState.eventType == EventType.MouseDrag;
}
}
}

View File

@@ -0,0 +1,195 @@
using System;
using UnityEngine;
namespace UnityEditor.U2D.Path.GUIFramework
{
/// <summary>
/// Represents a generic UI control.
/// </summary>
public class GenericControl : Control
{
/// <summary>
/// Func for OnBeginLayout
/// </summary>
public Func<IGUIState, LayoutData> onBeginLayout;
/// <summary>
/// Action for OnEndLayout
/// </summary>
public Action<IGUIState> onEndLayout;
/// <summary>
/// Action for OnRepaint
/// </summary>
public Action<IGUIState, Control, int> onRepaint;
/// <summary>
/// Func for GetCount
/// </summary>
public Func<int> count;
/// <summary>
/// Func for GetPosition
/// </summary>
public Func<int, Vector3> position;
/// <summary>
/// Func for GetDistance
/// </summary>
public Func<IGUIState, int, float> distance;
/// <summary>
/// Func for GetForward
/// </summary>
public Func<int, Vector3> forward;
/// <summary>
/// Func for GetUp
/// </summary>
public Func<int, Vector3> up;
/// <summary>
/// Func for GetRight
/// </summary>
public Func<int, Vector3> right;
/// <summary>
/// Func for GetUserData
/// </summary>
public Func<int, object> userData;
/// <summary>
/// Initializes and returns an instance of GenericControl
/// </summary>
/// <param name="name">The name of the generic control.</param>
public GenericControl(string name) : base(name)
{
}
/// <summary>
/// Gets the number of sub-controllers.
/// </summary>
/// <remarks>
/// By default, this is `1`. If you implement your own controller and want to use multiple sub-controllers within it, you can assign getCount to a function that returns the number of sub-controllers.
/// </remarks>
/// <returns>Returns the number of sub-controllers. If you do not assign getCount, this returns 1.</returns>
protected override int GetCount()
{
if (count != null)
return count();
return base.GetCount();
}
/// <summary>
/// Called when the control ends its layout.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
protected override void OnEndLayout(IGUIState guiState)
{
if (onEndLayout != null)
onEndLayout(guiState);
}
/// <summary>
/// Called when the control repaints its contents.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
/// <param name="index">Current Index</param>
protected override void OnRepaint(IGUIState guiState, int index)
{
if (onRepaint != null)
onRepaint(guiState, this, index);
}
/// <summary>
/// Called when the control begins its layout.
/// </summary>
/// <param name="data">The layout data.</param>
/// <param name="guiState">The current state of the custom editor.</param>
/// <returns>The LayoutData</returns>
protected override LayoutData OnBeginLayout(LayoutData data, IGUIState guiState)
{
if (onBeginLayout != null)
return onBeginLayout(guiState);
return data;
}
/// <summary>
/// Gets the position of the control.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
/// <param name="index">The Index</param>
/// <returns>The position</returns>
protected override Vector3 GetPosition(IGUIState guiState, int index)
{
if (position != null)
return position(index);
return base.GetPosition(guiState,index);
}
/// <summary>
/// Gets the distance from the Scene view camera to the control.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
/// <param name="index">The Index</param>
/// <returns>Returns the distance from the Scene view camera to the control.</returns>
protected override float GetDistance(IGUIState guiState, int index)
{
if (distance != null)
return distance(guiState, index);
return base.GetDistance(guiState, index);
}
/// <summary>
/// Gets the forward vector of the control.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
/// <param name="index">The Index</param>
/// <returns>Returns the generic control's forward vector.</returns>
protected override Vector3 GetForward(IGUIState guiState, int index)
{
if (forward != null)
return forward(index);
return base.GetForward(guiState,index);
}
/// <summary>
/// Gets the up vector of the control.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
/// <param name="index">The Index</param>
/// <returns>Returns the generic control's up vector.</returns>
protected override Vector3 GetUp(IGUIState guiState, int index)
{
if (up != null)
return up(index);
return base.GetUp(guiState,index);
}
/// <summary>
/// Gets the right vector of the control.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
/// <param name="index">The Index</param>
/// <returns>Returns the generic control's right vector.</returns>
protected override Vector3 GetRight(IGUIState guiState, int index)
{
if (right != null)
return right(index);
return base.GetRight(guiState,index);
}
/// <summary>
/// Override for GetUserData
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
/// <param name="index">The Index</param>
/// <returns>Return the user data</returns>
protected override object GetUserData(IGUIState guiState, int index)
{
if (userData != null)
return userData(index);
return base.GetUserData(guiState,index);
}
}
}

View File

@@ -0,0 +1,110 @@
using System;
using UnityEngine;
namespace UnityEditor.U2D.Path.GUIFramework
{
/// <summary>
/// Represents a default generic UI control.
/// </summary>
public class GenericDefaultControl : DefaultControl
{
/// <summary>
/// Func for GetPosition
/// </summary>
public Func<IGUIState, Vector3> position;
/// <summary>
/// Func for GetForward
/// </summary>
public Func<IGUIState, Vector3> forward;
/// <summary>
/// Func for GetUp
/// </summary>
public Func<IGUIState, Vector3> up;
/// <summary>
/// Func for GetRight
/// </summary>
public Func<IGUIState, Vector3> right;
/// <summary>
/// Func for GetUserData
/// </summary>
public Func<IGUIState, object> userData;
/// <summary>
/// Initializes and returns an instance of GenericDefaultControl
/// </summary>
/// <param name="name">The name of the generic default control.</param>
public GenericDefaultControl(string name) : base(name)
{
}
/// <summary>
/// Gets the distance from the Scene view camera to the control.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
/// <param name="index">The Index</param>
/// <returns>The distance from the Scene view camera to the control.</returns>
protected override Vector3 GetPosition(IGUIState guiState, int index)
{
if (position != null)
return position(guiState);
return base.GetPosition(guiState, index);
}
/// <summary>
/// Gets the forward vector of the control.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
/// <param name="index">The Index</param>
/// <returns>Returns the generic control's forward vector.</returns>
protected override Vector3 GetForward(IGUIState guiState, int index)
{
if (forward != null)
return forward(guiState);
return base.GetForward(guiState, index);
}
/// <summary>
/// Gets the up vector of the control.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
/// <param name="index">The Index</param>
/// <returns>Returns the generic control's up vector.</returns>
protected override Vector3 GetUp(IGUIState guiState, int index)
{
if (up != null)
return up(guiState);
return base.GetUp(guiState, index);
}
/// <summary>
/// Gets the right vector of the control.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
/// <param name="index">The Index</param>
/// <returns>Returns the generic control's right vector.</returns>
protected override Vector3 GetRight(IGUIState guiState, int index)
{
if (right != null)
return right(guiState);
return base.GetRight(guiState, index);
}
/// <summary>
/// Gets the control's user data.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
/// <param name="index">The Index</param>
/// <returns>Returns the user data</returns>
protected override object GetUserData(IGUIState guiState, int index)
{
if (userData != null)
return userData(guiState);
return base.GetUserData(guiState, index);
}
}
}

View File

@@ -0,0 +1,49 @@
using System;
using UnityEngine;
namespace UnityEditor.U2D.Path.GUIFramework
{
/// <summary>
/// Represents an action that is tied to a Control.
/// </summary>
public abstract class HoveredControlAction : GUIAction
{
private Control m_HoveredControl;
/// <summary>
/// The hovered control.
/// </summary>
public Control hoveredControl
{
get { return m_HoveredControl; }
}
/// <summary>
/// Initializes and returns an instance of HoverControlAction.
/// </summary>
/// <param name="control">The control to execcute an action for on hover.</param>
public HoveredControlAction(Control control)
{
m_HoveredControl = control;
}
/// <summary>
/// Determines whether the HoveredControlAction can trigger.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
/// <returns>Returns `true` if the HoveredControlAction can trigger. Otherwise, returns `false`.</returns>
protected override bool CanTrigger(IGUIState guiState)
{
return guiState.nearestControl == hoveredControl.ID;
}
/// <summary>
/// Calls the methods in its invocation list when triggered.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
protected override void OnTrigger(IGUIState guiState)
{
m_HoveredControl.SetActionID(ID);
}
}
}

View File

@@ -0,0 +1,155 @@
using UnityEngine;
using UnityEditor;
namespace UnityEditor.U2D.Path.GUIFramework
{
/// <summary>
/// Represents transform data for a slider.
/// </summary>
/// <remarks>
/// Unity uses this data to position and orient the slider in the custom editor.
/// </remarks>
public struct SliderData
{
/// <summary>
/// The slider's position.
/// </summary>
public Vector3 position;
/// <summary>
/// The slider's forward vector.
/// </summary>
public Vector3 forward;
/// <summary>
/// The slider's up vector.
/// </summary>
public Vector3 up;
/// <summary>
/// The slider's right vector.
/// </summary>
public Vector3 right;
/// <summary>
/// zero definition for SliderData
/// </summary>
public static readonly SliderData zero = new SliderData() { position = Vector3.zero, forward = Vector3.forward, up = Vector3.up, right = Vector3.right };
}
/// <summary>
/// Interface for GUIStates
/// </summary>
public interface IGUIState
{
/// <summary>
/// The mouse position.
/// </summary>
Vector2 mousePosition { get; }
/// <summary>
/// The mouse button pressed.
/// </summary>
int mouseButton { get; }
/// <summary>
/// The number of mouse clicks.
/// </summary>
int clickCount { get; set; }
/// <summary>
/// Indicates whether the shift key is pressed.
/// </summary>
bool isShiftDown { get; }
/// <summary>
/// Indicates whether the alt key is pressed.
/// </summary>
bool isAltDown { get; }
/// <summary>
/// Indicates whether the action key is pressed.
/// </summary>
bool isActionKeyDown { get; }
/// <summary>
/// The KeyCode of the currently pressed key.
/// </summary>
KeyCode keyCode { get; }
/// <summary>
/// The type of the event.
/// </summary>
EventType eventType { get; }
/// <summary>
/// The name of the event's command.
/// </summary>
string commandName { get; }
/// <summary>
/// The closest control to the event.
/// </summary>
int nearestControl { get; set; }
/// <summary>
/// Hot Control
/// </summary>
int hotControl { get; set; }
/// <summary>
/// Indicates whether the GUI has changed.
/// </summary>
bool changed { get; set; }
/// <summary>
/// <summary>
/// Gets the ID of a nested control by a hint and focus type.
/// </summary>
/// <param name="hint">The hint this function uses to identify the control ID.</param>
/// <param name="focusType">The focus Type</param>
/// <returns>Returns the ID of the control that matches the hint and focus type.</returns>
int GetControlID(int hint, FocusType focusType);
/// <summary>
/// Adds a control to the GUIState.
/// </summary>
/// <param name="controlID">The ID of the control to add.</param>
/// <param name="distance">The distance from the camera to the control.</param>
void AddControl(int controlID, float distance);
/// <summary>
/// Checks whether a slider value has changed.
/// </summary>
/// <param name="id">The ID of the slider to check.</param>
/// <param name="sliderData">The slider's data.</param>
/// <param name="newPosition">The new position of the slider.</param>
/// <returns>Returns `true` if the slider has changed. Otherwise, returns `false`.</returns>
bool Slider(int id, SliderData sliderData, out Vector3 newPosition);
/// <summary>
/// Uses the event.
/// </summary>
void UseEvent();
/// <summary>
/// Repaints the GUI.
/// </summary>
void Repaint();
/// <summary>
/// Checks if the current camera is valid.
/// </summary>
/// <returns>Returns `true` if the current camera is not null. Otherwise, returns `false`.</returns>
bool HasCurrentCamera();
/// <summary>
/// Gets the size of the handle.
/// </summary>
/// <param name="position">The position of the handle.</param>
/// <returns>Returns the size of the handle.</returns>
float GetHandleSize(Vector3 position);
/// <summary>
/// Measures the GUI-space distance between two points of a segment.
/// </summary>
/// <param name="p1">The first point.</param>
/// <param name="p2">The second point.</param>
/// <returns>Returns the GUI-space distance between p1 and p2.</returns>
float DistanceToSegment(Vector3 p1, Vector3 p2);
/// <summary>
/// Measures the distance to a circle.
/// </summary>
/// <param name="center">The center of the circle</param>
/// <param name="radius">The radius of the circle</param>
/// <returns>Returns the distance to a circle with the specified center and radius.</returns>
float DistanceToCircle(Vector3 center, float radius);
/// <summary>
/// Transforms a GUI-space position into world space.
/// </summary>
/// <param name="guiPosition">The GUI Position.</param>
/// <param name="planeNormal">The plane normal.</param>
/// <param name="planePos">The plane position.</param>
/// <returns>Returns the world-space position of `guiPosition`.</returns>
Vector3 GUIToWorld(Vector2 guiPosition, Vector3 planeNormal, Vector3 planePos);
}
}

View File

@@ -0,0 +1,58 @@
using UnityEngine;
namespace UnityEditor.U2D.Path.GUIFramework
{
/// <summary>
/// Represents the layout of a GUI element in a custom editor.
/// </summary>
public struct LayoutData
{
/// <summary>
/// The layout's index.
/// </summary>
public int index;
/// <summary>
/// The distance from the layout to the camera.
/// </summary>
public float distance;
/// <summary>
/// The layout's world-space position.
/// </summary>
public Vector3 position;
/// <summary>
/// The layout's world-space forward vector.
/// </summary>
public Vector3 forward;
/// <summary>
/// The layout's world-space up vector.
/// </summary>
public Vector3 up;
/// <summary>
/// The layout's world-space right vector.
/// </summary>
public Vector3 right;
/// <summary>
/// The layout's user data.
/// </summary>
public object userData;
/// <summary>
/// Zero definition of LayoutData.
/// </summary>
public static readonly LayoutData zero = new LayoutData() { index = 0, distance = float.MaxValue, position = Vector3.zero, forward = Vector3.forward, up = Vector3.up, right = Vector3.right };
/// <summary>
/// Gets the layout that is closest to the camera,
/// </summary>
/// <param name="currentData">The current layout.</param>
/// <param name="newData">The new layout to compare with.</param>
/// <returns>Returns the closest layout to the camera. If `currentData` is closest to the camera, returns `currentData`. Otherwise, if `newData` is closest to the camera, returns `newData`.</returns>
public static LayoutData Nearest(LayoutData currentData, LayoutData newData)
{
if (newData.distance <= currentData.distance)
return newData;
return currentData;
}
}
}

View File

@@ -0,0 +1,92 @@
using System;
using UnityEngine;
namespace UnityEditor.U2D.Path.GUIFramework
{
/// <summary>
/// SliderAction implementation of a ClickAction
/// </summary>
public class SliderAction : ClickAction
{
private SliderData m_SliderData;
/// <summary>
/// Action for OnSliderBegin
/// </summary>
public Action<IGUIState, Control, Vector3> onSliderBegin;
/// <summary>
/// Action for OnSliderChanged
/// </summary>
public Action<IGUIState, Control, Vector3> onSliderChanged;
/// <summary>
/// Action for OnSliderEnd
/// </summary>
public Action<IGUIState, Control, Vector3> onSliderEnd;
/// <summary>
/// Initializes and returns an instance of SliderAction
/// </summary>
/// <param name="control">The control to execcute an action for on slide.</param>
public SliderAction(Control control) : base(control, 0, false)
{
}
/// <summary>
/// Checks to see if the finish condition has been met or not.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
/// <returns>Returns `true` if the finish condition has been met. Otherwise, returns `false`.</returns>
protected override bool GetFinishContidtion(IGUIState guiState)
{
return guiState.eventType == EventType.MouseUp && guiState.mouseButton == 0;
}
/// <summary>
/// Called when there is interaction with the slider. It updates the stored slider data with data post-interaction.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
protected override void OnTrigger(IGUIState guiState)
{
base.OnTrigger(guiState);
m_SliderData.position = hoveredControl.hotLayoutData.position;
m_SliderData.forward = hoveredControl.hotLayoutData.forward;
m_SliderData.right = hoveredControl.hotLayoutData.right;
m_SliderData.up = hoveredControl.hotLayoutData.up;
if (onSliderBegin != null)
onSliderBegin(guiState, hoveredControl, m_SliderData.position);
}
/// <summary>
/// Post-processing for when the slider interaction finishes.
/// </summary>
/// <param name="guiState">The current state of the custom editor.</param>
protected override void OnFinish(IGUIState guiState)
{
if (onSliderEnd != null)
onSliderEnd(guiState, hoveredControl, m_SliderData.position);
guiState.UseEvent();
guiState.Repaint();
}
/// <summary>
/// Moves the slider to the new permission and executes `onSliderChanged` using the new position.
/// </summary>
/// <param name="guiState">The current state of the custom editor</param>
protected override void OnPerform(IGUIState guiState)
{
Vector3 newPosition;
var changed = guiState.Slider(ID, m_SliderData, out newPosition);
if (changed)
{
m_SliderData.position = newPosition;
if (onSliderChanged != null)
onSliderChanged(guiState, hoveredControl, newPosition);
}
}
}
}

View File

@@ -0,0 +1,15 @@
using UnityEngine;
namespace UnityEditor.U2D.Path
{
public interface IDrawer
{
void DrawCreatePointPreview(Vector3 position);
void DrawPoint(Vector3 position);
void DrawPointHovered(Vector3 position);
void DrawPointSelected(Vector3 position);
void DrawLine(Vector3 p1, Vector3 p2, float width, Color color);
void DrawBezier(Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4, float width, Color color);
void DrawTangent(Vector3 position, Vector3 tangent);
}
}

View File

@@ -0,0 +1,537 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditor.U2D.Path.GUIFramework;
namespace UnityEditor.U2D.Path
{
public class PathEditor
{
const float kSnappingDistance = 15f;
const string kDeleteCommandName = "Delete";
const string kSoftDeleteCommandName = "SoftDelete";
public IEditablePathController controller { get; set; }
public bool linearTangentIsZero { get; set; }
private IDrawer m_Drawer = new Drawer();
private IDrawer m_DrawerOverride;
private GUISystem m_GUISystem;
public IDrawer drawerOverride { get; set; }
private IDrawer drawer
{
get
{
if (drawerOverride != null)
return drawerOverride;
return m_Drawer;
}
}
public PathEditor() : this(new GUISystem(new GUIState())) { }
public PathEditor(GUISystem guiSystem)
{
m_GUISystem = guiSystem;
var m_PointControl = new GenericControl("Point")
{
count = GetPointCount,
distance = (guiState, i) =>
{
var position = GetPoint(i).position;
return guiState.DistanceToCircle(position, guiState.GetHandleSize(position) * 10f);
},
position = (i) => { return GetPoint(i).position; },
forward = (i) => { return GetForward(); },
up = (i) => { return GetUp(); },
right = (i) => { return GetRight(); },
onRepaint = DrawPoint
};
var m_EdgeControl = new GenericControl("Edge")
{
count = GetEdgeCount,
distance = DistanceToEdge,
position = (i) => { return GetPoint(i).position; },
forward = (i) => { return GetForward(); },
up = (i) => { return GetUp(); },
right = (i) => { return GetRight(); },
onRepaint = DrawEdge
};
m_EdgeControl.onEndLayout = (guiState) => { controller.AddClosestPath(m_EdgeControl.layoutData.distance); };
var m_LeftTangentControl = new GenericControl("LeftTangent")
{
count = () =>
{
if (GetShapeType() != ShapeType.Spline)
return 0;
return GetPointCount();
},
distance = (guiState, i) =>
{
if (linearTangentIsZero && GetPoint(i).tangentMode == TangentMode.Linear)
return float.MaxValue;
if (!IsSelected(i) || IsOpenEnded() && i == 0)
return float.MaxValue;
var position = GetLeftTangent(i);
return guiState.DistanceToCircle(position, guiState.GetHandleSize(position) * 10f);
},
position = (i) => { return GetLeftTangent(i); },
forward = (i) => { return GetForward(); },
up = (i) => { return GetUp(); },
right = (i) => { return GetRight(); },
onRepaint = (guiState, control, i) =>
{
if (!IsSelected(i) || IsOpenEnded() && i == 0)
return;
var point = GetPoint(i);
if (linearTangentIsZero && point.tangentMode == TangentMode.Linear)
return;
var position = point.position;
var leftTangent = GetLeftTangent(i);
drawer.DrawTangent(position, leftTangent);
}
};
var m_RightTangentControl = new GenericControl("RightTangent")
{
count = () =>
{
if (GetShapeType() != ShapeType.Spline)
return 0;
return GetPointCount();
},
distance = (guiState, i) =>
{
if (linearTangentIsZero && GetPoint(i).tangentMode == TangentMode.Linear)
return float.MaxValue;
if (!IsSelected(i) || IsOpenEnded() && i == GetPointCount()-1)
return float.MaxValue;
var position = GetRightTangent(i);
return guiState.DistanceToCircle(position, guiState.GetHandleSize(position) * 10f);
},
position = (i) => { return GetRightTangent(i); },
forward = (i) => { return GetForward(); },
up = (i) => { return GetUp(); },
right = (i) => { return GetRight(); },
onRepaint = (guiState, control, i) =>
{
if (!IsSelected(i) || IsOpenEnded() && i == GetPointCount()-1)
return;
var point = GetPoint(i);
if (linearTangentIsZero && point.tangentMode == TangentMode.Linear)
return;
var position = point.position;
var rightTangent = GetRightTangent(i);
drawer.DrawTangent(position, rightTangent);
}
};
var m_CreatePointAction = new CreatePointAction(m_PointControl, m_EdgeControl)
{
enable = (guiState, action) => !IsAltDown(guiState) && !guiState.isActionKeyDown && controller.closestEditablePath == controller.editablePath,
enableRepaint = (guiState, action) => EnableCreatePointRepaint(guiState, m_PointControl, m_LeftTangentControl, m_RightTangentControl),
repaintOnMouseMove = (guiState, action) => true,
guiToWorld = GUIToWorld,
onCreatePoint = (index, position) =>
{
controller.RegisterUndo("Create Point");
controller.CreatePoint(index, position);
},
onPreRepaint = (guiState, action) =>
{
if (GetPointCount() > 0)
{
var position = ClosestPointInEdge(guiState, guiState.mousePosition, m_EdgeControl.layoutData.index);
drawer.DrawCreatePointPreview(position);
}
}
};
Action<IGUIState> removePoints = (guiState) =>
{
controller.RegisterUndo("Remove Point");
controller.RemoveSelectedPoints();
guiState.changed = true;
};
var m_RemovePointAction1 = new CommandAction(kDeleteCommandName)
{
enable = (guiState, action) => { return GetSelectedPointCount() > 0; },
onCommand = removePoints
};
var m_RemovePointAction2 = new CommandAction(kSoftDeleteCommandName)
{
enable = (guiState, action) => { return GetSelectedPointCount() > 0; },
onCommand = removePoints
};
var dragged = false;
var m_MovePointAction = new SliderAction(m_PointControl)
{
enable = (guiState, action) => !IsAltDown(guiState),
onClick = (guiState, control) =>
{
dragged = false;
var index = control.layoutData.index;
if (!IsSelected(index))
{
controller.RegisterUndo("Selection");
if (!guiState.isActionKeyDown)
controller.ClearSelection();
controller.SelectPoint(index, true);
guiState.changed = true;
}
},
onSliderChanged = (guiState, control, position) =>
{
var index = control.hotLayoutData.index;
var delta = SnapIfNeeded(position) - GetPoint(index).position;
if (!dragged)
{
controller.RegisterUndo("Move Point");
dragged = true;
}
controller.MoveSelectedPoints(delta);
}
};
var m_MoveEdgeAction = new SliderAction(m_EdgeControl)
{
enable = (guiState, action) => !IsAltDown(guiState) && guiState.isActionKeyDown,
onSliderBegin = (guiState, control, position) =>
{
dragged = false;
},
onSliderChanged = (guiState, control, position) =>
{
var index = control.hotLayoutData.index;
var delta = position - GetPoint(index).position;
if (!dragged)
{
controller.RegisterUndo("Move Edge");
dragged = true;
}
controller.MoveEdge(index, delta);
}
};
var cachedRightTangent = Vector3.zero;
var cachedLeftTangent = Vector3.zero;
var cachedTangentMode = TangentMode.Linear;
var m_MoveLeftTangentAction = new SliderAction(m_LeftTangentControl)
{
enable = (guiState, action) => !IsAltDown(guiState),
onSliderBegin = (guiState, control, position) =>
{
dragged = false;
var point = GetPoint(control.hotLayoutData.index);
cachedRightTangent = point.rightTangent;
cachedTangentMode = point.tangentMode;
},
onSliderChanged = (guiState, control, position) =>
{
var index = control.hotLayoutData.index;
var setToLinear = m_PointControl.distance(guiState, index) <= DefaultControl.kPickDistance;
if (!dragged)
{
controller.RegisterUndo("Move Tangent");
dragged = true;
}
position = SnapIfNeeded(position);
controller.SetLeftTangent(index, position, setToLinear, guiState.isShiftDown, cachedRightTangent, cachedTangentMode);
}
};
var m_MoveRightTangentAction = new SliderAction(m_RightTangentControl)
{
enable = (guiState, action) => !IsAltDown(guiState),
onSliderBegin = (guiState, control, position) =>
{
dragged = false;
var point = GetPoint(control.hotLayoutData.index);
cachedLeftTangent = point.leftTangent;
cachedTangentMode = point.tangentMode;
},
onSliderChanged = (guiState, control, position) =>
{
var index = control.hotLayoutData.index;
var setToLinear = m_PointControl.distance(guiState, index) <= DefaultControl.kPickDistance;
if (!dragged)
{
controller.RegisterUndo("Move Tangent");
dragged = true;
}
position = SnapIfNeeded(position);
controller.SetRightTangent(index, position, setToLinear, guiState.isShiftDown, cachedLeftTangent, cachedTangentMode);
}
};
m_GUISystem.AddControl(m_EdgeControl);
m_GUISystem.AddControl(m_PointControl);
m_GUISystem.AddControl(m_LeftTangentControl);
m_GUISystem.AddControl(m_RightTangentControl);
m_GUISystem.AddAction(m_CreatePointAction);
m_GUISystem.AddAction(m_RemovePointAction1);
m_GUISystem.AddAction(m_RemovePointAction2);
m_GUISystem.AddAction(m_MovePointAction);
m_GUISystem.AddAction(m_MoveEdgeAction);
m_GUISystem.AddAction(m_MoveLeftTangentAction);
m_GUISystem.AddAction(m_MoveRightTangentAction);
}
public void OnGUI()
{
m_GUISystem.OnGUI();
}
private bool IsAltDown(IGUIState guiState)
{
return guiState.hotControl == 0 && guiState.isAltDown;
}
private ControlPoint GetPoint(int index)
{
return controller.editablePath.GetPoint(index);
}
private int GetPointCount()
{
return controller.editablePath.pointCount;
}
private int GetEdgeCount()
{
if (controller.editablePath.isOpenEnded)
return controller.editablePath.pointCount - 1;
return controller.editablePath.pointCount;
}
private int GetSelectedPointCount()
{
return controller.editablePath.selection.Count;
}
private bool IsSelected(int index)
{
return controller.editablePath.selection.Contains(index);
}
private Vector3 GetForward()
{
return controller.editablePath.forward;
}
private Vector3 GetUp()
{
return controller.editablePath.up;
}
private Vector3 GetRight()
{
return controller.editablePath.right;
}
private Matrix4x4 GetLocalToWorldMatrix()
{
return controller.editablePath.localToWorldMatrix;
}
private ShapeType GetShapeType()
{
return controller.editablePath.shapeType;
}
private bool IsOpenEnded()
{
return controller.editablePath.isOpenEnded;
}
private Vector3 GetLeftTangent(int index)
{
if (linearTangentIsZero)
return GetPoint(index).leftTangent;
return controller.editablePath.CalculateLeftTangent(index);
}
private Vector3 GetRightTangent(int index)
{
if (linearTangentIsZero)
return GetPoint(index).rightTangent;
return controller.editablePath.CalculateRightTangent(index);
}
private int NextIndex(int index)
{
return EditablePathUtility.Mod(index + 1, GetPointCount());
}
private ControlPoint NextControlPoint(int index)
{
return GetPoint(NextIndex(index));
}
private int PrevIndex(int index)
{
return EditablePathUtility.Mod(index - 1, GetPointCount());
}
private ControlPoint PrevControlPoint(int index)
{
return GetPoint(PrevIndex(index));
}
private Vector3 ClosestPointInEdge(IGUIState guiState, Vector2 mousePosition, int index)
{
if (GetShapeType() == ShapeType.Polygon)
{
var p0 = GetPoint(index).position;
var p1 = NextControlPoint(index).position;
var mouseWorldPosition = GUIToWorld(guiState, mousePosition);
var dir1 = (mouseWorldPosition - p0);
var dir2 = (p1 - p0);
return Mathf.Clamp01(Vector3.Dot(dir1, dir2.normalized) / dir2.magnitude) * dir2 + p0;
}
else if (GetShapeType() == ShapeType.Spline)
{
var nextIndex = NextIndex(index);
float t;
return BezierUtility.ClosestPointOnCurve(
GUIToWorld(guiState, mousePosition),
GetPoint(index).position,
GetPoint(nextIndex).position,
GetRightTangent(index),
GetLeftTangent(nextIndex),
out t);
}
return Vector3.zero;
}
private float DistanceToEdge(IGUIState guiState, int index)
{
if (GetShapeType() == ShapeType.Polygon)
{
return guiState.DistanceToSegment(GetPoint(index).position, NextControlPoint(index).position);
}
else if (GetShapeType() == ShapeType.Spline)
{
var closestPoint = ClosestPointInEdge(guiState, guiState.mousePosition, index);
var closestPoint2 = HandleUtility.WorldToGUIPoint(closestPoint);
return (closestPoint2 - guiState.mousePosition).magnitude;
}
return float.MaxValue;
}
private Vector3 GUIToWorld(IGUIState guiState, Vector2 position)
{
return guiState.GUIToWorld(position, GetForward(), GetLocalToWorldMatrix().MultiplyPoint3x4(Vector3.zero));
}
private void DrawPoint(IGUIState guiState, Control control, int index)
{
var position = GetPoint(index).position;
if (guiState.hotControl == control.actionID && control.hotLayoutData.index == index || IsSelected(index))
drawer.DrawPointSelected(position);
else if (guiState.hotControl == 0 && guiState.nearestControl == control.ID && !IsAltDown(guiState) && control.layoutData.index == index)
drawer.DrawPointHovered(position);
else
drawer.DrawPoint(position);
}
private void DrawEdge(IGUIState guiState, Control control, int index)
{
if (GetShapeType() == ShapeType.Polygon)
{
var nextIndex = NextIndex(index);
var color = Color.white;
if(guiState.nearestControl == control.ID && control.layoutData.index == index && guiState.hotControl == 0 && !IsAltDown(guiState))
color = Color.yellow;
drawer.DrawLine(GetPoint(index).position, GetPoint(nextIndex).position, 5f, color);
}
else if (GetShapeType() == ShapeType.Spline)
{
var nextIndex = NextIndex(index);
var color = Color.white;
if(guiState.nearestControl == control.ID && control.layoutData.index == index && guiState.hotControl == 0 && !IsAltDown(guiState))
color = Color.yellow;
drawer.DrawBezier(
GetPoint(index).position,
GetRightTangent(index),
GetLeftTangent(nextIndex),
GetPoint(nextIndex).position,
5f,
color);
}
}
private bool EnableCreatePointRepaint(IGUIState guiState, Control pointControl, Control leftTangentControl, Control rightTangentControl)
{
return guiState.nearestControl != pointControl.ID &&
guiState.hotControl == 0 &&
(guiState.nearestControl != leftTangentControl.ID) &&
(guiState.nearestControl != rightTangentControl.ID);
}
private Vector3 SnapIfNeeded(Vector3 position)
{
if (!controller.enableSnapping || controller.snapping == null)
return position;
var guiPosition = HandleUtility.WorldToGUIPoint(position);
var snappedGuiPosition = HandleUtility.WorldToGUIPoint(controller.snapping.Snap(position));
var sqrDistance = (guiPosition - snappedGuiPosition).sqrMagnitude;
if (sqrDistance < kSnappingDistance * kSnappingDistance)
position = controller.snapping.Snap(position);
return position;
}
}
}

View File

@@ -0,0 +1,132 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditor.U2D.Path.GUIFramework;
namespace UnityEditor.U2D.Path
{
public abstract class RectSelector<T> : ISelector<T>
{
public class Styles
{
public readonly GUIStyle selectionRectStyle;
public Styles()
{
selectionRectStyle = GUI.skin.FindStyle("selectionRect");
}
}
public Action<ISelector<T>, bool> onSelectionBegin;
public Action<ISelector<T>> onSelectionChanged;
public Action<ISelector<T>> onSelectionEnd;
private GUISystem m_GUISystem;
private Control m_RectSelectorControl;
private GUIAction m_RectSelectAction;
private Rect m_GUIRect;
private Styles m_Styles;
public Rect guiRect
{
get { return m_GUIRect; }
}
private Styles styles
{
get
{
if (m_Styles == null)
m_Styles = new Styles();
return m_Styles;
}
}
public RectSelector() : this(new GUISystem(new GUIState())) { }
public RectSelector(GUISystem guiSystem)
{
m_GUISystem = guiSystem;
m_RectSelectorControl = new GenericDefaultControl("RectSelector");
var start = Vector2.zero;
var rectEnd = Vector2.zero;
m_RectSelectAction = new SliderAction(m_RectSelectorControl)
{
enable = (guiState, action) => !IsAltDown(guiState),
enableRepaint = (guiState, action) =>
{
var size = start - rectEnd;
return size != Vector2.zero && guiState.hotControl == action.ID;
},
onSliderBegin = (guiState, control, position) =>
{
start = guiState.mousePosition;
rectEnd = guiState.mousePosition;
m_GUIRect = FromToRect(start, rectEnd);
if (onSelectionBegin != null)
onSelectionBegin(this, guiState.isShiftDown);
},
onSliderChanged = (guiState, control, position) =>
{
rectEnd = guiState.mousePosition;
m_GUIRect = FromToRect(start, rectEnd);
if (onSelectionChanged != null)
onSelectionChanged(this);
},
onSliderEnd = (guiState, control, position) =>
{
if (onSelectionEnd != null)
onSelectionEnd(this);
},
onRepaint = (guiState, action) =>
{
Handles.BeginGUI();
styles.selectionRectStyle.Draw(m_GUIRect, GUIContent.none, false, false, false, false);
Handles.EndGUI();
}
};
m_GUISystem.AddControl(m_RectSelectorControl);
m_GUISystem.AddAction(m_RectSelectAction);
}
private bool IsAltDown(IGUIState guiState)
{
return guiState.hotControl == 0 && guiState.isAltDown;
}
private Rect FromToRect(Vector2 start, Vector2 end)
{
Rect r = new Rect(start.x, start.y, end.x - start.x, end.y - start.y);
if (r.width < 0)
{
r.x += r.width;
r.width = -r.width;
}
if (r.height < 0)
{
r.y += r.height;
r.height = -r.height;
}
return r;
}
public void OnGUI()
{
m_GUISystem.OnGUI();
}
bool ISelector<T>.Select(T element)
{
return Select(element);
}
protected abstract bool Select(T element);
}
}