219 lines
11 KiB
C#
219 lines
11 KiB
C#
using UnityEngine;
|
|
using XericUI.XTable.Rendering.Elements;
|
|
|
|
namespace XericUI.XTable.Rendering.Component
|
|
{
|
|
public partial class XericUIActionTable
|
|
{
|
|
private static readonly Vector2 FrozenAnchor = new(0f, 1f);
|
|
|
|
private void CreateViewportRenderer()
|
|
{
|
|
if (m_ScrollRect == null || m_ScrollRect.viewport == null) return;
|
|
CreateFrozenContainers();
|
|
CreateFrozenRenderers();
|
|
}
|
|
|
|
private void CreateFrozenContainers()
|
|
{
|
|
Transform viewport = m_ScrollRect.viewport;
|
|
m_FrozenColumnsContainer = CreateFrozenContainer(viewport, "_FrozenColumns");
|
|
m_FrozenRowsContainer = CreateFrozenContainer(viewport, "_FrozenRows");
|
|
m_FrozenCornerContainer = CreateFrozenContainer(viewport, "_FrozenCorner");
|
|
}
|
|
|
|
private static RectTransform CreateFrozenContainer(Transform parent, string name)
|
|
{
|
|
var existing = parent.Find(name) as RectTransform;
|
|
if (existing != null) return existing;
|
|
var go = new GameObject(name, typeof(RectTransform));
|
|
go.hideFlags = HideFlags.DontSave | HideFlags.HideInHierarchy;
|
|
go.transform.SetParent(parent, false);
|
|
var rt = go.GetComponent<RectTransform>();
|
|
rt.anchorMin = FrozenAnchor;
|
|
rt.anchorMax = FrozenAnchor;
|
|
rt.pivot = FrozenAnchor;
|
|
return rt;
|
|
}
|
|
|
|
private void CreateFrozenRenderers()
|
|
{
|
|
if (m_FrozenColumnsContainer == null) return;
|
|
m_FrozenColumnRenderer?.DestroyAll();
|
|
m_FrozenRowRenderer?.DestroyAll();
|
|
m_FrozenCornerRenderer?.DestroyAll();
|
|
DestroyFrozenBorderRenderers();
|
|
m_FrozenColumnRenderer = CreateFrozenRenderer(m_FrozenColumnsContainer);
|
|
m_FrozenRowRenderer = CreateFrozenRenderer(m_FrozenRowsContainer);
|
|
m_FrozenCornerRenderer = CreateFrozenRenderer(m_FrozenCornerContainer);
|
|
}
|
|
|
|
private TableRenderer CreateFrozenRenderer(Transform parent)
|
|
{
|
|
var renderer = new TableRenderer(parent)
|
|
{
|
|
Assembler = m_Assembler,
|
|
PrefabPool = m_PrefabPool,
|
|
ActivePrefabMap = m_ActivePrefabMap,
|
|
CellPadding = CellPadding,
|
|
Config = Config,
|
|
SelectionTable = this,
|
|
GetStyleColor = (ns, path, fallback) => GetStyleColor(ns, path, fallback),
|
|
GetStyleInt = (ns, path, fallback) => GetStyleInt(ns, path, fallback),
|
|
GetStyleFontAsset = (ns, path) => GetStyleFontAsset(ns, path),
|
|
GetRowHeights = () => m_ResolvedRowHeights,
|
|
GetColWidths = () => m_ResolvedColWidths,
|
|
GetTableData = () => m_TableData,
|
|
GetDefaultStyleNamespace = () => m_DefaultStyleNamespace,
|
|
};
|
|
return renderer;
|
|
}
|
|
|
|
private void DestroyFrozenRenderers()
|
|
{
|
|
m_FrozenColumnRenderer?.DestroyAll();
|
|
m_FrozenRowRenderer?.DestroyAll();
|
|
m_FrozenCornerRenderer?.DestroyAll();
|
|
DestroyFrozenBorderRenderers();
|
|
m_FrozenColumnRenderer = null;
|
|
m_FrozenRowRenderer = null;
|
|
m_FrozenCornerRenderer = null;
|
|
DestroySelectionOverlay(ref m_FrozenColumnSelectionOverlay);
|
|
DestroySelectionOverlay(ref m_FrozenRowSelectionOverlay);
|
|
DestroySelectionOverlay(ref m_FrozenCornerSelectionOverlay);
|
|
DestroyFrozenContainer(ref m_FrozenColumnsContainer);
|
|
DestroyFrozenContainer(ref m_FrozenRowsContainer);
|
|
DestroyFrozenContainer(ref m_FrozenCornerContainer);
|
|
}
|
|
|
|
private static void DestroySelectionOverlay(ref XTableSelectionOverlay overlay)
|
|
{
|
|
if (overlay == null) return;
|
|
if (Application.isPlaying) Object.Destroy(overlay.gameObject);
|
|
else Object.DestroyImmediate(overlay.gameObject);
|
|
overlay = null;
|
|
}
|
|
|
|
private static void DestroyFrozenContainer(ref RectTransform container)
|
|
{
|
|
if (container == null) return;
|
|
if (Application.isPlaying) Object.Destroy(container.gameObject);
|
|
else Object.DestroyImmediate(container.gameObject);
|
|
container = null;
|
|
}
|
|
|
|
private void ReleaseFrozenCells()
|
|
{
|
|
m_FrozenColumnRenderer?.ReleaseAll();
|
|
m_FrozenRowRenderer?.ReleaseAll();
|
|
m_FrozenCornerRenderer?.ReleaseAll();
|
|
}
|
|
|
|
private void RenderFrozenCells(bool fullRefresh = true)
|
|
{
|
|
int rows = m_RowHeights?.Length ?? 0;
|
|
int cols = m_ColWidths?.Length ?? 0;
|
|
int freezeRows = Mathf.Clamp(m_FreezeRowCount, 0, rows);
|
|
int freezeCols = Mathf.Clamp(m_FreezeColCount, 0, cols);
|
|
if (freezeRows == 0 && freezeCols == 0)
|
|
{
|
|
DestroyFrozenRenderers();
|
|
if (m_RowHeights != null && m_ColWidths != null)
|
|
CreateOrUpdateSelectionOverlays(0, 0);
|
|
return;
|
|
}
|
|
|
|
if (m_FrozenColumnRenderer == null) CreateViewportRenderer();
|
|
if (m_FrozenColumnRenderer == null) return;
|
|
CreateOrUpdateSelectionOverlays(freezeRows, freezeCols);
|
|
CreateFrozenBackgrounds(freezeRows, freezeCols);
|
|
SyncFrozenContainers();
|
|
CalcVisibleRange(out int visibleStartRow, out int visibleEndRow, out int visibleStartCol, out int visibleEndCol);
|
|
#if UNITY_EDITOR
|
|
bool editorMode = !Application.isPlaying;
|
|
#else
|
|
bool editorMode = false;
|
|
#endif
|
|
// 固定顺序:Content 已先完成,然后冻结列、冻结行、左上交叉区。
|
|
if (freezeCols > 0)
|
|
m_FrozenColumnRenderer.Render(Mathf.Max(freezeRows, visibleStartRow), visibleEndRow,
|
|
0, freezeCols, editorMode, fullRefresh);
|
|
if (freezeRows > 0)
|
|
m_FrozenRowRenderer.Render(0, freezeRows, Mathf.Max(freezeCols, visibleStartCol), visibleEndCol,
|
|
editorMode, fullRefresh);
|
|
if (freezeRows > 0 && freezeCols > 0)
|
|
m_FrozenCornerRenderer.Render(0, freezeRows, 0, freezeCols, editorMode, fullRefresh);
|
|
}
|
|
|
|
private void CreateFrozenBackgrounds(int freezeRows, int freezeCols)
|
|
{
|
|
float freezeWidth = GetColLeftX(freezeCols);
|
|
float freezeHeight = GetRowTopY(freezeRows);
|
|
if (freezeCols > 0)
|
|
m_FrozenColumnRenderer.CreateBackground(freezeWidth, m_TotalHeight, m_DefaultStyleNamespace);
|
|
if (freezeRows > 0)
|
|
m_FrozenRowRenderer.CreateBackground(m_TotalWidth - freezeWidth, freezeHeight,
|
|
m_DefaultStyleNamespace, new Vector2(freezeWidth, 0f));
|
|
if (freezeRows > 0 && freezeCols > 0)
|
|
m_FrozenCornerRenderer.CreateBackground(freezeWidth, freezeHeight, m_DefaultStyleNamespace);
|
|
}
|
|
|
|
private void CreateOrUpdateSelectionOverlays(int freezeRows, int freezeCols)
|
|
{
|
|
int rows = m_RowHeights.Length;
|
|
int cols = m_ColWidths.Length;
|
|
m_NormalSelectionOverlay = CreateOrUpdateSelectionOverlay(ContentTransform, m_NormalSelectionOverlay, freezeRows, rows, freezeCols, cols, "_NormalSelectionOverlay");
|
|
m_FrozenColumnSelectionOverlay = CreateOrUpdateSelectionOverlay(m_FrozenColumnsContainer, m_FrozenColumnSelectionOverlay, freezeRows, rows, 0, freezeCols, "_FrozenColumnSelectionOverlay");
|
|
m_FrozenRowSelectionOverlay = CreateOrUpdateSelectionOverlay(m_FrozenRowsContainer, m_FrozenRowSelectionOverlay, 0, freezeRows, freezeCols, cols, "_FrozenRowSelectionOverlay");
|
|
m_FrozenCornerSelectionOverlay = CreateOrUpdateSelectionOverlay(m_FrozenCornerContainer, m_FrozenCornerSelectionOverlay, 0, freezeRows, 0, freezeCols, "_FrozenCornerSelectionOverlay");
|
|
}
|
|
|
|
private XTableSelectionOverlay CreateOrUpdateSelectionOverlay(Transform parent, XTableSelectionOverlay overlay,
|
|
int rowStart, int rowEnd, int colStart, int colEnd, string name)
|
|
{
|
|
// 父容器已销毁:旧引用是“假 null”,直接返回 null 让调用方清空字段,
|
|
// 避免把已销毁引用写回字段后在 RefreshSelectionOverlays 中崩溃。
|
|
if (parent == null) return null;
|
|
// Unity 的 == 能识别已销毁覆盖层的“假 null”,此处会正确重建。
|
|
if (overlay == null)
|
|
{
|
|
var go = new GameObject(name, typeof(RectTransform), typeof(CanvasRenderer), typeof(UnityEngine.UI.Image), typeof(XTableSelectionOverlay));
|
|
go.hideFlags = HideFlags.DontSave | HideFlags.HideInHierarchy;
|
|
go.transform.SetParent(parent, false);
|
|
overlay = go.GetComponent<XTableSelectionOverlay>();
|
|
}
|
|
if (overlay == null) return null;
|
|
// Overlay 必须处于当前区域最上层,才能先于边框和其他 Graphic 接收 PointerDown/Drag。
|
|
overlay.Configure(this, rowStart, rowEnd, colStart, colEnd);
|
|
overlay.transform.SetAsLastSibling();
|
|
return overlay;
|
|
}
|
|
|
|
private void RefreshSelectionOverlays()
|
|
{
|
|
// 注意:不能用 ?. —— C# 的 ?. 只检查托管 null,识别不了 Unity 已销毁对象的“假 null”;
|
|
// 对已销毁的覆盖层调用 RefreshVisual 会在 GetComponent 原生层抛 NullReferenceException。
|
|
if (m_NormalSelectionOverlay != null) m_NormalSelectionOverlay.RefreshVisual();
|
|
if (m_FrozenColumnSelectionOverlay != null) m_FrozenColumnSelectionOverlay.RefreshVisual();
|
|
if (m_FrozenRowSelectionOverlay != null) m_FrozenRowSelectionOverlay.RefreshVisual();
|
|
if (m_FrozenCornerSelectionOverlay != null) m_FrozenCornerSelectionOverlay.RefreshVisual();
|
|
}
|
|
|
|
private void SyncFrozenContainers()
|
|
{
|
|
if (m_ScrollRect == null || m_ScrollRect.content == null) return;
|
|
Vector2 contentPosition = m_ScrollRect.content.anchoredPosition;
|
|
SyncFrozenContainer(m_FrozenColumnsContainer, new Vector2(0f, contentPosition.y));
|
|
SyncFrozenContainer(m_FrozenRowsContainer, new Vector2(contentPosition.x, 0f));
|
|
SyncFrozenContainer(m_FrozenCornerContainer, Vector2.zero);
|
|
}
|
|
|
|
private void SyncFrozenContainer(RectTransform container, Vector2 position)
|
|
{
|
|
if (container == null) return;
|
|
container.anchoredPosition = position;
|
|
container.sizeDelta = new Vector2(m_TotalWidth, m_TotalHeight);
|
|
}
|
|
}
|
|
}
|