using System.Linq; 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; /// /// 기본 MessageBox를 대체하는 커스텀 다이얼로그. /// 테마 리소스를 사용하여 앱 디자인과 일관된 모습을 제공합니다. /// internal sealed class CustomMessageBox : Window { private MessageBoxResult _result = MessageBoxResult.None; private CustomMessageBox(string message, string title, MessageBoxButton buttons, MessageBoxImage icon) { Title = title; Width = 400; MinWidth = 320; MaxWidth = 520; 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 root = new Border { Background = bgBrush, CornerRadius = new CornerRadius(16), BorderBrush = borderBrush, BorderThickness = new Thickness(1), Padding = new Thickness(28, 24, 28, 20), 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, 16) }; titleBar.MouseLeftButtonDown += (_, _) => { try { DragMove(); } catch { } }; // 아이콘 + 제목 var titlePanel = new StackPanel { Orientation = Orientation.Horizontal }; var (iconText, iconColor) = GetIconInfo(icon); if (!string.IsNullOrEmpty(iconText)) { titlePanel.Children.Add(new TextBlock { Text = iconText, FontFamily = new FontFamily("Segoe MDL2 Assets"), FontSize = 18, Foreground = iconColor, VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 10, 0), }); } titlePanel.Children.Add(new TextBlock { Text = title, FontSize = 15, FontWeight = FontWeights.SemiBold, Foreground = primaryText, VerticalAlignment = VerticalAlignment.Center, }); titleBar.Children.Add(titlePanel); // 닫기 버튼 var closeBtn = new Button { Content = "\uE8BB", FontFamily = new FontFamily("Segoe MDL2 Assets"), FontSize = 10, Foreground = secondaryText, Background = Brushes.Transparent, BorderThickness = new Thickness(0), Padding = new Thickness(6), Cursor = Cursors.Hand, HorizontalAlignment = HorizontalAlignment.Right, VerticalAlignment = VerticalAlignment.Center, }; closeBtn.Click += (_, _) => { _result = MessageBoxResult.Cancel; Close(); }; titleBar.Children.Add(closeBtn); stack.Children.Add(titleBar); // 메시지 본문 stack.Children.Add(new TextBlock { Text = message, FontSize = 13, Foreground = primaryText, TextWrapping = TextWrapping.Wrap, LineHeight = 20, Margin = new Thickness(0, 0, 0, 24), }); // 버튼 영역 var btnPanel = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Right, }; switch (buttons) { case MessageBoxButton.OK: btnPanel.Children.Add(CreateButton("확인", accentBrush, true, MessageBoxResult.OK)); break; case MessageBoxButton.OKCancel: btnPanel.Children.Add(CreateButton("취소", itemBg, false, MessageBoxResult.Cancel)); btnPanel.Children.Add(CreateButton("확인", accentBrush, true, MessageBoxResult.OK)); break; case MessageBoxButton.YesNo: btnPanel.Children.Add(CreateButton("아니오", itemBg, false, MessageBoxResult.No)); btnPanel.Children.Add(CreateButton("예", accentBrush, true, MessageBoxResult.Yes)); break; case MessageBoxButton.YesNoCancel: btnPanel.Children.Add(CreateButton("취소", itemBg, false, MessageBoxResult.Cancel)); btnPanel.Children.Add(CreateButton("아니오", itemBg, false, MessageBoxResult.No)); btnPanel.Children.Add(CreateButton("예", accentBrush, true, MessageBoxResult.Yes)); break; } stack.Children.Add(btnPanel); root.Child = stack; Content = root; // ESC로 닫기 KeyDown += (_, e) => { if (e.Key == Key.Escape) { _result = buttons == MessageBoxButton.YesNo ? MessageBoxResult.No : MessageBoxResult.Cancel; Close(); } else if (e.Key == Key.Enter) { _result = buttons switch { MessageBoxButton.YesNo or MessageBoxButton.YesNoCancel => MessageBoxResult.Yes, _ => MessageBoxResult.OK, }; Close(); } }; } private Button CreateButton(string text, Brush bg, bool isPrimary, MessageBoxResult result) { var btn = new Button { Content = text, FontSize = 12.5, FontWeight = isPrimary ? FontWeights.SemiBold : FontWeights.Normal, Foreground = isPrimary ? Brushes.White : (Application.Current.TryFindResource("PrimaryText") as Brush ?? Brushes.White), Background = bg, BorderThickness = new Thickness(0), Padding = new Thickness(20, 8, 20, 8), Margin = new Thickness(6, 0, 0, 0), Cursor = Cursors.Hand, MinWidth = 80, }; // 라운드 코너 템플릿 var template = new ControlTemplate(typeof(Button)); var border = new FrameworkElementFactory(typeof(Border)); border.SetValue(Border.BackgroundProperty, bg); border.SetValue(Border.CornerRadiusProperty, new CornerRadius(10)); border.SetValue(Border.PaddingProperty, new Thickness(20, 8, 20, 8)); var cp = new FrameworkElementFactory(typeof(ContentPresenter)); cp.SetValue(ContentPresenter.HorizontalAlignmentProperty, HorizontalAlignment.Center); cp.SetValue(ContentPresenter.VerticalAlignmentProperty, VerticalAlignment.Center); border.AppendChild(cp); template.VisualTree = border; btn.Template = template; btn.Click += (_, _) => { _result = result; Close(); }; return btn; } private static (string icon, Brush color) GetIconInfo(MessageBoxImage image) => image switch { MessageBoxImage.Error => ("\uEA39", new SolidColorBrush(Color.FromRgb(0xE5, 0x3E, 0x3E))), MessageBoxImage.Warning => ("\uE7BA", new SolidColorBrush(Color.FromRgb(0xDD, 0x6B, 0x20))), MessageBoxImage.Information => ("\uE946", new SolidColorBrush(Color.FromRgb(0x4B, 0x5E, 0xFC))), MessageBoxImage.Question => ("\uE9CE", new SolidColorBrush(Color.FromRgb(0x4B, 0x5E, 0xFC))), _ => ("", Brushes.Transparent), }; // ─── 정적 호출 메서드 (기존 MessageBox.Show 시그니처 호환) ────────────── public static MessageBoxResult Show(string message) => Show(message, "AX Copilot", MessageBoxButton.OK, MessageBoxImage.None); public static MessageBoxResult Show(string message, string title) => Show(message, title, MessageBoxButton.OK, MessageBoxImage.None); public static MessageBoxResult Show(string message, string title, MessageBoxButton buttons) => Show(message, title, buttons, MessageBoxImage.None); public static MessageBoxResult Show(string message, string title, MessageBoxButton buttons, MessageBoxImage icon) { var dlg = new CustomMessageBox(message, title, buttons, icon); // 등장 애니메이션 (페이드인 + 스케일) dlg.Opacity = 0; dlg.Loaded += (_, _) => { var fadeIn = new System.Windows.Media.Animation.DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(150)); dlg.BeginAnimation(OpacityProperty, fadeIn); }; // ─── 부모 Window 하단에 토스트 알림 표시 ─── var parentWindow = Application.Current.Windows.OfType() .FirstOrDefault(w => w.IsActive && w is not CustomMessageBox) ?? Application.Current.MainWindow; Border? toast = null; Grid? rootGrid = null; if (parentWindow?.Content is Border outerBorder && outerBorder.Child is Grid grid) { rootGrid = grid; toast = CreateToastBanner(title, icon); rootGrid.Children.Add(toast); } else if (parentWindow?.Content is Grid directGrid) { rootGrid = directGrid; toast = CreateToastBanner(title, icon); rootGrid.Children.Add(toast); } dlg.ShowDialog(); // 다이얼로그 닫힌 후 토스트 페이드아웃 if (toast != null && rootGrid != null) { var fadeOut = new DoubleAnimation(1, 0, TimeSpan.FromMilliseconds(400)) { EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseIn }, }; fadeOut.Completed += (_, _) => rootGrid.Children.Remove(toast); toast.BeginAnimation(OpacityProperty, fadeOut); } return dlg._result; } /// 하단 토스트 배너 생성. private static Border CreateToastBanner(string title, MessageBoxImage icon) { var (iconText, iconColor) = GetIconInfo(icon); if (string.IsNullOrEmpty(iconText)) { iconText = "\uE946"; // default info icon iconColor = new SolidColorBrush(Color.FromRgb(0x4B, 0x5E, 0xFC)); } var panel = new StackPanel { Orientation = Orientation.Horizontal }; panel.Children.Add(new TextBlock { Text = iconText, FontFamily = new FontFamily("Segoe MDL2 Assets"), FontSize = 12, Foreground = iconColor, VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 8, 0), }); panel.Children.Add(new TextBlock { Text = title, FontSize = 11.5, FontWeight = FontWeights.SemiBold, Foreground = Brushes.White, VerticalAlignment = VerticalAlignment.Center, }); var toast = new Border { Background = new SolidColorBrush(Color.FromArgb(0xE8, 0x2A, 0x2B, 0x40)), CornerRadius = new CornerRadius(10), Padding = new Thickness(16, 8, 16, 8), HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Bottom, Margin = new Thickness(0, 0, 0, 12), Effect = new DropShadowEffect { BlurRadius = 12, ShadowDepth = 2, Opacity = 0.3, Color = Colors.Black, }, Opacity = 0, Child = panel, }; // 모든 Grid Row/Column 스팬하도록 설정 Grid.SetColumnSpan(toast, 10); Grid.SetRowSpan(toast, 10); // 슬라이드업 + 페이드인 애니메이션 var translate = new TranslateTransform(0, 20); toast.RenderTransform = translate; toast.Loaded += (_, _) => { var fadeIn = new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(250)) { EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseOut }, }; var slideUp = new DoubleAnimation(20, 0, TimeSpan.FromMilliseconds(300)) { EasingFunction = new BackEase { EasingMode = EasingMode.EaseOut, Amplitude = 0.3 }, }; toast.BeginAnimation(OpacityProperty, fadeIn); translate.BeginAnimation(TranslateTransform.YProperty, slideUp); }; return toast; } /// WinForms 호환 — 인스톨러에서도 사용 가능하도록 Window 없이 표시. public static MessageBoxResult ShowStandalone(string message, string title, MessageBoxButton buttons = MessageBoxButton.OK, MessageBoxImage icon = MessageBoxImage.None) { var dlg = new CustomMessageBox(message, title, buttons, icon); dlg.ShowDialog(); return dlg._result; } }