Skip to content

Commit

Permalink
Add Button Drawer
Browse files Browse the repository at this point in the history
Change Image Drawer: Instead of using the default texture, Image Drawer uses the Display Name as the path, so it supports reading an image outside the project.
  • Loading branch information
JasonMa0012 committed Nov 27, 2024
1 parent 94ef4c1 commit 5dae047
Show file tree
Hide file tree
Showing 18 changed files with 889 additions and 36 deletions.
14 changes: 8 additions & 6 deletions Editor/AssetProcessor/ExcludeFromBuild.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEngine;
// using System.Collections.Generic;
// using System.IO;
// using UnityEditor;
// using UnityEditor.Build;
// using UnityEditor.Build.Reporting;
// using UnityEngine;

namespace LWGUI
{
/*
/// <summary>
/// Used to exclude textures referenced by ImageDrawer in Build
/// </summary>
Expand Down Expand Up @@ -109,4 +110,5 @@ public void OnPostprocessBuild(BuildReport report)
OutputLogs();
}
}
*/
}
9 changes: 6 additions & 3 deletions Editor/MetaData/PerShaderData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public class DisplayModeData
public bool IsDefaultDisplayMode() { return !(showAllAdvancedProperties || showAllHiddenProperties || showOnlyModifiedProperties || showOnlyModifiedGroups); }
}

public class PropertyStaticData
public partial class PropertyStaticData
{
public string name = string.Empty;
public string displayName = string.Empty; // Decoded displayName (Helpbox and Tooltip are encoded in displayName)
Expand Down Expand Up @@ -105,6 +105,7 @@ public PerShaderData(Shader shader, MaterialProperty[] props)
propStaticDatas[prop.name] = propStaticData;

// Get Drawers and Build Drawer StaticMetaData
bool hasDecodedStaticMetaData = false;
{
var drawer = ReflectionHelper.GetPropertyDrawer(shader, prop, out var decoratorDrawers);

Expand All @@ -116,6 +117,7 @@ public PerShaderData(Shader shader, MaterialProperty[] props)
{
propStaticData.baseDrawers = new List<IBaseDrawer>() { baseDrawer };
baseDrawer.BuildStaticMetaData(shader, prop, props, propStaticData);
hasDecodedStaticMetaData = true;
}

decoratorDrawers?.ForEach(decoratorDrawer =>
Expand All @@ -133,7 +135,8 @@ public PerShaderData(Shader shader, MaterialProperty[] props)
});
}

DecodeMetaDataFromDisplayName(prop, propStaticData);
if (!hasDecodedStaticMetaData)
DecodeMetaDataFromDisplayName(prop, propStaticData);
}

// Check Data
Expand Down Expand Up @@ -245,7 +248,7 @@ public PropertyStaticData GetPropStaticData(string propName)

private static readonly string _helpboxSplitter = "%";

