feat: 动画系统第五阶段(未验收)
This commit is contained in:
@@ -14,6 +14,7 @@ namespace AibisDream.FrameAnimation.Editor
|
||||
private readonly Label behavior;
|
||||
private readonly Label flowBadge;
|
||||
private readonly Label issueBadge;
|
||||
private readonly FrameAnimationPreviewElement preview;
|
||||
private readonly Action<FrameAnimationClipNodeView> selectedCallback;
|
||||
|
||||
public AnimationNode Data { get; }
|
||||
@@ -27,6 +28,7 @@ namespace AibisDream.FrameAnimation.Editor
|
||||
Data = data;
|
||||
this.selectedCallback = selectedCallback;
|
||||
viewDataKey = data.InternalId;
|
||||
AddToClassList("fa-node");
|
||||
style.minWidth = 245f;
|
||||
style.maxWidth = 285f;
|
||||
|
||||
@@ -48,16 +50,9 @@ namespace AibisDream.FrameAnimation.Editor
|
||||
extensionContainer.Add(behavior);
|
||||
extensionContainer.Add(flowBadge);
|
||||
extensionContainer.Add(issueBadge);
|
||||
extensionContainer.Add(new Label("Preview · Phase 5")
|
||||
{
|
||||
style =
|
||||
{
|
||||
height = 72f,
|
||||
unityTextAlign = TextAnchor.MiddleCenter,
|
||||
backgroundColor = new StyleColor(new Color(0.12f, 0.12f, 0.12f, 0.8f)),
|
||||
marginTop = 5f
|
||||
}
|
||||
});
|
||||
preview = new FrameAnimationPreviewElement();
|
||||
preview.AddToClassList("fa-node__preview");
|
||||
extensionContainer.Add(preview);
|
||||
RefreshExpandedState();
|
||||
}
|
||||
|
||||
@@ -103,13 +98,28 @@ namespace AibisDream.FrameAnimation.Editor
|
||||
|
||||
internal void SetDimmed(bool dimmed)
|
||||
{
|
||||
style.opacity = dimmed ? 0.22f : 1f;
|
||||
style.opacity = dimmed ? 0.45f : 1f;
|
||||
}
|
||||
|
||||
internal void SetPreview(
|
||||
FrameAnimationPreviewDisplay display,
|
||||
FrameAnimationPreviewBackground background)
|
||||
{
|
||||
preview.SetBackground(background);
|
||||
preview.SetZoom(FrameAnimationPreviewZoomMode.Fit, 1f);
|
||||
preview.SetDisplay(display.Sprite, display.IsEmpty);
|
||||
preview.style.opacity = display.FlowState == FrameAnimationFlowElementState.Upcoming
|
||||
? 0.55f : display.FlowState == FrameAnimationFlowElementState.Passed ? 0.82f : 1f;
|
||||
EnableInClassList("fa-node--preview-current", display.FlowState == FrameAnimationFlowElementState.Current);
|
||||
EnableInClassList("fa-node--preview-passed", display.FlowState == FrameAnimationFlowElementState.Passed);
|
||||
EnableInClassList("fa-node--preview-upcoming", display.FlowState == FrameAnimationFlowElementState.Upcoming);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class FrameAnimationEdgeView : Edge
|
||||
{
|
||||
private readonly Action<FrameAnimationEdgeView> selectedCallback;
|
||||
private Color baseColor = Color.white;
|
||||
public AnimationEdge Data { get; }
|
||||
|
||||
internal FrameAnimationEdgeView(AnimationEdge data, Action<FrameAnimationEdgeView> selectedCallback)
|
||||
@@ -128,7 +138,7 @@ namespace AibisDream.FrameAnimation.Editor
|
||||
|
||||
internal void SetDimmed(bool dimmed)
|
||||
{
|
||||
style.opacity = dimmed ? 0.16f : 1f;
|
||||
style.opacity = dimmed ? 0.30f : 1f;
|
||||
}
|
||||
|
||||
internal void Refresh(IReadOnlyList<FrameAnimationEditorIssue> issues)
|
||||
@@ -139,12 +149,37 @@ namespace AibisDream.FrameAnimation.Editor
|
||||
var color = matching.Any(issue => issue.Severity == FrameAnimationValidationSeverity.Error)
|
||||
? new Color(1f, 0.3f, 0.25f)
|
||||
: matching.Length > 0 ? new Color(1f, 0.72f, 0.2f) : Color.white;
|
||||
baseColor = color;
|
||||
edgeControl.inputColor = color;
|
||||
edgeControl.outputColor = color;
|
||||
tooltip = matching.Length == 0
|
||||
? $"{Data.ExitName} / {Data.Condition}"
|
||||
: string.Join("\n", matching.Select(issue => $"[{issue.Code}] {issue.Message}"));
|
||||
}
|
||||
|
||||
internal void SetPreviewState(FrameAnimationFlowElementState state)
|
||||
{
|
||||
if (state == FrameAnimationFlowElementState.Current)
|
||||
{
|
||||
edgeControl.inputColor = new Color(0.25f, 0.75f, 1f);
|
||||
edgeControl.outputColor = new Color(0.25f, 0.75f, 1f);
|
||||
}
|
||||
else if (state == FrameAnimationFlowElementState.Passed)
|
||||
{
|
||||
edgeControl.inputColor = new Color(0.35f, 0.68f, 0.46f);
|
||||
edgeControl.outputColor = new Color(0.35f, 0.68f, 0.46f);
|
||||
}
|
||||
else if (state == FrameAnimationFlowElementState.Upcoming)
|
||||
{
|
||||
edgeControl.inputColor = new Color(0.55f, 0.55f, 0.55f);
|
||||
edgeControl.outputColor = new Color(0.55f, 0.55f, 0.55f);
|
||||
}
|
||||
else
|
||||
{
|
||||
edgeControl.inputColor = baseColor;
|
||||
edgeControl.outputColor = baseColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class FrameAnimationClipSearchProvider : ScriptableObject, ISearchWindowProvider
|
||||
@@ -200,16 +235,22 @@ namespace AibisDream.FrameAnimation.Editor
|
||||
private IReadOnlyList<FrameAnimationEditorIssue> issues = Array.Empty<FrameAnimationEditorIssue>();
|
||||
private bool rebuilding;
|
||||
private Vector2 contextPosition;
|
||||
private string focusedFlowId = string.Empty;
|
||||
|
||||
internal event Action<FrameAnimationEditorSelection> SelectionRequested;
|
||||
internal event Action<IReadOnlyList<AnimationNode>> MultiSelectionChanged;
|
||||
internal event Action<IReadOnlyList<FrameAnimationEditorSelection>> SelectionSetChanged;
|
||||
internal event Action<AnimationNode> CreateFlowRequested;
|
||||
internal event Action<AnimationNode> NodeCreated;
|
||||
internal event Action ExitFocusRequested;
|
||||
internal event Action GraphChanged;
|
||||
internal event Action<string> NotificationRequested;
|
||||
internal event Action<Vector3, float> WorkspaceViewTransformChanged;
|
||||
|
||||
internal FrameAnimationGraphView()
|
||||
{
|
||||
style.flexGrow = 1f;
|
||||
focusable = true;
|
||||
SetupZoom(ContentZoomer.DefaultMinScale, ContentZoomer.DefaultMaxScale);
|
||||
this.AddManipulator(new ContentDragger());
|
||||
this.AddManipulator(new SelectionDragger());
|
||||
@@ -218,9 +259,12 @@ namespace AibisDream.FrameAnimation.Editor
|
||||
Insert(0, grid);
|
||||
grid.StretchToParentSize();
|
||||
graphViewChanged = OnGraphViewChanged;
|
||||
viewTransformChanged += _ =>
|
||||
WorkspaceViewTransformChanged?.Invoke(viewTransform.position, viewTransform.scale.x);
|
||||
RegisterCallback<MouseUpEvent>(_ => NotifySelectionSummary());
|
||||
RegisterCallback<DragUpdatedEvent>(OnDragUpdated);
|
||||
RegisterCallback<DragPerformEvent>(OnDragPerform);
|
||||
RegisterCallback<KeyDownEvent>(OnKeyDown);
|
||||
}
|
||||
|
||||
internal void Bind(
|
||||
@@ -325,6 +369,7 @@ namespace AibisDream.FrameAnimation.Editor
|
||||
|
||||
internal void SetFocusedFlow(string flowId)
|
||||
{
|
||||
focusedFlowId = flowId ?? string.Empty;
|
||||
if (graph == null || string.IsNullOrEmpty(flowId))
|
||||
{
|
||||
foreach (var view in nodeViews.Values) view.SetDimmed(false);
|
||||
@@ -359,6 +404,36 @@ namespace AibisDream.FrameAnimation.Editor
|
||||
internal IReadOnlyList<AnimationNode> SelectedNodes =>
|
||||
selection.OfType<FrameAnimationClipNodeView>().Select(view => view.Data).ToArray();
|
||||
|
||||
internal IReadOnlyList<AnimationNode> VisibleNodes => nodeViews.Values
|
||||
.Where(view => view.worldBound.Overlaps(worldBound))
|
||||
.Select(view => view.Data)
|
||||
.ToArray();
|
||||
|
||||
internal void ApplyPreview(FrameAnimationPreviewCoordinator coordinator)
|
||||
{
|
||||
if (coordinator == null) return;
|
||||
foreach (var view in nodeViews.Values)
|
||||
{
|
||||
view.SetPreview(coordinator.GetNodeDisplay(view.Data), coordinator.Background);
|
||||
}
|
||||
foreach (var view in edgeViews.Values)
|
||||
{
|
||||
view.SetPreviewState(coordinator.GetEdgeState(view.Data));
|
||||
}
|
||||
}
|
||||
|
||||
internal void RestoreViewTransform(Vector3 position, float scale)
|
||||
{
|
||||
var safeScale = float.IsNaN(scale) || float.IsInfinity(scale)
|
||||
? 1f : Mathf.Clamp(scale, ContentZoomer.DefaultMinScale, ContentZoomer.DefaultMaxScale);
|
||||
if (float.IsNaN(position.x) || float.IsInfinity(position.x) ||
|
||||
float.IsNaN(position.y) || float.IsInfinity(position.y))
|
||||
{
|
||||
position = Vector3.zero;
|
||||
}
|
||||
UpdateViewTransform(position, new Vector3(safeScale, safeScale, 1f));
|
||||
}
|
||||
|
||||
internal void RequestDeleteNodes(IEnumerable<AnimationNode> nodes)
|
||||
{
|
||||
var items = (nodes ?? Array.Empty<AnimationNode>()).Where(node => node != null).Distinct().ToArray();
|
||||
@@ -437,10 +512,11 @@ namespace AibisDream.FrameAnimation.Editor
|
||||
SearchWindow.Open(new SearchWindowContext(GUIUtility.GUIToScreenPoint(panelPosition)), provider);
|
||||
}
|
||||
|
||||
private void CreateNode(FrameClip clip, Vector2 position)
|
||||
internal void CreateNode(FrameClip clip, Vector2 position)
|
||||
{
|
||||
if (FrameAnimationGraphMutationService.CreateNode(graph, clip, position, out var node, out var error))
|
||||
{
|
||||
NodeCreated?.Invoke(node);
|
||||
GraphChanged?.Invoke();
|
||||
SelectionRequested?.Invoke(new FrameAnimationEditorSelection(FrameAnimationEditorSelectionKind.Node, node));
|
||||
}
|
||||
@@ -583,19 +659,61 @@ namespace AibisDream.FrameAnimation.Editor
|
||||
|
||||
private void OnNodeSelected(FrameAnimationClipNodeView view)
|
||||
{
|
||||
if (rebuilding)
|
||||
{
|
||||
return;
|
||||
}
|
||||
SelectionRequested?.Invoke(new FrameAnimationEditorSelection(FrameAnimationEditorSelectionKind.Node, view.Data));
|
||||
NotifySelectionSummary();
|
||||
}
|
||||
|
||||
private void OnEdgeSelected(FrameAnimationEdgeView view)
|
||||
{
|
||||
if (rebuilding)
|
||||
{
|
||||
return;
|
||||
}
|
||||
SelectionRequested?.Invoke(new FrameAnimationEditorSelection(FrameAnimationEditorSelectionKind.Edge, view.Data));
|
||||
NotifySelectionSummary();
|
||||
}
|
||||
|
||||
private void NotifySelectionSummary()
|
||||
{
|
||||
if (rebuilding)
|
||||
{
|
||||
return;
|
||||
}
|
||||
MultiSelectionChanged?.Invoke(SelectedNodes);
|
||||
var selected = selection.Select(item => item switch
|
||||
{
|
||||
FrameAnimationClipNodeView nodeView => new FrameAnimationEditorSelection(
|
||||
FrameAnimationEditorSelectionKind.Node, nodeView.Data),
|
||||
FrameAnimationEdgeView edgeView => new FrameAnimationEditorSelection(
|
||||
FrameAnimationEditorSelectionKind.Edge, edgeView.Data),
|
||||
_ => default
|
||||
})
|
||||
.Where(item => item.Kind != FrameAnimationEditorSelectionKind.None)
|
||||
.ToArray();
|
||||
SelectionSetChanged?.Invoke(selected);
|
||||
}
|
||||
|
||||
private void OnKeyDown(KeyDownEvent evt)
|
||||
{
|
||||
if (!TryHandleFocusExitShortcut(evt.keyCode, evt.target is TextField))
|
||||
{
|
||||
return;
|
||||
}
|
||||
evt.StopImmediatePropagation();
|
||||
}
|
||||
|
||||
internal bool TryHandleFocusExitShortcut(KeyCode keyCode, bool textInputActive)
|
||||
{
|
||||
if (keyCode != KeyCode.Escape || textInputActive || string.IsNullOrEmpty(focusedFlowId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
ExitFocusRequested?.Invoke();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnDragUpdated(DragUpdatedEvent evt)
|
||||
|
||||
Reference in New Issue
Block a user