Files
AX-Copilot-Codex/src/AxCopilot/Views/ChatWindow.SelectionPopupPresentation.cs
lacvet 9f5a9d315c
Some checks failed
Release Gate / gate (push) Has been cancelled
AX Agent 선택 팝업 렌더를 분리해 footer 선택 UX 구조를 정리한다
- ChatWindow.SelectionPopupPresentation.cs를 추가해 워크트리 선택 팝업과 공통 선택 row 렌더를 메인 창 코드 밖으로 이동했다.

- ChatWindow.xaml.cs에서 작업 위치 선택 팝업 조립 책임을 제거해 대화 상태와 세션 orchestration 중심 구조를 더 선명하게 만들었다.

- README와 DEVELOPMENT 문서에 2026-04-06 09:14 (KST) 기준 구조 분리 이력을 반영했다.

- dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\ 검증 결과 경고 0 / 오류 0을 확인했다.
2026-04-06 09:19:10 +09:00

185 lines
7.0 KiB
C#

using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using AxCopilot.Services;
using AxCopilot.Services.Agent;
namespace AxCopilot.Views;
public partial class ChatWindow
{
private void ShowWorktreeMenu(UIElement placementTarget)
{
var (popup, panel) = CreateThemedPopupMenu(placementTarget, PlacementMode.Top, 320);
var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.White;
var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
var accentBrush = TryFindResource("AccentColor") as Brush ?? Brushes.CornflowerBlue;
var currentFolder = GetCurrentWorkFolder();
var root = string.IsNullOrWhiteSpace(currentFolder) ? "" : WorktreeStateStore.ResolveRoot(currentFolder);
var active = string.IsNullOrWhiteSpace(root) ? currentFolder : WorktreeStateStore.Load(root).Active;
var variants = GetAvailableWorkspaceVariants(root, active);
panel.Children.Add(CreatePopupSummaryStrip(new[]
{
("모드", string.Equals(active, root, StringComparison.OrdinalIgnoreCase) ? "로컬" : "워크트리", "#F8FAFC", "#E2E8F0", "#475569"),
("변형", variants.Count.ToString(), "#EFF6FF", "#BFDBFE", "#1D4ED8"),
}));
panel.Children.Add(CreatePopupSectionLabel("현재 작업 위치", new Thickness(8, 6, 8, 4)));
panel.Children.Add(CreatePopupMenuRow(
"\uED25",
"로컬",
string.IsNullOrWhiteSpace(root) ? "현재 워크스페이스" : root,
!string.IsNullOrWhiteSpace(root) && string.Equals(active, root, StringComparison.OrdinalIgnoreCase),
accentBrush,
secondaryText,
primaryText,
() =>
{
popup.IsOpen = false;
if (!string.IsNullOrWhiteSpace(root))
SwitchToWorkspace(root, root);
}));
if (!string.IsNullOrWhiteSpace(active) && !string.Equals(active, root, StringComparison.OrdinalIgnoreCase))
{
panel.Children.Add(CreatePopupMenuRow(
"\uE7BA",
Path.GetFileName(active),
active,
true,
accentBrush,
secondaryText,
primaryText,
() =>
{
popup.IsOpen = false;
SwitchToWorkspace(active, root);
}));
}
if (variants.Count > 0)
{
panel.Children.Add(CreatePopupSectionLabel($"워크트리 / 복사본 · {variants.Count}", new Thickness(8, 10, 8, 4)));
foreach (var variant in variants)
{
var isActive = !string.IsNullOrWhiteSpace(active) &&
string.Equals(Path.GetFullPath(variant), Path.GetFullPath(active), StringComparison.OrdinalIgnoreCase);
panel.Children.Add(CreatePopupMenuRow(
"\uE8B7",
Path.GetFileName(variant),
isActive ? $"현재 선택 · {variant}" : variant,
isActive,
accentBrush,
secondaryText,
primaryText,
() =>
{
popup.IsOpen = false;
SwitchToWorkspace(variant, root);
}));
}
}
panel.Children.Add(CreatePopupSectionLabel("새 작업 위치", new Thickness(8, 10, 8, 4)));
panel.Children.Add(CreatePopupMenuRow(
"\uE943",
"현재 브랜치로 워크트리 생성",
"Git 저장소면 분리된 작업 복사본을 만들고 전환합니다",
false,
accentBrush,
secondaryText,
primaryText,
() =>
{
popup.IsOpen = false;
_ = CreateCurrentBranchWorktreeAsync();
}));
popup.IsOpen = true;
}
private Border CreatePopupMenuRow(
string icon,
string title,
string description,
bool selected,
Brush accentBrush,
Brush secondaryText,
Brush primaryText,
Action? onClick)
{
var hintBackground = TryFindResource("HintBackground") as Brush ?? BrushFromHex("#F8FAFC");
var hoverBackground = TryFindResource("ItemHoverBackground") as Brush ?? BrushFromHex("#F8FAFC");
var row = new Border
{
Background = selected ? hintBackground : Brushes.Transparent,
BorderBrush = selected ? BrushFromHex("#D6E4FF") : Brushes.Transparent,
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(12),
Padding = new Thickness(12, 10, 12, 10),
Cursor = Cursors.Hand,
Margin = new Thickness(0, 0, 0, 6),
};
var grid = new Grid();
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
var iconBlock = new TextBlock
{
Text = icon,
FontFamily = new FontFamily("Segoe MDL2 Assets"),
FontSize = 14,
Foreground = selected ? accentBrush : secondaryText,
Margin = new Thickness(0, 1, 10, 0),
VerticalAlignment = VerticalAlignment.Center,
};
grid.Children.Add(iconBlock);
var textStack = new StackPanel();
textStack.Children.Add(new TextBlock
{
Text = title,
FontSize = 13.5,
FontWeight = selected ? FontWeights.SemiBold : FontWeights.Medium,
Foreground = primaryText,
});
if (!string.IsNullOrWhiteSpace(description))
{
textStack.Children.Add(new TextBlock
{
Text = description,
FontSize = 11.5,
Foreground = secondaryText,
Margin = new Thickness(0, 3, 0, 0),
TextWrapping = TextWrapping.Wrap,
});
}
Grid.SetColumn(textStack, 1);
grid.Children.Add(textStack);
if (selected)
{
var check = CreateSimpleCheck(accentBrush, 14);
Grid.SetColumn(check, 2);
check.Margin = new Thickness(10, 0, 0, 0);
if (check is FrameworkElement element)
element.VerticalAlignment = VerticalAlignment.Center;
grid.Children.Add(check);
}
row.Child = grid;
row.MouseEnter += (_, _) => row.Background = selected ? hintBackground : hoverBackground;
row.MouseLeave += (_, _) => row.Background = selected ? hintBackground : Brushes.Transparent;
row.MouseLeftButtonUp += (_, _) => onClick?.Invoke();
return row;
}
}