Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ME] Add renderer and material filter lists #241

Merged
merged 12 commits into from
Mar 21, 2024
1 change: 1 addition & 0 deletions src/MaterialEditor.Base/MaterialEditor.Base.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<Compile Include="$(MSBuildThisFileDirectory)UI\UI.ItemInfo.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UI\UI.ItemTemplate.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UI\UI.ListEntry.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UI\UI.SelectListPanel.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UI\UI.VirtualList.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Utilities.cs" />
</ItemGroup>
Expand Down
3 changes: 3 additions & 0 deletions src/MaterialEditor.Base/PluginBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public partial class MaterialEditorPluginBase : BaseUnityPlugin
public static ConfigEntry<float> UIScale { get; set; }
public static ConfigEntry<float> UIWidth { get; set; }
public static ConfigEntry<float> UIHeight { get; set; }
public static ConfigEntry<float> UIListWidth { get; set; }
public static ConfigEntry<bool> WatchTexChanges { get; set; }
public static ConfigEntry<bool> ShaderOptimization { get; set; }
public static ConfigEntry<bool> ExportBakedMesh { get; set; }
Expand All @@ -59,6 +60,7 @@ public virtual void Awake()
UIScale = Config.Bind("Config", "UI Scale", 1.75f, new ConfigDescription("Controls the size of the window.", new AcceptableValueRange<float>(1f, 3f), new ConfigurationManagerAttributes { Order = 5 }));
UIWidth = Config.Bind("Config", "UI Width", 0.3f, new ConfigDescription("Controls the size of the window.", new AcceptableValueRange<float>(0f, 1f), new ConfigurationManagerAttributes { Order = 4, ShowRangeAsPercent = false }));
UIHeight = Config.Bind("Config", "UI Height", 0.3f, new ConfigDescription("Controls the size of the window.", new AcceptableValueRange<float>(0f, 1f), new ConfigurationManagerAttributes { Order = 3, ShowRangeAsPercent = false }));
UIListWidth = Config.Bind("Config", "UI List Width", 180f, new ConfigDescription("Controls width of the renderer/materials lists to the side of the window", new AcceptableValueRange<float>(100f, 500f), new ConfigurationManagerAttributes { Order = 2, ShowRangeAsPercent = false }));
WatchTexChanges = Config.Bind("Config", "Watch File Changes", true, new ConfigDescription("Watch for file changes and reload textures on change. Can be toggled in the UI.", null, new ConfigurationManagerAttributes { Order = 2 }));
ShaderOptimization = Config.Bind("Config", "Shader Optimization", true, new ConfigDescription("Replaces every loaded shader with the MaterialEditor copy of the shader. Reduces the number of copies of shaders loaded which reduces RAM usage and improves performance.", null, new ConfigurationManagerAttributes { Order = 1 }));
ExportBakedMesh = Config.Bind("Config", "Export Baked Mesh", false, new ConfigDescription("When enabled, skinned meshes will be exported in their current state with all customization applied as well as in the current pose.", null, new ConfigurationManagerAttributes { Order = 1 }));
Expand All @@ -70,6 +72,7 @@ public virtual void Awake()
UIScale.SettingChanged += MaterialEditorUI.UISettingChanged;
UIWidth.SettingChanged += MaterialEditorUI.UISettingChanged;
UIHeight.SettingChanged += MaterialEditorUI.UISettingChanged;
UIListWidth.SettingChanged += MaterialEditorUI.UISettingChanged;
WatchTexChanges.SettingChanged += WatchTexChanges_SettingChanged;
ShaderOptimization.SettingChanged += ShaderOptimization_SettingChanged;
ConfigExportPath.SettingChanged += ConfigExportPath_SettingChanged;
Expand Down
96 changes: 96 additions & 0 deletions src/MaterialEditor.Base/UI/UI.SelectListPanel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
using System;
using System.Collections.Generic;
using UILib;
using UnityEngine;
using UnityEngine.UI;
using static MaterialEditorAPI.MaterialEditorPluginBase;

