using System.Text.RegularExpressions; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Shapes; namespace AxCopilot.Views; /// 커스텀 디자인 무드 추가/편집 다이얼로그. internal sealed partial class CustomMoodDialog : Window { private readonly TextBox _keyBox; private readonly TextBox _labelBox; private readonly TextBox _iconBox; private readonly TextBox _descBox; private readonly TextBox _cssBox; private readonly bool _isEdit; public string MoodKey => _keyBox.Text.Trim().ToLowerInvariant(); public string MoodLabel => _labelBox.Text.Trim(); public string MoodIcon => _iconBox.Text.Trim(); public string MoodDescription => _descBox.Text.Trim(); public string MoodCss => _cssBox.Text; public CustomMoodDialog( string existingKey = "", string existingLabel = "", string existingIcon = "🎯", string existingDesc = "", string existingCss = "") { _isEdit = !string.IsNullOrEmpty(existingKey); Title = _isEdit ? "무드 편집" : "무드 추가"; Width = 560; SizeToContent = SizeToContent.Height; WindowStartupLocation = WindowStartupLocation.CenterOwner; ResizeMode = ResizeMode.NoResize; WindowStyle = WindowStyle.None; AllowsTransparency = true; Background = Brushes.Transparent; 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 ?? Brushes.Blue; var itemBg = Application.Current.TryFindResource("ItemBackground") as Brush ?? new SolidColorBrush(Color.FromRgb(0x2A, 0x2B, 0x40)); var borderBrush = Application.Current.TryFindResource("BorderColor") as Brush ?? Brushes.Gray; var root = new Border { Background = bgBrush, CornerRadius = new CornerRadius(16), BorderBrush = borderBrush, BorderThickness = new Thickness(1), Padding = new Thickness(28, 24, 28, 24), Effect = new System.Windows.Media.Effects.DropShadowEffect { BlurRadius = 24, ShadowDepth = 4, Opacity = 0.3, Color = Colors.Black, }, }; var stack = new StackPanel(); // 헤더 var header = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 0, 0, 20) }; header.Children.Add(new TextBlock { Text = "\uE771", FontFamily = new FontFamily("Segoe MDL2 Assets"), FontSize = 18, Foreground = accentBrush, VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 10, 0), }); header.Children.Add(new TextBlock { Text = _isEdit ? "디자인 무드 편집" : "새 커스텀 디자인 무드", FontSize = 17, FontWeight = FontWeights.Bold, Foreground = primaryText, VerticalAlignment = VerticalAlignment.Center, }); stack.Children.Add(header); // ── 키 + 라벨 + 아이콘 (한 줄) ── var row1 = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 0, 0, 8) }; // 키 var keyStack = new StackPanel { Margin = new Thickness(0, 0, 12, 0) }; AddLabel(keyStack, "키 (영문)", primaryText); _keyBox = CreateTextBox(existingKey, primaryText, itemBg, accentBrush, borderBrush); _keyBox.Width = 140; _keyBox.IsEnabled = !_isEdit; // 편집 시 키 변경 불가 keyStack.Children.Add(new Border { CornerRadius = new CornerRadius(8), ClipToBounds = true, Child = _keyBox }); row1.Children.Add(keyStack); // 라벨 var labelStack = new StackPanel { Margin = new Thickness(0, 0, 12, 0) }; AddLabel(labelStack, "이름", primaryText); _labelBox = CreateTextBox(existingLabel, primaryText, itemBg, accentBrush, borderBrush); _labelBox.Width = 180; labelStack.Children.Add(new Border { CornerRadius = new CornerRadius(8), ClipToBounds = true, Child = _labelBox }); row1.Children.Add(labelStack); // 아이콘 (이모지) var iconStack = new StackPanel(); AddLabel(iconStack, "아이콘", primaryText); _iconBox = CreateTextBox(existingIcon, primaryText, itemBg, accentBrush, borderBrush); _iconBox.Width = 60; _iconBox.TextAlignment = TextAlignment.Center; iconStack.Children.Add(new Border { CornerRadius = new CornerRadius(8), ClipToBounds = true, Child = _iconBox }); row1.Children.Add(iconStack); stack.Children.Add(row1); // ── 설명 ── AddLabel(stack, "설명", primaryText); _descBox = CreateTextBox(existingDesc, primaryText, itemBg, accentBrush, borderBrush); stack.Children.Add(new Border { CornerRadius = new CornerRadius(8), ClipToBounds = true, Child = _descBox, Margin = new Thickness(0, 0, 0, 8) }); // ── CSS ── AddSeparator(stack, borderBrush); AddLabel(stack, "CSS 스타일", primaryText); AddHint(stack, "문서에 적용될 CSS입니다. body, h1~h6, table, .callout 등의 스타일을 정의하세요.", secondaryText); _cssBox = CreateTextBox(existingCss, primaryText, itemBg, accentBrush, borderBrush, multiline: true, height: 200); _cssBox.FontFamily = new FontFamily("Consolas, Courier New, monospace"); _cssBox.FontSize = 12; stack.Children.Add(new Border { CornerRadius = new CornerRadius(8), ClipToBounds = true, Child = _cssBox }); // CSS 힌트 버튼 var hintBtn = new TextBlock { Text = "CSS 예시 보기", FontSize = 11, Foreground = accentBrush, Cursor = Cursors.Hand, Margin = new Thickness(0, 6, 0, 0), TextDecorations = TextDecorations.Underline, }; hintBtn.MouseLeftButtonDown += (_, _) => { if (string.IsNullOrWhiteSpace(_cssBox.Text)) { _cssBox.Text = CssExample; } }; stack.Children.Add(hintBtn); // ── 버튼 바 ── var btnBar = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Right, Margin = new Thickness(0, 20, 0, 0), }; var cancelBtn = new Button { Content = "취소", Width = 80, Padding = new Thickness(0, 8, 0, 8), Margin = new Thickness(0, 0, 10, 0), Background = Brushes.Transparent, Foreground = secondaryText, BorderBrush = borderBrush, BorderThickness = new Thickness(1), Cursor = Cursors.Hand, FontSize = 13, }; cancelBtn.Click += (_, _) => { DialogResult = false; Close(); }; btnBar.Children.Add(cancelBtn); var okBtn = new Button { Content = _isEdit ? "저장" : "추가", Width = 80, Padding = new Thickness(0, 8, 0, 8), Background = accentBrush, Foreground = Brushes.White, BorderThickness = new Thickness(0), Cursor = Cursors.Hand, FontSize = 13, FontWeight = FontWeights.SemiBold, }; okBtn.Click += (_, _) => Validate(); btnBar.Children.Add(okBtn); stack.Children.Add(btnBar); root.Child = stack; Content = root; KeyDown += (_, ke) => { if (ke.Key == Key.Escape) { DialogResult = false; Close(); } }; Loaded += (_, _) => { (_isEdit ? _labelBox : _keyBox).Focus(); }; root.MouseLeftButtonDown += (_, me) => { if (me.LeftButton == MouseButtonState.Pressed) try { DragMove(); } catch { } }; } private void Validate() { if (string.IsNullOrWhiteSpace(_keyBox.Text)) { CustomMessageBox.Show("키를 입력하세요 (영문 소문자).", "입력 오류", MessageBoxButton.OK, MessageBoxImage.Warning); _keyBox.Focus(); return; } if (!Regex.IsMatch(_keyBox.Text.Trim(), @"^[a-z][a-z0-9_]{1,20}$")) { CustomMessageBox.Show("키는 영문 소문자로 시작하며, 소문자/숫자/밑줄만 허용됩니다 (2~21자).", "입력 오류", MessageBoxButton.OK, MessageBoxImage.Warning); _keyBox.Focus(); return; } if (string.IsNullOrWhiteSpace(_labelBox.Text)) { CustomMessageBox.Show("이름을 입력하세요.", "입력 오류", MessageBoxButton.OK, MessageBoxImage.Warning); _labelBox.Focus(); return; } // 내장 무드 키와 충돌 확인 if (!_isEdit) { var builtinKeys = new[] { "modern", "professional", "creative", "minimal", "elegant", "dark", "colorful", "corporate", "magazine", "dashboard" }; if (builtinKeys.Contains(_keyBox.Text.Trim().ToLowerInvariant())) { CustomMessageBox.Show("내장 무드와 동일한 키는 사용할 수 없습니다.", "입력 오류", MessageBoxButton.OK, MessageBoxImage.Warning); _keyBox.Focus(); return; } } DialogResult = true; Close(); } private static void AddLabel(StackPanel parent, string text, Brush fg) => parent.Children.Add(new TextBlock { Text = text, FontSize = 12, FontWeight = FontWeights.SemiBold, Foreground = fg, Margin = new Thickness(0, 0, 0, 6), }); private static void AddHint(StackPanel parent, string text, Brush fg) => parent.Children.Add(new TextBlock { Text = text, FontSize = 11, Foreground = fg, Margin = new Thickness(0, 0, 0, 8), }); private static void AddSeparator(StackPanel parent, Brush color) => parent.Children.Add(new Rectangle { Height = 1, Fill = color, Margin = new Thickness(0, 8, 0, 12), Opacity = 0.5, }); private static TextBox CreateTextBox(string text, Brush fg, Brush bg, Brush caret, Brush border, bool multiline = false, int height = 100) { var tb = new TextBox { Text = text, FontSize = 13, Padding = new Thickness(12, 8, 12, 8), Foreground = fg, Background = bg, CaretBrush = caret, BorderBrush = border, BorderThickness = new Thickness(1), }; if (multiline) { tb.AcceptsReturn = true; tb.AcceptsTab = true; tb.TextWrapping = TextWrapping.Wrap; tb.MinHeight = height; tb.MaxHeight = height + 100; tb.VerticalScrollBarVisibility = ScrollBarVisibility.Auto; } return tb; } private const string CssExample = @"body { font-family: 'Pretendard', 'Noto Sans KR', sans-serif; max-width: 900px; margin: 0 auto; padding: 40px; color: #1a1a2e; background: #f8f9fc; line-height: 1.8; } h1 { color: #2d3748; border-bottom: 2px solid #4a90d9; padding-bottom: 8px; } h2 { color: #4a5568; margin-top: 2em; } table { width: 100%; border-collapse: collapse; margin: 1.5em 0; } th { background: #4a90d9; color: white; padding: 10px; text-align: left; } td { padding: 8px 10px; border-bottom: 1px solid #e2e8f0; } tr:nth-child(even) { background: #f0f4f8; } "; }