using UnityEditor.Overlays; using UnityEditor.Toolbars; using UnityEngine; using UnityEngine.UIElements; using UnityEditor.EditorTools; using System; using System.Reflection; using System.Collections.Generic; using System.Runtime.CompilerServices; using Slider = UnityEngine.UIElements.Slider; namespace UnityEditor.TerrainTools.UI { [Overlay(typeof(SceneView), "Brush Attributes", defaultDockPosition = DockPosition.Top, defaultDockZone = DockZone.TopToolbar)] [Icon("Packages/com.unity.terrain-tools/Editor/Icons/TerrainOverlays/BrushSettingIcons/BrushAttributes.png")] internal class BrushAttributesOverlay : ToolbarOverlay, ITransientOverlay { public bool visible { get { var currTool = BrushesOverlay.ActiveTerrainTool as TerrainPaintToolWithOverlaysBase; if (currTool == null) return false; bool isTerrainToolPaintTool = currTool is ITerrainToolPaintTool; return currTool.HasBrushAttributes && BrushesOverlay.IsSelectedObjectTerrain() && isTerrainToolPaintTool; } } internal static BrushAttributesOverlay instance; internal Dictionary m_Attributes = new Dictionary(); public static Layout activeToolbarLayout => instance.activeLayout; public static string s_UssPath => "Styles/BrushPopup"; public static int popUpWidth = 200; public static int popUpHeight = 50; BrushAttributesOverlay() : base( BrushOpacity.id, BrushSize.id, BrushRotation.id, BrushSpacing.id, BrushScattering.id) { if (instance != null) { instance.m_Attributes.Clear(); instance = null; } instance = this; } internal static void RegisterAttribute(CondensedSlider attr) { if (instance != null) instance.m_Attributes.TryAdd(attr.contentLabel.GetHashCode(), attr); } internal static void RebuildContent() { if (instance != null) { foreach (var attr in instance.m_Attributes) { attr.Value.RebuildContent(); } } } public static void AddBorder(EditorWindow window) { Color color = EditorGUIUtility.isProSkin ? new Color(0.44f, 0.44f, 0.44f, 1f) : new Color(0.51f, 0.51f, 0.51f); window.rootVisualElement.style.borderLeftWidth = (StyleFloat) 1f; window.rootVisualElement.style.borderTopWidth = (StyleFloat) 1f; window.rootVisualElement.style.borderRightWidth = (StyleFloat) 1f; window.rootVisualElement.style.borderBottomWidth = (StyleFloat) 1f; window.rootVisualElement.style.borderLeftColor = (StyleColor) color; window.rootVisualElement.style.borderTopColor = (StyleColor) color; window.rootVisualElement.style.borderRightColor = (StyleColor) color; window.rootVisualElement.style.borderBottomColor = (StyleColor) color; } public static TerrainPaintToolWithOverlaysBase GetActiveOverlaysTool() { var tool = BrushesOverlay.ActiveTerrainTool as TerrainPaintToolWithOverlaysBase; if (!tool) return null; if (!Selection.activeGameObject) return null; if (!tool.Terrain) { if (Selection.activeGameObject == null || Selection.activeGameObject.GetComponent() == null) return null; // check when loading and unloading packages tool.Terrain = Selection.activeGameObject.GetComponent(); } if (!tool.Terrain) { Debug.LogError("Tool does NOT have associated terrain"); return null; } return tool; } public static IBrushUIGroup GetCommonUI() { // get commonUI var tool = GetActiveOverlaysTool(); if (!tool) return null; Type type = tool.GetType(); MethodInfo func = type.GetMethod("get_m_commonUI"); if (func == null) return null; // avoid null errors IBrushUIGroup commonUI = (IBrushUIGroup) func.Invoke(tool, null); return commonUI; } } // brush attributes (opacity, size, rotation, spacing, scattering) ------- // BRUSH OPACITY [EditorToolbarElement(id, typeof(SceneView))] internal class BrushOpacity : CondensedSliderDropdown { internal const string id = "Brushes/OpacityPackage"; private const string label = "Opacity"; private static float minValue { get { var commonUI = BrushAttributesOverlay.GetCommonUI(); if (commonUI == null) return 0f; return commonUI.brushStrengthMin; } } private static float maxValue { get { var commonUI = BrushAttributesOverlay.GetCommonUI(); if (commonUI == null) return 1f; return commonUI.brushStrengthMax; } } static Texture2D s_OpacityIcon; static Texture2D Texture { get { if(s_OpacityIcon == null) s_OpacityIcon = (Texture2D)AssetDatabase.LoadAssetAtPath("Packages/com.unity.terrain-tools/Editor/Icons/TerrainOverlays/BrushSettingIcons/Opacity.png", typeof(Texture2D)); return s_OpacityIcon; } } public void UpdateOverlayDirection(Layout l) { DropdownUpdateDirection(BrushAttributesOverlay.activeToolbarLayout == Layout.VerticalToolbar ? SliderDirection.Vertical : SliderDirection.Horizontal, null, minValue, maxValue); labelFormatting = (f, s, d) => { if (direction == SliderDirection.Vertical) return $"{f:F2}"; return $"{s} {f:F2}"; }; clicked += () => OpenPopup(CreatePopUp(), BrushAttributesOverlay.popUpWidth, BrushAttributesOverlay.popUpHeight); UpdateValues(); } public void UpdateOverlayDirection(bool collapsedChanged) { DropdownUpdateDirection(BrushAttributesOverlay.activeToolbarLayout == Layout.VerticalToolbar ? SliderDirection.Vertical : SliderDirection.Horizontal, null, minValue, maxValue); labelFormatting = (f, s, d) => { if (direction == SliderDirection.Vertical) return $"{f:F2}"; return $"{s} {f:F2}"; }; clicked += () => OpenPopup(CreatePopUp(), BrushAttributesOverlay.popUpWidth, BrushAttributesOverlay.popUpHeight); UpdateValues(); } // On first install of the package, need to rebuild the attribute UI after // assets have been loaded. So this function implements a reconstruction path, essentially all the same // steps as if you step through all the constructors internal override void RebuildContent() { RebuildContent(Texture, minValue, maxValue); ConstructDropdown(null, BrushAttributesOverlay.instance.layout == Layout.VerticalToolbar ? SliderDirection.Vertical : SliderDirection.Horizontal); UpdateOverlayDirection(true); SetContentWidth(); UpdateValues(); } public BrushOpacity() : base(label, Texture, minValue, maxValue, null, BrushAttributesOverlay.instance.layout == Layout.VerticalToolbar ? SliderDirection.Vertical : SliderDirection.Horizontal) { BrushAttributesOverlay.RegisterAttribute(this); labelFormatting = (f, s, d) => { if (direction == SliderDirection.Vertical) return $"{f:F2}"; return $"{s} {f:F2}"; }; void UpdateMin() { var commonUI = BrushAttributesOverlay.GetCommonUI(); if (commonUI == null) return; SetLowValueWithoutNotify(commonUI.brushStrengthMin); } void UpdateMax() { var commonUI = BrushAttributesOverlay.GetCommonUI(); if (commonUI == null) return; SetHighValueWithoutNotify(commonUI.brushStrengthMax); } UpdateOverlayDirection(true); RegisterCallback(e => { ToolManager.activeToolChanged += UpdateValues; ToolManager.activeContextChanged += UpdateValues; BrushAttributesOverlay.instance.layoutChanged += UpdateOverlayDirection; BrushAttributesOverlay.instance.collapsedChanged += UpdateOverlayDirection; BrushStrengthVariator.BrushStrengthChanged += UpdateValues; BrushStrengthVariator.BrushStrengthMinChanged += UpdateMin; BrushStrengthVariator.BrushStrengthMaxChanged += UpdateMax; }); this.RegisterValueChangedCallback(e => { var commonUI = BrushAttributesOverlay.GetCommonUI(); if (commonUI == null) return; commonUI.brushStrength = e.newValue; }); RegisterCallback(e => { ToolManager.activeToolChanged -= UpdateValues; ToolManager.activeContextChanged -= UpdateValues; BrushAttributesOverlay.instance.layoutChanged -= UpdateOverlayDirection; BrushAttributesOverlay.instance.collapsedChanged -= UpdateOverlayDirection; BrushStrengthVariator.BrushStrengthChanged -= UpdateValues; BrushStrengthVariator.BrushStrengthMinChanged -= UpdateMin; BrushStrengthVariator.BrushStrengthMaxChanged -= UpdateMax; }); SetContentWidth(); clicked += () => OpenPopup(CreatePopUp(), BrushAttributesOverlay.popUpWidth, BrushAttributesOverlay.popUpHeight); UpdateValues(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void SetContentWidth() { if (direction == SliderDirection.Horizontal) contentWidth = 100; } private void UpdateValues() { var commonUI = BrushAttributesOverlay.GetCommonUI(); if (commonUI == null) return; style.display = commonUI.hasBrushStrength ? DisplayStyle.Flex : DisplayStyle.None; if (!commonUI.hasBrushStrength) return; value = commonUI.brushStrengthVal; } private VisualElement CreatePopUp() { var opacityContainer = new VisualElement(); StyleSheet styleSheet = (StyleSheet)AssetDatabase.LoadAssetAtPath("Packages/com.unity.terrain-tools/Editor/Style/BrushPopup.uss", typeof(StyleSheet)); if (styleSheet) opacityContainer.styleSheets.Add(styleSheet); // add min/max slider var opacityMinMaxContainer = new VisualElement(); opacityMinMaxContainer.style.flexDirection = FlexDirection.Row; opacityContainer.Add(opacityMinMaxContainer); var opacityMinText = new TextElement(); opacityMinText.text = "Min"; opacityMinMaxContainer.Add(opacityMinText); var opacityMinField = new FloatField(); opacityMinMaxContainer.Add(opacityMinField); var opacityMaxText = new TextElement(); opacityMaxText.text = "Max"; opacityMinMaxContainer.Add(opacityMaxText); var opacityMaxField = new FloatField(); opacityMinMaxContainer.Add(opacityMaxField); // add jitter slider var opacityJitterContainer = new VisualElement(); opacityJitterContainer.style.flexDirection = FlexDirection.Row; opacityContainer.Add(opacityJitterContainer); var jitter = new Slider("Jitter", 0f, 1f); jitter.style.flexGrow = 1; opacityJitterContainer.Add(jitter); var opacityJitterTextField = new FloatField(); opacityJitterContainer.Add(opacityJitterTextField); opacityMinField.RegisterValueChangedCallback(e => { var commonUI = BrushAttributesOverlay.GetCommonUI(); if (commonUI == null) return; commonUI.brushStrengthMin = e.newValue; if (commonUI.brushStrengthMin > commonUI.brushStrengthMax) { commonUI.brushStrengthMax = e.newValue; opacityMaxField.value = e.newValue; SetHighValueWithoutNotify(e.newValue); } SetLowValueWithoutNotify(e.newValue); }); opacityMaxField.RegisterValueChangedCallback(e => { var commonUI = BrushAttributesOverlay.GetCommonUI(); if (commonUI == null) return; commonUI.brushStrengthMax = e.newValue; if (commonUI.brushStrengthMax < commonUI.brushStrengthMin) { commonUI.brushStrengthMin = e.newValue; opacityMinField.value = e.newValue; SetLowValueWithoutNotify(e.newValue); } SetHighValueWithoutNotify(e.newValue); }); jitter.RegisterValueChangedCallback(e => { var commonUI = BrushAttributesOverlay.GetCommonUI(); if (commonUI == null) return; commonUI.brushStrengthJitter = e.newValue; opacityJitterTextField.SetValueWithoutNotify(e.newValue); }); opacityJitterTextField.RegisterValueChangedCallback(e => { var commonUI = BrushAttributesOverlay.GetCommonUI(); if (commonUI == null) return; commonUI.brushStrengthJitter = e.newValue; jitter.SetValueWithoutNotify(e.newValue); }); var commonUI = BrushAttributesOverlay.GetCommonUI(); if (commonUI == null) Debug.LogWarning("cannot display STRENGTH in BrushAttributesOverlay.CS in PACKAGE"); // NOTE: when I set values, I am setting them to commonUI.brushStrengthVal rather than commonUI.brushStrength // this is because commonUI.brushStrengthVal gives the raw brush strength, where as commonUI.brushStrength uses // a getter which calculates the jitter, which I don't want to display in the UI // this is also true for size (commonUI.brushSizeVal) and rotation (commonUI.brushRotationVal) opacityMinField.value = commonUI.brushStrengthMin; opacityMaxField.value = commonUI.brushStrengthMax; jitter.value = commonUI.brushStrengthJitter; opacityJitterTextField.value = commonUI.brushStrengthJitter; return opacityContainer; } private void OpenPopup(VisualElement popup, float width, float height) { var window = ScriptableObject.CreateInstance(); BrushAttributesOverlay.AddBorder(window); window.rootVisualElement.Add(popup); window.ShowAsDropDown(GUIUtility.GUIToScreenRect(worldBound), new Vector2(width, height)); } } // BRUSH SIZE [EditorToolbarElement(id, typeof(SceneView))] internal class BrushSize : CondensedSliderDropdown { internal const string id = "Brushes/SizePackage"; private const string label = "Size"; private static float minValue { get { var commonUI = BrushAttributesOverlay.GetCommonUI(); if (commonUI == null) return 0f; return commonUI.brushSizeMin; } } private static float maxValue { get { var commonUI = BrushAttributesOverlay.GetCommonUI(); if (commonUI == null) return 500f; return commonUI.brushSizeMax; } } static Texture2D s_SizeIcon; static Texture2D Texture { get { if(s_SizeIcon == null) s_SizeIcon = (Texture2D)AssetDatabase.LoadAssetAtPath("Packages/com.unity.terrain-tools/Editor/Icons/TerrainOverlays/BrushSettingIcons/Size.png", typeof(Texture2D)); return s_SizeIcon; } } public void UpdateOverlayDirection(Layout l) { DropdownUpdateDirection(BrushAttributesOverlay.activeToolbarLayout == Layout.VerticalToolbar ? SliderDirection.Vertical : SliderDirection.Horizontal, null, minValue, maxValue); labelFormatting = (f, s, d) => { if (direction == SliderDirection.Vertical) return $"{f:F2}"; return $"{s} {f:F2}"; }; clicked += () => OpenPopup(CreatePopUp(), BrushAttributesOverlay.popUpWidth, BrushAttributesOverlay.popUpHeight); UpdateValues(); } public void UpdateOverlayDirection(bool collapsedChanged) { DropdownUpdateDirection(BrushAttributesOverlay.activeToolbarLayout == Layout.VerticalToolbar ? SliderDirection.Vertical : SliderDirection.Horizontal, null, minValue, maxValue); labelFormatting = (f, s, d) => { if (direction == SliderDirection.Vertical) return $"{f:F2}"; return $"{s} {f:F2}"; }; clicked += () => OpenPopup(CreatePopUp(), BrushAttributesOverlay.popUpWidth, BrushAttributesOverlay.popUpHeight); UpdateValues(); } // On first install of the package, need to rebuild the attribute UI after // assets have been loaded. So this function implements a reconstruction path, essentially all the same // steps as if you step through all the constructors internal override void RebuildContent() { RebuildContent(Texture, minValue, maxValue); ConstructDropdown(null, BrushAttributesOverlay.instance.layout == Layout.VerticalToolbar ? SliderDirection.Vertical : SliderDirection.Horizontal); UpdateOverlayDirection(true); SetContentWidth(); UpdateValues(); } public BrushSize() : base(label, Texture, minValue, maxValue, null, BrushAttributesOverlay.instance.layout == Layout.VerticalToolbar ? SliderDirection.Vertical : SliderDirection.Horizontal) { BrushAttributesOverlay.RegisterAttribute(this); labelFormatting = (f, s, d) => { if (direction == SliderDirection.Vertical) return $"{f:F2}"; return $"{s} {f:F2}"; }; void UpdateMin() { var commonUI = BrushAttributesOverlay.GetCommonUI(); if (commonUI == null) return; SetLowValueWithoutNotify(commonUI.brushSizeMin); } void UpdateMax() { var commonUI = BrushAttributesOverlay.GetCommonUI(); if (commonUI == null) return; SetHighValueWithoutNotify(commonUI.brushSizeMax); } UpdateOverlayDirection(true); RegisterCallback(e => { ToolManager.activeToolChanged += UpdateValues; ToolManager.activeContextChanged += UpdateValues; BrushAttributesOverlay.instance.layoutChanged += UpdateOverlayDirection; BrushAttributesOverlay.instance.collapsedChanged += UpdateOverlayDirection; BrushSizeVariator.BrushSizeChanged += UpdateValues; BrushSizeVariator.BrushSizeMinChanged += UpdateMin; BrushSizeVariator.BrushSizeMaxChanged += UpdateMax; }); this.RegisterValueChangedCallback(e => { var commonUI = BrushAttributesOverlay.GetCommonUI(); if (commonUI == null) return; commonUI.brushSize = e.newValue; }); RegisterCallback(e => { ToolManager.activeToolChanged -= UpdateValues; ToolManager.activeContextChanged -= UpdateValues; BrushAttributesOverlay.instance.layoutChanged -= UpdateOverlayDirection; BrushAttributesOverlay.instance.collapsedChanged -= UpdateOverlayDirection; BrushSizeVariator.BrushSizeChanged -= UpdateValues; BrushSizeVariator.BrushSizeMinChanged -= UpdateMin; BrushSizeVariator.BrushSizeMaxChanged -= UpdateMax; }); SetContentWidth(); clicked += () => OpenPopup(CreatePopUp(), BrushAttributesOverlay.popUpWidth, BrushAttributesOverlay.popUpHeight); UpdateValues(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] void SetContentWidth() { if (direction == SliderDirection.Horizontal) contentWidth = 100; } private void UpdateValues() { var commonUI = BrushAttributesOverlay.GetCommonUI(); if (commonUI == null) return; style.display = commonUI.hasBrushSize ? DisplayStyle.Flex : DisplayStyle.None; if (!commonUI.hasBrushSize) return; value = commonUI.brushSizeVal; } private VisualElement CreatePopUp() { var sizeContainer = new VisualElement(); StyleSheet styleSheet = (StyleSheet)AssetDatabase.LoadAssetAtPath("Packages/com.unity.terrain-tools/Editor/Style/BrushPopup.uss", typeof(StyleSheet)); if (styleSheet) sizeContainer.styleSheets.Add(styleSheet); // add min/max slider var sizeMinMaxContainer = new VisualElement(); sizeMinMaxContainer.style.flexDirection = FlexDirection.Row; sizeContainer.Add(sizeMinMaxContainer); var sizeMinText = new TextElement(); sizeMinText.text = "Min"; sizeMinMaxContainer.Add(sizeMinText); var sizeMinField = new FloatField(); sizeMinMaxContainer.Add(sizeMinField); var sizeMaxText = new TextElement(); sizeMaxText.text = "Max"; sizeMinMaxContainer.Add(sizeMaxText); var sizeMaxField = new FloatField(); sizeMinMaxContainer.Add(sizeMaxField); // add jitter slider var sizeJitterContainer = new VisualElement(); sizeJitterContainer.style.flexDirection = FlexDirection.Row; sizeContainer.Add(sizeJitterContainer); var jitter = new Slider("Jitter", 0f, 1f); jitter.style.flexGrow = 1; sizeJitterContainer.Add(jitter); var sizeJitterTextField = new FloatField(); sizeJitterContainer.Add(sizeJitterTextField); sizeMinField.RegisterValueChangedCallback(e => { var commonUI = BrushAttributesOverlay.GetCommonUI(); if (commonUI == null) return; commonUI.brushSizeMin = e.newValue; if (commonUI.brushSizeMin > commonUI.brushSizeMax) { commonUI.brushSizeMax = e.newValue; sizeMaxField.value = e.newValue; SetHighValueWithoutNotify(e.newValue); } SetLowValueWithoutNotify(e.newValue); }); sizeMaxField.RegisterValueChangedCallback(e => { var commonUI = BrushAttributesOverlay.GetCommonUI(); if (commonUI == null) return; commonUI.brushSizeMax = e.newValue; if (commonUI.brushSizeMax < commonUI.brushSizeMin) { commonUI.brushSizeMax = e.newValue; sizeMinField.value = e.newValue; SetLowValueWithoutNotify(e.newValue); } SetHighValueWithoutNotify(e.newValue); }); jitter.RegisterValueChangedCallback(e => { var commonUI = BrushAttributesOverlay.GetCommonUI(); if (commonUI == null) return; commonUI.brushSizeJitter = e.newValue; sizeJitterTextField.SetValueWithoutNotify(e.newValue); }); sizeJitterTextField.RegisterValueChangedCallback(e => { var commonUI = BrushAttributesOverlay.GetCommonUI(); if (commonUI == null) return; commonUI.brushSizeJitter = e.newValue; jitter.SetValueWithoutNotify(e.newValue); }); var commonUI = BrushAttributesOverlay.GetCommonUI(); if (commonUI == null) Debug.LogWarning("cannot display SIZE in BrushAttributesOverlay.CS in PACKAGE"); sizeMinField.value = commonUI.brushSizeMin; sizeMaxField.value = commonUI.brushSizeMax; jitter.value = commonUI.brushSizeJitter; sizeJitterTextField.value = commonUI.brushSizeJitter; return sizeContainer; } private void OpenPopup(VisualElement popup, float width, float height) { var window = ScriptableObject.CreateInstance(); BrushAttributesOverlay.AddBorder(window); window.rootVisualElement.Add(popup); window.ShowAsDropDown(GUIUtility.GUIToScreenRect(worldBound), new Vector2(width, height)); } } // BRUSH ROTATION [EditorToolbarElement(id, typeof(SceneView))] internal class BrushRotation : CondensedSliderDropdown { internal const string id = "Brushes/RotationPackage"; private const string label = "Rotation"; private const float minValue = -180; private const float maxValue = 180; static Texture2D s_RotationIcon; static Texture2D Texture { get { if(s_RotationIcon == null) s_RotationIcon = (Texture2D)AssetDatabase.LoadAssetAtPath("Packages/com.unity.terrain-tools/Editor/Icons/TerrainOverlays/BrushSettingIcons/Rotation.png", typeof(Texture2D)); return s_RotationIcon; } } public void UpdateOverlayDirection(Layout l) { DropdownUpdateDirection(BrushAttributesOverlay.activeToolbarLayout == Layout.VerticalToolbar ? SliderDirection.Vertical : SliderDirection.Horizontal, null, minValue, maxValue); labelFormatting = (f, s, d) => { if (direction == SliderDirection.Vertical) return $"{f:F2}"; return $"{s} {f:F2}"; }; clicked += () => OpenPopup(CreatePopUp(), BrushAttributesOverlay.popUpWidth, BrushAttributesOverlay.popUpHeight); UpdateValues(); } public void UpdateOverlayDirection(bool collapsedChanged) { DropdownUpdateDirection(BrushAttributesOverlay.activeToolbarLayout == Layout.VerticalToolbar ? SliderDirection.Vertical : SliderDirection.Horizontal, null, minValue, maxValue); labelFormatting = (f, s, d) => { if (direction == SliderDirection.Vertical) return $"{f:F2}"; return $"{s} {f:F2}"; }; clicked += () => OpenPopup(CreatePopUp(), BrushAttributesOverlay.popUpWidth, BrushAttributesOverlay.popUpHeight); UpdateValues(); } // On first install of the package, need to rebuild the attribute UI after // assets have been loaded. So this function implements a reconstruction path, essentially all the same // steps as if you step through all the constructors internal override void RebuildContent() { RebuildContent(Texture, minValue, maxValue); ConstructDropdown(null, BrushAttributesOverlay.instance.layout == Layout.VerticalToolbar ? SliderDirection.Vertical : SliderDirection.Horizontal); UpdateOverlayDirection(true); SetContentWidth(); UpdateValues(); } public BrushRotation() : base(label, Texture, minValue, maxValue, null, BrushAttributesOverlay.instance.layout == Layout.VerticalToolbar ? SliderDirection.Vertical : SliderDirection.Horizontal) { BrushAttributesOverlay.RegisterAttribute(this); labelFormatting = (f, s, d) => { if (direction == SliderDirection.Vertical) return $"{f:F2}"; return $"{s} {f:F2}"; }; UpdateOverlayDirection(true); RegisterCallback(e => { ToolManager.activeToolChanged += UpdateValues; ToolManager.activeContextChanged += UpdateValues; BrushAttributesOverlay.instance.layoutChanged += UpdateOverlayDirection; BrushAttributesOverlay.instance.collapsedChanged += UpdateOverlayDirection; BrushRotationVariator.BrushRotationChanged += UpdateValues; }); this.RegisterValueChangedCallback(e => { var commonUI = BrushAttributesOverlay.GetCommonUI(); if (commonUI == null) return; commonUI.brushRotation = e.newValue; }); RegisterCallback(e => { ToolManager.activeToolChanged -= UpdateValues; ToolManager.activeContextChanged -= UpdateValues; BrushAttributesOverlay.instance.layoutChanged -= UpdateOverlayDirection; BrushAttributesOverlay.instance.collapsedChanged -= UpdateOverlayDirection; BrushRotationVariator.BrushRotationChanged -= UpdateValues; }); SetContentWidth(); clicked += () => OpenPopup(CreatePopUp(), BrushAttributesOverlay.popUpWidth, BrushAttributesOverlay.popUpHeight); UpdateValues(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void SetContentWidth() { if (direction == SliderDirection.Horizontal) contentWidth = 120; } private void UpdateValues() { var commonUI = BrushAttributesOverlay.GetCommonUI(); if (commonUI == null) return; style.display = commonUI.hasBrushRotation ? DisplayStyle.Flex : DisplayStyle.None; if (!commonUI.hasBrushRotation) return; value = commonUI.brushRotationVal; } private VisualElement CreatePopUp() { var rotationContainer = new VisualElement(); StyleSheet styleSheet = (StyleSheet)AssetDatabase.LoadAssetAtPath("Packages/com.unity.terrain-tools/Editor/Style/BrushPopup.uss", typeof(StyleSheet)); if (styleSheet) rotationContainer.styleSheets.Add(styleSheet); // add jitter slider var rotationJitterContainer = new VisualElement(); rotationJitterContainer.style.flexDirection = FlexDirection.Row; rotationContainer.Add(rotationJitterContainer); var jitter = new Slider("Jitter", 0f, 1f); jitter.style.flexGrow = 1; rotationJitterContainer.Add(jitter); var rotationJitterTextField = new FloatField(); rotationJitterContainer.Add(rotationJitterTextField); jitter.RegisterValueChangedCallback(e => { var commonUI = BrushAttributesOverlay.GetCommonUI(); if (commonUI == null) return; commonUI.brushRotationJitter = e.newValue; rotationJitterTextField.SetValueWithoutNotify(e.newValue); }); rotationJitterTextField.RegisterValueChangedCallback(e => { var commonUI = BrushAttributesOverlay.GetCommonUI(); if (commonUI == null) return; commonUI.brushRotationJitter = e.newValue; jitter.SetValueWithoutNotify(e.newValue); }); var commonUI = BrushAttributesOverlay.GetCommonUI(); if (commonUI == null) Debug.LogWarning("cannot display ROTATION in BrushAttributesOverlay.CS in PACKAGE"); jitter.value = commonUI.brushRotationJitter; rotationJitterTextField.value = commonUI.brushRotationJitter; return rotationContainer; } private void OpenPopup(VisualElement popup, float width, float height) { var window = ScriptableObject.CreateInstance(); BrushAttributesOverlay.AddBorder(window); window.rootVisualElement.Add(popup); window.ShowAsDropDown(GUIUtility.GUIToScreenRect(worldBound), new Vector2(width, height)); } } // BRUSH SPACING [EditorToolbarElement(id, typeof(SceneView))] internal class BrushSpacing : CondensedSlider { internal const string id = "Brushes/SpacingPackage"; private const string label = "Spacing"; private const float minValue = 0; private const float maxValue = 100; private const float offset = 100; static Texture2D s_SpacingIcon; static Texture2D Texture { get { if(s_SpacingIcon == null) s_SpacingIcon = (Texture2D)AssetDatabase.LoadAssetAtPath("Packages/com.unity.terrain-tools/Editor/Icons/TerrainOverlays/BrushSettingIcons/Spacing.png", typeof(Texture2D)); return s_SpacingIcon; } } public void UpdateOverlayDirection(Layout l) { UpdateDirection(BrushAttributesOverlay.activeToolbarLayout == Layout.VerticalToolbar ? SliderDirection.Vertical : SliderDirection.Horizontal, minValue, maxValue); labelFormatting = (f, s, d) => { if (direction == SliderDirection.Vertical) return $"{f:F2}"; return $"{s} {f:F2}"; }; UpdateValues(); } public void UpdateOverlayDirection(bool collapsedChanged) { UpdateDirection(BrushAttributesOverlay.activeToolbarLayout == Layout.VerticalToolbar ? SliderDirection.Vertical : SliderDirection.Horizontal, minValue, maxValue); labelFormatting = (f, s, d) => { if (direction == SliderDirection.Vertical) return $"{f:F2}"; return $"{s} {f:F2}"; }; UpdateValues(); } // On first install of the package, need to rebuild the attribute UI after // assets have been loaded. So this function implements a reconstruction path, essentially all the same // steps as if you step through all the constructors internal override void RebuildContent() { RebuildContent(Texture, minValue, maxValue); UpdateOverlayDirection(true); SetContentWidth(); UpdateValues(); } public BrushSpacing() : base(label, Texture, minValue, maxValue, BrushAttributesOverlay.instance.layout == Layout.VerticalToolbar ? SliderDirection.Vertical : SliderDirection.Horizontal) { BrushAttributesOverlay.RegisterAttribute(this); labelFormatting = (f, s, d) => { if (direction == SliderDirection.Vertical) return $"{f:F2}"; return $"{s} {f:F2}"; }; UpdateOverlayDirection(true); RegisterCallback(e => { ToolManager.activeToolChanged += UpdateValues; ToolManager.activeContextChanged += UpdateValues; BrushAttributesOverlay.instance.layoutChanged += UpdateOverlayDirection; BrushAttributesOverlay.instance.collapsedChanged += UpdateOverlayDirection; BrushSpacingVariator.BrushSpacingChanged += UpdateValues; }); this.RegisterValueChangedCallback(e => { var commonUI = BrushAttributesOverlay.GetCommonUI(); if (commonUI == null) return; commonUI.brushSpacing = e.newValue / offset; }); RegisterCallback(e => { ToolManager.activeToolChanged -= UpdateValues; ToolManager.activeContextChanged -= UpdateValues; BrushAttributesOverlay.instance.layoutChanged -= UpdateOverlayDirection; BrushAttributesOverlay.instance.collapsedChanged -= UpdateOverlayDirection; BrushSpacingVariator.BrushSpacingChanged -= UpdateValues; }); SetContentWidth(); UpdateValues(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void SetContentWidth() { if (direction == SliderDirection.Horizontal) contentWidth = 118; } private void UpdateValues() { var commonUI = BrushAttributesOverlay.GetCommonUI(); if (commonUI == null) return; style.display = commonUI.hasBrushSpacing ? DisplayStyle.Flex : DisplayStyle.None; if (!commonUI.hasBrushSpacing) return; value = commonUI.brushSpacing * offset; } } // BRUSH SCATTERING [EditorToolbarElement(id, typeof(SceneView))] internal class BrushScattering : CondensedSlider { internal const string id = "Brushes/ScatteringPackage"; private const string label = "Scattering"; private const float minValue = 0; private const float maxValue = 100; private const float offset = 100; static Texture2D s_ScatteringIcon; static Texture2D Texture { get { if (s_ScatteringIcon == null) s_ScatteringIcon = (Texture2D)AssetDatabase.LoadAssetAtPath("Packages/com.unity.terrain-tools/Editor/Icons/TerrainOverlays/BrushSettingIcons/Scattering.png", typeof(Texture2D)); return s_ScatteringIcon; } } public void UpdateOverlayDirection(Layout l) { UpdateDirection(BrushAttributesOverlay.activeToolbarLayout == Layout.VerticalToolbar ? SliderDirection.Vertical : SliderDirection.Horizontal, minValue, maxValue); labelFormatting = (f, s, d) => { if (direction == SliderDirection.Vertical) return $"{f:F2}"; return $"{s} {f:F2}"; }; UpdateValues(); } public void UpdateOverlayDirection(bool collapsedChanged) { UpdateDirection(BrushAttributesOverlay.activeToolbarLayout == Layout.VerticalToolbar ? SliderDirection.Vertical : SliderDirection.Horizontal, minValue, maxValue); labelFormatting = (f, s, d) => { if (direction == SliderDirection.Vertical) return $"{f:F2}"; return $"{s} {f:F2}"; }; UpdateValues(); } // On first install of the package, need to rebuild the attribute UI after // assets have been loaded. So this function implements a reconstruction path, essentially all the same // steps as if you step through all the constructors internal override void RebuildContent() { RebuildContent(Texture, minValue, maxValue); UpdateOverlayDirection(true); SetContentWidth(); UpdateValues(); } public BrushScattering() : base(label, Texture, minValue, maxValue, BrushAttributesOverlay.instance.layout == Layout.VerticalToolbar ? SliderDirection.Vertical : SliderDirection.Horizontal) { BrushAttributesOverlay.RegisterAttribute(this); labelFormatting = (f, s, d) => { if (direction == SliderDirection.Vertical) return $"{f:F2}"; return $"{s} {f:F2}"; }; UpdateOverlayDirection(true); RegisterCallback(e => { ToolManager.activeToolChanged += UpdateValues; ToolManager.activeContextChanged += UpdateValues; BrushAttributesOverlay.instance.layoutChanged += UpdateOverlayDirection; BrushAttributesOverlay.instance.collapsedChanged += UpdateOverlayDirection; BrushScatterVariator.BrushScatterChanged += UpdateValues; }); this.RegisterValueChangedCallback(e => { var commonUI = BrushAttributesOverlay.GetCommonUI(); if (commonUI == null) return; commonUI.brushScatter = e.newValue / offset; }); RegisterCallback(e => { ToolManager.activeToolChanged -= UpdateValues; ToolManager.activeContextChanged -= UpdateValues; BrushAttributesOverlay.instance.layoutChanged -= UpdateOverlayDirection; BrushAttributesOverlay.instance.collapsedChanged -= UpdateOverlayDirection; BrushScatterVariator.BrushScatterChanged -= UpdateValues; }); SetContentWidth(); UpdateValues(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void SetContentWidth() { if (direction == SliderDirection.Horizontal) contentWidth = 130; } private void UpdateValues() { var commonUI = BrushAttributesOverlay.GetCommonUI(); if (commonUI == null) return; style.display = commonUI.hasBrushScatter ? DisplayStyle.Flex : DisplayStyle.None; if (!commonUI.hasBrushScatter) return; value = commonUI.brushScatter * offset; } } }