599 lines
24 KiB
C#
599 lines
24 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UnityEditor;
|
|
using UnityEditor.IMGUI.Controls;
|
|
using UnityEngine;
|
|
using UnityEngine.Assertions;
|
|
|
|
namespace HeurekaGames.AssetHunterPRO.BaseTreeviewImpl.AssetTreeView
|
|
{
|
|
class AH_TreeViewWithTreeModel : TreeViewWithTreeModel<AH_TreeviewElement>
|
|
{
|
|
const float kRowHeights = 20f;
|
|
const float kToggleWidth = 18f;
|
|
|
|
AH_TreeViewSelectionInfo treeviewSelectionInfo = new AH_TreeViewSelectionInfo();
|
|
|
|
GUIContent[] guiContents_toolbarShowSelection = new GUIContent[3]
|
|
{
|
|
new GUIContent(AH_MultiColumnHeader.AssetShowMode.Unused.ToString(),"Show only assets that was NOT included in build"),
|
|
new GUIContent(AH_MultiColumnHeader.AssetShowMode.Used.ToString(),"Show only assets that WAS included in build"),
|
|
new GUIContent(AH_MultiColumnHeader.AssetShowMode.All.ToString(),"Show all assets in project")
|
|
};
|
|
|
|
// All columns
|
|
enum MyColumns
|
|
{
|
|
//Dummy,
|
|
Icon,
|
|
Name,
|
|
AssetSize,
|
|
FileSize,
|
|
UsedInBuild,
|
|
LevelUsage
|
|
}
|
|
|
|
public enum SortOption
|
|
{
|
|
AssetType,
|
|
Name,
|
|
AssetSize,
|
|
FileSize,
|
|
Used,
|
|
LevelRefs,
|
|
}
|
|
|
|
// Sort options per column
|
|
SortOption[] m_SortOptions =
|
|
{
|
|
//SortOption.Value1,
|
|
SortOption.AssetType,
|
|
SortOption.Name,
|
|
SortOption.AssetSize,
|
|
SortOption.FileSize,
|
|
SortOption.Used,
|
|
SortOption.LevelRefs
|
|
};
|
|
|
|
public static void TreeToList(TreeViewItem root, IList<TreeViewItem> result)
|
|
{
|
|
if (root == null)
|
|
throw new NullReferenceException("root");
|
|
if (result == null)
|
|
throw new NullReferenceException("result");
|
|
|
|
result.Clear();
|
|
|
|
if (root.children == null)
|
|
return;
|
|
|
|
Stack<TreeViewItem> stack = new Stack<TreeViewItem>();
|
|
for (int i = root.children.Count - 1; i >= 0; i--)
|
|
stack.Push(root.children[i]);
|
|
|
|
while (stack.Count > 0)
|
|
{
|
|
TreeViewItem current = stack.Pop();
|
|
result.Add(current);
|
|
|
|
if (current.hasChildren && current.children[0] != null)
|
|
{
|
|
for (int i = current.children.Count - 1; i >= 0; i--)
|
|
{
|
|
stack.Push(current.children[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public AH_TreeViewWithTreeModel(TreeViewState state, MultiColumnHeader multicolumnHeader, TreeModel<AH_TreeviewElement> model) : base(state, multicolumnHeader, model)
|
|
{
|
|
Assert.AreEqual(m_SortOptions.Length, Enum.GetValues(typeof(MyColumns)).Length, "Ensure number of sort options are in sync with number of MyColumns enum values");
|
|
|
|
// Custom setup
|
|
rowHeight = kRowHeights;
|
|
columnIndexForTreeFoldouts = 1;
|
|
showAlternatingRowBackgrounds = true;
|
|
showBorder = true;
|
|
customFoldoutYOffset = (kRowHeights - EditorGUIUtility.singleLineHeight) * 0.5f; // center foldout in the row since we also center content. See RowGUI
|
|
extraSpaceBeforeIconAndLabel = kToggleWidth;
|
|
multicolumnHeader.sortingChanged += OnSortingChanged;
|
|
|
|
//Make sure we show the correct columns
|
|
updateColumns();
|
|
|
|
//IF we want to start expanded one level
|
|
if (model.root.hasChildren)
|
|
SetExpanded(model.root.children[0].id, true);
|
|
|
|
Reload();
|
|
}
|
|
|
|
protected override bool RequiresSorting()
|
|
{
|
|
//Show as list if base requires sorting OR if we chose sortedList
|
|
return base.RequiresSorting() || ((AH_MultiColumnHeader)multiColumnHeader).mode == AH_MultiColumnHeader.Mode.SortedList;
|
|
}
|
|
|
|
protected override void AddChildrenRecursive(AH_TreeviewElement parent, int depth, IList<TreeViewItem> newRows)
|
|
{
|
|
AH_MultiColumnHeader.AssetShowMode showMode = ((AH_MultiColumnHeader)multiColumnHeader).ShowMode;
|
|
|
|
foreach (AH_TreeviewElement child in parent.children)
|
|
{
|
|
bool isFolder = child.hasChildren;
|
|
bool isFolderWithValidChildren = isFolder && (showMode == AH_MultiColumnHeader.AssetShowMode.All || child.HasChildrenThatMatchesState(showMode));
|
|
|
|
//TODO EXTRACT THIS TO BE ABLE TO Ignore TYPES ETC OR MAYBE THAT SHOULD BE DONE WHEN POPULATING THE FIRST TIME...maybe better
|
|
//If its a folder or an asset that matches showmode
|
|
if (isFolderWithValidChildren || child.AssetMatchesState(showMode))
|
|
{
|
|
//Add new row
|
|
var item = new TreeViewItem<AH_TreeviewElement>(child.id, depth, child.Name, child);
|
|
newRows.Add(item);
|
|
|
|
if (isFolder)
|
|
{
|
|
if (IsExpanded(child.id))
|
|
{
|
|
AddChildrenRecursive(child, depth + 1, newRows);
|
|
}
|
|
else
|
|
{
|
|
item.children = CreateChildListForCollapsedParent();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void OnGUI(Rect rect)
|
|
{
|
|
Rect treeViewRect = rect;
|
|
|
|
//If we have selection, we want to make room in the rect to show that
|
|
if (treeviewSelectionInfo.HasSelection)
|
|
treeViewRect.height -= AH_TreeViewSelectionInfo.Height;
|
|
|
|
base.OnGUI(treeViewRect);
|
|
|
|
Rect SelectionRect = new Rect(treeViewRect.x, treeViewRect.yMax, treeViewRect.width, AH_TreeViewSelectionInfo.Height);
|
|
|
|
if (treeviewSelectionInfo.HasSelection/* && ((AH_MultiColumnHeader)multiColumnHeader).ShowMode == AH_MultiColumnHeader.AssetShowMode.Unused*/)
|
|
treeviewSelectionInfo.OnGUISelectionInfo(SelectionRect);
|
|
}
|
|
// Note we We only build the visible rows, only the backend has the full tree information.
|
|
// The treeview only creates info for the row list.
|
|
protected override IList<TreeViewItem> BuildRows(TreeViewItem root)
|
|
{
|
|
var rows = base.BuildRows(root);
|
|
SortIfNeeded(root, rows);
|
|
return rows;
|
|
}
|
|
|
|
void GetAllChildren(TreeViewItem root, ref List<TreeViewItem<AH_TreeviewElement>> children)
|
|
{
|
|
if (root != null && root.hasChildren && root.children != null)
|
|
{
|
|
foreach (var child in root.children)
|
|
{
|
|
children.AddRange(root.children.Cast<TreeViewItem<AH_TreeviewElement>>());
|
|
GetAllChildren(child, ref children);
|
|
}
|
|
}
|
|
}
|
|
|
|
void OnSortingChanged(MultiColumnHeader multiColumnHeader)
|
|
{
|
|
ModelChanged();
|
|
SortIfNeeded(rootItem, GetRows());
|
|
}
|
|
|
|
void SortIfNeeded(TreeViewItem root, IList<TreeViewItem> rows)
|
|
{
|
|
if (rows.Count <= 1)
|
|
return;
|
|
|
|
if (multiColumnHeader.sortedColumnIndex == -1)
|
|
{
|
|
return; // No column to sort for (just use the order the data are in)
|
|
}
|
|
|
|
SortByMultipleColumns();
|
|
TreeToList(root, rows);
|
|
|
|
Repaint();
|
|
}
|
|
|
|
private void deselectItems()
|
|
{
|
|
treeviewSelectionInfo.Reset();
|
|
IList<int> emptyList = new List<int>();
|
|
this.SetSelection(emptyList);
|
|
}
|
|
|
|
void SortByMultipleColumns()
|
|
{
|
|
var sortedColumns = multiColumnHeader.state.sortedColumns;
|
|
|
|
if (sortedColumns.Length == 0)
|
|
return;
|
|
|
|
var myTypes = rootItem.children.Cast<TreeViewItem<AH_TreeviewElement>>();
|
|
|
|
//Try to visualize to user that we are getting asset sizes
|
|
if (AH_SettingsManager.Instance.EstimateAssetSize)
|
|
{
|
|
int counter = 0;
|
|
int typeCount = myTypes.Count();
|
|
|
|
foreach (var item in myTypes)
|
|
{
|
|
counter++;
|
|
|
|
if (item == null)
|
|
continue;
|
|
|
|
EditorUtility.DisplayProgressBar($"Getting asset sizes", $"{ counter} / {typeCount} : {item.data.Name} : {item.data.AssetSizeStringRepresentation}", (float)counter / (float)typeCount);
|
|
}
|
|
EditorUtility.ClearProgressBar();
|
|
}
|
|
|
|
var orderedQuery = InitialOrder(myTypes, sortedColumns);
|
|
for (int i = 1; i < sortedColumns.Length; i++)
|
|
{
|
|
SortOption sortOption = m_SortOptions[sortedColumns[i]];
|
|
bool ascending = multiColumnHeader.IsSortedAscending(sortedColumns[i]);
|
|
|
|
switch (sortOption)
|
|
{
|
|
case SortOption.Name:
|
|
orderedQuery = orderedQuery.ThenBy(l => l.data.Name, ascending);
|
|
break;
|
|
case SortOption.AssetSize:
|
|
orderedQuery = orderedQuery.ThenBy(l => l.data.AssetSize, ascending);
|
|
break;
|
|
case SortOption.FileSize:
|
|
orderedQuery = orderedQuery.ThenBy(l => l.data.FileSize, ascending);
|
|
break;
|
|
case SortOption.AssetType:
|
|
//Make sure that type is not null, if it is null then its a folder and we sort by path
|
|
orderedQuery = orderedQuery.ThenBy(l => ((l.data.AssetType != null) ? l.data.AssetType.ToString() : l.data.RelativePath), ascending);//.ThenBy(x => x.Pet != null ? x.Pet.Name : "");
|
|
break;
|
|
case SortOption.LevelRefs:
|
|
orderedQuery = orderedQuery.ThenBy(l => l.data.SceneRefCount, ascending);
|
|
break;
|
|
case SortOption.Used:
|
|
orderedQuery = orderedQuery.ThenBy(l => l.data.UsedInBuild, ascending);
|
|
break;
|
|
default:
|
|
Assert.IsTrue(false, "Unhandled enum");
|
|
break;
|
|
}
|
|
}
|
|
rootItem.children = orderedQuery.Cast<TreeViewItem>().ToList();
|
|
}
|
|
|
|
internal long GetCombinedUnusedSize()
|
|
{
|
|
return treeModel.root.GetFileSizeRecursively(AH_MultiColumnHeader.AssetShowMode.Unused);
|
|
}
|
|
|
|
internal void DrawDeleteAllButton(GUIContent content, GUIStyle style, params GUILayoutOption[] layout)
|
|
{
|
|
treeviewSelectionInfo.DrawDeleteFolderButton(content, treeModel.root, style, content.tooltip, "This will delete ALL assets not used in last build. Its recommended to review the unused asset list and to backup before proceding", layout);
|
|
}
|
|
|
|
internal void ShowTreeMode()
|
|
{
|
|
searchString = "";
|
|
multiColumnHeader.sortedColumnIndex = -1;
|
|
((AH_MultiColumnHeader)multiColumnHeader).mode = AH_MultiColumnHeader.Mode.Treeview;
|
|
|
|
//Make sure we dont cause an endless loop
|
|
ModelChanged();
|
|
}
|
|
|
|
//Check if it matches state and searchstring
|
|
protected override bool IsValidElement(TreeElement element, string searchString)
|
|
{
|
|
AH_TreeviewElement ahElement = (element as AH_TreeviewElement);
|
|
AH_MultiColumnHeader.AssetShowMode hShowMode = ((AH_MultiColumnHeader)multiColumnHeader).ShowMode;
|
|
|
|
bool validAsset = !ahElement.IsFolder && ahElement.AssetMatchesState(hShowMode);
|
|
|
|
//Only show folders if we are in treeview mode and the folder has children that matches state
|
|
bool validFolder = searchString == "" && (ahElement.IsFolder && (((AH_MultiColumnHeader)multiColumnHeader).mode == AH_MultiColumnHeader.Mode.Treeview) && ahElement.HasChildrenThatMatchesState(hShowMode));
|
|
|
|
return (validAsset || validFolder) && base.IsValidElement(element, searchString);
|
|
}
|
|
|
|
internal void AssetSelectionToolBarGUI()
|
|
{
|
|
EditorGUI.BeginChangeCheck();
|
|
((AH_MultiColumnHeader)multiColumnHeader).ShowMode = (AH_MultiColumnHeader.AssetShowMode)GUILayout.Toolbar((int)((AH_MultiColumnHeader)multiColumnHeader).ShowMode, guiContents_toolbarShowSelection/*, GUILayout.MaxWidth(AH_SettingsManager.Instance.ShowMenuText ? 290 : 128)*/);
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
showModeChanged();
|
|
}
|
|
}
|
|
|
|
private void showModeChanged()
|
|
{
|
|
|
|
updateColumns();
|
|
|
|
deselectItems();
|
|
ModelChanged();
|
|
}
|
|
|
|
private void updateColumns()
|
|
{
|
|
#region Add or remove the "asset size" column as required
|
|
bool currentlyShowsAssetSize = (multiColumnHeader.state.visibleColumns.ToList().Contains((int)MyColumns.AssetSize));
|
|
bool couldHaveAssetSize = ((AH_MultiColumnHeader)multiColumnHeader).ShowMode != AH_MultiColumnHeader.AssetShowMode.Unused;
|
|
if (AH_SettingsManager.Instance.EstimateAssetSize)
|
|
{
|
|
if (!currentlyShowsAssetSize && couldHaveAssetSize)
|
|
{
|
|
var tmpList = multiColumnHeader.state.visibleColumns.ToList();
|
|
tmpList.Add((int)MyColumns.AssetSize);
|
|
multiColumnHeader.state.visibleColumns = tmpList.ToArray();
|
|
Array.Sort(multiColumnHeader.state.visibleColumns);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (currentlyShowsAssetSize)
|
|
{
|
|
var tmpList = multiColumnHeader.state.visibleColumns.ToList();
|
|
tmpList.Remove((int)MyColumns.AssetSize);
|
|
multiColumnHeader.state.visibleColumns = tmpList.ToArray();
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
|
|
IOrderedEnumerable<TreeViewItem<AH_TreeviewElement>> InitialOrder(IEnumerable<TreeViewItem<AH_TreeviewElement>> myTypes, int[] history)
|
|
{
|
|
SortOption sortOption = m_SortOptions[history[0]];
|
|
bool ascending = multiColumnHeader.IsSortedAscending(history[0]);
|
|
switch (sortOption)
|
|
{
|
|
case SortOption.Name:
|
|
return myTypes.Order(l => l.data.Name, ascending);
|
|
case SortOption.AssetSize:
|
|
return myTypes.Order(l => l.data.AssetSize, ascending);
|
|
case SortOption.FileSize:
|
|
return myTypes.Order(l => l.data.FileSize, ascending);
|
|
case SortOption.AssetType:
|
|
return myTypes.Order(l => l.data.AssetTypeSerialized, ascending);
|
|
case SortOption.LevelRefs:
|
|
return myTypes.Order(l => l.data.ScenesReferencingAsset?.Count, ascending);
|
|
case SortOption.Used:
|
|
return myTypes.Order(l => l.data.UsedInBuild, ascending);
|
|
default:
|
|
Assert.IsTrue(false, "Unhandled enum");
|
|
break;
|
|
}
|
|
|
|
// default
|
|
return myTypes.Order(l => l.data.Name, ascending);
|
|
}
|
|
|
|
protected override void RowGUI(RowGUIArgs args)
|
|
{
|
|
var item = (TreeViewItem<AH_TreeviewElement>)args.item;
|
|
|
|
for (int i = 0; i < args.GetNumVisibleColumns(); ++i)
|
|
{
|
|
CellGUI(args.GetCellRect(i), item, (MyColumns)args.GetColumn(i), ref args);
|
|
}
|
|
}
|
|
|
|
void CellGUI(Rect cellRect, TreeViewItem<AH_TreeviewElement> item, MyColumns column, ref RowGUIArgs args)
|
|
{
|
|
// Center cell rect vertically (makes it easier to place controls, icons etc in the cells)
|
|
CenterRectUsingSingleLineHeight(ref cellRect);
|
|
AH_TreeviewElement element = (AH_TreeviewElement)item.data;
|
|
|
|
switch (column)
|
|
{
|
|
|
|
case MyColumns.Icon:
|
|
{
|
|
if (item.data.AssetType != null)
|
|
GUI.DrawTexture(cellRect, AH_TreeviewElement.GetIcon(item.data.AssetType), ScaleMode.ScaleToFit);
|
|
}
|
|
break;
|
|
|
|
case MyColumns.Name:
|
|
{
|
|
Rect nameRect = cellRect;
|
|
nameRect.x += GetContentIndent(item);
|
|
DefaultGUI.Label(nameRect, item.data.m_Name, args.selected, args.focused);
|
|
}
|
|
break;
|
|
|
|
case MyColumns.AssetSize:
|
|
case MyColumns.FileSize:
|
|
{
|
|
string value = "";
|
|
if (column == MyColumns.AssetSize && element.AssetSize > 0)
|
|
value = element.AssetSizeStringRepresentation;
|
|
if (column == MyColumns.FileSize && element.FileSize > 0)
|
|
value = element.FileSizeStringRepresentation;
|
|
|
|
|
|
if (element.IsFolder && column == MyColumns.FileSize/*&& !IsExpanded(element.id)*/)
|
|
{
|
|
value = "{"+AH_Utils.BytesToString(element.GetFileSizeRecursively(((AH_MultiColumnHeader)multiColumnHeader).ShowMode))+"}";
|
|
DefaultGUI.Label(cellRect, value, args.selected, args.focused);
|
|
}
|
|
else
|
|
DefaultGUI.LabelRightAligned(cellRect, value, args.selected, args.focused);
|
|
}
|
|
break;
|
|
case MyColumns.UsedInBuild:
|
|
{
|
|
if (item.data.UsedInBuild)
|
|
DefaultGUI.LabelRightAligned(cellRect, "\u2713", args.selected, args.focused);
|
|
}
|
|
break;
|
|
case MyColumns.LevelUsage:
|
|
{
|
|
if (item.data.UsedInBuild && item.data.ScenesReferencingAsset != null)
|
|
{
|
|
if (item.data.ScenesReferencingAsset.Count > 0)
|
|
{
|
|
string cellString = String.Format("Usage: {0}", item.data.ScenesReferencingAsset.Count.ToString());
|
|
if (args.selected && args.focused)
|
|
{
|
|
if (GUI.Button(cellRect, cellString))
|
|
{
|
|
UnityEngine.Object[] sceneAssets = new UnityEngine.Object[item.data.ScenesReferencingAsset.Count];
|
|
string message = "";
|
|
string path = "";
|
|
for (int i = 0; i < item.data.ScenesReferencingAsset.Count; i++)
|
|
{
|
|
path = AssetDatabase.GUIDToAssetPath(item.data.ScenesReferencingAsset[i]);
|
|
message += (path + Environment.NewLine);
|
|
sceneAssets[i] = AssetDatabase.LoadMainAssetAtPath(path);
|
|
}
|
|
Selection.objects = sceneAssets;
|
|
EditorUtility.DisplayDialog("Scenes referencing " + path, message, "OK");
|
|
}
|
|
}
|
|
else
|
|
DefaultGUI.LabelRightAligned(cellRect, cellString, args.selected, args.focused);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
protected override void SelectionChanged(IList<int> selectedIds)
|
|
{
|
|
base.SelectionChanged(selectedIds);
|
|
|
|
//Only set selection if we are in "Unused Mode"
|
|
//if (((AH_MultiColumnHeader)multiColumnHeader).ShowMode == AH_MultiColumnHeader.AssetShowMode.Unused)
|
|
treeviewSelectionInfo.SetSelection(this, selectedIds);
|
|
}
|
|
|
|
protected override bool CanMultiSelect(TreeViewItem item)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public static MultiColumnHeaderState CreateDefaultMultiColumnHeaderState(float treeViewWidth)
|
|
{
|
|
var columns = new[]
|
|
{
|
|
new MultiColumnHeaderState.Column
|
|
{
|
|
headerContent = new GUIContent(EditorGUIUtility.FindTexture("FilterByType"), "Type of asset"),
|
|
contextMenuText = "Type",
|
|
headerTextAlignment = TextAlignment.Center,
|
|
sortedAscending = true,
|
|
sortingArrowAlignment = TextAlignment.Right,
|
|
width = 30,
|
|
minWidth = 30,
|
|
maxWidth = 30,
|
|
autoResize = false,
|
|
allowToggleVisibility = true
|
|
},
|
|
new MultiColumnHeaderState.Column
|
|
{
|
|
headerContent = new GUIContent("Name"),
|
|
headerTextAlignment = TextAlignment.Left,
|
|
sortedAscending = true,
|
|
sortingArrowAlignment = TextAlignment.Center,
|
|
width = 280,
|
|
minWidth = 175,
|
|
autoResize = true,
|
|
allowToggleVisibility = false
|
|
},
|
|
new MultiColumnHeaderState.Column
|
|
{
|
|
headerContent = new GUIContent("Asset size", "Size of the asset in project"),
|
|
headerTextAlignment = TextAlignment.Left,
|
|
sortedAscending = true,
|
|
sortingArrowAlignment = TextAlignment.Center,
|
|
width = 80,
|
|
minWidth = 80,
|
|
maxWidth = 130,
|
|
autoResize = true
|
|
},
|
|
new MultiColumnHeaderState.Column
|
|
{
|
|
headerContent = new GUIContent("Disc size", "Size of the file on disc"),
|
|
headerTextAlignment = TextAlignment.Left,
|
|
sortedAscending = true,
|
|
sortingArrowAlignment = TextAlignment.Center,
|
|
width = 80,
|
|
minWidth = 80,
|
|
maxWidth = 130,
|
|
autoResize = true,
|
|
allowToggleVisibility = true
|
|
},
|
|
new MultiColumnHeaderState.Column
|
|
{
|
|
headerContent = new GUIContent("Used", "If this asset is used in build"),
|
|
headerTextAlignment = TextAlignment.Left,
|
|
sortedAscending = true,
|
|
sortingArrowAlignment = TextAlignment.Center,
|
|
width = 45,
|
|
minWidth = 30,
|
|
maxWidth = 45,
|
|
autoResize = true
|
|
},
|
|
new MultiColumnHeaderState.Column
|
|
{
|
|
headerContent = new GUIContent("Level refs", "How many scenes are using this asset"),
|
|
headerTextAlignment = TextAlignment.Left,
|
|
sortedAscending = true,
|
|
sortingArrowAlignment = TextAlignment.Center,
|
|
width = 70,
|
|
minWidth = 70,
|
|
maxWidth = 100,
|
|
autoResize = true
|
|
}
|
|
};
|
|
|
|
Assert.AreEqual(columns.Length, Enum.GetValues(typeof(MyColumns)).Length, "Number of columns should match number of enum values: You probably forgot to update one of them.");
|
|
|
|
var state = new MultiColumnHeaderState(columns);
|
|
return state;
|
|
}
|
|
}
|
|
|
|
static class MyExtensionMethods
|
|
{
|
|
public static IOrderedEnumerable<T> Order<T, TKey>(this IEnumerable<T> source, Func<T, TKey> selector, bool ascending)
|
|
{
|
|
if (ascending)
|
|
{
|
|
return source.OrderBy(selector);
|
|
}
|
|
else
|
|
{
|
|
return source.OrderByDescending(selector);
|
|
}
|
|
}
|
|
|
|
public static IOrderedEnumerable<T> ThenBy<T, TKey>(this IOrderedEnumerable<T> source, Func<T, TKey> selector, bool ascending)
|
|
{
|
|
if (ascending)
|
|
{
|
|
return source.ThenBy(selector);
|
|
}
|
|
else
|
|
{
|
|
return source.ThenByDescending(selector);
|
|
}
|
|
}
|
|
}
|
|
} |