33// https://unity3d.com/legal/licenses/Unity_Reference_Only_License
44
55using System . Collections . Generic ;
6- using System . Reflection ;
7- using System . Text ;
86using UnityEditor . UIElements ;
97using UnityEngine ;
108using UnityEngine . Rendering ;
@@ -30,18 +28,18 @@ public static bool IsEmpty(SerializedProperty property, out string warnings)
3028 // Used in Unit tests
3129 public static IEnumerable < SerializedProperty > VisibleChildrenEnumerator ( SerializedProperty property )
3230 {
33- if ( property . hasVisibleChildren )
34- {
35- var iterator = property . Copy ( ) ;
36- var end = iterator . GetEndProperty ( ) ;
31+ if ( ! property . hasVisibleChildren )
32+ yield break ;
3733
38- iterator . NextVisible ( true ) ; // Move to the first child property
39- do
40- {
41- yield return iterator ;
42- iterator . NextVisible ( false ) ;
43- } while ( ! SerializedProperty . EqualContents ( iterator , end ) ) ; // Move to the next sibling property
44- }
34+ var iterator = property . Copy ( ) ;
35+ var end = iterator . GetEndProperty ( ) ;
36+
37+ iterator . NextVisible ( true ) ; // Move to the first child property
38+ do
39+ {
40+ yield return iterator ;
41+ iterator . NextVisible ( false ) ;
42+ } while ( ! SerializedProperty . EqualContents ( iterator , end ) ) ; // Move to the next sibling property
4543 }
4644
4745 public override void OnGUI ( Rect position , SerializedProperty property , GUIContent label )
@@ -52,13 +50,33 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten
5250 return ;
5351 }
5452
53+ int baseNameLength = property . propertyPath . Length + 1 ;
5554 foreach ( var child in VisibleChildrenEnumerator ( property ) )
5655 {
57- EditorGUI . PropertyField ( position , child ) ;
58- position . y += EditorGUI . GetPropertyHeight ( property ) + EditorGUIUtility . standardVerticalSpacing ;
56+ Rect childPosition = position ;
57+ childPosition . height = EditorGUI . GetPropertyHeight ( child ) ;
58+ EditorGUI . BeginChangeCheck ( ) ;
59+ EditorGUI . PropertyField ( childPosition , child ) ;
60+ if ( EditorGUI . EndChangeCheck ( ) )
61+ {
62+ child . serializedObject . ApplyModifiedProperties ( ) ;
63+ var settings = property . boxedValue as IRenderPipelineGraphicsSettings ;
64+ settings . NotifyValueChanged ( child . propertyPath . Substring ( baseNameLength ) ) ;
65+ }
66+ position . y += childPosition . height + EditorGUIUtility . standardVerticalSpacing ;
5967 }
6068 }
6169
70+ public override float GetPropertyHeight ( SerializedProperty property , GUIContent label )
71+ {
72+ float height = 0f ;
73+ foreach ( var child in VisibleChildrenEnumerator ( property ) )
74+ height += EditorGUI . GetPropertyHeight ( child ) + EditorGUIUtility . standardVerticalSpacing ;
75+ if ( height > 0 )
76+ height -= EditorGUIUtility . standardVerticalSpacing ; //remove last one
77+ return height ;
78+ }
79+
6280 public override VisualElement CreatePropertyGUI ( SerializedProperty property )
6381 {
6482 VisualElement root = new ( ) ;
@@ -69,12 +87,107 @@ public override VisualElement CreatePropertyGUI(SerializedProperty property)
6987 return root ;
7088 }
7189
90+ notifier = new ( property ) ;
7291 foreach ( var child in VisibleChildrenEnumerator ( property ) )
7392 {
74- root . Add ( new PropertyField ( child ) ) ;
93+ var propertyField = new PropertyField ( child ) ;
94+ propertyField . RegisterCallback ( PropagateOnChangeRecursivelyAtBinding ( propertyField ) ) ;
95+ root . Add ( propertyField ) ;
7596 }
7697
7798 return root ;
7899 }
100+
101+ EventCallback < SerializedPropertyBindEvent > PropagateOnChangeRecursivelyAtBinding ( PropertyField propertyField )
102+ {
103+ EventCallback < SerializedPropertyBindEvent > callback = null ;
104+ callback = ( SerializedPropertyBindEvent evt ) => {
105+ UQueryState < VisualElement > childs ;
106+ if ( evt . bindProperty . isArray
107+ || ( childs = propertyField . Query ( name : "unity-content" ) . Visible ( ) . Build ( ) )
108+ . AtIndex ( 0 ) == null ) //no child
109+ {
110+ // Fields that have no childs or array (handle all resizing and cell changes directly)
111+ propertyField . RegisterCallback < SerializedPropertyChangeEvent > ( notifier . AddNotificationRequest ) ;
112+ }
113+ else
114+ {
115+ // Propagate to custom struct and class childs
116+ childs . ForEach ( e => e . Query < PropertyField > ( ) . Visible ( ) . ForEach ( p => p . RegisterCallback ( PropagateOnChangeRecursivelyAtBinding ( p ) ) ) ) ;
117+ }
118+
119+ propertyField . UnregisterCallback ( callback ) ;
120+ callback = null ;
121+ } ;
122+ return callback ;
123+ }
124+
125+ // Aim is to batch notification for case where several are trigerred.
126+ // Example:
127+ // - Modifying a color, several channel amongst r, g, b, a can be altered with same editor modification
128+ // - This result in several time the SerializedPropertyChangeEvent being raised.
129+ // Also at binding we do not want to raise notification. As there is a previous SerializedObject state to
130+ // compare, we can trim that, even for Arrays that have a lot of elements to bind/update.
131+ class Notifier
132+ {
133+ HashSet < string > notificationRequests = new ( ) ;
134+ SerializedObject lastSO ;
135+ IRenderPipelineGraphicsSettings rpgs ;
136+ int baseNameLength ;
137+
138+ public Notifier ( SerializedProperty property )
139+ {
140+ rpgs = property . boxedValue as IRenderPipelineGraphicsSettings ;
141+ baseNameLength = property . propertyPath . Length + 1 ;
142+ lastSO = new SerializedObject ( property . serializedObject . targetObject ) ;
143+ }
144+
145+ public void AddNotificationRequest ( SerializedPropertyChangeEvent evt )
146+ {
147+ SerializedProperty property = evt ? . changedProperty ;
148+ if ( property == null )
149+ throw new System . ArgumentException ( "ChangedProperty cannot be null." , $ "{ nameof ( evt ) } .changedProperty") ;
150+
151+ if ( SameThanLast ( property ) )
152+ return ;
153+
154+ Register ( property . propertyPath ) ;
155+ }
156+
157+ bool SameThanLast ( SerializedProperty property )
158+ {
159+ if ( SerializedProperty . DataEquals ( property , lastSO ? . FindProperty ( property . propertyPath ) ) )
160+ return true ;
161+
162+ lastSO = new SerializedObject ( property . serializedObject . targetObject ) ;
163+ return false ;
164+ }
165+
166+ string TrimPath ( string path )
167+ {
168+ int length = path . IndexOf ( '.' , baseNameLength ) - baseNameLength ;
169+ path = length < 0
170+ ? path . Substring ( baseNameLength )
171+ : path . Substring ( baseNameLength , length ) ;
172+ return path ;
173+ }
174+
175+ void Register ( string propertyPath )
176+ {
177+ if ( notificationRequests . Count == 0 )
178+ EditorApplication . CallDelayed ( Notify ) ;
179+
180+ notificationRequests . Add ( TrimPath ( propertyPath ) ) ;
181+ }
182+
183+ void Notify ( )
184+ {
185+ foreach ( var path in notificationRequests )
186+ rpgs . NotifyValueChanged ( path ) ;
187+ notificationRequests . Clear ( ) ;
188+ }
189+ }
190+
191+ Notifier notifier ;
79192 }
80193}
0 commit comments