Newer
Older
DungeonShooting / DungeonShooting_Godot / src / framework / ui / grid / UiGrid.cs
  1.  
  2.  
  3. using System;
  4. using System.Collections.Generic;
  5. using Godot;
  6.  
  7. /// <summary>
  8. /// Ui网格组件
  9. /// </summary>
  10. /// <typeparam name="TUiCellNode">Ui节点类型</typeparam>
  11. /// <typeparam name="TData">传给Cell的数据类型</typeparam>
  12. public class UiGrid<TUiCellNode, TData> : IUiGrid where TUiCellNode : IUiCellNode
  13. {
  14. public bool IsDestroyed { get; private set; }
  15. public int SelectIndex
  16. {
  17. get => _selectIndex;
  18. set
  19. {
  20. var newIndex = Mathf.Clamp(value, -1, _cellList.Count - 1);
  21. if (_selectIndex != newIndex)
  22. {
  23. //检测新的 Cell 是否可以被选中
  24. if (newIndex >= 0)
  25. {
  26. var uiCell = _cellList[newIndex];
  27. //不能被选中, 直接跳出
  28. if (!uiCell.CanSelect())
  29. {
  30. return;
  31. }
  32. }
  33. var prevIndex = _selectIndex;
  34. _selectIndex = newIndex;
  35.  
  36. //取消选中上一个
  37. if (prevIndex >= 0 && prevIndex < _cellList.Count)
  38. {
  39. var uiCell = _cellList[prevIndex];
  40. uiCell.OnUnSelect();
  41. }
  42.  
  43. //选中新的
  44. if (newIndex >= 0)
  45. {
  46. var uiCell = _cellList[newIndex];
  47. uiCell.OnSelect();
  48. }
  49. }
  50. }
  51. }
  52.  
  53. /// <summary>
  54. /// 选中的 Cell 包含的数据
  55. /// </summary>
  56. public TData SelectData => _selectIndex >= 0 ? _cellList[_selectIndex].Data : default;
  57. public bool Visible
  58. {
  59. get => _gridContainer.Visible;
  60. set => _gridContainer.Visible = value;
  61. }
  62.  
  63. public int Count => _cellList.Count;
  64. //模板对象
  65. private TUiCellNode _template;
  66. //模板大小
  67. private Vector2 _size = Vector2.Zero;
  68. //cell逻辑处理类
  69. private Type _cellType;
  70. //当前活动的cell池
  71. private List<UiCell<TUiCellNode, TData>> _cellList = new List<UiCell<TUiCellNode, TData>>();
  72. //当前已被回收的cell池
  73. private Stack<UiCell<TUiCellNode, TData>> _cellPool = new Stack<UiCell<TUiCellNode, TData>>();
  74. //godot原生网格组件
  75. private UiGridContainer _gridContainer;
  76. //单个cell偏移
  77. private Vector2I _cellOffset;
  78. //列数
  79. private int _columns;
  80. //是否自动扩展列数
  81. private bool _autoColumns;
  82. //选中的cell索引
  83. private int _selectIndex = -1;
  84.  
  85. public UiGrid(TUiCellNode template, Type cellType)
  86. {
  87. _gridContainer = new UiGridContainer(OnReady, OnProcess);
  88. _gridContainer.Ready += OnReady;
  89. _template = template;
  90. _cellType = cellType;
  91. var uiInstance = _template.GetUiInstance();
  92. uiInstance.AddSibling(_gridContainer);
  93. uiInstance.GetParent().RemoveChild(uiInstance);
  94. if (uiInstance is Control control)
  95. {
  96. _size = control.Size;
  97. }
  98. }
  99.  
  100. /// <summary>
  101. /// 设置每个 Cell 之间的偏移量
  102. /// </summary>
  103. public void SetCellOffset(Vector2I offset)
  104. {
  105. _cellOffset = offset;
  106. _gridContainer.AddThemeConstantOverride("h_separation", offset.X);
  107. _gridContainer.AddThemeConstantOverride("v_separation", offset.Y);
  108. }
  109.  
  110. /// <summary>
  111. /// 获取每个 Cell 之间的偏移量
  112. /// </summary>
  113. public Vector2I GetCellOffset()
  114. {
  115. return _cellOffset;
  116. }
  117.  
  118. /// <summary>
  119. /// 设置列数
  120. /// </summary>
  121. public void SetColumns(int columns)
  122. {
  123. _columns = columns;
  124. _gridContainer.Columns = columns;
  125. }
  126.  
  127. /// <summary>
  128. /// 获取列数
  129. /// </summary>
  130. public int GetColumns()
  131. {
  132. return _gridContainer.Columns;
  133. }
  134.  
  135. /// <summary>
  136. /// 设置是否开启自动扩展列, 如果开启, 则组件会根据 GridContainer 组件所占用的宽度自动设置列数
  137. /// </summary>
  138. public void SetAutoColumns(bool flag)
  139. {
  140. if (flag != _autoColumns)
  141. {
  142. _autoColumns = flag;
  143. if (_autoColumns)
  144. {
  145. _gridContainer.Resized += OnGridResized;
  146. OnGridResized();
  147. }
  148. else
  149. {
  150. _gridContainer.Columns = _columns;
  151. _gridContainer.Resized -= OnGridResized;
  152. }
  153. }
  154. }
  155.  
  156. /// <summary>
  157. /// 获取是否开启自动扩展列
  158. /// </summary>
  159. public bool GetAutoColumns()
  160. {
  161. return _autoColumns;
  162. }
  163.  
  164. /// <summary>
  165. /// 设置当前组件布局方式是否横向扩展, 如果为 true, 则 GridContainer 的宽度会撑满父物体
  166. /// </summary>
  167. public void SetHorizontalExpand(bool flag)
  168. {
  169. _gridContainer.SetHorizontalExpand(flag);
  170. }
  171.  
  172. /// <summary>
  173. /// 获取当前组件布局方式是否横向扩展
  174. /// </summary>
  175. public bool GetHorizontalExpand()
  176. {
  177. return _gridContainer.GetHorizontalExpand();
  178. }
  179.  
  180. /// <summary>
  181. /// 获取所有数据
  182. /// </summary>
  183. public TData[] GetAllData()
  184. {
  185. var array = new TData[_cellList.Count];
  186. for (var i = 0; i < _cellList.Count; i++)
  187. {
  188. array[i] = _cellList[i].Data;
  189. }
  190.  
  191. return array;
  192. }
  193.  
  194. /// <summary>
  195. /// 获取所有 Cell 对象
  196. /// </summary>
  197. public UiCell<TUiCellNode, TData>[] GetAllCell()
  198. {
  199. return _cellList.ToArray();
  200. }
  201.  
  202. /// <summary>
  203. /// 根据指定索引获取数据
  204. /// </summary>
  205. public TData GetData(int index)
  206. {
  207. if (index < 0 || index >= _cellList.Count)
  208. {
  209. return default;
  210. }
  211.  
  212. return _cellList[index].Data;
  213. }
  214.  
  215. /// <summary>
  216. /// 根据指定索引获取 Cell 对象
  217. /// </summary>
  218. public UiCell<TUiCellNode, TData> GetCell(int index)
  219. {
  220. if (index < 0 || index >= _cellList.Count)
  221. {
  222. return default;
  223. }
  224.  
  225. return _cellList[index];
  226. }
  227.  
  228. /// <summary>
  229. /// 设置当前网格组件中的所有 Cell 数据, 性能较低
  230. /// </summary>
  231. public void SetDataList(TData[] array)
  232. {
  233. //取消选中
  234. SelectIndex = -1;
  235. if (array.Length > _cellList.Count)
  236. {
  237. do
  238. {
  239. var cell = GetCellInstance();
  240. _gridContainer.AddChild(cell.CellNode.GetUiInstance());
  241. } while (array.Length > _cellList.Count);
  242. }
  243. else if (array.Length < _cellList.Count)
  244. {
  245. do
  246. {
  247. var cell = _cellList[_cellList.Count - 1];
  248. _cellList.RemoveAt(_cellList.Count - 1);
  249. ReclaimCellInstance(cell);
  250. } while (array.Length < _cellList.Count);
  251. }
  252.  
  253. for (var i = 0; i < _cellList.Count; i++)
  254. {
  255. var data = array[i];
  256. _cellList[i].SetData(data);
  257. }
  258. }
  259.  
  260. /// <summary>
  261. /// 添加单条 Cell 数据
  262. /// </summary>
  263. public void Add(TData data)
  264. {
  265. //取消选中
  266. SelectIndex = -1;
  267. var cell = GetCellInstance();
  268. _gridContainer.AddChild(cell.CellNode.GetUiInstance());
  269. cell.SetData(data);
  270. }
  271.  
  272. /// <summary>
  273. /// 修改指定索引的位置的 Cell 数据
  274. /// </summary>
  275. public void UpdateByIndex(int index, TData data)
  276. {
  277. var uiCell = GetCell(index);
  278. if (uiCell != null)
  279. {
  280. uiCell.SetData(data);
  281. }
  282. }
  283.  
  284. /// <summary>
  285. /// 移除指定索引的 Cell
  286. /// </summary>
  287. /// <param name="index"></param>
  288. public void RemoveByIndex(int index)
  289. {
  290. if (index < 0 || index >= _cellList.Count)
  291. {
  292. return;
  293. }
  294.  
  295. if (index >= _selectIndex)
  296. {
  297. //取消选中
  298. SelectIndex = -1;
  299. }
  300. var uiCell = _cellList[index];
  301. _cellList.RemoveAt(index);
  302. ReclaimCellInstance(uiCell);
  303. //更新后面的索引
  304. for (var i = index; i < _cellList.Count; i++)
  305. {
  306. var tempCell = _cellList[i];
  307. tempCell.SetIndex(i);
  308. }
  309. }
  310.  
  311. /// <summary>
  312. /// 移除所有 Cell
  313. /// </summary>
  314. public void RemoveAll()
  315. {
  316. //取消选中
  317. SelectIndex = -1;
  318. var uiCells = _cellList.ToArray();
  319. foreach (var uiCell in uiCells)
  320. {
  321. ReclaimCellInstance(uiCell);
  322. }
  323. }
  324. public void Click(int index)
  325. {
  326. if (index < 0 || index >= _cellList.Count)
  327. {
  328. return;
  329. }
  330. _cellList[index].Click();
  331. }
  332.  
  333. /// <summary>
  334. /// 对所有已经启用的 Cell 进行排序操作, 排序时会调用 Cell 的 OnSort() 函数用于处理排序逻辑<br/>
  335. /// 注意: 排序会影响 Cell 的 Index
  336. /// </summary>
  337. public void Sort()
  338. {
  339. if (_cellList.Count <= 0)
  340. {
  341. return;
  342. }
  343. //这里记录 SelectIndex 是让排序后 SelectIndex 指向的 Cell 不变
  344. var selectIndex = SelectIndex;
  345. var selectCell = GetCell(selectIndex);
  346. //执行排序操作
  347. Utils.QuickSort(_cellList, (a, b) => a.OnSort(b));
  348. if (selectIndex >= 0)
  349. {
  350. selectIndex = _cellList.FindIndex(cell => cell == selectCell);
  351. }
  352. //先移除所有节点
  353. for (var i = 0; i < _cellList.Count; i++)
  354. {
  355. _gridContainer.RemoveChild(_cellList[i].CellNode.GetUiInstance());
  356. }
  357.  
  358. if (selectIndex >= 0)
  359. {
  360. _selectIndex = selectIndex;
  361. }
  362. //以新的顺序加入GridContainer
  363. for (var i = 0; i < _cellList.Count; i++)
  364. {
  365. _gridContainer.AddChild(_cellList[i].CellNode.GetUiInstance());
  366. }
  367. //刷新Index
  368. for (var i = 0; i < _cellList.Count; i++)
  369. {
  370. var cell = _cellList[i];
  371. cell.SetIndex(i);
  372. }
  373. }
  374. /// <summary>
  375. /// 销毁当前网格组件
  376. /// </summary>
  377. public void Destroy()
  378. {
  379. if (IsDestroyed)
  380. {
  381. return;
  382. }
  383.  
  384. IsDestroyed = true;
  385.  
  386. for (var i = 0; i < _cellList.Count; i++)
  387. {
  388. _cellList[i].Destroy();
  389. }
  390.  
  391. foreach (var uiCell in _cellPool)
  392. {
  393. uiCell.Destroy();
  394. }
  395.  
  396. _cellList = null;
  397. _cellPool = null;
  398. _gridContainer.QueueFree();
  399. }
  400. private void OnReady()
  401. {
  402. if (_template.GetUiInstance() is Control control)
  403. {
  404. _gridContainer.Position = control.Position;
  405. }
  406. }
  407. private void OnProcess(float delta)
  408. {
  409. if (IsDestroyed || !_template.GetUiPanel().IsOpen)
  410. {
  411. return;
  412. }
  413. //调用 cell 更新
  414. var uiCells = _cellPool.ToArray();
  415. for (var i = 0; i < uiCells.Length; i++)
  416. {
  417. var item = uiCells[i];
  418. if (item.Enable)
  419. {
  420. item.Process(delta);
  421. }
  422. }
  423. }
  424.  
  425. //获取 cell 实例
  426. private UiCell<TUiCellNode, TData> GetCellInstance()
  427. {
  428. if (_cellPool.Count > 0)
  429. {
  430. var cell = _cellPool.Pop();
  431. cell.SetIndex(_cellList.Count);
  432. cell.SetEnable(true);
  433. _cellList.Add(cell);
  434. return cell;
  435. }
  436.  
  437. var uiCell = Activator.CreateInstance(_cellType) as UiCell<TUiCellNode, TData>;
  438. if (uiCell is null)
  439. {
  440. throw new Exception($"cellType 无法转为'{typeof(UiCell<TUiCellNode, TData>).FullName}'类型!");
  441. }
  442.  
  443. _cellList.Add(uiCell);
  444. uiCell.Init(this, (TUiCellNode)_template.CloneUiCell(), _cellList.Count - 1);
  445. uiCell.SetEnable(true);
  446. return uiCell;
  447. }
  448.  
  449. //回收 cell
  450. private void ReclaimCellInstance(UiCell<TUiCellNode, TData> cell)
  451. {
  452. cell.SetEnable(false);
  453. _gridContainer.RemoveChild(cell.CellNode.GetUiInstance());
  454. _cellPool.Push(cell);
  455. }
  456.  
  457. private void OnGridResized()
  458. {
  459. if (_autoColumns && _gridContainer != null)
  460. {
  461. var width = _gridContainer.Size.X;
  462. if (width <= _size.X + _cellOffset.X)
  463. {
  464. _gridContainer.Columns = 1;
  465. }
  466. else
  467. {
  468. _gridContainer.Columns = Mathf.FloorToInt(width / (_size.X + _cellOffset.X));
  469. }
  470. }
  471. }
  472. }