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