33using System . Linq ;
44using System . Reflection ;
55using HarmonyLib ;
6- using RuntimeUnityEditor . Core . Breakpoints ;
76using RuntimeUnityEditor . Core . ChangeHistory ;
87using RuntimeUnityEditor . Core . Inspector . Entries ;
98using RuntimeUnityEditor . Core . ObjectTree ;
@@ -24,6 +23,8 @@ public class ContextMenu : FeatureBase<ContextMenu>
2423 {
2524 private object _obj ;
2625 private MemberInfo _objMemberInfo ;
26+ private Func < object > _getValue ;
27+ private Action < object > _setValue ;
2728 private string _objName ;
2829
2930 private Rect _windowRect ;
@@ -115,41 +116,74 @@ o is Sprite ||
115116
116117 new MenuEntry ( "Replace texture..." ,
117118 o => o is Texture2D ||
118- ( o is Material m && m . mainTexture != null ) ||
119- ( o is RawImage i && i . mainTexture != null ) ||
120- ( o is Renderer r && ( r . sharedMaterial ?? r . material ) != null && ( r . sharedMaterial ?? r . material ) . mainTexture != null ) ,
119+ o is Texture && _setValue != null ||
120+ o is Material ||
121+ o is RawImage ||
122+ ( o is Renderer r && ( r . sharedMaterial != null || r . material != null ) ) ,
121123 o =>
122124 {
123125 string filename = "null" ;
124126 var newTex = TextureUtils . LoadTextureFromFileWithDialog ( ref filename ) ;
125127
126- if ( o is Texture2D t )
128+ if ( o is Texture t )
127129 {
128- //todo GetRawTextureData is not available in Unity 4.x
129- t . LoadRawTextureData ( newTex . GetRawTextureData ( ) ) ;
130- t . Apply ( true ) ;
131- UnityEngine . Object . Destroy ( newTex ) ;
132- Change . Report ( $ "(ContextMenu)::{ _objName } .LoadImage(File.ReadAllBytes(\" { filename } \" ))") ;
130+ try
131+ {
132+ if ( _setValue != null )
133+ {
134+ var current = _getValue ? . Invoke ( ) ;
135+ Change . Action ( $ "(ContextMenu)::{ _objName } = Texture2D.LoadImage(File.ReadAllBytes(\" { filename } \" ))", o , action : _ => _setValue ( newTex ) , undoAction : current != null ? _ => _setValue ( current ) : ( Action < object > ) null ) ;
136+ }
137+ else throw new NotImplementedException ( ) ;
138+ }
139+ catch ( Exception e )
140+ {
141+ if ( ! ( e is NotImplementedException ) )
142+ RuntimeUnityEditorCore . Logger . Log ( LogLevel . Error , e ) ;
143+
144+ if ( t is Texture2D t2d )
145+ {
146+ // Backup texture replace
147+ try
148+ {
149+ // GetRawTextureData is not available in Unity 4.x so it can't be touched directly
150+ new Action < Texture2D , Texture2D > ( ( target , source ) => { target . LoadRawTextureData ( source . GetRawTextureData ( ) ) ; target . Apply ( true ) ; } ) . Invoke ( t2d , newTex ) ;
151+ Change . Report ( $ "(ContextMenu)::{ _objName } .LoadImage(File.ReadAllBytes(\" { filename } \" ))") ;
152+ }
153+ catch ( Exception e2 )
154+ {
155+ RuntimeUnityEditorCore . Logger . Log ( LogLevel . Error , e2 ) ;
156+ }
157+ finally
158+ {
159+ UnityEngine . Object . Destroy ( newTex ) ;
160+ }
161+ }
162+ }
133163 }
134164 else if ( o is Material m )
135165 {
136- m . mainTexture = newTex ;
137- Change . Report ( $ "(ContextMenu):: { _objName } .mainTexture = Texture2D.LoadImage(File.ReadAllBytes( \" { filename } \" ))" ) ;
166+ Change . WithUndo ( $ "(ContextMenu):: { _objName } . mainTexture = Texture2D.LoadImage(File.ReadAllBytes( \" { filename } \" ))" ,
167+ m , newTex , ( material , texture2D ) => material . mainTexture = texture2D , oldValue : m . mainTexture ) ;
138168 }
139169 else if ( o is Image i && i . material != null )
140170 {
141- i . material . mainTexture = newTex ;
142- Change . Report ( $ "(ContextMenu):: { _objName } .mainTexture = Texture2D.LoadImage(File.ReadAllBytes( \" { filename } \" ))" ) ;
171+ Change . WithUndo ( $ "(ContextMenu):: { _objName } . material.mainTexture = Texture2D.LoadImage(File.ReadAllBytes( \" { filename } \" ))" ,
172+ i . material , newTex , ( material , texture2D ) => material . mainTexture = texture2D , oldValue : i . material . mainTexture ) ;
143173 }
144174 else if ( o is RawImage ri )
145175 {
146- ri . texture = newTex ;
147- Change . Report ( $ "(ContextMenu):: { _objName } .texture = Texture2D.LoadImage(File.ReadAllBytes( \" { filename } \" ))" ) ;
176+ Change . WithUndo ( $ "(ContextMenu):: { _objName } . texture = Texture2D.LoadImage(File.ReadAllBytes( \" { filename } \" ))" ,
177+ ri , newTex , ( rawImage , texture2D ) => rawImage . texture = texture2D , oldValue : ri . texture ) ;
148178 }
149179 else if ( o is Renderer r )
150180 {
151- ( r . sharedMaterial ?? r . material ) . mainTexture = newTex ;
152- Change . Report ( $ "(ContextMenu)::{ _objName } .mainTexture = Texture2D.LoadImage(File.ReadAllBytes(\" { filename } \" ))") ;
181+ if ( r . sharedMaterial != null )
182+ Change . WithUndo ( $ "(ContextMenu)::{ _objName } .sharedMaterial.mainTexture = Texture2D.LoadImage(File.ReadAllBytes(\" { filename } \" ))",
183+ r . sharedMaterial , newTex , ( material , texture2D ) => material . mainTexture = texture2D , oldValue : r . sharedMaterial . mainTexture ) ;
184+ else
185+ Change . WithUndo ( $ "(ContextMenu)::{ _objName } .material.mainTexture = Texture2D.LoadImage(File.ReadAllBytes(\" { filename } \" ))",
186+ r . material , newTex , ( material , texture2D ) => material . mainTexture = texture2D , oldValue : r . material . mainTexture ) ;
153187 }
154188 } ) ,
155189
@@ -210,36 +244,78 @@ IEnumerable<MenuEntry> AddGroup(string name, Func<object, MemberInfo, MethodBase
210244 }
211245 }
212246
247+ /// <summary>
248+ /// Show the context menu at current cursor position.
249+ /// </summary>
250+ /// <param name="obj">Instance of the object to show the menu for. Can be null if objEntry can be used to get it instead.</param>
251+ /// <param name="objEntry">Info about the member containing the displayed object.</param>
252+
253+ public void Show ( object obj , ICacheEntry objEntry )
254+ {
255+ if ( objEntry == null ) throw new ArgumentNullException ( nameof ( objEntry ) ) ;
256+
257+ if ( obj == null && objEntry . CanEnterValue ( ) )
258+ obj = objEntry . GetValue ( ) ;
259+
260+ if ( obj == null ) throw new ArgumentException ( $ "{ nameof ( obj ) } needs to be not null or { nameof ( objEntry ) } has to be gettable") ;
261+
262+ string name ;
263+ switch ( objEntry )
264+ {
265+ case FieldCacheEntry f :
266+ name = $ "{ f . FieldInfo . DeclaringType ? . Name } .{ f . FieldInfo . Name } ";
267+ break ;
268+ case PropertyCacheEntry p :
269+ name = $ "{ p . PropertyInfo . DeclaringType ? . Name } .{ p . PropertyInfo . Name } ";
270+ break ;
271+ case null :
272+ name = obj . GetType ( ) . FullDescription ( ) ;
273+ break ;
274+ default :
275+ name = objEntry . Name ( ) ;
276+ break ;
277+ }
278+
279+ var entryValid = objEntry . CanSetValue ( ) ;
280+ Show ( obj , objEntry . MemberInfo , name , entryValid ? objEntry . SetValue : ( Action < object > ) null , entryValid ? objEntry . GetValue : ( Func < object > ) null ) ;
281+ }
282+
213283 /// <summary>
214284 /// Show the context menu at current cursor position.
215285 /// </summary>
216286 /// <param name="obj">Object to show the menu for. Set to null to hide the menu.</param>
217- /// <param name="objMemberInfo">MemberInfo of wherever the object came from. Can be null.</param>
218- public void Show ( object obj , MemberInfo objMemberInfo )
287+ public void Show ( object obj )
219288 {
220- var m = UnityInput . Current . mousePosition ;
221- Show ( obj , objMemberInfo , new Vector2 ( m . x , Screen . height - m . y ) ) ;
289+ Show ( obj , null , obj ? . GetType ( ) . FullDescription ( ) , null , null ) ;
222290 }
223291
224292 /// <summary>
225293 /// Show the context menu at a specific screen position.
226294 /// </summary>
227- /// <param name="obj">Object to show the menu for. Set to null to hide the menu.</param>
228- /// <param name="objMemberInfo">MemberInfo of wherever the object came from. Can be null.</param>
229- /// <param name="clickPoint">Screen position to show the menu at.</param>
230- public void Show ( object obj , MemberInfo objMemberInfo , Vector2 clickPoint )
295+ /// <param name="obj">Object to show the menu for. Set to null to hide the menu (getObj also needs to be null or it will be used to get the obj).</param>
296+ /// <param name="memberInfo">MemberInfo of wherever the object came from. Can be null.</param>
297+ /// <param name="memberFullName">Name to show in the title bar and in change history.</param>
298+ /// <param name="setObj">Set value of the object</param>
299+ /// <param name="getObj">Get current value of the object</param>
300+ public void Show ( object obj , MemberInfo memberInfo , string memberFullName , Action < object > setObj , Func < object > getObj )
231301 {
302+ var m = UnityInput . Current . mousePosition ;
303+ var clickPoint = new Vector2 ( m . x , Screen . height - m . y ) ;
232304#if IL2CPP
233305 _windowRect = new Rect ( clickPoint , new Vector2 ( 100 , 100 ) ) ; // This one doesn't get stripped it seems
234306#else
235307 _windowRect = new Rect ( clickPoint . x , clickPoint . y , 100 , 100 ) ; // Unity4 only has the 4xfloat constructor
236308#endif
309+ if ( obj == null && getObj != null )
310+ obj = getObj ( ) ;
237311
238- if ( obj != null || objMemberInfo != null )
312+ if ( obj != null )
239313 {
240314 _obj = obj ;
241- _objMemberInfo = objMemberInfo ;
242- _objName = objMemberInfo != null ? $ "{ objMemberInfo . DeclaringType ? . Name } .{ objMemberInfo . Name } " : obj . GetType ( ) . FullDescription ( ) ;
315+ _objMemberInfo = memberInfo ?? obj as MemberInfo ;
316+ _objName = memberFullName ; //parentMemberInfo != null ? $"{parentMemberInfo.DeclaringType?.Name}.{parentMemberInfo.Name}" : obj.GetType().FullDescription();
317+ _setValue = setObj ;
318+ _getValue = getObj ;
243319
244320 _currentContents = MenuContents . Where ( x => x . IsVisible ( _obj ) ) . ToList ( ) ;
245321
@@ -258,10 +334,18 @@ public void Show(object obj, MemberInfo objMemberInfo, Vector2 clickPoint)
258334 /// <summary>
259335 /// Draw a GUILayout button that opens the context menu when clicked. It's only shown if the object is not null.
260336 /// </summary>
261- public void DrawContextButton ( object obj , MemberInfo objMemberInfo )
337+ public void DrawContextButton ( object obj , ICacheEntry objEntry )
338+ {
339+ if ( obj != null && GUILayout . Button ( "..." , IMGUIUtils . LayoutOptionsExpandWidthFalse ) )
340+ Show ( obj , objEntry ) ;
341+ }
342+ /// <summary>
343+ /// Draw a GUILayout button that opens the context menu when clicked. It's only shown if the object is not null.
344+ /// </summary>
345+ public void DrawContextButton ( object obj , MemberInfo memberInfo , string memberFullName , Action < object > setObj , Func < object > getObj )
262346 {
263347 if ( obj != null && GUILayout . Button ( "..." , IMGUIUtils . LayoutOptionsExpandWidthFalse ) )
264- Show ( obj , objMemberInfo ) ;
348+ Show ( obj , memberInfo , memberFullName , setObj , getObj ) ;
265349 }
266350
267351 /// <inheritdoc />
0 commit comments