#if UNITY_EDITOR using UnityEngine; using UnityEditor; using UnityEngine.UIElements; using System.Collections.Generic; using System; namespace GSpawn { public class GridView : VisualElement, IGridView where TItem : GridViewItem, new() where TItemData : IUIItemStateProvider { public delegate void SelectionChangedHandler (GridView gridView); public delegate void SelectedItemsWillBeDeletedHandler (GridView gridView, List itemIds); public delegate void PerformPasteHandler (GridView gridView, List srcItemIds, List destItemIds, CopyPasteMode copyPasteMode); public event SelectionChangedHandler selectionChanged; public event SelectedItemsWillBeDeletedHandler selectedItemsWillBeDeleted; public event PerformPasteHandler paste; private class CopyOperation { public CopyPasteMode copyPasteMode = CopyPasteMode.Copy; public List sourceItems = new List(); public bool isActive { get { return sourceItems.Count != 0; } } } public class DragAndDropData { private List _items = new List(); public int numItems { get { return _items.Count; } } public DragAndDropData(GridView gridView) { gridView.getVisibleSelectedItems(_items); } public TItem getItem(int index) { return _items[index]; } } private GridViewState _state = null; [NonSerialized] private Dictionary _items = new Dictionary(); [NonSerialized] private GridViewItemPool _itemPool; [NonSerialized] private CopyOperation _copyOp = new CopyOperation(); [NonSerialized] private TItem _selectRangeBegin = null; [NonSerialized] private TItem _selectRangeEnd = null; [NonSerialized] private ScrollView _scrollView; [NonSerialized] private VisualElement _itemContainer; private bool _canMultiSelect = true; private float _itemMarginLeft = 1.0f; private float _itemMarginTop = 1.0f; private Vector2 _imageSize = new Vector2(128.0f, 128.0f); private bool hasSelectRange { get { return _selectRangeBegin != null && _selectRangeEnd != null; } } public int numSelectedItems { get { return _state.selectedItems.Count; } } public int dragAndDropInitiatorId { get { return GetHashCode(); } } public int copyPasteInitiatorId { get { return GetHashCode(); } } public System.Object dragAndDropData { get { return new DragAndDropData(this); } } public bool canCopyPaste { get; set; } public bool canCutPaste { get; set; } public bool canDelete { get; set; } public bool canMultiSelect { get { return _canMultiSelect; } set { _canMultiSelect = value; } } public GridView(GridViewState state, VisualElement parent) { _itemPool = new GridViewItemPool(this); _state = state; focusable = true; style.flexGrow = 1.0f; parent.Add(this); _scrollView = new ScrollView(ScrollViewMode.Vertical); _scrollView.focusable = true; _scrollView.contentContainer.style.flexWrap = Wrap.NoWrap; _scrollView.contentContainer.style.flexDirection = FlexDirection.Column; _scrollView.contentContainer.style.flexGrow = 1.0f; _scrollView.RegisterCallback(onKeyDown); _scrollView.verticalScroller.valueChanged += p => { _state.storeScrollData(_scrollView); }; Add(_scrollView); _itemContainer = new VisualElement(); _itemContainer.style.flexWrap = Wrap.Wrap; _itemContainer.style.flexDirection = FlexDirection.Row; _scrollView.Add(_itemContainer); RegisterCallback((p) => { PluginDragAndDrop.visualMode = DragAndDropVisualMode.Copy; }); } public void setItemCopyPasteMode(PluginGuid itemId, CopyPasteMode copyPasteMode) { _items[itemId].setCopyPasteMode(copyPasteMode); } public void setItemsCopyPasteMode(IEnumerable itemIds, CopyPasteMode copyPasteMode) { foreach (PluginGuid id in itemIds) setItemCopyPasteMode(id, copyPasteMode); } public void setAllItemsCopyPasteMode(CopyPasteMode copyPasteMode) { foreach (var pair in _items) setItemCopyPasteMode(pair.Key, copyPasteMode); } public bool containsItem(PluginGuid itemId) { return _items.ContainsKey(itemId); } public void filterMissingItems(List itemIds) { itemIds.RemoveAll(itemId => !containsItem(itemId)); } public void scheduleScrollToItem(PluginGuid itemId) { GSpawn.active.registerEditorUpdateAction(new ScrollToItem(_scrollView, _items[itemId], Undo.GetCurrentGroup(), _state)); } public void scheduleScrollToItems(List itemIds) { var items = new List(); idsToItems(itemIds, items, false); GSpawn.active.registerEditorUpdateAction(new ScrollToItem(_scrollView, _itemContainer.findChildItemWithGreatestIndex(items), Undo.GetCurrentGroup(), _state)); } public int deleteItems(Predicate removalCondition) { int numRemoved = 0; var items = new List(_items.Values); foreach(var item in items) { if (removalCondition(item.data)) { ++numRemoved; doDeleteItem(item.guid); } } return numRemoved; } public void setImageSize(Vector2 size) { foreach(var pair in _items) pair.Value.imageSize = size; _imageSize = size; } public void setAllItemsSelected(bool selected, bool onlyVisible, bool notify) { UndoEx.record(_state); if (onlyVisible) { foreach (var pair in _items) if (pair.Value.itemVisible) setItemSelected(pair.Value, selected, notify); } else { foreach (var pair in _items) setItemSelected(pair.Value, selected, notify); } } public void setItemsSelected(IEnumerable itemIds, bool selected, bool onlyVisible, bool notify) { UndoEx.record(_state); if (onlyVisible) { foreach (var id in itemIds) { var item = _items[id]; if (item.itemVisible) setItemSelected(item, selected, notify); } } else { foreach (var id in itemIds) setItemSelected(_items[id], selected, notify); } } public void setItemSelected(PluginGuid itemId, bool selected, bool notify) { bool alreadySelected = isItemSelected(itemId); if (selected && alreadySelected) return; if (!selected && !alreadySelected) return; UndoEx.record(_state); setItemSelected(_items[itemId], selected, notify); } public void setAllItemsVisible(bool visible) { foreach (var pair in _items) pair.Value.setVisible(visible); } public void setItemsVisible(List itemIds, bool visible) { foreach (var itemId in itemIds) _items[itemId].setVisible(visible); } public void setItemVisible(PluginGuid itemId, bool visible) { _items[itemId].setVisible(visible); } public void getSelectedItemData(List itemData) { itemData.Clear(); foreach (var id in _state.selectedItems) itemData.Add(_items[id].data); } public void getSelectedItemData(List itemData, bool onlyVisible) { if (onlyVisible) getVisibleSelectedItemData(itemData); else getSelectedItemData(itemData); } public void getVisibleSelectedItemData(List itemData) { itemData.Clear(); foreach (var id in _state.selectedItems) { var item = _items[id]; if (item.itemVisible) itemData.Add(item.data); } } public void getVisibleItemData(List itemData) { itemData.Clear(); foreach (var pair in _items) { var item = pair.Value; if (item.itemVisible) itemData.Add(item.data); } } public void refreshUI() { if (GSpawn.active == null) return; foreach (var pair in _items) pair.Value.refreshUI(); } public void refreshItemUI(PluginGuid itemId) { _items[itemId].refreshUI(); } public void refreshItemsUI(List itemIds) { foreach (var id in itemIds) _items[id].refreshUI(); } public void refreshSelectedItemsUI() { foreach (var id in _state.selectedItems) _items[id].refreshUI(); } public TItemData getItemData(PluginGuid itemId) { return _items[itemId].data; } public void getItemData(IEnumerable itemIds, List itemData) { itemData.Clear(); foreach (var id in itemIds) itemData.Add(_items[id].data); } public void getItemData(List itemData) { itemData.Clear(); foreach (var pair in _items) itemData.Add(pair.Value.data); } public void filterItems(Func filter) { foreach (var pair in _items) pair.Value.setVisible(filter(pair.Value.data)); } public bool isItemSelected(PluginGuid itemId) { return _state.selectedItems.Contains(itemId); } public void onBeginBuild() { foreach (var pair in _items) _itemPool.releaseItem(pair.Value); _items.Clear(); _itemContainer.Clear(); _copyOp.sourceItems.Clear(); } public void onEndBuild() { _state.selectedItems.RemoveWhere(itemId => !containsItem(itemId)); foreach (var pair in _items) { var item = pair.Value; if (_state.selectedItems.Contains(pair.Key)) setItemSelected(item, true, false); } if (_state.hasSelectRange) { if (containsItem(_state.selectRangeBeginId)) _selectRangeBegin = _items[_state.selectRangeBeginId]; else _selectRangeBegin = null; if (containsItem(_state.selectRangeEndId)) _selectRangeEnd = _items[_state.selectRangeEndId]; else _selectRangeEnd = null; if (_selectRangeBegin == null || _selectRangeEnd == null) { _state.hasSelectRange = false; _selectRangeBegin = null; _selectRangeEnd = null; } } else { _selectRangeBegin = null; _selectRangeEnd = null; } _state.applyScrollState(_scrollView); } public PluginGuid addItem(TItemData itemData, bool visible) { var item = _itemPool.obtainItem(itemData); _items.Add(item.guid, item); _itemContainer.Add(item); item.imageSize = _imageSize; item.style.marginLeft = _itemMarginLeft; item.style.marginTop = _itemMarginTop; //item.buildUI(); if (item.hasTag(GridViewItemPool.newItemTag)) registerItemEventHandlers(item); if (!visible) item.setVisible(false); setItemSelected(item.guid, itemData.uiSelected, false); item.setCopyPasteMode(itemData.uiCopyPasteMode); item.refreshUI(); return item.guid; } private void onItemClickedDown(TItem item, MouseDownEvent e) { if (e.button != (int)MouseButton.LeftMouse || e.altKey) return; UndoEx.record(_state); if (((!e.ctrlKey && !e.shiftKey) || !canMultiSelect) && !isItemSelected(item.guid)) { clearSelection(false); setItemSelected(item, true, true); setSelectRange(item, item); _scrollView.ScrollTo(item); } else if (e.ctrlKey && !e.shiftKey) { bool selected = !isItemSelected(item.guid); setItemSelected(item, selected, true); setSelectRange(item, item); if (selected) _scrollView.ScrollTo(item); } else if (!e.ctrlKey && e.shiftKey && numSelectedItems != 0) { clearSelection(false); setSelectRange(_selectRangeBegin, item); setItemsSelected(_itemContainer.IndexOf(_selectRangeBegin), _itemContainer.IndexOf(_selectRangeEnd), true); _scrollView.ScrollTo(_selectRangeEnd); } if (numSelectedItems == 0) setSelectRange(null, null); } private void onItemClickedUp(TItem item, MouseUpEvent e) { if (e.button != (int)MouseButton.LeftMouse) return; UndoEx.record(_state); if (!e.ctrlKey && !e.shiftKey && numSelectedItems > 1) { clearSelection(false); setItemSelected(item, true, true); setSelectRange(item, item); _scrollView.ScrollTo(item); } if (numSelectedItems == 0) setSelectRange(null, null); } private void onKeyDown(KeyDownEvent e) { if (_copyOp.isActive) { if (FixedShortcuts.cancelAction(e)) { UICopyPaste.cancel(); return; } } if (FixedShortcuts.ui_DeleteSelected(e)) deleteSelected(); else if (FixedShortcuts.ui_SelectUp(e)) selectUp(e); else if (FixedShortcuts.ui_SelectDown(e)) selectDown(e); else if (FixedShortcuts.ui_SelectLeft(e)) selectLeft(e); else if (FixedShortcuts.ui_SelectRight(e)) selectRight(e); else if (FixedShortcuts.ui_Copy(e)) copySelected(CopyPasteMode.Copy); else if (FixedShortcuts.ui_Cut(e)) copySelected(CopyPasteMode.Cut); else if (FixedShortcuts.ui_Paste(e)) pasteSelected(); else if (FixedShortcuts.ui_SelectAll(e)) selectAll(); } private void copySelected(CopyPasteMode copyPasteMode) { if (numSelectedItems == 0) return; if (copyPasteMode == CopyPasteMode.Copy && !canCopyPaste) return; if (copyPasteMode == CopyPasteMode.Cut && !canCutPaste) return; cancelCopyPaste(); UICopyPaste.begin(copyPasteMode, copyPasteInitiatorId, pasteSelected, cancelCopyPaste); _copyOp.copyPasteMode = copyPasteMode; foreach (var selectedId in _state.selectedItems) { var item = _items[selectedId]; if (item.setCopyPasteMode(copyPasteMode)) _copyOp.sourceItems.Add(item); } } private void pasteSelected() { if (paste == null || !_copyOp.isActive) return; if (_copyOp.copyPasteMode == CopyPasteMode.Copy && !canCopyPaste) return; if (_copyOp.copyPasteMode == CopyPasteMode.Cut && !canCutPaste) return; var destItemIds = new List(); getSelectedItemIds(destItemIds); if (destItemIds.Count == 0) return; var srcItemIds = new List(); getItemIds(_copyOp.sourceItems, srcItemIds); if (paste != null) paste(this, srcItemIds, destItemIds, _copyOp.copyPasteMode); cancelCopyPaste(); foreach (var itemId in _state.selectedItems) _items[itemId].refreshUI(); foreach (var itemId in srcItemIds) _items[itemId].refreshUI(); } private void cancelCopyPaste() { foreach (var item in _copyOp.sourceItems) item.setCopyPasteMode(CopyPasteMode.None); _copyOp.sourceItems.Clear(); } private void deleteSelected() { if (numSelectedItems == 0 || !canDelete) return; var selectedIds = new List(); getSelectedItemIds(selectedIds); if (selectedItemsWillBeDeleted != null) selectedItemsWillBeDeleted(this, selectedIds); UndoEx.record(_state); _state.selectedItems.Clear(); foreach (var selectedId in selectedIds) doDeleteItem(selectedId); } private void selectAll() { if (!canMultiSelect) return; UndoEx.record(_state); clearSelection(false); setSelectRange((TItem)_itemContainer[0], (TItem)_itemContainer[_itemContainer.childCount - 1]); setItemsSelected(0, _itemContainer.childCount - 1, true); } private void selectUp(KeyDownEvent e) { if (hasSelectRange) { if (FixedShortcuts.ui_EnableDirectionalSelectAppend(e) && canMultiSelect) { int itemPitch = getNumItemsPerRow(); int endIndex = _itemContainer.IndexOf(_selectRangeEnd); int beginIndex = _itemContainer.IndexOf(_selectRangeBegin); int nextIndex = Mathf.Max(0, endIndex - itemPitch); while (nextIndex >= 0) { TItem nextItem = (TItem)_itemContainer[nextIndex]; if (nextItem.itemVisible) { UndoEx.record(_state); clearSelection(false); setSelectRange(_selectRangeBegin, nextItem); setItemsSelected(beginIndex, nextIndex, true); break; } nextIndex -= itemPitch; nextIndex = Mathf.Max(0, nextIndex); } _scrollView.ScrollTo(_selectRangeEnd); } else { int itemPitch = getNumItemsPerRow(); int nextIndex = Mathf.Max(0, _itemContainer.IndexOf(_selectRangeEnd) - itemPitch); while (nextIndex >= 0) { TItem nextItem = (TItem)_itemContainer[nextIndex]; if (nextItem.itemVisible) { UndoEx.record(_state); clearSelection(false); setSelectRange(nextItem, nextItem); setItemSelected(_selectRangeBegin, true, false); break; } nextIndex -= itemPitch; nextIndex = Mathf.Max(0, nextIndex); } _scrollView.ScrollTo(_selectRangeEnd); if (selectionChanged != null) selectionChanged(this); } } } private void selectDown(KeyDownEvent e) { if (hasSelectRange) { if (FixedShortcuts.ui_EnableDirectionalSelectAppend(e) && canMultiSelect) { int itemPitch = getNumItemsPerRow(); int endIndex = _itemContainer.IndexOf(_selectRangeEnd); int beginIndex = _itemContainer.IndexOf(_selectRangeBegin); int nextIndex = Mathf.Min(_itemContainer.childCount - 1, endIndex + itemPitch); while (nextIndex >= 0) { TItem nextItem = (TItem)_itemContainer[nextIndex]; if (nextItem.itemVisible) { UndoEx.record(_state); clearSelection(false); setSelectRange(_selectRangeBegin, nextItem); setItemsSelected(beginIndex, nextIndex, true); break; } nextIndex += itemPitch; nextIndex = Mathf.Min(_itemContainer.childCount - 1, nextIndex); } _scrollView.ScrollTo(_selectRangeEnd); } else { int itemPitch = getNumItemsPerRow(); int nextIndex = Mathf.Min(_itemContainer.childCount - 1, _itemContainer.IndexOf(_selectRangeEnd) + itemPitch); while (nextIndex < _itemContainer.childCount) { TItem nextItem = (TItem)_itemContainer[nextIndex]; if (nextItem.itemVisible) { UndoEx.record(_state); clearSelection(false); setSelectRange(nextItem, nextItem); setItemSelected(_selectRangeBegin, true, false); break; } nextIndex += itemPitch; nextIndex = Mathf.Min(_itemContainer.childCount - 1, nextIndex); } _scrollView.ScrollTo(_selectRangeEnd); if (selectionChanged != null) selectionChanged(this); } } } private void selectLeft(KeyDownEvent e) { if (hasSelectRange) { if (FixedShortcuts.ui_EnableDirectionalSelectAppend(e)) { int endIndex = _itemContainer.IndexOf(_selectRangeEnd); int beginIndex = _itemContainer.IndexOf(_selectRangeBegin); int nextIndex = endIndex - 1; while (nextIndex >= 0) { TItem nextItem = (TItem)_itemContainer[nextIndex]; if (nextItem.itemVisible) { UndoEx.record(_state); clearSelection(false); setSelectRange(_selectRangeBegin, nextItem); setItemsSelected(beginIndex, nextIndex, true); break; } --nextIndex; } _scrollView.ScrollTo(_selectRangeEnd); } else { int nextIndex = _itemContainer.IndexOf(_selectRangeEnd) - 1; while (nextIndex >= 0) { TItem nextItem = (TItem)_itemContainer[nextIndex]; if (nextItem.itemVisible) { UndoEx.record(_state); clearSelection(false); setSelectRange(nextItem, nextItem); setItemSelected(_selectRangeBegin, true, false); break; } --nextIndex; } _scrollView.ScrollTo(_selectRangeEnd); if (selectionChanged != null) selectionChanged(this); } } } private void selectRight(KeyDownEvent e) { if (hasSelectRange) { if (FixedShortcuts.ui_EnableDirectionalSelectAppend(e)) { int endIndex = _itemContainer.IndexOf(_selectRangeEnd); int beginIndex = _itemContainer.IndexOf(_selectRangeBegin); int nextIndex = endIndex + 1; while (nextIndex < _itemContainer.childCount) { TItem nextItem = (TItem)_itemContainer[nextIndex]; if (nextItem.itemVisible) { UndoEx.record(_state); clearSelection(false); setSelectRange(_selectRangeBegin, nextItem); setItemsSelected(beginIndex, nextIndex, true); break; } ++nextIndex; } _scrollView.ScrollTo(_selectRangeEnd); } else { int nextIndex = _itemContainer.IndexOf(_selectRangeEnd) + 1; while (nextIndex < _itemContainer.childCount) { TItem nextItem = (TItem)_itemContainer[nextIndex]; if (nextItem.itemVisible) { UndoEx.record(_state); clearSelection(false); setSelectRange(nextItem, nextItem); setItemSelected(_selectRangeBegin, true, false); break; } ++nextIndex; } _scrollView.ScrollTo(_selectRangeEnd); if (selectionChanged != null) selectionChanged(this); } } } private void clearSelection(bool notify) { foreach (var pair in _items) setItemSelected(pair.Value, false, false); if (notify && selectionChanged != null) selectionChanged(this); } private void setItemsSelected(int beginIndex, int endIndex, bool selected) { int dir = (int)Mathf.Sign(endIndex - beginIndex); int stop = endIndex + dir; if (dir == 0) { dir = 1; stop = endIndex + 1; } for (int itemIndex = beginIndex; itemIndex != stop; itemIndex += dir) setItemSelected((TItem)_itemContainer[itemIndex], selected, false); if (selectionChanged != null) selectionChanged(this); } private void setItemSelected(TItem item, bool selected, bool notify) { if (selected) _state.selectedItems.Add(item.guid); else _state.selectedItems.Remove(item.guid); item.setSelected(selected); if (notify && selectionChanged != null) selectionChanged(this); } private void getSelectedItemIds(List ids) { ids.Clear(); ids.AddRange(_state.selectedItems.hashSet); } private void getItemIds(List items, List ids) { ids.Clear(); foreach (var item in items) ids.Add(item.guid); } private void setSelectRange(TItem begin, TItem end) { _selectRangeBegin = begin; if (_selectRangeBegin != null) _state.selectRangeBeginId = _selectRangeBegin.guid; _selectRangeEnd = end; if (_selectRangeEnd != null) _state.selectRangeEndId = _selectRangeEnd.guid; _state.hasSelectRange = _selectRangeBegin != null && _selectRangeEnd != null; } private void getSelectedItems(List selectedItems) { selectedItems.Clear(); foreach (var selectedId in _state.selectedItems) selectedItems.Add(_items[selectedId]); } private void getVisibleSelectedItems(List selectedItems) { selectedItems.Clear(); foreach (var selectedId in _state.selectedItems) { var item = _items[selectedId]; if (item.itemVisible) selectedItems.Add(item); } } private void doDeleteItem(PluginGuid itemId) { var item = _items[itemId]; unregisterItemEventHandlers(item); _itemContainer.Remove(item); _items.Remove(itemId); _state.selectedItems.Remove(itemId); if (itemId == _state.selectRangeBeginId || itemId == _state.selectRangeEndId) { _state.hasSelectRange = false; _selectRangeBegin = null; _selectRangeEnd = null; } _itemPool.releaseItem(item); } private void registerItemEventHandlers(TItem item) { item.RegisterCallback((p) => { onItemClickedDown(item, p); }); item.RegisterCallback((p) => { onItemClickedUp(item, p); }); } private void unregisterItemEventHandlers(TItem item) { } private void idsToItems(List itemIds, List items, bool onlyVisible) { if (onlyVisible) { items.Clear(); foreach (var id in itemIds) { var item = _items[id]; if (!item.itemVisible) continue; items.Add(_items[id]); } } else { items.Clear(); foreach (var id in itemIds) items.Add(_items[id]); } } private float getItemAreaWidth() { return _scrollView.worldBound.width - _scrollView.verticalScroller.worldBound.width; } private int getNumItemsPerRow() { return Mathf.Max(1, (int)(getItemAreaWidth() / (_imageSize.x + _itemMarginLeft))); } } } #endif