[Phase46] 대형 파일 분할 리팩터링 2차 — 19개 신규 파셜 파일 생성
## 분할 대상 및 결과 ### AgentLoopService.cs (1,334줄 → 846줄) - AgentLoopService.HtmlReport.cs (151줄): AutoSaveAsHtml, ConvertTextToHtml, EscapeHtml - AgentLoopService.Verification.cs (349줄): 도구 분류 판별 + RunPostToolVerificationAsync + EmitEvent + CheckDecisionRequired + FormatToolCallSummary ### ChatWindow 분할 (8개 신규 파셜 파일) - ChatWindow.PlanViewer.cs (474줄): 계획 뷰어 — AddPlanningCard, AddDecisionButtons, CollapseDecisionButtons, ShowPlanButton 등 8개 메서드 - ChatWindow.EventBanner.cs (411줄): AddAgentEventBanner, BuildFileQuickActions - ChatWindow.TaskDecomposition.cs (1,170줄 → 307줄): RenderSuggestActionChips, BuildFeedbackContext, UpdateProgressBar, BuildDiffView 잔류 - ChatWindow.BottomBar.cs (345줄): BuildBottomBar, BuildCodeBottomBar, ShowLogLevelMenu, ShowLanguageMenu 등 - ChatWindow.MoodMenu.cs (456줄): ShowFormatMenu, ShowMoodMenu, ShowCustomMoodDialog 등 - ChatWindow.CustomPresets.cs (978줄 → 203줄): ShowCustomPresetDialog, SelectTopic 잔류 - ChatWindow.ConversationMenu.cs (255줄): ShowConversationMenu (카테고리/삭제/즐겨찾기 팝업) - ChatWindow.ConversationTitleEdit.cs (108줄): EnterTitleEditMode ### SettingsViewModel 분할 - SettingsViewModel.LlmProperties.cs (417줄): LLM·에이전트 관련 바인딩 프로퍼티 - SettingsViewModel.Properties.cs (837줄 → 427줄): 기능 토글·테마·스니펫 등 앱 수준 프로퍼티 ### TemplateService 분할 - TemplateService.Css.cs (559줄): 11종 CSS 테마 문자열 상수 - TemplateService.cs (734줄 → 179줄): 메서드 로직만 잔류 ### PlanViewerWindow 분할 - PlanViewerWindow.StepRenderer.cs (616줄): RenderSteps + SwapSteps + EditStep + 버튼 빌더 9개 - PlanViewerWindow.cs (931줄 → 324줄): Win32/생성자/공개 API 잔류 ### App.xaml.cs 분할 (776줄 → 452줄) - App.Settings.cs (252줄): SetupTrayIcon, OpenSettings, ToggleDockBar, RefreshDockBar, OpenAiChat - App.Helpers.cs (92줄): LoadAppIcon, IsAutoStartEnabled, SetAutoStart, OnExit ### LlmService.ToolUse.cs 분할 (719줄 → 115줄) - LlmService.ClaudeTools.cs (180줄): SendClaudeWithToolsAsync, BuildClaudeToolBody - LlmService.GeminiTools.cs (175줄): SendGeminiWithToolsAsync, BuildGeminiToolBody - LlmService.OpenAiTools.cs (215줄): SendOpenAiWithToolsAsync, BuildOpenAiToolBody ### SettingsWindow.UI.cs 분할 (802줄 → 310줄) - SettingsWindow.Storage.cs (167줄): RefreshStorageInfo, BtnStorageCleanup_Click 등 - SettingsWindow.HotkeyUI.cs (127줄): RefreshHotkeyBadges, EnsureHotkeyInCombo, GetKeyName 등 - SettingsWindow.DevMode.cs (90줄): DevModeCheckBox_Checked, UpdateDevModeContentVisibility ## 빌드 결과: 경고 0, 오류 0 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
616
src/AxCopilot/Views/PlanViewerWindow.StepRenderer.cs
Normal file
616
src/AxCopilot/Views/PlanViewerWindow.StepRenderer.cs
Normal file
@@ -0,0 +1,616 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace AxCopilot.Views;
|
||||
|
||||
internal sealed partial class PlanViewerWindow
|
||||
{
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// 단계 목록 렌더링
|
||||
// ════════════════════════════════════════════════════════════
|
||||
|
||||
private void RenderSteps()
|
||||
{
|
||||
_stepsPanel.Children.Clear();
|
||||
|
||||
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 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 canEdit = !_isExecuting && _currentStep < 0; // 승인 대기 중에만 편집/순서변경 가능
|
||||
|
||||
for (int i = 0; i < _steps.Count; i++)
|
||||
{
|
||||
var step = _steps[i];
|
||||
var capturedIdx = i;
|
||||
var isComplete = i < _currentStep;
|
||||
var isCurrent = i == _currentStep;
|
||||
var isPending = i > _currentStep;
|
||||
var isExpanded = _expandedSteps.Contains(i);
|
||||
|
||||
// ─ 카드 Border ─
|
||||
var card = new Border
|
||||
{
|
||||
CornerRadius = new CornerRadius(10),
|
||||
Padding = new Thickness(10, 7, 10, 7),
|
||||
Margin = new Thickness(0, 0, 0, 5),
|
||||
Background = isCurrent
|
||||
? new SolidColorBrush(Color.FromArgb(0x18,
|
||||
((SolidColorBrush)accentBrush).Color.R,
|
||||
((SolidColorBrush)accentBrush).Color.G,
|
||||
((SolidColorBrush)accentBrush).Color.B))
|
||||
: itemBg,
|
||||
BorderBrush = isCurrent ? accentBrush : Brushes.Transparent,
|
||||
BorderThickness = new Thickness(isCurrent ? 1.5 : 0),
|
||||
AllowDrop = canEdit,
|
||||
};
|
||||
|
||||
// 열기/닫기 토글 — 텍스트 또는 배경 클릭
|
||||
card.Cursor = Cursors.Hand;
|
||||
card.MouseLeftButtonUp += (s, e) =>
|
||||
{
|
||||
// 드래그 직후 클릭이 발생하는 경우 무시
|
||||
if (e.OriginalSource is Border src && src.Tag?.ToString() == "DragHandle") return;
|
||||
if (_expandedSteps.Contains(capturedIdx)) _expandedSteps.Remove(capturedIdx);
|
||||
else _expandedSteps.Add(capturedIdx);
|
||||
RenderSteps();
|
||||
};
|
||||
|
||||
// ─ 카드 Grid: [drag?][badge][*text][chevron][edit?] ─
|
||||
var cardGrid = new Grid();
|
||||
int badgeCol = canEdit ? 1 : 0;
|
||||
int textCol = canEdit ? 2 : 1;
|
||||
int chevCol = canEdit ? 3 : 2;
|
||||
int editCol = canEdit ? 4 : -1;
|
||||
|
||||
if (canEdit)
|
||||
cardGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); // drag
|
||||
cardGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); // badge
|
||||
cardGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }); // text
|
||||
cardGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); // chevron
|
||||
if (canEdit)
|
||||
cardGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); // edit btns
|
||||
|
||||
// ── 드래그 핸들 (편집 모드 전용) ──
|
||||
if (canEdit)
|
||||
{
|
||||
var dimColor = Color.FromArgb(0x55, 0x80, 0x80, 0x80);
|
||||
var dimBrush = new SolidColorBrush(dimColor);
|
||||
var dragHandle = new Border
|
||||
{
|
||||
Tag = "DragHandle",
|
||||
Width = 20, Cursor = Cursors.SizeAll,
|
||||
Background = Brushes.Transparent,
|
||||
Margin = new Thickness(0, 0, 6, 0),
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Child = new TextBlock
|
||||
{
|
||||
Text = "\uE8FD", // Sort/Lines 아이콘 (드래그 핸들)
|
||||
FontFamily = ThemeResourceHelper.SegoeMdl2,
|
||||
FontSize = 11,
|
||||
Foreground = dimBrush,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
},
|
||||
};
|
||||
dragHandle.MouseEnter += (s, _) =>
|
||||
((TextBlock)((Border)s).Child).Foreground = secondaryText;
|
||||
dragHandle.MouseLeave += (s, _) =>
|
||||
((TextBlock)((Border)s).Child).Foreground = dimBrush;
|
||||
|
||||
// 드래그 시작 — 마우스 눌림 위치 기록
|
||||
dragHandle.PreviewMouseLeftButtonDown += (s, e) =>
|
||||
{
|
||||
_dragSourceIndex = capturedIdx;
|
||||
_dragStartPoint = e.GetPosition(_stepsPanel);
|
||||
e.Handled = true; // 카드 클릭(expand) 이벤트 방지
|
||||
};
|
||||
// 충분히 움직이면 DragDrop 시작
|
||||
dragHandle.PreviewMouseMove += (s, e) =>
|
||||
{
|
||||
if (_dragSourceIndex < 0 || e.LeftButton != MouseButtonState.Pressed) return;
|
||||
var cur = e.GetPosition(_stepsPanel);
|
||||
if (Math.Abs(cur.X - _dragStartPoint.X) > SystemParameters.MinimumHorizontalDragDistance ||
|
||||
Math.Abs(cur.Y - _dragStartPoint.Y) > SystemParameters.MinimumVerticalDragDistance)
|
||||
{
|
||||
int idx = _dragSourceIndex;
|
||||
_dragSourceIndex = -1;
|
||||
DragDrop.DoDragDrop((DependencyObject)s,
|
||||
new DataObject(DragDataFormat, idx), DragDropEffects.Move);
|
||||
// DoDragDrop 완료 후 비주얼 정리
|
||||
Dispatcher.InvokeAsync(RenderSteps);
|
||||
}
|
||||
};
|
||||
dragHandle.PreviewMouseLeftButtonUp += (_, _) => _dragSourceIndex = -1;
|
||||
|
||||
Grid.SetColumn(dragHandle, 0);
|
||||
cardGrid.Children.Add(dragHandle);
|
||||
|
||||
// ── 카드 Drop 이벤트 ──
|
||||
card.DragOver += (s, e) =>
|
||||
{
|
||||
if (!e.Data.GetDataPresent(DragDataFormat)) return;
|
||||
int src = (int)e.Data.GetData(DragDataFormat);
|
||||
if (src != capturedIdx)
|
||||
{
|
||||
((Border)s).BorderBrush = accentBrush;
|
||||
((Border)s).BorderThickness = new Thickness(1.5);
|
||||
e.Effects = DragDropEffects.Move;
|
||||
}
|
||||
else e.Effects = DragDropEffects.None;
|
||||
e.Handled = true;
|
||||
};
|
||||
card.DragLeave += (s, _) =>
|
||||
{
|
||||
bool isCurr = _currentStep == capturedIdx;
|
||||
((Border)s).BorderBrush = isCurr ? accentBrush : Brushes.Transparent;
|
||||
((Border)s).BorderThickness = new Thickness(isCurr ? 1.5 : 0);
|
||||
};
|
||||
card.Drop += (s, e) =>
|
||||
{
|
||||
if (!e.Data.GetDataPresent(DragDataFormat)) { e.Handled = true; return; }
|
||||
int srcIdx = (int)e.Data.GetData(DragDataFormat);
|
||||
int dstIdx = capturedIdx;
|
||||
if (srcIdx != dstIdx && srcIdx >= 0 && srcIdx < _steps.Count)
|
||||
{
|
||||
var item = _steps[srcIdx];
|
||||
_steps.RemoveAt(srcIdx);
|
||||
// srcIdx < dstIdx 이면 제거 후 인덱스가 1 감소
|
||||
int insertAt = srcIdx < dstIdx ? dstIdx - 1 : dstIdx;
|
||||
_steps.Insert(insertAt, item);
|
||||
_expandedSteps.Clear();
|
||||
RenderSteps();
|
||||
}
|
||||
e.Handled = true;
|
||||
};
|
||||
}
|
||||
|
||||
// ── 상태 배지 ──
|
||||
UIElement badge;
|
||||
if (isComplete)
|
||||
{
|
||||
badge = new TextBlock
|
||||
{
|
||||
Text = "\uE73E", FontFamily = ThemeResourceHelper.SegoeMdl2,
|
||||
FontSize = 13, Foreground = new SolidColorBrush(Color.FromRgb(0x10, 0xB9, 0x81)),
|
||||
VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 10, 0),
|
||||
Width = 20, TextAlignment = TextAlignment.Center,
|
||||
};
|
||||
}
|
||||
else if (isCurrent)
|
||||
{
|
||||
badge = new TextBlock
|
||||
{
|
||||
Text = "\uE768", FontFamily = ThemeResourceHelper.SegoeMdl2,
|
||||
FontSize = 13, Foreground = accentBrush,
|
||||
VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 10, 0),
|
||||
Width = 20, TextAlignment = TextAlignment.Center,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
badge = new Border
|
||||
{
|
||||
Width = 22, Height = 22, CornerRadius = new CornerRadius(11),
|
||||
Background = new SolidColorBrush(Color.FromArgb(0x25, 0x80, 0x80, 0x80)),
|
||||
Margin = new Thickness(0, 0, 10, 0), VerticalAlignment = VerticalAlignment.Center,
|
||||
Child = new TextBlock
|
||||
{
|
||||
Text = $"{i + 1}", FontSize = 11, Foreground = secondaryText,
|
||||
FontWeight = FontWeights.SemiBold,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
},
|
||||
};
|
||||
}
|
||||
Grid.SetColumn(badge, badgeCol);
|
||||
cardGrid.Children.Add(badge);
|
||||
|
||||
// ── 단계 텍스트 ──
|
||||
var textBlock = new TextBlock
|
||||
{
|
||||
Text = step,
|
||||
FontSize = 13,
|
||||
Foreground = isComplete ? secondaryText : primaryText,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Opacity = isPending && _isExecuting ? 0.6 : 1.0,
|
||||
TextDecorations = isComplete ? TextDecorations.Strikethrough : null,
|
||||
Margin = new Thickness(0, 0, 4, 0),
|
||||
};
|
||||
if (isExpanded)
|
||||
{
|
||||
textBlock.TextWrapping = TextWrapping.Wrap;
|
||||
textBlock.TextTrimming = TextTrimming.None;
|
||||
}
|
||||
else
|
||||
{
|
||||
textBlock.TextWrapping = TextWrapping.NoWrap;
|
||||
textBlock.TextTrimming = TextTrimming.CharacterEllipsis;
|
||||
textBlock.ToolTip = step; // 접힌 상태: 호버 시 전체 텍스트 툴팁
|
||||
}
|
||||
Grid.SetColumn(textBlock, textCol);
|
||||
cardGrid.Children.Add(textBlock);
|
||||
|
||||
// ── 펼침/접힘 Chevron ──
|
||||
var chevron = new Border
|
||||
{
|
||||
Width = 22, Height = 22, CornerRadius = new CornerRadius(4),
|
||||
Background = Brushes.Transparent, Cursor = Cursors.Hand,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Margin = new Thickness(0, 0, canEdit ? 4 : 0, 0),
|
||||
Child = new TextBlock
|
||||
{
|
||||
Text = isExpanded ? "\uE70E" : "\uE70D", // ChevronUp / ChevronDown
|
||||
FontFamily = ThemeResourceHelper.SegoeMdl2,
|
||||
FontSize = 9,
|
||||
Foreground = new SolidColorBrush(Color.FromArgb(0x70, 0x80, 0x80, 0x80)),
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
},
|
||||
};
|
||||
chevron.MouseEnter += (s, _) => ((Border)s).Background = hoverBg;
|
||||
chevron.MouseLeave += (s, _) => ((Border)s).Background = Brushes.Transparent;
|
||||
chevron.MouseLeftButtonUp += (_, e) =>
|
||||
{
|
||||
if (_expandedSteps.Contains(capturedIdx)) _expandedSteps.Remove(capturedIdx);
|
||||
else _expandedSteps.Add(capturedIdx);
|
||||
RenderSteps();
|
||||
e.Handled = true;
|
||||
};
|
||||
Grid.SetColumn(chevron, chevCol);
|
||||
cardGrid.Children.Add(chevron);
|
||||
|
||||
// ── 편집 버튼 (위/아래/편집/삭제) ──
|
||||
if (canEdit)
|
||||
{
|
||||
var editBtnPanel = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Horizontal,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Margin = new Thickness(2, 0, 0, 0),
|
||||
};
|
||||
|
||||
if (i > 0)
|
||||
{
|
||||
var upBtn = CreateMiniButton("\uE70E", secondaryText, hoverBg);
|
||||
upBtn.ToolTip = "위로 이동";
|
||||
upBtn.MouseLeftButtonUp += (_, e) => { SwapSteps(capturedIdx, capturedIdx - 1); e.Handled = true; };
|
||||
editBtnPanel.Children.Add(upBtn);
|
||||
}
|
||||
if (i < _steps.Count - 1)
|
||||
{
|
||||
var downBtn = CreateMiniButton("\uE70D", secondaryText, hoverBg);
|
||||
downBtn.ToolTip = "아래로 이동";
|
||||
downBtn.MouseLeftButtonUp += (_, e) => { SwapSteps(capturedIdx, capturedIdx + 1); e.Handled = true; };
|
||||
editBtnPanel.Children.Add(downBtn);
|
||||
}
|
||||
|
||||
var editBtn = CreateMiniButton("\uE70F", accentBrush, hoverBg);
|
||||
editBtn.ToolTip = "편집";
|
||||
editBtn.MouseLeftButtonUp += (_, e) => { EditStep(capturedIdx); e.Handled = true; };
|
||||
editBtnPanel.Children.Add(editBtn);
|
||||
|
||||
var delBtn = CreateMiniButton("\uE74D", new SolidColorBrush(Color.FromRgb(0xDC, 0x26, 0x26)), hoverBg);
|
||||
delBtn.ToolTip = "삭제";
|
||||
delBtn.MouseLeftButtonUp += (_, e) =>
|
||||
{
|
||||
if (_steps.Count > 1)
|
||||
{
|
||||
_steps.RemoveAt(capturedIdx);
|
||||
_expandedSteps.Remove(capturedIdx);
|
||||
RenderSteps();
|
||||
}
|
||||
e.Handled = true;
|
||||
};
|
||||
editBtnPanel.Children.Add(delBtn);
|
||||
|
||||
Grid.SetColumn(editBtnPanel, editCol);
|
||||
cardGrid.Children.Add(editBtnPanel);
|
||||
}
|
||||
|
||||
card.Child = cardGrid;
|
||||
_stepsPanel.Children.Add(card);
|
||||
}
|
||||
|
||||
// ── 단계 추가 버튼 (편집 모드) ──
|
||||
if (canEdit)
|
||||
{
|
||||
var st2 = Application.Current.TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
|
||||
var hb2 = Application.Current.TryFindResource("ItemHoverBackground") as Brush
|
||||
?? new SolidColorBrush(Color.FromArgb(0x18, 0xFF, 0xFF, 0xFF));
|
||||
var addBtn = new Border
|
||||
{
|
||||
CornerRadius = new CornerRadius(10),
|
||||
Padding = new Thickness(14, 8, 14, 8),
|
||||
Margin = new Thickness(0, 4, 0, 0),
|
||||
Background = Brushes.Transparent,
|
||||
BorderBrush = new SolidColorBrush(Color.FromArgb(0x30, 0x80, 0x80, 0x80)),
|
||||
BorderThickness = new Thickness(1),
|
||||
Cursor = Cursors.Hand,
|
||||
};
|
||||
var addSp = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Center };
|
||||
addSp.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "\uE710", FontFamily = ThemeResourceHelper.SegoeMdl2,
|
||||
FontSize = 12, Foreground = st2,
|
||||
VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 6, 0),
|
||||
});
|
||||
addSp.Children.Add(new TextBlock { Text = "단계 추가", FontSize = 12, Foreground = st2 });
|
||||
addBtn.Child = addSp;
|
||||
addBtn.MouseEnter += (s, _) => ((Border)s).Background = hb2;
|
||||
addBtn.MouseLeave += (s, _) => ((Border)s).Background = Brushes.Transparent;
|
||||
addBtn.MouseLeftButtonUp += (_, _) =>
|
||||
{
|
||||
_steps.Add("새 단계");
|
||||
RenderSteps();
|
||||
EditStep(_steps.Count - 1);
|
||||
};
|
||||
_stepsPanel.Children.Add(addBtn);
|
||||
}
|
||||
|
||||
// 현재 단계로 자동 스크롤
|
||||
if (_currentStep >= 0 && _stepsPanel.Children.Count > _currentStep)
|
||||
{
|
||||
_stepsPanel.UpdateLayout();
|
||||
var target = (FrameworkElement)_stepsPanel.Children[Math.Min(_currentStep, _stepsPanel.Children.Count - 1)];
|
||||
target.BringIntoView();
|
||||
}
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// 단계 편집 / 교환
|
||||
// ════════════════════════════════════════════════════════════
|
||||
|
||||
private void SwapSteps(int a, int b)
|
||||
{
|
||||
if (a < 0 || b < 0 || a >= _steps.Count || b >= _steps.Count) return;
|
||||
(_steps[a], _steps[b]) = (_steps[b], _steps[a]);
|
||||
RenderSteps();
|
||||
}
|
||||
|
||||
private void EditStep(int index)
|
||||
{
|
||||
if (index < 0 || index >= _steps.Count) return;
|
||||
var primaryText = Application.Current.TryFindResource("PrimaryText") as Brush ?? Brushes.White;
|
||||
var itemBg = Application.Current.TryFindResource("ItemBackground") as Brush
|
||||
?? new SolidColorBrush(Color.FromRgb(0x2A, 0x2B, 0x40));
|
||||
var accentBrush = Application.Current.TryFindResource("AccentColor") as Brush
|
||||
?? new SolidColorBrush(Color.FromRgb(0x4B, 0x5E, 0xFC));
|
||||
|
||||
if (index >= _stepsPanel.Children.Count) return;
|
||||
|
||||
var editCard = new Border
|
||||
{
|
||||
CornerRadius = new CornerRadius(10),
|
||||
Padding = new Thickness(10, 8, 10, 8),
|
||||
Margin = new Thickness(0, 0, 0, 5),
|
||||
Background = itemBg,
|
||||
BorderBrush = accentBrush,
|
||||
BorderThickness = new Thickness(1.5),
|
||||
};
|
||||
|
||||
var textBox = new TextBox
|
||||
{
|
||||
Text = _steps[index],
|
||||
FontSize = 13,
|
||||
Background = Brushes.Transparent,
|
||||
Foreground = primaryText,
|
||||
CaretBrush = primaryText,
|
||||
BorderThickness = new Thickness(0),
|
||||
AcceptsReturn = false,
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
Padding = new Thickness(4),
|
||||
};
|
||||
|
||||
var capturedIdx = index;
|
||||
textBox.KeyDown += (_, e) =>
|
||||
{
|
||||
if (e.Key == Key.Enter) { _steps[capturedIdx] = textBox.Text.Trim(); RenderSteps(); e.Handled = true; }
|
||||
if (e.Key == Key.Escape) { RenderSteps(); e.Handled = true; }
|
||||
};
|
||||
textBox.LostFocus += (_, _) => { _steps[capturedIdx] = textBox.Text.Trim(); RenderSteps(); };
|
||||
|
||||
editCard.Child = textBox;
|
||||
_stepsPanel.Children[index] = editCard;
|
||||
textBox.Focus();
|
||||
textBox.SelectAll();
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// 하단 버튼 빌드
|
||||
// ════════════════════════════════════════════════════════════
|
||||
|
||||
private void BuildApprovalButtons()
|
||||
{
|
||||
_btnPanel.Children.Clear();
|
||||
var accentBrush = Application.Current.TryFindResource("AccentColor") as Brush
|
||||
?? new SolidColorBrush(Color.FromRgb(0x4B, 0x5E, 0xFC));
|
||||
|
||||
var approveBtn = CreateActionButton("\uE73E", "승인", accentBrush, Brushes.White, true);
|
||||
approveBtn.MouseLeftButtonUp += (_, _) =>
|
||||
{
|
||||
_tcs?.TrySetResult(null);
|
||||
SwitchToExecutionMode();
|
||||
};
|
||||
_btnPanel.Children.Add(approveBtn);
|
||||
|
||||
var editBtn = CreateActionButton("\uE70F", "수정 요청", accentBrush, accentBrush, false);
|
||||
editBtn.MouseLeftButtonUp += (_, _) => ShowEditInput();
|
||||
_btnPanel.Children.Add(editBtn);
|
||||
|
||||
var reconfirmBtn = CreateActionButton("\uE72C", "재확인",
|
||||
Application.Current.TryFindResource("SecondaryText") as Brush ?? Brushes.Gray,
|
||||
Application.Current.TryFindResource("PrimaryText") as Brush ?? Brushes.White, false);
|
||||
reconfirmBtn.MouseLeftButtonUp += (_, _) =>
|
||||
_tcs?.TrySetResult("계획을 다시 검토하고 더 구체적으로 수정해주세요.");
|
||||
_btnPanel.Children.Add(reconfirmBtn);
|
||||
|
||||
var cancelBrush = new SolidColorBrush(Color.FromRgb(0xDC, 0x26, 0x26));
|
||||
var cancelBtn = CreateActionButton("\uE711", "취소", cancelBrush, cancelBrush, false);
|
||||
cancelBtn.MouseLeftButtonUp += (_, _) => { _tcs?.TrySetResult("취소"); Hide(); };
|
||||
_btnPanel.Children.Add(cancelBtn);
|
||||
}
|
||||
|
||||
private void BuildExecutionButtons()
|
||||
{
|
||||
_btnPanel.Children.Clear();
|
||||
var secondaryText = Application.Current.TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
|
||||
var hideBtn = CreateActionButton("\uE921", "숨기기", secondaryText,
|
||||
Application.Current.TryFindResource("PrimaryText") as Brush ?? Brushes.White, false);
|
||||
hideBtn.MouseLeftButtonUp += (_, _) => Hide();
|
||||
_btnPanel.Children.Add(hideBtn);
|
||||
}
|
||||
|
||||
private void BuildCloseButton()
|
||||
{
|
||||
_btnPanel.Children.Clear();
|
||||
var accentBrush = Application.Current.TryFindResource("AccentColor") as Brush
|
||||
?? new SolidColorBrush(Color.FromRgb(0x4B, 0x5E, 0xFC));
|
||||
var closeBtn = CreateActionButton("\uE73E", "닫기", accentBrush, Brushes.White, true);
|
||||
closeBtn.MouseLeftButtonUp += (_, _) => Hide();
|
||||
_btnPanel.Children.Add(closeBtn);
|
||||
}
|
||||
|
||||
private void ShowEditInput()
|
||||
{
|
||||
var editPanel = new Border
|
||||
{
|
||||
Margin = new Thickness(20, 0, 20, 12),
|
||||
Padding = new Thickness(12, 8, 12, 8),
|
||||
CornerRadius = new CornerRadius(10),
|
||||
Background = Application.Current.TryFindResource("ItemBackground") as Brush
|
||||
?? new SolidColorBrush(Color.FromRgb(0x2A, 0x2B, 0x40)),
|
||||
};
|
||||
var editStack = new StackPanel();
|
||||
editStack.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "수정 사항을 입력하세요:",
|
||||
FontSize = 11.5,
|
||||
Foreground = Application.Current.TryFindResource("SecondaryText") as Brush ?? Brushes.Gray,
|
||||
Margin = new Thickness(0, 0, 0, 6),
|
||||
});
|
||||
var textBox = new TextBox
|
||||
{
|
||||
MinHeight = 44,
|
||||
MaxHeight = 120,
|
||||
AcceptsReturn = true,
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
FontSize = 13,
|
||||
Background = Application.Current.TryFindResource("LauncherBackground") as Brush
|
||||
?? new SolidColorBrush(Color.FromRgb(0x1A, 0x1B, 0x2E)),
|
||||
Foreground = Application.Current.TryFindResource("PrimaryText") as Brush ?? Brushes.White,
|
||||
CaretBrush = Application.Current.TryFindResource("PrimaryText") as Brush ?? Brushes.White,
|
||||
BorderBrush = Application.Current.TryFindResource("BorderColor") as Brush ?? Brushes.Gray,
|
||||
BorderThickness = new Thickness(1),
|
||||
Padding = new Thickness(10, 8, 10, 8),
|
||||
};
|
||||
editStack.Children.Add(textBox);
|
||||
|
||||
var accentBrush = Application.Current.TryFindResource("AccentColor") as Brush
|
||||
?? new SolidColorBrush(Color.FromRgb(0x4B, 0x5E, 0xFC));
|
||||
var sendBtn = new Border
|
||||
{
|
||||
Background = accentBrush,
|
||||
CornerRadius = new CornerRadius(8),
|
||||
Padding = new Thickness(14, 6, 14, 6),
|
||||
Margin = new Thickness(0, 8, 0, 0),
|
||||
Cursor = Cursors.Hand,
|
||||
HorizontalAlignment = HorizontalAlignment.Right,
|
||||
Child = new TextBlock
|
||||
{
|
||||
Text = "전송", FontSize = 12.5, FontWeight = FontWeights.SemiBold, Foreground = Brushes.White,
|
||||
},
|
||||
};
|
||||
sendBtn.MouseEnter += (s, _) => ((Border)s).Opacity = 0.85;
|
||||
sendBtn.MouseLeave += (s, _) => ((Border)s).Opacity = 1.0;
|
||||
sendBtn.MouseLeftButtonUp += (_, _) =>
|
||||
{
|
||||
var feedback = textBox.Text.Trim();
|
||||
if (string.IsNullOrEmpty(feedback)) return;
|
||||
_tcs?.TrySetResult(feedback);
|
||||
};
|
||||
editStack.Children.Add(sendBtn);
|
||||
editPanel.Child = editStack;
|
||||
|
||||
if (_btnPanel.Parent is Grid parentGrid)
|
||||
{
|
||||
for (int i = parentGrid.Children.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (parentGrid.Children[i] is Border b && b.Tag?.ToString() == "EditPanel")
|
||||
parentGrid.Children.RemoveAt(i);
|
||||
}
|
||||
editPanel.Tag = "EditPanel";
|
||||
Grid.SetRow(editPanel, 4); // row 4 = 하단 버튼 행 (toolBar 추가로 1 증가)
|
||||
parentGrid.Children.Add(editPanel);
|
||||
_btnPanel.Margin = new Thickness(20, 0, 20, 16);
|
||||
textBox.Focus();
|
||||
}
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// 공통 버튼 팩토리
|
||||
// ════════════════════════════════════════════════════════════
|
||||
|
||||
private static Border CreateMiniButton(string icon, Brush fg, Brush hoverBg)
|
||||
{
|
||||
var btn = new Border
|
||||
{
|
||||
Width = 24, Height = 24,
|
||||
CornerRadius = new CornerRadius(6),
|
||||
Background = Brushes.Transparent,
|
||||
Cursor = Cursors.Hand,
|
||||
Margin = new Thickness(1, 0, 1, 0),
|
||||
Child = new TextBlock
|
||||
{
|
||||
Text = icon, FontFamily = ThemeResourceHelper.SegoeMdl2,
|
||||
FontSize = 10, Foreground = fg,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
},
|
||||
};
|
||||
btn.MouseEnter += (s, _) => ((Border)s).Background = hoverBg;
|
||||
btn.MouseLeave += (s, _) => ((Border)s).Background = Brushes.Transparent;
|
||||
return btn;
|
||||
}
|
||||
|
||||
private static Border CreateActionButton(string icon, string text, Brush borderColor,
|
||||
Brush textColor, bool filled)
|
||||
{
|
||||
var color = ((SolidColorBrush)borderColor).Color;
|
||||
var btn = new Border
|
||||
{
|
||||
CornerRadius = new CornerRadius(12),
|
||||
Padding = new Thickness(16, 8, 16, 8),
|
||||
Margin = new Thickness(4, 0, 4, 0),
|
||||
Cursor = Cursors.Hand,
|
||||
Background = filled ? borderColor
|
||||
: new SolidColorBrush(Color.FromArgb(0x18, color.R, color.G, color.B)),
|
||||
BorderBrush = filled ? Brushes.Transparent
|
||||
: new SolidColorBrush(Color.FromArgb(0x80, color.R, color.G, color.B)),
|
||||
BorderThickness = new Thickness(filled ? 0 : 1.2),
|
||||
};
|
||||
var sp = new StackPanel { Orientation = Orientation.Horizontal };
|
||||
sp.Children.Add(new TextBlock
|
||||
{
|
||||
Text = icon, FontFamily = ThemeResourceHelper.SegoeMdl2,
|
||||
FontSize = 12, Foreground = filled ? Brushes.White : textColor,
|
||||
VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 6, 0),
|
||||
});
|
||||
sp.Children.Add(new TextBlock
|
||||
{
|
||||
Text = text, FontSize = 12.5, FontWeight = FontWeights.SemiBold,
|
||||
Foreground = filled ? Brushes.White : textColor,
|
||||
});
|
||||
btn.Child = sp;
|
||||
btn.MouseEnter += (s, _) => ((Border)s).Opacity = 0.85;
|
||||
btn.MouseLeave += (s, _) => ((Border)s).Opacity = 1.0;
|
||||
return btn;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user