DisplayWindow.EnvironmentLibrarySidePanel.cs 21 KB

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