297 lines
12 KiB
C#
297 lines
12 KiB
C#
/// ---------------------------------------------
|
|
/// Ultimate Character Controller
|
|
/// Copyright (c) Opsive. All Rights Reserved.
|
|
/// https://www.opsive.com
|
|
/// ---------------------------------------------
|
|
|
|
namespace Opsive.UltimateCharacterController.Editor.Controls
|
|
{
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using UnityEditor;
|
|
using UnityEditor.IMGUI.Controls;
|
|
using UnityEngine;
|
|
|
|
/// <summary>
|
|
/// Uses Unity's TreeView class to create a single column tree view that does not have any children. The elements can be reordered and searched.
|
|
/// </summary>
|
|
public class FlatTreeView<T> : TreeView where T : TreeModal
|
|
{
|
|
private const string c_GenericDragID = "ModalDragging";
|
|
|
|
private TreeModal m_TreeModal;
|
|
private List<TreeViewItem> m_Rows = new List<TreeViewItem>();
|
|
private TreeViewItem m_Root;
|
|
|
|
private Action m_TreeChange;
|
|
|
|
public TreeModal TreeModal { get { return m_TreeModal; } set { m_TreeModal = value; Reload(); } }
|
|
public Action TreeChange { get { return m_TreeChange; } set { m_TreeChange = value; } }
|
|
|
|
/// <summary>
|
|
/// Constructor for FlatTreeView.
|
|
/// </summary>
|
|
/// <param name="state">The TreeView's state.</param>
|
|
/// <param name="modal">The TreeView's data.</param>
|
|
public FlatTreeView(TreeViewState state, TreeModal modal) : base (state)
|
|
{
|
|
showBorder = true;
|
|
m_TreeModal = modal;
|
|
Reload();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates the root element of the TreeView.
|
|
/// </summary>
|
|
/// <returns>The TreeView's root element.</returns>
|
|
protected override TreeViewItem BuildRoot()
|
|
{
|
|
m_Root = new TreeViewItem(0, -1, "Root");
|
|
return m_Root;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates all of the TreeView rows.
|
|
/// </summary>
|
|
/// <param name="root">The root of the tree.</param>
|
|
/// <returns>A list of all of the TreeView rows.</returns>
|
|
protected override IList<TreeViewItem> BuildRows(TreeViewItem root)
|
|
{
|
|
m_Rows.Clear();
|
|
if (!string.IsNullOrEmpty(searchString) && root.children != null) {
|
|
// Not all of the rows are shown while searching.
|
|
Search(root, m_Rows);
|
|
} else {
|
|
// Show all of the rows.
|
|
var rowCount = m_TreeModal.GetRowCount();
|
|
for (int i = 0; i < rowCount; ++i) {
|
|
m_Rows.Add(new TreeViewItem(i, -1));
|
|
}
|
|
SetupParentsAndChildrenFromDepths(root, m_Rows);
|
|
|
|
// If the children list was null then the tree hasn't been initialized yet. At this point the tree would have been initialized so
|
|
// perform a serach if necessary while the list is initialized.
|
|
if (!string.IsNullOrEmpty(searchString)) {
|
|
m_Rows.Clear();
|
|
Search(root, m_Rows);
|
|
}
|
|
}
|
|
return m_Rows;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Searches the tree for the searchString.
|
|
/// </summary>
|
|
/// <param name="root">The root of the tree.</param>
|
|
/// <param name="result">Any found rows.</param>
|
|
private void Search(TreeViewItem root, List<TreeViewItem> result)
|
|
{
|
|
for (int i = 0; i < root.children.Count; ++i) {
|
|
if (m_TreeModal.MatchesSearch(root.children[i].id, searchString)) {
|
|
result.Add(root.children[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a custom height for the row.
|
|
/// </summary>
|
|
/// <param name="row">The row to get the custom height of.</param>
|
|
/// <param name="item">The item to get the custom height of.</param>
|
|
/// <returns>The custom height for the row.</returns>
|
|
protected override float GetCustomRowHeight(int row, TreeViewItem item)
|
|
{
|
|
var height = m_TreeModal.GetRowHeight(item, state);
|
|
// -1 indicates the model doesn't supply the height.
|
|
if (height != -1) {
|
|
return height;
|
|
}
|
|
return base.GetCustomRowHeight(row, item);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draws the row with the specified arguments.
|
|
/// </summary>
|
|
/// <param name="args">The row to draw.</param>
|
|
protected override void RowGUI(RowGUIArgs args)
|
|
{
|
|
var rowRect = args.rowRect;
|
|
rowRect.x = GetContentIndent(args.item);
|
|
m_TreeModal.RowGUI(rowRect, args.item, state);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when the TreeView changes selection.
|
|
/// </summary>
|
|
/// <param name="selectedIds">The new ids being selected.</param>
|
|
protected override void SelectionChanged(IList<int> selectedIds)
|
|
{
|
|
base.SelectionChanged(selectedIds);
|
|
RefreshCustomRowHeights();
|
|
Repaint();
|
|
|
|
// Notify those interested that there was a change - this allows the tree to be serialized.
|
|
if (m_TreeChange != null) {
|
|
m_TreeChange();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Can the TreeView have multiple selections?
|
|
/// </summary>
|
|
/// <param name="item">Can this item be part of a multiselection?</param>
|
|
/// <returns>True if the TreeView can have multiple selections.</returns>
|
|
protected override bool CanMultiSelect(TreeViewItem item)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Can the row be dragged?
|
|
/// </summary>
|
|
/// <param name="args">The row that is trying to be dragged.</param>
|
|
/// <returns>True if the row can be dragged.</returns>
|
|
protected override bool CanStartDrag(CanStartDragArgs args)
|
|
{
|
|
return !hasSearch;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Prepares the row for a drag.
|
|
/// </summary>
|
|
/// <param name="args">The row that is being dragged.</param>
|
|
protected override void SetupDragAndDrop(SetupDragAndDropArgs args)
|
|
{
|
|
DragAndDrop.PrepareStartDrag();
|
|
var draggedRows = new List<TreeViewItem>();
|
|
var rows = GetRows();
|
|
// Convert the row IDs to row items.
|
|
for (int i = 0; i < rows.Count; ++i) {
|
|
for (int j = 0; j < args.draggedItemIDs.Count; ++j) {
|
|
if (rows[i].id == args.draggedItemIDs[j]) {
|
|
draggedRows.Add(rows[i]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// Start the drag.
|
|
DragAndDrop.SetGenericData(c_GenericDragID, draggedRows);
|
|
DragAndDrop.objectReferences = new UnityEngine.Object[] { }; // Required for dragging to work.
|
|
DragAndDrop.StartDrag("Drag");
|
|
}
|
|
|
|
/// <summary>
|
|
/// The row is being dragged - handle the dragging.
|
|
/// </summary>
|
|
/// <param name="args">The row that is being dragged.</param>
|
|
/// <returns>The status of the drag.</returns>
|
|
protected override DragAndDropVisualMode HandleDragAndDrop(DragAndDropArgs args)
|
|
{
|
|
// Return early if the dragging is occurring from a different window.
|
|
var draggedRows = DragAndDrop.GetGenericData(c_GenericDragID) as List<TreeViewItem>;
|
|
if (draggedRows == null) {
|
|
return DragAndDropVisualMode.None;
|
|
}
|
|
|
|
switch (args.dragAndDropPosition) {
|
|
case DragAndDropPosition.UponItem: // Dropping on top of other items is not allowed in a flat tree.
|
|
return DragAndDropVisualMode.None;
|
|
case DragAndDropPosition.BetweenItems: // The item can be dropped in between other items.
|
|
{
|
|
if (args.performDrop) {
|
|
// Do the drop.
|
|
OnDropDraggedElementsAtIndex(draggedRows, args.insertAtIndex == -1 ? 0 : args.insertAtIndex);
|
|
}
|
|
return DragAndDropVisualMode.Move;
|
|
}
|
|
|
|
case DragAndDropPosition.OutsideItems: // The item will be dropped to the last row if it is outside the tree.
|
|
{
|
|
if (args.performDrop) {
|
|
// Do the drop.
|
|
OnDropDraggedElementsAtIndex(draggedRows, m_Root.children.Count - 1);
|
|
}
|
|
return DragAndDropVisualMode.Move;
|
|
}
|
|
default:
|
|
Debug.LogError("Unhandled enum " + args.dragAndDropPosition);
|
|
return DragAndDropVisualMode.None;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The rows specified have been dropped at the insert index.
|
|
/// </summary>
|
|
/// <param name="draggedRows">The rows that have been dropped.</param>
|
|
/// <param name="insertIndex">The index to insert the dropped rows at.</param>
|
|
public virtual void OnDropDraggedElementsAtIndex(List<TreeViewItem> draggedRows, int insertIndex)
|
|
{
|
|
// Convert the rows indicies to row ids.
|
|
var draggedElements = new List<int>();
|
|
for (int i = 0; i < draggedRows.Count; ++i) {
|
|
draggedElements.Add(draggedRows[i].id);
|
|
}
|
|
|
|
// Let the model to the drop.
|
|
var insertIDs = m_TreeModal.MoveRows(draggedElements, insertIndex);
|
|
// Update the selection.
|
|
SetSelection(insertIDs, TreeViewSelectionOptions.RevealAndFrame);
|
|
RefreshCustomRowHeights();
|
|
|
|
// Notify those interested that the tree has changed.
|
|
if (m_TreeChange != null) {
|
|
m_TreeChange();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The TreeModal class acts as the data source for the tree.
|
|
/// </summary>
|
|
[Serializable]
|
|
public abstract class TreeModal
|
|
{
|
|
protected Action m_BeforeModalChange;
|
|
protected Action m_AfterModalChange;
|
|
public Action BeforeModalChange { get { return m_BeforeModalChange; } set { m_BeforeModalChange = value; } }
|
|
public Action AfterModalChange { get { return m_AfterModalChange; } set { m_AfterModalChange = value; } }
|
|
|
|
/// <summary>
|
|
/// Returns the number of rows in the tree.
|
|
/// </summary>
|
|
/// <returns>The number of rows in the tree.</returns>
|
|
public abstract int GetRowCount();
|
|
|
|
/// <summary>
|
|
/// Returns the height of the row.
|
|
/// </summary>
|
|
/// <param name="item">The item that occupies the row with the requested height.</param>
|
|
/// <param name="state">The state of the tree.</param>
|
|
/// <returns>The height of the row.</returns>
|
|
public virtual float GetRowHeight(TreeViewItem item, TreeViewState state) { return -1; }
|
|
|
|
/// <summary>
|
|
/// Draws the GUI for the row.
|
|
/// </summary>
|
|
/// <param name="rowRect">The rect of the row being drawn.</param>
|
|
/// <param name="item">The item that occupies the row which is being drawn.</param>
|
|
/// <param name="state">The state of the tree.</param>
|
|
public abstract void RowGUI(Rect rowRect, TreeViewItem item, TreeViewState state);
|
|
|
|
/// <summary>
|
|
/// Moves the rows to the specified index.
|
|
/// </summary>
|
|
/// <param name="rows">The rows being moved.</param>
|
|
/// <param name="insertIndex">The index to insert the rows at.</param>
|
|
/// <returns>An updated list of row ids.</returns>
|
|
public abstract List<int> MoveRows(List<int> rows, int insertIndex);
|
|
|
|
/// <summary>
|
|
/// Does the specified row id match the search?
|
|
/// </summary>
|
|
/// <param name="id">The id of the row.</param>
|
|
/// <param name="searchString">The string value of the search.</param>
|
|
/// <returns>True if the row matches the search string.</returns>
|
|
public virtual bool MatchesSearch(int id, string searchString) { return false; }
|
|
}
|
|
} |