public void DecodeMetaDataFromDisplayName(MaterialProperty prop, PropertyStaticData propStaticData)
public static void DecodeMetaDataFromDisplayName(MaterialProperty prop, PropertyStaticData propStaticData)
{
var tooltips = prop.displayName.Split(new String[] { _tooltipSplitter }, StringSplitOptions.None);
if (tooltips.Length > 1)
Expand Down
217 changes: 203 additions & 14 deletions Editor/ShaderDrawer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using LWGUI.LwguiGradientEditor;
using LWGUI.Runtime.LwguiGradient;
using UnityEditor;
Expand All @@ -25,6 +27,17 @@ public interface IBasePresetDrawer
{
ShaderPropertyPreset.Preset GetActivePreset(MaterialProperty inProp, ShaderPropertyPreset shaderPropertyPreset);
}

public partial class PropertyStaticData
{
// Image
public Texture2D image;

// Button
public List<string> buttonDisplayNames = new();
public List<string> buttonCommands = new();
public List<float> buttonDisplayNameWidths = new();
}

/// <summary>
/// Create a Folding Group
Expand Down Expand Up @@ -66,6 +79,7 @@ public virtual void BuildStaticMetaData(Shader inShader, MaterialProperty inProp
inoutPropertyStaticData.groupName = _group;
inoutPropertyStaticData.isMain = true;
inoutPropertyStaticData.isExpanding = _defaultFoldingState;
PerShaderData.DecodeMetaDataFromDisplayName(inProp, inoutPropertyStaticData);
}

public virtual void GetDefaultValueDescription(Shader inShader, MaterialProperty inProp, MaterialProperty inDefaultProp, PerShaderData inPerShaderData, PerMaterialData inoutPerMaterialData)
Expand Down Expand Up @@ -137,6 +151,7 @@ protected virtual float GetVisibleHeight(MaterialProperty prop)
public virtual void BuildStaticMetaData(Shader inShader, MaterialProperty inProp, MaterialProperty[] inProps, PropertyStaticData inoutPropertyStaticData)
{
inoutPropertyStaticData.groupName = group;
PerShaderData.DecodeMetaDataFromDisplayName(inProp, inoutPropertyStaticData);
}

public virtual void GetDefaultValueDescription(Shader inShader, MaterialProperty inProp, MaterialProperty inDefaultProp, PerShaderData inPerShaderData, PerMaterialData inoutPerMaterialData) { }
Expand Down Expand Up @@ -691,15 +706,12 @@ public override void DrawProp(Rect position, MaterialProperty prop, GUIContent l
}

/// <summary>
/// Draw a read only texture preview. Select the default texture to be displayed in the shader import settings.
/// Note: Selected default textures will always be excluded from the build!!!
/// Draw an image preview.
/// display name: The path of the image file relative to the Unity project, such as: "Assets/test.png", "Doc/test.png", "../test.png"
/// group:father group name, support suffix keyword for conditional display (Default: none)
/// Target Property Type: Texture
/// </summary>
public class ImageDrawer : SubDrawer
{
private Texture _defaultTex = null;

public ImageDrawer() { }

public ImageDrawer(string group)
Expand All @@ -709,23 +721,39 @@ public ImageDrawer(string group)

protected override float GetVisibleHeight(MaterialProperty prop) { return 0; }

protected override bool IsMatchPropType(MaterialProperty property) { return property.type == MaterialProperty.PropType.Texture; }

public override void OverrideDefaultValue(Shader inShader, MaterialProperty inProp, MaterialProperty inDefaultProp, PerShaderData inPerShaderData)
public override void BuildStaticMetaData(Shader inShader, MaterialProperty inProp, MaterialProperty[] inProps, PropertyStaticData inoutPropertyStaticData)
{
// To disable revert button
_defaultTex = inDefaultProp.textureValue;
inDefaultProp.textureValue = null;
var imagePath = Application.dataPath.Substring(0, Application.dataPath.Length - 6) + inProp.displayName;
if (File.Exists(imagePath))
{
var fileData = File.ReadAllBytes(imagePath);
Texture2D texture = new Texture2D(2, 2);

// LoadImage will auto-resize the texture dimensions
if (texture.LoadImage(fileData))
{
inoutPropertyStaticData.image = texture;
}
else
{
Debug.LogError($"LWGUI: Failed to load image data into texture: { imagePath }");
}
}
else
{
Debug.LogError($"LWGUI: Image path not found: { imagePath }");
}
}

public override void DrawProp(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor)
{
if (_defaultTex)
var image = metaDatas.GetPropStaticData(prop).image;
if (image)
{
var scaledheight = Mathf.Max(0, _defaultTex.height / (_defaultTex.width / Helper.GetCurrentPropertyLayoutWidth()));
var scaledheight = Mathf.Max(0, image.height / (image.width / Helper.GetCurrentPropertyLayoutWidth()));
var rect = EditorGUILayout.GetControlRect(true, scaledheight);
rect = RevertableHelper.IndentRect(EditorGUI.IndentedRect(rect));
EditorGUI.DrawPreviewTexture(rect, _defaultTex);
EditorGUI.DrawPreviewTexture(rect, image);

if (GUI.enabled)
prop.textureValue = null;
Expand Down Expand Up @@ -1176,6 +1204,167 @@ public override void Apply(MaterialProperty prop)
presetFile.presets[(int)prop.floatValue].ApplyKeywordsToMaterials(prop.targets);
}
}

/// <summary>
/// Draw one or more Buttons within the same row, using the Display Name to control the appearance and behavior of the buttons
///
/// Declaring a set of Button Name and Button Command in Display Name generates a Button, separated by '@':
/// ButtonName0@ButtonCommand0@ButtonName1@ButtonCommand1
///
/// Button Name can be any other string, the format of Button Command is:
/// TYPE:Argument
///
/// The following TYPEs are currently supported:
/// - URL: Open the URL, Argument is the URL
/// - C#: Call the public static C# function, Argument is NameSpace.Class.Method(arg0, arg1, ...),
/// for target function signatures, see: LWGUI.ButtonDrawer.TestMethod().
///
/// The full example:
/// [Button(_)] _button0 ("URL Button@URL:https://github.com/JasonMa0012/LWGUI@C#:LWGUI.ButtonDrawer.TestMethod(1234, abcd)", Float) = 0
///
/// group:father group name, support suffix keyword for conditional display (Default: none)
/// </summary>
public class ButtonDrawer : SubDrawer
{
private const string _urlPrefix = "URL:";
private const string _csPrefix = "C#:";
private const string _separator = "@";

public ButtonDrawer() { }

public ButtonDrawer(string group)
{
this.group = group;
}

public override void BuildStaticMetaData(Shader inShader, MaterialProperty inProp, MaterialProperty[] inProps, PropertyStaticData inoutPropertyStaticData)
{
inoutPropertyStaticData.groupName = group;

// Display Name: ButtonName@URL:XXX@ButtonName@CS:NameSpace.Class.Method(arg0, arg1, ...)@...
var buttonNameAndCommands = inProp.displayName.Split(_separator);
if (buttonNameAndCommands != null && buttonNameAndCommands.Length > 0 && buttonNameAndCommands.Length % 2 == 0)
{
for (int i = 0; i < buttonNameAndCommands.Length; i++)
{
if (i % 2 == 0)
{
inoutPropertyStaticData.buttonDisplayNames.Add(buttonNameAndCommands[i]);
inoutPropertyStaticData.buttonDisplayNameWidths.Add(EditorStyles.label.CalcSize(new GUIContent(buttonNameAndCommands[i])).x);
}
else
{
inoutPropertyStaticData.buttonCommands.Add(buttonNameAndCommands[i]);
}
}
}
else
{
Debug.LogError($"LWGUI: ButtonDrawer with invalid Display Name Commands: { buttonNameAndCommands } ! prop: { inProp.name }");
}
}

public override void DrawProp(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor)
{
var buttonDisplayNames = metaDatas.GetPropStaticData(prop).buttonDisplayNames;
var buttonDisplayNameWidths = metaDatas.GetPropStaticData(prop).buttonDisplayNameWidths;
var buttonCommands = metaDatas.GetPropStaticData(prop).buttonCommands;
if (buttonDisplayNames == null || buttonCommands == null || buttonDisplayNames.Count == 0 || buttonCommands.Count == 0
|| buttonDisplayNames.Count != buttonCommands.Count)
{
return;
}

var enbaled = GUI.enabled;
GUI.enabled = true;

position = EditorGUI.IndentedRect(position);
var rect = new Rect(position.x, position.y, 0, position.height);
var spaceWidth = (position.width - buttonDisplayNameWidths.Sum()) / buttonDisplayNames.Count;

for (int i = 0; i < buttonDisplayNames.Count; i++)
{
var displayName = buttonDisplayNames[i];
var displayNameRelativeWidth = buttonDisplayNameWidths[i];
var command = buttonCommands[i];
rect.xMax = rect.xMin + displayNameRelativeWidth + spaceWidth;

if (GUI.Button(rect, new GUIContent(displayName, command)))
{
if (command.StartsWith(_urlPrefix))
{
Application.OpenURL(command.Substring(_urlPrefix.Length, command.Length - _urlPrefix.Length));
}
else if (command.StartsWith(_csPrefix))
{
var csCommand = command.Substring(_csPrefix.Length, command.Length - _csPrefix.Length);

// Get method name and args
string className = null, methodName = null;
string[] args = null;
{
var lastPointIndex = csCommand.LastIndexOf('.');
if (lastPointIndex != -1)
{
className = csCommand.Substring(0, lastPointIndex);
var leftBracketIndex = csCommand.IndexOf('(');
if (leftBracketIndex != -1)
{
methodName = csCommand.Substring(lastPointIndex + 1, leftBracketIndex - lastPointIndex - 1);
args = csCommand.Substring(leftBracketIndex + 1, csCommand.Length - leftBracketIndex - 2)
?.Split(',').Select(s => s.TrimStart()).ToArray();
}
}
}

// Find and call method
if (!string.IsNullOrEmpty(className) && !string.IsNullOrEmpty(methodName) && args != null)
{
Type type = ReflectionHelper.GetAllTypes().FirstOrDefault((type1 => type1.Name == className || type1.FullName == className));
if (type != null)
{
var methodInfo = type.GetMethod(methodName, BindingFlags.Static | BindingFlags.Public);
if (methodInfo != null)
{
methodInfo.Invoke(null, new object[]{ prop, editor, metaDatas, args });
}
else
{
Debug.LogError($"LWGUI: Method {methodName} not found in {className}");
}
}
else
{
Debug.LogError($"LWGUI: Class {className} not found");
}
}
else
{
Debug.LogError($"LWGUI: Invalid C# command: {csCommand}");
}
}
else
{
Debug.LogError($"LWGUI: Unknown command type: {command}");
}
}

rect.xMin = rect.xMax;
}

GUI.enabled = enbaled;
}

public static void TestMethod(MaterialProperty prop, MaterialEditor editor, LWGUIMetaDatas metaDatas, string[] args)
{
Debug.Log($"LWGUI: ButtonDrawer.TestMethod({prop}, {editor}, {metaDatas}, {args})");

foreach (var arg in args)
{
Debug.Log(arg);
}
}
}

/// <summary>
/// Similar to Header()
Expand Down
Loading

0 comments on commit 5dae047

Please sign in to comment.