AX Agent 선택 팝업 렌더를 분리해 footer 선택 UX 구조를 정리한다
Some checks failed
Release Gate / gate (push) Has been cancelled
Some checks failed
Release Gate / gate (push) Has been cancelled
- 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을 확인했다.
This commit is contained in:
@@ -7,6 +7,10 @@ Windows 전용 시맨틱 런처 & 워크스페이스 매니저
|
||||
개발 참고: Claw Code 동등성 작업 추적 문서
|
||||
`docs/claw-code-parity-plan.md`
|
||||
|
||||
- 업데이트: 2026-04-06 09:14 (KST)
|
||||
- AX Agent 워크트리 선택 팝업과 공통 선택 row 렌더를 `ChatWindow.SelectionPopupPresentation.cs`로 분리했습니다. 작업 위치/워크트리 전환 메뉴와 선택 상태 row 조립이 메인 창 코드 밖으로 이동해 footer 선택 UX를 별도 파일에서 정리할 수 있게 됐습니다.
|
||||
- `ChatWindow.xaml.cs`는 대화 상태와 세션 orchestration 쪽에 더 집중하도록 정리했고, 향후 브랜치/워크트리/선택형 팝업 UX를 `claw-code` 기준으로 계속 다듬기 쉬운 구조를 만들었습니다.
|
||||
|
||||
- 업데이트: 2026-04-06 09:03 (KST)
|
||||
- AX Agent 공통 선택 팝업 조립 로직을 `ChatWindow.PopupPresentation.cs`로 분리했습니다. 테마 팝업 컨테이너, 공통 메뉴 아이템, 구분선, 최근 폴더 우클릭 컨텍스트 메뉴가 메인 창 코드 밖으로 이동해 footer/file-browser 쪽 팝업 품질 작업을 이어가기 쉬운 구조로 정리했습니다.
|
||||
- `ChatWindow.xaml.cs`는 대화 상태와 런타임 orchestration 쪽에 더 집중하도록 정리했고, 공통 팝업 시각 언어를 한 곳에서 다듬을 수 있는 기반을 만들었습니다.
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
# AX Copilot - 媛쒕컻 臾몄꽌
|
||||
|
||||
- Document update: 2026-04-06 09:14 (KST) - Split worktree-selection popup rendering into `ChatWindow.SelectionPopupPresentation.cs`. The current workspace/worktree chooser and the shared selected-row popup card renderer now live outside `ChatWindow.xaml.cs`, reducing footer selection UI density in the main window file.
|
||||
- Document update: 2026-04-06 09:14 (KST) - This keeps branch/worktree/footer chooser UX on the same presentation side of the codebase and leaves the main chat window more focused on runtime orchestration and conversation flow.
|
||||
|
||||
- Document update: 2026-04-06 09:03 (KST) - Split common themed popup construction out of `ChatWindow.xaml.cs` into `ChatWindow.PopupPresentation.cs`. Shared popup container creation, generic popup menu items/separators, and the recent-folder context menu now live in a dedicated partial instead of the main window orchestration file.
|
||||
- Document update: 2026-04-06 09:03 (KST) - This keeps footer/file-browser popup styling on a single visual path and reduces direct popup composition inside the main chat window flow, making further `claw-code` style popup UX work easier to maintain.
|
||||
|
||||
|
||||
184
src/AxCopilot/Views/ChatWindow.SelectionPopupPresentation.cs
Normal file
184
src/AxCopilot/Views/ChatWindow.SelectionPopupPresentation.cs
Normal file
@@ -0,0 +1,184 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -10700,176 +10700,6 @@ public partial class ChatWindow : Window
|
||||
.ToList();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private void SwitchToWorkspace(string targetPath, string rootPath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(targetPath) || !Directory.Exists(targetPath))
|
||||
|
||||
Reference in New Issue
Block a user