Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9f9b2a90f | ||
|
|
315e2eaa46 | ||
|
|
2d703efdf4 |
@@ -2,10 +2,20 @@
|
||||
|
||||
## [Unrealse]
|
||||
|
||||
* 添加win10+右下角消息框
|
||||
* 修复文件对话框中筛选器格式
|
||||
|
||||
## [0.6.4] 2026-08-10
|
||||
|
||||
* 将XericLibrary提供的数据类型命名空间移动到Runtime下。这可能会带来一些兼容性问题,但现在Type成员太多了,再不调整这个遗留问题就不好解决了。
|
||||
* 添加图表数据结构
|
||||
* 添加windows7+的应用程序图表进度条功能。
|
||||
* 恢复引导接口的使用。
|
||||
* 修复excel在一些环境缺失的电脑上会因为zip调用437报错的问题(旧unity还是需要自己手动解决)。
|
||||
|
||||
部分提交来自尚未合并到主分支的提交(此次提交来自 work2 -> Blueprint5)
|
||||
* 重写蓝图嵌入样式系统的结构
|
||||
* 重写样式表系统结构,旧样式表结构可能存在部分不兼容的情况,但是提供了可以扩展的解析器用法。
|
||||
|
||||
# [0.6.3] 2026-07-16
|
||||
|
||||
|
||||
Binary file not shown.
@@ -32,6 +32,9 @@ Material:
|
||||
- _GridExp: 0.001
|
||||
- _GridLineThreshold: 0.99
|
||||
- _GridOverlayPower: 5
|
||||
- _LineShapeScale: 1.1
|
||||
- _LodTransitionEnd: 1
|
||||
- _LodTransitionStart: -0.8
|
||||
- _LodTransthd: 200
|
||||
- _OverlayerMult: 1
|
||||
- _Stencil: 0
|
||||
@@ -41,9 +44,10 @@ Material:
|
||||
- _StencilWriteMask: 255
|
||||
- _UseUIAlphaClip: 0
|
||||
m_Colors:
|
||||
- _CellCenter: {r: 0.5, g: 0.5, b: 0, a: 0}
|
||||
- _Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
- _GridBackgroundColor: {r: 0.8666667, g: 0.8862745, b: 0.93333334, a: 1}
|
||||
- _GridColor: {r: 0.654902, g: 0.67058825, b: 0.7019608, a: 1}
|
||||
- _Transform: {r: 8.90683, g: 7.61659, b: -4.453415, a: -3.808295}
|
||||
- _GridColor: {r: 0.8666667, g: 0.8862745, b: 0.93333334, a: 1}
|
||||
- _Transform: {r: 9.787723, g: 8.369877, b: -4.381715, a: -3.846801}
|
||||
- _Vector0: {r: 0.95, g: 1, b: 0, a: 0}
|
||||
m_BuildTextureStacks: []
|
||||
|
||||
@@ -1,59 +1,24 @@
|
||||
// ==================================================
|
||||
// Xeric Style Sheet (XSSS) - 超级干燥样式表 - 默认样式
|
||||
// Xeric Style Sheet (XSSS) - 默认 UI 样式
|
||||
// ==================================================
|
||||
// 规范声明:路径:组件类型/.字段路径 = 值:值类型 @标记:参数
|
||||
// 未配置实际资源时,不保留空 guid: 或 None 占位声明。
|
||||
|
||||
namespace: default
|
||||
|
||||
button/TargetGraphic/ColorTint:UnityEngine.UI.ColorBlock/ {
|
||||
.NormalColor = (1,1,1,1)
|
||||
.HighlightedColor = (0.96,0.96,0.96,1)
|
||||
.PressedColor = (0.78,0.78,0.78,1)
|
||||
.SelectedColor = (0.96,0.96,0.96,1)
|
||||
.DisabledColor = (0.6,0.6,0.6,0.8)
|
||||
}
|
||||
// UnityEngine.UI.Button 的 ColorBlock。
|
||||
Button:UnityEngine.UI.Button/.colors.normalColor = #FFFFFFFF:UnityEngine.Color
|
||||
Button:UnityEngine.UI.Button/.colors.highlightedColor = #F5F5F5FF:UnityEngine.Color
|
||||
Button:UnityEngine.UI.Button/.colors.pressedColor = #C7C7C7FF:UnityEngine.Color
|
||||
Button:UnityEngine.UI.Button/.colors.selectedColor = #F5F5F5FF:UnityEngine.Color
|
||||
Button:UnityEngine.UI.Button/.colors.disabledColor = #999999CC:UnityEngine.Color
|
||||
|
||||
button/TargetGraphic/SpriteState:UnityEngine.UI.SpriteState/ {
|
||||
.NormalColor = guid::UnityEngine.Sprite
|
||||
.HighlightedColor = guid::UnityEngine.Sprite
|
||||
.PressedColor = guid::UnityEngine.Sprite
|
||||
.SelectedColor = guid::UnityEngine.Sprite
|
||||
.DisabledColor = guid::UnityEngine.Sprite
|
||||
}
|
||||
// UnityEngine.UI.Toggle 的 ColorBlock。
|
||||
Toggle:UnityEngine.UI.Toggle/.colors.normalColor = #FFFFFFFF:UnityEngine.Color
|
||||
Toggle:UnityEngine.UI.Toggle/.colors.highlightedColor = #F5F5F5FF:UnityEngine.Color
|
||||
Toggle:UnityEngine.UI.Toggle/.colors.pressedColor = #C7C7C7FF:UnityEngine.Color
|
||||
Toggle:UnityEngine.UI.Toggle/.colors.selectedColor = #F5F5F5FF:UnityEngine.Color
|
||||
Toggle:UnityEngine.UI.Toggle/.colors.disabledColor = #999999CC:UnityEngine.Color
|
||||
|
||||
button/TargetGraphic/Animation/ {
|
||||
.NormalColor = None
|
||||
.HighlightedColor = None
|
||||
.PressedColor = None
|
||||
.SelectedColor = None
|
||||
.DisabledColor = None
|
||||
}
|
||||
|
||||
toggle/TargetGraphic/ColorTint:UnityEngine.UI.ColorBlock/ {
|
||||
.NormalColor = (1,1,1,1)
|
||||
.HighlightedColor = (0.96,0.96,0.96,1)
|
||||
.PressedColor = (0.78,0.78,0.78,1)
|
||||
.SelectedColor = (0.96,0.96,0.96,1)
|
||||
.DisabledColor = (0.6,0.6,0.6,0.8)
|
||||
}
|
||||
|
||||
toggle/TargetGraphic/SpriteState:UnityEngine.UI.SpriteState/ {
|
||||
.NormalColor = guid::UnityEngine.Sprite
|
||||
.HighlightedColor = guid::UnityEngine.Sprite
|
||||
.PressedColor = guid::UnityEngine.Sprite
|
||||
.SelectedColor = guid::UnityEngine.Sprite
|
||||
.DisabledColor = guid::UnityEngine.Sprite
|
||||
}
|
||||
|
||||
toggle/TargetGraphic/Animation/ {
|
||||
.NormalColor = None
|
||||
.HighlightedColor = None
|
||||
.PressedColor = None
|
||||
.SelectedColor = None
|
||||
.DisabledColor = None
|
||||
}
|
||||
|
||||
image/ColorTint:UnityEngine.UI.ColorBlock/ {
|
||||
.NormalColor = (1,1,1,1)
|
||||
.Sprite = guid::UnityEngine.Sprite
|
||||
.Material = guid::UnityEngine.Material
|
||||
}
|
||||
// UnityEngine.UI.Image 的默认色。
|
||||
Image:UnityEngine.UI.Image/.color = #FFFFFFFF:UnityEngine.Color
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 38115e5721db6514bb8b8be6be10f26a, type: 3}
|
||||
m_Name: QuickGraphGridBackgroundConfig
|
||||
m_EditorClassIdentifier:
|
||||
ShaderName: XericLibrary/BluePrint/BlueprintBackgound_GridLine
|
||||
OverrideMaterial: {fileID: 0}
|
||||
GridSize: 100
|
||||
GridOverlayPower: 5
|
||||
GridLineThreshold: 0.99
|
||||
GridExp: 0.001
|
||||
GridColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
GridBackgroundColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
@@ -1,8 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 092ad6102a8bc354bb081e3ee26e5ed6
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,20 +0,0 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 4735b93342f4fde4a8ed46dd28b46d4c, type: 3}
|
||||
m_Name: QuickGraphInputConfig
|
||||
m_EditorClassIdentifier:
|
||||
ZoomDivisor: 30
|
||||
DragPanSpeed: 1
|
||||
MinZoom: 0.1
|
||||
MaxZoom: 3
|
||||
KeyboardPanSpeed: 600
|
||||
KeyboardZoomSpeed: 0.05
|
||||
@@ -1,8 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3f441c61e0e252146a114c471a77c507
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,25 +0,0 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 8038bf45323375b46a3a2820ddfe212e, type: 3}
|
||||
m_Name: QuickGraphNodeRenderConfig
|
||||
m_EditorClassIdentifier:
|
||||
NodeWidth: 90
|
||||
NodeHeight: 130
|
||||
BorderThickness: 3
|
||||
ChamferSize: 0.2
|
||||
ChamferSegments: 4
|
||||
DefaultBackgroundColor: {r: 0.16, g: 0.16, b: 0.16, a: 1}
|
||||
DefaultTextColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
TitleFontSize: 14
|
||||
TitleFont: {fileID: 0}
|
||||
Lod0Threshold: 0.3
|
||||
Lod1Threshold: 0.7
|
||||
@@ -1,8 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cdebd6b7f25eb334093a428b6414b0ce
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,41 +1,102 @@
|
||||
// QuickGraph runtime render style
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
namespace: quickgraph
|
||||
|
||||
quickgraph/node/ {
|
||||
width = 130:float
|
||||
height = 80:float
|
||||
border = 3:float
|
||||
chamfer = 0.2:float
|
||||
chamferSegments = 4:int
|
||||
titleFontSize = 14:float
|
||||
width = 130:float @MinMaxRange:0,4096
|
||||
height = 80:float @MinMaxRange:0,4096
|
||||
border = 3:float @MinMaxRange:0,1024
|
||||
chamfer = 0.2:float @MinMaxRange:0,1
|
||||
chamferSegments = 4:int @MinMaxRange:0,128,1
|
||||
titleFontSize = 20:float @MinMaxRange:0,512
|
||||
titleAlignment = Center:TMPro.TextAlignmentOptions
|
||||
showTitle/full = true:bool
|
||||
outerColor = #00000000:UnityEngine.Color
|
||||
axisScaleX = 1:float @MinMaxRange:0,100
|
||||
axisScaleY = 1:float @MinMaxRange:0,100
|
||||
sideCount = 4:int @MinMaxRange:0,128,1
|
||||
fillColor = #333333FF:UnityEngine.Color
|
||||
borderColor = #FFFFFFFF:UnityEngine.Color
|
||||
titleColor = #FFFFFFFF:UnityEngine.Color
|
||||
}
|
||||
quickgraph/port/ {
|
||||
inputSide = -0.5:float @MinMaxRange:-1,1
|
||||
outputSide = 0.5:float @MinMaxRange:-1,1
|
||||
topRatio = 0.5:float @MinMaxRange:-1,1
|
||||
bottomRatio = -0.5:float @MinMaxRange:-1,1
|
||||
}
|
||||
|
||||
quickgraph/wire/ {
|
||||
width = 5:float
|
||||
hitRadius = 8:float
|
||||
sourceHandle = 80:float
|
||||
targetHandle = 80:float
|
||||
arrowWidth = 12:float
|
||||
arrowHeight = 10:float
|
||||
arrowProgress = 1:float
|
||||
arrowDepth = -1:float
|
||||
tessellation = 32:int
|
||||
width = 5:float @MinMaxRange:0,4096
|
||||
hitRadius = 8:float @MinMaxRange:0,4096
|
||||
sourceHandle = 80:float @MinMaxRange:0,4096
|
||||
targetHandle = 80:float @MinMaxRange:0,4096
|
||||
arrowWidth = 12:float @MinMaxRange:0,4096
|
||||
arrowHeight = 10:float @MinMaxRange:0,4096
|
||||
arrowProgress = 1:float @MinMaxRange:0,1
|
||||
arrowDepth = -1:float @MinMaxRange:-4096,4096
|
||||
tessellation = 32:int @MinMaxRange:0,1024,1
|
||||
minTessellation = 2:int @MinMaxRange:0,1024,1
|
||||
arrowShape = Triangle:XericLibrary.Runtime.UIGraph.ArrowShape
|
||||
arrowReversed = false:bool
|
||||
useNodeColorGradient = true:bool
|
||||
color = #FFFFFFFF:UnityEngine.Color
|
||||
}
|
||||
|
||||
quickgraph/grid/ {
|
||||
material = guid:c16d74b277ae17c479319990fca9477b:UnityEngine.Material:UnityEngine.Material @ResourceSoftReference:Packages/com.lrss3.deconstruction/Resources/Material/Graph/UI/Blueprint/BlueprintBackgound_GridLine.mat
|
||||
shader = res:XericLibrary/BluePrint/BlueprintBackgound_GridLine:string
|
||||
size = 100:float
|
||||
overlayPower = 5:float
|
||||
threshold = 0.99:float
|
||||
exp = 0.001:float
|
||||
size = 100:float @MinMaxRange:0.000001,1000000
|
||||
overlayPower = 5:float @MinMaxRange:0,1000000
|
||||
threshold = 0.99:float @MinMaxRange:0,1
|
||||
exp = 0.001:float @MinMaxRange:0,1000000
|
||||
color = #A7ABB3FF:UnityEngine.Color
|
||||
background = #DDE2EEFF:UnityEngine.Color
|
||||
raycastTarget = true:bool
|
||||
maskable = false:bool
|
||||
lodTransitionThreshold = 200:float @MinMaxRange:0,1000000
|
||||
lodTransitionStart = -0.8:float @MinMaxRange:-100,100
|
||||
lodTransitionEnd = 1:float @MinMaxRange:-100,100
|
||||
cellCenter = (0.5,0.5):UnityEngine.Vector2
|
||||
lineShapeScale = 1.1:float @MinMaxRange:0,100
|
||||
}
|
||||
quickgraph/input/ {
|
||||
zoomDivisor = 100:float @MinMaxRange:0.000001,1000000
|
||||
dragPanSpeed = 1:float @MinMaxRange:0,1000000
|
||||
minZoom = 0.2:float @MinMaxRange:0,1000000
|
||||
maxZoom = 2:float @MinMaxRange:0,1000000
|
||||
defaultZoom = 1:float @MinMaxRange:0,1000000
|
||||
focusZoom = 1:float @MinMaxRange:0,1000000
|
||||
focusPadding = 100:float @MinMaxRange:0,1000000
|
||||
keyboardPanSpeed = 100:float @MinMaxRange:0,1000000
|
||||
keyboardZoomSpeed = 0.01:float @MinMaxRange:0,1000000
|
||||
panMouseButton = 2:int @MinMaxRange:0,2,1
|
||||
}
|
||||
quickgraph/lod/minimal/ {
|
||||
enterZoom = 0:float @MinMaxRange:0,1000000
|
||||
exitZoom = 0:float @MinMaxRange:0,1000000
|
||||
wireTessellationMultiplier = 0.35:float @MinMaxRange:0,100
|
||||
nodeBorder = 0:float @MinMaxRange:0,1024
|
||||
useNodeColorAsBackground = true:bool
|
||||
showTitle = false:bool
|
||||
}
|
||||
quickgraph/lod/simplified/ {
|
||||
enterZoom = 0.65:float @MinMaxRange:0,1000000
|
||||
exitZoom = 0.55:float @MinMaxRange:0,1000000
|
||||
wireTessellationMultiplier = 0.65:float @MinMaxRange:0,100
|
||||
nodeBorder = 3:float @MinMaxRange:0,1024
|
||||
useNodeColorAsBackground = false:bool
|
||||
showTitle = false:bool
|
||||
}
|
||||
quickgraph/lod/full/ {
|
||||
enterZoom = 1.15:float @MinMaxRange:0,1000000
|
||||
exitZoom = 1:float @MinMaxRange:0,1000000
|
||||
wireTessellationMultiplier = 1:float @MinMaxRange:0,100
|
||||
nodeBorder = 3:float @MinMaxRange:0,1024
|
||||
useNodeColorAsBackground = false:bool
|
||||
showTitle = true:bool
|
||||
}
|
||||
quickgraph/selection/ {
|
||||
dragThreshold = 10:float @MinMaxRange:0,1000000
|
||||
fillColor = #3380FF26:UnityEngine.Color
|
||||
outlineColor = #FFFFFFFF:UnityEngine.Color
|
||||
outlineOffset = (1,-1):UnityEngine.Vector2
|
||||
raycastTarget = false:bool
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: c95f6e1f1ece40444839f0a29ac371e5, type: 3}
|
||||
m_Name: QuickGraphWireRenderConfig
|
||||
m_EditorClassIdentifier:
|
||||
WireWidth: 5
|
||||
SourceHandleLength: 80
|
||||
TargetHandleLength: 80
|
||||
ArrowShape: 0
|
||||
ArrowReversed: 0
|
||||
ArrowProgress: 1
|
||||
ArrowWidth: 12
|
||||
ArrowHeight: 10
|
||||
ArrowDepthCompensation: -1
|
||||
TessellationSegments: 32
|
||||
@@ -1,8 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aad533644ae5c8b4bb61786c413d4844
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -50,8 +50,10 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
#endif
|
||||
_inputActions = value;
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
if (Graph != null && value is InputActionAsset newAsset)
|
||||
if (Graph != null)
|
||||
{
|
||||
var newAsset = value as InputActionAsset;
|
||||
EnsureEventSystem(newAsset);
|
||||
var newTool = BlueprintToolProvider.GetToolByType<BlueprintInputTool_InputSystem>(Graph);
|
||||
if (newTool != null)
|
||||
newTool.InputActionAsset = newAsset;
|
||||
@@ -162,14 +164,12 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
private void SetupInput()
|
||||
{
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
if (_inputActions is InputActionAsset asset)
|
||||
{
|
||||
EnsureEventSystem(asset);
|
||||
// 将 InputActionAsset 传递给输入工具(由工具自身管理绑定)
|
||||
var tool = BlueprintToolProvider.GetToolByType<BlueprintInputTool_InputSystem>(Graph);
|
||||
if (tool != null)
|
||||
tool.InputActionAsset = asset;
|
||||
}
|
||||
var asset = _inputActions as InputActionAsset;
|
||||
EnsureEventSystem(asset);
|
||||
// 将 InputActionAsset 传递给输入工具(由工具自身管理绑定)。
|
||||
var tool = BlueprintToolProvider.GetToolByType<BlueprintInputTool_InputSystem>(Graph);
|
||||
if (tool != null)
|
||||
tool.InputActionAsset = asset;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -192,29 +192,64 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
}
|
||||
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
if (actionsAsset != null)
|
||||
var uiModule = eventSys.GetComponent<UnityEngine.InputSystem.UI.InputSystemUIInputModule>();
|
||||
if (uiModule == null)
|
||||
{
|
||||
var uiModule = eventSys.GetComponent<UnityEngine.InputSystem.UI.InputSystemUIInputModule>();
|
||||
if (uiModule == null)
|
||||
{
|
||||
var standalone = eventSys.GetComponent<StandaloneInputModule>();
|
||||
if (standalone != null)
|
||||
DestroyImmediate(standalone);
|
||||
var standalone = eventSys.GetComponent<StandaloneInputModule>();
|
||||
if (standalone != null)
|
||||
DestroyImmediate(standalone);
|
||||
|
||||
uiModule = eventSys.gameObject.AddComponent<UnityEngine.InputSystem.UI.InputSystemUIInputModule>();
|
||||
}
|
||||
uiModule.actionsAsset = actionsAsset;
|
||||
uiModule = eventSys.gameObject.AddComponent<UnityEngine.InputSystem.UI.InputSystemUIInputModule>();
|
||||
}
|
||||
|
||||
if (actionsAsset != null)
|
||||
ConfigureBlueprintUiActions(uiModule, actionsAsset);
|
||||
else
|
||||
#endif
|
||||
{
|
||||
if (eventSys.GetComponent<StandaloneInputModule>() == null)
|
||||
{
|
||||
eventSys.gameObject.AddComponent<StandaloneInputModule>();
|
||||
}
|
||||
// 未提供蓝图资产时使用 Input System 内置 UI 动作,仍可产生指针事件。
|
||||
uiModule.actionsAsset = null;
|
||||
uiModule.AssignDefaultActions();
|
||||
}
|
||||
#else
|
||||
if (eventSys.GetComponent<StandaloneInputModule>() == null)
|
||||
{
|
||||
eventSys.gameObject.AddComponent<StandaloneInputModule>();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
/// <summary>
|
||||
/// 将 Blueprint map 的鼠标动作显式映射到 UI 模块。
|
||||
/// InputSystemUIInputModule 不会仅凭 actionsAsset 自动推断这些引用。
|
||||
/// </summary>
|
||||
private static void ConfigureBlueprintUiActions(
|
||||
UnityEngine.InputSystem.UI.InputSystemUIInputModule uiModule,
|
||||
InputActionAsset actionsAsset)
|
||||
{
|
||||
var map = actionsAsset.FindActionMap(BlueprintInputConstants.MapName);
|
||||
var point = map?.FindAction("Point");
|
||||
var leftClick = map?.FindAction("LeftClick");
|
||||
var rightClick = map?.FindAction("RightClick");
|
||||
var middleClick = map?.FindAction("MiddleClick");
|
||||
var scrollWheel = map?.FindAction("ScrollWheel");
|
||||
if (point == null || leftClick == null || rightClick == null ||
|
||||
middleClick == null || scrollWheel == null)
|
||||
{
|
||||
uiModule.actionsAsset = null;
|
||||
uiModule.AssignDefaultActions();
|
||||
return;
|
||||
}
|
||||
|
||||
uiModule.actionsAsset = actionsAsset;
|
||||
uiModule.point = InputActionReference.Create(point);
|
||||
uiModule.leftClick = InputActionReference.Create(leftClick);
|
||||
uiModule.rightClick = InputActionReference.Create(rightClick);
|
||||
uiModule.middleClick = InputActionReference.Create(middleClick);
|
||||
uiModule.scrollWheel = InputActionReference.Create(scrollWheel);
|
||||
}
|
||||
#endif
|
||||
|
||||
// ===== 调试可见性 =====
|
||||
|
||||
private void SyncDebugVisibility()
|
||||
|
||||
@@ -1,213 +1,24 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
using XericLibrary.Runtime.Blueprint.QuickGraph;
|
||||
namespace XericLibrary.Runtime.Blueprint
|
||||
{
|
||||
/// <summary>
|
||||
/// 蓝图框选工具 —— 鼠标左键在空白区域拖拽,绘制矩形选框,
|
||||
/// 释放时计算框选范围内所有元素的交集并输出统计日志。
|
||||
/// <para>选框 UGUI Image 默认渲染在所有层之上(SetAsLastSibling)。</para>
|
||||
/// <para>框选结果通过 <see cref="BlueprintToolProvider.GetSelector"/>(Graph).<see cref="SelectorHandle.CurrentSelection"/> 读取。</para>
|
||||
/// <para>
|
||||
/// 选框仅在拖拽距离超过 <see cref="BoxSelectThreshold"/> 后才显示,
|
||||
/// 避免误触显示闪烁。
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[BlueprintTool(phase: ToolPhase.PreUpdate, order: 60)]
|
||||
public class BlueprintBoxSelectTool : BlueprintTool
|
||||
{
|
||||
// ---------- 状态 ----------
|
||||
private bool _isBoxSelecting;
|
||||
private Vector2 _dragStartCanvas; // 按下时的画布坐标
|
||||
private Vector2 _dragEndCanvas; // 当前的画布坐标
|
||||
|
||||
/// <summary>选框 GameObject 是否已创建并可见(超过阈值后)</summary>
|
||||
private bool _boxVisible;
|
||||
|
||||
// 选框 UI
|
||||
private GameObject _selectionBoxGo;
|
||||
private Image _selectionBoxImage;
|
||||
private RectTransform _selectionBoxRt;
|
||||
|
||||
// ---------- 配置 ----------
|
||||
|
||||
/// <summary>框选拖拽触发阈值(画布单位)。拖拽距离超过此值后才显示选框并开始框选。</summary>
|
||||
public float BoxSelectThreshold = 10f;
|
||||
|
||||
// ---------- 结果 ----------
|
||||
|
||||
/// <summary>结果:通过 Provider.Selector.CurrentSelection 读取框选结果。</summary>
|
||||
|
||||
// ===== 鼠标按下 =====
|
||||
|
||||
public override void OnPointerDown(GraphInputTool sender, Vector2 canvasPoint, int mouseButton)
|
||||
{
|
||||
if (Graph == null) return;
|
||||
if (mouseButton != 0) return; // 仅左键
|
||||
|
||||
// 仅在空白区域开始框选(HitTest 无结果)
|
||||
var hit = Graph.HitTest(canvasPoint);
|
||||
if (hit != null) return;
|
||||
|
||||
_isBoxSelecting = true;
|
||||
_boxVisible = false;
|
||||
_dragStartCanvas = canvasPoint;
|
||||
_dragEndCanvas = canvasPoint;
|
||||
}
|
||||
|
||||
// ===== 拖拽 =====
|
||||
|
||||
public override void OnPointerDrag(GraphInputTool sender, Vector2 canvasPoint, Vector2 delta, int mouseButton)
|
||||
{
|
||||
if (!_isBoxSelecting || mouseButton != 0) return;
|
||||
|
||||
_dragEndCanvas = canvasPoint;
|
||||
|
||||
// 检查拖拽距离是否超过阈值
|
||||
float threshold = BoxSelectThreshold;
|
||||
float dragDist = Vector2.Distance(_dragStartCanvas, _dragEndCanvas);
|
||||
|
||||
if (!_boxVisible && dragDist > threshold)
|
||||
{
|
||||
// 首次超过阈值 → 创建选框
|
||||
_boxVisible = true;
|
||||
CreateSelectionBox();
|
||||
}
|
||||
|
||||
if (_boxVisible)
|
||||
{
|
||||
UpdateSelectionBox();
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 释放 =====
|
||||
|
||||
public override void OnPointerUp(GraphInputTool sender, Vector2 canvasPoint, int mouseButton)
|
||||
{
|
||||
if (!_isBoxSelecting || mouseButton != 0) return;
|
||||
|
||||
_dragEndCanvas = canvasPoint;
|
||||
|
||||
if (_boxVisible)
|
||||
{
|
||||
FinishSelection();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 未超过阈值 → 点击行为(框选起点处的元素)
|
||||
SelectAtPoint(_dragStartCanvas);
|
||||
}
|
||||
|
||||
_isBoxSelecting = false;
|
||||
}
|
||||
|
||||
// ===== 销毁 =====
|
||||
|
||||
public override void OnDestroy()
|
||||
{
|
||||
DestroySelectionBox();
|
||||
}
|
||||
|
||||
// ===== 选框 UI =====
|
||||
|
||||
private void CreateSelectionBox()
|
||||
{
|
||||
if (Graph?.Canvas == null) return;
|
||||
var root = Graph.Canvas.GetRootTransform();
|
||||
if (root == null) return;
|
||||
|
||||
// 销毁已有的选框(应对重复点击)
|
||||
DestroySelectionBox();
|
||||
|
||||
_selectionBoxGo = new GameObject("__Bp_BoxSelect", typeof(RectTransform), typeof(Image));
|
||||
_selectionBoxGo.hideFlags = HideFlags.HideAndDontSave;
|
||||
|
||||
_selectionBoxRt = _selectionBoxGo.GetComponent<RectTransform>();
|
||||
_selectionBoxRt.SetParent(root, false);
|
||||
|
||||
// 枢轴设为左下角;锚点居中对齐 RenderRoot 枢轴(与 CanvasToLocal 空间一致)
|
||||
_selectionBoxRt.pivot = Vector2.zero;
|
||||
_selectionBoxRt.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
_selectionBoxRt.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
|
||||
_selectionBoxRt.SetAsLastSibling(); // 渲染在最顶层
|
||||
|
||||
_selectionBoxImage = _selectionBoxGo.GetComponent<Image>();
|
||||
_selectionBoxImage.color = new Color(0.2f, 0.5f, 1.0f, 0.15f);
|
||||
var outline = _selectionBoxGo.AddComponent<Outline>();
|
||||
outline.effectColor = Color.white;
|
||||
outline.effectDistance = new Vector2(1, -1);
|
||||
}
|
||||
|
||||
private void UpdateSelectionBox()
|
||||
{
|
||||
if (_selectionBoxRt == null || Graph?.Canvas == null) return;
|
||||
|
||||
// 将画布坐标转为 RenderRoot 本地空间坐标(CanvasToLocal)
|
||||
var startLocal = Graph.Canvas.CanvasToLocal(_dragStartCanvas);
|
||||
var endLocal = Graph.Canvas.CanvasToLocal(_dragEndCanvas);
|
||||
|
||||
// 计算 RectTransform 位置和尺寸(RenderRoot 本地空间)
|
||||
var minX = Mathf.Min(startLocal.x, endLocal.x);
|
||||
var minY = Mathf.Min(startLocal.y, endLocal.y);
|
||||
var maxX = Mathf.Max(startLocal.x, endLocal.x);
|
||||
var maxY = Mathf.Max(startLocal.y, endLocal.y);
|
||||
|
||||
_selectionBoxRt.anchoredPosition = new Vector2(minX, minY);
|
||||
_selectionBoxRt.sizeDelta = new Vector2(maxX - minX, maxY - minY);
|
||||
}
|
||||
|
||||
private void FinishSelection()
|
||||
{
|
||||
UpdateSelectionBox();
|
||||
|
||||
// 计算选框内的元素(画布坐标空间)
|
||||
var selectList = new System.Collections.Generic.List<IBlueprintElement>();
|
||||
var min = new Vector2(
|
||||
Mathf.Min(_dragStartCanvas.x, _dragEndCanvas.x),
|
||||
Mathf.Min(_dragStartCanvas.y, _dragEndCanvas.y));
|
||||
var max = new Vector2(
|
||||
Mathf.Max(_dragStartCanvas.x, _dragEndCanvas.x),
|
||||
Mathf.Max(_dragStartCanvas.y, _dragEndCanvas.y));
|
||||
var selectRect = new Rect(min, max - min);
|
||||
|
||||
var selector = BlueprintToolProvider.GetSelector(Graph);
|
||||
selector.RectHitTest(Graph, selectRect, selectList);
|
||||
|
||||
// 通过当前 Graph 的 SelectorHandle 提交多选
|
||||
selector.CommitMultiSelection(selectList);
|
||||
|
||||
// 销毁选框
|
||||
DestroySelectionBox();
|
||||
}
|
||||
|
||||
private void SelectAtPoint(Vector2 canvasPoint)
|
||||
{
|
||||
if (Graph == null) return;
|
||||
|
||||
var selector = BlueprintToolProvider.GetSelector(Graph);
|
||||
var hit = selector.HitTest(Graph, canvasPoint);
|
||||
if (hit != null)
|
||||
selector.CommitSelection(hit);
|
||||
else
|
||||
selector.SwapToHistory();
|
||||
}
|
||||
|
||||
private void DestroySelectionBox()
|
||||
{
|
||||
if (_selectionBoxGo != null)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
Object.DestroyImmediate(_selectionBoxGo);
|
||||
else
|
||||
#endif
|
||||
Object.Destroy(_selectionBoxGo);
|
||||
_selectionBoxGo = null;
|
||||
_selectionBoxRt = null;
|
||||
_selectionBoxImage = null;
|
||||
}
|
||||
}
|
||||
private bool _selecting, _visible; private Vector2 _start, _end; private GameObject _box; private Image _image; private RectTransform _rect; private Outline _outline; private QuickGraphRenderStyleResolver _styles;
|
||||
public override void OnInitialize() { _styles = new QuickGraphRenderStyleResolver(Graph); _styles.Changed += _ => ApplyStyle(); }
|
||||
public override void OnConfigChanged() => _styles?.Refresh();
|
||||
public override void OnPointerDown(GraphInputTool sender, Vector2 point, int button) { if (button != 0 || Graph == null || Graph.HitTest(point) != null) return; _selecting = true; _visible = false; _start = _end = point; }
|
||||
public override void OnPointerDrag(GraphInputTool sender, Vector2 point, Vector2 delta, int button) { if (!_selecting || button != 0) return; _end = point; if (!_visible && Vector2.Distance(_start, _end) > _styles.Snapshot.Selection.DragThreshold) { _visible = true; Create(); } if (_visible) UpdateBox(); }
|
||||
public override void OnPointerUp(GraphInputTool sender, Vector2 point, int button) { if (!_selecting || button != 0) return; _end = point; if (_visible) Finish(); else SelectAt(_start); _selecting = false; }
|
||||
private void Create() { if (Graph?.Canvas == null) return; var root = Graph.Canvas.GetRootTransform(); if (root == null) return; DestroyBox(); _box = new GameObject("__Bp_BoxSelect", typeof(RectTransform), typeof(Image), typeof(Outline)); _box.hideFlags = HideFlags.HideAndDontSave; _rect = _box.GetComponent<RectTransform>(); _rect.SetParent(root, false); _rect.pivot = Vector2.zero; _rect.anchorMin = _rect.anchorMax = new Vector2(.5f, .5f); _rect.SetAsLastSibling(); _image = _box.GetComponent<Image>(); _outline = _box.GetComponent<Outline>(); ApplyStyle(); }
|
||||
private void ApplyStyle() { if (_image == null || _styles == null) return; var s = _styles.Snapshot.Selection; _image.color = s.FillColor; _image.raycastTarget = s.RaycastTarget; _outline.effectColor = s.OutlineColor; _outline.effectDistance = s.OutlineOffset; }
|
||||
private void UpdateBox() { if (_rect == null || Graph?.Canvas == null) return; var a = Graph.Canvas.CanvasToLocal(_start); var b = Graph.Canvas.CanvasToLocal(_end); var min = Vector2.Min(a, b); _rect.anchoredPosition = min; _rect.sizeDelta = Vector2.Max(a, b) - min; }
|
||||
private void Finish() { UpdateBox(); var min = Vector2.Min(_start, _end); var selector = BlueprintToolProvider.GetSelector(Graph); var result = new List<IBlueprintElement>(); selector.RectHitTest(Graph, new Rect(min, Vector2.Max(_start, _end) - min), result); selector.CommitMultiSelection(result); DestroyBox(); }
|
||||
private void SelectAt(Vector2 point) { var selector = BlueprintToolProvider.GetSelector(Graph); var hit = selector.HitTest(Graph, point); if (hit != null) selector.CommitSelection(hit); else selector.SwapToHistory(); }
|
||||
private void DestroyBox() { if (_box == null) return; if (Application.isPlaying) Object.Destroy(_box); else Object.DestroyImmediate(_box); _box = null; _image = null; _rect = null; _outline = null; }
|
||||
public override void OnDestroy() { _styles?.Dispose(); _styles = null; DestroyBox(); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,8 +107,8 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
/// <summary>
|
||||
/// 确保场景中存在 EventSystem 和合适的 InputModule。
|
||||
/// 旧输入系统 → StandaloneInputModule。
|
||||
/// 新输入系统 → InputSystemUIInputModule(由 GraphTheoryBlueprintComponent 创建)。
|
||||
/// 不会重复创建模块(通过 GetComponent 检查)。
|
||||
/// 新输入系统 → InputSystemUIInputModule;无蓝图资产配置时使用模块默认动作兜底。
|
||||
/// 不会覆盖 GraphTheoryBlueprintComponent 已配置的模块。
|
||||
/// </summary>
|
||||
private static void EnsureEventSystemExists()
|
||||
{
|
||||
@@ -119,7 +119,17 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
es = go.AddComponent<EventSystem>();
|
||||
}
|
||||
|
||||
#if !ENABLE_INPUT_SYSTEM
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
if (es.GetComponent<UnityEngine.InputSystem.UI.InputSystemUIInputModule>() == null)
|
||||
{
|
||||
var standalone = es.GetComponent<StandaloneInputModule>();
|
||||
if (standalone != null)
|
||||
Object.DestroyImmediate(standalone);
|
||||
|
||||
var uiModule = es.gameObject.AddComponent<UnityEngine.InputSystem.UI.InputSystemUIInputModule>();
|
||||
uiModule.AssignDefaultActions();
|
||||
}
|
||||
#else
|
||||
if (es.GetComponent<StandaloneInputModule>() == null)
|
||||
{
|
||||
es.gameObject.AddComponent<StandaloneInputModule>();
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
/// 蓝图视口控制器 —— 管理视图状态(Pan/Zoom/范围),提供外部 API 供工具和外部代码读写。
|
||||
/// <para>
|
||||
/// 在 <c>ToolPhase.PreUpdate, order: 40</c> 执行,每帧直接将目标值写入 <see cref="IBlueprintCanvas"/>,
|
||||
/// 不做插值平滑。输入工具通过写入 <see cref="TargetPanOffset"/> / <see cref="TargetZoomLevel"/>
|
||||
/// 不做插值平滑或消费端缩放钳制。输入工具通过写入 <see cref="TargetPanOffset"/> / <see cref="TargetZoomLevel"/>
|
||||
/// 来驱动视图,外部代码可通过 <c>BlueprintToolProvider.GetTool<BlueprintViewportController>(graph)</c>
|
||||
/// 获取实例直接控制视图。
|
||||
/// </para>
|
||||
@@ -23,13 +23,7 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
public Vector2 TargetPanOffset = Vector2.zero;
|
||||
|
||||
/// <summary>目标缩放级别。外部代码直接设置此值即可控制视图缩放。</summary>
|
||||
public float TargetZoomLevel = 1f;
|
||||
|
||||
/// <summary>最小缩放限制。</summary>
|
||||
public float MinZoom = 0.1f;
|
||||
|
||||
/// <summary>最大缩放限制。</summary>
|
||||
public float MaxZoom = 3f;
|
||||
public float TargetZoomLevel;
|
||||
|
||||
// ── 帧间脏检测 ──
|
||||
|
||||
@@ -43,22 +37,19 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
|
||||
var canvas = Graph.Canvas;
|
||||
|
||||
// 首次同步
|
||||
// 首次同步使用样式默认缩放。
|
||||
if (!_initialized)
|
||||
{
|
||||
TargetPanOffset = canvas.PanOffset;
|
||||
TargetZoomLevel = canvas.ZoomLevel;
|
||||
TargetZoomLevel = Graph.GetRenderStyleValue<float>("/quickgraph/input/defaultZoom");
|
||||
_prevPanOffset = TargetPanOffset;
|
||||
_prevZoomLevel = TargetZoomLevel;
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
// 同步范围到 Canvas
|
||||
canvas.MinZoom = MinZoom;
|
||||
canvas.MaxZoom = MaxZoom;
|
||||
|
||||
// 钳制目标值
|
||||
TargetZoomLevel = Mathf.Clamp(TargetZoomLevel, MinZoom, MaxZoom);
|
||||
// 缩放范围由样式写入 Canvas,由 Canvas 负责约束。
|
||||
canvas.MinZoom = Graph.GetRenderStyleValue<float>("/quickgraph/input/minZoom");
|
||||
canvas.MaxZoom = Graph.GetRenderStyleValue<float>("/quickgraph/input/maxZoom");
|
||||
|
||||
// 直接写入 Canvas(无插值)
|
||||
canvas.PanOffset = TargetPanOffset;
|
||||
@@ -77,18 +68,18 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
// 公共 API
|
||||
// ====================================================================
|
||||
|
||||
/// <summary>重置视图到原点,缩放归 1。</summary>
|
||||
/// <summary>重置视图到原点和样式默认缩放。</summary>
|
||||
public void ResetView()
|
||||
{
|
||||
TargetPanOffset = Vector2.zero;
|
||||
TargetZoomLevel = 1f;
|
||||
TargetZoomLevel = Graph.GetRenderStyleValue<float>("/quickgraph/input/defaultZoom");
|
||||
Graph?.MarkDirty();
|
||||
}
|
||||
|
||||
/// <summary>聚焦到某个画布坐标点。</summary>
|
||||
public void FocusOn(Vector2 canvasCenter, float zoom = 1f)
|
||||
/// <summary>聚焦到某个画布坐标点;省略缩放时使用样式聚焦缩放。</summary>
|
||||
public void FocusOn(Vector2 canvasCenter, float? zoom = null)
|
||||
{
|
||||
TargetZoomLevel = Mathf.Clamp(zoom, MinZoom, MaxZoom);
|
||||
TargetZoomLevel = zoom ?? Graph.GetRenderStyleValue<float>("/quickgraph/input/focusZoom");
|
||||
if (Graph?.Canvas != null)
|
||||
{
|
||||
// local = canvas * zoom + pan;令目标画布点落在实际 viewport 的 RenderRoot local 中心。
|
||||
@@ -98,12 +89,14 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
Graph?.MarkDirty();
|
||||
}
|
||||
|
||||
/// <summary>聚焦到一组元素的包围盒区域。</summary>
|
||||
public void FocusOnElements(System.Collections.Generic.IReadOnlyList<IBlueprintElement> elements, float padding = 100f)
|
||||
/// <summary>聚焦到一组元素的包围盒区域,使用样式聚焦边距。</summary>
|
||||
public void FocusOnElements(System.Collections.Generic.IReadOnlyList<IBlueprintElement> elements)
|
||||
{
|
||||
if (elements == null || elements.Count == 0) return;
|
||||
if (Graph?.Canvas == null) return;
|
||||
|
||||
float padding = Graph.GetRenderStyleValue<float>("/quickgraph/input/focusPadding");
|
||||
|
||||
// 计算所有元素的包围盒
|
||||
Rect bounds = elements[0].BoundingBox;
|
||||
for (int i = 1; i < elements.Count; i++)
|
||||
@@ -122,10 +115,9 @@ namespace XericLibrary.Runtime.Blueprint
|
||||
|
||||
// 以实际 viewport 的 RenderRoot local 尺寸适配;再复用 FocusOn 的统一公式。
|
||||
var viewportLocal = Graph.Canvas.GetViewportLocalRect();
|
||||
float zoomX = bounds.width > 0.0001f ? viewportLocal.width / bounds.width : MaxZoom;
|
||||
float zoomY = bounds.height > 0.0001f ? viewportLocal.height / bounds.height : MaxZoom;
|
||||
float zoom = Mathf.Clamp(Mathf.Min(zoomX, zoomY), MinZoom, MaxZoom);
|
||||
FocusOn(bounds.center, zoom);
|
||||
float zoomX = bounds.width > 0.0001f ? viewportLocal.width / bounds.width : Graph.GetRenderStyleValue<float>("/quickgraph/input/maxZoom");
|
||||
float zoomY = bounds.height > 0.0001f ? viewportLocal.height / bounds.height : Graph.GetRenderStyleValue<float>("/quickgraph/input/maxZoom");
|
||||
FocusOn(bounds.center, Mathf.Min(zoomX, zoomY));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,198 +1,19 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace XericLibrary.Runtime.Blueprint.QuickGraph
|
||||
{
|
||||
/// <summary>
|
||||
/// QuickGraph 输入交互工具 —— 处理画布平移、缩放。
|
||||
/// <para>中键拖拽 = 平移;滚轮 = 缩放。</para>
|
||||
/// <para>视图状态读写委托给 <see cref="BlueprintViewportController"/>,
|
||||
/// 输入参数从 <see cref="QuickGraphInputConfig"/> 读取。</para>
|
||||
/// </summary>
|
||||
[BlueprintTool(phase: ToolPhase.PreUpdate, order: 50)]
|
||||
[BlueprintTheme("QuickGraph")]
|
||||
[BlueprintTool(phase: ToolPhase.PreUpdate, order: 50)] [BlueprintTheme("QuickGraph")]
|
||||
public class QuickGraphInputTool : BlueprintTool
|
||||
{
|
||||
// ---------- 配置(从 ToolConfigAssets 读取) ----------
|
||||
private QuickGraphInputConfig Config
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ToolConfig is QuickGraphInputConfig external)
|
||||
return external;
|
||||
if (_defaultConfig == null)
|
||||
_defaultConfig = ScriptableObject.CreateInstance<QuickGraphInputConfig>();
|
||||
return _defaultConfig;
|
||||
}
|
||||
}
|
||||
private static QuickGraphInputConfig _defaultConfig;
|
||||
|
||||
// ---------- 缓存的 ViewportController 引用 ----------
|
||||
private BlueprintViewportController _viewport;
|
||||
private BlueprintViewportController Viewport
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_viewport == null && Graph != null)
|
||||
_viewport = BlueprintToolProvider.GetToolByType<BlueprintViewportController>(Graph);
|
||||
return _viewport;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 拖拽状态 ----------
|
||||
private bool _isDragging;
|
||||
private int _activeDragButton = -1;
|
||||
|
||||
private Vector2 senderPanDirection() => GraphInputTool.GetAdapter(Graph)?.PanDirection ?? Vector2.zero;
|
||||
private float senderZoomAxis() => GraphInputTool.GetAdapter(Graph)?.ZoomAxis ?? 0f;
|
||||
|
||||
// ---------- 选择状态 ----------
|
||||
private BlueprintElementBase _hoveredElement;
|
||||
|
||||
public override void OnPointerMove(GraphInputTool sender, Vector2 canvasPoint, Vector2 delta)
|
||||
{
|
||||
if (Graph == null || _isDragging) return;
|
||||
|
||||
var hit = Graph.HitTest(canvasPoint);
|
||||
_hoveredElement = hit as BlueprintElementBase;
|
||||
}
|
||||
|
||||
/// <summary>当前悬停的元素(可能为 null)</summary>
|
||||
public BlueprintElementBase HoveredElement => _hoveredElement;
|
||||
|
||||
/// <summary>当前选中的元素(委托给 SelectorHandle)。</summary>
|
||||
public BlueprintElementBase SelectedElement =>
|
||||
BlueprintToolProvider.GetSelector(Graph).PrimarySelection as BlueprintElementBase;
|
||||
|
||||
public override void OnConfigChanged()
|
||||
{
|
||||
var viewport = Viewport;
|
||||
if (viewport == null) return;
|
||||
|
||||
var cfg = Config;
|
||||
viewport.MinZoom = cfg.MinZoom;
|
||||
viewport.MaxZoom = cfg.MaxZoom;
|
||||
viewport.TargetZoomLevel = Mathf.Clamp(viewport.TargetZoomLevel, cfg.MinZoom, cfg.MaxZoom);
|
||||
|
||||
// ViewportController 是 Canvas Pan/Zoom 的唯一提交者;这里只更新 controller 的范围与目标。
|
||||
Graph?.MarkDirty();
|
||||
}
|
||||
|
||||
public override void OnUpdate(float deltaTime)
|
||||
{
|
||||
if (Graph?.Canvas == null) return;
|
||||
var viewport = Viewport;
|
||||
if (viewport == null) return;
|
||||
|
||||
var cfg = Config;
|
||||
|
||||
// ── 同步缩放范围 ──
|
||||
viewport.MinZoom = cfg.MinZoom;
|
||||
viewport.MaxZoom = cfg.MaxZoom;
|
||||
|
||||
// ── 键盘 Pan ──
|
||||
viewport.TargetPanOffset += senderPanDirection() * cfg.KeyboardPanSpeed * deltaTime;
|
||||
|
||||
// ── 键盘 Zoom ──
|
||||
float zoomInput = senderZoomAxis();
|
||||
if (!Mathf.Approximately(zoomInput, 0f))
|
||||
{
|
||||
float oldZoom = viewport.TargetZoomLevel;
|
||||
float zoomDelta = zoomInput / cfg.ZoomDivisor;
|
||||
viewport.TargetZoomLevel = Mathf.Clamp(
|
||||
viewport.TargetZoomLevel + zoomDelta,
|
||||
viewport.MinZoom, viewport.MaxZoom);
|
||||
ApplyZoomFocus(oldZoom);
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 鼠标按下 =====
|
||||
|
||||
public override void OnPointerDown(GraphInputTool sender, Vector2 canvasPoint, int mouseButton)
|
||||
{
|
||||
if (Graph == null) return;
|
||||
|
||||
if (mouseButton == 2)
|
||||
{
|
||||
_isDragging = true;
|
||||
_activeDragButton = 2;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 鼠标释放 =====
|
||||
|
||||
public override void OnPointerUp(GraphInputTool sender, Vector2 canvasPoint, int mouseButton)
|
||||
{
|
||||
if (_isDragging && _activeDragButton == mouseButton)
|
||||
{
|
||||
_isDragging = false;
|
||||
_activeDragButton = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 拖拽 =====
|
||||
|
||||
public override void OnPointerDrag(GraphInputTool sender, Vector2 canvasPoint, Vector2 delta, int mouseButton)
|
||||
{
|
||||
var viewport = Viewport;
|
||||
if (viewport == null) return;
|
||||
|
||||
if (mouseButton == 2)
|
||||
{
|
||||
// canvas delta 在本帧 Pan 提交后会反向跳变;仅使用 RenderRoot local 平面 delta。
|
||||
viewport.TargetPanOffset += (sender?.PointerLocalDelta ?? Vector2.zero) * Config.DragPanSpeed;
|
||||
Graph.MarkDirty();
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 滚轮 =====
|
||||
|
||||
public override void OnScroll(GraphInputTool sender, Vector2 canvasPoint, Vector2 scrollDelta)
|
||||
{
|
||||
var viewport = Viewport;
|
||||
if (viewport == null) return;
|
||||
|
||||
float oldZoom = viewport.TargetZoomLevel;
|
||||
|
||||
viewport.TargetZoomLevel = Mathf.Clamp(
|
||||
viewport.TargetZoomLevel + scrollDelta.y / Config.ZoomDivisor,
|
||||
viewport.MinZoom, viewport.MaxZoom);
|
||||
|
||||
if (!Mathf.Approximately(oldZoom, viewport.TargetZoomLevel))
|
||||
{
|
||||
viewport.TargetPanOffset += canvasPoint * (oldZoom - viewport.TargetZoomLevel);
|
||||
Graph.MarkDirty();
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 快捷键 =====
|
||||
|
||||
public override void OnAction(GraphInputTool sender, string actionName)
|
||||
{
|
||||
if (Graph?.Canvas == null) return;
|
||||
|
||||
switch (actionName)
|
||||
{
|
||||
case BlueprintInputConstants.FocusHome:
|
||||
Viewport?.ResetView();
|
||||
break;
|
||||
case BlueprintInputConstants.Undo:
|
||||
break;
|
||||
case BlueprintInputConstants.Redo:
|
||||
break;
|
||||
case BlueprintInputConstants.NavigateParent:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 以鼠标焦点为中心缩放(键盘调用 — 自行读取鼠标位置) =====
|
||||
|
||||
private void ApplyZoomFocus(float oldZoom)
|
||||
{
|
||||
var viewport = Viewport;
|
||||
if (viewport == null || Graph?.Canvas == null) return;
|
||||
|
||||
Vector2 canvasCursor = Graph.Canvas.ScreenToCanvas(BlueprintUGUIInputManager.MousePosition);
|
||||
viewport.TargetPanOffset += canvasCursor * (oldZoom - viewport.TargetZoomLevel);
|
||||
}
|
||||
private QuickGraphRenderStyleResolver _styles; private BlueprintViewportController _viewport; private bool _dragging; private int _button = -1; private BlueprintViewportController Viewport => _viewport ?? (_viewport = BlueprintToolProvider.GetToolByType<BlueprintViewportController>(Graph));
|
||||
private Vector2 Pan => GraphInputTool.GetAdapter(Graph)?.PanDirection ?? Vector2.zero; private float Zoom => GraphInputTool.GetAdapter(Graph)?.ZoomAxis ?? 0f;
|
||||
public override void OnInitialize() { _styles = new QuickGraphRenderStyleResolver(Graph); _styles.Changed += _ => ApplyViewport(); ApplyViewport(); }
|
||||
public override void OnConfigChanged() => _styles?.Refresh(); private void ApplyViewport() { if (Viewport == null || _styles == null) return; Graph?.MarkDirty(); }
|
||||
public override void OnUpdate(float dt) { if (Graph?.Canvas == null || Viewport == null || _styles == null) return; var s = _styles.Snapshot.Input; ApplyViewport(); Viewport.TargetPanOffset += Pan * s.KeyboardPanSpeed * dt; var input = Zoom; if (!Mathf.Approximately(input, 0f)) { var old = Viewport.TargetZoomLevel; Viewport.TargetZoomLevel += input * s.KeyboardZoomSpeed; ApplyFocus(old); } }
|
||||
public override void OnPointerDown(GraphInputTool sender, Vector2 point, int button) { if (_styles != null && button == _styles.Snapshot.Input.PanMouseButton) { _dragging = true; _button = button; } }
|
||||
public override void OnPointerUp(GraphInputTool sender, Vector2 point, int button) { if (_dragging && _button == button) { _dragging = false; _button = -1; } }
|
||||
public override void OnPointerDrag(GraphInputTool sender, Vector2 point, Vector2 delta, int button) { if (_dragging && _button == button && Viewport != null) { Viewport.TargetPanOffset += (sender?.PointerLocalDelta ?? Vector2.zero) * _styles.Snapshot.Input.DragPanSpeed; Graph.MarkDirty(); } }
|
||||
public override void OnScroll(GraphInputTool sender, Vector2 point, Vector2 delta) { if (Viewport == null || _styles == null) return; var old = Viewport.TargetZoomLevel; Viewport.TargetZoomLevel += delta.y / _styles.Snapshot.Input.ZoomDivisor; if (!Mathf.Approximately(old, Viewport.TargetZoomLevel)) { Viewport.TargetPanOffset += point * (old - Viewport.TargetZoomLevel); Graph.MarkDirty(); } }
|
||||
private void ApplyFocus(float old) { if (Viewport == null || Graph?.Canvas == null) return; var cursor = Graph.Canvas.ScreenToCanvas(BlueprintUGUIInputManager.MousePosition); Viewport.TargetPanOffset += cursor * (old - Viewport.TargetZoomLevel); }
|
||||
public override void OnDestroy() { _styles?.Dispose(); _styles = null; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace XericLibrary.Runtime.Blueprint.QuickGraph
|
||||
{
|
||||
/// <summary>
|
||||
/// QuickGraph 网格背景配置表 —— 控制 Shader 全部可调参数。
|
||||
/// 材质实例由此配置统一管理,所有调用者通过 <see cref="GetOrCreateMaterial"/> 获取同一实例。
|
||||
/// <see cref="ApplyToMaterial"/> 封装所有材质属性写入(含 _Transform 同步画布平移/缩放)。
|
||||
/// 右键 Project 窗口 → Create/Blueprint/QuickGraph/Grid Background Config 创建资产。
|
||||
/// </summary>
|
||||
[CreateAssetMenu(
|
||||
fileName = "QuickGraphGridBackgroundConfig",
|
||||
menuName = "Xeric Library/Blueprint/QuickGraph/Grid Background Config",
|
||||
order = 3)]
|
||||
[BlueprintTheme("QuickGraph")]
|
||||
public class QuickGraphGridBackgroundConfig : BlueprintToolConfigBase
|
||||
{
|
||||
/// <summary>目标工具类型</summary>
|
||||
public override System.Type TargetToolType
|
||||
{
|
||||
get { return typeof(QuickUGUIGraphGridBackgroundTool); }
|
||||
}
|
||||
|
||||
[Header("材质来源")]
|
||||
[Tooltip("网格 Shader 名称")]
|
||||
public string ShaderName = "XericLibrary/BluePrint/BlueprintBackgound_GridLine";
|
||||
|
||||
[Tooltip("可选:直接指定背景材质。设置后将忽略 ShaderName。")]
|
||||
public Material OverrideMaterial = null;
|
||||
|
||||
[Header("网格参数")]
|
||||
[Tooltip("网格间距(画布单位)。值越小网格越密。")]
|
||||
public float GridSize = 100f;
|
||||
|
||||
[Tooltip("叠加网格密度")]
|
||||
public float GridOverlayPower = 5f;
|
||||
|
||||
[Tooltip("线条阈值。0.5=极粗, 1=极细")]
|
||||
[Range(0.5f, 1f)]
|
||||
public float GridLineThreshold = 0.99f;
|
||||
|
||||
[Tooltip("线条扩展/柔和度。越大线条越宽。")]
|
||||
[Range(0.001f, 1f)]
|
||||
public float GridExp = 0.001f;
|
||||
|
||||
[Header("颜色")]
|
||||
[Tooltip("网格线颜色")]
|
||||
public Color GridColor = Color.white;
|
||||
|
||||
[Tooltip("背景底色")]
|
||||
public Color GridBackgroundColor = Color.black;
|
||||
|
||||
// ─── 运行时缓存的材质实例 ───
|
||||
|
||||
[NonSerialized]
|
||||
private Material _cachedMaterial;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或创建材质实例。
|
||||
/// - 若 OverrideMaterial 不为空,直接返回。
|
||||
/// - 否则按 ShaderName 查找 Shader,创建材质并缓存。
|
||||
/// - 同一配置实例上多次调用返回同一材质对象。
|
||||
/// </summary>
|
||||
public Material GetOrCreateMaterial()
|
||||
{
|
||||
if (OverrideMaterial != null)
|
||||
return OverrideMaterial;
|
||||
|
||||
if (_cachedMaterial != null)
|
||||
return _cachedMaterial;
|
||||
|
||||
if (!string.IsNullOrEmpty(ShaderName))
|
||||
{
|
||||
var shader = Shader.Find(ShaderName);
|
||||
if (shader != null)
|
||||
{
|
||||
_cachedMaterial = new Material(shader);
|
||||
_cachedMaterial.name = "BpGridBackground_Mat";
|
||||
}
|
||||
}
|
||||
|
||||
return _cachedMaterial;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将当前配置的所有参数写入材质。
|
||||
/// <para>包括:
|
||||
/// - _Transform(scaleX, scaleY, offsetX, offsetY):随画布 zoom / pan 同步更新;
|
||||
/// - _GridOverlayPower、_GridLineThreshold、_GridExp;
|
||||
/// - _GridColor、_GridBackgroundColor。</para>
|
||||
/// 每次渲染时调用以保持与蓝图画布的缩放/平移同步。
|
||||
/// </summary>
|
||||
/// <param name="mat">目标材质实例</param>
|
||||
/// <param name="rectSize">背景板 RectTransform 的像素尺寸 (width, height)</param>
|
||||
/// <param name="zoom">当前画布缩放级别</param>
|
||||
/// <param name="panOffset">当前画布平移偏移量</param>
|
||||
public void ApplyToMaterial(Material mat, Vector2 rectSize, float zoom, Vector2 panOffset)
|
||||
{
|
||||
if (mat == null) return;
|
||||
|
||||
float sx = rectSize.x / (zoom * GridSize);
|
||||
float sy = rectSize.y / (zoom * GridSize);
|
||||
float ox = -(panOffset.x + 0.5f * rectSize.x) / (zoom * GridSize);
|
||||
float oy = -(panOffset.y + 0.5f * rectSize.y) / (zoom * GridSize);
|
||||
mat.SetVector("_Transform", new Vector4(sx, sy, ox, oy));
|
||||
|
||||
mat.SetFloat("_GridOverlayPower", GridOverlayPower);
|
||||
mat.SetFloat("_GridLineThreshold", GridLineThreshold);
|
||||
mat.SetFloat("_GridExp", GridExp);
|
||||
|
||||
mat.SetColor("_GridColor", GridColor);
|
||||
mat.SetColor("_GridBackgroundColor", GridBackgroundColor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放此配置创建的材质实例(不释放 OverrideMaterial)。
|
||||
/// </summary>
|
||||
public void ReleaseMaterial()
|
||||
{
|
||||
if (_cachedMaterial != null)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
DestroyImmediate(_cachedMaterial);
|
||||
else
|
||||
#endif
|
||||
Destroy(_cachedMaterial);
|
||||
_cachedMaterial = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace XericLibrary.Runtime.Blueprint.QuickGraph
|
||||
{
|
||||
/// <summary>
|
||||
/// QuickGraph 输入工具配置表 —— 控制平移/缩放灵敏度等可调参数。
|
||||
/// 右键 Project 窗口 → Create/Blueprint/QuickGraph/Input Config 创建资产。
|
||||
/// 拖入蓝图组件的 ToolConfigAssets 列表即可覆盖默认值。
|
||||
/// </summary>
|
||||
[CreateAssetMenu(
|
||||
fileName = "QuickGraphInputConfig",
|
||||
menuName = "Xeric Library/Blueprint/QuickGraph/Input Config",
|
||||
order = 4)]
|
||||
[BlueprintTheme("QuickGraph")]
|
||||
public class QuickGraphInputConfig : BlueprintToolConfigBase
|
||||
{
|
||||
/// <summary>目标工具类型</summary>
|
||||
public override System.Type TargetToolType
|
||||
{
|
||||
get { return typeof(QuickGraphInputTool); }
|
||||
}
|
||||
|
||||
[Header("缩放")]
|
||||
[Tooltip("滚轮缩放除数。公式:zoom += scrollDelta / ZoomDivisor。\n旧输入系统 scrollDelta.y 约 ±3,默认 30 = 每格 ±0.1")]
|
||||
[Range(1f, 200f)]
|
||||
public float ZoomDivisor = 30f;
|
||||
|
||||
[Header("拖拽平移")]
|
||||
[Tooltip("中键拖拽平移速度系数。1=鼠标移动 1px,画布移动 1px(经缩放校正)")]
|
||||
[Range(0.1f, 10f)]
|
||||
public float DragPanSpeed = 1f;
|
||||
|
||||
[Header("缩放范围")]
|
||||
[Tooltip("最小缩放级别")]
|
||||
[Range(0.01f, 1f)]
|
||||
public float MinZoom = 0.1f;
|
||||
[Tooltip("最大缩放级别")]
|
||||
[Range(1f, 10f)]
|
||||
public float MaxZoom = 3f;
|
||||
|
||||
[Header("键盘")]
|
||||
[Tooltip("键盘方向键平移速度(画布单位/秒)")]
|
||||
[Range(50f, 2000f)]
|
||||
public float KeyboardPanSpeed = 600f;
|
||||
|
||||
[Tooltip("键盘 +/- 缩放速度(与 ZoomDivisor 配合,公式:zoom += 1 / ZoomDivisor)")]
|
||||
[Range(0.005f, 0.5f)]
|
||||
public float KeyboardZoomSpeed = 0.05f;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4735b93342f4fde4a8ed46dd28b46d4c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,59 +0,0 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace XericLibrary.Runtime.Blueprint.QuickGraph
|
||||
{
|
||||
/// <summary>
|
||||
/// QuickGraph 节点渲染配置表 —— 控制节点矩形、边框、圆角等视觉参数。
|
||||
/// 右键 Project 窗口 → Create/Blueprint/QuickGraph/Node Render Config 创建资产。
|
||||
/// 拖入蓝图组件的 ToolConfigAssets 列表即可覆盖默认值。
|
||||
/// </summary>
|
||||
[CreateAssetMenu(
|
||||
fileName = "QuickGraphNodeRenderConfig",
|
||||
menuName = "Xeric Library/Blueprint/QuickGraph/Node Render Config",
|
||||
order = 1)]
|
||||
[BlueprintTheme("QuickGraph")]
|
||||
public class QuickGraphNodeRenderConfig : BlueprintToolConfigBase
|
||||
{
|
||||
/// <summary>目标工具类型</summary>
|
||||
public override System.Type TargetToolType => typeof(NodeCanvasRenderTool);
|
||||
|
||||
[Header("尺寸")]
|
||||
[Tooltip("节点宽度(像素)")]
|
||||
public float NodeWidth = 90f;
|
||||
|
||||
[Tooltip("节点高度(像素)")]
|
||||
public float NodeHeight = 130f;
|
||||
|
||||
[Header("边框")]
|
||||
[Tooltip("边框厚度(像素)")]
|
||||
public float BorderThickness = 3f;
|
||||
|
||||
[Header("圆角")]
|
||||
[Tooltip("圆角大小(0~1 归一化)")]
|
||||
[Range(0f, 0.5f)]
|
||||
public float ChamferSize = 0.2f;
|
||||
|
||||
[Tooltip("圆角细分段数")]
|
||||
[Range(1, 16)]
|
||||
public int ChamferSegments = 4;
|
||||
|
||||
[Header("颜色")]
|
||||
[Tooltip("节点默认背景色")]
|
||||
public Color DefaultBackgroundColor = new Color(0.16f, 0.16f, 0.16f);
|
||||
[Tooltip("节点默认文字色")]
|
||||
public Color DefaultTextColor = Color.white;
|
||||
|
||||
[Header("文本")]
|
||||
[Tooltip("节点标题字号")]
|
||||
public float TitleFontSize = 14f;
|
||||
[Tooltip("节点标题字体(留空使用默认 TMP 字体)")]
|
||||
public TMPro.TMP_FontAsset TitleFont;
|
||||
|
||||
[Header("已弃用的 LOD 兼容字段")]
|
||||
[Obsolete("节点 LOD 已迁移至 BlueprintRenderLodConfig(绝对 Zoom + 滞回),此字段不再参与渲染。")]
|
||||
[HideInInspector] public float Lod0Threshold = 0.3f;
|
||||
[Obsolete("节点 LOD 已迁移至 BlueprintRenderLodConfig(绝对 Zoom + 滞回),此字段不再参与渲染。")]
|
||||
[HideInInspector] public float Lod1Threshold = 0.7f;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8038bf45323375b46a3a2820ddfe212e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,67 +0,0 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using XericLibrary.Runtime.UIGraph;
|
||||
|
||||
namespace XericLibrary.Runtime.Blueprint.QuickGraph
|
||||
{
|
||||
/// <summary>
|
||||
/// QuickGraph 连线渲染配置表 —— 控制贝塞尔曲线宽度、箭头形状等全部视觉参数。
|
||||
/// 右键 Project 窗口 → Create/Blueprint/QuickGraph/Wire Render Config 创建资产。
|
||||
/// 拖入蓝图组件的 ToolConfigAssets 列表即可覆盖默认值。
|
||||
/// </summary>
|
||||
[CreateAssetMenu(
|
||||
fileName = "QuickGraphWireRenderConfig",
|
||||
menuName = "Xeric Library/Blueprint/QuickGraph/Wire Render Config",
|
||||
order = 2)]
|
||||
[BlueprintTheme("QuickGraph")]
|
||||
public class QuickGraphWireRenderConfig : BlueprintToolConfigBase
|
||||
{
|
||||
/// <summary>目标工具类型</summary>
|
||||
public override System.Type TargetToolType => typeof(WireCanvasRenderTool);
|
||||
|
||||
[Header("连线")]
|
||||
[Tooltip("连线宽度(像素)")]
|
||||
[Range(1f, 20f)]
|
||||
public float WireWidth = 5f;
|
||||
|
||||
[Tooltip("线条额外点击命中半径(画布单位);实际半径至少为线宽的一半)。")]
|
||||
[Range(0f, 40f)]
|
||||
public float HitTestRadius = 8f;
|
||||
|
||||
[Header("贝塞尔手柄")]
|
||||
[Tooltip("源端手柄从端口伸出的距离(画布单位)。数值越大曲线越平缓。")]
|
||||
[Range(10f, 500f)]
|
||||
public float SourceHandleLength = 80f;
|
||||
[Tooltip("目标端手柄从端口伸出的距离(画布单位)。")]
|
||||
[Range(10f, 500f)]
|
||||
public float TargetHandleLength = 80f;
|
||||
|
||||
[Header("箭头")]
|
||||
[Tooltip("箭头形状")]
|
||||
public ArrowShape ArrowShape = ArrowShape.Triangle;
|
||||
|
||||
[Tooltip("箭头是否反向")]
|
||||
public bool ArrowReversed = false;
|
||||
|
||||
[Tooltip("箭头在曲线上的位置(0=起点, 1=终点)")]
|
||||
[Range(0f, 1f)]
|
||||
public float ArrowProgress = 1f;
|
||||
|
||||
[Tooltip("箭头宽度(垂直切线方向,像素)")]
|
||||
[Range(4f, 40f)]
|
||||
public float ArrowWidth = 12f;
|
||||
|
||||
[Tooltip("箭头高度(沿切线方向,像素)")]
|
||||
[Range(4f, 40f)]
|
||||
public float ArrowHeight = 10f;
|
||||
|
||||
[Tooltip("深度补偿:-1=尾部对齐曲线点,0=中心对齐,1=头部对齐曲线点")]
|
||||
[Range(-1f, 1f)]
|
||||
public float ArrowDepthCompensation = -1f;
|
||||
|
||||
[Header("曲线质量")]
|
||||
[Tooltip("贝塞尔曲线细分段数")]
|
||||
[Range(8, 64)]
|
||||
public int TessellationSegments = 32;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c95f6e1f1ece40444839f0a29ac371e5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -7,35 +7,27 @@ using PrimitiveType = XericLibrary.Runtime.UIGraph.PrimitiveType;
|
||||
|
||||
namespace XericLibrary.Runtime.Blueprint.QuickGraph
|
||||
{
|
||||
[BlueprintTool(phase: ToolPhase.Render, order: 100)]
|
||||
[BlueprintTheme("QuickGraph")]
|
||||
[BlueprintTool(phase: ToolPhase.Render, order: 100)] [BlueprintTheme("QuickGraph")]
|
||||
public class NodeCanvasRenderTool : BlueprintCanvasRenderTool<BlueprintPrimitiveRenderer>
|
||||
{
|
||||
private QuickGraphNodeRenderConfig _defaultConfig;
|
||||
private QuickGraphRenderStyleResolver _styleResolver;
|
||||
private QuickGraphRenderStyleResolver _styles;
|
||||
private readonly Dictionary<string, (ulong chunkID, TMP_Text text)> _nodeTexts = new Dictionary<string, (ulong, TMP_Text)>();
|
||||
private readonly HashSet<string> _staleTextCandidates = new HashSet<string>();
|
||||
private QuickGraphNodeRenderConfig Config
|
||||
|
||||
public override void OnInitialize()
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ToolConfig is QuickGraphNodeRenderConfig external) return external;
|
||||
if (_defaultConfig == null) _defaultConfig = ScriptableObject.CreateInstance<QuickGraphNodeRenderConfig>();
|
||||
return _defaultConfig;
|
||||
}
|
||||
base.OnInitialize();
|
||||
_styles = new QuickGraphRenderStyleResolver(Graph);
|
||||
_styles.Changed += _ => SyncAllNodeGeometry();
|
||||
SyncAllNodeGeometry();
|
||||
}
|
||||
|
||||
public override void OnInitialize() { base.OnInitialize(); _styleResolver = new QuickGraphRenderStyleResolver(Graph, nodeFallback: ConfigStyle); SyncAllNodeGeometry(); }
|
||||
public override void OnConfigChanged()
|
||||
{
|
||||
_styleResolver?.Refresh(); SyncAllNodeGeometry();
|
||||
_styles?.Refresh();
|
||||
base.OnConfigChanged();
|
||||
}
|
||||
private QuickGraphNodeRenderStyle ConfigStyle()
|
||||
{
|
||||
var cfg = Config;
|
||||
return new QuickGraphNodeRenderStyle { Width = cfg.NodeWidth, Height = cfg.NodeHeight, Border = cfg.BorderThickness, Chamfer = cfg.ChamferSize, ChamferSegments = cfg.ChamferSegments, TitleFontSize = cfg.TitleFontSize };
|
||||
}
|
||||
|
||||
protected override BlueprintPrimitiveRenderer BuildChunkRenderer(GameObject go) => go.AddComponent<BlueprintPrimitiveRenderer>();
|
||||
protected override void ClearChunkData(BlueprintPrimitiveRenderer renderer) => renderer.ClearAll();
|
||||
protected override void SetChunkDirty(BlueprintPrimitiveRenderer renderer) => renderer.SetPrimitiveDirty(0);
|
||||
@@ -43,76 +35,161 @@ namespace XericLibrary.Runtime.Blueprint.QuickGraph
|
||||
|
||||
protected override void AppendElementData(BlueprintPrimitiveRenderer renderer, IBlueprintElement element, ulong chunkID)
|
||||
{
|
||||
if (!(element is BlueprintNode node)) return;
|
||||
var cfg = Config; var style = _styleResolver != null ? _styleResolver.Snapshot.Node : new QuickGraphNodeRenderStyle { Width=cfg.NodeWidth, Height=cfg.NodeHeight, Border=cfg.BorderThickness, Chamfer=cfg.ChamferSize, ChamferSegments=cfg.ChamferSegments, TitleFontSize=cfg.TitleFontSize }; var lod = CurrentElementSubmission.LOD;
|
||||
CalculatePortPositions(node, style.Width, style.Height);
|
||||
var local = CurrentElementSubmission.ChunkLocalPosition;
|
||||
float border = lod == BlueprintLodLevel.Minimal ? 0f : style.Border;
|
||||
Color bg = lod == BlueprintLodLevel.Minimal ? node.NodeColor : node.NodeBGColor;
|
||||
Color edge = node.NodeColor;
|
||||
renderer.AddPrimitive(new PrimitiveCacheEntry { type = PrimitiveType.Rectangle, sizeMode = SizeMode.InscribedEllipse, center = local, size = new Vector2(style.Width, style.Height), @params = new PrimitiveParams { axisScaleX = 1f, axisScaleY = 1f, sideCount = 4, chamferSize = style.Chamfer, chamferSegments = style.ChamferSegments, bgColor = new Color32(0,0,0,0), centerColor = bg, borderColor = edge, borderThickness = border } });
|
||||
if (lod == BlueprintLodLevel.Full) UpdateNodeText(node, chunkID, local, style.Width, style.Height, border, style.TitleFontSize, cfg); else HideNodeText(node.ElementId);
|
||||
if (!(element is BlueprintNode node))
|
||||
return;
|
||||
|
||||
var graphStyle = _styles.Snapshot;
|
||||
var nodeStyle = graphStyle.Node;
|
||||
var lod = graphStyle.GetLod(CurrentElementSubmission.LOD);
|
||||
CalculatePortPositions(node, nodeStyle.Width, nodeStyle.Height, graphStyle.Port);
|
||||
|
||||
float border = lod.NodeBorder;
|
||||
Color fillColor = node.GetStyleValue<Color>(QuickGraphStylePaths.NodeFillColor);
|
||||
Color borderColor = node.GetStyleValue<Color>(QuickGraphStylePaths.NodeBorderColor);
|
||||
Color titleColor = node.GetStyleValue<Color>(QuickGraphStylePaths.NodeTitleColor);
|
||||
Color centerColor = lod.UseNodeColorAsBackground ? borderColor : fillColor;
|
||||
|
||||
renderer.AddPrimitive(new PrimitiveCacheEntry
|
||||
{
|
||||
type = PrimitiveType.Rectangle,
|
||||
sizeMode = SizeMode.InscribedEllipse,
|
||||
center = CurrentElementSubmission.ChunkLocalPosition,
|
||||
size = new Vector2(nodeStyle.Width, nodeStyle.Height),
|
||||
@params = new PrimitiveParams
|
||||
{
|
||||
axisScaleX = nodeStyle.AxisScaleX,
|
||||
axisScaleY = nodeStyle.AxisScaleY,
|
||||
sideCount = nodeStyle.SideCount,
|
||||
chamferSize = nodeStyle.Chamfer,
|
||||
chamferSegments = nodeStyle.ChamferSegments,
|
||||
bgColor = nodeStyle.OuterColor,
|
||||
centerColor = centerColor,
|
||||
borderColor = borderColor,
|
||||
borderThickness = border
|
||||
}
|
||||
});
|
||||
|
||||
if (lod.ShowTitle && (CurrentElementSubmission.LOD != BlueprintLodLevel.Full || nodeStyle.ShowTitleFull))
|
||||
UpdateNodeText(node, chunkID, CurrentElementSubmission.ChunkLocalPosition, nodeStyle, border, titleColor);
|
||||
else
|
||||
HideNodeText(node.ElementId);
|
||||
}
|
||||
|
||||
private void SyncAllNodeGeometry()
|
||||
{
|
||||
if (Graph == null) return;
|
||||
var cfg = Config; var style = _styleResolver != null ? _styleResolver.Snapshot.Node : new QuickGraphNodeRenderStyle { Width=cfg.NodeWidth, Height=cfg.NodeHeight };
|
||||
if (Graph == null || _styles == null)
|
||||
return;
|
||||
|
||||
var style = _styles.Snapshot;
|
||||
foreach (var node in Graph.Nodes)
|
||||
{
|
||||
var oldSize = node.NodeSize;
|
||||
CalculatePortPositions(node, style.Width, style.Height);
|
||||
if (oldSize != node.NodeSize) Graph.UpdateNodeGeometry(node);
|
||||
var old = node.NodeSize;
|
||||
CalculatePortPositions(node, style.Node.Width, style.Node.Height, style.Port);
|
||||
if (old != node.NodeSize)
|
||||
Graph.UpdateNodeGeometry(node);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CalculatePortPositions(BlueprintNode node, float width, float height)
|
||||
private static void CalculatePortPositions(BlueprintNode node, float width, float height, QuickGraphPortRenderStyle style)
|
||||
{
|
||||
// Position 语义为视觉中心;渲染配置尺寸必须同步给数据模型,供 BoundingBox / RTree 使用。
|
||||
node.NodeSize = new Vector2(width, height);
|
||||
for (int i=0;i<node.InputPorts.Count;i++) node.InputPorts[i].RelativePosition = new Vector2(-width*.5f, height*(.5f-(float)(i+1)/(node.InputPorts.Count+1)));
|
||||
for (int i=0;i<node.OutputPorts.Count;i++) node.OutputPorts[i].RelativePosition = new Vector2(width*.5f, height*(.5f-(float)(i+1)/(node.OutputPorts.Count+1)));
|
||||
for (int i = 0; i < node.InputPorts.Count; i++)
|
||||
node.InputPorts[i].RelativePosition = new Vector2(width * style.InputSide, height * Mathf.Lerp(style.TopRatio, style.BottomRatio, node.InputPorts.Count == 1 ? .5f : (float)i / (node.InputPorts.Count - 1)));
|
||||
for (int i = 0; i < node.OutputPorts.Count; i++)
|
||||
node.OutputPorts[i].RelativePosition = new Vector2(width * style.OutputSide, height * Mathf.Lerp(style.TopRatio, style.BottomRatio, node.OutputPorts.Count == 1 ? .5f : (float)i / (node.OutputPorts.Count - 1)));
|
||||
}
|
||||
|
||||
protected override void BeginChunkSubmission(ulong chunk, BlueprintPrimitiveRenderer renderer)
|
||||
{
|
||||
foreach (var pair in _nodeTexts)
|
||||
if (pair.Value.chunkID == chunk) _staleTextCandidates.Add(pair.Key);
|
||||
if (pair.Value.chunkID == chunk)
|
||||
_staleTextCandidates.Add(pair.Key);
|
||||
}
|
||||
|
||||
protected override void EndChunkSubmission(ulong chunk, BlueprintPrimitiveRenderer renderer)
|
||||
{
|
||||
var remove = new List<string>();
|
||||
foreach (var id in _staleTextCandidates)
|
||||
if (_nodeTexts.TryGetValue(id, out var item) && item.chunkID == chunk) remove.Add(id);
|
||||
if (_nodeTexts.TryGetValue(id, out var item) && item.chunkID == chunk)
|
||||
remove.Add(id);
|
||||
foreach (var id in remove)
|
||||
{
|
||||
if (_nodeTexts[id].text != null) Object.Destroy(_nodeTexts[id].text.gameObject);
|
||||
if (_nodeTexts[id].text != null)
|
||||
Object.Destroy(_nodeTexts[id].text.gameObject);
|
||||
_nodeTexts.Remove(id);
|
||||
}
|
||||
_staleTextCandidates.Clear();
|
||||
}
|
||||
private void UpdateNodeText(BlueprintNode node, ulong chunk, Vector2 local, float width, float height, float border, float titleFontSize, QuickGraphNodeRenderConfig cfg)
|
||||
|
||||
private void UpdateNodeText(BlueprintNode node, ulong chunk, Vector2 local, QuickGraphNodeRenderStyle style, float border, Color titleColor)
|
||||
{
|
||||
_staleTextCandidates.Remove(node.ElementId);
|
||||
TMP_Text text;
|
||||
if (_nodeTexts.TryGetValue(node.ElementId, out var old) && old.chunkID == chunk && old.text != null) text = old.text;
|
||||
if (_nodeTexts.TryGetValue(node.ElementId, out var old) && old.chunkID == chunk && old.text != null)
|
||||
{
|
||||
text = old.text;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (old.text != null) Object.Destroy(old.text.gameObject);
|
||||
var go = new GameObject($"__Text_{node.ElementId}", typeof(RectTransform)); go.hideFlags = HideFlags.HideAndDontSave; go.transform.SetParent(rendererTransform(chunk), false);
|
||||
text = go.AddComponent<TextMeshProUGUI>(); text.alignment = TextAlignmentOptions.Center; _nodeTexts[node.ElementId] = (chunk, text);
|
||||
if (old.text != null)
|
||||
Object.Destroy(old.text.gameObject);
|
||||
var go = new GameObject("__Text_" + node.ElementId, typeof(RectTransform));
|
||||
go.hideFlags = HideFlags.HideAndDontSave;
|
||||
go.transform.SetParent(GetOrCreateChunk(chunk).transform, false);
|
||||
text = go.AddComponent<TextMeshProUGUI>();
|
||||
_nodeTexts[node.ElementId] = (chunk, text);
|
||||
}
|
||||
text.text=node.NodeTitle; text.color=node.NodeTextColor; text.fontSize=titleFontSize; if(cfg.TitleFont!=null) text.font=cfg.TitleFont;
|
||||
var rt=text.rectTransform; var chunkPivot = text.transform.parent is RectTransform parent ? parent.pivot : new Vector2(.5f, .5f);
|
||||
rt.anchorMin=chunkPivot;rt.anchorMax=chunkPivot;rt.pivot=new Vector2(.5f,.5f);rt.anchoredPosition=local;rt.sizeDelta=new Vector2(width-border*2,height-border*2);text.gameObject.SetActive(true);rt.SetAsLastSibling();
|
||||
|
||||
text.text = node.NodeTitle;
|
||||
text.color = titleColor;
|
||||
text.fontSize = style.TitleFontSize;
|
||||
text.font = style.TitleFont;
|
||||
text.alignment = style.TitleAlignment;
|
||||
var rt = text.rectTransform;
|
||||
var pivot = text.transform.parent is RectTransform parent ? parent.pivot : new Vector2(.5f, .5f);
|
||||
rt.anchorMin = pivot;
|
||||
rt.anchorMax = pivot;
|
||||
rt.pivot = new Vector2(.5f, .5f);
|
||||
rt.anchoredPosition = local;
|
||||
rt.sizeDelta = new Vector2(style.Width - border * 2, style.Height - border * 2);
|
||||
text.gameObject.SetActive(true);
|
||||
rt.SetAsLastSibling();
|
||||
}
|
||||
private Transform rendererTransform(ulong chunk) => GetOrCreateChunk(chunk).transform;
|
||||
private void HideNodeText(string id) { if (_nodeTexts.TryGetValue(id,out var item) && item.text != null) item.text.gameObject.SetActive(false); }
|
||||
|
||||
private void HideNodeText(string id)
|
||||
{
|
||||
if (_nodeTexts.TryGetValue(id, out var item) && item.text != null)
|
||||
item.text.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
protected override void OnChunkRecycled(ulong chunk, BlueprintPrimitiveRenderer renderer)
|
||||
{
|
||||
_staleTextCandidates.Clear();
|
||||
var remove=new List<string>(); foreach(var pair in _nodeTexts) if(pair.Value.chunkID==chunk) remove.Add(pair.Key);
|
||||
foreach(var id in remove) { if(_nodeTexts[id].text!=null) Object.Destroy(_nodeTexts[id].text.gameObject); _nodeTexts.Remove(id); }
|
||||
var remove = new List<string>();
|
||||
foreach (var pair in _nodeTexts)
|
||||
if (pair.Value.chunkID == chunk)
|
||||
remove.Add(pair.Key);
|
||||
foreach (var id in remove)
|
||||
{
|
||||
if (_nodeTexts[id].text != null)
|
||||
Object.Destroy(_nodeTexts[id].text.gameObject);
|
||||
_nodeTexts.Remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DestroyAllChunks()
|
||||
{
|
||||
foreach (var pair in _nodeTexts)
|
||||
if (pair.Value.text != null)
|
||||
Object.Destroy(pair.Value.text.gameObject);
|
||||
_nodeTexts.Clear();
|
||||
base.DestroyAllChunks();
|
||||
}
|
||||
|
||||
public override void OnDestroy()
|
||||
{
|
||||
_styles?.Dispose();
|
||||
_styles = null;
|
||||
base.OnDestroy();
|
||||
}
|
||||
protected override void DestroyAllChunks() { foreach(var pair in _nodeTexts) if(pair.Value.text!=null) Object.Destroy(pair.Value.text.gameObject); _nodeTexts.Clear(); base.DestroyAllChunks(); }
|
||||
public override void OnDestroy() { _styleResolver?.Dispose(); _styleResolver = null; base.OnDestroy(); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using XericLibrary.Runtime.Blueprint;
|
||||
using XericLibrary.Runtime.SuperStyleSheet;
|
||||
@@ -6,64 +9,81 @@ using XericLibrary.Runtime.UIGraph;
|
||||
|
||||
namespace XericLibrary.Runtime.Blueprint.QuickGraph
|
||||
{
|
||||
[Flags] public enum QuickGraphRenderStyleChange { None = 0, NodeGeometry = 1, NodeAppearance = 2, WireGeometry = 4, WireAppearance = 8, GridAppearance = 16, All = NodeGeometry | NodeAppearance | WireGeometry | WireAppearance | GridAppearance }
|
||||
public struct QuickGraphNodeRenderStyle { public float Width, Height, Border, Chamfer; public int ChamferSegments; public float TitleFontSize; }
|
||||
public struct QuickGraphWireRenderStyle { public float Width, HitRadius, SourceHandle, TargetHandle, ArrowWidth, ArrowHeight, ArrowProgress, ArrowDepth; public int Tessellation; public ArrowShape ArrowShape; public bool ArrowReversed; }
|
||||
public struct QuickGraphGridRenderStyle { public Material Material; public string ShaderName; public float Size, OverlayPower, Threshold, Exp; public Color Color, Background; }
|
||||
public sealed class QuickGraphRenderStyleSnapshot { public int Revision; public QuickGraphRenderStyleChange Changes; public QuickGraphNodeRenderStyle Node; public QuickGraphWireRenderStyle Wire; public QuickGraphGridRenderStyle Grid; }
|
||||
[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; }
|
||||
|
||||
/// <summary>每次样式变更只解析一次;渲染热路径只消费强类型快照。</summary>
|
||||
public sealed class QuickGraphRenderStyleResolver : IDisposable
|
||||
{
|
||||
private readonly BlueprintGraph _graph;
|
||||
private readonly Func<QuickGraphNodeRenderStyle> _nodeFallback;
|
||||
private readonly Func<QuickGraphWireRenderStyle> _wireFallback;
|
||||
private readonly Func<QuickGraphGridRenderStyle> _gridFallback;
|
||||
private static readonly ConditionalWeakTable<BlueprintGraph, ValidationState> _validationStates = new ConditionalWeakTable<BlueprintGraph, ValidationState>();
|
||||
private sealed class ValidationState { public string LastError; }
|
||||
public QuickGraphRenderStyleSnapshot Snapshot { get; private set; }
|
||||
public event Action<QuickGraphRenderStyleSnapshot> Changed;
|
||||
public QuickGraphRenderStyleResolver(BlueprintGraph graph) { _graph = graph; _graph.RenderStyleChanged += Refresh; StyleManager.OnStylesReloaded += Refresh; Refresh(); }
|
||||
private T Value<T>(string path) => _graph.GetRenderStyleValue<T>(path);
|
||||
private QuickGraphLodRenderStyle Lod(string name) => new QuickGraphLodRenderStyle { EnterZoom = Value<float>(QuickGraphStylePaths.Lod(name, "enterZoom")), ExitZoom = Value<float>(QuickGraphStylePaths.Lod(name, "exitZoom")), WireTessellationMultiplier = Value<float>(QuickGraphStylePaths.Lod(name, "wireTessellationMultiplier")), NodeBorder = Value<float>(QuickGraphStylePaths.Lod(name, "nodeBorder")), UseNodeColorAsBackground = Value<bool>(QuickGraphStylePaths.Lod(name, "useNodeColorAsBackground")), ShowTitle = Value<bool>(QuickGraphStylePaths.Lod(name, "showTitle")) };
|
||||
|
||||
public QuickGraphRenderStyleResolver(BlueprintGraph graph, Func<QuickGraphNodeRenderStyle> nodeFallback = null, Func<QuickGraphWireRenderStyle> wireFallback = null, Func<QuickGraphGridRenderStyle> gridFallback = null)
|
||||
{
|
||||
_graph = graph;
|
||||
_nodeFallback = nodeFallback ?? DefaultNode;
|
||||
_wireFallback = wireFallback ?? DefaultWire;
|
||||
_gridFallback = gridFallback ?? DefaultGrid;
|
||||
_graph.RenderStyleChanged += Refresh;
|
||||
StyleManager.OnStylesReloaded += Refresh;
|
||||
Refresh();
|
||||
}
|
||||
|
||||
private static QuickGraphNodeRenderStyle DefaultNode() => new QuickGraphNodeRenderStyle { Width = 90f, Height = 130f, Border = 3f, Chamfer = .2f, ChamferSegments = 4, TitleFontSize = 14f };
|
||||
private static QuickGraphWireRenderStyle DefaultWire() => new QuickGraphWireRenderStyle { Width = 5f, HitRadius = 8f, SourceHandle = 80f, TargetHandle = 80f, ArrowWidth = 12f, ArrowHeight = 10f, ArrowProgress = 1f, ArrowDepth = -1f, Tessellation = 32, ArrowShape = ArrowShape.Triangle };
|
||||
private static QuickGraphGridRenderStyle DefaultGrid() => new QuickGraphGridRenderStyle { ShaderName = "XericLibrary/BluePrint/BlueprintBackgound_GridLine", Size = 100f, OverlayPower = 5f, Threshold = .99f, Exp = .001f, Color = Color.white, Background = Color.black };
|
||||
|
||||
// 样式项存在即优先使用;只有项缺失时才使用旧 ToolConfig 回退值。
|
||||
private T Value<T>(string path, T fallback) { var value = _graph.GetRenderStyle(path); return value == null ? fallback : value.GetValueAs<T>(); }
|
||||
public void Refresh()
|
||||
{
|
||||
var node = _nodeFallback();
|
||||
node = new QuickGraphNodeRenderStyle { Width = Value("/quickgraph/node/width", node.Width), Height = Value("/quickgraph/node/height", node.Height), Border = Value("/quickgraph/node/border", node.Border), Chamfer = Value("/quickgraph/node/chamfer", node.Chamfer), ChamferSegments = Value("/quickgraph/node/chamferSegments", node.ChamferSegments), TitleFontSize = Value("/quickgraph/node/titleFontSize", node.TitleFontSize) };
|
||||
var wire = _wireFallback();
|
||||
wire = new QuickGraphWireRenderStyle { Width = Value("/quickgraph/wire/width", wire.Width), HitRadius = Value("/quickgraph/wire/hitRadius", wire.HitRadius), SourceHandle = Value("/quickgraph/wire/sourceHandle", wire.SourceHandle), TargetHandle = Value("/quickgraph/wire/targetHandle", wire.TargetHandle), ArrowWidth = Value("/quickgraph/wire/arrowWidth", wire.ArrowWidth), ArrowHeight = Value("/quickgraph/wire/arrowHeight", wire.ArrowHeight), ArrowProgress = Value("/quickgraph/wire/arrowProgress", wire.ArrowProgress), ArrowDepth = Value("/quickgraph/wire/arrowDepth", wire.ArrowDepth), Tessellation = Value("/quickgraph/wire/tessellation", wire.Tessellation), ArrowShape = Value("/quickgraph/wire/arrowShape", wire.ArrowShape), ArrowReversed = Value("/quickgraph/wire/arrowReversed", wire.ArrowReversed) };
|
||||
var grid = _gridFallback();
|
||||
grid = new QuickGraphGridRenderStyle { Material = Value("/quickgraph/grid/material", grid.Material), ShaderName = Value("/quickgraph/grid/shader", grid.ShaderName), Size = Value("/quickgraph/grid/size", grid.Size), OverlayPower = Value("/quickgraph/grid/overlayPower", grid.OverlayPower), Threshold = Value("/quickgraph/grid/threshold", grid.Threshold), Exp = Value("/quickgraph/grid/exp", grid.Exp), Color = Value("/quickgraph/grid/color", grid.Color), Background = Value("/quickgraph/grid/background", grid.Background) };
|
||||
|
||||
var old = Snapshot;
|
||||
var changes = old == null ? QuickGraphRenderStyleChange.All : Classify(old, node, wire, grid);
|
||||
Snapshot = new QuickGraphRenderStyleSnapshot { Revision = old == null ? 1 : old.Revision + 1, Changes = changes, Node = node, Wire = wire, Grid = grid };
|
||||
if (changes != QuickGraphRenderStyleChange.None) Changed?.Invoke(Snapshot);
|
||||
StyleManager.EnsureNamespaceLoaded(_graph.RenderStyleContext == null ? "default" : _graph.RenderStyleContext.BaseNamespace);
|
||||
var node = new QuickGraphNodeRenderStyle { Width = Value<float>(QuickGraphStylePaths.NodeWidth), Height = Value<float>(QuickGraphStylePaths.NodeHeight), Border = Value<float>(QuickGraphStylePaths.NodeBorder), Chamfer = Value<float>(QuickGraphStylePaths.NodeChamfer), ChamferSegments = Value<int>(QuickGraphStylePaths.NodeChamferSegments), TitleFontSize = Value<float>(QuickGraphStylePaths.NodeTitleFontSize), TitleFont = Value<TMP_FontAsset>(QuickGraphStylePaths.NodeTitleFont), TitleAlignment = Value<TextAlignmentOptions>(QuickGraphStylePaths.NodeTitleAlignment), ShowTitleFull = Value<bool>(QuickGraphStylePaths.NodeShowTitleFull), OuterColor = Value<Color>(QuickGraphStylePaths.NodeOuterColor), AxisScaleX = Value<float>(QuickGraphStylePaths.NodeAxisScaleX), AxisScaleY = Value<float>(QuickGraphStylePaths.NodeAxisScaleY), SideCount = Value<int>(QuickGraphStylePaths.NodeSideCount) };
|
||||
var port = new QuickGraphPortRenderStyle { InputSide = Value<float>(QuickGraphStylePaths.PortInputSide), OutputSide = Value<float>(QuickGraphStylePaths.PortOutputSide), TopRatio = Value<float>(QuickGraphStylePaths.PortTopRatio), BottomRatio = Value<float>(QuickGraphStylePaths.PortBottomRatio) };
|
||||
var wire = new QuickGraphWireRenderStyle { Width = Value<float>(QuickGraphStylePaths.WireWidth), HitRadius = Value<float>(QuickGraphStylePaths.WireHitRadius), SourceHandle = Value<float>(QuickGraphStylePaths.WireSourceHandle), TargetHandle = Value<float>(QuickGraphStylePaths.WireTargetHandle), ArrowWidth = Value<float>(QuickGraphStylePaths.WireArrowWidth), ArrowHeight = Value<float>(QuickGraphStylePaths.WireArrowHeight), ArrowProgress = Value<float>(QuickGraphStylePaths.WireArrowProgress), ArrowDepth = Value<float>(QuickGraphStylePaths.WireArrowDepth), Tessellation = Value<int>(QuickGraphStylePaths.WireTessellation), MinTessellation = Value<int>(QuickGraphStylePaths.WireMinTessellation), ArrowShape = Value<ArrowShape>(QuickGraphStylePaths.WireArrowShape), ArrowReversed = Value<bool>(QuickGraphStylePaths.WireArrowReversed), UseNodeColorGradient = Value<bool>(QuickGraphStylePaths.WireUseNodeColorGradient), Color = Value<Color>(QuickGraphStylePaths.WireColor) };
|
||||
var grid = new QuickGraphGridRenderStyle { Material = Value<Material>(QuickGraphStylePaths.GridMaterial), ShaderName = Value<string>(QuickGraphStylePaths.GridShader), Size = Value<float>(QuickGraphStylePaths.GridSize), OverlayPower = Value<float>(QuickGraphStylePaths.GridOverlayPower), Threshold = Value<float>(QuickGraphStylePaths.GridThreshold), Exp = Value<float>(QuickGraphStylePaths.GridExp), Color = Value<Color>(QuickGraphStylePaths.GridColor), Background = Value<Color>(QuickGraphStylePaths.GridBackground), RaycastTarget = Value<bool>(QuickGraphStylePaths.GridRaycastTarget), Maskable = Value<bool>(QuickGraphStylePaths.GridMaskable), LodTransitionThreshold = Value<float>(QuickGraphStylePaths.GridLodTransitionThreshold), LodTransitionStart = Value<float>(QuickGraphStylePaths.GridLodTransitionStart), LodTransitionEnd = Value<float>(QuickGraphStylePaths.GridLodTransitionEnd), CellCenter = Value<Vector2>(QuickGraphStylePaths.GridCellCenter), LineShapeScale = Value<float>(QuickGraphStylePaths.GridLineShapeScale) };
|
||||
var input = new QuickGraphInputRenderStyle { ZoomDivisor = Value<float>(QuickGraphStylePaths.InputZoomDivisor), DragPanSpeed = Value<float>(QuickGraphStylePaths.InputDragPanSpeed), MinZoom = Value<float>(QuickGraphStylePaths.InputMinZoom), MaxZoom = Value<float>(QuickGraphStylePaths.InputMaxZoom), DefaultZoom = Value<float>(QuickGraphStylePaths.InputDefaultZoom), FocusZoom = Value<float>(QuickGraphStylePaths.InputFocusZoom), FocusPadding = Value<float>(QuickGraphStylePaths.InputFocusPadding), KeyboardPanSpeed = Value<float>(QuickGraphStylePaths.InputKeyboardPanSpeed), KeyboardZoomSpeed = Value<float>(QuickGraphStylePaths.InputKeyboardZoomSpeed), PanMouseButton = Value<int>(QuickGraphStylePaths.InputPanMouseButton) };
|
||||
var selection = new QuickGraphSelectionRenderStyle { DragThreshold = Value<float>(QuickGraphStylePaths.SelectionDragThreshold), FillColor = Value<Color>(QuickGraphStylePaths.SelectionFillColor), OutlineColor = Value<Color>(QuickGraphStylePaths.SelectionOutlineColor), OutlineOffset = Value<Vector2>(QuickGraphStylePaths.SelectionOutlineOffset), RaycastTarget = Value<bool>(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 static QuickGraphRenderStyleChange Classify(QuickGraphRenderStyleSnapshot old, QuickGraphNodeRenderStyle node, QuickGraphWireRenderStyle wire, QuickGraphGridRenderStyle grid)
|
||||
private void Validate(QuickGraphNodeRenderStyle node, QuickGraphWireRenderStyle wire, QuickGraphGridRenderStyle grid, QuickGraphInputRenderStyle input)
|
||||
{
|
||||
QuickGraphRenderStyleChange changes = QuickGraphRenderStyleChange.None;
|
||||
if (old.Node.Width != node.Width || old.Node.Height != node.Height) changes |= QuickGraphRenderStyleChange.NodeGeometry;
|
||||
if (old.Node.Border != node.Border || old.Node.Chamfer != node.Chamfer || old.Node.ChamferSegments != node.ChamferSegments || old.Node.TitleFontSize != node.TitleFontSize) changes |= QuickGraphRenderStyleChange.NodeAppearance;
|
||||
if (old.Wire.Width != wire.Width || old.Wire.HitRadius != wire.HitRadius || old.Wire.SourceHandle != wire.SourceHandle || old.Wire.TargetHandle != wire.TargetHandle) changes |= QuickGraphRenderStyleChange.WireGeometry;
|
||||
if (old.Wire.ArrowWidth != wire.ArrowWidth || old.Wire.ArrowHeight != wire.ArrowHeight || old.Wire.ArrowProgress != wire.ArrowProgress || old.Wire.ArrowDepth != wire.ArrowDepth || old.Wire.Tessellation != wire.Tessellation || old.Wire.ArrowShape != wire.ArrowShape || old.Wire.ArrowReversed != wire.ArrowReversed) changes |= QuickGraphRenderStyleChange.WireAppearance;
|
||||
if (old.Grid.Material != grid.Material || old.Grid.ShaderName != grid.ShaderName || old.Grid.Size != grid.Size || old.Grid.OverlayPower != grid.OverlayPower || old.Grid.Threshold != grid.Threshold || old.Grid.Exp != grid.Exp || old.Grid.Color != grid.Color || old.Grid.Background != grid.Background) changes |= QuickGraphRenderStyleChange.GridAppearance;
|
||||
var errors = new List<string>();
|
||||
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=<missing> 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 ?? "<null>"} 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<string> errors, string path) { if (!Has(path)) errors.Add($"缺少关键路径 path={path} value=<missing> 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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
namespace XericLibrary.Runtime.Blueprint.QuickGraph
|
||||
{
|
||||
/// <summary>
|
||||
/// QuickGraph XSSS 路径的唯一声明处。
|
||||
/// 命名空间决定主题,路径仅描述视觉/交互要素。
|
||||
/// </summary>
|
||||
public static class QuickGraphStylePaths
|
||||
{
|
||||
public const string NodeRoot = "/quickgraph/node/";
|
||||
public const string NodeWidth = NodeRoot + "width";
|
||||
public const string NodeHeight = NodeRoot + "height";
|
||||
public const string NodeBorder = NodeRoot + "border";
|
||||
public const string NodeChamfer = NodeRoot + "chamfer";
|
||||
public const string NodeChamferSegments = NodeRoot + "chamferSegments";
|
||||
public const string NodeTitleFontSize = NodeRoot + "titleFontSize";
|
||||
public const string NodeTitleFont = NodeRoot + "titleFont";
|
||||
public const string NodeTitleAlignment = NodeRoot + "titleAlignment";
|
||||
public const string NodeShowTitleFull = NodeRoot + "showTitle/full";
|
||||
public const string NodeOuterColor = NodeRoot + "outerColor";
|
||||
public const string NodeAxisScaleX = NodeRoot + "axisScaleX";
|
||||
public const string NodeAxisScaleY = NodeRoot + "axisScaleY";
|
||||
public const string NodeSideCount = NodeRoot + "sideCount";
|
||||
public const string NodeFillColor = NodeRoot + "fillColor";
|
||||
public const string NodeBorderColor = NodeRoot + "borderColor";
|
||||
public const string NodeTitleColor = NodeRoot + "titleColor";
|
||||
|
||||
public const string PortRoot = "/quickgraph/port/";
|
||||
public const string PortInputSide = PortRoot + "inputSide";
|
||||
public const string PortOutputSide = PortRoot + "outputSide";
|
||||
public const string PortTopRatio = PortRoot + "topRatio";
|
||||
public const string PortBottomRatio = PortRoot + "bottomRatio";
|
||||
|
||||
public const string WireRoot = "/quickgraph/wire/";
|
||||
public const string WireWidth = WireRoot + "width";
|
||||
public const string WireHitRadius = WireRoot + "hitRadius";
|
||||
public const string WireSourceHandle = WireRoot + "sourceHandle";
|
||||
public const string WireTargetHandle = WireRoot + "targetHandle";
|
||||
public const string WireArrowWidth = WireRoot + "arrowWidth";
|
||||
public const string WireArrowHeight = WireRoot + "arrowHeight";
|
||||
public const string WireArrowProgress = WireRoot + "arrowProgress";
|
||||
public const string WireArrowDepth = WireRoot + "arrowDepth";
|
||||
public const string WireTessellation = WireRoot + "tessellation";
|
||||
public const string WireMinTessellation = WireRoot + "minTessellation";
|
||||
public const string WireArrowShape = WireRoot + "arrowShape";
|
||||
public const string WireArrowReversed = WireRoot + "arrowReversed";
|
||||
public const string WireUseNodeColorGradient = WireRoot + "useNodeColorGradient";
|
||||
public const string WireColor = WireRoot + "color";
|
||||
|
||||
public const string GridRoot = "/quickgraph/grid/";
|
||||
public const string GridMaterial = GridRoot + "material";
|
||||
public const string GridShader = GridRoot + "shader";
|
||||
public const string GridSize = GridRoot + "size";
|
||||
public const string GridOverlayPower = GridRoot + "overlayPower";
|
||||
public const string GridThreshold = GridRoot + "threshold";
|
||||
public const string GridExp = GridRoot + "exp";
|
||||
public const string GridColor = GridRoot + "color";
|
||||
public const string GridBackground = GridRoot + "background";
|
||||
public const string GridRaycastTarget = GridRoot + "raycastTarget";
|
||||
public const string GridMaskable = GridRoot + "maskable";
|
||||
public const string GridLodTransitionThreshold = GridRoot + "lodTransitionThreshold";
|
||||
public const string GridLodTransitionStart = GridRoot + "lodTransitionStart";
|
||||
public const string GridLodTransitionEnd = GridRoot + "lodTransitionEnd";
|
||||
public const string GridCellCenter = GridRoot + "cellCenter";
|
||||
public const string GridLineShapeScale = GridRoot + "lineShapeScale";
|
||||
|
||||
public const string InputRoot = "/quickgraph/input/";
|
||||
public const string InputZoomDivisor = InputRoot + "zoomDivisor";
|
||||
public const string InputDragPanSpeed = InputRoot + "dragPanSpeed";
|
||||
public const string InputMinZoom = InputRoot + "minZoom";
|
||||
public const string InputMaxZoom = InputRoot + "maxZoom";
|
||||
public const string InputDefaultZoom = InputRoot + "defaultZoom";
|
||||
public const string InputFocusZoom = InputRoot + "focusZoom";
|
||||
public const string InputFocusPadding = InputRoot + "focusPadding";
|
||||
public const string InputKeyboardPanSpeed = InputRoot + "keyboardPanSpeed";
|
||||
public const string InputKeyboardZoomSpeed = InputRoot + "keyboardZoomSpeed";
|
||||
public const string InputPanMouseButton = InputRoot + "panMouseButton";
|
||||
|
||||
public const string SelectionRoot = "/quickgraph/selection/";
|
||||
public const string SelectionDragThreshold = SelectionRoot + "dragThreshold";
|
||||
public const string SelectionFillColor = SelectionRoot + "fillColor";
|
||||
public const string SelectionOutlineColor = SelectionRoot + "outlineColor";
|
||||
public const string SelectionOutlineOffset = SelectionRoot + "outlineOffset";
|
||||
public const string SelectionRaycastTarget = SelectionRoot + "raycastTarget";
|
||||
|
||||
public static string Lod(string level, string property)
|
||||
{
|
||||
return "/quickgraph/lod/" + level + "/" + property;
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 38115e5721db6514bb8b8be6be10f26a
|
||||
guid: 46bc7416448ac4d488bdeb2359b46692
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -1,113 +1,18 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace XericLibrary.Runtime.Blueprint.QuickGraph
|
||||
{
|
||||
/// <summary>QuickGraph 网格背景;样式资源材质只绑定不拥有,运行时创建材质才由本工具释放。</summary>
|
||||
[BlueprintTool(phase: ToolPhase.Render, order: 0)]
|
||||
[BlueprintTheme("QuickGraph")]
|
||||
[BlueprintTool(phase: ToolPhase.Render, order: 0)] [BlueprintTheme("QuickGraph")]
|
||||
public class QuickUGUIGraphGridBackgroundTool : BlueprintTool
|
||||
{
|
||||
private const string BackgroundGoName = "__Bp_GridBackground";
|
||||
private QuickGraphGridBackgroundConfig _defaultConfig;
|
||||
private QuickGraphRenderStyleResolver _styleResolver;
|
||||
private Image _backgroundImage;
|
||||
private Material _backgroundMaterial;
|
||||
private QuickGraphGridBackgroundConfig _ownedMaterialConfig;
|
||||
private string _ownedShaderName;
|
||||
private QuickGraphGridBackgroundConfig Config
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ToolConfig is QuickGraphGridBackgroundConfig external) return external;
|
||||
if (_defaultConfig == null) _defaultConfig = ScriptableObject.CreateInstance<QuickGraphGridBackgroundConfig>();
|
||||
return _defaultConfig;
|
||||
}
|
||||
}
|
||||
public override void OnInitialize()
|
||||
{
|
||||
_styleResolver = new QuickGraphRenderStyleResolver(Graph, gridFallback: ConfigStyle);
|
||||
_styleResolver.Changed += OnStyleChanged;
|
||||
EnsureBackground(); RebindMaterial(_styleResolver.Snapshot.Grid, true);
|
||||
}
|
||||
public override void OnRender()
|
||||
{
|
||||
if (Graph == null) return;
|
||||
EnsureBackground();
|
||||
var style = _styleResolver != null ? _styleResolver.Snapshot.Grid : ConfigStyle();
|
||||
RebindMaterial(style, false);
|
||||
ApplyStyle(style);
|
||||
}
|
||||
public override void OnConfigChanged()
|
||||
{
|
||||
_styleResolver?.Refresh();
|
||||
if (_styleResolver == null) RebindMaterial(ConfigStyle(), true);
|
||||
Graph?.MarkDirty();
|
||||
}
|
||||
private QuickGraphGridRenderStyle ConfigStyle()
|
||||
{
|
||||
var cfg = Config;
|
||||
return new QuickGraphGridRenderStyle { Material = cfg.OverrideMaterial, ShaderName = cfg.ShaderName, Size = cfg.GridSize, OverlayPower = cfg.GridOverlayPower, Threshold = cfg.GridLineThreshold, Exp = cfg.GridExp, Color = cfg.GridColor, Background = cfg.GridBackgroundColor };
|
||||
}
|
||||
private void OnStyleChanged(QuickGraphRenderStyleSnapshot snapshot)
|
||||
{
|
||||
if ((snapshot.Changes & QuickGraphRenderStyleChange.GridAppearance) == 0) return;
|
||||
RebindMaterial(snapshot.Grid, true);
|
||||
Graph?.MarkDirty();
|
||||
}
|
||||
private void EnsureBackground()
|
||||
{
|
||||
if (_backgroundImage != null || Graph?.Canvas?.GetRootTransform() == null) return;
|
||||
var root = Graph.Canvas.GetRootTransform(); var existing = root.Find(BackgroundGoName);
|
||||
if (existing != null) _backgroundImage = existing.GetComponent<Image>();
|
||||
if (_backgroundImage != null) return;
|
||||
if (existing != null) Object.DestroyImmediate(existing.gameObject);
|
||||
var go = new GameObject(BackgroundGoName, typeof(RectTransform)); go.hideFlags = HideFlags.HideAndDontSave; go.transform.SetParent(root, false); go.transform.SetAsFirstSibling();
|
||||
var rect = go.GetComponent<RectTransform>(); rect.anchorMin = Vector2.zero; rect.anchorMax = Vector2.one; rect.offsetMin = Vector2.zero; rect.offsetMax = Vector2.zero;
|
||||
_backgroundImage = go.AddComponent<Image>(); _backgroundImage.raycastTarget = true; _backgroundImage.maskable = false;
|
||||
}
|
||||
private void RebindMaterial(QuickGraphGridRenderStyle style, bool force)
|
||||
{
|
||||
if (_backgroundImage == null) return;
|
||||
Material target = style.Material;
|
||||
QuickGraphGridBackgroundConfig owner = null;
|
||||
string ownedShader = null;
|
||||
if (target == null)
|
||||
{
|
||||
// 旧 Config 仅在 style 未提供共享材质时参与回退;其缓存材质为唯一可释放对象。
|
||||
if (style.ShaderName == Config.ShaderName) { target = Config.GetOrCreateMaterial(); owner = Config.OverrideMaterial == null ? Config : null; }
|
||||
else if (!string.IsNullOrEmpty(style.ShaderName))
|
||||
{
|
||||
if (_backgroundMaterial != null && _ownedShaderName == style.ShaderName) return;
|
||||
var shader = Shader.Find(style.ShaderName); if (shader != null) { target = new Material(shader) { name = "BpGridBackground_StyleMat" }; ownedShader = style.ShaderName; }
|
||||
}
|
||||
}
|
||||
if (target == _backgroundMaterial) return;
|
||||
ReleaseOwnedMaterial();
|
||||
_backgroundMaterial = target; _ownedMaterialConfig = owner; _ownedShaderName = ownedShader;
|
||||
if (_backgroundImage != null) _backgroundImage.material = _backgroundMaterial;
|
||||
}
|
||||
private void ApplyStyle(QuickGraphGridRenderStyle style)
|
||||
{
|
||||
if (_backgroundMaterial == null || Graph?.Canvas == null || _backgroundImage == null) return;
|
||||
var size = _backgroundImage.rectTransform.rect.size; var zoom = Graph.Canvas.ZoomLevel; var pan = Graph.Canvas.PanOffset; var gridSize = Mathf.Max(.0001f, style.Size);
|
||||
_backgroundMaterial.SetVector("_Transform", new Vector4(size.x / (zoom * gridSize), size.y / (zoom * gridSize), -(pan.x + .5f * size.x) / (zoom * gridSize), -(pan.y + .5f * size.y) / (zoom * gridSize)));
|
||||
_backgroundMaterial.SetFloat("_GridOverlayPower", style.OverlayPower); _backgroundMaterial.SetFloat("_GridLineThreshold", style.Threshold); _backgroundMaterial.SetFloat("_GridExp", style.Exp);
|
||||
_backgroundMaterial.SetColor("_GridColor", style.Color); _backgroundMaterial.SetColor("_GridBackgroundColor", style.Background);
|
||||
}
|
||||
private void ReleaseOwnedMaterial()
|
||||
{
|
||||
if (_backgroundImage != null) _backgroundImage.material = null;
|
||||
if (_ownedMaterialConfig != null) _ownedMaterialConfig.ReleaseMaterial();
|
||||
else if (_backgroundMaterial != null && !string.IsNullOrEmpty(_ownedShaderName)) { if (Application.isPlaying) Object.Destroy(_backgroundMaterial); else Object.DestroyImmediate(_backgroundMaterial); }
|
||||
_backgroundMaterial = null; _ownedMaterialConfig = null; _ownedShaderName = null;
|
||||
}
|
||||
public override void OnDestroy()
|
||||
{
|
||||
if (_styleResolver != null) _styleResolver.Changed -= OnStyleChanged;
|
||||
_styleResolver?.Dispose(); _styleResolver = null; ReleaseOwnedMaterial();
|
||||
if (_backgroundImage != null) { if (Application.isPlaying) Object.Destroy(_backgroundImage.gameObject); else Object.DestroyImmediate(_backgroundImage.gameObject); _backgroundImage = null; }
|
||||
_defaultConfig = null;
|
||||
}
|
||||
private const string Name = "__Bp_GridBackground"; private QuickGraphRenderStyleResolver _styles; private Image _image; private Material _material; private string _ownedShader; private bool _transformErrorLogged; private bool _materialUnavailableErrorLogged;
|
||||
public override void OnInitialize() { _styles = new QuickGraphRenderStyleResolver(Graph); _styles.Changed += _ => Graph?.MarkDirty(); Ensure(); Rebind(_styles.Snapshot.Grid); }
|
||||
public override void OnConfigChanged() => _styles?.Refresh();
|
||||
public override void OnRender() { Ensure(); if (_styles == null) return; var style = _styles.Snapshot.Grid; Rebind(style); Apply(style); }
|
||||
private void Ensure() { if (_image != null || Graph?.Canvas?.GetRootTransform() == null) return; var root = Graph.Canvas.GetRootTransform(); var existing = root.Find(Name); if (existing != null) _image = existing.GetComponent<Image>(); if (_image == null) { if (existing != null) Object.DestroyImmediate(existing.gameObject); var go = new GameObject(Name, typeof(RectTransform), typeof(Image)); go.hideFlags = HideFlags.HideAndDontSave; go.transform.SetParent(root, false); go.transform.SetAsFirstSibling(); var rt = go.GetComponent<RectTransform>(); rt.anchorMin = Vector2.zero; rt.anchorMax = Vector2.one; rt.offsetMin = Vector2.zero; rt.offsetMax = Vector2.zero; _image = go.GetComponent<Image>(); } }
|
||||
private void Rebind(QuickGraphGridRenderStyle style) { if (_image == null) return; Material target = style.Material; string owned = null; if (target == null && !string.IsNullOrEmpty(style.ShaderName)) { if (_material != null && _ownedShader == style.ShaderName) return; var shader = Shader.Find(style.ShaderName); if (shader != null) { target = new Material(shader) { name = "BpGridBackground_StyleMat" }; owned = style.ShaderName; } } if (target == null) { if (!_materialUnavailableErrorLogged) { Debug.LogError($"[QuickGraph] 网格 material 与 shader 均不可用: GraphId={Graph?.GraphId}, material={style.Material}, shader=\"{style.ShaderName}\"。"); _materialUnavailableErrorLogged = true; } } else _materialUnavailableErrorLogged = false; if (target == _material) return; ReleaseMaterial(); _material = target; _ownedShader = owned; _image.material = target; }
|
||||
private void Apply(QuickGraphGridRenderStyle style) { if (_image == null) return; _image.raycastTarget = style.RaycastTarget; _image.maskable = style.Maskable; if (_material == null || Graph?.Canvas == null) return; var size = _image.rectTransform.rect.size; var zoom = Graph.Canvas.ZoomLevel; if (style.Size != 0f && zoom != 0f) { var pan = Graph.Canvas.PanOffset; _material.SetVector("_Transform", new Vector4(size.x / (zoom * style.Size), size.y / (zoom * style.Size), -(pan.x + .5f * size.x) / (zoom * style.Size), -(pan.y + .5f * size.y) / (zoom * style.Size))); _transformErrorLogged = false; } else if (!_transformErrorLogged) { Debug.LogError("[QuickGraph] 网格样式 size 或 zoom 为 0,未写入 _Transform。"); _transformErrorLogged = true; } _material.SetFloat("_GridOverlayPower", style.OverlayPower); _material.SetFloat("_GridLineThreshold", style.Threshold); _material.SetFloat("_GridExp", style.Exp); _material.SetColor("_GridColor", style.Color); _material.SetColor("_GridBackgroundColor", style.Background); _material.SetFloat("_LodTransthd", style.LodTransitionThreshold); _material.SetFloat("_LodTransitionStart", style.LodTransitionStart); _material.SetFloat("_LodTransitionEnd", style.LodTransitionEnd); _material.SetVector("_CellCenter", style.CellCenter); _material.SetFloat("_LineShapeScale", style.LineShapeScale); }
|
||||
private void ReleaseMaterial() { if (_image != null) _image.material = null; if (_material != null && _ownedShader != null) { if (Application.isPlaying) Object.Destroy(_material); else Object.DestroyImmediate(_material); } _material = null; _ownedShader = null; }
|
||||
public override void OnDestroy() { _styles?.Dispose(); ReleaseMaterial(); if (_image != null) { if (Application.isPlaying) Object.Destroy(_image.gameObject); else Object.DestroyImmediate(_image.gameObject); } _image = null; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,82 +1,21 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using XericLibrary.Runtime.Blueprint.Render;
|
||||
using XericLibrary.Runtime.UIGraph;
|
||||
|
||||
namespace XericLibrary.Runtime.Blueprint.QuickGraph
|
||||
{
|
||||
/// <summary>按源节点 Chunk 提交连线的渲染工具;所有局部坐标由基类 submission 提供。</summary>
|
||||
[BlueprintTool(phase: ToolPhase.Render, order: 110)]
|
||||
[BlueprintTheme("QuickGraph")]
|
||||
[BlueprintTool(phase: ToolPhase.Render, order: 110)] [BlueprintTheme("QuickGraph")]
|
||||
public class WireCanvasRenderTool : BlueprintCanvasRenderTool<BlueprintCurveRenderer>
|
||||
{
|
||||
private QuickGraphWireRenderConfig _defaultConfig;
|
||||
private QuickGraphRenderStyleResolver _styleResolver;
|
||||
private int _appliedStyleRevision = -1;
|
||||
private QuickGraphWireRenderConfig Config
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ToolConfig is QuickGraphWireRenderConfig external) return external;
|
||||
if (_defaultConfig == null) _defaultConfig = ScriptableObject.CreateInstance<QuickGraphWireRenderConfig>();
|
||||
return _defaultConfig;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnInitialize()
|
||||
{
|
||||
base.OnInitialize();
|
||||
_styleResolver = new QuickGraphRenderStyleResolver(Graph, wireFallback: ConfigStyle);
|
||||
_styleResolver.Changed += OnStyleChanged;
|
||||
ApplyWireGeometry(_styleResolver.Snapshot.Wire, true);
|
||||
}
|
||||
public override void OnConfigChanged()
|
||||
{
|
||||
_styleResolver?.Refresh();
|
||||
if (_styleResolver == null) ApplyWireGeometry(ConfigStyle(), true);
|
||||
base.OnConfigChanged();
|
||||
}
|
||||
private QuickGraphWireRenderStyle ConfigStyle()
|
||||
{
|
||||
var cfg = Config;
|
||||
return new QuickGraphWireRenderStyle { Width = cfg.WireWidth, HitRadius = cfg.HitTestRadius, SourceHandle = cfg.SourceHandleLength, TargetHandle = cfg.TargetHandleLength, ArrowShape = cfg.ArrowShape, ArrowReversed = cfg.ArrowReversed, ArrowProgress = cfg.ArrowProgress, ArrowWidth = cfg.ArrowWidth, ArrowHeight = cfg.ArrowHeight, ArrowDepth = cfg.ArrowDepthCompensation, Tessellation = cfg.TessellationSegments };
|
||||
}
|
||||
private void OnStyleChanged(QuickGraphRenderStyleSnapshot snapshot)
|
||||
{
|
||||
if ((snapshot.Changes & (QuickGraphRenderStyleChange.WireGeometry | QuickGraphRenderStyleChange.WireAppearance)) == 0) return;
|
||||
ApplyWireGeometry(snapshot.Wire, (snapshot.Changes & QuickGraphRenderStyleChange.WireGeometry) != 0);
|
||||
Graph?.MarkAllElementsDirty();
|
||||
}
|
||||
private void ApplyWireGeometry(QuickGraphWireRenderStyle style, bool force)
|
||||
{
|
||||
if (Graph == null || (!force && _appliedStyleRevision == _styleResolver.Snapshot.Revision)) return;
|
||||
Graph.SetWireGeometrySettings(new BlueprintWireGeometrySettings { SourceHandleLength = style.SourceHandle, TargetHandleLength = style.TargetHandle, WireWidth = style.Width, HitTestRadius = style.HitRadius });
|
||||
foreach (var wire in Graph.Wires) Graph.UpdateWireGeometry(wire);
|
||||
_appliedStyleRevision = _styleResolver != null ? _styleResolver.Snapshot.Revision : _appliedStyleRevision + 1;
|
||||
}
|
||||
|
||||
protected override BlueprintCurveRenderer BuildChunkRenderer(GameObject chunkGo) => chunkGo.AddComponent<BlueprintCurveRenderer>();
|
||||
protected override void ClearChunkData(BlueprintCurveRenderer renderer) => renderer.ClearAll();
|
||||
protected override void SetChunkDirty(BlueprintCurveRenderer renderer) => renderer.RebuildAll();
|
||||
protected override bool AcceptsElement(IBlueprintElement element) => element is BlueprintWire;
|
||||
protected override void AppendElementData(BlueprintCurveRenderer renderer, IBlueprintElement element, ulong chunkID)
|
||||
{
|
||||
if (!(element is BlueprintWire wire) || wire.SourcePort == null || wire.TargetPort == null || wire.SourcePort.OwnerNode == null || wire.TargetPort.OwnerNode == null) return;
|
||||
var style = _styleResolver != null ? _styleResolver.Snapshot.Wire : ConfigStyle();
|
||||
wire.GetControlPoints(out var startCanvas, out var handle1Canvas, out var handle2Canvas, out var endCanvas);
|
||||
var submission = CurrentElementSubmission;
|
||||
var ctrlPts = new[] { submission.ToChunkLocal(startCanvas), submission.ToChunkLocal(handle1Canvas), submission.ToChunkLocal(handle2Canvas), submission.ToChunkLocal(endCanvas) };
|
||||
var widths = new[] { style.Width, style.Width, style.Width, style.Width };
|
||||
Color32 srcColor = wire.SourcePort.OwnerNode.NodeColor;
|
||||
Color32 tgtColor = wire.TargetPort.OwnerNode.NodeColor;
|
||||
var arrow = new ArrowHeadData { shape = style.ArrowShape, reversed = style.ArrowReversed, progress = style.ArrowProgress, width = style.ArrowWidth, height = style.ArrowHeight, depthCompensation = style.ArrowDepth, color = tgtColor };
|
||||
var entry = new CurveCacheEntry { startColor = srcColor, endColor = tgtColor, tessellationSegments = Mathf.Max(2, Mathf.RoundToInt(style.Tessellation * Graph.CurrentLodSettings.WireTessellationMultiplier)) };
|
||||
renderer.AddCurve(entry, ctrlPts, widths, new[] { arrow });
|
||||
}
|
||||
public override void OnDestroy()
|
||||
{
|
||||
if (_styleResolver != null) _styleResolver.Changed -= OnStyleChanged;
|
||||
_styleResolver?.Dispose(); _styleResolver = null;
|
||||
base.OnDestroy();
|
||||
}
|
||||
private QuickGraphRenderStyleResolver _styles; private int _revision = -1;
|
||||
private readonly HashSet<string> _invalidWireWarningLogged = new HashSet<string>();
|
||||
public override void OnInitialize() { base.OnInitialize(); _styles = new QuickGraphRenderStyleResolver(Graph); _styles.Changed += OnStyleChanged; ApplyGeometry(_styles.Snapshot.Wire, true); }
|
||||
public override void OnConfigChanged() => _styles?.Refresh();
|
||||
private void OnStyleChanged(QuickGraphRenderStyleSnapshot snapshot) { ApplyGeometry(snapshot.Wire, true); Graph?.MarkAllElementsDirty(); }
|
||||
private void ApplyGeometry(QuickGraphWireRenderStyle style, bool force) { if (Graph == null || (!force && _revision == _styles.Snapshot.Revision)) return; Graph.SetWireGeometrySettings(new BlueprintWireGeometrySettings { SourceHandleLength = style.SourceHandle, TargetHandleLength = style.TargetHandle, WireWidth = style.Width, HitTestRadius = style.HitRadius }); foreach (var wire in Graph.Wires) Graph.UpdateWireGeometry(wire); _revision = _styles.Snapshot.Revision; }
|
||||
protected override BlueprintCurveRenderer BuildChunkRenderer(GameObject go) => go.AddComponent<BlueprintCurveRenderer>(); protected override void ClearChunkData(BlueprintCurveRenderer renderer) => renderer.ClearAll(); protected override void SetChunkDirty(BlueprintCurveRenderer renderer) => renderer.RebuildAll(); protected override bool AcceptsElement(IBlueprintElement element) => element is BlueprintWire;
|
||||
protected override void AppendElementData(BlueprintCurveRenderer renderer, IBlueprintElement element, ulong chunkID) { if (!(element is BlueprintWire wire)) return; if (wire.SourcePort == null || wire.TargetPort == null || wire.SourcePort.OwnerNode == null || wire.TargetPort.OwnerNode == null) { ReportInvalidWire(wire); return; } var style = _styles.Snapshot.Wire; bool useNodeColorGradient = wire.GetStyleValue<bool>(QuickGraphStylePaths.WireUseNodeColorGradient); Color wireColor = wire.GetStyleValue<Color>(QuickGraphStylePaths.WireColor); wire.GetControlPoints(out var a, out var b, out var c, out var d); var submit = CurrentElementSubmission; var points = new[] { submit.ToChunkLocal(a), submit.ToChunkLocal(b), submit.ToChunkLocal(c), submit.ToChunkLocal(d) }; Color32 src = useNodeColorGradient ? wire.SourcePort.OwnerNode.NodeColor : wireColor; Color32 dst = useNodeColorGradient ? wire.TargetPort.OwnerNode.NodeColor : wireColor; var arrow = new ArrowHeadData { shape = style.ArrowShape, reversed = style.ArrowReversed, progress = style.ArrowProgress, width = style.ArrowWidth, height = style.ArrowHeight, depthCompensation = style.ArrowDepth, color = dst }; renderer.AddCurve(new CurveCacheEntry { startColor = src, endColor = dst, tessellationSegments = Mathf.RoundToInt(style.Tessellation * Graph.CurrentLodSettings.WireTessellationMultiplier) }, points, new[] { style.Width, style.Width, style.Width, style.Width }, new[] { arrow }); }
|
||||
private void ReportInvalidWire(BlueprintWire wire) { if (!_invalidWireWarningLogged.Add(wire.ElementId)) return; var missing = new List<string>(); if (wire.SourcePort == null) missing.Add("SourcePort"); else if (wire.SourcePort.OwnerNode == null) missing.Add("SourcePort.OwnerNode"); if (wire.TargetPort == null) missing.Add("TargetPort"); else if (wire.TargetPort.OwnerNode == null) missing.Add("TargetPort.OwnerNode"); Debug.LogWarning($"[WireCanvasRenderTool] 跳过无效 wire: GraphId={Graph.GraphId}, WireId={wire.ElementId}, missing={string.Join(", ", missing)}。"); }
|
||||
public override void OnDestroy() { if (_styles != null) _styles.Changed -= OnStyleChanged; _styles?.Dispose(); _styles = null; _invalidWireWarningLogged.Clear(); base.OnDestroy(); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ namespace XericLibrary.Runtime.Blueprint.Render
|
||||
protected RectTransform ViewportRect;
|
||||
private Vector2 _prevPan;
|
||||
private float _prevZoom;
|
||||
private readonly HashSet<ulong> _chunkRendererFailureLogged = new HashSet<ulong>();
|
||||
private readonly HashSet<ulong> _zeroAcceptedSubmissionLogged = new HashSet<ulong>();
|
||||
|
||||
protected abstract TChildRenderer BuildChunkRenderer(GameObject chunkGo);
|
||||
/// <summary>当前模板方法提交;子类不得重新计算 chunk 本地偏移。</summary>
|
||||
@@ -40,13 +42,21 @@ namespace XericLibrary.Runtime.Blueprint.Render
|
||||
{
|
||||
if (submission.Kind == ChunkSubmissionKind.Exited) { RecycleChunk(submission.ChunkId); continue; }
|
||||
var renderer = GetOrCreateChunk(submission.ChunkId);
|
||||
if (renderer == null) continue;
|
||||
if (renderer == null)
|
||||
{
|
||||
if (_chunkRendererFailureLogged.Add(submission.ChunkId))
|
||||
Debug.LogError($"[BlueprintCanvasRenderTool] 创建 chunk renderer 失败: renderer={GetType().Name}, GraphId={Graph.GraphId}, chunk={submission.ChunkId}。");
|
||||
continue;
|
||||
}
|
||||
ClearChunkData(renderer);
|
||||
BeginChunkSubmission(submission.ChunkId, renderer);
|
||||
var session = new RenderSession(Graph, submission);
|
||||
int acceptedCount = 0;
|
||||
if (submission.Elements != null)
|
||||
foreach (var element in submission.Elements)
|
||||
if (AcceptsElement(element)) { CurrentElementSubmission = session.CreateElementSubmission(element); AppendElementData(renderer, element, submission.ChunkId); }
|
||||
if (AcceptsElement(element)) { acceptedCount++; CurrentElementSubmission = session.CreateElementSubmission(element); AppendElementData(renderer, element, submission.ChunkId); }
|
||||
if (submission.Elements != null && submission.Elements.Count != 0 && acceptedCount == 0 && _zeroAcceptedSubmissionLogged.Add(submission.ChunkId))
|
||||
Debug.LogWarning($"[BlueprintCanvasRenderTool] chunk 提交未接受任何元素: renderer={GetType().Name}, GraphId={Graph.GraphId}, chunk={submission.ChunkId}, elements={submission.Elements.Count}。");
|
||||
CurrentElementSubmission = null;
|
||||
EndChunkSubmission(submission.ChunkId, renderer);
|
||||
SetChunkDirty(renderer);
|
||||
@@ -123,11 +133,15 @@ namespace XericLibrary.Runtime.Blueprint.Render
|
||||
if (ChunkPool.Count < MaxPoolSize) { renderer.gameObject.SetActive(false); ChunkPool.Push(renderer); }
|
||||
else UnityEngine.Object.Destroy(renderer.gameObject);
|
||||
ActiveChunks.Remove(id);
|
||||
_chunkRendererFailureLogged.Remove(id);
|
||||
_zeroAcceptedSubmissionLogged.Remove(id);
|
||||
}
|
||||
protected virtual void DestroyAllChunks()
|
||||
{
|
||||
foreach (var pair in ActiveChunks) if (pair.Value != null) UnityEngine.Object.Destroy(pair.Value.gameObject);
|
||||
ActiveChunks.Clear();
|
||||
_chunkRendererFailureLogged.Clear();
|
||||
_zeroAcceptedSubmissionLogged.Clear();
|
||||
while (ChunkPool.Count > 0) { var item = ChunkPool.Pop(); if (item != null) UnityEngine.Object.Destroy(item.gameObject); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using XericLibrary.Runtime.MacroLibrary;
|
||||
using XericLibrary.Runtime.XericComponent;
|
||||
|
||||
namespace Deconstruction.Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// Xeric 库的运行时辅助类库
|
||||
/// 程序运行辅助
|
||||
/// </summary>
|
||||
public static class XericHelper
|
||||
{
|
||||
#region 程序运行
|
||||
private static object _pusher = new object();
|
||||
private static bool _isMinimized = false;
|
||||
private static bool _isPrased = false;
|
||||
|
||||
/// <summary>
|
||||
/// 离开出游戏模式(在编辑模式时仅退出)
|
||||
@@ -26,6 +30,71 @@ namespace Deconstruction.Runtime
|
||||
#endif
|
||||
}
|
||||
|
||||
#endregion
|
||||
/// <summary>
|
||||
/// 最小化窗口
|
||||
/// </summary>
|
||||
public static void MinimizedWindow()
|
||||
{
|
||||
_isMinimized = true;
|
||||
MacroForm.MinimizeWindow();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从最小化恢复窗口
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static bool RestoreWindow()
|
||||
{
|
||||
if (!_isMinimized) return false;
|
||||
_isMinimized = false;
|
||||
MacroForm.RestoreWindow();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 全屏
|
||||
/// </summary>
|
||||
public static void FullScreen()
|
||||
{
|
||||
if (RestoreWindow())
|
||||
XericLifeCycleCore.SubmitTask(_pusher,
|
||||
f => Screen.fullScreenMode = FullScreenMode.ExclusiveFullScreen,
|
||||
phase: LifecyclePhase.Coroutine);
|
||||
Screen.fullScreenMode = FullScreenMode.ExclusiveFullScreen;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 全屏无边框窗口
|
||||
/// </summary>
|
||||
public static void FullScreenWindow()
|
||||
{
|
||||
if (RestoreWindow())
|
||||
XericLifeCycleCore.SubmitTask(_pusher,
|
||||
f => Screen.fullScreenMode = FullScreenMode.FullScreenWindow,
|
||||
phase: LifecyclePhase.Coroutine);
|
||||
Screen.fullScreenMode = FullScreenMode.FullScreenWindow;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 最大化窗口
|
||||
/// </summary>
|
||||
public static void MaximizedWindow()
|
||||
{
|
||||
if (RestoreWindow())
|
||||
XericLifeCycleCore.SubmitTask(_pusher,
|
||||
f => Screen.fullScreenMode = FullScreenMode.MaximizedWindow,
|
||||
phase: LifecyclePhase.Coroutine);
|
||||
Screen.fullScreenMode = FullScreenMode.MaximizedWindow;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前是否最小化
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static bool IsMinimized()
|
||||
{
|
||||
// return UnityEditor.EditorApplication.isPaused;
|
||||
return _isMinimized;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,7 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5f497a3b416599744ba515971811704f
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user