using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Media.Effects; namespace AxCopilot.Views; /// /// 에이전트가 사용자에게 질문할 때 표시하는 커스텀 대화 상자. /// 선택지 버튼 + 직접 입력 텍스트 박스를 제공합니다. /// internal sealed class UserAskDialog : Window { private string _selectedResponse = ""; private readonly TextBox _customInput; private readonly StackPanel _optionPanel; public string SelectedResponse => _selectedResponse; private UserAskDialog(string question, List options, string defaultValue) { Width = 460; MinWidth = 380; MaxWidth = 560; SizeToContent = SizeToContent.Height; WindowStartupLocation = WindowStartupLocation.CenterScreen; ResizeMode = ResizeMode.NoResize; WindowStyle = WindowStyle.None; AllowsTransparency = true; Background = Brushes.Transparent; Topmost = true; var bgBrush = Application.Current.TryFindResource("LauncherBackground") as Brush ?? new SolidColorBrush(Color.FromRgb(0x1A, 0x1B, 0x2E)); var primaryText = Application.Current.TryFindResource("PrimaryText") as Brush ?? Brushes.White; var secondaryText = Application.Current.TryFindResource("SecondaryText") as Brush ?? Brushes.Gray; var accentBrush = Application.Current.TryFindResource("AccentColor") as Brush ?? new SolidColorBrush(Color.FromRgb(0x4B, 0x5E, 0xFC)); var borderBrush = Application.Current.TryFindResource("BorderColor") as Brush ?? Brushes.Gray; var itemBg = Application.Current.TryFindResource("ItemBackground") as Brush ?? new SolidColorBrush(Color.FromRgb(0x2A, 0x2B, 0x40)); var hoverBg = Application.Current.TryFindResource("ItemHoverBackground") as Brush ?? new SolidColorBrush(Color.FromArgb(0x18, 0xFF, 0xFF, 0xFF)); // 루트 var root = new Border { Background = bgBrush, CornerRadius = new CornerRadius(16), BorderBrush = borderBrush, BorderThickness = new Thickness(1), Padding = new Thickness(24, 20, 24, 18), Effect = new DropShadowEffect { BlurRadius = 24, ShadowDepth = 4, Opacity = 0.3, Color = Colors.Black, }, }; var stack = new StackPanel(); // 타이틀 바 (드래그 가능) var titleBar = new Grid { Margin = new Thickness(0, 0, 0, 12) }; titleBar.MouseLeftButtonDown += (_, _) => { try { DragMove(); } catch { } }; var titleSp = new StackPanel { Orientation = Orientation.Horizontal }; titleSp.Children.Add(new TextBlock { Text = "\uE9CE", FontFamily = new FontFamily("Segoe MDL2 Assets"), FontSize = 16, Foreground = accentBrush, VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 8, 0), }); titleSp.Children.Add(new TextBlock { Text = "AX Agent — 질문", FontSize = 14, FontWeight = FontWeights.SemiBold, Foreground = primaryText, VerticalAlignment = VerticalAlignment.Center, }); titleBar.Children.Add(titleSp); stack.Children.Add(titleBar); // 구분선 stack.Children.Add(new Border { Height = 1, Background = borderBrush, Opacity = 0.3, Margin = new Thickness(0, 0, 0, 14), }); // 질문 텍스트 stack.Children.Add(new TextBlock { Text = question, FontSize = 13.5, Foreground = primaryText, TextWrapping = TextWrapping.Wrap, Margin = new Thickness(0, 0, 0, 16), LineHeight = 20, }); // 선택지 버튼들 _optionPanel = new StackPanel { Margin = new Thickness(0, 0, 0, 12) }; Border? selectedBorder = null; foreach (var option in options) { var capturedOption = option; var optBorder = new Border { Background = itemBg, CornerRadius = new CornerRadius(10), Padding = new Thickness(14, 10, 14, 10), Margin = new Thickness(0, 0, 0, 6), Cursor = Cursors.Hand, BorderBrush = Brushes.Transparent, BorderThickness = new Thickness(1.5), }; var optSp = new StackPanel { Orientation = Orientation.Horizontal }; var radioCircle = new Border { Width = 18, Height = 18, CornerRadius = new CornerRadius(9), BorderBrush = secondaryText, BorderThickness = new Thickness(1.5), Margin = new Thickness(0, 0, 10, 0), VerticalAlignment = VerticalAlignment.Center, Child = new Border { Width = 10, Height = 10, CornerRadius = new CornerRadius(5), Background = Brushes.Transparent, HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center, }, }; optSp.Children.Add(radioCircle); optSp.Children.Add(new TextBlock { Text = capturedOption, FontSize = 13, Foreground = primaryText, VerticalAlignment = VerticalAlignment.Center, TextWrapping = TextWrapping.Wrap, }); optBorder.Child = optSp; optBorder.MouseEnter += (s, _) => { var b = (Border)s; if (b.BorderBrush != accentBrush) b.Background = hoverBg; }; optBorder.MouseLeave += (s, _) => { var b = (Border)s; if (b.BorderBrush != accentBrush) b.Background = itemBg; }; optBorder.MouseLeftButtonUp += (s, _) => { // 이전 선택 해제 if (selectedBorder != null) { selectedBorder.BorderBrush = Brushes.Transparent; selectedBorder.Background = itemBg; var prevCircle = FindRadioCircle(selectedBorder); if (prevCircle != null) prevCircle.Background = Brushes.Transparent; } // 현재 선택 var cur = (Border)s; cur.BorderBrush = accentBrush; cur.Background = new SolidColorBrush(Color.FromArgb(0x15, ((SolidColorBrush)accentBrush).Color.R, ((SolidColorBrush)accentBrush).Color.G, ((SolidColorBrush)accentBrush).Color.B)); var circle = FindRadioCircle(cur); if (circle != null) circle.Background = accentBrush; selectedBorder = cur; _selectedResponse = capturedOption; if (_customInput != null) _customInput.Text = ""; // 선택지 고르면 직접 입력 초기화 }; _optionPanel.Children.Add(optBorder); } stack.Children.Add(_optionPanel); // 직접 입력 영역 stack.Children.Add(new TextBlock { Text = "또는 직접 입력:", FontSize = 11.5, Foreground = secondaryText, Margin = new Thickness(2, 0, 0, 6), }); _customInput = new TextBox { MinHeight = 40, MaxHeight = 100, AcceptsReturn = true, TextWrapping = TextWrapping.Wrap, FontSize = 13, Background = itemBg, Foreground = primaryText, CaretBrush = primaryText, BorderBrush = borderBrush, BorderThickness = new Thickness(1), Padding = new Thickness(10, 8, 10, 8), Text = defaultValue, }; _customInput.TextChanged += (_, _) => { if (!string.IsNullOrEmpty(_customInput.Text)) { // 직접 입력 시 선택 해제 if (selectedBorder != null) { selectedBorder.BorderBrush = Brushes.Transparent; selectedBorder.Background = itemBg; var prevCircle = FindRadioCircle(selectedBorder); if (prevCircle != null) prevCircle.Background = Brushes.Transparent; selectedBorder = null; } _selectedResponse = _customInput.Text; } }; stack.Children.Add(_customInput); // 하단 버튼 var btnPanel = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Right, Margin = new Thickness(0, 16, 0, 0), }; // 확인 버튼 var confirmBtn = new Border { Background = accentBrush, CornerRadius = new CornerRadius(10), Padding = new Thickness(20, 9, 20, 9), Cursor = Cursors.Hand, Margin = new Thickness(8, 0, 0, 0), }; confirmBtn.Child = new TextBlock { Text = "확인", FontSize = 13, FontWeight = FontWeights.SemiBold, Foreground = Brushes.White, }; confirmBtn.MouseEnter += (s, _) => ((Border)s).Opacity = 0.85; confirmBtn.MouseLeave += (s, _) => ((Border)s).Opacity = 1.0; confirmBtn.MouseLeftButtonUp += (_, _) => { if (string.IsNullOrWhiteSpace(_selectedResponse) && !string.IsNullOrWhiteSpace(defaultValue)) _selectedResponse = defaultValue; DialogResult = true; Close(); }; btnPanel.Children.Add(confirmBtn); // 취소 버튼 var cancelBtn = new Border { Background = Brushes.Transparent, CornerRadius = new CornerRadius(10), Padding = new Thickness(16, 9, 16, 9), Cursor = Cursors.Hand, BorderBrush = new SolidColorBrush(Color.FromArgb(0x40, 0xDC, 0x26, 0x26)), BorderThickness = new Thickness(1), }; cancelBtn.Child = new TextBlock { Text = "취소", FontSize = 13, Foreground = new SolidColorBrush(Color.FromRgb(0xDC, 0x26, 0x26)), }; cancelBtn.MouseEnter += (s, _) => ((Border)s).Opacity = 0.85; cancelBtn.MouseLeave += (s, _) => ((Border)s).Opacity = 1.0; cancelBtn.MouseLeftButtonUp += (_, _) => { _selectedResponse = ""; DialogResult = false; Close(); }; // 취소를 왼쪽에 배치 btnPanel.Children.Insert(0, cancelBtn); stack.Children.Add(btnPanel); root.Child = stack; Content = root; // 등장 애니메이션 root.RenderTransformOrigin = new Point(0.5, 0.5); root.RenderTransform = new ScaleTransform(0.95, 0.95); root.Opacity = 0; Loaded += (_, _) => { var fade = new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(150)); root.BeginAnimation(OpacityProperty, fade); var scaleX = new DoubleAnimation(0.95, 1, TimeSpan.FromMilliseconds(200)) { EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseOut } }; var scaleY = new DoubleAnimation(0.95, 1, TimeSpan.FromMilliseconds(200)) { EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseOut } }; ((ScaleTransform)root.RenderTransform).BeginAnimation(ScaleTransform.ScaleXProperty, scaleX); ((ScaleTransform)root.RenderTransform).BeginAnimation(ScaleTransform.ScaleYProperty, scaleY); }; // ESC → 취소 KeyDown += (_, e) => { if (e.Key == Key.Escape) { _selectedResponse = ""; DialogResult = false; Close(); } if (e.Key == Key.Enter && !_customInput.IsFocused) { DialogResult = true; Close(); } }; } private static Border? FindRadioCircle(Border optionBorder) { if (optionBorder.Child is StackPanel sp && sp.Children.Count > 0 && sp.Children[0] is Border outer && outer.Child is Border inner) return inner; return null; } /// 에이전트 질문 다이얼로그를 표시합니다. /// 사용자 응답 문자열. 취소 시 null. public static string? Show(string question, List options, string defaultValue = "") { var dlg = new UserAskDialog(question, options, defaultValue); var result = dlg.ShowDialog(); return result == true ? dlg.SelectedResponse : null; } }