Initial commit to new repository
This commit is contained in:
283
src/AxCopilot/Views/CustomMoodDialog.cs
Normal file
283
src/AxCopilot/Views/CustomMoodDialog.cs
Normal file
@@ -0,0 +1,283 @@
|
||||
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;
|
||||
|
||||
/// <summary>커스텀 디자인 무드 추가/편집 다이얼로그.</summary>
|
||||
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; }
|
||||
";
|
||||
}
|
||||
Reference in New Issue
Block a user