Add LinkedAnimLayer double-click to open layer sub-graph tab

Generalized GetLayerName to use any Root node's "Name" property as the
layer name (not just "AnimGraph"), enabling proper naming for
LinkedAnimLayer sub-graphs.

Added double-click handling on nodes: double-clicking a LinkedAnimLayer
node finds the matching layer and opens/selects a tab for it.

Co-authored-by: LoogLong <86428208+LoogLong@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-03-03 08:08:23 +00:00
parent 6516fab7a9
commit 1d2ef5905e
2 changed files with 96 additions and 59 deletions

View File

@ -212,16 +212,18 @@ public class AnimGraphViewModel
/// <summary>
/// Determines a display name for a layer based on the types of nodes it contains.
/// A Root node's "Name" property defines the layer/sub-graph name
/// (e.g., "AnimGraph" for the main output pose, or a specific name for LinkedAnimLayer sub-graphs).
/// </summary>
private static string GetLayerName(List<AnimGraphNode> nodes, int index)
{
// The final output pose layer contains an AnimGraphNode_Root whose
// "Name" property (in AdditionalProperties) is set to "AnimGraph"
var rootNode = nodes.FirstOrDefault(n =>
n.AdditionalProperties.TryGetValue("Name", out var name) &&
name.Equals("AnimGraph", StringComparison.OrdinalIgnoreCase));
if (rootNode != null)
return "AnimGraph";
n.ExportType.EndsWith("_Root", StringComparison.OrdinalIgnoreCase) &&
n.AdditionalProperties.TryGetValue("Name", out _));
if (rootNode != null &&
rootNode.AdditionalProperties.TryGetValue("Name", out var rootName) &&
!string.IsNullOrEmpty(rootName))
return rootName;
var stateMachine = nodes.FirstOrDefault(n =>
n.ExportType.Contains("StateMachine", StringComparison.OrdinalIgnoreCase));

View File

@ -63,69 +63,67 @@ public partial class AnimGraphViewer
LayerTabControl.Items.Clear();
_layerStates.Clear();
// If no layers were built (empty graph), show nothing
if (_viewModel.Layers.Count == 0)
return;
// Show only the final output pose layer (AnimGraph) initially.
// The root node of the output layer has a "Name" property set to "AnimGraph"
// in its AdditionalProperties (from the AnimGraphNode_Root struct).
// Show only the AnimGraph layer initially
var outputLayer = _viewModel.Layers.FirstOrDefault(l =>
l.Nodes.Any(n => n.AdditionalProperties.TryGetValue("Name", out var name) &&
name.Equals("AnimGraph", StringComparison.OrdinalIgnoreCase)))
l.Name.Equals("AnimGraph", StringComparison.OrdinalIgnoreCase))
?? _viewModel.Layers[0];
var layersToShow = new[] { outputLayer };
AddLayerTab(outputLayer);
foreach (var layer in layersToShow)
{
var tabItem = new System.Windows.Controls.TabItem
{
Header = layer.Name,
Tag = layer
};
// Create canvas container for this layer
var canvasBorder = new Border
{
ClipToBounds = true,
Background = new SolidColorBrush(Color.FromRgb(24, 24, 24))
};
var canvas = new Canvas();
var scaleTransform = new ScaleTransform(1, 1);
var translateTransform = new TranslateTransform(0, 0);
var transformGroup = new TransformGroup();
transformGroup.Children.Add(scaleTransform);
transformGroup.Children.Add(translateTransform);
canvas.RenderTransform = transformGroup;
canvasBorder.Child = canvas;
canvasBorder.MouseWheel += OnMouseWheel;
canvasBorder.AddHandler(UIElement.MouseLeftButtonDownEvent,
new MouseButtonEventHandler(OnCanvasMouseDown), true);
canvasBorder.MouseLeftButtonUp += OnCanvasMouseUp;
canvasBorder.MouseMove += OnCanvasMouseMove;
tabItem.Content = canvasBorder;
var state = new LayerCanvasState
{
Layer = layer,
Canvas = canvas,
ScaleTransform = scaleTransform,
TranslateTransform = translateTransform
};
_layerStates[layer] = state;
LayerTabControl.Items.Add(tabItem);
}
// Select the first tab
if (LayerTabControl.Items.Count > 0)
LayerTabControl.SelectedIndex = 0;
}
/// <summary>
/// Creates a new tab for the given layer and selects it.
/// </summary>
private void AddLayerTab(AnimGraphLayer layer)
{
var tabItem = new System.Windows.Controls.TabItem
{
Header = layer.Name,
Tag = layer
};
var canvasBorder = new Border
{
ClipToBounds = true,
Background = new SolidColorBrush(Color.FromRgb(24, 24, 24))
};
var canvas = new Canvas();
var scaleTransform = new ScaleTransform(1, 1);
var translateTransform = new TranslateTransform(0, 0);
var transformGroup = new TransformGroup();
transformGroup.Children.Add(scaleTransform);
transformGroup.Children.Add(translateTransform);
canvas.RenderTransform = transformGroup;
canvasBorder.Child = canvas;
canvasBorder.MouseWheel += OnMouseWheel;
canvasBorder.AddHandler(UIElement.MouseLeftButtonDownEvent,
new MouseButtonEventHandler(OnCanvasMouseDown), true);
canvasBorder.MouseLeftButtonUp += OnCanvasMouseUp;
canvasBorder.MouseMove += OnCanvasMouseMove;
tabItem.Content = canvasBorder;
var state = new LayerCanvasState
{
Layer = layer,
Canvas = canvas,
ScaleTransform = scaleTransform,
TranslateTransform = translateTransform
};
_layerStates[layer] = state;
LayerTabControl.Items.Add(tabItem);
LayerTabControl.SelectedItem = tabItem;
}
private void OnLayerTabChanged(object sender, SelectionChangedEventArgs e)
{
if (LayerTabControl.SelectedItem is not System.Windows.Controls.TabItem { Tag: AnimGraphLayer layer })
@ -309,9 +307,15 @@ public partial class AnimGraphViewer
border.ToolTip = $"{node.ExportType}\n{node.Name}";
// Click to select node and show properties
// Click to select, double-click to open linked layer
border.MouseLeftButtonDown += (s, e) =>
{
if (e.ClickCount == 2)
{
TryOpenLinkedLayer(node);
e.Handled = true;
return;
}
SelectNode(node, border);
e.Handled = true;
};
@ -397,6 +401,37 @@ public partial class AnimGraphViewer
PopulatePropertiesPanel(node);
}
/// <summary>
/// When a LinkedAnimLayer node is double-clicked, opens its corresponding
/// layer sub-graph in a new tab. The layer is identified by matching the
/// node's "Layer" property with a layer whose Root node has the same "Name".
/// </summary>
private void TryOpenLinkedLayer(AnimGraphNode node)
{
if (!node.ExportType.Contains("LinkedAnimLayer", StringComparison.OrdinalIgnoreCase))
return;
if (!node.AdditionalProperties.TryGetValue("Layer", out var layerName) || string.IsNullOrEmpty(layerName))
return;
var targetLayer = _viewModel.Layers.FirstOrDefault(l =>
l.Name.Equals(layerName, StringComparison.OrdinalIgnoreCase));
if (targetLayer == null)
return;
// If tab already exists, just select it
foreach (System.Windows.Controls.TabItem tab in LayerTabControl.Items)
{
if (tab.Tag == targetLayer)
{
LayerTabControl.SelectedItem = tab;
return;
}
}
AddLayerTab(targetLayer);
}
/// <summary>
/// Fills the properties panel with the selected node's information,
/// similar to UE's Details panel when a node is selected.