namespace MaterialEditorAPI
{
internal class SelectListPanel
{
public Image Panel { get; }
private string name;
private Transform parent;
private ScrollRect scrollRect;

private InputField filterInputField;
private Dictionary<string, Image> listItems;

public SelectListPanel(Transform parent, string name, string title)
{
listItems = new Dictionary<string, Image>();
this.parent = parent;
this.name = name;

Panel = UIUtility.CreatePanel($"{name}Panel", parent);
Panel.color = new Color(0.42f, 0.42f, 0.42f);

var nametext = UIUtility.CreateText($"{name}Title", Panel.transform, title);
nametext.transform.SetRect(0f, 1f, 0.4f, 1f, 5f, -MaterialEditorUI.HeaderSize, -2f, -2f);
nametext.alignment = TextAnchor.UpperLeft;

filterInputField = UIUtility.CreateInputField($"{name}Filter", Panel.transform, "Filter");
filterInputField.text = "";
filterInputField.transform.SetRect(0.4f, 1f, 1f, 1f, 2f, -MaterialEditorUI.HeaderSize, -2f, -2f);
filterInputField.onValueChanged.AddListener(FilterList);

scrollRect = UIUtility.CreateScrollView(name, Panel.transform);
scrollRect.transform.SetRect(0f, 0f, 1f, 1f, 2f, 2f, -2f, -MaterialEditorUI.HeaderSize);
scrollRect.gameObject.AddComponent<Mask>();
scrollRect.content.gameObject.AddComponent<VerticalLayoutGroup>();
scrollRect.content.gameObject.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
scrollRect.verticalScrollbar.GetComponent<RectTransform>().offsetMin = new Vector2(MaterialEditorUI.ScrollOffsetX, 0f);
scrollRect.viewport.offsetMax = new Vector2(MaterialEditorUI.ScrollOffsetX, 0f);
scrollRect.movementType = ScrollRect.MovementType.Clamped;
scrollRect.verticalScrollbar.GetComponent<Image>().color = new Color(1, 1, 1, 0.6f);
}

public void AddEntry(string name, Action<bool> onValueChanged)
{
if (listItems.ContainsKey(name))
return;

var contentList = UIUtility.CreatePanel($"{this.name}Entry", scrollRect.content.transform);
contentList.gameObject.AddComponent<LayoutElement>().preferredHeight = MaterialEditorUI.PanelHeight;
contentList.gameObject.AddComponent<Mask>();
contentList.color = MaterialEditorUI.RowColor;

var itemPanel = UIUtility.CreatePanel($"{this.name}EntryPanel", contentList.transform);
itemPanel.gameObject.AddComponent<CanvasGroup>();
itemPanel.gameObject.AddComponent<HorizontalLayoutGroup>().padding = MaterialEditorUI.Padding;
itemPanel.color = MaterialEditorUI.ItemColor;

Toggle toggle = UIUtility.CreateToggle($"{this.name}Toggle", itemPanel.transform, name);
var toggleLE = toggle.gameObject.AddComponent<LayoutElement>();
toggle.gameObject.GetComponentInChildren<CanvasRenderer>(true).transform.SetRect(0f, 1f, 0f, 1f, 1f, -18f, 18f, -1f);
toggle.isOn = false;
toggle.onValueChanged.AddListener(value => onValueChanged(value));

itemPanel.gameObject.AddComponent<Button>().onClick.AddListener(() => toggle.isOn = !toggle.isOn);

listItems[name] = contentList;
FilterList(filterInputField.text);
}

public void ClearList()
{
if (!PersistFilter.Value)
filterInputField.Set("");
listItems.Clear();
foreach (Transform child in scrollRect.content.transform)
UnityEngine.Object.Destroy(child.gameObject);
}

public void ToggleVisibility(bool visible)
{
Panel.gameObject.SetActive(visible);
}

private void FilterList(string filter)
{
foreach (var name in listItems.Keys)
listItems[name].gameObject.SetActive(MaterialEditorUI.WildCardSearch(name, filter));
}
}
}
105 changes: 96 additions & 9 deletions src/MaterialEditor.Base/UI/UI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ public abstract class MaterialEditorUI : BaseUnityPlugin
private static ScrollRect MaterialEditorScrollableUI;
private static InputField FilterInputField;

private static SelectListPanel MaterialEditorRendererList;
private static List<Renderer> SelectedRenderers = new List<Renderer>();
private static SelectListPanel MaterialEditorMaterialList;
private static List<Material> SelectedMaterials = new List<Material>();
private static bool ListsVisible = false;


internal static FileSystemWatcher TexChangeWatcher;
private VirtualList VirtualList;

Expand Down Expand Up @@ -112,7 +119,7 @@ protected void InitUI()
persistSearchText.transform.SetRect(0f, 0.15f, 1f, 0.85f, 120f, 0f, 0, 0f);

var close = UIUtility.CreateButton("CloseButton", DragPanel.transform, "");
close.transform.SetRect(1f, 0f, 1f, 1f, -20f, 1f, -1f, -1f);
close.transform.SetRect(1f, 0f, 1f, 1f, -40f, 1f, -21f, -1f);
close.onClick.AddListener(() => Visible = false);

