├── CHANGELOG.md
├── CHANGELOG.md.meta
├── Editor.meta
├── Editor
├── ProjectWindowHistory.cs
├── ProjectWindowHistory.cs.meta
├── ProjectWindowHistoryHolder.cs
├── ProjectWindowHistoryHolder.cs.meta
├── ProjectWindowHistoryManager.cs
├── ProjectWindowHistoryManager.cs.meta
├── ProjectWindowHistoryRecord.cs
├── ProjectWindowHistoryRecord.cs.meta
├── ProjectWindowHistoryView.cs
├── ProjectWindowHistoryView.cs.meta
├── ProjectWindowReflectionUtility.cs
├── ProjectWindowReflectionUtility.cs.meta
├── YujiAp.ProjectWindowHistory.Editor.asmdef
└── YujiAp.ProjectWindowHistory.Editor.asmdef.meta
├── LICENSE
├── LICENSE.meta
├── README.md
├── README.md.meta
├── package.json
└── package.json.meta
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # ChangeLog
2 |
3 | ## [1.0.0] - 2023-12-02
4 | ### first release
5 |
--------------------------------------------------------------------------------
/CHANGELOG.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: a9defd240d87d4c05a60cee0179513df
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Editor.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 928cab63c0c9344f6ae976278e3e2746
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Editor/ProjectWindowHistory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using UnityEngine;
5 |
6 | namespace ProjectWindowHistory
7 | {
8 | ///
9 | /// ProjectWindowの履歴を保持するクラス
10 | ///
11 | [Serializable]
12 | public class ProjectWindowHistory
13 | {
14 | [SerializeField] private List _records;
15 | [SerializeField] private int _currentRecordIndex;
16 |
17 | public ProjectWindowHistoryRecord CurrentRecord =>
18 | _currentRecordIndex >= 0 && _currentRecordIndex < _records.Count ? _records[_currentRecordIndex] : null;
19 |
20 | public bool CanUndo => _currentRecordIndex > 0;
21 | public bool CanRedo => _currentRecordIndex < _records.Count - 1;
22 |
23 | // 履歴レコードの最大数
24 | private const int MaxRecordCount = 50;
25 |
26 | public ProjectWindowHistory()
27 | {
28 | _records = new List(MaxRecordCount);
29 | _currentRecordIndex = -1;
30 | }
31 |
32 | ///
33 | /// 現在の状態をセット
34 | ///
35 | ///
36 | public void SetCurrentRecord(ProjectWindowHistoryRecord record)
37 | {
38 | // Redo側のレコードを削除
39 | if (_currentRecordIndex < _records.Count - 1)
40 | {
41 | _records.RemoveRange(_currentRecordIndex + 1, _records.Count - _currentRecordIndex - 1);
42 | }
43 |
44 | // レコードが最大数を超えていたら古いものから削除
45 | if (_records.Count >= MaxRecordCount)
46 | {
47 | var overCount = _records.Count - MaxRecordCount + 1;
48 | _records.RemoveRange(0, overCount);
49 | _currentRecordIndex -= overCount;
50 | }
51 |
52 | _records.Add(record);
53 | _currentRecordIndex++;
54 | }
55 |
56 | ///
57 | /// Undo操作
58 | ///
59 | ///
60 | public ProjectWindowHistoryRecord Undo()
61 | {
62 | RemoveInvalidRecords();
63 |
64 | if (!CanUndo)
65 | {
66 | return null;
67 | }
68 |
69 | _currentRecordIndex--;
70 | return CurrentRecord;
71 | }
72 |
73 | ///
74 | /// Redo操作
75 | ///
76 | ///
77 | public ProjectWindowHistoryRecord Redo()
78 | {
79 | RemoveInvalidRecords();
80 |
81 | if (!CanRedo)
82 | {
83 | return null;
84 | }
85 |
86 | _currentRecordIndex++;
87 | return CurrentRecord;
88 | }
89 |
90 | ///
91 | /// 複数回Undoをまとめて行う
92 | ///
93 | ///
94 | ///
95 | public ProjectWindowHistoryRecord UndoMultiple(int count)
96 | {
97 | RemoveInvalidRecords();
98 |
99 | for (var i = 0; i < count; i++)
100 | {
101 | if (CanUndo)
102 | {
103 | Undo();
104 | }
105 | }
106 |
107 | return CurrentRecord;
108 | }
109 |
110 | ///
111 | /// 複数回Redoをまとめて行う
112 | ///
113 | ///
114 | ///
115 | public ProjectWindowHistoryRecord RedoMultiple(int count)
116 | {
117 | RemoveInvalidRecords();
118 |
119 | for (var i = 0; i < count; i++)
120 | {
121 | if (CanRedo)
122 | {
123 | Redo();
124 | }
125 | }
126 |
127 | return CurrentRecord;
128 | }
129 |
130 | ///
131 | /// Undoレコード一覧を返す
132 | ///
133 | ///
134 | public IEnumerable GetUndoHistoryRecordList()
135 | {
136 | RemoveInvalidRecords();
137 | return CanUndo ? _records.Take(_currentRecordIndex) : new List();
138 | }
139 |
140 | ///
141 | /// Redoレコード一覧を返す
142 | ///
143 | ///
144 | public IEnumerable GetRedoHistoryRecordList()
145 | {
146 | RemoveInvalidRecords();
147 | return CanRedo ? _records.Skip(_currentRecordIndex + 1) : new List();
148 | }
149 |
150 | ///
151 | /// Invalidなレコードを削除する
152 | ///
153 | private void RemoveInvalidRecords()
154 | {
155 | for (var i = _records.Count - 1; i >= 0; i--)
156 | {
157 | var record = _records[i];
158 | if (record.IsValid())
159 | {
160 | continue;
161 | }
162 |
163 | _records.RemoveAt(i);
164 | if (i <= _currentRecordIndex)
165 | {
166 | _currentRecordIndex--;
167 | }
168 | }
169 | }
170 |
171 | ///
172 | /// 履歴をクリアする
173 | ///
174 | public void Clear()
175 | {
176 | _records.Clear();
177 | _currentRecordIndex = -1;
178 | }
179 | }
180 | }
--------------------------------------------------------------------------------
/Editor/ProjectWindowHistory.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 10bda852e25b34bcfa8ba36200ec6ccf
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Editor/ProjectWindowHistoryHolder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using UnityEditor;
5 | using UnityEngine;
6 |
7 | namespace ProjectWindowHistory
8 | {
9 | ///
10 | /// ProjectWindowとHistoryのペアを保持しておくScriptableSingleton
11 | /// アセットとして保存はしてないので、Unityエディタ再起動時には履歴情報は消える
12 | ///
13 | public class ProjectWindowHistoryHolder : ScriptableSingleton
14 | {
15 | [SerializeField] private List _saveDataList = new();
16 |
17 | public ProjectWindowHistory GetHistory(EditorWindow targetWindow)
18 | {
19 | return _saveDataList.FirstOrDefault(data => data.WindowInstanceId == targetWindow.GetInstanceID())?.History;
20 | }
21 |
22 | public void Add(EditorWindow targetWindow, ProjectWindowHistory history)
23 | {
24 | var saveData = new ProjectWindowHistorySaveData(targetWindow, history);
25 | _saveDataList.Add(saveData);
26 | }
27 | }
28 |
29 | [Serializable]
30 | public class ProjectWindowHistorySaveData
31 | {
32 | [SerializeField] private int _windowInstanceId;
33 | [SerializeField] private ProjectWindowHistory _history;
34 |
35 | public int WindowInstanceId => _windowInstanceId;
36 | public ProjectWindowHistory History => _history;
37 |
38 | public ProjectWindowHistorySaveData(EditorWindow window, ProjectWindowHistory history)
39 | {
40 | _windowInstanceId = window.GetInstanceID();
41 | _history = history;
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/Editor/ProjectWindowHistoryHolder.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e713151264734644824da386f9d8d245
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Editor/ProjectWindowHistoryManager.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using UnityEditor;
4 |
5 | namespace ProjectWindowHistory
6 | {
7 | ///
8 | /// 各ProjectWindowHistoryViewを管理するクラス
9 | ///
10 | [InitializeOnLoad]
11 | public static class ProjectWindowHistoryManager
12 | {
13 | private static readonly Dictionary _historyViews = new();
14 | private static bool _isInitialized;
15 |
16 | static ProjectWindowHistoryManager()
17 | {
18 | _isInitialized = false;
19 | EditorApplication.update += OnUpdate;
20 | }
21 |
22 | private static void AllProjectWindowUpdate()
23 | {
24 | var windows = ProjectWindowReflectionUtility.GetAllProjectWindows();
25 | foreach (var window in windows)
26 | {
27 | UpdateProjectWindow(window);
28 | }
29 | }
30 |
31 | private static void OnUpdate()
32 | {
33 | // 1フレーム目で初期化
34 | if (!_isInitialized)
35 | {
36 | AllProjectWindowUpdate();
37 | _isInitialized = true;
38 | }
39 |
40 | // 既に閉じられたProjectWindowがあればDictionaryから消す
41 | var closedPairs = _historyViews.Where(pair => pair.Key == null).ToList();
42 | foreach (var (window, view) in closedPairs)
43 | {
44 | view.Destroy();
45 | _historyViews.Remove(window);
46 | }
47 |
48 | // 最後に操作したProjectWindowを取得
49 | var lastProjectWindow = ProjectWindowReflectionUtility.GetLastProjectWindow();
50 | if (lastProjectWindow == null)
51 | {
52 | return;
53 | }
54 |
55 | UpdateProjectWindow(lastProjectWindow);
56 | }
57 |
58 | private static void UpdateProjectWindow(EditorWindow projectWindow)
59 | {
60 | // 保存している履歴を取得、なければ新規作成
61 | var history = ProjectWindowHistoryHolder.instance.GetHistory(projectWindow);
62 | if (history == null)
63 | {
64 | history = new ProjectWindowHistory();
65 | ProjectWindowHistoryHolder.instance.Add(projectWindow, history);
66 | }
67 |
68 | // 新規ProjectWindowならViewを作成してDictionaryに追加
69 | if (!_historyViews.ContainsKey(projectWindow))
70 | {
71 | var view = new ProjectWindowHistoryView(projectWindow, history);
72 | _historyViews.Add(projectWindow, view);
73 | }
74 |
75 | // Viewの更新処理を呼ぶ
76 | _historyViews[projectWindow].OnUpdate();
77 | }
78 | }
79 | }
--------------------------------------------------------------------------------
/Editor/ProjectWindowHistoryManager.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 408a1a238ecb45d5acc30166f346377e
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Editor/ProjectWindowHistoryRecord.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 | using UnityEditor;
5 | using UnityEngine;
6 | using SearchViewState = ProjectWindowHistory.ProjectWindowReflectionUtility.SearchViewState;
7 |
8 | namespace ProjectWindowHistory
9 | {
10 | ///
11 | /// ProjectWindowの履歴レコード
12 | ///
13 | [Serializable]
14 | public class ProjectWindowHistoryRecord
15 | {
16 | [SerializeField] private int[] _selectedFolderInstanceIds;
17 | [SerializeField] private string _searchedText;
18 | [SerializeField] private SearchViewState _searchViewState;
19 |
20 | public int[] SelectedFolderInstanceIDs => _selectedFolderInstanceIds;
21 | public string SearchedText => _searchedText;
22 | public SearchViewState SearchViewState => _searchViewState;
23 |
24 | public ProjectWindowHistoryRecord(int[] selectedFolderInstanceIds, string searchedText, SearchViewState searchViewState)
25 | {
26 | _selectedFolderInstanceIds = selectedFolderInstanceIds;
27 | _searchedText = searchedText;
28 | _searchViewState = searchViewState;
29 | }
30 |
31 | public void ChangeSearchViewState(SearchViewState searchViewState)
32 | {
33 | _searchViewState = searchViewState;
34 | }
35 |
36 | public bool IsValid()
37 | {
38 | // フォルダが何かしら削除されていた場合は無効にしておく
39 | return (_selectedFolderInstanceIds?.Any() ?? false)
40 | && _selectedFolderInstanceIds.All(instanceId => EditorUtility.InstanceIDToObject(instanceId) != null);
41 | }
42 |
43 | ///
44 | /// 表示用テキストを生成して返す
45 | ///
46 | ///
47 | public string ToLabelText()
48 | {
49 | // 検索文字列がない場合は選択フォルダ名
50 | if (string.IsNullOrEmpty(_searchedText))
51 | {
52 | return SelectedFolderToLabelText();
53 | }
54 |
55 | // 検索文字列がある場合は検索文字列と検索範囲
56 | var labelText = $"\"{_searchedText}\" [{_searchViewState}]";
57 |
58 | // 検索範囲が選択フォルダ内の場合は選択フォルダ名も追記
59 | if (_searchViewState == SearchViewState.SubFolders)
60 | {
61 | labelText = $"{SelectedFolderToLabelText()} : {labelText}";
62 | }
63 |
64 | return labelText;
65 |
66 | string SelectedFolderToLabelText()
67 | {
68 | const int displayFolderCountMax = 3; // 表示は最大3件
69 | var targetFolderNames = _selectedFolderInstanceIds
70 | .Take(displayFolderCountMax)
71 | .Select(id =>
72 | {
73 | var path = AssetDatabase.GetAssetPath(id);
74 | return Path.GetFileName(path);
75 | });
76 |
77 | var suffix = _selectedFolderInstanceIds.Length > displayFolderCountMax ? "+" : string.Empty;
78 | return string.Join(",", targetFolderNames) + suffix;
79 | }
80 | }
81 | }
82 | }
--------------------------------------------------------------------------------
/Editor/ProjectWindowHistoryRecord.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 2ca4524e080e4bf88756c536a78ea446
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Editor/ProjectWindowHistoryView.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using UnityEditor;
3 | using UnityEngine;
4 | using UnityEngine.UIElements;
5 | using SearchViewState = ProjectWindowHistory.ProjectWindowReflectionUtility.SearchViewState;
6 |
7 | namespace ProjectWindowHistory
8 | {
9 | ///
10 | /// ProjectWindowにUndo/Redoボタンを追加し、押下時に履歴を辿る処理を呼び出す
11 | /// 選択フォルダと検索結果の変化を検知し、履歴を追加する
12 | ///
13 | public class ProjectWindowHistoryView
14 | {
15 | private readonly EditorWindow _projectWindow;
16 | private readonly ProjectWindowHistory _history;
17 | private Button _undoButton;
18 | private Button _redoButton;
19 |
20 | private bool _isOneColumnViewMode; // 1カラムビューか
21 | private float _timeAddToHistoryForSearchedText; // 現在の検索文字列が入力完了してからの経過時間
22 | private string _lastSearchedText; // 前フレームの検索文字列
23 | private const float DurationForInputCompleted = 2f; // 入力完了と判断する秒数
24 |
25 | public ProjectWindowHistoryView(EditorWindow projectWindow, ProjectWindowHistory history)
26 | {
27 | _projectWindow = projectWindow;
28 | _history = history;
29 |
30 | CreateButton();
31 | RefreshButtons();
32 | }
33 |
34 | ///
35 | /// Undo/Redoボタンを作成する
36 | ///
37 | private void CreateButton()
38 | {
39 | const float buttonWidth = 20f;
40 |
41 | #if UNITY_2022_2_OR_NEWER
42 | // Unity2022.2以降ではSearchByImportLogTypeボタンが増えたため、その分ボタンの位置を左にずらす
43 | const float buttonMarginRight = 470f;
44 | #else
45 | const float buttonMarginRight = 440f;
46 | #endif
47 |
48 | _undoButton = new Button(Undo)
49 | {
50 | text = "<",
51 | focusable = false,
52 | style =
53 | {
54 | width = buttonWidth,
55 | position = new StyleEnum(Position.Absolute),
56 | right = buttonMarginRight + buttonWidth
57 | }
58 | };
59 | _redoButton = new Button(Redo)
60 | {
61 | text = ">",
62 | focusable = false,
63 | style =
64 | {
65 | width = buttonWidth,
66 | position = new StyleEnum(Position.Absolute),
67 | right = buttonMarginRight
68 | }
69 | };
70 |
71 | // 右クリックで履歴一覧を表示
72 | _undoButton.RegisterCallback(evt =>
73 | {
74 | if (evt.button == 1)
75 | {
76 | ShowHistoryRecordListMenu(true);
77 | }
78 | });
79 | _redoButton.RegisterCallback(evt =>
80 | {
81 | if (evt.button == 1)
82 | {
83 | ShowHistoryRecordListMenu(false);
84 | }
85 | });
86 |
87 | _projectWindow.rootVisualElement.Add(_undoButton);
88 | _projectWindow.rootVisualElement.Add(_redoButton);
89 | }
90 |
91 | private void Undo()
92 | {
93 | _isOneColumnViewMode = ProjectWindowReflectionUtility.IsOneColumnViewMode(_projectWindow);
94 | if (_isOneColumnViewMode)
95 | {
96 | return;
97 | }
98 |
99 | var record = _history.Undo();
100 | if (record != null)
101 | {
102 | ApplyHistoryRecord(record);
103 | }
104 | }
105 |
106 | private void Redo()
107 | {
108 | _isOneColumnViewMode = ProjectWindowReflectionUtility.IsOneColumnViewMode(_projectWindow);
109 | if (_isOneColumnViewMode)
110 | {
111 | return;
112 | }
113 |
114 | var record = _history.Redo();
115 | if (record != null)
116 | {
117 | ApplyHistoryRecord(record);
118 | }
119 | }
120 |
121 | ///
122 | /// 履歴情報をProjectWindowに反映する
123 | ///
124 | ///
125 | private void ApplyHistoryRecord(ProjectWindowHistoryRecord record)
126 | {
127 | ProjectWindowReflectionUtility.SetFolderSelection(_projectWindow, record.SelectedFolderInstanceIDs);
128 | ProjectWindowReflectionUtility.SetSearch(_projectWindow, record.SearchedText, record.SelectedFolderInstanceIDs);
129 | if (record.SearchViewState > 0)
130 | {
131 | ProjectWindowReflectionUtility.SetSearchViewState(_projectWindow, record.SearchViewState);
132 | }
133 |
134 | RefreshButtons();
135 | }
136 |
137 | ///
138 | /// Undo/Redoボタンの状態を更新する
139 | ///
140 | private void RefreshButtons()
141 | {
142 | _undoButton.SetEnabled(!_isOneColumnViewMode && _history.CanUndo);
143 | _redoButton.SetEnabled(!_isOneColumnViewMode && _history.CanRedo);
144 | }
145 |
146 | public void OnUpdate()
147 | {
148 | // 1カラムビューかを取得
149 | _isOneColumnViewMode = ProjectWindowReflectionUtility.IsOneColumnViewMode(_projectWindow);
150 | if (_isOneColumnViewMode)
151 | {
152 | // 現状1カラムビューは未対応なので、ボタン状態の更新だけして終了
153 | RefreshButtons();
154 | return;
155 | }
156 |
157 | CheckSearchedText();
158 | CheckSelectedFolder();
159 | }
160 |
161 | ///
162 | /// 検索文字列を履歴に追加するかチェック
163 | ///
164 | private void CheckSearchedText()
165 | {
166 | var searchedText = ProjectWindowReflectionUtility.GetSearchedText(_projectWindow); // 現在の検索文字列
167 | var realtimeSinceStartup = Time.realtimeSinceStartup;
168 |
169 | // 検索文字列の入力完了からの経過時間を更新
170 | if (searchedText != _lastSearchedText)
171 | {
172 | _timeAddToHistoryForSearchedText = realtimeSinceStartup + DurationForInputCompleted;
173 | _lastSearchedText = searchedText;
174 | }
175 |
176 | // 入力完了からの経過時間が閾値を超えていなければ、入力中と判断して何もしない
177 | if (realtimeSinceStartup < _timeAddToHistoryForSearchedText)
178 | {
179 | return;
180 | }
181 |
182 | // 検索文字列が空なら何もしない
183 | if (string.IsNullOrEmpty(searchedText))
184 | {
185 | return;
186 | }
187 |
188 | // 検索範囲が指定されていない(SearchViewState.NotSearching)なら何もしない
189 | var searchViewState = ProjectWindowReflectionUtility.GetSearchViewState(_projectWindow);
190 | if (searchViewState == SearchViewState.NotSearching)
191 | {
192 | return;
193 | }
194 |
195 | var currentRecord = _history.CurrentRecord;
196 |
197 | // 検索文字列が最新履歴と変わったら履歴に追加
198 | var isSearchedTextChanged = searchedText != currentRecord?.SearchedText;
199 | if (isSearchedTextChanged)
200 | {
201 | var record = new ProjectWindowHistoryRecord(currentRecord?.SelectedFolderInstanceIDs, searchedText, searchViewState);
202 | _history.SetCurrentRecord(record);
203 | RefreshButtons();
204 | }
205 |
206 | // 検索範囲が変わっただけなら、最新履歴の検索範囲を更新
207 | var isSearchViewStateChanged = searchViewState != currentRecord?.SearchViewState;
208 | if (isSearchViewStateChanged)
209 | {
210 | currentRecord?.ChangeSearchViewState(searchViewState);
211 | }
212 | }
213 |
214 | ///
215 | /// 選択フォルダを履歴に追加するかチェック
216 | ///
217 | private void CheckSelectedFolder()
218 | {
219 | var selectedFolderInstanceIds = ProjectWindowReflectionUtility.GetLastFolderInstanceIds(_projectWindow);
220 | var isFolderSelected = selectedFolderInstanceIds != null && selectedFolderInstanceIds.Any();
221 |
222 | // ツリービューでフォルダが選択されていなければ何もしない
223 | if (!isFolderSelected)
224 | {
225 | return;
226 | }
227 |
228 | selectedFolderInstanceIds = selectedFolderInstanceIds
229 | .Where(instanceId => AssetDatabase.IsValidFolder(AssetDatabase.GetAssetPath(instanceId)))
230 | .ToArray();
231 | var lastRecord = _history.CurrentRecord;
232 | var lastSelectedFolderInstanceIds = lastRecord?.SelectedFolderInstanceIDs;
233 | var isFirstFolderSelected = lastSelectedFolderInstanceIds == null;
234 |
235 | // 初めてフォルダを選択した、もしくは選択フォルダが最新履歴と変わったら履歴に追加
236 | if (isFirstFolderSelected || !selectedFolderInstanceIds.SequenceEqual(lastSelectedFolderInstanceIds))
237 | {
238 | // 検索範囲が選択フォルダ内なら検索を維持しつつフォルダ選択され、それ以外の検索範囲なら検索はリセットされる
239 | var isSearchedSubFolders = lastRecord?.SearchViewState == SearchViewState.SubFolders;
240 | var searchedText = isSearchedSubFolders ? lastRecord.SearchedText : null;
241 | var searchViewState = isSearchedSubFolders ? SearchViewState.SubFolders : SearchViewState.NotSearching;
242 |
243 | // 履歴に追加する
244 | var record = new ProjectWindowHistoryRecord(selectedFolderInstanceIds, searchedText, searchViewState);
245 | _history.SetCurrentRecord(record);
246 | RefreshButtons();
247 | }
248 | }
249 |
250 | ///
251 | /// 履歴一覧を表示
252 | ///
253 | ///
254 | private void ShowHistoryRecordListMenu(bool isUndo)
255 | {
256 | var menu = new GenericMenu();
257 | var recordList = isUndo ? _history.GetUndoHistoryRecordList().Reverse().ToList() : _history.GetRedoHistoryRecordList().ToList();
258 | for (var i = 0; i < recordList.Count; i++)
259 | {
260 | var labelText = recordList[i].ToLabelText();
261 |
262 | var operationCount = i + 1;
263 | menu.AddItem(new GUIContent(labelText), false, () =>
264 | {
265 | var record = isUndo ? _history.UndoMultiple(operationCount) : _history.RedoMultiple(operationCount);
266 | ApplyHistoryRecord(record);
267 | });
268 | }
269 |
270 | menu.AddSeparator("");
271 | menu.AddItem(new GUIContent("UndoRedo履歴を全削除"), false, () =>
272 | {
273 | _history.Clear();
274 | RefreshButtons();
275 | });
276 |
277 | menu.ShowAsContext();
278 | }
279 |
280 | public void Destroy()
281 | {
282 | _undoButton.RemoveFromHierarchy();
283 | _redoButton.RemoveFromHierarchy();
284 | }
285 | }
286 | }
--------------------------------------------------------------------------------
/Editor/ProjectWindowHistoryView.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: c853a699abc3142bea2f0aef99eddadd
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Editor/ProjectWindowReflectionUtility.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection;
5 | using UnityEditor;
6 |
7 | namespace ProjectWindowHistory
8 | {
9 | ///
10 | /// ProjectWindowのリフレクションUtilityクラス
11 | ///
12 | public static class ProjectWindowReflectionUtility
13 | {
14 | // ====== ProjectBrowserの型情報 ======
15 | private static Type _projectBrowserType;
16 | private static Type ProjectBrowserType => _projectBrowserType ??= Type.GetType("UnityEditor.ProjectBrowser,UnityEditor");
17 |
18 | private static Type _projectBrowserListType;
19 | private static Type ProjectBrowserListType => _projectBrowserListType ??= typeof(List<>).MakeGenericType(ProjectBrowserType);
20 |
21 | // ====== ProjectBrowserのフィールド ======
22 | private static FieldInfo _lastInteractedProjectBrowserField;
23 | private static FieldInfo LastInteractedProjectBrowserField => _lastInteractedProjectBrowserField
24 | ??= ProjectBrowserType.GetField("s_LastInteractedProjectBrowser", BindingFlags.Public | BindingFlags.Static);
25 |
26 | private static FieldInfo _viewModeField;
27 | private static FieldInfo ViewModeField => _viewModeField
28 | ??= ProjectBrowserType.GetField("m_ViewMode", BindingFlags.NonPublic | BindingFlags.Instance);
29 |
30 | private static FieldInfo _lastFoldersField;
31 | private static FieldInfo LastFoldersField => _lastFoldersField
32 | ??= ProjectBrowserType.GetField("m_LastFolders", BindingFlags.NonPublic | BindingFlags.Instance);
33 |
34 | private static FieldInfo _searchFieldTextField;
35 | private static FieldInfo SearchFieldTextField => _searchFieldTextField
36 | ??= ProjectBrowserType.GetField("m_SearchFieldText", BindingFlags.NonPublic | BindingFlags.Instance);
37 |
38 | // ====== ProjectBrowserのメソッド ======
39 | private static MethodInfo _getAllProjectBrowsersMethod;
40 | private static MethodInfo GetAllProjectBrowsersMethod => _getAllProjectBrowsersMethod
41 | ??= ProjectBrowserType.GetMethod("GetAllProjectBrowsers", BindingFlags.Public | BindingFlags.Static);
42 |
43 | private static MethodInfo _getFolderInstanceIDsMethod;
44 | private static MethodInfo GetFolderInstanceIDsMethod => _getFolderInstanceIDsMethod
45 | ??= ProjectBrowserType.GetMethod("GetFolderInstanceIDs", BindingFlags.NonPublic | BindingFlags.Static);
46 |
47 | private static MethodInfo _setFolderSelectionMethod;
48 | // オーバーロードがあるので引数2つのメソッドの方を探して呼ぶ
49 | private static MethodInfo SetFolderSelectionMethod => _setFolderSelectionMethod
50 | ??= ProjectBrowserType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
51 | .FirstOrDefault(method => method.Name == "SetFolderSelection" && method.GetParameters().Length == 2);
52 |
53 | private static MethodInfo _setSearchMethod;
54 | // オーバーロードがあるのでSearchFilter型引数のメソッドの方を探して呼ぶ
55 | private static MethodInfo SetSearchMethod => _setSearchMethod
56 | ??= ProjectBrowserType.GetMethods(BindingFlags.Public | BindingFlags.Instance)
57 | .FirstOrDefault(method => method.Name == "SetSearch" && method.GetParameters().First().ParameterType == SearchFilterType);
58 |
59 | private static MethodInfo _getSearchViewStateMethod;
60 | private static MethodInfo GetSearchViewStateMethod => _getSearchViewStateMethod
61 | ??= ProjectBrowserType.GetMethod("GetSearchViewState", BindingFlags.NonPublic | BindingFlags.Instance);
62 |
63 | private static MethodInfo _setSearchViewStateMethod;
64 | private static MethodInfo SetSearchViewStateMethod => _setSearchViewStateMethod
65 | ??= ProjectBrowserType.GetMethod("SetSearchViewState", BindingFlags.NonPublic | BindingFlags.Instance);
66 |
67 | // ====== SearchFilterの型情報 ======
68 | private const string SearchFilterTypeName = "UnityEditor.SearchFilter,UnityEditor";
69 | private static Type _searchFilterType;
70 | private static Type SearchFilterType => _searchFilterType ??= Type.GetType(SearchFilterTypeName);
71 |
72 | // ====== SearchFilterのフィールド ======
73 | private static FieldInfo _searchFilterFoldersField;
74 | private static FieldInfo SearchFilterFoldersField => _searchFilterFoldersField
75 | ??= SearchFilterType.GetField("m_Folders", BindingFlags.NonPublic | BindingFlags.Instance);
76 |
77 | // ====== SearchFilterのメソッド ======
78 | private static MethodInfo _createSearchFilterFromStringMethod;
79 | private static MethodInfo CreateSearchFilterFromStringMethod => _createSearchFilterFromStringMethod
80 | ??= SearchFilterType.GetMethod("CreateSearchFilterFromString", BindingFlags.NonPublic | BindingFlags.Static);
81 |
82 | ///
83 | /// 現在エディタ上に存在する全てのProjectビューを取得する
84 | ///
85 | ///
86 | public static List GetAllProjectWindows()
87 | {
88 | var projectWindowsObject = GetAllProjectBrowsersMethod.Invoke(null, null);
89 |
90 | // 以下object型をList型に変換する処理
91 | var countProperty = ProjectBrowserListType.GetProperty("Count");
92 | var indexer = ProjectBrowserListType.GetProperty("Item");
93 |
94 | if (countProperty == null || indexer == null)
95 | {
96 | return new List();
97 | }
98 |
99 | var projectWindowCount = (int) countProperty.GetValue(projectWindowsObject, null);
100 | var projectWindows = new List();
101 | for (var i = 0; i < projectWindowCount; i++)
102 | {
103 | var projectWindow = (EditorWindow) indexer.GetValue(projectWindowsObject, new object[] { i });
104 | projectWindows.Add(projectWindow);
105 | }
106 |
107 | return projectWindows;
108 | }
109 |
110 | ///
111 | /// 開いているProjectビューを取得する
112 | ///
113 | ///
114 | public static EditorWindow GetLastProjectWindow()
115 | {
116 | return (EditorWindow) LastInteractedProjectBrowserField.GetValue(null);
117 | }
118 |
119 | ///
120 | /// ProjectWindowが1カラムビューかどうか
121 | ///
122 | ///
123 | public static bool IsOneColumnViewMode(EditorWindow targetProjectWindow)
124 | {
125 | // OneColumn=0, TwoColumns=1
126 | return ((int) ViewModeField.GetValue(targetProjectWindow)) == 0;
127 | }
128 |
129 | ///
130 | /// 左側のツリーで選択したフォルダのインスタンスIDを取得する
131 | ///
132 | ///
133 | public static int[] GetLastFolderInstanceIds(EditorWindow targetProjectWindow)
134 | {
135 | // 選択中のフォルダのパス配列を取得
136 | var lastFolderPaths = LastFoldersField.GetValue(targetProjectWindow) ?? Array.Empty