Skip to content

Commit 0b5f4e9

Browse files
authored
Merge pull request #118 from FishingCactus/feature/serializable_interface
Feature/serializable interface
2 parents 0258181 + 1ea86b3 commit 0b5f4e9

10 files changed

+498
-31
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## [2.2.0] - 2024-06-03
4+
### Added
5+
- Add `SerializableInterface` struct: used to reference objects that implement a specific interface
6+
- Add `CustomObjectField`: used to draw customized object field with more functionality than `EditorGUILayout.ObjectField`
7+
### Updated
8+
- Moved `HideInCreateAssetDropdownAttribute` inside `FishingCactus.CommonCode` namespace
9+
310
## [2.1.0] - 2024-05-23
411
### Added
512
- Add `HideInCreateAssetDropdownAttribute`: scriptable object classes that define this attribute are hidden in the create asset dropdown

Editor/Inspector/CustomObjectField.cs

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
using System;
2+
using UnityEditor;
3+
using UnityEngine;
4+
using static UnityEditor.EditorGUI;
5+
using Object = UnityEngine.Object;
6+
7+
namespace FishingCactus.CommonCode
8+
{
9+
public class CustomObjectField
10+
{
11+
private readonly RectOffset _pickerRectOffset = new( -1, 0, -1, -1 );
12+
private readonly RectOffset _iconRectOffset = new( -2, 0, -2, -2 );
13+
private readonly Predicate<Object> _validForFieldPredicate = null;
14+
private readonly Func<Object, GUIContent> _objectLabelGetter = null;
15+
16+
public Rect FieldRect { get; private set; }
17+
18+
public event Action OnObjectPickerButtonClicked = null;
19+
20+
public CustomObjectField(
21+
Predicate<Object> is_object_valid_for_field,
22+
Func<Object, GUIContent> object_label_getter = null
23+
)
24+
{
25+
_validForFieldPredicate = is_object_valid_for_field;
26+
_objectLabelGetter = object_label_getter ?? DefaultObjectLabelGetter;
27+
}
28+
29+
public Object Draw(
30+
Rect position,
31+
GUIContent label,
32+
Object active_object
33+
)
34+
{
35+
Rect object_rect;
36+
37+
using( new IndentLevelScope( 0 ) )
38+
{
39+
object_rect = EditorGUI.PrefixLabel( position, label );
40+
}
41+
42+
Rect picker_rect = object_rect;
43+
picker_rect.xMin = position.xMax - 20f;
44+
picker_rect = _pickerRectOffset.Add( picker_rect );
45+
46+
if( Event.current.type == EventType.Repaint )
47+
{
48+
FieldRect = object_rect;
49+
}
50+
51+
if( GUI.enabled )
52+
{
53+
if( HandleObjectPickerEvent( picker_rect )
54+
|| HandleObjectPingEvent( object_rect, active_object )
55+
|| HandleDragEvents( object_rect, GetDraggedObjectIfValid(), ref active_object )
56+
)
57+
{
58+
Event.current.Use();
59+
}
60+
}
61+
62+
GUIContent active_object_label = _objectLabelGetter( active_object );
63+
64+
if( active_object_label.image != null )
65+
{
66+
DrawObjectField( object_rect, GUIContent.none );
67+
68+
Rect icon_rect = object_rect;
69+
icon_rect.width = icon_rect.height;
70+
icon_rect = _iconRectOffset.Add( icon_rect );
71+
72+
Rect label_rect = object_rect;
73+
label_rect.xMin = icon_rect.xMax + 1f;
74+
75+
Texture icon = active_object_label.image;
76+
active_object_label.image = null;
77+
78+
using( new IndentLevelScope( 0 ) )
79+
{
80+
EditorGUI.LabelField( label_rect, active_object_label, Styles.ObjectLabel );
81+
}
82+
83+
GUI.DrawTexture( icon_rect, icon, ScaleMode.ScaleToFit );
84+
}
85+
else
86+
{
87+
DrawObjectField( object_rect, active_object_label );
88+
}
89+
90+
GUI.Button( picker_rect, GUIContent.none, Styles.Picker );
91+
92+
return active_object;
93+
}
94+
95+
private void DrawObjectField(
96+
Rect object_rect,
97+
GUIContent label
98+
)
99+
{
100+
bool highlight = object_rect.Contains( Event.current.mousePosition ) && HasValidDraggedObject();
101+
GUI.Toggle( object_rect, highlight, label, Styles.ObjectField );
102+
}
103+
104+
private Object GetPingableObject(
105+
Object activeObject
106+
)
107+
{
108+
if( activeObject is Component component )
109+
{
110+
return component.gameObject;
111+
}
112+
else
113+
{
114+
return activeObject;
115+
}
116+
}
117+
118+
private Object GetDraggedObjectIfValid()
119+
{
120+
Object[] dragged_objects = DragAndDrop.objectReferences;
121+
122+
if( dragged_objects.Length != 1 )
123+
{
124+
return null;
125+
}
126+
127+
Object dragged_object = dragged_objects[ 0 ];
128+
129+
return _validForFieldPredicate.Invoke( dragged_object ) ? dragged_object : null;
130+
}
131+
132+
private bool HasValidDraggedObject()
133+
{
134+
return GetDraggedObjectIfValid() != null;
135+
}
136+
137+
private bool HandleObjectPickerEvent(
138+
Rect button_rect
139+
)
140+
{
141+
Event current_event = Event.current;
142+
143+
if( current_event.type != EventType.MouseDown )
144+
{
145+
return false;
146+
}
147+
148+
bool is_mouse_over_select_button = button_rect.Contains( current_event.mousePosition );
149+
150+
if( is_mouse_over_select_button )
151+
{
152+
OnObjectPickerButtonClicked?.Invoke();
153+
154+
return true;
155+
}
156+
157+
return false;
158+
}
159+
160+
private bool HandleObjectPingEvent(
161+
Rect button_rect,
162+
Object active_object
163+
)
164+
{
165+
Event current_event = Event.current;
166+
167+
if( current_event.type != EventType.MouseDown
168+
|| active_object == null
169+
)
170+
{
171+
return false;
172+
}
173+
174+
bool is_mouse_over_select_button = button_rect.Contains( current_event.mousePosition );
175+
176+
if( is_mouse_over_select_button )
177+
{
178+
EditorGUIUtility.PingObject( GetPingableObject( active_object ) );
179+
180+
return true;
181+
}
182+
183+
return false;
184+
}
185+
186+
private static bool HandleDragEvents(
187+
Rect object_rect,
188+
bool is_valid_object_being_dragged,
189+
ref Object active_object
190+
)
191+
{
192+
bool is_mouse_over_object_field = object_rect.Contains( Event.current.mousePosition );
193+
194+
if( !is_mouse_over_object_field )
195+
{
196+
return false;
197+
}
198+
199+
EventType event_type = Event.current.type;
200+
201+
switch( event_type )
202+
{
203+
case EventType.DragUpdated:
204+
{
205+
DragAndDrop.visualMode = is_valid_object_being_dragged ? DragAndDropVisualMode.Link : DragAndDropVisualMode.Rejected;
206+
207+
return true;
208+
}
209+
case EventType.DragPerform:
210+
{
211+
if( is_valid_object_being_dragged )
212+
{
213+
DragAndDrop.AcceptDrag();
214+
active_object = DragAndDrop.objectReferences[ 0 ];
215+
}
216+
217+
return true;
218+
}
219+
case EventType.DragExited:
220+
{
221+
return true;
222+
}
223+
default:
224+
{
225+
return false;
226+
}
227+
}
228+
}
229+
230+
public static GUIContent DefaultObjectLabelGetter(
231+
Object unity_object
232+
)
233+
{
234+
return unity_object ? new GUIContent( unity_object.ToString(), AssetPreview.GetMiniThumbnail( unity_object ) ) : new GUIContent( "None" );
235+
}
236+
237+
private static class Styles
238+
{
239+
public static readonly GUIStyle ObjectField = EditorStyles.objectField;
240+
public static readonly GUIStyle Picker = new( "ObjectFieldButton" );
241+
public static readonly GUIStyle ObjectLabel = CreateObjectLabelStyle();
242+
243+
private static GUIStyle CreateObjectLabelStyle()
244+
{
245+
GUIStyle label_style = new( EditorStyles.objectField );
246+
label_style.normal.background = Texture2D.blackTexture;
247+
248+
return label_style;
249+
}
250+
}
251+
}
252+
}

Editor/Inspector/CustomObjectField.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)