AX Agent 공통 팝업과 시각 상호작용 렌더를 분리해 메인 창 구조를 정리한다
Some checks failed
Release Gate / gate (push) Has been cancelled

- ChatWindow.VisualInteractionHelpers.cs를 추가해 메시지 액션 버튼, 선택 스타일, hover 애니메이션, 공통 체크 아이콘 생성을 메인 창 코드 밖으로 이동했다.

- ChatWindow.PopupPresentation.cs를 추가해 공통 테마 팝업 컨테이너, 메뉴 아이템, 구분선, 최근 폴더 컨텍스트 메뉴 구성을 한 곳으로 모았다.

- README와 DEVELOPMENT 문서에 2026-04-06 09:03 (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:
2026-04-06 09:02:41 +09:00
parent ccaa24745e
commit 4c1513a5da
5 changed files with 425 additions and 382 deletions

View File

@@ -0,0 +1,159 @@
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
namespace AxCopilot.Views;
public partial class ChatWindow
{
private Popup? _sharedContextPopup;
private (Popup Popup, StackPanel Panel) CreateThemedPopupMenu(
UIElement? placementTarget = null,
PlacementMode placement = PlacementMode.MousePoint,
double minWidth = 200)
{
_sharedContextPopup?.SetCurrentValue(Popup.IsOpenProperty, false);
var bg = TryFindResource("LauncherBackground") as Brush ?? Brushes.White;
var border = TryFindResource("BorderColor") as Brush ?? Brushes.Gray;
var panel = new StackPanel { Margin = new Thickness(2) };
var container = new Border
{
Background = bg,
BorderBrush = border,
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(10),
Padding = new Thickness(6),
MinWidth = minWidth,
Child = panel,
Effect = new System.Windows.Media.Effects.DropShadowEffect
{
BlurRadius = 16,
ShadowDepth = 3,
Opacity = 0.18,
Color = Colors.Black,
Direction = 270,
},
};
var popup = new Popup
{
Child = container,
StaysOpen = false,
AllowsTransparency = true,
PopupAnimation = PopupAnimation.Fade,
Placement = placement,
PlacementTarget = placementTarget,
};
_sharedContextPopup = popup;
return (popup, panel);
}
private Border CreatePopupMenuItem(
Popup popup,
string icon,
string label,
Brush iconBrush,
Brush labelBrush,
Brush hoverBrush,
Action action)
{
var sp = new StackPanel { Orientation = Orientation.Horizontal };
sp.Children.Add(new TextBlock
{
Text = icon,
FontFamily = new FontFamily("Segoe MDL2 Assets"),
FontSize = 12.5,
Foreground = iconBrush,
VerticalAlignment = VerticalAlignment.Center,
Margin = new Thickness(0, 0, 9, 0),
});
sp.Children.Add(new TextBlock
{
Text = label,
FontSize = 12.5,
Foreground = labelBrush,
VerticalAlignment = VerticalAlignment.Center,
});
var item = new Border
{
Child = sp,
Background = Brushes.Transparent,
CornerRadius = new CornerRadius(8),
Cursor = Cursors.Hand,
Padding = new Thickness(10, 7, 12, 7),
Margin = new Thickness(0, 1, 0, 1),
};
item.MouseEnter += (s, _) => { if (s is Border b) b.Background = hoverBrush; };
item.MouseLeave += (s, _) => { if (s is Border b) b.Background = Brushes.Transparent; };
item.MouseLeftButtonUp += (_, _) =>
{
popup.SetCurrentValue(Popup.IsOpenProperty, false);
action();
};
return item;
}
private static void AddPopupMenuSeparator(Panel panel, Brush brush)
{
panel.Children.Add(new Border
{
Height = 1,
Margin = new Thickness(10, 4, 10, 4),
Background = brush,
Opacity = 0.35,
});
}
/// <summary>최근 폴더 항목 우클릭 컨텍스트 메뉴를 표시합니다.</summary>
private void ShowRecentFolderContextMenu(string folderPath)
{
var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.White;
var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
var borderBrush = TryFindResource("BorderColor") as Brush ?? Brushes.Gray;
var hoverBg = TryFindResource("ItemHoverBackground") as Brush ?? Brushes.Transparent;
var warningBrush = new SolidColorBrush(Color.FromRgb(0xEF, 0x44, 0x44));
var (popup, panel) = CreateThemedPopupMenu();
panel.Children.Add(CreatePopupMenuItem(popup, "\uED25", "폴더 열기", secondaryText, primaryText, hoverBg, () =>
{
try
{
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
{
FileName = folderPath,
UseShellExecute = true,
});
}
catch
{
}
}));
panel.Children.Add(CreatePopupMenuItem(popup, "\uE8C8", "경로 복사", secondaryText, primaryText, hoverBg, () =>
{
try { Clipboard.SetText(folderPath); } catch { }
}));
AddPopupMenuSeparator(panel, borderBrush);
panel.Children.Add(CreatePopupMenuItem(popup, "\uE74D", "목록에서 삭제", warningBrush, warningBrush, hoverBg, () =>
{
_settings.Settings.Llm.RecentWorkFolders.RemoveAll(
p => p.Equals(folderPath, StringComparison.OrdinalIgnoreCase));
_settings.Save();
if (FolderMenuPopup.IsOpen)
ShowFolderMenu();
}));
Dispatcher.BeginInvoke(() => { popup.IsOpen = true; }, DispatcherPriority.Input);
}
}