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