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