Newer
Older
DungeonShooting / DungeonShooting_Godot / src / framework / ui / grid / UiGrid.cs
@小李xl 小李xl on 16 Jan 2024 15 KB 修复UiGrid产生的游离节点
  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. /// <summary>
  15. /// 选中Cell的时的回调, 参数为 Cell 索引
  16. /// </summary>
  17. public event Action<int> SelectEvent;
  18. public bool IsDestroyed { get; private set; }
  19.  
  20. public int SelectIndex
  21. {
  22. get => _selectIndex;
  23. set
  24. {
  25. var newIndex = Mathf.Clamp(value, -1, _cellList.Count - 1);
  26. if (_selectIndex != newIndex)
  27. {
  28. //检测新的 Cell 是否可以被选中
  29. if (newIndex >= 0)
  30. {
  31. var uiCell = _cellList[newIndex];
  32. //不能被选中, 直接跳出
  33. if (!uiCell.CanSelect())
  34. {
  35. return;
  36. }
  37. }
  38.  
  39. var prevIndex = _selectIndex;
  40. _selectIndex = newIndex;
  41.  
  42. //取消选中上一个
  43. if (prevIndex >= 0 && prevIndex < _cellList.Count)
  44. {
  45. var uiCell = _cellList[prevIndex];
  46. uiCell.OnUnSelect();
  47. }
  48.  
  49. //选中新的
  50. if (newIndex >= 0)
  51. {
  52. var uiCell = _cellList[newIndex];
  53. uiCell.OnSelect();
  54. }
  55. if (SelectEvent != null)
  56. {
  57. SelectEvent(newIndex);
  58. }
  59. }
  60. }
  61. }
  62.  
  63. /// <summary>
  64. /// 选中的 Cell 包含的数据
  65. /// </summary>
  66. public TData SelectData => _selectIndex >= 0 ? _cellList[_selectIndex].Data : default;
  67.  
  68. public bool Visible
  69. {
  70. get => GridContainer.Visible;
  71. set => GridContainer.Visible = value;
  72. }
  73.  
  74. public int Count => _cellList.Count;
  75.  
  76. public GridContainer GridContainer { get; private set; }
  77.  
  78. //模板对象
  79. private TUiCellNode _template;
  80.  
  81. //模板大小
  82. private Vector2 _size = Vector2.Zero;
  83.  
  84. //cell逻辑处理类
  85. private Type _cellType;
  86.  
  87. //当前活动的cell池
  88. private List<UiCell<TUiCellNode, TData>> _cellList = new List<UiCell<TUiCellNode, TData>>();
  89.  
  90. //当前已被回收的cell池
  91. private Stack<UiCell<TUiCellNode, TData>> _cellPool = new Stack<UiCell<TUiCellNode, TData>>();
  92.  
  93. //单个cell偏移
  94. private Vector2I _cellOffset;
  95.  
  96. //列数
  97. private int _columns;
  98.  
  99. //是否自动扩展列数
  100. private bool _autoColumns;
  101.  
  102. //选中的cell索引
  103. private int _selectIndex = -1;
  104.  
  105. public UiGrid(TUiCellNode template, Node parent, Type cellType)
  106. {
  107. GridContainer = new UiGridContainer(OnReady, OnProcess);
  108. GridContainer.Ready += OnReady;
  109. _template = template;
  110. _cellType = cellType;
  111. parent.AddChild(GridContainer);
  112. var uiInstance = _template.GetUiInstance();
  113. uiInstance.GetParent()?.RemoveChild(uiInstance);
  114. if (uiInstance is Control control)
  115. {
  116. _size = control.Size;
  117. if (control.CustomMinimumSize == Vector2.Zero)
  118. {
  119. control.CustomMinimumSize = _size;
  120. }
  121. }
  122. }
  123. public UiGrid(TUiCellNode template, Type cellType)
  124. {
  125. GridContainer = new UiGridContainer(OnReady, OnProcess);
  126. GridContainer.Ready += OnReady;
  127. _template = template;
  128. _cellType = cellType;
  129. var uiInstance = _template.GetUiInstance();
  130. uiInstance.AddSibling(GridContainer);
  131. uiInstance.GetParent().RemoveChild(uiInstance);
  132. if (uiInstance is Control control)
  133. {
  134. _size = control.Size;
  135. if (control.CustomMinimumSize == Vector2.Zero)
  136. {
  137. control.CustomMinimumSize = _size;
  138. }
  139. }
  140. }
  141.  
  142. /// <summary>
  143. /// 设置每个 Cell 之间的偏移量
  144. /// </summary>
  145. public void SetCellOffset(Vector2I offset)
  146. {
  147. _cellOffset = offset;
  148. GridContainer.AddThemeConstantOverride("h_separation", offset.X);
  149. GridContainer.AddThemeConstantOverride("v_separation", offset.Y);
  150. }
  151.  
  152. /// <summary>
  153. /// 获取每个 Cell 之间的偏移量
  154. /// </summary>
  155. public Vector2I GetCellOffset()
  156. {
  157. return _cellOffset;
  158. }
  159.  
  160. /// <summary>
  161. /// 设置列数
  162. /// </summary>
  163. public void SetColumns(int columns)
  164. {
  165. _columns = columns;
  166. GridContainer.Columns = columns;
  167. }
  168.  
  169. /// <summary>
  170. /// 获取列数
  171. /// </summary>
  172. public int GetColumns()
  173. {
  174. return GridContainer.Columns;
  175. }
  176.  
  177. /// <summary>
  178. /// 设置是否开启自动扩展列, 如果开启, 则组件会根据 GridContainer 组件所占用的宽度自动设置列数
  179. /// </summary>
  180. public void SetAutoColumns(bool flag)
  181. {
  182. if (flag != _autoColumns)
  183. {
  184. _autoColumns = flag;
  185. if (_autoColumns)
  186. {
  187. GridContainer.Resized += OnGridResized;
  188. OnGridResized();
  189. }
  190. else
  191. {
  192. GridContainer.Columns = _columns;
  193. GridContainer.Resized -= OnGridResized;
  194. }
  195. }
  196. }
  197.  
  198. /// <summary>
  199. /// 获取是否开启自动扩展列
  200. /// </summary>
  201. public bool GetAutoColumns()
  202. {
  203. return _autoColumns;
  204. }
  205.  
  206. /// <summary>
  207. /// 设置当前组件布局方式是否横向扩展, 如果为 true, 则 GridContainer 的宽度会撑满父物体
  208. /// </summary>
  209. public void SetHorizontalExpand(bool flag)
  210. {
  211. SetHorizontalExpand(GridContainer, flag);
  212. }
  213.  
  214. /// <summary>
  215. /// 获取当前组件布局方式是否横向扩展
  216. /// </summary>
  217. public bool GetHorizontalExpand()
  218. {
  219. return GetHorizontalExpand(GridContainer);
  220. }
  221.  
  222. /// <summary>
  223. /// 获取所有数据
  224. /// </summary>
  225. public TData[] GetAllData()
  226. {
  227. var array = new TData[_cellList.Count];
  228. for (var i = 0; i < _cellList.Count; i++)
  229. {
  230. array[i] = _cellList[i].Data;
  231. }
  232.  
  233. return array;
  234. }
  235.  
  236. /// <summary>
  237. /// 获取所有 Cell 对象
  238. /// </summary>
  239. public UiCell<TUiCellNode, TData>[] GetAllCell()
  240. {
  241. return _cellList.ToArray();
  242. }
  243.  
  244. /// <summary>
  245. /// 根据指定索引获取数据
  246. /// </summary>
  247. public TData GetData(int index)
  248. {
  249. if (index < 0 || index >= _cellList.Count)
  250. {
  251. return default;
  252. }
  253.  
  254. return _cellList[index].Data;
  255. }
  256.  
  257. /// <summary>
  258. /// 根据指定索引获取 Cell 对象
  259. /// </summary>
  260. public UiCell<TUiCellNode, TData> GetCell(int index)
  261. {
  262. if (index < 0 || index >= _cellList.Count)
  263. {
  264. return default;
  265. }
  266.  
  267. return _cellList[index];
  268. }
  269. /// <summary>
  270. /// 根据自定义回调查询数据
  271. /// </summary>
  272. public UiCell<TUiCellNode, TData> Find(Func<UiCell<TUiCellNode, TData>, bool> func)
  273. {
  274. foreach (var uiCell in _cellList)
  275. {
  276. if (func(uiCell))
  277. {
  278. return uiCell;
  279. }
  280. }
  281.  
  282. return null;
  283. }
  284. /// <summary>
  285. /// 遍历所有 Cell
  286. /// </summary>
  287. public void ForEach(Action<UiCell<TUiCellNode, TData>> callback)
  288. {
  289. foreach (var uiCell in _cellList)
  290. {
  291. callback(uiCell);
  292. }
  293. }
  294. /// <summary>
  295. /// 遍历所有 Cell, 回调函数返回 false 跳出循环
  296. /// </summary>
  297. public void ForEach(Func<UiCell<TUiCellNode, TData>, bool> callback)
  298. {
  299. foreach (var uiCell in _cellList)
  300. {
  301. if (!callback(uiCell))
  302. {
  303. return;
  304. }
  305. }
  306. }
  307.  
  308. /// <summary>
  309. /// 设置当前网格组件中的所有 Cell 数据, 性能较低
  310. /// </summary>
  311. public void SetDataList(TData[] array)
  312. {
  313. //取消选中
  314. SelectIndex = -1;
  315. if (array.Length > _cellList.Count)
  316. {
  317. do
  318. {
  319. var cell = GetCellInstance();
  320. GridContainer.AddChild(cell.CellNode.GetUiInstance());
  321. } while (array.Length > _cellList.Count);
  322. }
  323. else if (array.Length < _cellList.Count)
  324. {
  325. do
  326. {
  327. var cell = _cellList[_cellList.Count - 1];
  328. _cellList.RemoveAt(_cellList.Count - 1);
  329. ReclaimCellInstance(cell);
  330. } while (array.Length < _cellList.Count);
  331. }
  332.  
  333. for (var i = 0; i < _cellList.Count; i++)
  334. {
  335. var data = array[i];
  336. _cellList[i].SetData(data);
  337. }
  338. }
  339.  
  340. /// <summary>
  341. /// 添加单条 Cell 数据, select 为是否立即选中
  342. /// </summary>
  343. public void Add(TData data, bool select = false)
  344. {
  345. var cell = GetCellInstance();
  346. GridContainer.AddChild(cell.CellNode.GetUiInstance());
  347. cell.SetData(data);
  348. if (select)
  349. {
  350. SelectIndex = Count - 1;
  351. }
  352. }
  353.  
  354. /// <summary>
  355. /// 修改指定索引的位置的 Cell 数据
  356. /// </summary>
  357. public void UpdateByIndex(int index, TData data)
  358. {
  359. var uiCell = GetCell(index);
  360. if (uiCell != null)
  361. {
  362. uiCell.SetData(data);
  363. }
  364. }
  365.  
  366. /// <summary>
  367. /// 移除指定索引的 Cell
  368. /// </summary>
  369. /// <param name="index"></param>
  370. public void RemoveByIndex(int index)
  371. {
  372. if (index < 0 || index >= _cellList.Count)
  373. {
  374. return;
  375. }
  376.  
  377. if (index >= _selectIndex)
  378. {
  379. //取消选中
  380. SelectIndex = -1;
  381. }
  382.  
  383. var uiCell = _cellList[index];
  384. _cellList.RemoveAt(index);
  385. ReclaimCellInstance(uiCell);
  386. //更新后面的索引
  387. for (var i = index; i < _cellList.Count; i++)
  388. {
  389. var tempCell = _cellList[i];
  390. tempCell.SetIndex(i);
  391. }
  392. }
  393.  
  394. /// <summary>
  395. /// 移除所有 Cell
  396. /// </summary>
  397. public void RemoveAll()
  398. {
  399. //取消选中
  400. SelectIndex = -1;
  401. var uiCells = _cellList.ToArray();
  402. _cellList.Clear();
  403. foreach (var uiCell in uiCells)
  404. {
  405. ReclaimCellInstance(uiCell);
  406. }
  407. }
  408.  
  409. public void Click(int index)
  410. {
  411. if (index < 0 || index >= _cellList.Count)
  412. {
  413. return;
  414. }
  415.  
  416. _cellList[index].Click();
  417. }
  418.  
  419. /// <summary>
  420. /// 对所有已经启用的 Cell 进行排序操作, 排序时会调用 Cell 的 OnSort() 函数用于处理排序逻辑<br/>
  421. /// 注意: 排序会影响 Cell 的 Index
  422. /// </summary>
  423. public void Sort()
  424. {
  425. if (_cellList.Count <= 0)
  426. {
  427. return;
  428. }
  429.  
  430. //这里记录 SelectIndex 是让排序后 SelectIndex 指向的 Cell 不变
  431. var selectIndex = SelectIndex;
  432. var selectCell = GetCell(selectIndex);
  433. //执行排序操作
  434. _cellList.Sort((a, b) => a.OnSort(b));
  435. if (selectIndex >= 0)
  436. {
  437. selectIndex = _cellList.FindIndex(cell => cell == selectCell);
  438. }
  439.  
  440. //先移除所有节点
  441. for (var i = 0; i < _cellList.Count; i++)
  442. {
  443. GridContainer.RemoveChild(_cellList[i].CellNode.GetUiInstance());
  444. }
  445.  
  446. if (selectIndex >= 0)
  447. {
  448. _selectIndex = selectIndex;
  449. }
  450.  
  451. //以新的顺序加入GridContainer
  452. for (var i = 0; i < _cellList.Count; i++)
  453. {
  454. GridContainer.AddChild(_cellList[i].CellNode.GetUiInstance());
  455. }
  456.  
  457. //刷新Index
  458. for (var i = 0; i < _cellList.Count; i++)
  459. {
  460. var cell = _cellList[i];
  461. cell.SetIndex(i);
  462. }
  463. }
  464.  
  465. /// <summary>
  466. /// 销毁当前网格组件
  467. /// </summary>
  468. public void Destroy()
  469. {
  470. if (IsDestroyed)
  471. {
  472. return;
  473. }
  474.  
  475. IsDestroyed = true;
  476.  
  477. for (var i = 0; i < _cellList.Count; i++)
  478. {
  479. _cellList[i].Destroy();
  480. }
  481.  
  482. foreach (var uiCell in _cellPool)
  483. {
  484. uiCell.Destroy();
  485. }
  486.  
  487. _cellList = null;
  488. _cellPool = null;
  489. _template.GetUiInstance().QueueFree();
  490. GridContainer.QueueFree();
  491. }
  492.  
  493. private void OnReady()
  494. {
  495. if (_template.GetUiInstance() is Control control)
  496. {
  497. GridContainer.Position = control.Position;
  498. }
  499. }
  500.  
  501. private void OnProcess(float delta)
  502. {
  503. if (IsDestroyed || !_template.GetUiPanel().IsOpen)
  504. {
  505. return;
  506. }
  507.  
  508. //调用 cell 更新
  509. var uiCells = _cellList.ToArray();
  510. for (var i = 0; i < uiCells.Length; i++)
  511. {
  512. var item = uiCells[i];
  513. if (item.Enable)
  514. {
  515. item.Process(delta);
  516. }
  517. }
  518. }
  519.  
  520. //获取 cell 实例
  521. private UiCell<TUiCellNode, TData> GetCellInstance()
  522. {
  523. if (_cellPool.Count > 0)
  524. {
  525. var cell = _cellPool.Pop();
  526. cell.SetIndex(_cellList.Count);
  527. cell.SetEnable(true);
  528. _cellList.Add(cell);
  529. return cell;
  530. }
  531.  
  532. var uiCell = Activator.CreateInstance(_cellType) as UiCell<TUiCellNode, TData>;
  533. if (uiCell is null)
  534. {
  535. throw new Exception($"cellType 无法转为'{typeof(UiCell<TUiCellNode, TData>).FullName}'类型!");
  536. }
  537.  
  538. _cellList.Add(uiCell);
  539. uiCell.Init(this, (TUiCellNode)_template.CloneUiCell(), _cellList.Count - 1);
  540. uiCell.SetEnable(true);
  541. return uiCell;
  542. }
  543.  
  544. //回收 cell
  545. private void ReclaimCellInstance(UiCell<TUiCellNode, TData> cell)
  546. {
  547. cell.SetEnable(false);
  548. GridContainer.RemoveChild(cell.CellNode.GetUiInstance());
  549. _cellPool.Push(cell);
  550. }
  551.  
  552. private void OnGridResized()
  553. {
  554. if (_autoColumns && GridContainer != null)
  555. {
  556. var width = GridContainer.Size.X;
  557. if (width <= _size.X + _cellOffset.X)
  558. {
  559. GridContainer.Columns = 1;
  560. }
  561. else
  562. {
  563. GridContainer.Columns = Mathf.FloorToInt(width / (_size.X + _cellOffset.X));
  564. }
  565. }
  566. }
  567.  
  568. /// <summary>
  569. /// 设置Ui布局方式是否横向扩展, 如果为 true, 则 GridContainer 的宽度会撑满父物体
  570. /// </summary>
  571. private static void SetHorizontalExpand(Control control, bool flag)
  572. {
  573. if (flag)
  574. {
  575. control.LayoutMode = 1;
  576. control.AnchorsPreset = (int)Control.LayoutPreset.TopWide;
  577. control.SizeFlagsHorizontal |= Control.SizeFlags.Expand;
  578. }
  579. else if ((control.SizeFlagsHorizontal & Control.SizeFlags.Expand) != 0)
  580. {
  581. control.LayoutMode = 1;
  582. control.AnchorsPreset = (int)Control.LayoutPreset.TopLeft;
  583. control.SizeFlagsHorizontal ^= Control.SizeFlags.Expand;
  584. }
  585. }
  586.  
  587. /// <summary>
  588. /// 获取Ui布局方式是否横向扩展
  589. /// </summary>
  590. private static bool GetHorizontalExpand(Control control)
  591. {
  592. return (control.SizeFlagsHorizontal & Control.SizeFlags.Expand) != 0;
  593. }
  594. }