//X button
Expand All @@ -125,6 +132,19 @@ protected void InitUI()
x2.rectTransform.eulerAngles = new Vector3(0f, 0f, -45f);
x2.color = Color.black;

var listButton = UIUtility.CreateButton("ViewListButton", DragPanel.transform, ">");
listButton.transform.SetRect(1f, 0f, 1f, 1f, -20f, 1f, -1f, -1f);
listButton.onClick.AddListener(() => MaterialEditorRendererList.ToggleVisibility(!ListsVisible));
listButton.onClick.AddListener(() => MaterialEditorMaterialList.ToggleVisibility(!ListsVisible));
listButton.onClick.AddListener(() => {
var text = listButton.GetComponentInChildren<Text>(true);
ListsVisible = !ListsVisible;
if (ListsVisible)
text.text = "<";
else
text.text = ">";
});

MaterialEditorScrollableUI = UIUtility.CreateScrollView("MaterialEditorWindow", MaterialEditorMainPanel.transform);
MaterialEditorScrollableUI.transform.SetRect(0f, 0f, 1f, 1f, MarginSize, MarginSize, -MarginSize, -HeaderSize - MarginSize / 2f);
MaterialEditorScrollableUI.gameObject.AddComponent<Mask>();
Expand All @@ -141,6 +161,13 @@ protected void InitUI()
VirtualList.ScrollRect = MaterialEditorScrollableUI;
VirtualList.EntryTemplate = template;
VirtualList.Initialize();

MaterialEditorRendererList = new SelectListPanel(MaterialEditorMainPanel.transform, "RendererList", "Renderers");
MaterialEditorRendererList.Panel.transform.SetRect(1f, 0.5f, 1f, 1f, MarginSize, MarginSize / 2f, MarginSize + UIListWidth.Value);
MaterialEditorRendererList.ToggleVisibility(false);
MaterialEditorMaterialList = new SelectListPanel(MaterialEditorMainPanel.transform, "MaterialList", "Materials");
MaterialEditorMaterialList.Panel.transform.SetRect(1f, 0f, 1f, 0.5f, MarginSize, 0f, MarginSize + UIListWidth.Value, -MarginSize);
MaterialEditorMaterialList.ToggleVisibility(false);
}

/// <summary>
Expand Down Expand Up @@ -186,19 +213,74 @@ internal static void UISettingChanged(object sender, EventArgs e)
MaterialEditorWindow.GetComponent<CanvasScaler>().referenceResolution = new Vector2(1920f / UIScale.Value, 1080f / UIScale.Value);
if (MaterialEditorMainPanel != null)
SetMainRectWithMemory(0.05f, 0.05f, UIWidth.Value * UIScale.Value, UIHeight.Value * UIScale.Value);
if (MaterialEditorRendererList != null)
MaterialEditorRendererList.Panel.transform.SetRect(1f, 0.5f, 1f, 1f, MarginSize, MarginSize / 2f, MarginSize + UIListWidth.Value);
if (MaterialEditorMaterialList != null)
MaterialEditorMaterialList.Panel.transform.SetRect(1f, 0f, 1f, 0.5f, MarginSize, 0f, MarginSize + UIListWidth.Value, -MarginSize);
}

/// <summary>
/// Search text using wildcards.
/// </summary>
/// <param name="text">Text to search in</param>
/// <param name="filter">Filter with which to search the text</param>
private bool WildCardSearch(string text, string filter)
internal static bool WildCardSearch(string text, string filter)
{
string regex = "^.*" + Regex.Escape(filter).Replace("\\?", ".").Replace("\\*", ".*") + ".*$";
return Regex.IsMatch(text, regex, RegexOptions.IgnoreCase);
}

/// <summary>
/// Populate the renderer list
/// </summary>
/// <param name="go">GameObject for which to read the renderers</param>
/// <param name="data">Object that will be passed through to the get/set/reset events</param>
/// <param name="rendListFull">List of all renderers to display</param>
private void PopulateRendererList(GameObject go, object data, IEnumerable<Renderer> rendListFull)
{
if (go != CurrentGameObject)
{
SelectedRenderers.Clear();
MaterialEditorRendererList.ClearList();

foreach (var rend in rendListFull)
MaterialEditorRendererList.AddEntry(rend.NameFormatted(), value =>
{
if (value)
SelectedRenderers.Add(rend);
else
SelectedRenderers.Remove(rend);
PopulateList(go, data, CurrentFilter);
PopulateMaterialList(go, data, rendListFull);
});
PopulateMaterialList(go, data, rendListFull);
}
}


