Add closable tabs and single-line tab strip layout

- Tab headers for dynamically-opened sub-graphs now include a close
  button (×). The initial AnimGraph tab is not closable.
- TabControl uses a custom template with horizontal ScrollViewer to
  keep all tabs in a single line instead of wrapping.
- Long tab names are truncated with ellipsis (MaxWidth=200).

Co-authored-by: LoogLong <86428208+LoogLong@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-03-05 02:14:42 +00:00
parent 7e0e897a91
commit 5998953bfe
2 changed files with 74 additions and 8 deletions

View File

@ -41,9 +41,24 @@
<ColumnDefinition Width="280" MinWidth="200"/>
</Grid.ColumnDefinitions>
<!-- Layer tabs with graph canvas -->
<!-- Layer tabs with graph canvas (single-line, scrollable tab strip) -->
<TabControl x:Name="LayerTabControl" Grid.Column="0" SelectionChanged="OnLayerTabChanged"
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer0BackgroundBrush}}"/>
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer0BackgroundBrush}}">
<TabControl.Template>
<ControlTemplate TargetType="TabControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ScrollViewer Grid.Row="0" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled">
<TabPanel IsItemsHost="True"/>
</ScrollViewer>
<ContentPresenter Grid.Row="1" ContentSource="SelectedContent"/>
</Grid>
</ControlTemplate>
</TabControl.Template>
</TabControl>
<GridSplitter Grid.Column="1" Width="4" HorizontalAlignment="Center" VerticalAlignment="Stretch"
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer1BorderBrush}}"/>

View File

@ -86,7 +86,7 @@ public partial class AnimGraphViewer
l.Name.Equals("AnimGraph", StringComparison.OrdinalIgnoreCase))
?? _viewModel.Layers[0];
AddLayerTab(outputLayer);
AddLayerTab(outputLayer, closable: false);
if (LayerTabControl.Items.Count > 0)
LayerTabControl.SelectedIndex = 0;
@ -95,13 +95,46 @@ public partial class AnimGraphViewer
/// <summary>
/// Creates a new tab for the given layer and selects it.
/// </summary>
private void AddLayerTab(AnimGraphLayer layer)
private void AddLayerTab(AnimGraphLayer layer, bool closable = true)
{
var tabItem = new System.Windows.Controls.TabItem
var tabItem = new System.Windows.Controls.TabItem { Tag = layer };
if (closable)
{
Header = layer.Name,
Tag = layer
};
// Build a header with text + close button
var headerPanel = new StackPanel { Orientation = Orientation.Horizontal };
headerPanel.Children.Add(new TextBlock
{
Text = layer.Name,
VerticalAlignment = VerticalAlignment.Center,
MaxWidth = 200,
TextTrimming = TextTrimming.CharacterEllipsis
});
var closeBtn = new Button
{
Content = "×",
FontSize = 12,
Padding = new Thickness(4, 0, 4, 0),
Margin = new Thickness(6, 0, 0, 0),
VerticalAlignment = VerticalAlignment.Center,
Background = Brushes.Transparent,
BorderThickness = new Thickness(0),
Cursor = Cursors.Hand,
Foreground = Brushes.Gray
};
closeBtn.Click += (_, _) => CloseTab(tabItem);
headerPanel.Children.Add(closeBtn);
tabItem.Header = headerPanel;
}
else
{
tabItem.Header = new TextBlock
{
Text = layer.Name,
MaxWidth = 200,
TextTrimming = TextTrimming.CharacterEllipsis
};
}
var canvasBorder = new Border
{
@ -139,6 +172,24 @@ public partial class AnimGraphViewer
LayerTabControl.SelectedItem = tabItem;
}
/// <summary>
/// Closes the given tab and cleans up its layer state.
/// </summary>
private void CloseTab(System.Windows.Controls.TabItem tabItem)
{
if (tabItem.Tag is AnimGraphLayer layer)
_layerStates.Remove(layer);
var index = LayerTabControl.Items.IndexOf(tabItem);
LayerTabControl.Items.Remove(tabItem);
// Select the previous tab or the first one
if (LayerTabControl.Items.Count > 0)
{
LayerTabControl.SelectedIndex = Math.Max(0, index - 1);
}
}
private void OnLayerTabChanged(object sender, SelectionChangedEventArgs e)
{
if (LayerTabControl.SelectedItem is not System.Windows.Controls.TabItem { Tag: AnimGraphLayer layer })