Files
Cielonos/Assets/PotaToon/Editor/Scripts/PotaToonShaderGUIBase.cs
SoulliesOfficial 50ee502684 完善
2026-02-13 09:22:11 -05:00

752 lines
33 KiB
C#

using System;
using System.IO;
using UnityEditor;
using UnityEngine;
using System.Collections.Generic;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using static PotaToon.Editor.PotaToonShaderGUISearchHelper;
using static PotaToon.Editor.PotaToonEditorUtility;
using static PotaToon.Editor.PotaToonMaterialPresetBase;
namespace PotaToon.Editor
{
public class PotaToonShaderGUIBase : ShaderGUI
{
internal static class Property
{
public static readonly string BlendMode = "_Blend";
public static readonly string SrcBlend = "_SrcBlend";
public static readonly string DstBlend = "_DstBlend";
public static readonly string SrcBlendAlpha = "_SrcBlendAlpha";
public static readonly string DstBlendAlpha = "_DstBlendAlpha";
}
private static int[] s_AutoRenderQueues = new int[] { 2000, 2450, 2900, 3000 };
protected static bool s_ShowMaininfo;
protected int m_ShaderType;
protected bool m_AutoRenderQueue = true;
protected int m_RenderQueue = 2000;
// Presets
internal static Dictionary<int, List<PotaToonMaterialPresetBase>> s_MaterialPresets = new Dictionary<int, List<PotaToonMaterialPresetBase>>();
private static Material s_CopyBuffer;
protected static bool s_ShowPreset;
protected static Texture2D s_PresetButtonIcon;
protected bool m_PrestIconInitialized;
protected Vector2 m_ScrollPos = Vector2.zero;
protected void DrawTitle(int shaderType, bool showType, Material target, Material[] targets = null)
{
const float titleHeight = 35f;
GUIStyle titleStyle = new GUIStyle(EditorStyles.boldLabel)
{
fontSize = 18,
alignment = TextAnchor.MiddleLeft
};
GUIStyle versionStyle = new GUIStyle(EditorStyles.label)
{
fontSize = 11,
alignment = TextAnchor.MiddleLeft,
};
GUIStyle presetButtonStyle = new GUIStyle(GUI.skin.button)
{
fontSize = 13,
fontStyle = FontStyle.Bold,
alignment = TextAnchor.MiddleCenter,
};
EditorGUILayout.Space(4);
EditorGUILayout.BeginHorizontal();
var typeText = PotaToonGUIUtility.k_Types[shaderType];
var text = showType ? $"PotaToon ({typeText})" : "PotaToon";
var width = showType ? 100f + typeText.Length * 16f : 100f;
EditorGUILayout.LabelField(text, titleStyle, GUILayout.Width(width), GUILayout.Height(titleHeight));
EditorGUILayout.LabelField("v" + PotaToonGUIUtility.k_Version, versionStyle, GUILayout.Width(40), GUILayout.Height(titleHeight));
GUILayout.FlexibleSpace();
if (GUILayout.Button(EditorGUIUtility.IconContent("Clipboard", "Copy settings from the active material"), GUILayout.Width(titleHeight), GUILayout.Height(titleHeight)))
{
CopyComponent(target);
}
if (GUILayout.Button(EditorGUIUtility.IconContent("d_SaveAs", "Paste settings. When multiple materials are selected, applies to all selected materials using the same shader."), GUILayout.Width(titleHeight), GUILayout.Height(titleHeight)))
{
if (targets != null && targets.Length > 1)
PasteComponent(targets);
else
PasteComponent(target);
}
var bgColor = GUI.backgroundColor;
GUI.backgroundColor = s_ShowPreset ? new Color(0.8f, 0.8f, 1f) : bgColor;
var presetIconConcent = s_PresetButtonIcon != null ? new GUIContent(s_PresetButtonIcon, "Preset") : EditorGUIUtility.IconContent("d_Preset.Context@2x", "|Preset");
if (GUILayout.Button(presetIconConcent, presetButtonStyle, GUILayout.Width(titleHeight), GUILayout.Height(titleHeight)))
{
s_ShowPreset = !s_ShowPreset;
}
GUI.backgroundColor = bgColor;
EditorGUILayout.EndHorizontal();
// Enable search field for General/Face types.
if (m_ShaderType < 2)
searchQuery = EditorGUILayout.TextField(searchQuery, EditorStyles.toolbarSearchField);
EditorGUILayout.Space(4);
}
protected GUIContent[] GetToonTypeContents()
{
var types = PotaToonGUIUtility.k_Types;
GUIContent[] toonTypeContents = new GUIContent[types.Length];
for (int i = 0; i < types.Length; i++)
toonTypeContents[i] = new GUIContent(types[i]);
if (presetIconContents.Count > 0)
{
for (int i = 0; i < types.Length; i++)
toonTypeContents[i].image = presetIconContents[typeIconStart + i].image;
}
return toonTypeContents;
}
protected void DrawInfoBox(string message)
{
GUIStyle boxStyle = new GUIStyle("box")
{
padding = new RectOffset(10, 10, 10, 10),
margin = new RectOffset(5, 5, 5, 5),
normal = { textColor = EditorStyles.label.normal.textColor }
};
GUIStyle iconStyle = new GUIStyle(EditorStyles.label)
{
fixedWidth = 20,
alignment = TextAnchor.MiddleLeft
};
GUIStyle textAreaStyle = new GUIStyle(EditorStyles.textArea)
{
wordWrap = true
};
EditorGUILayout.BeginHorizontal(boxStyle);
GUILayout.Label(EditorGUIUtility.IconContent("console.infoicon"), iconStyle);
EditorGUILayout.TextArea(message, textAreaStyle);
EditorGUILayout.EndHorizontal();
}
private void CopyComponent(Material mat)
{
if (mat == null)
return;
s_CopyBuffer = new Material(mat);
PotaToonGUIUtility.ShowNotification("Copied!");
}
private void PasteComponent(Material mat)
{
if (mat == null || s_CopyBuffer == null)
return;
if (mat.shader != s_CopyBuffer.shader)
{
Debug.LogWarning("[PotaToon] Paste component shader mismatch");
return;
}
var originalName = mat.name;
Undo.RecordObject(mat, "Paste Material Properties");
EditorUtility.CopySerialized(s_CopyBuffer, mat);
mat.name = originalName;
EditorUtility.SetDirty(mat);
PotaToonGUIUtility.ShowNotification("Pasted!");
}
private void PasteComponent(Material[] mats)
{
if (mats == null || mats.Length == 0 || s_CopyBuffer == null)
return;
int applied = 0;
foreach (var mat in mats)
{
if (mat == null)
continue;
if (mat.shader != s_CopyBuffer.shader)
{
Debug.LogWarning("[PotaToon] Paste component shader mismatch: " + mat.name);
continue;
}
var originalName = mat.name;
Undo.RecordObject(mat, "Paste Material Properties");
EditorUtility.CopySerialized(s_CopyBuffer, mat);
mat.name = originalName;
EditorUtility.SetDirty(mat);
applied++;
}
if (applied > 1)
PotaToonGUIUtility.ShowNotification($"Pasted to {applied} materials!");
else if (applied == 1)
PotaToonGUIUtility.ShowNotification("Pasted!");
}
protected void DrawPresetField(Material mat, Material[] mats = null)
{
if (!s_ShowPreset)
return;
const float scrollHeight = 270f;
const float itemWidth = 60f;
EditorGUILayout.BeginVertical(GUI.skin.box);
EditorGUILayout.HelpBox("Right-click to edit preset. Note that presets do not contain textures except for 'MatCap Map'.", MessageType.Info);
int cols = Mathf.Max(1, Mathf.FloorToInt(EditorGUIUtility.currentViewWidth / itemWidth) - 1);
m_ScrollPos = EditorGUILayout.BeginScrollView( m_ScrollPos, false, true,
GUIStyle.none, GUI.skin.verticalScrollbar, GUI.skin.box,
GUILayout.Height(scrollHeight), GUILayout.ExpandWidth(true));
var iconButtonStyle = new GUIStyle(GUI.skin.button)
{
imagePosition = ImagePosition.ImageAbove,
alignment = TextAnchor.LowerCenter,
padding = new RectOffset(4,4,4,4),
wordWrap = true,
fontSize = 10
};
var evt = Event.current;
foreach (var materialPresets in s_MaterialPresets)
{
var presets = materialPresets.Value;
if (!materialPresets.Key.Equals(m_ShaderType))
continue;
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button(EditorGUIUtility.IconContent("Toolbar Plus", "|Create preset"), GUILayout.Height(30f)))
{
if (CreateAndAddPreset(presets))
{
evt.Use();
PopupWindow.Show(new Rect(0, 0, 0, 0), new MaterialPresetContextMenu(presets, presets.Count - 1, mat));
}
}
if (GUILayout.Button(EditorGUIUtility.IconContent("Import", "|Import preset"), GUILayout.Height(30f)))
{
ImportPreset();
GUIUtility.ExitGUI();
}
EditorGUILayout.EndHorizontal();
// Display groups
var groupedPresets = PotaToonMaterialPresetBase.SplitByDisplayIndex(presets);
int idx = 0;
for (int i = 0; i < groupedPresets.Count; i++)
{
var currPresets = groupedPresets[i];
var presetCount = currPresets.Count;
if (presetCount == 0)
continue;
EditorGUILayout.LabelField(currPresets[0].displayGroup.ToString(), EditorStyles.boldLabel);
int groupedIdx = 0;
var rows = Mathf.CeilToInt((float)presetCount / cols);
for (int y = 0; y < rows; y++)
{
EditorGUILayout.BeginHorizontal();
for (int x = 0; x < cols; x++)
{
if (groupedIdx < presetCount)
{
if (GUILayout.Button(presets[idx].GetIconContent(presets[idx].name), iconButtonStyle, GUILayout.Width(itemWidth), GUILayout.Height(itemWidth)))
{
if (evt.button == 0)
{
var selectedPreset = presets[idx];
int appliedCount = 0;
if (mats != null && mats.Length > 0)
{
foreach (var m in mats)
{
if (m == null) continue;
Undo.RecordObject(m, "Apply PotaToon Preset");
// Ensure shader type matches preset
PotaToonGUIUtility.ChangeShader(m, (int)selectedPreset._ToonType, m_RenderQueue, false);
selectedPreset.ApplyTo(m);
EditorUtility.SetDirty(m);
appliedCount++;
}
}
else if (mat != null)
{
Undo.RecordObject(mat, "Apply PotaToon Preset");
selectedPreset.ApplyTo(mat);
EditorUtility.SetDirty(mat);
appliedCount = 1;
}
if (appliedCount > 1)
PotaToonGUIUtility.ShowNotification($"Applied preset: [{selectedPreset.name}] to {appliedCount} materials");
else if (appliedCount == 1)
PotaToonGUIUtility.ShowNotification($"Applied preset: [{selectedPreset.name}]");
}
else if (evt.button == 1)
{
evt.Use();
PopupWindow.Show(new Rect(0, 0, 0, 0), new MaterialPresetContextMenu(presets, idx, mat));
}
}
idx++;
groupedIdx++;
}
else
{
GUILayout.Space(itemWidth);
}
}
EditorGUILayout.EndHorizontal();
}
// Add divider if not a last group
if (i < groupedPresets.Count - 1)
EditorGUILayout.LabelField("", GUI.skin.horizontalSlider);
}
}
EditorGUILayout.EndScrollView();
EditorGUILayout.EndVertical();
}
private bool CreateAndAddPreset(List<PotaToonMaterialPresetBase> presets)
{
var typeName = GetType().Name;
var guids = AssetDatabase.FindAssets($"{typeName} t:MonoScript");
if (guids == null || guids.Length == 0)
return false;
var scriptPath = AssetDatabase.GUIDToAssetPath(guids[0]);
var editorDir = Path.GetDirectoryName(scriptPath).Replace("\\Scripts", "/");
var presetsBase = $"{editorDir}/Presets";
var materialBase = $"{presetsBase}/Material";
var typeString = PotaToonGUIUtility.k_Types[m_ShaderType];
var presetsDir = $"{materialBase}/{typeString}";
if (!AssetDatabase.IsValidFolder(presetsBase))
AssetDatabase.CreateFolder(editorDir, "Presets");
if (!AssetDatabase.IsValidFolder(materialBase))
AssetDatabase.CreateFolder(presetsBase, "Material");
if (!AssetDatabase.IsValidFolder(presetsDir))
AssetDatabase.CreateFolder(materialBase, typeString);
var assetPath = AssetDatabase.GenerateUniqueAssetPath($"{presetsDir}/New {typeString}.asset");
PotaToonMaterialPresetBase newPreset = m_ShaderType < (int)ToonType.Eye ? ScriptableObject.CreateInstance<PotaToonMaterialPreset>() : ScriptableObject.CreateInstance<PotaToonEyeMaterialPreset>();
newPreset._ToonType = (ToonType)m_ShaderType;
AssetDatabase.CreateAsset(newPreset, assetPath);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
presets.Add(newPreset);
return true;
}
private void ImportPreset()
{
var absPath = EditorUtility.OpenFilePanel("Import PotaToonMaterialPreset", "", "asset");
if (string.IsNullOrEmpty(absPath))
return;
var typeName = GetType().Name;
var guids = AssetDatabase.FindAssets($"{typeName} t:MonoScript");
if (guids == null || guids.Length == 0)
return;
var scriptPath = AssetDatabase.GUIDToAssetPath(guids[0]);
var editorDir = Path.GetDirectoryName(scriptPath).Replace("\\Scripts", "/");
var presetsBase = $"{editorDir}/Presets";
var materialBase = $"{presetsBase}/Material";
var typeString = PotaToonGUIUtility.k_Types[m_ShaderType];
var presetsDir = $"{materialBase}/{typeString}";
if (!AssetDatabase.IsValidFolder(presetsBase))
AssetDatabase.CreateFolder(editorDir, "Presets");
if (!AssetDatabase.IsValidFolder(materialBase))
AssetDatabase.CreateFolder(presetsBase, "Material");
if (!AssetDatabase.IsValidFolder(presetsDir))
AssetDatabase.CreateFolder(materialBase, typeString);
var fileName = Path.GetFileName(absPath);
var destPath = AssetDatabase.GenerateUniqueAssetPath($"{presetsDir}/{fileName}");
File.Copy(absPath, destPath, overwrite: false);
AssetDatabase.ImportAsset(destPath);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
var imported = AssetDatabase.LoadAssetAtPath<PotaToonMaterialPresetBase>(destPath);
if (imported == null)
{
EditorUtility.DisplayDialog("Invalid Preset",
"The selected file is not a PotaToonMaterialPreset asset.", "OK");
AssetDatabase.DeleteAsset(destPath);
AssetDatabase.SaveAssets();
return;
}
// Move preset folder based on type
int importedType = (int)imported._ToonType;
if (importedType != m_ShaderType)
{
typeString = PotaToonGUIUtility.k_Types[importedType];
presetsDir = $"{materialBase}/{typeString}";
var oldPath = destPath;
destPath = AssetDatabase.GenerateUniqueAssetPath($"{presetsDir}/{fileName}");
if (!AssetDatabase.IsValidFolder(presetsDir))
AssetDatabase.CreateFolder(materialBase, typeString);
AssetDatabase.MoveAsset(oldPath, destPath);
AssetDatabase.SaveAssets();
}
foreach (var materialPresets in s_MaterialPresets)
{
if (materialPresets.Key.Equals(importedType))
{
materialPresets.Value.Add(imported);
PotaToonGUIUtility.ShowNotification($"Imported {imported.name} into {imported._ToonType}!");
return;
}
}
}
protected void InitializePresetsAndIcons()
{
// Load default editor icons first if needed
PotaToonMaterialPresetBase.LoadPresetIconsIfNeeded();
var typeName = GetType().Name;
var guids = AssetDatabase.FindAssets($"{typeName} t:MonoScript");
if (guids == null || guids.Length == 0)
return;
var scriptPath = AssetDatabase.GUIDToAssetPath(guids[0]);
var editorDir = Path.GetDirectoryName(scriptPath).Replace("\\Scripts", "/");
var iconPath = $"{editorDir}/Textures/potatoon_icon.png";
s_PresetButtonIcon = AssetDatabase.LoadAssetAtPath<Texture2D>(iconPath);
for (int i = 0; i < PotaToonGUIUtility.k_Types.Length; i++)
s_MaterialPresets[i] = new List<PotaToonMaterialPresetBase>();
var presetDir = $"{editorDir}/Presets/Material";
if (AssetDatabase.IsValidFolder(presetDir))
{
foreach (var guid in AssetDatabase.FindAssets("t:PotaToonMaterialPreset", new[] { presetDir }))
{
var preset = AssetDatabase.LoadAssetAtPath<PotaToonMaterialPresetBase>(AssetDatabase.GUIDToAssetPath(guid));
if (preset != null)
s_MaterialPresets[(int)preset._ToonType].Add(preset);
}
foreach (var guid in AssetDatabase.FindAssets("t:PotaToonEyeMaterialPreset", new[] { presetDir }))
{
var preset = AssetDatabase.LoadAssetAtPath<PotaToonMaterialPresetBase>(AssetDatabase.GUIDToAssetPath(guid));
if (preset != null)
s_MaterialPresets[(int)preset._ToonType].Add(preset);
}
}
}
internal static void SetRenderQueueAndKeywords(Material[] materials, bool renderQueueChanged, bool autoRenderQueue, int renderQueue)
{
if (materials == null || materials.Length == 0)
return;
for (int i = 0; i < materials.Length; i++)
{
var mat = materials[i];
if (mat == null) continue;
int surfaceType = mat.GetInt("_SurfaceType");
if (!autoRenderQueue)
{
renderQueue = renderQueueChanged ? renderQueue : mat.renderQueue;
switch ((SurfaceType)surfaceType)
{
case SurfaceType.Opaque:
if (renderQueue > 2100) renderQueue = 2100;
break;
case SurfaceType.Cutout:
renderQueue = Mathf.Clamp(renderQueue, 2450, 2500);
break;
case SurfaceType.Refraction:
renderQueue = Mathf.Clamp(renderQueue, 2501, 2900);
break;
case SurfaceType.Transparent:
renderQueue = Mathf.Clamp(renderQueue, 2901, 5000);
break;
}
}
int finalRenderQueue = renderQueue;
if (autoRenderQueue)
finalRenderQueue = s_AutoRenderQueues[Mathf.Clamp(surfaceType, 0, s_AutoRenderQueues.Length - 1)];
mat.SetInt("_ZWriteMode", surfaceType < (int)SurfaceType.Refraction ? 1 : 0);
mat.SetInt("_AutoRenderQueue", autoRenderQueue ? 1 : 0);
mat.renderQueue = finalRenderQueue;
CoreUtils.SetKeyword(mat, ShaderKeywordStrings._ALPHATEST_ON, mat.renderQueue >= 2450);
CoreUtils.SetKeyword(mat, ShaderKeywordStrings._SURFACE_TYPE_TRANSPARENT, mat.renderQueue > 2500);
}
}
internal static void SetBlendingMode(Material[] materials)
{
if (materials == null || materials.Length == 0)
return;
for (int i = 0; i < materials.Length; i++)
{
var mat = materials[i];
if (mat == null) continue;
int surfaceType = mat.GetInt("_SurfaceType");
if (surfaceType < (int)SurfaceType.Refraction)
{
mat.SetInt(Property.SrcBlend, (int)BlendMode.One);
mat.SetInt(Property.DstBlend, (int)BlendMode.Zero);
mat.SetInt(Property.SrcBlendAlpha, (int)BlendMode.One);
mat.SetInt(Property.DstBlendAlpha, (int)BlendMode.Zero);
}
else
{
var alphaMode = (AlphaMode)mat.GetInt(Property.BlendMode);
switch (alphaMode)
{
case AlphaMode.Alpha:
mat.SetInt(Property.SrcBlend, (int)BlendMode.SrcAlpha);
mat.SetInt(Property.DstBlend, (int)BlendMode.OneMinusSrcAlpha);
mat.SetInt(Property.SrcBlendAlpha, (int)BlendMode.One);
mat.SetInt(Property.DstBlendAlpha, (int)BlendMode.OneMinusSrcAlpha);
break;
case AlphaMode.Premultiply:
mat.SetInt(Property.SrcBlend, (int)BlendMode.One);
mat.SetInt(Property.DstBlend, (int)BlendMode.OneMinusSrcAlpha);
mat.SetInt(Property.SrcBlendAlpha, (int)BlendMode.One);
mat.SetInt(Property.DstBlendAlpha, (int)BlendMode.OneMinusSrcAlpha);
break;
case AlphaMode.Additive:
mat.SetInt(Property.SrcBlend, (int)BlendMode.SrcAlpha);
mat.SetInt(Property.DstBlend, (int)BlendMode.One);
mat.SetInt(Property.SrcBlendAlpha, (int)BlendMode.One);
mat.SetInt(Property.DstBlendAlpha, (int)BlendMode.One);
break;
case AlphaMode.Multiply: // Multiply RGB only, keep A
mat.SetInt(Property.SrcBlend, (int)BlendMode.DstColor);
mat.SetInt(Property.DstBlend, (int)BlendMode.Zero);
mat.SetInt(Property.SrcBlendAlpha, (int)BlendMode.Zero);
mat.SetInt(Property.DstBlendAlpha, (int)BlendMode.One);
break;
}
}
}
}
}
internal class MaterialPresetContextMenu : PopupWindowContent
{
private List<PotaToonMaterialPresetBase> m_Presets;
private string m_TempName;
private int m_Index;
private Material m_Material;
public MaterialPresetContextMenu(List<PotaToonMaterialPresetBase> presets, int idx, Material mat)
{
m_Presets = presets;
m_TempName = m_Presets[idx].name;
m_Index = idx;
m_Material = mat;
}
public override Vector2 GetWindowSize() => new Vector2(250, 270);
public override void OnGUI(Rect rect)
{
var preset = m_Presets[m_Index];
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Edit Preset", EditorStyles.boldLabel, GUILayout.Height(20f));
if (GUILayout.Button("X", GUILayout.Width(20f)))
{
editorWindow.Close();
}
EditorGUILayout.EndHorizontal();
m_TempName = GUILayout.TextField(m_TempName);
if (GUILayout.Button("Rename", GUILayout.Height(20f)))
{
if (!preset.name.Equals(m_TempName, StringComparison.Ordinal))
{
var oldPath = AssetDatabase.GetAssetPath(preset);
var newNameNoExt = Path.GetFileNameWithoutExtension(m_TempName);
AssetDatabase.RenameAsset(oldPath, newNameNoExt);
AssetDatabase.SaveAssets();
PotaToonGUIUtility.ShowNotification($"Renamed to {m_TempName}.");
}
}
if (GUILayout.Button("Find Preset in Project", GUILayout.Height(20f)))
{
EditorUtility.FocusProjectWindow();
EditorGUIUtility.PingObject(preset);
}
if (GUILayout.Button("Export Preset", GUILayout.Height(20f)))
{
ExportPreset(preset);
}
// Icons
EditorGUILayout.BeginHorizontal();
var iconPreviewStyle = new GUIStyle()
{
alignment = TextAnchor.MiddleCenter,
};
EditorGUILayout.BeginVertical();
GUILayout.FlexibleSpace();
EditorGUILayout.LabelField(preset.GetIconContent(""), iconPreviewStyle, GUILayout.Width(100f), GUILayout.Height(100f));
GUILayout.FlexibleSpace();
EditorGUILayout.EndVertical();
var presetIconCount = PotaToonMaterialPresetBase.presetIconContents.Count;
const float iconBtnSize = 25f;
const int cols = 5;
var rows = Mathf.CeilToInt(presetIconCount / (float)cols);
EditorGUILayout.BeginVertical();
for (int row = 0; row < rows; row++)
{
EditorGUILayout.BeginHorizontal();
for (int col = 0; col < cols; col++)
{
int idx = row * cols + col;
if (idx < presetIconCount)
{
if (GUILayout.Button(PotaToonMaterialPresetBase.presetIconContents[idx], GUILayout.Width(iconBtnSize), GUILayout.Height(iconBtnSize)))
{
Undo.RecordObject(preset, "Change PotaToonMaterialPreset Icon");
preset.presetIconIndex = idx;
EditorUtility.SetDirty(preset);
AssetDatabase.SaveAssets();
PotaToonGUIUtility.ShowNotification("Icon changed.");
}
}
else
{
GUILayout.Space(iconBtnSize);
}
}
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.EndVertical();
EditorGUILayout.EndHorizontal();
var bottomStyle = new GUIStyle() {
padding = new RectOffset(2, 2, 0, 0)
};
EditorGUILayout.BeginHorizontal(bottomStyle, GUILayout.Height(20f));
if (GUILayout.Button("Save (Override)", GUILayout.ExpandHeight(true)))
{
// Rename if needed
if (!preset.name.Equals(m_TempName, StringComparison.Ordinal))
{
var oldPath = AssetDatabase.GetAssetPath(preset);
var newNameNoExt = Path.GetFileNameWithoutExtension(m_TempName);
AssetDatabase.RenameAsset(oldPath, newNameNoExt);
}
preset.SaveFrom(m_Material);
Undo.RecordObject(preset, "Save PotaToonMaterialPreset");
EditorUtility.SetDirty(preset);
AssetDatabase.SaveAssets();
PotaToonGUIUtility.ShowNotification($"Saved {preset.name}.");
}
if (GUILayout.Button("Delete", GUILayout.ExpandHeight(true)))
{
var path = AssetDatabase.GetAssetPath(preset);
if (EditorUtility.DisplayDialog("Delete Preset", $"Are you sure you want to delete '{preset.name}'? This operation can't be undone.", "Delete", "Cancel"))
{
AssetDatabase.DeleteAsset(path);
AssetDatabase.SaveAssets();
m_Presets.RemoveAt(m_Index);
}
editorWindow.Close();
}
EditorGUILayout.EndHorizontal();
}
private void ExportPreset(PotaToonMaterialPresetBase preset)
{
// Get source asset path
var sourcePath = AssetDatabase.GetAssetPath(preset);
if (string.IsNullOrEmpty(sourcePath))
{
EditorUtility.DisplayDialog("Export Failed", "Could not find the preset asset path.", "OK");
return;
}
// Ask user for target save path (anywhere)
var defaultName = preset.name + ".asset";
var absTarget = EditorUtility.SaveFilePanel(
"Export Material Preset",
"", // default folder
defaultName,
"asset"
);
if (string.IsNullOrEmpty(absTarget))
return;
// Convert source to absolute path
var absSource = Path.GetFullPath(sourcePath).Replace("\\", "/");
// Copy file
try
{
// Notify and refresh
System.IO.File.Copy(absSource, absTarget, overwrite: true);
EditorUtility.RevealInFinder(absTarget);
var win = EditorWindow.focusedWindow;
if (win != null)
win.ShowNotification(new GUIContent("Preset exported!"));
}
catch (System.Exception ex)
{
PotaToonLog($"Error exporting preset: {ex.Message}", true);
EditorUtility.DisplayDialog("Export Failed", ex.Message, "OK");
}
}
}
}