IQuestManager+QuestManager: add FillQuestsInState/FillFilterQuests buffer overloads (no-alloc hot path); remove R49 duplicate implementations. QuestGiver: cache current quest result (_cachedQuest/_cachedState/_cacheDirty) to avoid per-frame foreach in InteractPrompt; invalidate on OnEnable and Interact_Internal state changes. IDialogueService+DialogueManager: add StartDialogue(..., Action onComplete) overload; callback fires once on ForceEnd (covers both normal end and interrupt); supports chained callbacks via += accumulation. DialogueVariantPreviewWindow: add 'Copy CSV' button in matrix section; exports all 2^N flag combinations with winner column; handles N>10 guard and CSV-safe escaping. WorldStateRegistry: add TryGetCategory(id, out category) reverse lookup for debug tools. NpcSOEditor: new CustomEditor for NpcSO showing live nameKey localization preview in Inspector (green label or warning box if Key not found). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
585 lines
23 KiB
C#
585 lines
23 KiB
C#
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using UnityEditor;
|
||
using UnityEditor.UIElements;
|
||
using UnityEngine;
|
||
using UnityEngine.UIElements;
|
||
using BaseGames.Dialogue;
|
||
|
||
namespace BaseGames.Editor.Dialogue
|
||
{
|
||
/// <summary>
|
||
/// 对话变体预览窗口。
|
||
/// 给定一个 DialogueSequenceSO,模拟世界状态标志的开关组合,
|
||
/// 实时显示各条件变体是否满足,并高亮胜出的变体。
|
||
/// 菜单:BaseGames/Dialogue/Variant Preview
|
||
/// </summary>
|
||
public class DialogueVariantPreviewWindow : EditorWindow
|
||
{
|
||
private DialogueSequenceSO _target;
|
||
private readonly HashSet<string> _enabledFlags = new(System.StringComparer.Ordinal);
|
||
private readonly List<string> _allFlags = new();
|
||
|
||
private ObjectField _targetField;
|
||
private VisualElement _flagContainer;
|
||
private VisualElement _resultContainer;
|
||
private VisualElement _matrixContainer;
|
||
|
||
private static readonly Color ColWin = new(0.20f, 0.75f, 0.35f, 1f);
|
||
private static readonly Color ColFail = new(0.55f, 0.55f, 0.55f, 1f);
|
||
private static readonly Color ColOverride = new(0.70f, 0.70f, 0.25f, 1f);
|
||
private static readonly Color ColBlocked = new(0.85f, 0.35f, 0.30f, 1f);
|
||
|
||
[MenuItem("BaseGames/Dialogue/Variant Preview")]
|
||
public static void Open()
|
||
{
|
||
var win = GetWindow<DialogueVariantPreviewWindow>("对话变体预览");
|
||
win.minSize = new Vector2(480, 400);
|
||
}
|
||
|
||
/// <summary>从外部打开并预填目标 SO。</summary>
|
||
public static void OpenWith(DialogueSequenceSO target)
|
||
{
|
||
var win = GetWindow<DialogueVariantPreviewWindow>("对话变体预览");
|
||
win.minSize = new Vector2(480, 400);
|
||
win.SetTarget(target);
|
||
}
|
||
|
||
private void CreateGUI()
|
||
{
|
||
_mockReader = new MockFlagReader(_enabledFlags);
|
||
rootVisualElement.style.paddingLeft = 10;
|
||
rootVisualElement.style.paddingRight = 10;
|
||
rootVisualElement.style.paddingTop = 10;
|
||
rootVisualElement.style.paddingBottom = 10;
|
||
|
||
// ── 标题栏 ──
|
||
var header = new Label("对话变体预览工具");
|
||
header.style.fontSize = 14;
|
||
header.style.unityFontStyleAndWeight = FontStyle.Bold;
|
||
header.style.marginBottom = 8;
|
||
rootVisualElement.Add(header);
|
||
|
||
var desc = new Label("在模拟的世界状态标志组合下,预览哪个条件变体会被选中。");
|
||
desc.style.fontSize = 11;
|
||
desc.style.opacity = 0.6f;
|
||
desc.style.marginBottom = 10;
|
||
rootVisualElement.Add(desc);
|
||
|
||
// ── 目标选择器 ──
|
||
_targetField = new ObjectField("对话序列 SO")
|
||
{
|
||
objectType = typeof(DialogueSequenceSO),
|
||
allowSceneObjects = false
|
||
};
|
||
_targetField.value = _target;
|
||
_targetField.RegisterValueChangedCallback(evt =>
|
||
{
|
||
SetTarget(evt.newValue as DialogueSequenceSO);
|
||
});
|
||
rootVisualElement.Add(_targetField);
|
||
|
||
rootVisualElement.Add(MakeDivider());
|
||
|
||
// ── 标志模拟区 ──
|
||
var flagHeader = new Label("模拟世界状态标志");
|
||
flagHeader.style.fontSize = 12;
|
||
flagHeader.style.unityFontStyleAndWeight = FontStyle.Bold;
|
||
flagHeader.style.marginBottom = 4;
|
||
rootVisualElement.Add(flagHeader);
|
||
|
||
_flagContainer = new VisualElement();
|
||
rootVisualElement.Add(_flagContainer);
|
||
|
||
rootVisualElement.Add(MakeDivider());
|
||
|
||
// ── 变体结果区 ──
|
||
var resultHeader = new Label("变体求值结果");
|
||
resultHeader.style.fontSize = 12;
|
||
resultHeader.style.unityFontStyleAndWeight = FontStyle.Bold;
|
||
resultHeader.style.marginBottom = 4;
|
||
rootVisualElement.Add(resultHeader);
|
||
|
||
var scrollView = new ScrollView(ScrollViewMode.Vertical);
|
||
scrollView.style.flexGrow = 1;
|
||
rootVisualElement.Add(scrollView);
|
||
|
||
_resultContainer = new VisualElement();
|
||
scrollView.Add(_resultContainer);
|
||
|
||
rootVisualElement.Add(MakeDivider());
|
||
|
||
// ── 矩阵分析区 ──
|
||
var matrixFoldout = new Foldout { text = "矩阵分析(所有标志组合 → 胜出变体)", value = false };
|
||
matrixFoldout.style.marginTop = 4;
|
||
rootVisualElement.Add(matrixFoldout);
|
||
|
||
_matrixContainer = new VisualElement();
|
||
matrixFoldout.Add(_matrixContainer);
|
||
|
||
var matrixBtn = new Button(() => RebuildMatrix()) { text = "矩阵分析" };
|
||
matrixBtn.style.marginBottom = 4;
|
||
matrixFoldout.Add(matrixBtn);
|
||
|
||
var csvBtn = new Button(() => ExportMatrixCsv()) { text = "复制为 CSV" };
|
||
csvBtn.style.marginBottom = 4;
|
||
matrixFoldout.Add(csvBtn);
|
||
|
||
Rebuild();
|
||
}
|
||
|
||
private void SetTarget(DialogueSequenceSO target)
|
||
{
|
||
_target = target;
|
||
_enabledFlags.Clear();
|
||
if (_targetField != null && _targetField.value != target)
|
||
_targetField.SetValueWithoutNotify(target);
|
||
Rebuild();
|
||
}
|
||
|
||
// ── 重建 ─────────────────────────────────────────────────────────────
|
||
|
||
private void Rebuild()
|
||
{
|
||
RebuildFlagToggles();
|
||
RebuildResults();
|
||
}
|
||
|
||
private void RebuildFlagToggles()
|
||
{
|
||
if (_flagContainer == null) return;
|
||
_flagContainer.Clear();
|
||
_allFlags.Clear();
|
||
|
||
if (_target == null || _target.variants == null || _target.variants.Length == 0)
|
||
{
|
||
var empty = new Label(_target == null
|
||
? "(请选择一个 DialogueSequenceSO)"
|
||
: "(该序列无条件变体,无需模拟)");
|
||
empty.style.opacity = 0.5f;
|
||
empty.style.fontSize = 11;
|
||
_flagContainer.Add(empty);
|
||
return;
|
||
}
|
||
|
||
// 收集所有变体中涉及的 Flag
|
||
var flagSet = new HashSet<string>(System.StringComparer.Ordinal);
|
||
foreach (var v in _target.variants)
|
||
{
|
||
if (v.requiredFlags != null)
|
||
foreach (var f in v.requiredFlags)
|
||
if (!string.IsNullOrEmpty(f)) flagSet.Add(f);
|
||
}
|
||
_allFlags.AddRange(flagSet.OrderBy(x => x));
|
||
|
||
if (_allFlags.Count == 0)
|
||
{
|
||
var empty = new Label("(变体未使用任何 requiredFlags)");
|
||
empty.style.opacity = 0.5f;
|
||
empty.style.fontSize = 11;
|
||
_flagContainer.Add(empty);
|
||
return;
|
||
}
|
||
|
||
// 全选 / 全不选 快速按钮
|
||
var btnRow = new VisualElement();
|
||
btnRow.style.flexDirection = FlexDirection.Row;
|
||
btnRow.style.marginBottom = 4;
|
||
|
||
var btnAll = new Button(() =>
|
||
{
|
||
foreach (var f in _allFlags) _enabledFlags.Add(f);
|
||
Rebuild();
|
||
}) { text = "全选" };
|
||
btnAll.style.fontSize = 10;
|
||
btnAll.style.height = 18;
|
||
btnRow.Add(btnAll);
|
||
|
||
var btnNone = new Button(() =>
|
||
{
|
||
_enabledFlags.Clear();
|
||
Rebuild();
|
||
}) { text = "全不选" };
|
||
btnNone.style.fontSize = 10;
|
||
btnNone.style.height = 18;
|
||
btnRow.Add(btnNone);
|
||
|
||
_flagContainer.Add(btnRow);
|
||
|
||
// 每个 Flag 对应一个 Toggle
|
||
foreach (var flag in _allFlags)
|
||
{
|
||
bool isOn = _enabledFlags.Contains(flag);
|
||
var toggle = new Toggle(flag) { value = isOn };
|
||
toggle.style.fontSize = 11;
|
||
toggle.RegisterValueChangedCallback(evt =>
|
||
{
|
||
if (evt.newValue) _enabledFlags.Add(flag);
|
||
else _enabledFlags.Remove(flag);
|
||
RebuildResults();
|
||
});
|
||
_flagContainer.Add(toggle);
|
||
}
|
||
}
|
||
|
||
private void RebuildResults()
|
||
{
|
||
if (_resultContainer == null) return;
|
||
_resultContainer.Clear();
|
||
|
||
if (_target == null)
|
||
return;
|
||
|
||
if (_target.variants == null || _target.variants.Length == 0)
|
||
{
|
||
var msg = new Label("(序列无条件变体,直接使用本序列默认台词)");
|
||
msg.style.opacity = 0.5f;
|
||
msg.style.fontSize = 11;
|
||
_resultContainer.Add(msg);
|
||
return;
|
||
}
|
||
|
||
bool winnerFound = false;
|
||
|
||
for (int i = 0; i < _target.variants.Length; i++)
|
||
{
|
||
var variant = _target.variants[i];
|
||
var row = BuildVariantRow(i, variant, winnerFound);
|
||
_resultContainer.Add(row);
|
||
|
||
if (!winnerFound && EvaluateVariant(variant))
|
||
winnerFound = true;
|
||
}
|
||
|
||
// 若无变体胜出,提示将回退到本序列默认台词
|
||
if (!winnerFound)
|
||
{
|
||
var fallback = new Label("↳ 无变体满足,将使用本序列默认台词(无变体覆盖)");
|
||
fallback.style.fontSize = 11;
|
||
fallback.style.opacity = 0.6f;
|
||
fallback.style.marginTop = 4;
|
||
_resultContainer.Add(fallback);
|
||
}
|
||
}
|
||
|
||
private VisualElement BuildVariantRow(int index, DialogueSequenceSO.ConditionalVariant variant, bool higherWon)
|
||
{
|
||
bool condMet = EvaluateVariant(variant);
|
||
bool isWinner = condMet && !higherWon;
|
||
|
||
var card = new VisualElement();
|
||
card.style.borderLeftWidth = 3;
|
||
card.style.paddingLeft = 8;
|
||
card.style.paddingRight = 8;
|
||
card.style.paddingTop = 5;
|
||
card.style.paddingBottom = 5;
|
||
card.style.marginBottom = 4;
|
||
card.style.backgroundColor = new StyleColor(new Color(0.18f, 0.18f, 0.18f, 1f));
|
||
|
||
Color borderColor;
|
||
string statusText;
|
||
Color statusColor;
|
||
|
||
if (isWinner)
|
||
{
|
||
borderColor = ColWin;
|
||
statusText = "✓ 胜出";
|
||
statusColor = ColWin;
|
||
}
|
||
else if (condMet)
|
||
{
|
||
borderColor = ColOverride;
|
||
statusText = "⏩ 被更高优先级覆盖";
|
||
statusColor = ColOverride;
|
||
}
|
||
else
|
||
{
|
||
borderColor = ColFail;
|
||
statusText = "✗ 条件不满足";
|
||
statusColor = ColFail;
|
||
}
|
||
card.style.borderLeftColor = new StyleColor(borderColor);
|
||
|
||
// 标题行
|
||
var titleRow = new VisualElement();
|
||
titleRow.style.flexDirection = FlexDirection.Row;
|
||
titleRow.style.alignItems = Align.Center;
|
||
titleRow.style.marginBottom = 3;
|
||
|
||
var idxLabel = new Label($"变体 {index}");
|
||
idxLabel.style.fontSize = 11;
|
||
idxLabel.style.flexGrow = 1;
|
||
idxLabel.style.unityFontStyleAndWeight = isWinner ? FontStyle.Bold : FontStyle.Normal;
|
||
titleRow.Add(idxLabel);
|
||
|
||
var seqName = new Label(variant.sequence != null ? variant.sequence.name : "(未设置序列)");
|
||
seqName.style.fontSize = 10;
|
||
seqName.style.opacity = 0.6f;
|
||
seqName.style.width = 160;
|
||
titleRow.Add(seqName);
|
||
|
||
var statusLabel = new Label(statusText);
|
||
statusLabel.style.fontSize = 10;
|
||
statusLabel.style.color = new StyleColor(statusColor);
|
||
statusLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
|
||
titleRow.Add(statusLabel);
|
||
card.Add(titleRow);
|
||
|
||
// 逻辑类型
|
||
var logicLabel = new Label($"逻辑:{variant.logic}");
|
||
logicLabel.style.fontSize = 10;
|
||
logicLabel.style.opacity = 0.5f;
|
||
card.Add(logicLabel);
|
||
|
||
// 条件详情
|
||
if (variant.requiredFlags != null && variant.requiredFlags.Length > 0)
|
||
{
|
||
foreach (var flag in variant.requiredFlags)
|
||
{
|
||
if (string.IsNullOrEmpty(flag)) continue;
|
||
bool flagOn = _enabledFlags.Contains(flag);
|
||
var flagRow = new VisualElement();
|
||
flagRow.style.flexDirection = FlexDirection.Row;
|
||
flagRow.style.alignItems = Align.Center;
|
||
flagRow.style.marginTop = 1;
|
||
|
||
var icon = new Label(flagOn ? "✓" : "✗");
|
||
icon.style.fontSize = 10;
|
||
icon.style.color = new StyleColor(flagOn ? ColWin : ColBlocked);
|
||
icon.style.width = 16;
|
||
flagRow.Add(icon);
|
||
|
||
var flagLabel = new Label(flag);
|
||
flagLabel.style.fontSize = 10;
|
||
flagRow.Add(flagLabel);
|
||
|
||
card.Add(flagRow);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
var noFlags = new Label("(无 requiredFlags — 无条件激活)");
|
||
noFlags.style.fontSize = 10;
|
||
noFlags.style.opacity = 0.5f;
|
||
card.Add(noFlags);
|
||
}
|
||
|
||
return card;
|
||
}
|
||
|
||
private bool EvaluateVariant(DialogueSequenceSO.ConditionalVariant variant)
|
||
{
|
||
// 使用 DialogueSequenceSO.CheckVariant 统一变体求值逻辑,避免重复实现
|
||
return _target != null && _target.CheckVariant(variant, _mockReader);
|
||
}
|
||
|
||
/// <summary>将 _enabledFlags 包装为 IWorldStateReader,供 CheckVariant 调用。</summary>
|
||
private MockFlagReader _mockReader;
|
||
|
||
private sealed class MockFlagReader : BaseGames.Core.IWorldStateReader
|
||
{
|
||
private readonly System.Collections.Generic.HashSet<string> _flags;
|
||
public MockFlagReader(System.Collections.Generic.HashSet<string> flags) => _flags = flags;
|
||
public bool HasFlag(string key) => _flags.Contains(key);
|
||
}
|
||
|
||
// ── 矩阵分析 ─────────────────────────────────────────────────────────
|
||
|
||
/// <summary>
|
||
/// 将矩阵分析结果复制为 CSV 字符串到系统剪贴板。
|
||
/// 格式:首行为标志名称列头(各列)+ "胜出变体",后续每行为一个组合及其结果。
|
||
/// N > 10 时与 <see cref="RebuildMatrix"/> 一致,提示用户先减少标志数量。
|
||
/// </summary>
|
||
private void ExportMatrixCsv()
|
||
{
|
||
if (_target == null || _target.variants == null || _target.variants.Length == 0)
|
||
{
|
||
EditorUtility.DisplayDialog("矩阵分析 CSV", "当前无可导出的变体数据,请先选择对话序列 SO。", "确定");
|
||
return;
|
||
}
|
||
|
||
var matrixFlags = _allFlags.Count > 0 ? _allFlags : new List<string>();
|
||
if (matrixFlags.Count == 0)
|
||
{
|
||
EditorUtility.DisplayDialog("矩阵分析 CSV", "变体未使用任何 requiredFlags,无数据可导出。", "确定");
|
||
return;
|
||
}
|
||
|
||
const int MaxFlags = 10;
|
||
if (matrixFlags.Count > MaxFlags)
|
||
{
|
||
EditorUtility.DisplayDialog("矩阵分析 CSV",
|
||
$"标志数量 ({matrixFlags.Count}) 超过 {MaxFlags},无法导出。\n请先在上方取消勾选不关心的标志,再点击此按钮。", "确定");
|
||
return;
|
||
}
|
||
|
||
int n = matrixFlags.Count;
|
||
int combos = 1 << n;
|
||
var sb = new System.Text.StringBuilder();
|
||
|
||
// 表头
|
||
foreach (var f in matrixFlags)
|
||
sb.Append(EscapeCsv(f)).Append(',');
|
||
sb.AppendLine("胜出变体");
|
||
|
||
// 数据行
|
||
for (int mask = 0; mask < combos; mask++)
|
||
{
|
||
var combo = new HashSet<string>(System.StringComparer.Ordinal);
|
||
for (int bit = 0; bit < n; bit++)
|
||
if ((mask & (1 << bit)) != 0) combo.Add(matrixFlags[bit]);
|
||
|
||
var mockReader = new MockFlagReader(combo);
|
||
int winner = -1;
|
||
for (int vi = 0; vi < _target.variants.Length; vi++)
|
||
if (_target.CheckVariant(_target.variants[vi], mockReader)) { winner = vi; break; }
|
||
|
||
for (int ci = 0; ci < n; ci++)
|
||
sb.Append((mask & (1 << ci)) != 0 ? "1" : "0").Append(',');
|
||
|
||
string winnerLabel = winner >= 0
|
||
? $"变体{winner}" +
|
||
(_target.variants[winner].sequence != null
|
||
? $"({_target.variants[winner].sequence.name})" : "(无序列)")
|
||
: "默认台词";
|
||
sb.AppendLine(EscapeCsv(winnerLabel));
|
||
}
|
||
|
||
EditorGUIUtility.systemCopyBuffer = sb.ToString();
|
||
Debug.Log($"[DialogueVariantPreviewWindow] 矩阵 CSV({combos} 行)已复制到剪贴板。");
|
||
ShowNotification(new GUIContent($"✓ 已复制 {combos} 行 CSV 到剪贴板"));
|
||
}
|
||
|
||
private static string EscapeCsv(string s)
|
||
{
|
||
if (string.IsNullOrEmpty(s)) return string.Empty;
|
||
if (s.Contains(',') || s.Contains('"') || s.Contains('\n'))
|
||
return '"' + s.Replace("\"", "\"\"") + '"';
|
||
return s;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 枚举全部 2^N 标志组合(N ≤ 10),以表格形式展示每种组合下胜出的变体索引。
|
||
/// N > 10 时显示提示,建议手动筛选标志后分析。
|
||
/// </summary>
|
||
private void RebuildMatrix()
|
||
{
|
||
if (_matrixContainer == null) return;
|
||
_matrixContainer.Clear();
|
||
|
||
if (_target == null || _target.variants == null || _target.variants.Length == 0)
|
||
{
|
||
_matrixContainer.Add(new Label("(无可分析的变体)") { style = { opacity = 0.5f, fontSize = 11 } });
|
||
return;
|
||
}
|
||
|
||
var matrixFlags = _allFlags.Count > 0 ? _allFlags : new List<string>();
|
||
if (matrixFlags.Count == 0)
|
||
{
|
||
_matrixContainer.Add(new Label("(变体不使用任何 requiredFlags,无需矩阵分析)") { style = { opacity = 0.5f, fontSize = 11 } });
|
||
return;
|
||
}
|
||
|
||
const int MaxFlags = 10;
|
||
if (matrixFlags.Count > MaxFlags)
|
||
{
|
||
var warn = new Label($"⚠ 标志数量 ({matrixFlags.Count}) 超过 {MaxFlags},枚举 2^N 组合代价过高。请在上方取消勾选不关心的标志后重新点击「矩阵分析」。");
|
||
warn.style.fontSize = 11;
|
||
warn.style.color = new StyleColor(new Color(0.9f, 0.7f, 0.2f));
|
||
warn.style.whiteSpace = WhiteSpace.Normal;
|
||
_matrixContainer.Add(warn);
|
||
return;
|
||
}
|
||
|
||
int n = matrixFlags.Count;
|
||
int combos = 1 << n; // 2^n
|
||
|
||
// ── 表头 ──
|
||
var headerRow = MakeMatrixRow(isHeader: true);
|
||
for (int ci = 0; ci < n; ci++)
|
||
{
|
||
var cell = MakeMatrixCell(matrixFlags[ci], isHeader: true);
|
||
cell.style.minWidth = 90;
|
||
headerRow.Add(cell);
|
||
}
|
||
headerRow.Add(MakeMatrixCell("胜出变体", isHeader: true));
|
||
_matrixContainer.Add(headerRow);
|
||
|
||
// ── 数据行 ──
|
||
for (int mask = 0; mask < combos; mask++)
|
||
{
|
||
var combo = new HashSet<string>(System.StringComparer.Ordinal);
|
||
for (int bit = 0; bit < n; bit++)
|
||
if ((mask & (1 << bit)) != 0) combo.Add(matrixFlags[bit]);
|
||
|
||
// 求胜出变体
|
||
var mockReader = new MockFlagReader(combo);
|
||
int winner = -1;
|
||
for (int vi = 0; vi < _target.variants.Length; vi++)
|
||
if (_target.CheckVariant(_target.variants[vi], mockReader)) { winner = vi; break; }
|
||
|
||
string winnerText = winner >= 0
|
||
? $"变体 {winner}" +
|
||
(_target.variants[winner].sequence != null
|
||
? $"\n({_target.variants[winner].sequence.name})"
|
||
: "(无序列)")
|
||
: "默认台词";
|
||
|
||
var dataRow = MakeMatrixRow(isHeader: false);
|
||
// 标志列
|
||
for (int ci = 0; ci < n; ci++)
|
||
{
|
||
bool on = (mask & (1 << ci)) != 0;
|
||
var cell = MakeMatrixCell(on ? "✓" : "–", isHeader: false);
|
||
cell.style.color = new StyleColor(on ? ColWin : ColFail);
|
||
cell.style.minWidth = 90;
|
||
dataRow.Add(cell);
|
||
}
|
||
// 胜出列
|
||
var winCell = MakeMatrixCell(winnerText, isHeader: false);
|
||
winCell.style.color = new StyleColor(winner >= 0 ? ColWin : new Color(0.5f, 0.5f, 0.5f));
|
||
dataRow.Add(winCell);
|
||
|
||
_matrixContainer.Add(dataRow);
|
||
}
|
||
}
|
||
|
||
private static VisualElement MakeMatrixRow(bool isHeader)
|
||
{
|
||
var row = new VisualElement();
|
||
row.style.flexDirection = FlexDirection.Row;
|
||
row.style.borderBottomWidth = 1;
|
||
row.style.borderBottomColor = new StyleColor(new Color(0.3f, 0.3f, 0.3f, 0.5f));
|
||
if (isHeader)
|
||
row.style.backgroundColor = new StyleColor(new Color(0.22f, 0.22f, 0.28f, 1f));
|
||
return row;
|
||
}
|
||
|
||
private static Label MakeMatrixCell(string text, bool isHeader)
|
||
{
|
||
var lbl = new Label(text);
|
||
lbl.style.fontSize = isHeader ? 10 : 10;
|
||
lbl.style.unityFontStyleAndWeight = isHeader ? FontStyle.Bold : FontStyle.Normal;
|
||
lbl.style.paddingLeft = 4;
|
||
lbl.style.paddingRight = 4;
|
||
lbl.style.paddingTop = 3;
|
||
lbl.style.paddingBottom = 3;
|
||
lbl.style.whiteSpace = WhiteSpace.Normal;
|
||
lbl.style.width = 80;
|
||
return lbl;
|
||
}
|
||
|
||
// ── 辅助 ─────────────────────────────────────────────────────────────
|
||
|
||
private static VisualElement MakeDivider()
|
||
{
|
||
var d = new VisualElement();
|
||
d.style.height = 1;
|
||
d.style.backgroundColor = new StyleColor(new Color(0.35f, 0.35f, 0.35f, 0.5f));
|
||
d.style.marginTop = 6;
|
||
d.style.marginBottom = 6;
|
||
return d;
|
||
}
|
||
}
|
||
}
|