update 0.6.3.7

This commit is contained in:
2026-07-30 09:18:26 +08:00
parent 54644a9f15
commit 46560ea78f
49 changed files with 1126 additions and 1325 deletions
@@ -0,0 +1,131 @@
using UnityEngine;
namespace XericLibrary.Runtime.Blueprint
{
/// <summary>
/// 蓝图视口控制器 —— 管理视图状态(Pan/Zoom/范围),提供外部 API 供工具和外部代码读写。
/// <para>
/// 在 <c>ToolPhase.PreUpdate, order: 40</c> 执行,每帧直接将目标值写入 <see cref="IBlueprintCanvas"/>,
/// 不做插值平滑。输入工具通过写入 <see cref="TargetPanOffset"/> / <see cref="TargetZoomLevel"/>
/// 来驱动视图,外部代码可通过 <c>BlueprintToolProvider.GetTool&lt;BlueprintViewportController&gt;(graph)</c>
/// 获取实例直接控制视图。
/// </para>
/// <para>
/// 此工具为通用基础工具(无主题限定),对所有蓝图主题可见。
/// </para>
/// </summary>
[BlueprintTool(phase: ToolPhase.PreUpdate, order: 40)]
public class BlueprintViewportController : BlueprintTool
{
// ── 目标值(由输入工具或外部代码写入) ──
/// <summary>目标平移偏移量。外部代码直接设置此值即可控制视图平移。</summary>
public Vector2 TargetPanOffset = Vector2.zero;
/// <summary>目标缩放级别。外部代码直接设置此值即可控制视图缩放。</summary>
public float TargetZoomLevel = 1f;
/// <summary>最小缩放限制。</summary>
public float MinZoom = 0.1f;
/// <summary>最大缩放限制。</summary>
public float MaxZoom = 3f;
// ── 帧间脏检测 ──
private Vector2 _prevPanOffset;
private float _prevZoomLevel;
private bool _initialized;
public override void OnUpdate(float deltaTime)
{
if (Graph?.Canvas == null) return;
var canvas = Graph.Canvas;
// 首次同步
if (!_initialized)
{
TargetPanOffset = canvas.PanOffset;
TargetZoomLevel = canvas.ZoomLevel;
_prevPanOffset = TargetPanOffset;
_prevZoomLevel = TargetZoomLevel;
_initialized = true;
}
// 同步范围到 Canvas
canvas.MinZoom = MinZoom;
canvas.MaxZoom = MaxZoom;
// 钳制目标值
TargetZoomLevel = Mathf.Clamp(TargetZoomLevel, MinZoom, MaxZoom);
// 直接写入 Canvas(无插值)
canvas.PanOffset = TargetPanOffset;
canvas.ZoomLevel = TargetZoomLevel;
// 标记脏
if (TargetPanOffset != _prevPanOffset || !Mathf.Approximately(TargetZoomLevel, _prevZoomLevel))
{
Graph.MarkDirty();
_prevPanOffset = TargetPanOffset;
_prevZoomLevel = TargetZoomLevel;
}
}
// ====================================================================
// 公共 API
// ====================================================================
/// <summary>重置视图到原点,缩放归 1。</summary>
public void ResetView()
{
TargetPanOffset = Vector2.zero;
TargetZoomLevel = 1f;
Graph?.MarkDirty();
}
/// <summary>聚焦到某个画布坐标点。</summary>
public void FocusOn(Vector2 canvasCenter, float zoom = 1f)
{
TargetZoomLevel = Mathf.Clamp(zoom, MinZoom, MaxZoom);
if (Graph?.Canvas != null)
{
// local = canvas * zoom + pan;令目标画布点落在实际 viewport 的 RenderRoot local 中心。
TargetPanOffset = Graph.Canvas.GetViewportLocalRect().center - canvasCenter * TargetZoomLevel;
}
else TargetPanOffset = -canvasCenter * TargetZoomLevel;
Graph?.MarkDirty();
}
/// <summary>聚焦到一组元素的包围盒区域。</summary>
public void FocusOnElements(System.Collections.Generic.IReadOnlyList<IBlueprintElement> elements, float padding = 100f)
{
if (elements == null || elements.Count == 0) return;
if (Graph?.Canvas == null) return;
// 计算所有元素的包围盒
Rect bounds = elements[0].BoundingBox;
for (int i = 1; i < elements.Count; i++)
{
var b = elements[i].BoundingBox;
bounds.xMin = Mathf.Min(bounds.xMin, b.xMin);
bounds.yMin = Mathf.Min(bounds.yMin, b.yMin);
bounds.xMax = Mathf.Max(bounds.xMax, b.xMax);
bounds.yMax = Mathf.Max(bounds.yMax, b.yMax);
}
bounds.xMin -= padding;
bounds.yMin -= padding;
bounds.xMax += padding;
bounds.yMax += padding;
// 以实际 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);
}
}
}