using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.Serialization;
using Random = UnityEngine.Random;
namespace XNodeEditor
{
public enum NoodlePath
{
Curvy,
Straight,
Angled,
ShaderLab
}
public enum NoodleStroke
{
Full,
Dashed
}
public static class NodeEditorPreferences
{
/// The last editor we checked. This should be the one we modify
private static NodeGraphEditor lastEditor;
/// The last key we checked. This should be the one we modify
private static string lastKey = "xNode.Settings";
private static Dictionary typeColors = new Dictionary();
private static readonly Dictionary settings = new Dictionary();
[Serializable]
public class Settings : ISerializationCallbackReceiver
{
[SerializeField] private Color32 _gridLineColor = new Color(.23f, .23f, .23f);
public Color32 gridLineColor
{
get => _gridLineColor;
set
{
_gridLineColor = value;
_gridTexture = null;
_crossTexture = null;
}
}
[SerializeField] private Color32 _gridBgColor = new Color(.19f, .19f, .19f);
public Color32 gridBgColor
{
get => _gridBgColor;
set
{
_gridBgColor = value;
_gridTexture = null;
}
}
[Obsolete("Use maxZoom instead")]
public float zoomOutLimit
{
get => maxZoom;
set => maxZoom = value;
}
[FormerlySerializedAs("zoomOutLimit")]
public float maxZoom = 5f;
public float minZoom = 1f;
public Color32 tintColor = new Color32(90, 97, 105, 255);
public Color32 bgHeaderColor = new Color32(50, 51, 54, 255);
public Color32 bgPortsColor = new Color32(65, 67, 70, 255);
public Color32 bgBodyColor = new Color32(65, 67, 70, 255);
public Color32 highlightColor = new Color32(255, 255, 255, 255);
public Color32 resizeIconColor = new Color32(255, 255, 255, 26);
public Color32 resizeIconHoverColor = new Color32(255, 255, 255, 100);
public bool gridSnap = true;
public bool autoSave = true;
public bool openOnCreate = true;
public bool dragToCreate = true;
public bool createFilter = true;
public bool zoomToMouse = true;
public bool portTooltips = true;
[SerializeField] private string typeColorsData = "";
[NonSerialized] public Dictionary typeColors = new Dictionary();
[FormerlySerializedAs("noodleType")] public NoodlePath noodlePath = NoodlePath.Curvy;
public float noodleThickness = 2f;
public NoodleStroke noodleStroke = NoodleStroke.Full;
private Texture2D _gridTexture;
public Texture2D gridTexture
{
get
{
if (_gridTexture == null)
{
_gridTexture = NodeEditorResources.GenerateGridTexture(gridLineColor, gridBgColor);
}
return _gridTexture;
}
}
private Texture2D _crossTexture;
public Texture2D crossTexture
{
get
{
if (_crossTexture == null)
{
_crossTexture = NodeEditorResources.GenerateCrossTexture(gridLineColor);
}
return _crossTexture;
}
}
public void OnAfterDeserialize()
{
// Deserialize typeColorsData
typeColors = new Dictionary();
string[] data = typeColorsData.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < data.Length; i += 2)
{
Color col;
if (ColorUtility.TryParseHtmlString("#" + data[i + 1], out col))
{
typeColors.Add(data[i], col);
}
}
}
public void OnBeforeSerialize()
{
// Serialize typeColors
typeColorsData = "";
foreach (var item in typeColors)
{
typeColorsData += item.Key + "," + ColorUtility.ToHtmlStringRGB(item.Value) + ",";
}
}
}
/// Get settings of current active editor
public static Settings GetSettings()
{
if (NodeEditorWindow.current == null)
{
return new Settings();
}
if (lastEditor != NodeEditorWindow.current.graphEditor)
{
object[] attribs = NodeEditorWindow.current.graphEditor.GetType()
.GetCustomAttributes(typeof(NodeGraphEditor.CustomNodeGraphEditorAttribute), true);
if (attribs.Length == 1)
{
NodeGraphEditor.CustomNodeGraphEditorAttribute attrib =
attribs[0] as NodeGraphEditor.CustomNodeGraphEditorAttribute;
lastEditor = NodeEditorWindow.current.graphEditor;
lastKey = attrib.editorPrefsKey;
}
else
{
return null;
}
}
if (!settings.ContainsKey(lastKey))
{
VerifyLoaded();
}
return settings[lastKey];
}
#if UNITY_2019_1_OR_NEWER
[SettingsProvider]
public static SettingsProvider CreateXNodeSettingsProvider()
{
SettingsProvider provider = new SettingsProvider("Preferences/Node Editor", SettingsScope.User)
{
guiHandler = searchContext => { PreferencesGUI(); },
keywords = new HashSet(new[]
{ "xNode", "node", "editor", "graph", "connections", "noodles", "ports" })
};
return provider;
}
#endif
#if !UNITY_2019_1_OR_NEWER
[PreferenceItem("Node Editor")]
#endif
private static void PreferencesGUI()
{
VerifyLoaded();
Settings settings = NodeEditorPreferences.settings[lastKey];
EditorGUILayout.BeginHorizontal();
EditorGUILayout.Space(5);
EditorGUILayout.BeginVertical();
if (GUILayout.Button(new GUIContent("Documentation", "https://github.com/Siccity/xNode/wiki"),
GUILayout.Width(100)))
{
Application.OpenURL("https://github.com/Siccity/xNode/wiki");
}
EditorGUILayout.Space();
NodeSettingsGUI(lastKey, settings);
GridSettingsGUI(lastKey, settings);
SystemSettingsGUI(lastKey, settings);
TypeColorsGUI(lastKey, settings);
if (GUILayout.Button(new GUIContent("Set Default", "Reset all values to default"), GUILayout.Width(120)))
{
ResetPrefs();
}
EditorGUILayout.EndVertical();
EditorGUILayout.EndHorizontal();
}
private static void GridSettingsGUI(string key, Settings settings)
{
//Label
// EditorGUILayout.LabelField("Grid", EditorStyles.boldLabel);
Separator("Grid Appearance");
settings.gridSnap = EditorGUILayout.Toggle(new GUIContent("Snap", "Hold CTRL in editor to invert"),
settings.gridSnap);
settings.zoomToMouse =
EditorGUILayout.Toggle(new GUIContent("Zoom to Mouse", "Zooms towards mouse position"),
settings.zoomToMouse);
EditorGUILayout.LabelField("Zoom");
EditorGUI.indentLevel++;
settings.maxZoom =
EditorGUILayout.FloatField(new GUIContent("Max", "Upper limit to zoom"), settings.maxZoom);
settings.minZoom =
EditorGUILayout.FloatField(new GUIContent("Min", "Lower limit to zoom"), settings.minZoom);
EditorGUI.indentLevel--;
settings.gridLineColor = EditorGUILayout.ColorField("Line Color", settings.gridLineColor);
settings.gridBgColor = EditorGUILayout.ColorField("Background Color", settings.gridBgColor);
if (GUI.changed)
{
SavePrefs(key, settings);
NodeEditorWindow.RepaintAll();
}
EditorGUILayout.Space();
}
private static void SystemSettingsGUI(string key, Settings settings)
{
//Label
// EditorGUILayout.LabelField("System", EditorStyles.boldLabel);
Separator("System");
settings.autoSave =
EditorGUILayout.Toggle(new GUIContent("Autosave", "Disable for better editor performance"),
settings.autoSave);
settings.openOnCreate =
EditorGUILayout.Toggle(
new GUIContent("Open Editor on Create",
"Disable to prevent openening the editor when creating a new graph"), settings.openOnCreate);
if (GUI.changed)
{
SavePrefs(key, settings);
}
EditorGUILayout.Space();
}
private static void NodeSettingsGUI(string key, Settings settings)
{
//Label
// EditorGUILayout.LabelField("Node", EditorStyles.boldLabel);
Separator("Node Appearance");
settings.tintColor = EditorGUILayout.ColorField("Global Tint", settings.tintColor);
settings.bgHeaderColor = EditorGUILayout.ColorField("Header Background", settings.bgHeaderColor);
settings.bgPortsColor = EditorGUILayout.ColorField("Ports Background", settings.bgPortsColor);
settings.bgBodyColor = EditorGUILayout.ColorField("Body Background", settings.bgBodyColor);
settings.resizeIconColor = EditorGUILayout.ColorField("Resize Icon Color", settings.resizeIconColor);
settings.resizeIconHoverColor =
EditorGUILayout.ColorField("Resize Icon Hover Color", settings.resizeIconHoverColor);
settings.highlightColor = EditorGUILayout.ColorField("Selection", settings.highlightColor);
EditorGUILayout.Space();
settings.noodlePath = (NoodlePath)EditorGUILayout.EnumPopup("Noodle path", settings.noodlePath);
settings.noodleThickness = EditorGUILayout.FloatField(
new GUIContent("Noodle thickness", "Noodle Thickness of the node connections"),
settings.noodleThickness);
settings.noodleStroke = (NoodleStroke)EditorGUILayout.EnumPopup("Noodle stroke", settings.noodleStroke);
EditorGUILayout.Space();
settings.portTooltips = EditorGUILayout.Toggle("Port Tooltips", settings.portTooltips);
settings.dragToCreate =
EditorGUILayout.Toggle(
new GUIContent("Drag to Create",
"Drag a port connection anywhere on the grid to create and connect a node"),
settings.dragToCreate);
settings.createFilter =
EditorGUILayout.Toggle(
new GUIContent("Create Filter", "Only show nodes that are compatible with the selected port"),
settings.createFilter);
//END
if (GUI.changed)
{
SavePrefs(key, settings);
NodeEditorWindow.RepaintAll();
}
EditorGUILayout.Space();
}
private static void TypeColorsGUI(string key, Settings settings)
{
//Label
// EditorGUILayout.LabelField("Types", EditorStyles.boldLabel);
Separator("Types");
//Clone keys so we can enumerate the dictionary and make changes.
var typeColorKeys = new List(typeColors.Keys);
//Display type colors. Save them if they are edited by the user
foreach (Type type in typeColorKeys)
{
string typeColorKey = type.PrettyName();
Color col = typeColors[type];
EditorGUI.BeginChangeCheck();
EditorGUILayout.BeginHorizontal();
col = EditorGUILayout.ColorField(typeColorKey, col);
EditorGUILayout.EndHorizontal();
if (EditorGUI.EndChangeCheck())
{
typeColors[type] = col;
if (settings.typeColors.ContainsKey(typeColorKey))
{
settings.typeColors[typeColorKey] = col;
}
else
{
settings.typeColors.Add(typeColorKey, col);
}
SavePrefs(key, settings);
NodeEditorWindow.RepaintAll();
}
}
}
private static void Separator(string label = "")
{
Rect labelRect = EditorGUILayout.BeginHorizontal(GUILayout.Height(25));
labelRect.y += 10;
GUIStyle labelStyle = new GUIStyle(GUI.skin.label);
labelStyle.fontStyle = FontStyle.Bold;
Vector2 textSize = labelStyle.CalcSize(new GUIContent(label));
float separatorWidth = (labelRect.width - textSize.x) / 2 - 5;
// Needed here otherwise BeginHorizontal group has 0 height.
GUILayout.Label("");
Color initialColor = GUI.color;
Color lineColor = new Color(0.5f, 0.5f, 0.5f);
GUI.color = lineColor;
GUI.Box(new Rect(labelRect.xMin + 5, labelRect.yMin, separatorWidth - 5, 1), string.Empty);
GUI.color = initialColor;
GUI.Label(new Rect(labelRect.xMin + separatorWidth + 5, labelRect.yMin - 10, textSize.x, 20), label,
labelStyle);
GUI.color = lineColor;
GUI.Box(new Rect(labelRect.xMin + separatorWidth + 10 + textSize.x, labelRect.yMin, separatorWidth - 5, 1),
string.Empty);
GUI.color = initialColor;
EditorGUILayout.EndHorizontal();
}
/// Load prefs if they exist. Create if they don't
private static Settings LoadPrefs()
{
// Create settings if it doesn't exist
if (!EditorPrefs.HasKey(lastKey))
{
if (lastEditor != null)
{
EditorPrefs.SetString(lastKey, JsonUtility.ToJson(lastEditor.GetDefaultPreferences()));
}
else
{
EditorPrefs.SetString(lastKey, JsonUtility.ToJson(new Settings()));
}
}
return JsonUtility.FromJson(EditorPrefs.GetString(lastKey));
}
/// Delete all prefs
public static void ResetPrefs()
{
if (EditorPrefs.HasKey(lastKey))
{
EditorPrefs.DeleteKey(lastKey);
}
if (settings.ContainsKey(lastKey))
{
settings.Remove(lastKey);
}
typeColors = new Dictionary();
VerifyLoaded();
NodeEditorWindow.RepaintAll();
}
/// Save preferences in EditorPrefs
private static void SavePrefs(string key, Settings settings)
{
EditorPrefs.SetString(key, JsonUtility.ToJson(settings));
}
/// Check if we have loaded settings for given key. If not, load them
private static void VerifyLoaded()
{
if (!settings.ContainsKey(lastKey))
{
settings.Add(lastKey, LoadPrefs());
}
}
/// Return color based on type
public static Color GetTypeColor(Type type)
{
VerifyLoaded();
if (type == null)
{
return Color.gray;
}
Color col;
if (!typeColors.TryGetValue(type, out col))
{
string typeName = type.PrettyName();
if (settings[lastKey].typeColors.ContainsKey(typeName))
{
typeColors.Add(type, settings[lastKey].typeColors[typeName]);
}
else
{
#if UNITY_5_4_OR_NEWER
Random.State oldState = Random.state;
Random.InitState(typeName.GetHashCode());
#else
int oldSeed = UnityEngine.Random.seed;
UnityEngine.Random.seed = typeName.GetHashCode();
#endif
col = new Color(Random.value, Random.value, Random.value);
typeColors.Add(type, col);
#if UNITY_5_4_OR_NEWER
Random.state = oldState;
#else
UnityEngine.Random.seed = oldSeed;
#endif
}
}
return col;
}
}
}