A game about forced loneliness, made by TACStudios
at master 494 lines 22 kB view raw
1using System; 2using System.Collections.Generic; 3using UnityEditor.UIElements; 4using UnityEngine; 5using UnityEngine.UIElements; 6 7namespace UnityEditor.Rendering.LookDev 8{ 9 /// <summary>Interface that must implement the EnvironmentLibrary view to communicate with the data management</summary> 10 public interface IEnvironmentDisplayer 11 { 12 /// <summary>Repaint the UI</summary> 13 void Repaint(); 14 15 /// <summary>Callback on Environment change in the Library</summary> 16 event Action<EnvironmentLibrary> OnChangingEnvironmentLibrary; 17 } 18 19 partial class DisplayWindow : IEnvironmentDisplayer 20 { 21 static partial class Style 22 { 23 internal static readonly Texture2D k_AddIcon = CoreEditorUtils.LoadIcon(Style.k_IconFolder, "Add", forceLowRes: true); 24 internal static readonly Texture2D k_RemoveIcon = CoreEditorUtils.LoadIcon(Style.k_IconFolder, "Remove", forceLowRes: true); 25 internal static readonly Texture2D k_DuplicateIcon = CoreEditorUtils.LoadIcon(Style.k_IconFolder, "Duplicate", forceLowRes: true); 26 27 internal const string k_DragAndDropLibrary = "Drag and drop EnvironmentLibrary here"; 28 } 29 30 VisualElement m_EnvironmentContainer; 31 ListView m_EnvironmentList; 32 EnvironmentElement m_EnvironmentInspector; 33 UIElements.Toolbar m_EnvironmentListToolbar; 34 UIElements.ObjectField m_LibraryField; 35 36 //event Action<UnityEngine.Object> OnAddingEnvironmentInternal; 37 //event Action<UnityEngine.Object> IEnvironmentDisplayer.OnAddingEnvironment 38 //{ 39 // add => OnAddingEnvironmentInternal += value; 40 // remove => OnAddingEnvironmentInternal -= value; 41 //} 42 43 //event Action<int> OnRemovingEnvironmentInternal; 44 //event Action<int> IEnvironmentDisplayer.OnRemovingEnvironment 45 //{ 46 // add => OnRemovingEnvironmentInternal += value; 47 // remove => OnRemovingEnvironmentInternal -= value; 48 //} 49 50 event Action<EnvironmentLibrary> OnChangingEnvironmentLibraryInternal; 51 event Action<EnvironmentLibrary> IEnvironmentDisplayer.OnChangingEnvironmentLibrary 52 { 53 add => OnChangingEnvironmentLibraryInternal += value; 54 remove => OnChangingEnvironmentLibraryInternal -= value; 55 } 56 57 static int FirstVisibleIndex(ListView listView) 58 => (int)(listView.Q<ScrollView>().scrollOffset.y / (int)listView.fixedItemHeight); 59 60 void CreateEnvironment() 61 { 62 if (m_MainContainer == null || m_MainContainer.Equals(null)) 63 throw new System.MemberAccessException("m_MainContainer should be assigned prior CreateEnvironment()"); 64 65 m_EnvironmentContainer = new VisualElement() { name = Style.k_EnvironmentContainerName }; 66 m_MainContainer.Add(m_EnvironmentContainer); 67 if (sidePanel == SidePanel.Environment) 68 m_MainContainer.AddToClassList(Style.k_ShowEnvironmentPanelClass); 69 70 m_EnvironmentInspector = new EnvironmentElement(withPreview: false, () => 71 { 72 LookDev.SaveContextChangeAndApply(ViewIndex.First); 73 LookDev.SaveContextChangeAndApply(ViewIndex.Second); 74 }); 75 m_EnvironmentList = new ListView(); 76 m_EnvironmentList.AddToClassList("list-environment"); 77 m_EnvironmentList.selectionType = SelectionType.Single; 78 m_EnvironmentList.fixedItemHeight = EnvironmentElement.k_SkyThumbnailHeight; 79 m_EnvironmentList.makeItem = () => 80 { 81 var preview = new Image(); 82 preview.AddManipulator(new EnvironmentPreviewDragger(this, m_ViewContainer)); 83 return preview; 84 }; 85 m_EnvironmentList.bindItem = (e, i) => 86 { 87 if (LookDev.currentContext.environmentLibrary == null) 88 return; 89 90 (e as Image).image = EnvironmentElement.GetLatLongThumbnailTexture( 91 LookDev.currentContext.environmentLibrary[i], 92 EnvironmentElement.k_SkyThumbnailWidth); 93 }; 94#if UNITY_2022_2_OR_NEWER 95 m_EnvironmentList.selectionChanged += objects => 96 { 97 bool empty = !objects.GetEnumerator().MoveNext(); 98 if (empty || (LookDev.currentContext.environmentLibrary?.Count ?? 0) == 0) 99#elif UNITY_2020_1_OR_NEWER 100 m_EnvironmentList.onSelectionChange += objects => 101 { 102 bool empty = !objects.GetEnumerator().MoveNext(); 103 if (empty || (LookDev.currentContext.environmentLibrary?.Count ?? 0) == 0) 104#else 105 m_EnvironmentList.onSelectionChanged += objects => 106 107 { 108 if (objects.Count == 0 || (LookDev.currentContext.environmentLibrary?.Count ?? 0) == 0) 109#endif 110 { 111 m_EnvironmentInspector.style.visibility = Visibility.Hidden; 112 m_EnvironmentInspector.style.height = 0; 113 } 114 else 115 { 116 m_EnvironmentInspector.style.visibility = Visibility.Visible; 117 m_EnvironmentInspector.style.height = new StyleLength(StyleKeyword.Auto); 118 int firstVisibleIndex = FirstVisibleIndex(m_EnvironmentList); 119 Environment environment = LookDev.currentContext.environmentLibrary[m_EnvironmentList.selectedIndex]; 120 var container = m_EnvironmentList.Q("unity-content-container"); 121 if (m_EnvironmentList.selectedIndex - firstVisibleIndex >= container.childCount || m_EnvironmentList.selectedIndex < firstVisibleIndex) 122 { 123 m_EnvironmentList.ScrollToItem(m_EnvironmentList.selectedIndex); 124 firstVisibleIndex = FirstVisibleIndex(m_EnvironmentList); 125 } 126 Image deportedLatLong = container[m_EnvironmentList.selectedIndex - firstVisibleIndex] as Image; 127 m_EnvironmentInspector.Bind(environment, deportedLatLong); 128 } 129 }; 130#if UNITY_2022_2_OR_NEWER 131 m_EnvironmentList.itemsChosen += objCollection => 132 { 133 foreach (var obj in objCollection) 134 EditorGUIUtility.PingObject(LookDev.currentContext.environmentLibrary ? [(int)obj]); 135 }; 136#elif UNITY_2020_1_OR_NEWER 137 m_EnvironmentList.onItemsChosen += objCollection => 138 { 139 foreach (var obj in objCollection) 140 EditorGUIUtility.PingObject(LookDev.currentContext.environmentLibrary ? [(int)obj]); 141 }; 142#else 143 m_EnvironmentList.onItemChosen += obj => 144 EditorGUIUtility.PingObject(LookDev.currentContext.environmentLibrary?[(int)obj]); 145#endif 146 m_NoEnvironmentList = new Label(Style.k_DragAndDropLibrary); 147 m_NoEnvironmentList.style.flexGrow = 1; 148 m_NoEnvironmentList.style.unityTextAlign = TextAnchor.MiddleCenter; 149 m_EnvironmentContainer.Add(m_EnvironmentInspector); 150 151 m_EnvironmentListToolbar = new UIElements.Toolbar(); 152 ToolbarButton addEnvironment = new ToolbarButton(() => 153 { 154 if (LookDev.currentContext.environmentLibrary == null) 155 return; 156 157 LookDev.currentContext.environmentLibrary.Add(); 158 RefreshLibraryDisplay(); 159 m_EnvironmentList.ScrollToItem(-1); //-1: scroll to end 160 m_EnvironmentList.selectedIndex = LookDev.currentContext.environmentLibrary.Count - 1; 161 ScrollToEnd(); 162 }) 163 { 164 name = "add", 165 tooltip = "Add new empty environment" 166 }; 167 addEnvironment.Add(new Image() { image = Style.k_AddIcon }); 168 ToolbarButton removeEnvironment = new ToolbarButton(() => 169 { 170 if (m_EnvironmentList.selectedIndex == -1 || LookDev.currentContext.environmentLibrary == null) 171 return; 172 LookDev.currentContext.environmentLibrary?.Remove(m_EnvironmentList.selectedIndex); 173 RefreshLibraryDisplay(); 174 m_EnvironmentList.selectedIndex = -1; 175 }) 176 { 177 name = "remove", 178 tooltip = "Remove environment currently selected" 179 }; 180 removeEnvironment.Add(new Image() { image = Style.k_RemoveIcon }); 181 ToolbarButton duplicateEnvironment = new ToolbarButton(() => 182 { 183 if (m_EnvironmentList.selectedIndex == -1 || LookDev.currentContext.environmentLibrary == null) 184 return; 185 LookDev.currentContext.environmentLibrary.Duplicate(m_EnvironmentList.selectedIndex); 186 RefreshLibraryDisplay(); 187 m_EnvironmentList.ScrollToItem(-1); //-1: scroll to end 188 m_EnvironmentList.selectedIndex = LookDev.currentContext.environmentLibrary.Count - 1; 189 ScrollToEnd(); 190 }) 191 { 192 name = "duplicate", 193 tooltip = "Duplicate environment currently selected" 194 }; 195 duplicateEnvironment.Add(new Image() { image = Style.k_DuplicateIcon }); 196 m_EnvironmentListToolbar.Add(addEnvironment); 197 m_EnvironmentListToolbar.Add(removeEnvironment); 198 m_EnvironmentListToolbar.Add(duplicateEnvironment); 199 m_EnvironmentListToolbar.AddToClassList("list-environment-overlay"); 200 201 var m_EnvironmentInspectorSeparator = new VisualElement() { name = "separator-line" }; 202 m_EnvironmentInspectorSeparator.Add(new VisualElement() { name = "separator" }); 203 m_EnvironmentContainer.Add(m_EnvironmentInspectorSeparator); 204 205 VisualElement listContainer = new VisualElement(); 206 listContainer.AddToClassList("list-environment"); 207 listContainer.Add(m_EnvironmentList); 208 listContainer.Add(m_EnvironmentListToolbar); 209 210 m_LibraryField = new ObjectField("Library") 211 { 212 tooltip = "The currently used library" 213 }; 214 m_LibraryField.allowSceneObjects = false; 215 m_LibraryField.objectType = typeof(EnvironmentLibrary); 216 m_LibraryField.SetValueWithoutNotify(LookDev.currentContext.environmentLibrary); 217 m_LibraryField.RegisterValueChangedCallback(evt => 218 { 219 m_EnvironmentList.selectedIndex = -1; 220 OnChangingEnvironmentLibraryInternal?.Invoke(evt.newValue as EnvironmentLibrary); 221 RefreshLibraryDisplay(); 222 }); 223 224 var environmentListCreationToolbar = new UIElements.Toolbar() 225 { 226 name = "environmentListCreationToolbar" 227 }; 228 environmentListCreationToolbar.Add(m_LibraryField); 229 environmentListCreationToolbar.Add(new ToolbarButton(() 230 => EnvironmentLibraryCreator.CreateAndAssignTo(m_LibraryField)) 231 { 232 text = "New", 233 tooltip = "Create a new EnvironmentLibrary" 234 }); 235 236 m_EnvironmentContainer.Add(listContainer); 237 m_EnvironmentContainer.Add(m_NoEnvironmentList); 238 m_EnvironmentContainer.Add(environmentListCreationToolbar); 239 240 //add ability to unselect 241 m_EnvironmentList.RegisterCallback<MouseDownEvent>(evt => 242 { 243 var clickedIndex = (int)(evt.localMousePosition.y / (int)m_EnvironmentList.fixedItemHeight); 244 if (clickedIndex >= m_EnvironmentList.itemsSource.Count) 245 { 246 m_EnvironmentList.selectedIndex = -1; 247 evt.StopPropagation(); 248 } 249 }); 250 251 RefreshLibraryDisplay(); 252 } 253 254 //necessary as the scrollview need to be updated which take some editor frames. 255 void ScrollToEnd(int attemptRemaining = 5) 256 { 257 m_EnvironmentList.ScrollToItem(-1); //-1: scroll to end 258 if (attemptRemaining > 0) 259 EditorApplication.delayCall += () => ScrollToEnd(--attemptRemaining); 260 } 261 262 void RefreshLibraryDisplay() 263 { 264 if (m_LibraryField != null) 265 m_LibraryField.SetValueWithoutNotify(LookDev.currentContext.environmentLibrary); 266 267 if (m_EnvironmentInspector != null && m_EnvironmentList != null) 268 { 269 int itemMax = LookDev.currentContext.environmentLibrary?.Count ?? 0; 270 if (itemMax == 0 || m_EnvironmentList.selectedIndex == -1) 271 { 272 m_EnvironmentInspector.style.visibility = Visibility.Hidden; 273 m_EnvironmentInspector.style.height = 0; 274 } 275 else 276 { 277 m_EnvironmentInspector.style.visibility = Visibility.Visible; 278 m_EnvironmentInspector.style.height = new StyleLength(StyleKeyword.Auto); 279 } 280 281 var items = new List<int>(itemMax); 282 for (int i = 0; i < itemMax; i++) 283 items.Add(i); 284 m_EnvironmentList.itemsSource = items; 285 if (LookDev.currentContext.environmentLibrary == null) 286 { 287 m_EnvironmentList 288 .Q(className: "unity-scroll-view__vertical-scroller") 289 .Q("unity-dragger") 290 .style.visibility = Visibility.Hidden; 291 m_EnvironmentListToolbar.style.visibility = Visibility.Hidden; 292 m_NoEnvironmentList.style.display = DisplayStyle.Flex; 293 } 294 else 295 { 296 m_EnvironmentList 297 .Q(className: "unity-scroll-view__vertical-scroller") 298 .Q("unity-dragger") 299 .style.visibility = itemMax == 0 300 ? Visibility.Hidden 301 : Visibility.Visible; 302 m_EnvironmentListToolbar.style.visibility = Visibility.Visible; 303 m_NoEnvironmentList.style.display = DisplayStyle.None; 304 } 305 m_EnvironmentList.RefreshItems(); 306 } 307 } 308 309 DraggingContext StartDragging(VisualElement item, Vector2 worldPosition) 310 => new DraggingContext( 311 rootVisualElement, 312 item as Image, 313 //note: this even can come before the selection event of the 314 //ListView. Reconstruct index by looking at target of the event. 315 (int)item.layout.y / (int)m_EnvironmentList.fixedItemHeight, 316 worldPosition); 317 318 void EndDragging(DraggingContext context, Vector2 mouseWorldPosition) 319 { 320 Environment environment = LookDev.currentContext.environmentLibrary?[context.draggedIndex]; 321 if (environment == null) 322 return; 323 324 if (m_Views[(int)ViewIndex.First].ContainsPoint(mouseWorldPosition)) 325 { 326 if (viewLayout == Layout.CustomSplit) 327 OnChangingEnvironmentInViewInternal?.Invoke(environment, ViewCompositionIndex.Composite, mouseWorldPosition); 328 else 329 OnChangingEnvironmentInViewInternal?.Invoke(environment, ViewCompositionIndex.First, mouseWorldPosition); 330 m_NoEnvironment1.style.visibility = environment == null || environment.Equals(null) ? Visibility.Visible : Visibility.Hidden; 331 } 332 else 333 { 334 OnChangingEnvironmentInViewInternal?.Invoke(environment, ViewCompositionIndex.Second, mouseWorldPosition); 335 m_NoEnvironment2.style.visibility = environment == null || environment.Equals(null) ? Visibility.Visible : Visibility.Hidden; 336 } 337 } 338 339 class DraggingContext : IDisposable 340 { 341 const string k_CursorFollowerName = "cursorFollower"; 342 public readonly int draggedIndex; 343 readonly Image cursorFollower; 344 readonly Vector2 cursorOffset; 345 readonly VisualElement windowContent; 346 347 public DraggingContext(VisualElement windowContent, Image draggedElement, int draggedIndex, Vector2 worldPosition) 348 { 349 this.windowContent = windowContent; 350 this.draggedIndex = draggedIndex; 351 cursorFollower = new Image() 352 { 353 name = k_CursorFollowerName, 354 image = draggedElement.image 355 }; 356 cursorFollower.tintColor = new Color(1f, 1f, 1f, .5f); 357 windowContent.Add(cursorFollower); 358 cursorOffset = draggedElement.WorldToLocal(worldPosition); 359 360 cursorFollower.style.position = Position.Absolute; 361 UpdateCursorFollower(worldPosition); 362 } 363 364 public void UpdateCursorFollower(Vector2 mouseWorldPosition) 365 { 366 Vector2 windowLocalPosition = windowContent.WorldToLocal(mouseWorldPosition); 367 cursorFollower.style.left = windowLocalPosition.x - cursorOffset.x; 368 cursorFollower.style.top = windowLocalPosition.y - cursorOffset.y; 369 } 370 371 public void Dispose() 372 { 373 if (windowContent.Contains(cursorFollower)) 374 windowContent.Remove(cursorFollower); 375 } 376 } 377 378 class EnvironmentPreviewDragger : Manipulator 379 { 380 VisualElement m_DropArea; 381 DisplayWindow m_Window; 382 383 //Note: static as only one drag'n'drop at a time 384 static DraggingContext s_Context; 385 386 public EnvironmentPreviewDragger(DisplayWindow window, VisualElement dropArea) 387 { 388 m_Window = window; 389 m_DropArea = dropArea; 390 } 391 392 protected override void RegisterCallbacksOnTarget() 393 { 394 target.RegisterCallback<MouseDownEvent>(OnMouseDown); 395 target.RegisterCallback<MouseUpEvent>(OnMouseUp); 396 } 397 398 protected override void UnregisterCallbacksFromTarget() 399 { 400 target.UnregisterCallback<MouseDownEvent>(OnMouseDown); 401 target.UnregisterCallback<MouseUpEvent>(OnMouseUp); 402 } 403 404 void Release() 405 { 406 target.UnregisterCallback<MouseMoveEvent>(OnMouseMove); 407 s_Context?.Dispose(); 408 target.ReleaseMouse(); 409 s_Context = null; 410 } 411 412 void OnMouseDown(MouseDownEvent evt) 413 { 414 if (evt.button == 0) 415 { 416 target.CaptureMouse(); 417 target.RegisterCallback<MouseMoveEvent>(OnMouseMove); 418 s_Context = m_Window.StartDragging(target, evt.mousePosition); 419 //do not stop event as we still need to propagate it to the ListView for selection 420 } 421 } 422 423 void OnMouseUp(MouseUpEvent evt) 424 { 425 if (evt.button != 0) 426 return; 427 if (m_DropArea.ContainsPoint(m_DropArea.WorldToLocal(Event.current.mousePosition))) 428 { 429 m_Window.EndDragging(s_Context, evt.mousePosition); 430 evt.StopPropagation(); 431 } 432 Release(); 433 } 434 435 void OnMouseMove(MouseMoveEvent evt) 436 { 437 evt.StopPropagation(); 438 s_Context.UpdateCursorFollower(evt.mousePosition); 439 } 440 } 441 442 void IEnvironmentDisplayer.Repaint() 443 { 444 //can be unsync if library asset is destroy by user, so if null force sync 445 if (LookDev.currentContext.environmentLibrary == null) 446 m_LibraryField.value = null; 447 448 RefreshLibraryDisplay(); 449 } 450 451 void OnFocus() 452 { 453 //OnFocus is called before OnEnable that open backend if not already opened, so only sync if backend is open 454 if (LookDev.open) 455 { 456 //If EnvironmentLibrary asset as been edited by the user (deletion), 457 //update all view to use null environment if it was not temporary ones 458 if (LookDev.currentContext.HasLibraryAssetChanged(m_LibraryField.value as EnvironmentLibrary)) 459 { 460 ViewContext viewContext = LookDev.currentContext.GetViewContent(ViewIndex.First); 461 if (!(viewContext.environment?.isCubemapOnly ?? false)) 462 OnChangingEnvironmentInViewInternal?.Invoke(viewContext.environment, ViewCompositionIndex.First, default); 463 viewContext = LookDev.currentContext.GetViewContent(ViewIndex.Second); 464 if (!(viewContext.environment?.isCubemapOnly ?? false)) 465 OnChangingEnvironmentInViewInternal?.Invoke(viewContext.environment, ViewCompositionIndex.Second, default); 466 } 467 468 //If Cubemap asset as been edited by the user (deletion), 469 //update all views to use null environment if it was temporary ones 470 //and update all other views' environment to not use cubemap anymore 471 foreach (ViewContext viewContext in LookDev.currentContext.viewContexts) 472 { 473 if (viewContext.environment == null || !viewContext.environment.HasCubemapAssetChanged(viewContext.environment.cubemap)) 474 continue; 475 476 if (viewContext.environment.isCubemapOnly) 477 viewContext.UpdateEnvironment(null); 478 else 479 viewContext.environment.cubemap = null; 480 } 481 482 ((IEnvironmentDisplayer)this).Repaint(); 483 } 484 } 485 486 void FullRefreshEnvironmentList() 487 { 488 if (LookDev.currentContext.environmentLibrary != null) 489 LookDev.currentContext.FullReimportEnvironmentLibrary(); 490 491 ((IEnvironmentDisplayer)this).Repaint(); 492 } 493 } 494}