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