//********************************** Banshee Engine (www.banshee3d.com) **************************************************// //**************** Copyright (c) 2016 Marko Pintera (marko.pintera@gmail.com). All rights reserved. **********************// using System; using System.Collections.Generic; using bs; namespace bs.Editor { /** @addtogroup Inspectors * @{ */ /// /// Renders an inspector for the resource. /// [CustomInspector(typeof (Material))] internal class MaterialInspector : Inspector { private MaterialParamGUI[] guiParams; private MaterialVariationGUI guiVariations; private GUIResourceField shaderField; private GUIEnumField builtinShaderField; private Material material; /// protected internal override void Initialize() { LoadResource(); material = InspectedObject as Material; if (material == null) return; Shader activeShader = material.Shader.Value; BuiltinShader builtinType = ShaderToBuiltin(activeShader); builtinShaderField = new GUIEnumField(typeof(BuiltinShader), new LocEdString("Shader")); builtinShaderField.Value = (ulong) builtinType; builtinShaderField.OnSelectionChanged += OnBuiltinShaderFieldChanged; shaderField = new GUIResourceField(typeof(Shader), new LocEdString("Shader file")); shaderField.ValueRef = material.Shader; shaderField.OnChanged += (x) => { Shader shader = Resources.Load(x.UUID); material.Shader = shader; EditorApplication.SetDirty(material); RebuildParamGUI(material); }; bool isCustom = builtinType == BuiltinShader.Custom; shaderField.Active = isCustom; RebuildParamGUI(material); } /// protected internal override InspectableState Refresh(bool force = false) { if (material == null) return InspectableState.NotModified; if (material.Shader != shaderField.ValueRef) { shaderField.ValueRef = material.Shader; RebuildParamGUI(material); } if (guiParams != null) { foreach (var param in guiParams) param.Refresh(material); } return InspectableState.NotModified; } private void OnBuiltinShaderFieldChanged(UInt64 value) { Shader activeShader = material.Shader.Value; BuiltinShader builtinType = ShaderToBuiltin(activeShader); BuiltinShader newBuiltinType = (BuiltinShader)value; if (builtinType == newBuiltinType) return; material.Shader = Builtin.GetShader(newBuiltinType); EditorApplication.SetDirty(material); RebuildParamGUI(material); bool newIsCustom = newBuiltinType == BuiltinShader.Custom; shaderField.Active = newIsCustom; } /// /// Recreates GUI elements for all material parameters. /// /// Material to create parameters for private void RebuildParamGUI(Material material) { if (guiParams != null) { foreach (var param in guiParams) param.Destroy(); guiParams = null; } guiVariations = null; Layout.Clear(); Layout.AddElement(builtinShaderField); Layout.AddElement(shaderField); if (material != null && material.Shader != null) { guiVariations = new MaterialVariationGUI(material, Layout); guiParams = CreateMaterialGUI(material, "", null, Layout); } } /// /// Converts a shader resource into a builtin shader. /// /// Shader resource to convert. /// Type of builtin shader, if any. private BuiltinShader ShaderToBuiltin(Shader shader) { // Note: Might be nice to have a better way to detect the builtin shader perhaps (store it in Material?) // - Or I could just compare UUID's here to avoid loading the shaders var enumValues = Enum.GetValues(typeof(BuiltinShader)); for (int i = 1; i < enumValues.Length; i++) { Shader builtinShader = Builtin.GetShader((BuiltinShader) i); if (builtinShader == shader) return (BuiltinShader) i; } return BuiltinShader.Custom; } /// /// Creates a set of objects in which each object represents a GUI for a material parameter. /// /// Material for whose parameters to create GUI for. /// Path to the material field in the parent object. /// Optional component the material is part of (if any). /// Layout to add the parameter GUI elements to. /// A material parameter GUI object for each supported material parameter. internal static MaterialParamGUI[] CreateMaterialGUI(Material mat, string path, Component component, GUILayout layout) { Shader shader = mat.Shader.Value; if (shader == null) return new MaterialParamGUI[0]; List guiParams = new List(); ShaderParameter[] shaderParams = shader.Parameters; foreach (var param in shaderParams) { if (param.flags.HasFlag(ShaderParameterFlag.Internal) || param.flags.HasFlag(ShaderParameterFlag.HideInInspector)) continue; switch (param.type) { case ShaderParameterType.Float: layout.AddSpace(5); guiParams.Add(new MaterialParamFloatGUI(param, path, component, mat, layout)); break; case ShaderParameterType.Vector2: layout.AddSpace(5); guiParams.Add(new MaterialParamVec2GUI(param, path, component, mat, layout)); break; case ShaderParameterType.Vector3: layout.AddSpace(5); guiParams.Add(new MaterialParamVec3GUI(param, path, component, mat, layout)); break; case ShaderParameterType.Vector4: layout.AddSpace(5); guiParams.Add(new MaterialParamVec4GUI(param, path, component, mat, layout)); break; case ShaderParameterType.Matrix3: layout.AddSpace(5); guiParams.Add(new MaterialParamMat3GUI(param, path, component, mat, layout)); break; case ShaderParameterType.Matrix4: layout.AddSpace(5); guiParams.Add(new MaterialParamMat4GUI(param, path, component, mat, layout)); break; case ShaderParameterType.Color: bool hdr = param.flags.HasFlag(ShaderParameterFlag.HDR); layout.AddSpace(5); guiParams.Add(new MaterialParamColorGUI(param, path, hdr, component, mat, layout)); break; case ShaderParameterType.Texture2D: case ShaderParameterType.Texture3D: case ShaderParameterType.TextureCube: layout.AddSpace(5); guiParams.Add(new MaterialParamTextureGUI(param, path, component, mat, layout)); break; } } return guiParams.ToArray(); } } /// /// Draws GUI that allows the user to change the active shader variation for a material. /// internal class MaterialVariationGUI { private Material material; private ShaderVariation variation; /// /// Creates necessary GUI elements for selecting a material variation. /// /// Material for which to provide variation selection. /// GUI layout to which to append GUI elements. internal MaterialVariationGUI(Material material, GUILayout layout) { this.material = material; variation = material.Variation; Shader shader = material.Shader.Value; if (shader == null) return; ShaderVariationParamInfo[] variationParams = shader.VariationParams; foreach (var param in variationParams) { if (param.isInternal) continue; LocString[] names = new LocString[param.values.Length]; int[] values = new int[names.Length]; for (int i = 0; i < names.Length; i++) { names[i] = new LocEdString(param.values[i].name); values[i] = param.values[i].value; } GUIListBoxField listBox = new GUIListBoxField(names, new LocEdString(param.name)); listBox.OnSelectionChanged += idx => { int value = values[idx]; variation.SetInt(param.identifier, value); material.Variation = variation; }; int curValue = variation.GetInt(param.identifier); for (int j = 0; j < values.Length; j++) { if (curValue == values[j]) { listBox.Index = j; break; } } layout.AddElement(listBox); } } } /// /// Contains GUI element(s) for a single parameter in a . /// internal abstract class MaterialParamGUI { protected ShaderParameter shaderParam; protected string path; protected Component component; /// /// Underlying shader parameter the field is representing. /// internal ShaderParameter Param { get => shaderParam; } /// /// Creates a new material parameter GUI. /// /// Shader parameter to create the GUI for. /// Path to the material field in the parent object. /// Optional component the material is part of (if any). protected MaterialParamGUI(ShaderParameter shaderParam, string path, Component component) { this.shaderParam = shaderParam; this.path = path; this.component = component; } /// /// Checks if the data stored in GUI and in the material matches, and updates the GUI if it doesn't. /// /// Material whose data to check. internal abstract void Refresh(Material material); /// /// Destroys the internal GUI elements. /// internal abstract void Destroy(); /// /// Moves keyboard focus to this field. /// /// /// Name of the sub-field to focus on. Only relevant if the field represents multiple GUI input elements. /// public virtual void SetHasFocus(string subFieldName = null) { } /// /// Zero parameter wrapper for /// protected void StartUndo() { StartUndo(null); } /// /// Notifies the system to start recording a new undo command. Any changes to the field after this is called /// will be recorded in the command. User must call after field is done being changed. /// /// Additional path to append to the end of the current field path. protected void StartUndo(string subPath) { if (component != null) { string fullPath = path.TrimEnd('/'); fullPath += "/" + shaderParam.name; if (!string.IsNullOrEmpty(subPath)) fullPath += '/' + subPath.TrimStart('/'); GameObjectUndo.RecordComponent(component, fullPath); } } /// /// Finishes recording an undo command started via . If any changes are detected on /// the field an undo command is recorded onto the undo-redo stack, otherwise nothing is done. /// protected void EndUndo() { GameObjectUndo.ResolveDiffs(); } } /// /// Contains GUI element(s) for a single floating point parameter in a . /// internal class MaterialParamFloatGUI : MaterialParamGUI { private GUILayout fieldLayout; private GUIFloatField guiConstant; private GUICurvesField guiCurves; /// /// Creates a new material parameter GUI. /// /// Shader parameter to create the GUI for. Must be of floating point type. /// Path to the material field in the parent object. /// Optional component the material is part of (if any). /// Material the parameter is a part of. /// Layout to append the GUI elements to. internal MaterialParamFloatGUI(ShaderParameter shaderParam, string path, Component component, Material material, GUILayout layout) : base(shaderParam, path, component) { LocString title = new LocEdString(shaderParam.name); var guiToggle = new GUIToggle(new GUIContent( EditorBuiltin.GetEditorToggleIcon(EditorToggleIcon.AnimateProperty), new LocString("Animate"))); guiConstant = new GUIFloatField(title); guiCurves = new GUICurvesField(title); bool isAnimated = material.IsAnimated(shaderParam.name); guiConstant.Active = !isAnimated; guiCurves.Active = isAnimated; fieldLayout = layout.AddLayoutX(); fieldLayout.AddElement(guiConstant); fieldLayout.AddElement(guiCurves); fieldLayout.AddSpace(10); fieldLayout.AddElement(guiToggle); guiConstant.OnChanged += (x) => { material.SetFloat(shaderParam.name, x); EditorApplication.SetDirty(material); }; guiConstant.OnFocusGained += () => StartUndo("constant"); guiConstant.OnFocusLost += EndUndo; guiConstant.OnConfirmed += () => { EndUndo(); StartUndo("constant"); }; guiCurves.OnChanged += x => { StartUndo("curve"); material.SetFloatCurve(shaderParam.name, x); EditorApplication.SetDirty(material); EndUndo(); }; guiToggle.OnToggled += x => { guiConstant.Active = !x; guiCurves.Active = x; }; } /// internal override void Refresh(Material material) { bool isAnimated = material.IsAnimated(shaderParam.name); if (isAnimated) guiCurves.SetCurve(material.GetFloatCurve(shaderParam.name)); else guiConstant.Value = material.GetFloat(shaderParam.name); } /// internal override void Destroy() { fieldLayout.Destroy(); } /// public override void SetHasFocus(string subFieldName = null) { if (subFieldName == "constant") guiConstant.Focus = true; } } /// /// Contains GUI element(s) for a single 2D vector parameter in a . /// internal class MaterialParamVec2GUI : MaterialParamGUI { private GUIVector2Field guiElem; /// /// Creates a new material parameter GUI. /// /// Shader parameter to create the GUI for. Must be of 2D vector type. /// Path to the material field in the parent object. /// Optional component the material is part of (if any). /// Material the parameter is a part of. /// Layout to append the GUI elements to. internal MaterialParamVec2GUI(ShaderParameter shaderParam, string path, Component component, Material material, GUILayout layout) : base(shaderParam, path, component) { LocString title = new LocEdString(shaderParam.name); guiElem = new GUIVector2Field(title); guiElem.OnValueChanged += (x) => { material.SetVector2(shaderParam.name, x); EditorApplication.SetDirty(material); }; guiElem.OnComponentFocusChanged += (focus, comp) => { if(focus) StartUndo(comp.ToString()); else EndUndo(); }; guiElem.OnConfirm += comp => { EndUndo(); StartUndo(comp.ToString()); }; layout.AddElement(guiElem); } /// internal override void Refresh(Material material) { guiElem.Value = material.GetVector2(shaderParam.name); } /// internal override void Destroy() { guiElem.Destroy(); } /// public override void SetHasFocus(string subFieldName = null) { if (subFieldName == "x") guiElem.SetInputFocus(VectorComponent.X, true); else if(subFieldName == "y") guiElem.SetInputFocus(VectorComponent.Y, true); } } /// /// Contains GUI element(s) for a single 3D vector parameter in a . /// internal class MaterialParamVec3GUI : MaterialParamGUI { private GUIVector3Field guiElem; /// /// Creates a new material parameter GUI. /// /// Shader parameter to create the GUI for. Must be of 3D vector type. /// Path to the material field in the parent object. /// Optional component the material is part of (if any). /// Material the parameter is a part of. /// Layout to append the GUI elements to. /// /// If true, any assigned texture resources will be loaded in memory. If false the resources will be just referenced /// without loading. /// internal MaterialParamVec3GUI(ShaderParameter shaderParam, string path, Component component, Material material, GUILayout layout) : base(shaderParam, path, component) { LocString title = new LocEdString(shaderParam.name); guiElem = new GUIVector3Field(title); guiElem.OnValueChanged += (x) => { material.SetVector3(shaderParam.name, x); EditorApplication.SetDirty(material); }; guiElem.OnComponentFocusChanged += (focus, comp) => { if(focus) StartUndo(comp.ToString()); else EndUndo(); }; guiElem.OnConfirm += comp => { EndUndo(); StartUndo(comp.ToString()); }; layout.AddElement(guiElem); } /// internal override void Refresh(Material material) { guiElem.Value = material.GetVector3(shaderParam.name); } /// internal override void Destroy() { guiElem.Destroy(); } /// public override void SetHasFocus(string subFieldName = null) { if (subFieldName == "x") guiElem.SetInputFocus(VectorComponent.X, true); else if(subFieldName == "y") guiElem.SetInputFocus(VectorComponent.Y, true); else if(subFieldName == "z") guiElem.SetInputFocus(VectorComponent.Z, true); } } /// /// Contains GUI element(s) for a single 4D vector parameter in a . /// internal class MaterialParamVec4GUI : MaterialParamGUI { private GUIVector4Field guiElem; /// /// Creates a new material parameter GUI. /// /// Shader parameter to create the GUI for. Must be of 4D vector type. /// Path to the material field in the parent object. /// Optional component the material is part of (if any). /// Material the parameter is a part of. /// Layout to append the GUI elements to. internal MaterialParamVec4GUI(ShaderParameter shaderParam, string path, Component component, Material material, GUILayout layout) : base(shaderParam, path, component) { LocString title = new LocEdString(shaderParam.name); guiElem = new GUIVector4Field(title); guiElem.OnValueChanged += (x) => { material.SetVector4(shaderParam.name, x); EditorApplication.SetDirty(material); }; guiElem.OnComponentFocusChanged += (focus, comp) => { if(focus) StartUndo(comp.ToString()); else EndUndo(); }; guiElem.OnConfirm += comp => { EndUndo(); StartUndo(comp.ToString()); }; layout.AddElement(guiElem); } /// internal override void Refresh(Material material) { guiElem.Value = material.GetVector4(shaderParam.name); } /// internal override void Destroy() { guiElem.Destroy(); } /// public override void SetHasFocus(string subFieldName = null) { if (subFieldName == "x") guiElem.SetInputFocus(VectorComponent.X, true); else if(subFieldName == "y") guiElem.SetInputFocus(VectorComponent.Y, true); else if(subFieldName == "z") guiElem.SetInputFocus(VectorComponent.Z, true); else if(subFieldName == "w") guiElem.SetInputFocus(VectorComponent.W, true); } } /// /// Contains GUI element(s) for a single 3x3 matrix parameter in a . /// internal class MaterialParamMat3GUI : MaterialParamGUI { private const int MAT_SIZE = 3; private GUILayout mainLayout; private GUIFloatField[] guiMatFields = new GUIFloatField[MAT_SIZE * MAT_SIZE]; /// /// Creates a new material parameter GUI. /// /// Shader parameter to create the GUI for. Must be of 3x3 matrix type. /// Path to the material field in the parent object. /// Optional component the material is part of (if any). /// Material the parameter is a part of. /// Layout to append the GUI elements to. internal MaterialParamMat3GUI(ShaderParameter shaderParam, string path, Component component, Material material, GUILayout layout) : base(shaderParam, path, component) { LocString title = new LocEdString(shaderParam.name); GUILabel guiTitle = new GUILabel(title, GUIOption.FixedWidth(100)); mainLayout = layout.AddLayoutY(); GUILayoutX titleLayout = mainLayout.AddLayoutX(); titleLayout.AddElement(guiTitle); titleLayout.AddFlexibleSpace(); GUILayoutY contentLayout = mainLayout.AddLayoutY(); GUILayoutX[] rows = new GUILayoutX[MAT_SIZE]; for (int i = 0; i < rows.Length; i++) rows[i] = contentLayout.AddLayoutX(); for (int row = 0; row < MAT_SIZE; row++) { for (int col = 0; col < MAT_SIZE; col++) { int index = row * MAT_SIZE + col; guiMatFields[index] = new GUIFloatField(row + "," + col, 20, "", GUIOption.FixedWidth(80)); GUIFloatField field = guiMatFields[index]; rows[row].AddElement(field); rows[row].AddSpace(5); int hoistedRow = row; int hoistedCol = col; field.OnChanged += (x) => { Matrix3 value = material.GetMatrix3(shaderParam.name); value[hoistedRow, hoistedCol] = x; material.SetMatrix3(shaderParam.name, value); EditorApplication.SetDirty(material); }; field.OnFocusGained += () => StartUndo(hoistedRow + "x" + hoistedCol); field.OnFocusLost += EndUndo; field.OnConfirmed += () => { EndUndo(); StartUndo(hoistedRow + "x" + hoistedCol); }; } } } /// internal override void Refresh(Material material) { Matrix3 value = material.GetMatrix3(shaderParam.name); for (int row = 0; row < MAT_SIZE; row++) { for (int col = 0; col < MAT_SIZE; col++) { int index = row * MAT_SIZE + col; guiMatFields[index].Value = value[row, col]; } } } /// internal override void Destroy() { mainLayout.Destroy(); } /// public override void SetHasFocus(string subFieldName = null) { if (string.IsNullOrEmpty(subFieldName)) return; string[] parts = subFieldName.Split('x'); if (parts.Length != 2) return; if (!int.TryParse(parts[0], out int row) || !int.TryParse(parts[1], out int col)) return; if (row >= MAT_SIZE && col >= MAT_SIZE) return; int index = row * MAT_SIZE + col; guiMatFields[index].Focus = true; } } /// /// Contains GUI element(s) for a single 4x4 matrix parameter in a . /// internal class MaterialParamMat4GUI : MaterialParamGUI { private const int MAT_SIZE = 4; private GUILayout mainLayout; private GUIFloatField[] guiMatFields = new GUIFloatField[MAT_SIZE * MAT_SIZE]; /// /// Creates a new material parameter GUI. /// /// Shader parameter to create the GUI for. Must be of 4x4 matrix type. /// Path to the material field in the parent object. /// Optional component the material is part of (if any). /// Material the parameter is a part of. /// Layout to append the GUI elements to. internal MaterialParamMat4GUI(ShaderParameter shaderParam, string path, Component component, Material material, GUILayout layout) : base(shaderParam, path, component) { LocString title = new LocEdString(shaderParam.name); GUILabel guiTitle = new GUILabel(title, GUIOption.FixedWidth(100)); mainLayout = layout.AddLayoutY(); GUILayoutX titleLayout = mainLayout.AddLayoutX(); titleLayout.AddElement(guiTitle); titleLayout.AddFlexibleSpace(); GUILayoutY contentLayout = mainLayout.AddLayoutY(); GUILayoutX[] rows = new GUILayoutX[MAT_SIZE]; for (int i = 0; i < rows.Length; i++) rows[i] = contentLayout.AddLayoutX(); for (int row = 0; row < MAT_SIZE; row++) { for (int col = 0; col < MAT_SIZE; col++) { int index = row * MAT_SIZE + col; guiMatFields[index] = new GUIFloatField(row + "," + col, 20, "", GUIOption.FixedWidth(80)); GUIFloatField field = guiMatFields[index]; rows[row].AddElement(field); rows[row].AddSpace(5); int hoistedRow = row; int hoistedCol = col; field.OnChanged += (x) => { Matrix4 value = material.GetMatrix4(shaderParam.name); value[hoistedRow, hoistedCol] = x; material.SetMatrix4(shaderParam.name, value); EditorApplication.SetDirty(material); }; field.OnFocusGained += () => StartUndo(hoistedRow + "x" + hoistedCol); field.OnFocusLost += EndUndo; field.OnConfirmed += () => { EndUndo(); StartUndo(hoistedRow + "x" + hoistedCol); }; } } } /// internal override void Refresh(Material material) { Matrix4 value = material.GetMatrix4(shaderParam.name); for (int row = 0; row < MAT_SIZE; row++) { for (int col = 0; col < MAT_SIZE; col++) { int index = row * MAT_SIZE + col; guiMatFields[index].Value = value[row, col]; } } } /// internal override void Destroy() { mainLayout.Destroy(); } /// public override void SetHasFocus(string subFieldName = null) { if (string.IsNullOrEmpty(subFieldName)) return; string[] parts = subFieldName.Split('x'); if (parts.Length != 2) return; if (!int.TryParse(parts[0], out int row) || !int.TryParse(parts[1], out int col)) return; if (row >= MAT_SIZE && col >= MAT_SIZE) return; int index = row * MAT_SIZE + col; guiMatFields[index].Focus = true; } } /// /// Contains GUI element(s) for a single color parameter in a . /// internal class MaterialParamColorGUI : MaterialParamGUI { private GUILayout fieldLayout; private GUIColorField guiColor; private GUIColorGradientHDRField guiColorGradient; /// /// Creates a new material parameter GUI. /// /// Shader parameter to create the GUI for. Must be of color type. /// Path to the material field in the parent object. /// If true the color supports range outside of [0, 1]. /// Optional component the material is part of (if any). /// Material the parameter is a part of. /// Layout to append the GUI elements to. internal MaterialParamColorGUI(ShaderParameter shaderParam, string path, bool hdr, Component component, Material material, GUILayout layout) : base(shaderParam, path, component) { LocString title = new LocEdString(shaderParam.name); var guiToggle = new GUIToggle(new GUIContent( EditorBuiltin.GetEditorToggleIcon(EditorToggleIcon.AnimateProperty), new LocString("Animate"))); guiColor = new GUIColorField(title); guiColor.AllowHDR = hdr; guiColorGradient = new GUIColorGradientHDRField(title); bool isAnimated = material.IsAnimated(shaderParam.name); guiColor.Active = !isAnimated; guiColorGradient.Active = isAnimated; fieldLayout = layout.AddLayoutX(); fieldLayout.AddElement(guiColor); fieldLayout.AddElement(guiColorGradient); fieldLayout.AddSpace(10); fieldLayout.AddElement(guiToggle); guiColor.OnChanged += (x) => { StartUndo(); material.SetColor(shaderParam.name, x); EditorApplication.SetDirty(material); EndUndo(); }; guiColorGradient.OnChanged += x => { StartUndo(); material.SetColorGradient(shaderParam.name, x); EditorApplication.SetDirty(material); EndUndo(); }; guiToggle.OnToggled += x => { guiColor.Active = !x; guiColorGradient.Active = x; if (x) { ColorGradientHDR gradient = material.GetColorGradient(shaderParam.name); if (gradient.NumKeys == 0) material.SetColorGradient(shaderParam.name, new ColorGradientHDR(material.GetColor(shaderParam.name))); } }; } /// internal override void Refresh(Material material) { bool isAnimated = material.IsAnimated(shaderParam.name); if (isAnimated) guiColorGradient.Value = material.GetColorGradient(shaderParam.name); else guiColor.Value = material.GetColor(shaderParam.name); } /// internal override void Destroy() { fieldLayout.Destroy(); } } /// /// Contains GUI element(s) for a single texture parameter in a . /// internal class MaterialParamTextureGUI : MaterialParamGUI { private GUITextureField guiElem; /// /// Creates a new material parameter GUI. /// /// Shader parameter to create the GUI for. Must be of texture type. /// Path to the material field in the parent object. /// Optional component the material is part of (if any). /// Material the parameter is a part of. /// Layout to append the GUI elements to. internal MaterialParamTextureGUI(ShaderParameter shaderParam, string path, Component component, Material material, GUILayout layout) : base(shaderParam, path, component) { LocString title = new LocEdString(shaderParam.name); GUITextureFieldType type = shaderParam.type == ShaderParameterType.Texture2D ? GUITextureFieldType.TextureOrSpriteTexture : GUITextureFieldType.Texture; guiElem = new GUITextureField(type, title); switch (shaderParam.type) { case ShaderParameterType.Texture2D: case ShaderParameterType.Texture3D: case ShaderParameterType.TextureCube: guiElem.OnChanged += (x) => { string texPath = ProjectLibrary.GetPath(x.UUID); if (!string.IsNullOrEmpty(texPath)) { if (ProjectLibrary.GetEntry(texPath) is FileEntry fileEntry) { if (fileEntry.ResourceMetas.Length > 0) { StartUndo(); // Note: Ideally we can avoid loading texture resources if the material is not // referenced anywhere in the scene. But we don't have a good way to check that at // the moment. ResourceMeta meta = fileEntry.ResourceMetas[0]; if (meta.ResType == ResourceType.SpriteTexture) material.SetSpriteTexture(shaderParam.name, Resources.LoadAsync(x.UUID)); else if (meta.ResType == ResourceType.Texture) material.SetTexture(shaderParam.name, Resources.LoadAsync(x.UUID)); EndUndo(); } } } else { StartUndo(); material.SetTexture(shaderParam.name, null); EndUndo(); } EditorApplication.SetDirty(material); }; break; } layout.AddElement(guiElem); } /// internal override void Refresh(Material material) { RRef texture; switch (shaderParam.type) { case ShaderParameterType.Texture2D: RRef spriteTex = material.GetSpriteTexture(shaderParam.name); if (spriteTex != null && spriteTex.UUID != UUID.Empty) guiElem.SpriteTextureRef = spriteTex; else { texture = material.GetTexture(shaderParam.name); guiElem.TextureRef = texture; } break; case ShaderParameterType.Texture3D: case ShaderParameterType.TextureCube: texture = material.GetTexture(shaderParam.name); guiElem.TextureRef = texture; break; } } /// internal override void Destroy() { guiElem.Destroy(); } } /** @} */ }