/// <summary>
/// Populate the materials list
/// </summary>
/// <param name="go">GameObject for which to read the renderers</param>
/// <param name="data">Object that will be passed through to the get/set/reset events</param>
/// <param name="materials">List of all materials to display</param>
private void PopulateMaterialList(GameObject go, object data, IEnumerable<Renderer> materials)
{
SelectedMaterials.Clear();
MaterialEditorMaterialList.ClearList();

foreach (var rend in materials.Where(rend => SelectedRenderers.Count == 0 || SelectedRenderers.Contains(rend)))
foreach (var mat in GetMaterials(go, rend))
MaterialEditorMaterialList.AddEntry(mat.NameFormatted(), value =>
{
if (value)
SelectedMaterials.Add(mat);
else
SelectedMaterials.Remove(mat);
PopulateList(go, data, CurrentFilter);
});
}

/// <summary>
/// Populate the MaterialEditor UI
/// </summary>
Expand All @@ -218,10 +300,6 @@ protected void PopulateList(GameObject go, object data, string filter = null)
SetMainRectWithMemory(0.05f, 0.05f, UIWidth.Value * UIScale.Value, UIHeight.Value * UIScale.Value);
FilterInputField.Set(filter);

CurrentGameObject = go;
CurrentData = data;
CurrentFilter = filter;

if (go == null) return;

List<Renderer> rendList = new List<Renderer>();
Expand All @@ -231,6 +309,12 @@ protected void PopulateList(GameObject go, object data, string filter = null)
List<ItemInfo> items = new List<ItemInfo>();
Dictionary<string, Material> matList = new Dictionary<string, Material>();

PopulateRendererList(go, data, rendListFull);

CurrentGameObject = go;
CurrentData = data;
CurrentFilter = filter;

if (!filter.IsNullOrEmpty())
{
filterList = filter.Split(',').Select(x => x.Trim()).ToList();
Expand All @@ -242,7 +326,9 @@ protected void PopulateList(GameObject go, object data, string filter = null)
}

//Get all renderers and materials matching the filter
if (filterList.Count == 0)
if (SelectedRenderers.Count > 0)
rendList.AddRange(SelectedRenderers);
else if (filterList.Count == 0)
rendList = rendListFull.ToList();
else
foreach (var rend in rendListFull)
Expand All @@ -251,7 +337,7 @@ protected void PopulateList(GameObject go, object data, string filter = null)
if (WildCardSearch(rend.NameFormatted(), filterWord.Trim()) && !rendList.Contains(rend))
rendList.Add(rend);

foreach (var mat in GetMaterials(go, rend))
foreach (var mat in SelectedMaterials.Count == 0 ? GetMaterials(go, rend) : GetMaterials(go, rend).Where(mat => SelectedMaterials.Contains(mat)))
foreach (string filterWord in filterList)
if (WildCardSearch(mat.NameFormatted(), filterWord.Trim()))
matList[mat.NameFormatted()] = mat;
Expand All @@ -262,7 +348,7 @@ protected void PopulateList(GameObject go, object data, string filter = null)
var rend = rendList[i];
//Get materials if materials list wasn't previously built by the filter
if (filterList.Count == 0)
foreach (var mat in GetMaterials(go, rend))
foreach (var mat in SelectedMaterials.Count == 0 ? GetMaterials(go, rend) : GetMaterials(go, rend).Where(mat => SelectedMaterials.Contains(mat)))
matList[mat.NameFormatted()] = mat;

var rendererItem = new ItemInfo(ItemInfo.RowItemType.Renderer, "Renderer")
Expand Down Expand Up @@ -361,6 +447,7 @@ protected void PopulateList(GameObject go, object data, string filter = null)
{
MaterialCopyRemove(data, mat, go);
PopulateList(go, data, filter);
PopulateMaterialList(go, data, rendListFull);
};
items.Add(materialItem);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ private void InitStudioUI(string sceneName)
InitUI();

ItemTypeDropDown = UIUtility.CreateDropdown("ItemType", DragPanel.transform);
ItemTypeDropDown.transform.SetRect(1f, 0f, 1f, 1f, -200f, 1f, -19f, -1f);
ItemTypeDropDown.transform.SetRect(1f, 0f, 1f, 1f, -220f, 1f, -39f, -1f);
ItemTypeDropDown.captionText.transform.SetRect(0.05f, 0f, 1f, 1f, 5f, 2f, -15f, -2f);
ItemTypeDropDown.captionText.alignment = TextAnchor.MiddleLeft;
ItemTypeDropDown.gameObject.SetActive(false);
Expand Down