using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using TMPro; using UnityEngine; using XericLibrary.Runtime.Blueprint; using XericLibrary.Runtime.SuperStyleSheet; using XericLibrary.Runtime.UIGraph; namespace XericLibrary.Runtime.Blueprint.QuickGraph { [Flags] public enum QuickGraphRenderStyleChange { None = 0, NodeGeometry = 1, NodeAppearance = 2, PortLayout = 4, WireGeometry = 8, WireAppearance = 16, GridAppearance = 32, Input = 64, Lod = 128, Selection = 256, All = NodeGeometry | NodeAppearance | PortLayout | WireGeometry | WireAppearance | GridAppearance | Input | Lod | Selection } public struct QuickGraphNodeRenderStyle { public float Width, Height, Border, Chamfer, TitleFontSize, AxisScaleX, AxisScaleY; public int ChamferSegments, SideCount; public TMP_FontAsset TitleFont; public TextAlignmentOptions TitleAlignment; public bool ShowTitleFull; public Color OuterColor; } public struct QuickGraphPortRenderStyle { public float InputSide, OutputSide, TopRatio, BottomRatio; } public struct QuickGraphWireRenderStyle { public float Width, HitRadius, SourceHandle, TargetHandle, ArrowWidth, ArrowHeight, ArrowProgress, ArrowDepth; public int Tessellation, MinTessellation; public ArrowShape ArrowShape; public bool ArrowReversed, UseNodeColorGradient; public Color Color; } public struct QuickGraphGridRenderStyle { public Material Material; public string ShaderName; public float Size, OverlayPower, Threshold, Exp, LodTransitionThreshold, LodTransitionStart, LodTransitionEnd, LineShapeScale; public Color Color, Background; public bool RaycastTarget, Maskable; public Vector2 CellCenter; } public struct QuickGraphInputRenderStyle { public float ZoomDivisor, DragPanSpeed, MinZoom, MaxZoom, DefaultZoom, FocusZoom, FocusPadding, KeyboardPanSpeed, KeyboardZoomSpeed; public int PanMouseButton; } public struct QuickGraphLodRenderStyle { public float EnterZoom, ExitZoom, WireTessellationMultiplier, NodeBorder; public bool UseNodeColorAsBackground, ShowTitle; } public struct QuickGraphSelectionRenderStyle { public float DragThreshold; public Color FillColor, OutlineColor; public Vector2 OutlineOffset; public bool RaycastTarget; } public sealed class QuickGraphRenderStyleSnapshot { public int Revision; public QuickGraphRenderStyleChange Changes; public QuickGraphNodeRenderStyle Node; public QuickGraphPortRenderStyle Port; public QuickGraphWireRenderStyle Wire; public QuickGraphGridRenderStyle Grid; public QuickGraphInputRenderStyle Input; public QuickGraphLodRenderStyle MinimalLod, SimplifiedLod, FullLod; public QuickGraphSelectionRenderStyle Selection; public QuickGraphLodRenderStyle GetLod(BlueprintLodLevel level) => level == BlueprintLodLevel.Minimal ? MinimalLod : level == BlueprintLodLevel.Simplified ? SimplifiedLod : FullLod; } public sealed class QuickGraphRenderStyleResolver : IDisposable { private readonly BlueprintGraph _graph; private static readonly ConditionalWeakTable _validationStates = new ConditionalWeakTable(); private sealed class ValidationState { public string LastError; } public QuickGraphRenderStyleSnapshot Snapshot { get; private set; } public event Action Changed; public QuickGraphRenderStyleResolver(BlueprintGraph graph) { _graph = graph; _graph.RenderStyleChanged += Refresh; StyleManager.OnStylesReloaded += Refresh; Refresh(); } private T Value(string path) => _graph.GetRenderStyleValue(path); private QuickGraphLodRenderStyle Lod(string name) => new QuickGraphLodRenderStyle { EnterZoom = Value(QuickGraphStylePaths.Lod(name, "enterZoom")), ExitZoom = Value(QuickGraphStylePaths.Lod(name, "exitZoom")), WireTessellationMultiplier = Value(QuickGraphStylePaths.Lod(name, "wireTessellationMultiplier")), NodeBorder = Value(QuickGraphStylePaths.Lod(name, "nodeBorder")), UseNodeColorAsBackground = Value(QuickGraphStylePaths.Lod(name, "useNodeColorAsBackground")), ShowTitle = Value(QuickGraphStylePaths.Lod(name, "showTitle")) }; public void Refresh() { StyleManager.EnsureNamespaceLoaded(_graph.RenderStyleContext == null ? "default" : _graph.RenderStyleContext.BaseNamespace); var node = new QuickGraphNodeRenderStyle { Width = Value(QuickGraphStylePaths.NodeWidth), Height = Value(QuickGraphStylePaths.NodeHeight), Border = Value(QuickGraphStylePaths.NodeBorder), Chamfer = Value(QuickGraphStylePaths.NodeChamfer), ChamferSegments = Value(QuickGraphStylePaths.NodeChamferSegments), TitleFontSize = Value(QuickGraphStylePaths.NodeTitleFontSize), TitleFont = Value(QuickGraphStylePaths.NodeTitleFont), TitleAlignment = Value(QuickGraphStylePaths.NodeTitleAlignment), ShowTitleFull = Value(QuickGraphStylePaths.NodeShowTitleFull), OuterColor = Value(QuickGraphStylePaths.NodeOuterColor), AxisScaleX = Value(QuickGraphStylePaths.NodeAxisScaleX), AxisScaleY = Value(QuickGraphStylePaths.NodeAxisScaleY), SideCount = Value(QuickGraphStylePaths.NodeSideCount) }; var port = new QuickGraphPortRenderStyle { InputSide = Value(QuickGraphStylePaths.PortInputSide), OutputSide = Value(QuickGraphStylePaths.PortOutputSide), TopRatio = Value(QuickGraphStylePaths.PortTopRatio), BottomRatio = Value(QuickGraphStylePaths.PortBottomRatio) }; var wire = new QuickGraphWireRenderStyle { Width = Value(QuickGraphStylePaths.WireWidth), HitRadius = Value(QuickGraphStylePaths.WireHitRadius), SourceHandle = Value(QuickGraphStylePaths.WireSourceHandle), TargetHandle = Value(QuickGraphStylePaths.WireTargetHandle), ArrowWidth = Value(QuickGraphStylePaths.WireArrowWidth), ArrowHeight = Value(QuickGraphStylePaths.WireArrowHeight), ArrowProgress = Value(QuickGraphStylePaths.WireArrowProgress), ArrowDepth = Value(QuickGraphStylePaths.WireArrowDepth), Tessellation = Value(QuickGraphStylePaths.WireTessellation), MinTessellation = Value(QuickGraphStylePaths.WireMinTessellation), ArrowShape = Value(QuickGraphStylePaths.WireArrowShape), ArrowReversed = Value(QuickGraphStylePaths.WireArrowReversed), UseNodeColorGradient = Value(QuickGraphStylePaths.WireUseNodeColorGradient), Color = Value(QuickGraphStylePaths.WireColor) }; var grid = new QuickGraphGridRenderStyle { Material = Value(QuickGraphStylePaths.GridMaterial), ShaderName = Value(QuickGraphStylePaths.GridShader), Size = Value(QuickGraphStylePaths.GridSize), OverlayPower = Value(QuickGraphStylePaths.GridOverlayPower), Threshold = Value(QuickGraphStylePaths.GridThreshold), Exp = Value(QuickGraphStylePaths.GridExp), Color = Value(QuickGraphStylePaths.GridColor), Background = Value(QuickGraphStylePaths.GridBackground), RaycastTarget = Value(QuickGraphStylePaths.GridRaycastTarget), Maskable = Value(QuickGraphStylePaths.GridMaskable), LodTransitionThreshold = Value(QuickGraphStylePaths.GridLodTransitionThreshold), LodTransitionStart = Value(QuickGraphStylePaths.GridLodTransitionStart), LodTransitionEnd = Value(QuickGraphStylePaths.GridLodTransitionEnd), CellCenter = Value(QuickGraphStylePaths.GridCellCenter), LineShapeScale = Value(QuickGraphStylePaths.GridLineShapeScale) }; var input = new QuickGraphInputRenderStyle { ZoomDivisor = Value(QuickGraphStylePaths.InputZoomDivisor), DragPanSpeed = Value(QuickGraphStylePaths.InputDragPanSpeed), MinZoom = Value(QuickGraphStylePaths.InputMinZoom), MaxZoom = Value(QuickGraphStylePaths.InputMaxZoom), DefaultZoom = Value(QuickGraphStylePaths.InputDefaultZoom), FocusZoom = Value(QuickGraphStylePaths.InputFocusZoom), FocusPadding = Value(QuickGraphStylePaths.InputFocusPadding), KeyboardPanSpeed = Value(QuickGraphStylePaths.InputKeyboardPanSpeed), KeyboardZoomSpeed = Value(QuickGraphStylePaths.InputKeyboardZoomSpeed), PanMouseButton = Value(QuickGraphStylePaths.InputPanMouseButton) }; var selection = new QuickGraphSelectionRenderStyle { DragThreshold = Value(QuickGraphStylePaths.SelectionDragThreshold), FillColor = Value(QuickGraphStylePaths.SelectionFillColor), OutlineColor = Value(QuickGraphStylePaths.SelectionOutlineColor), OutlineOffset = Value(QuickGraphStylePaths.SelectionOutlineOffset), RaycastTarget = Value(QuickGraphStylePaths.SelectionRaycastTarget) }; var minimalLod = Lod("minimal"); var simplifiedLod = Lod("simplified"); var fullLod = Lod("full"); var old = Snapshot; var changes = old == null ? QuickGraphRenderStyleChange.All : Changes(old, node, port, wire, grid, input, minimalLod, simplifiedLod, fullLod, selection); Snapshot = new QuickGraphRenderStyleSnapshot { Revision = old == null ? 1 : old.Revision + 1, Changes = changes, Node = node, Port = port, Wire = wire, Grid = grid, Input = input, MinimalLod = minimalLod, SimplifiedLod = simplifiedLod, FullLod = fullLod, Selection = selection }; Validate(node, wire, grid, input); Changed?.Invoke(Snapshot); } private void Validate(QuickGraphNodeRenderStyle node, QuickGraphWireRenderStyle wire, QuickGraphGridRenderStyle grid, QuickGraphInputRenderStyle input) { var errors = new List(); Require(errors, QuickGraphStylePaths.NodeWidth); Require(errors, QuickGraphStylePaths.NodeHeight); Require(errors, QuickGraphStylePaths.WireWidth); Require(errors, QuickGraphStylePaths.WireTessellation); Require(errors, QuickGraphStylePaths.WireMinTessellation); Require(errors, QuickGraphStylePaths.GridSize); Require(errors, QuickGraphStylePaths.InputMinZoom); Require(errors, QuickGraphStylePaths.InputMaxZoom); Require(errors, QuickGraphStylePaths.InputDefaultZoom); if (!Has(QuickGraphStylePaths.GridMaterial) && !Has(QuickGraphStylePaths.GridShader)) errors.Add($"缺少关键路径 path={QuickGraphStylePaths.GridMaterial} 或 path={QuickGraphStylePaths.GridShader} value= expected=至少提供一个可用的 Grid 材质或 Shader"); if (node.Width <= 0f) errors.Add($"无效节点宽度 path={QuickGraphStylePaths.NodeWidth} value={node.Width} expected=> 0"); if (node.Height <= 0f) errors.Add($"无效节点高度 path={QuickGraphStylePaths.NodeHeight} value={node.Height} expected=> 0"); if (wire.Width <= 0f) errors.Add($"无效连线宽度 path={QuickGraphStylePaths.WireWidth} value={wire.Width} expected=> 0"); if (wire.MinTessellation < 2 || wire.Tessellation < wire.MinTessellation) errors.Add($"无效连线细分 path={QuickGraphStylePaths.WireTessellation} value={wire.Tessellation}; path={QuickGraphStylePaths.WireMinTessellation} value={wire.MinTessellation} expected=tessellation >= minTessellation >= 2"); if (grid.Size <= 0f) errors.Add($"无效网格尺寸 path={QuickGraphStylePaths.GridSize} value={grid.Size} expected=> 0"); if (input.MinZoom <= 0f || input.MaxZoom <= input.MinZoom || input.DefaultZoom < input.MinZoom || input.DefaultZoom > input.MaxZoom) errors.Add($"无效缩放范围 path={QuickGraphStylePaths.InputMinZoom} value={input.MinZoom}; path={QuickGraphStylePaths.InputMaxZoom} value={input.MaxZoom}; path={QuickGraphStylePaths.InputDefaultZoom} value={input.DefaultZoom} expected=0 < minZoom <= defaultZoom <= maxZoom"); bool materialUsable = grid.Material != null && grid.Material.shader != null; bool shaderUsable = !string.IsNullOrEmpty(grid.ShaderName) && Shader.Find(grid.ShaderName) != null; if (!materialUsable && !shaderUsable) errors.Add($"Grid 材质与 Shader 不可用 path={QuickGraphStylePaths.GridMaterial} value={grid.Material}; path={QuickGraphStylePaths.GridShader} value={grid.ShaderName ?? ""} expected=有效 Material(含 shader) 或可由 Shader.Find 找到的 Shader 名称"); string error = errors.Count == 0 ? null : $"[QuickGraph] 样式快照无效 GraphId={_graph.GraphId} ThemeName={_graph.ThemeName} Namespace={Namespace}:\n - " + string.Join("\n - ", errors); var state = _validationStates.GetOrCreateValue(_graph); if (error == state.LastError) return; state.LastError = error; if (error != null) Debug.LogError(error); } private string Namespace => _graph.RenderStyleContext == null || string.IsNullOrEmpty(_graph.RenderStyleContext.BaseNamespace) ? "default" : _graph.RenderStyleContext.BaseNamespace; private bool Has(string path) { return _graph.TryGetRenderStyle(path, out _); } private void Require(List errors, string path) { if (!Has(path)) errors.Add($"缺少关键路径 path={path} value= expected=定义可解析的样式值"); } private static QuickGraphRenderStyleChange Changes(QuickGraphRenderStyleSnapshot old, QuickGraphNodeRenderStyle node, QuickGraphPortRenderStyle port, QuickGraphWireRenderStyle wire, QuickGraphGridRenderStyle grid, QuickGraphInputRenderStyle input, QuickGraphLodRenderStyle minimalLod, QuickGraphLodRenderStyle simplifiedLod, QuickGraphLodRenderStyle fullLod, QuickGraphSelectionRenderStyle selection) { var changes = QuickGraphRenderStyleChange.None; if (!Equals(old.Node, node)) changes |= QuickGraphRenderStyleChange.NodeGeometry | QuickGraphRenderStyleChange.NodeAppearance; if (!Equals(old.Port, port)) changes |= QuickGraphRenderStyleChange.PortLayout; if (!Equals(old.Wire, wire)) changes |= QuickGraphRenderStyleChange.WireGeometry | QuickGraphRenderStyleChange.WireAppearance; if (!Equals(old.Grid, grid)) changes |= QuickGraphRenderStyleChange.GridAppearance; if (!Equals(old.Input, input)) changes |= QuickGraphRenderStyleChange.Input; if (!Equals(old.MinimalLod, minimalLod) || !Equals(old.SimplifiedLod, simplifiedLod) || !Equals(old.FullLod, fullLod)) changes |= QuickGraphRenderStyleChange.Lod; if (!Equals(old.Selection, selection)) changes |= QuickGraphRenderStyleChange.Selection; return changes; } public void Dispose() { _graph.RenderStyleChanged -= Refresh; StyleManager.OnStylesReloaded -= Refresh; Changed = null; } } }