/// <summary> /// When the window is first shown, create and organize all the top-level UI elements. /// </summary> private void InitializeUI() { var root = rootVisualElement; messageField = new Label(); headerContainer = new Box(); headerContainer.AddToClassList(headersClassName); headerContainer.AddToClassList("top-headers"); headerContainer.AddToClassList(rowClassName); valueScrollView = new ScrollView(); valueScrollView.AddToClassList("value-container"); buttonContainer = new Box(); buttonContainer.AddToClassList("button-container"); buttonContainer.AddToClassList(rowClassName); // In Windows & Mac editor, providing a callback on Button creation works a expected. uploadButton = new Button(() => SyncDataManager.UpdateRemoteConfigAsync( RemoteConfigData.CreateUploadData())) { text = "Upload to Remote Config" }; downloadButton = new Button(() => SyncDataManager.GetRemoteConfigDataAsync()) { text = "Sync from Remote Config" }; resetChangesButton = new Button(ResetLocalChanges) { text = "Reset All" }; // Register callbacks on MouseDownEvent for Linux editor. #if UNITY_EDITOR && !UNITY_EDITOR_WIN && !UNITY_EDITOR_OSX downloadButton.RegisterCallback <MouseDownEvent>( evt => SyncDataManager.GetRemoteConfigDataAsync()); uploadButton.RegisterCallback <MouseDownEvent>(evt => SyncDataManager .UpdateRemoteConfigAsync(RemoteConfigData.CreateUploadData())); resetChangesButton.RegisterCallback <MouseDownEvent>(evt => ResetLocalChanges()); #endif // Until first Remote Config data sync, only show Sync From button. buttonContainer.Add(downloadButton); root.Add(headerContainer); root.Add(valueScrollView); root.Add(buttonContainer); // Add target style to root element. root.styleSheets.Add(AssetDatabase.LoadAssetAtPath <StyleSheet>(stylePath)); }
/// <summary> /// Used for the top-level SyncTargetContainer which contains all discovered SyncTargets. /// Creates a header box with "Sync Targets" and uses this element as the top level UI /// element for all created children. /// </summary> /// <returns>This VisualElement, to contain all created UI child elements.</returns> private VisualElement CreateTopLevelGroupElement() { var box = new Box(); box.AddToClassList("headers"); box.AddToClassList("row"); var header = new Label("Sync Targets"); box.Add(header); this.Add(box); return(this); }
private TextField AddTextField(string text, VisualElement container, EventCallback <ChangeEvent <string> > e, int lines = 1) { var row = new Box(); row.AddToClassList("properties-item"); var label = new Label(text); var field = new TextField(); if (lines > 1) { field.multiline = true; field.AddToClassList("multiline"); } field.RegisterCallback(e); row.Add(label); row.Add(field); container.Add(row); return(field); }
void PopulatePackageList(string search = null) { ListView packageListView = rootVisualElement.Q <ListView>("listView"); packageListView.Clear(); for (int i = 0; i < packageList.Count; i++) { int elementID = i; if (search != null) { if (!packageList[i].name.ToLower().StartsWith(search)) { continue; } } Box listContainer = new Box(); listContainer.name = "listItem"; listContainer.style.height = 30; listContainer.AddManipulator(new Clickable(() => { SelectPackage(elementID); PopulatePackageList(); UpdateRightPanel(); })); Label listLabel = new Label(packageList[i].name); listLabel.style.unityTextAlign = TextAnchor.LowerLeft; listLabel.style.marginLeft = 3; listLabel.style.height = 23; Box separator = new Box(); separator.style.width = new StyleLength(StyleKeyword.Auto); separator.style.height = 2; separator.style.bottom = 0; separator.style.marginTop = new StyleLength(StyleKeyword.Auto); separator.AddToClassList("separator"); listContainer.Add(listLabel); listContainer.Add(separator); packageListView.Insert(packageListView.childCount, listContainer); if (elementID == selectedPackage) { listContainer.style.backgroundColor = new StyleColor(new Color32(48, 48, 48, 255)); } } }
public override VisualElement CreateInspectorGUI() { var root = new VisualElement { name = "HierarchyHeaderRoot" }; scrollView = new ScrollView(); #if UNITY_2020_2_OR_NEWER var boxContainer = new Box(); boxContainer.AddToClassList("mainBoxContainer"); boxContainer.Add(scrollView); #endif serializedObject.Update(); var property = serializedObject.GetIterator(); if (property.NextVisible(true)) { do { var propPath = property.propertyPath; var propertyField = new PropertyField(property) { name = "PropertyField:" + propPath }; switch (propPath) { case "m_Script" when serializedObject.targetObject != null: propertyField.visible = true; propertyField.SetEnabled(true); break; default: if (property.IsReallyArray() && serializedObject.targetObject != null) { var copiedProperty = property.Copy(); // @formatter:off #if UNITY_2020_2_OR_NEWER var imDefaultProperty = new IMGUIContainer(() => { DoDrawDefaultIMGUIProperty(serializedObject, copiedProperty); }) { name = propPath }; #else reorderableList = new ReorderableList(serializedObject, copiedProperty) { drawHeaderCallback = DrawHeaderCallback, drawElementCallback = DrawElementCallback, elementHeightCallback = ElementHeightCallback, onAddCallback = OnAddCallback }; // @formatter:on reorderableList.elementHeightCallback = ElementHeightCallback; var imDefaultProperty = new IMGUIContainer(() => { serializedObject.Update(); reorderableList.DoLayoutList(); serializedObject.ApplyModifiedProperties(); }) { name = propPath }; #endif imDefaultProperty.RegisterCallback <ChangeEvent <bool> >(evt => RecomputeSize(imDefaultProperty)); scrollView.Add(imDefaultProperty); } break; // @formatter:on } } while (property.NextVisible(false)); } foreach (var foldoutList in scrollView.Query <Foldout>().ToList()) { foldoutList.RegisterValueChangedCallback(e => { if (!(e.target is Foldout fd)) { return; } var path = fd.bindingPath; var container = scrollView.Q <IMGUIContainer>(path); RecomputeSize(container); }); } serializedObject.ApplyModifiedProperties(); #if UNITY_2020_2_OR_NEWER root.Add(boxContainer); #else root.Add(scrollView); #endif return(root); }
public override VisualElement CreateInspectorGUI() { if (Selection.activeObject is null || Selection.objects.Length == 0) { return(base.CreateInspectorGUI()); } if (!GetType().IsSubclassOf(typeof(ScriptableObject)) || categoryList is null || categoryList.Count == 0) { return(base.CreateInspectorGUI()); } if (!idConfig.AAIConfiguration().enableCustomEditors) { return(base.CreateInspectorGUI()); } var baseStyleSheet = idConfig.GetStyleSheet("AAIDefaultEditorBase"); if (defaultStyleSheet is null) { defaultStyleSheet = idConfig.GetStyleSheet("AAIDefaultEditorStyle"); } if (defaultStyleSheet is null) { Debug.Log("Could not locate AAIDefaultEditorStyle"); } serializedObject.Update(); defaultRoot = new VisualElement(); defaultRoot.styleSheets.Add(baseStyleSheet); defaultRoot.styleSheets.Add(defaultStyleSheet); defaultRoot.AddToClassList("rootContainer"); var boxContainer = new Box(); boxContainer.AddToClassList("mainBoxContainer"); if (beforeDefaultElements == null) { beforeDefaultElements = new VisualElement(); } beforeDefaultElements.name = "beforeDefaultElements"; beforeDefaultElements.AddToClassList("beforeDefaultElements"); defaultRoot.Add(beforeDefaultElements); categoryList.ForEach(x => { if (x is null) { return; } x.AddToClassList("categoryFoldout"); boxContainer.Add(x); }); m_ScrollView = new ScrollView(); boxContainer.Add(m_ScrollView); keyData = classDataDictionary.Keys.ToList(); #region Property Iteration var property = serializedObject.GetIterator(); if (property.NextVisible(true)) { do { // -- Shortening name for ease of typing ------------- var propPath = property.propertyPath; // -- Skip over excluded fields ---------------------- if (excludedFields.Contains(propPath) && serializedObject.targetObject != null) { continue; } // -- Property row VisualElement --------------------- var propertyRow = new VisualElement(); var propertyColumn = new VisualElement(); propertyRow.AddToClassList("propertyRow"); propertyColumn.AddToClassList("propertyColumn"); // -- Property fallback field ------------------------ var propertyField = new PropertyField(property) { name = "PropertyField:" + propPath }; // -- Determine if current property is field data ---- if (!classDataDictionary[Enumerable.First(keyData)].fieldDatas.Keys.Contains(propPath)) { switch (propPath) { case "m_Script" when serializedObject.targetObject != null: propertyField.visible = false; // @formatter:off propertyField.SetEnabled(false); break; default: if (property.IsReallyArray() && serializedObject.targetObject != null) { var copiedProperty = property.Copy(); var imDefaultProperty = new IMGUIContainer(() => { DoDrawDefaultIMGUIProperty(serializedObject, copiedProperty); }) { name = propPath }; m_ScrollView.Add(imDefaultProperty); } break; // @formatter:on } } else { var propertyData = classDataDictionary[Enumerable.First(keyData)].fieldDatas[propPath]; switch (propertyData.fieldInfo) { // -- String/TextField Elements -------------- case FieldInfo a when a.FieldType == typeof(string): case FieldInfo b when b.FieldType == typeof(PropertyName): if (defaultEditorDebug) { Debug.Log($"String: {propPath}"); } var propertyTextLabel = new Label(property.displayName); propertyTextLabel.name = $"{propPath}Label"; var propertyTextField = new TextField { bindingPath = propPath, name = $"{propPath}Text" }; if (propertyData.categoryAttr.toolTip != "") { propertyTextLabel.tooltip = propertyData.categoryAttr.toolTip; propertyTextField.tooltip = propertyData.categoryAttr.toolTip; } propertyTextLabel.AddToClassList("propertyTextLabel"); propertyTextField.AddToClassList("propertyTextField"); propertyRow.Add(propertyTextLabel); propertyRow.Add(propertyTextField); boxContainer.Q(propertyData.categoryAttr.category).Add(propertyRow); break; // -- Integer Elements ----------------------- case FieldInfo a when a.FieldType == typeof(int): if (defaultEditorDebug) { Debug.Log($"Integer: {propPath}"); } var propertyIntegerLabel = new Label(property.displayName); propertyIntegerLabel.name = $"{propPath}Label"; var propertyIntegerField = new IntegerField { bindingPath = propPath, name = $"{propPath}Integer" }; if (propertyData.categoryAttr.toolTip != "") { propertyIntegerLabel.tooltip = propertyData.categoryAttr.toolTip; propertyIntegerField.tooltip = propertyData.categoryAttr.toolTip; } propertyIntegerLabel.AddToClassList("propertyIntegerLabel"); propertyIntegerField.AddToClassList("propertyIntegerField"); propertyRow.Add(propertyIntegerLabel); propertyRow.Add(propertyIntegerField); boxContainer.Q(propertyData.categoryAttr.category).Add(propertyRow); break; // -- Float Elements ------------------------- case FieldInfo a when a.FieldType == typeof(float): if (defaultEditorDebug) { Debug.Log($"Float: {propPath}"); } var propertyFloatLabel = new Label(property.displayName); propertyFloatLabel.name = $"{propPath}Label"; var propertyFloatField = new FloatField { bindingPath = propPath, name = $"{propPath}Float" }; if (propertyData.categoryAttr.toolTip != "") { propertyFloatLabel.tooltip = propertyData.categoryAttr.toolTip; propertyFloatField.tooltip = propertyData.categoryAttr.toolTip; } propertyFloatLabel.AddToClassList("propertyFloatLabel"); propertyFloatField.AddToClassList("propertyFloatField"); propertyRow.Add(propertyFloatLabel); propertyRow.Add(propertyFloatField); boxContainer.Q(propertyData.categoryAttr.category).Add(propertyRow); break; // -- Bool/Toggle Elements ------------------- case FieldInfo a when a.FieldType == typeof(bool): case FieldInfo b when b.FieldType == typeof(Toggle): if (defaultEditorDebug) { Debug.Log($"Toggle: {propPath}"); } var propertyToggleLabel = new Label(property.displayName); propertyToggleLabel.name = $"{propPath}ToggleLabel"; var propertyToggleSpacer = new VisualElement(); var propertyToggleField = new Toggle { bindingPath = propPath, name = $"{propPath}ToggleField" }; if (propertyData.categoryAttr.toolTip != "") { propertyToggleLabel.tooltip = propertyData.categoryAttr.toolTip; propertyToggleField.tooltip = propertyData.categoryAttr.toolTip; } propertyToggleLabel.AddToClassList("propertyToggleLabel"); propertyToggleLabel.AddToClassList("propertyToggleSpacer"); propertyToggleField.AddToClassList("propertyToggleField"); propertyRow.Add(propertyToggleLabel); propertyRow.Add(propertyToggleField); propertyRow.Add(propertyToggleSpacer); propertyRow.RemoveFromClassList("propertyRow"); propertyRow.AddToClassList("propertyToggleRow"); boxContainer.Q(propertyData.categoryAttr.category).Add(propertyRow); break; // -- Dictionary Elements -------------------- case FieldInfo a when typeof(IDictionary).IsAssignableFrom(a.FieldType): case FieldInfo b when typeof(IDictionary).IsSubclassOf(b.FieldType): var dictionaryFoldout = new Foldout { text = property.displayName }; dictionaryFoldout.AddToClassList("arrayFoldout"); dictionaryFoldout.value = false; if (propertyData.categoryAttr.toolTip != "") { dictionaryFoldout.tooltip = propertyData.categoryAttr.toolTip; propertyColumn.tooltip = propertyData.categoryAttr.toolTip; } dictionaryFoldout.Add(propertyField); propertyColumn.Add(dictionaryFoldout); boxContainer.Q(propertyData.categoryAttr.category).Add(propertyColumn); break; // -- List/Set Elements ---------------------- case FieldInfo a when typeof(IList).IsAssignableFrom(a.FieldType): case FieldInfo b when typeof(IList).IsSubclassOf(b.FieldType): case FieldInfo c when typeof(ISet <>).IsAssignableFrom(c.FieldType): case FieldInfo d when typeof(ISet <>).IsSubclassOf(d.FieldType): var arrayElementBuilder = new ArrayElementBuilder(property, propertyData); if (propertyData.categoryAttr.toolTip != "") { propertyRow.tooltip = propertyData.categoryAttr.toolTip; } propertyRow.Add(arrayElementBuilder); boxContainer.Q(propertyData.categoryAttr.category).Add(propertyRow); break; // -- Object Elements ---------------------- case FieldInfo a when a.FieldType == typeof(Object): case FieldInfo b when typeof(Object).IsSubclassOf(b.FieldType): case FieldInfo c when typeof(Object).IsAssignableFrom(c.FieldType): var propertyObjectLabel = new Label(property.displayName); propertyObjectLabel.name = $"{propPath}ObjectLabel"; var propertyObjectField = new ObjectField { objectType = propertyData.fieldType, bindingPath = propPath, name = $"{propPath}ObjectField" }; if (propertyData.categoryAttr.toolTip != "") { propertyObjectLabel.tooltip = propertyData.categoryAttr.toolTip; propertyObjectField.tooltip = propertyData.categoryAttr.toolTip; } propertyObjectLabel.AddToClassList("propertyObjectLabel"); propertyObjectField.AddToClassList("propertyObjectField"); propertyRow.Add(propertyObjectLabel); propertyRow.Add(propertyObjectField); boxContainer.Q(propertyData.categoryAttr.category).Add(propertyRow); if (defaultEditorDebug) { Debug.Log($"Fallback Test: Name: {propPath} Type: {property.type} Array: {property.isArray} : {property.propertyType}"); } break; default: if (property.IsReallyArray()) { propertyColumn.Add(propertyField); boxContainer.Q(propertyData.categoryAttr.category).Add(propertyColumn); } // else propertyColumn.Add(propertyField); // if (propertyData.categoryAttr.toolTip != "") { propertyColumn.tooltip = propertyData.categoryAttr.toolTip; } // // boxContainer.Q(propertyData.categoryAttr.category).Add(propertyColumn); break; } } } while (property.NextVisible(false)); } #endregion foreach (var foldoutList in m_ScrollView.Query <Foldout>().ToList()) { foldoutList.RegisterValueChangedCallback(e => { // TODO Remove this: if (!(e.target is Foldout fd)) { return; } Debug.Log($" {fd.name}"); var path = fd.bindingPath; var container = m_ScrollView.Q <IMGUIContainer>(path); RecomputeSize(container); }); } foreach (var foldoutList in m_ScrollView.Query <AnimatedFoldout>().ToList()) { foldoutList.RegisterValueChangedCallback(e => { // TODO Remove this: if (!(e.target is Foldout fd)) { return; } Debug.Log($" {fd.name}"); var path = fd.bindingPath; var container = m_ScrollView.Q <IMGUIContainer>(path); RecomputeSize(container); }); } VisualElement defaultCategory = null; for (var i = 0; i < categoryList.Count; i++) { VisualElement x; if (isAnimated) { x = categoryList[i].Q <AnimatedFoldout>(); } else { x = categoryList[i].Q <Foldout>(); } if (x.name != "Default") { continue; } defaultCategory = x; break; } if (defaultCategory.childCount == 0) { defaultCategory.style.display = DisplayStyle.None; } if (isAnimated) { var listItems = boxContainer.Query <AnimatedFoldout>().ToList(); listItems.ForEach(x => foldout.Add((AnimatedFoldout)x)); } else { var listItems = boxContainer.Query <Foldout>().ToList(); listItems.ForEach(x => foldout.Add((Foldout)x)); } foldout.ForEach(x => { Toggle toggleItem; if (isAnimated) { var item = (AnimatedFoldout)x; var contentItem = item.Q(null, AnimatedFoldout.expanderUssClassName); contentItem.ToggleInClassList("categoryFoldoutClosed"); item.Q(null, "unity-toggle__checkmark").AddToClassList("toggleCheckmark"); item.RegisterCallback((ChangeEvent <bool> evt) => { var targetElement = evt.target as VisualElement; if (targetElement == item) { item.value = evt.newValue; } if (targetElement.parent == item || targetElement == item || targetElement.contentContainer == item) { item.value = evt.newValue; // if (evt.newValue) item.contentContainer.style.display = DisplayStyle.Flex; // if (!evt.newValue) // @formatter:off // { // item.schedule.Execute(() => // { // item.contentContainer.style.display = DisplayStyle.None; // }).StartingIn(500); // item.schedule.Execute(() => // { // contentItem.style.display = DisplayStyle.None; // }).StartingIn(600); // @formatter:on // } } else { item.expander.TriggerExpanderResize(true); } }); // @formatter:on } else { var item = (Foldout)x; toggleItem = item.Q <Toggle>(); toggleItem.ToggleInClassList("categoryFoldoutClosed"); item.Q(null, "unity-toggle__checkmark").AddToClassList("toggleCheckmark"); } }); serializedObject.ApplyModifiedProperties(); if (afterDefaultElements == null) { afterDefaultElements = new VisualElement(); } afterDefaultElements.name = "afterDefaultElements"; afterDefaultElements.AddToClassList("afterDefaultElements"); boxContainer.Add(afterDefaultElements); defaultRoot.Add(boxContainer); defaultRoot.RegisterCallback <GeometryChangedEvent>(ExecutePostBuildTask); defaultRoot.schedule.Execute(ExecuteLocalDeferredTask).StartingIn(0); return(defaultRoot); }
private void Init() { var uiAsset = AssetDatabase.LoadAssetAtPath <VisualTreeAsset>("Assets/Scripts/USS attempt/Editor/MyWindow.uxml"); Layout = uiAsset.CloneTree(); var myStyle = AssetDatabase.LoadAssetAtPath <StyleSheet>("Assets/Scripts/USS attempt/Editor/MyStylesheet.uss"); var myBox = new Box { name = "myBox" }; myBox.AddToClassList("My_Fancy_box"); input = new TextField("New Name"); myBox.Add(input); var row = new VisualElement { name = "row" }; row.Add(new Label("Position")); var x = new FloatField("x"); var y = new FloatField("y"); var z = new FloatField("z"); x.RegisterCallback <ChangeEvent <float> >(l => pos.x = x.value); y.RegisterCallback <ChangeEvent <float> >(l => pos.y = y.value); z.RegisterCallback <ChangeEvent <float> >(l => pos.z = z.value); row.Add(x); row.Add(y); row.Add(z); myBox.Add(row); myBox.Q <TextField>().RegisterCallback <ChangeEvent <string> >(l => newName = (l.target as TextField).value); doTheThing = new Button(); doTheThing.AddToClassList("Fancy__button"); doTheThing.text = "Do the thing..."; ///Button Click methods????\\\ //doTheThing.clickable = new Clickable(delegate () { DoTheThing(); }); //doTheThing.clickable = new Clickable(l => DoTheThing()); //doTheThing.clickable = new Clickable(delegate () { DoTheThing(); }, 1, 1); //doTheThing.clickable.clicked += DoTheThing; //////Finally something worked doTheThing.clickable.clickedWithEventInfo += GoDoThatThing(); ///Button Click methods????\\\ myBox.Add(doTheThing); Layout.Add(myBox); rootVisualElement.styleSheets.Add(myStyle); rootVisualElement.Add(Layout); }
/// <summary> /// Once RemoteConfigData and SyncTargets are retrieved, render them in the ScrollView. /// First show unmapped parameters in a list, ending in a TextField+Button to add a new /// unmapped parameter. /// Then show all the discovered SyncTargets in hierarchy view, and highlight which ones /// are not synced with RemoteConfig. /// </summary> public void RenderParameters() { InitHeaders(); var offset = valueScrollView.scrollOffset; valueScrollView.Clear(); topLevelElement = new TemplateContainer(); valueScrollView.Add(topLevelElement); // Render unmapped targets, if any. var unmappedParamsSection = new TemplateContainer(); // Create Unmapped Parameters header section. var unmappedParamsHeader = new Box(); unmappedParamsHeader.AddToClassList(headersClassName); var unmappedLabel = new Label("Unmapped Parameters"); unmappedParamsHeader.Add(unmappedLabel); unmappedParamsSection.Add(unmappedParamsHeader); foreach (var param in unmappedParams.OrderBy(p => p.Key)) { var unmappedTarget = new UnmappedSyncElement(param); unmappedParamsSection.Add(unmappedTarget); } // Add a section with TextField and Button to create a new unmapped Parameter. var newKeyContainer = new TemplateContainer(); newKeyContainer.AddToClassList(columnClassName); newKeyContainer.AddToClassList(rowClassName); var newUnmappedParamText = "New Unmapped Param"; var newKeyField = new TextField { value = newUnmappedParamText }; newKeyContainer.Add(newKeyField); var newUnmappedButton = new Button(() => { if (string.IsNullOrWhiteSpace(newKeyField.value)) { Debug.LogWarning("Cannot create parameter with null/whitespace key."); return; } if (RemoteConfigData.parameters.ContainsKey(newKeyField.value)) { Debug.LogWarning($"A parameter with key {newKeyField.value} already exists."); return; } var newParam = RemoteConfigData.GetOrCreateParameter(newKeyField.value); var newUnmappedTarget = new UnmappedSyncElement(newParam); // Insert the new unmapped key at the end of the unmapped keys list. var index = unmappedParamsSection.IndexOf(newKeyContainer); unmappedParamsSection.Insert(index, newUnmappedTarget); newKeyField.value = newUnmappedParamText; // Apply column sizing to newly created SyncTargetElement. newUnmappedTarget .Query(null, columnClassName) .ForEach(col => col.style.minWidth = col.style.maxWidth = columnSize); }); newUnmappedButton.text = "+"; newUnmappedButton.AddToClassList("flex-0"); newKeyContainer.Add(newUnmappedButton); unmappedParamsSection.Add(newKeyContainer); topLevelElement.Add(unmappedParamsSection); // Create a SyncGroupElement for the top-level SyncTargetContainer. SyncGroupElement and // the various SyncTypeElement classes handle the logic for creating the hierarchy UI. topLevelSyncTarget = new SyncGroupElement(SyncTargets); topLevelElement.Add(topLevelSyncTarget); // Below the ScrollView area, show a set of buttons to sync to/from RC and reset local changes. buttonContainer.Clear(); buttonContainer.Add(uploadButton); buttonContainer.Add(downloadButton); buttonContainer.Add(resetChangesButton); // Reset UI by scrolling to previous scroll position and sizing the newly created columns. valueScrollView.scrollOffset = offset; lastTabWidth = position.width; ResizeColumns(); }
private void OnMaterialsDropped(Material[] droppedMaterials) { remappingRoot.Clear(); DisposeOfSerializedObjects(); //Construct a dictionary between shaders and their materials shadersToMaterials = new Dictionary <Shader, List <SerializedObject> >(); foreach (Material material in droppedMaterials) { if (!shadersToMaterials.TryGetValue(material.shader, out var list)) { list = new List <SerializedObject>(); shadersToMaterials.Add(material.shader, list); } list.Add(new SerializedObject(material)); } foreach (var pair in shadersToMaterials) { Box shaderRoot = new Box(); shaderRoot.AddToClassList("innerPadding"); remappingRoot.Add(shaderRoot); Shader shader = pair.Key; ObjectField shaderField = new ObjectField { objectType = typeof(Shader), value = shader }; shaderField.SetEnabled(false); shaderRoot.Add(shaderField); //RemapType, to a set of keys referring to values that could be remapped. var remapsFrom = new Dictionary <RemapType, HashSet <string> >(); var remapsTo = new Dictionary <RemapType, HashSet <string> >(); List <SerializedObject> materials = pair.Value; foreach (SerializedObject materialSO in materials) { SerializedProperty savedProperties = materialSO.FindProperty("m_SavedProperties"); //Textures ------------------------------------------------------------------------- SerializedProperty texEnvsArray = savedProperties.FindPropertyRelative("m_TexEnvs"); var textureKeys = new HashSet <string>(); remapsTo.Add(RemapType.Texture, textureKeys); var textureKeysFrom = new HashSet <string>(); remapsFrom.Add(RemapType.Texture, textureKeysFrom); for (int i = 0; i < texEnvsArray.arraySize; i++) { SerializedProperty texEnv = texEnvsArray.GetArrayElementAtIndex(i); SerializedProperty key = texEnv.FindPropertyRelative("first"); SerializedProperty texture = texEnv.FindPropertyRelative("second.m_Texture"); textureKeys.Add(key.stringValue); //Only add to the "from", if there is a key with a meaningful value in it. if (texture.objectReferenceValue != null) { textureKeysFrom.Add(key.stringValue); } } //----------------------------------------------------------------------------------- //Floats ---------------------------------------------------------------------------- SerializedProperty floatsArray = savedProperties.FindPropertyRelative("m_Floats"); //floats have no way of telling if they're not set, so they can use the same set var floatKeys = new HashSet <string>(); remapsTo.Add(RemapType.Float, floatKeys); remapsFrom.Add(RemapType.Float, floatKeys); for (int i = 0; i < floatsArray.arraySize; i++) { SerializedProperty @float = floatsArray.GetArrayElementAtIndex(i); SerializedProperty key = @float.FindPropertyRelative("first"); floatKeys.Add(key.stringValue); } //----------------------------------------------------------------------------------- //Vectors and Colors ---------------------------------------------------------------- SerializedProperty colorsArray = savedProperties.FindPropertyRelative("m_Colors"); //colors have no way of telling if they're not set, so they can use the same set var vectorKeys = new HashSet <string>(); remapsTo.Add(RemapType.VectorOrColor, vectorKeys); remapsFrom.Add(RemapType.VectorOrColor, vectorKeys); for (int i = 0; i < colorsArray.arraySize; i++) { SerializedProperty vector = colorsArray.GetArrayElementAtIndex(i); SerializedProperty key = vector.FindPropertyRelative("first"); vectorKeys.Add(key.stringValue); } //----------------------------------------------------------------------------------- } //CONTROLS var remapTypeField = new EnumField("Remap Type", RemapType.None) { name = remapTypeName }; remapTypeField.RegisterCallback <ChangeEvent <Enum> >(SelectedRemapType); shaderRoot.Add(remapTypeField); PopupField <string> popupFromField = new PopupField <string>(fromKeyLabel, noneList, 0) { name = fromKeyPopupName }; popupFromField.SetEnabled(false); shaderRoot.Add(popupFromField); PopupField <string> popupToField = new PopupField <string>(toKeyLabel, noneList, 0) { name = toKeyPopupName }; popupToField.SetEnabled(false); shaderRoot.Add(popupToField); Button remapButton = new Button { text = "Remap", name = remapButtonName }; remapButton.SetEnabled(false); shaderRoot.Add(remapButton); void SelectedRemapType(ChangeEvent <Enum> remapEvt) { if (remapEvt == null) { throw new ArgumentNullException(nameof(remapEvt)); } RemapType value = (RemapType)remapEvt.newValue; if (value == RemapType.None) { GetButton().SetEnabled(false); GetRemapTo().SetEnabled(false); GetRemapFrom().SetEnabled(false); return; } PopupField <string> child = GetRemapFrom(); int index = shaderRoot.IndexOf(child); child.RemoveFromHierarchy(); List <string> choicesFrom = remapsFrom[value].ToList(); choicesFrom.Insert(0, none); popupFromField = new PopupField <string>(fromKeyLabel, choicesFrom, none) { name = fromKeyPopupName }; shaderRoot.Insert(index, popupFromField); popupFromField.RegisterCallback <ChangeEvent <string> >(SelectedRemapFrom); } void SelectedRemapFrom(ChangeEvent <string> popupFromEvt) { string remapFromKey = popupFromEvt.newValue; if (remapFromKey == none) { GetButton().SetEnabled(false); GetRemapTo().SetEnabled(false); return; } PopupField <string> child = GetRemapTo(); int index = shaderRoot.IndexOf(child); child.RemoveFromHierarchy(); List <string> choicesTo = remapsTo[(RemapType)remapTypeField.value].ToList(); choicesTo.Insert(0, none); popupToField = new PopupField <string>(toKeyLabel, choicesTo, none) { name = toKeyPopupName }; shaderRoot.Insert(index, popupToField); popupToField.RegisterCallback <ChangeEvent <string>, (PopupField <string>, string)>(SelectedRemapTo, (popupToField, remapFromKey)); } void SelectedRemapTo(ChangeEvent <string> popupToEvt, (PopupField <string> popupToField, string remapFromKey) args) { string remapToKey = popupToEvt.newValue; VisualElement background = args.popupToField.Q(null, PopupField <string> .inputUssClassName); if (remapToKey == none) { GetButton().SetEnabled(false); background.style.unityBackgroundImageTintColor = default; return; } if (remapToKey == args.remapFromKey) { background.style.unityBackgroundImageTintColor = new Color(1f, 0.46f, 0.51f); return; } background.style.unityBackgroundImageTintColor = default; var button = GetButton(); button.SetEnabled(true); button.RemoveManipulator(button.clickable); button.clickable = new Clickable(() => PerformRemap(shader, (RemapType)GetRemapType().value, args.remapFromKey, remapToKey)); button.AddManipulator(button.clickable); } Button GetButton() => shaderRoot.Q <Button>(remapButtonName); EnumField GetRemapType() => shaderRoot.Q <EnumField>(remapTypeName); PopupField <string> GetRemapFrom() => shaderRoot.Q <PopupField <string> >(fromKeyPopupName); PopupField <string> GetRemapTo() => shaderRoot.Q <PopupField <string> >(toKeyPopupName); } }