AX Agent footer와 Git 브랜치 프레젠테이션 구조를 분리하고 회귀 점검 루틴을 고정한다
Some checks failed
Release Gate / gate (push) Has been cancelled
Some checks failed
Release Gate / gate (push) Has been cancelled
- ChatWindow.FooterPresentation에서 Git 브랜치 팝업 렌더와 요약 helper를 분리해 폴더 바 상태/프리셋 안내 동기화 책임만 남긴다 - ChatWindow.GitBranchPresentation을 추가해 브랜치 팝업 조립, 요약 pill, 최근 브랜치/전환 액션 렌더를 별도 프레젠테이션 계층으로 옮긴다 - AX_AGENT_REGRESSION_PROMPTS 문서를 재작성해 실패 분류와 Chat/Cowork/Code별 필수 회귀 묶음을 개발 루틴으로 고정한다 - 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\ (경고 0 / 오류 0)
This commit is contained in:
@@ -1,11 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using AxCopilot.Models;
|
||||
|
||||
namespace AxCopilot.Views;
|
||||
@@ -14,7 +11,9 @@ public partial class ChatWindow
|
||||
{
|
||||
private void UpdateFolderBar()
|
||||
{
|
||||
if (FolderBar == null) return;
|
||||
if (FolderBar == null)
|
||||
return;
|
||||
|
||||
if (_activeTab == "Chat")
|
||||
{
|
||||
FolderBar.Visibility = Visibility.Collapsed;
|
||||
@@ -90,352 +89,4 @@ public partial class ChatWindow
|
||||
: preset.Description;
|
||||
SelectedPresetGuide.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
private void UpdateGitBranchUi(string? branchName, string filesText, string addedText, string deletedText, string tooltip, Visibility visibility)
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
_currentGitBranchName = branchName;
|
||||
_currentGitTooltip = tooltip;
|
||||
|
||||
if (BtnGitBranch != null)
|
||||
{
|
||||
BtnGitBranch.Visibility = visibility;
|
||||
BtnGitBranch.ToolTip = string.IsNullOrWhiteSpace(tooltip) ? "현재 Git 브랜치 상태" : tooltip;
|
||||
}
|
||||
|
||||
if (GitBranchLabel != null)
|
||||
GitBranchLabel.Text = string.IsNullOrWhiteSpace(branchName) ? "브랜치 없음" : branchName;
|
||||
if (GitBranchFilesText != null)
|
||||
GitBranchFilesText.Text = filesText;
|
||||
if (GitBranchAddedText != null)
|
||||
GitBranchAddedText.Text = addedText;
|
||||
if (GitBranchDeletedText != null)
|
||||
GitBranchDeletedText.Text = deletedText;
|
||||
if (GitBranchSeparator != null)
|
||||
GitBranchSeparator.Visibility = visibility;
|
||||
});
|
||||
}
|
||||
|
||||
private void BuildGitBranchPopup()
|
||||
{
|
||||
if (GitBranchItems == null)
|
||||
return;
|
||||
|
||||
GitBranchItems.Children.Clear();
|
||||
|
||||
var gitRoot = _currentGitRoot ?? ResolveGitRoot(GetCurrentWorkFolder());
|
||||
var branchName = _currentGitBranchName ?? "detached";
|
||||
var tooltip = _currentGitTooltip ?? "";
|
||||
var fileText = GitBranchFilesText?.Text ?? "";
|
||||
var addedText = GitBranchAddedText?.Text ?? "";
|
||||
var deletedText = GitBranchDeletedText?.Text ?? "";
|
||||
var query = (_gitBranchSearchText ?? "").Trim();
|
||||
var accentBrush = TryFindResource("AccentColor") as Brush ?? Brushes.CornflowerBlue;
|
||||
var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.White;
|
||||
var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
|
||||
|
||||
GitBranchItems.Children.Add(CreatePopupSummaryStrip(new[]
|
||||
{
|
||||
("브랜치", string.IsNullOrWhiteSpace(branchName) ? "없음" : branchName, "#F8FAFC", "#E2E8F0", "#475569"),
|
||||
("파일", string.IsNullOrWhiteSpace(fileText) ? "0" : fileText, "#EFF6FF", "#BFDBFE", "#1D4ED8"),
|
||||
("최근", _recentGitBranches.Count.ToString(), "#F5F3FF", "#DDD6FE", "#6D28D9"),
|
||||
}));
|
||||
GitBranchItems.Children.Add(CreatePopupSectionLabel("현재 브랜치", new Thickness(8, 6, 8, 4)));
|
||||
GitBranchItems.Children.Add(CreatePopupMenuRow(
|
||||
"\uE943",
|
||||
branchName,
|
||||
string.IsNullOrWhiteSpace(fileText) ? "현재 브랜치" : fileText,
|
||||
true,
|
||||
accentBrush,
|
||||
secondaryText,
|
||||
primaryText,
|
||||
() => { }));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(addedText) || !string.IsNullOrWhiteSpace(deletedText))
|
||||
{
|
||||
var stats = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Horizontal,
|
||||
Margin = new Thickness(8, 2, 8, 8),
|
||||
};
|
||||
if (!string.IsNullOrWhiteSpace(addedText))
|
||||
stats.Children.Add(CreateMetricPill(addedText, "#16A34A"));
|
||||
if (!string.IsNullOrWhiteSpace(deletedText))
|
||||
stats.Children.Add(CreateMetricPill(deletedText, "#DC2626"));
|
||||
GitBranchItems.Children.Add(stats);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(gitRoot))
|
||||
{
|
||||
GitBranchItems.Children.Add(CreatePopupSectionLabel("저장소", new Thickness(8, 6, 8, 4)));
|
||||
GitBranchItems.Children.Add(CreatePopupMenuRow(
|
||||
"\uED25",
|
||||
System.IO.Path.GetFileName(gitRoot.TrimEnd('\\', '/')),
|
||||
gitRoot,
|
||||
false,
|
||||
accentBrush,
|
||||
secondaryText,
|
||||
primaryText,
|
||||
() => { }));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_currentGitUpstreamStatus))
|
||||
{
|
||||
GitBranchItems.Children.Add(CreatePopupMenuRow(
|
||||
"\uE8AB",
|
||||
"업스트림",
|
||||
_currentGitUpstreamStatus!,
|
||||
false,
|
||||
accentBrush,
|
||||
secondaryText,
|
||||
primaryText,
|
||||
() => { }));
|
||||
}
|
||||
|
||||
GitBranchItems.Children.Add(CreatePopupSectionLabel("빠른 작업", new Thickness(8, 10, 8, 4)));
|
||||
GitBranchItems.Children.Add(CreatePopupMenuRow(
|
||||
"\uE8C8",
|
||||
"상태 요약 복사",
|
||||
"브랜치, 변경 파일, 추가/삭제 라인 복사",
|
||||
false,
|
||||
accentBrush,
|
||||
secondaryText,
|
||||
primaryText,
|
||||
() =>
|
||||
{
|
||||
try { Clipboard.SetText(tooltip); } catch { }
|
||||
GitBranchPopup.IsOpen = false;
|
||||
}));
|
||||
|
||||
GitBranchItems.Children.Add(CreatePopupMenuRow(
|
||||
"\uE72B",
|
||||
"새로고침",
|
||||
"Git 상태를 다시 조회합니다",
|
||||
false,
|
||||
accentBrush,
|
||||
secondaryText,
|
||||
primaryText,
|
||||
async () =>
|
||||
{
|
||||
await RefreshGitBranchStatusAsync();
|
||||
BuildGitBranchPopup();
|
||||
}));
|
||||
|
||||
var filteredBranches = _currentGitBranches
|
||||
.Where(branch => string.IsNullOrWhiteSpace(query)
|
||||
|| branch.Contains(query, StringComparison.OrdinalIgnoreCase))
|
||||
.Take(20)
|
||||
.ToList();
|
||||
|
||||
var recentBranches = _recentGitBranches
|
||||
.Where(branch => _currentGitBranches.Any(current => string.Equals(current, branch, StringComparison.OrdinalIgnoreCase)))
|
||||
.Where(branch => string.IsNullOrWhiteSpace(query)
|
||||
|| branch.Contains(query, StringComparison.OrdinalIgnoreCase))
|
||||
.Take(5)
|
||||
.ToList();
|
||||
|
||||
if (recentBranches.Count > 0)
|
||||
{
|
||||
GitBranchItems.Children.Add(CreatePopupSectionLabel($"최근 전환 · {recentBranches.Count}", new Thickness(8, 10, 8, 4)));
|
||||
|
||||
foreach (var branch in recentBranches)
|
||||
{
|
||||
var isCurrent = string.Equals(branch, branchName, StringComparison.OrdinalIgnoreCase);
|
||||
GitBranchItems.Children.Add(CreatePopupMenuRow(
|
||||
isCurrent ? "\uE73E" : "\uE8FD",
|
||||
branch,
|
||||
isCurrent ? "현재 브랜치" : "최근 사용 브랜치",
|
||||
isCurrent,
|
||||
accentBrush,
|
||||
secondaryText,
|
||||
primaryText,
|
||||
isCurrent ? null : () => _ = SwitchGitBranchAsync(branch)));
|
||||
}
|
||||
}
|
||||
|
||||
if (_currentGitBranches.Count > 0)
|
||||
{
|
||||
var branchSectionLabel = string.IsNullOrWhiteSpace(query)
|
||||
? $"브랜치 전환 · {_currentGitBranches.Count}"
|
||||
: $"브랜치 전환 · {filteredBranches.Count}/{_currentGitBranches.Count}";
|
||||
GitBranchItems.Children.Add(CreatePopupSectionLabel(branchSectionLabel, new Thickness(8, 10, 8, 4)));
|
||||
|
||||
foreach (var branch in filteredBranches)
|
||||
{
|
||||
if (recentBranches.Any(recent => string.Equals(recent, branch, StringComparison.OrdinalIgnoreCase)))
|
||||
continue;
|
||||
|
||||
var isCurrent = string.Equals(branch, branchName, StringComparison.OrdinalIgnoreCase);
|
||||
GitBranchItems.Children.Add(CreatePopupMenuRow(
|
||||
isCurrent ? "\uE73E" : "\uE943",
|
||||
branch,
|
||||
isCurrent ? "현재 브랜치" : "이 브랜치로 전환",
|
||||
isCurrent,
|
||||
accentBrush,
|
||||
secondaryText,
|
||||
primaryText,
|
||||
isCurrent ? null : () => _ = SwitchGitBranchAsync(branch)));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query) && filteredBranches.Count == 0)
|
||||
{
|
||||
GitBranchItems.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "검색 결과가 없습니다.",
|
||||
FontSize = 11.5,
|
||||
Foreground = secondaryText,
|
||||
Margin = new Thickness(10, 6, 10, 10),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
GitBranchItems.Children.Add(CreatePopupSectionLabel("브랜치 작업", new Thickness(8, 10, 8, 4)));
|
||||
GitBranchItems.Children.Add(CreatePopupMenuRow(
|
||||
"\uE710",
|
||||
"새 브랜치 생성",
|
||||
"현재 작업 기준으로 새 브랜치를 만들고 전환합니다",
|
||||
false,
|
||||
accentBrush,
|
||||
secondaryText,
|
||||
primaryText,
|
||||
() => _ = CreateGitBranchAsync()));
|
||||
}
|
||||
|
||||
private TextBlock CreatePopupSectionLabel(string text, Thickness? margin = null)
|
||||
{
|
||||
return new TextBlock
|
||||
{
|
||||
Text = text,
|
||||
FontSize = 10.5,
|
||||
FontWeight = FontWeights.SemiBold,
|
||||
Foreground = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray,
|
||||
Margin = margin ?? new Thickness(8, 8, 8, 4),
|
||||
};
|
||||
}
|
||||
|
||||
private UIElement CreatePopupSummaryStrip(IEnumerable<(string Label, string Value, string BgHex, string BorderHex, string FgHex)> items)
|
||||
{
|
||||
var wrap = new WrapPanel
|
||||
{
|
||||
Margin = new Thickness(8, 6, 8, 6),
|
||||
};
|
||||
|
||||
foreach (var item in items)
|
||||
wrap.Children.Add(CreateMetricPill($"{item.Label} {item.Value}", item.FgHex, item.BgHex, item.BorderHex));
|
||||
|
||||
return wrap;
|
||||
}
|
||||
|
||||
private Border CreateMetricPill(string text, string colorHex)
|
||||
=> CreateMetricPill(text, colorHex, $"{colorHex}18", $"{colorHex}44");
|
||||
|
||||
private Border CreateMetricPill(string text, string colorHex, string bgHex, string borderHex)
|
||||
{
|
||||
return new Border
|
||||
{
|
||||
Background = BrushFromHex(bgHex),
|
||||
BorderBrush = BrushFromHex(borderHex),
|
||||
BorderThickness = new Thickness(1),
|
||||
CornerRadius = new CornerRadius(999),
|
||||
Padding = new Thickness(8, 3, 8, 3),
|
||||
Margin = new Thickness(0, 0, 6, 0),
|
||||
Child = new TextBlock
|
||||
{
|
||||
Text = text,
|
||||
FontSize = 10.5,
|
||||
FontWeight = FontWeights.SemiBold,
|
||||
Foreground = BrushFromHex(colorHex),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Border CreateFlatPopupRow(string icon, string title, string description, string colorHex, bool clickable, Action? onClick)
|
||||
{
|
||||
var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.White;
|
||||
var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
|
||||
var hoverBrush = TryFindResource("ItemHoverBackground") as Brush ?? Brushes.LightGray;
|
||||
var borderColor = TryFindResource("BorderColor") as Brush ?? Brushes.Gray;
|
||||
|
||||
var border = new Border
|
||||
{
|
||||
Background = Brushes.Transparent,
|
||||
BorderBrush = borderColor,
|
||||
BorderThickness = new Thickness(0, 0, 0, 1),
|
||||
Padding = new Thickness(8, 9, 8, 9),
|
||||
Cursor = clickable ? Cursors.Hand : Cursors.Arrow,
|
||||
Focusable = clickable,
|
||||
};
|
||||
KeyboardNavigation.SetIsTabStop(border, clickable);
|
||||
|
||||
var grid = new Grid();
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
|
||||
|
||||
grid.Children.Add(new TextBlock
|
||||
{
|
||||
Text = icon,
|
||||
FontFamily = new FontFamily("Segoe MDL2 Assets"),
|
||||
FontSize = 11,
|
||||
Foreground = BrushFromHex(colorHex),
|
||||
VerticalAlignment = VerticalAlignment.Top,
|
||||
Margin = new Thickness(0, 1, 10, 0),
|
||||
});
|
||||
|
||||
var textStack = new StackPanel();
|
||||
textStack.Children.Add(new TextBlock
|
||||
{
|
||||
Text = title,
|
||||
FontSize = 12,
|
||||
FontWeight = FontWeights.SemiBold,
|
||||
Foreground = primaryText,
|
||||
});
|
||||
if (!string.IsNullOrWhiteSpace(description))
|
||||
{
|
||||
textStack.Children.Add(new TextBlock
|
||||
{
|
||||
Text = description,
|
||||
FontSize = 10.5,
|
||||
Foreground = secondaryText,
|
||||
Margin = new Thickness(0, 2, 0, 0),
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
});
|
||||
}
|
||||
|
||||
Grid.SetColumn(textStack, 1);
|
||||
grid.Children.Add(textStack);
|
||||
if (clickable)
|
||||
{
|
||||
var chevron = new TextBlock
|
||||
{
|
||||
Text = "\uE76C",
|
||||
FontFamily = new FontFamily("Segoe MDL2 Assets"),
|
||||
FontSize = 10,
|
||||
Foreground = secondaryText,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Margin = new Thickness(8, 0, 0, 0),
|
||||
};
|
||||
Grid.SetColumn(chevron, 2);
|
||||
grid.Children.Add(chevron);
|
||||
}
|
||||
border.Child = grid;
|
||||
|
||||
if (clickable && onClick != null)
|
||||
{
|
||||
border.MouseEnter += (_, _) => border.Background = hoverBrush;
|
||||
border.MouseLeave += (_, _) => border.Background = Brushes.Transparent;
|
||||
border.MouseLeftButtonUp += (_, _) => onClick();
|
||||
border.KeyDown += (_, ke) =>
|
||||
{
|
||||
if (ke.Key is Key.Enter or Key.Space)
|
||||
{
|
||||
ke.Handled = true;
|
||||
onClick();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return border;
|
||||
}
|
||||
}
|
||||
|
||||
361
src/AxCopilot/Views/ChatWindow.GitBranchPresentation.cs
Normal file
361
src/AxCopilot/Views/ChatWindow.GitBranchPresentation.cs
Normal file
@@ -0,0 +1,361 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace AxCopilot.Views;
|
||||
|
||||
public partial class ChatWindow
|
||||
{
|
||||
private void UpdateGitBranchUi(string? branchName, string filesText, string addedText, string deletedText, string tooltip, Visibility visibility)
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
_currentGitBranchName = branchName;
|
||||
_currentGitTooltip = tooltip;
|
||||
|
||||
if (BtnGitBranch != null)
|
||||
{
|
||||
BtnGitBranch.Visibility = visibility;
|
||||
BtnGitBranch.ToolTip = string.IsNullOrWhiteSpace(tooltip) ? "현재 Git 브랜치 상태" : tooltip;
|
||||
}
|
||||
|
||||
if (GitBranchLabel != null)
|
||||
GitBranchLabel.Text = string.IsNullOrWhiteSpace(branchName) ? "브랜치 없음" : branchName;
|
||||
if (GitBranchFilesText != null)
|
||||
GitBranchFilesText.Text = filesText;
|
||||
if (GitBranchAddedText != null)
|
||||
GitBranchAddedText.Text = addedText;
|
||||
if (GitBranchDeletedText != null)
|
||||
GitBranchDeletedText.Text = deletedText;
|
||||
if (GitBranchSeparator != null)
|
||||
GitBranchSeparator.Visibility = visibility;
|
||||
});
|
||||
}
|
||||
|
||||
private void BuildGitBranchPopup()
|
||||
{
|
||||
if (GitBranchItems == null)
|
||||
return;
|
||||
|
||||
GitBranchItems.Children.Clear();
|
||||
|
||||
var gitRoot = _currentGitRoot ?? ResolveGitRoot(GetCurrentWorkFolder());
|
||||
var branchName = _currentGitBranchName ?? "detached";
|
||||
var tooltip = _currentGitTooltip ?? "";
|
||||
var fileText = GitBranchFilesText?.Text ?? "";
|
||||
var addedText = GitBranchAddedText?.Text ?? "";
|
||||
var deletedText = GitBranchDeletedText?.Text ?? "";
|
||||
var query = (_gitBranchSearchText ?? "").Trim();
|
||||
var accentBrush = TryFindResource("AccentColor") as Brush ?? Brushes.CornflowerBlue;
|
||||
var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.White;
|
||||
var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
|
||||
|
||||
GitBranchItems.Children.Add(CreatePopupSummaryStrip(new[]
|
||||
{
|
||||
("브랜치", string.IsNullOrWhiteSpace(branchName) ? "없음" : branchName, "#F8FAFC", "#E2E8F0", "#475569"),
|
||||
("파일", string.IsNullOrWhiteSpace(fileText) ? "0" : fileText, "#EFF6FF", "#BFDBFE", "#1D4ED8"),
|
||||
("최근", _recentGitBranches.Count.ToString(), "#F5F3FF", "#DDD6FE", "#6D28D9"),
|
||||
}));
|
||||
GitBranchItems.Children.Add(CreatePopupSectionLabel("현재 브랜치", new Thickness(8, 6, 8, 4)));
|
||||
GitBranchItems.Children.Add(CreatePopupMenuRow(
|
||||
"\uE943",
|
||||
branchName,
|
||||
string.IsNullOrWhiteSpace(fileText) ? "현재 브랜치" : fileText,
|
||||
true,
|
||||
accentBrush,
|
||||
secondaryText,
|
||||
primaryText,
|
||||
() => { }));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(addedText) || !string.IsNullOrWhiteSpace(deletedText))
|
||||
{
|
||||
var stats = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Horizontal,
|
||||
Margin = new Thickness(8, 2, 8, 8),
|
||||
};
|
||||
if (!string.IsNullOrWhiteSpace(addedText))
|
||||
stats.Children.Add(CreateMetricPill(addedText, "#16A34A"));
|
||||
if (!string.IsNullOrWhiteSpace(deletedText))
|
||||
stats.Children.Add(CreateMetricPill(deletedText, "#DC2626"));
|
||||
GitBranchItems.Children.Add(stats);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(gitRoot))
|
||||
{
|
||||
GitBranchItems.Children.Add(CreatePopupSectionLabel("저장소", new Thickness(8, 6, 8, 4)));
|
||||
GitBranchItems.Children.Add(CreatePopupMenuRow(
|
||||
"\uED25",
|
||||
System.IO.Path.GetFileName(gitRoot.TrimEnd('\\', '/')),
|
||||
gitRoot,
|
||||
false,
|
||||
accentBrush,
|
||||
secondaryText,
|
||||
primaryText,
|
||||
() => { }));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_currentGitUpstreamStatus))
|
||||
{
|
||||
GitBranchItems.Children.Add(CreatePopupMenuRow(
|
||||
"\uE8AB",
|
||||
"업스트림",
|
||||
_currentGitUpstreamStatus!,
|
||||
false,
|
||||
accentBrush,
|
||||
secondaryText,
|
||||
primaryText,
|
||||
() => { }));
|
||||
}
|
||||
|
||||
GitBranchItems.Children.Add(CreatePopupSectionLabel("빠른 작업", new Thickness(8, 10, 8, 4)));
|
||||
GitBranchItems.Children.Add(CreatePopupMenuRow(
|
||||
"\uE8C8",
|
||||
"상태 요약 복사",
|
||||
"브랜치 변경 파일, 추가/삭제 라인 복사",
|
||||
false,
|
||||
accentBrush,
|
||||
secondaryText,
|
||||
primaryText,
|
||||
() =>
|
||||
{
|
||||
try { Clipboard.SetText(tooltip); } catch { }
|
||||
GitBranchPopup.IsOpen = false;
|
||||
}));
|
||||
|
||||
GitBranchItems.Children.Add(CreatePopupMenuRow(
|
||||
"\uE72B",
|
||||
"새로고침",
|
||||
"Git 상태를 다시 조회합니다.",
|
||||
false,
|
||||
accentBrush,
|
||||
secondaryText,
|
||||
primaryText,
|
||||
async () =>
|
||||
{
|
||||
await RefreshGitBranchStatusAsync();
|
||||
BuildGitBranchPopup();
|
||||
}));
|
||||
|
||||
var filteredBranches = _currentGitBranches
|
||||
.Where(branch => string.IsNullOrWhiteSpace(query)
|
||||
|| branch.Contains(query, StringComparison.OrdinalIgnoreCase))
|
||||
.Take(20)
|
||||
.ToList();
|
||||
|
||||
var recentBranches = _recentGitBranches
|
||||
.Where(branch => _currentGitBranches.Any(current => string.Equals(current, branch, StringComparison.OrdinalIgnoreCase)))
|
||||
.Where(branch => string.IsNullOrWhiteSpace(query)
|
||||
|| branch.Contains(query, StringComparison.OrdinalIgnoreCase))
|
||||
.Take(5)
|
||||
.ToList();
|
||||
|
||||
if (recentBranches.Count > 0)
|
||||
{
|
||||
GitBranchItems.Children.Add(CreatePopupSectionLabel($"최근 전환 · {recentBranches.Count}", new Thickness(8, 10, 8, 4)));
|
||||
|
||||
foreach (var branch in recentBranches)
|
||||
{
|
||||
var isCurrent = string.Equals(branch, branchName, StringComparison.OrdinalIgnoreCase);
|
||||
GitBranchItems.Children.Add(CreatePopupMenuRow(
|
||||
isCurrent ? "\uE73E" : "\uE8FD",
|
||||
branch,
|
||||
isCurrent ? "현재 브랜치" : "최근 사용 브랜치",
|
||||
isCurrent,
|
||||
accentBrush,
|
||||
secondaryText,
|
||||
primaryText,
|
||||
isCurrent ? null : () => _ = SwitchGitBranchAsync(branch)));
|
||||
}
|
||||
}
|
||||
|
||||
if (_currentGitBranches.Count > 0)
|
||||
{
|
||||
var branchSectionLabel = string.IsNullOrWhiteSpace(query)
|
||||
? $"브랜치 전환 · {_currentGitBranches.Count}"
|
||||
: $"브랜치 전환 · {filteredBranches.Count}/{_currentGitBranches.Count}";
|
||||
GitBranchItems.Children.Add(CreatePopupSectionLabel(branchSectionLabel, new Thickness(8, 10, 8, 4)));
|
||||
|
||||
foreach (var branch in filteredBranches)
|
||||
{
|
||||
if (recentBranches.Any(recent => string.Equals(recent, branch, StringComparison.OrdinalIgnoreCase)))
|
||||
continue;
|
||||
|
||||
var isCurrent = string.Equals(branch, branchName, StringComparison.OrdinalIgnoreCase);
|
||||
GitBranchItems.Children.Add(CreatePopupMenuRow(
|
||||
isCurrent ? "\uE73E" : "\uE943",
|
||||
branch,
|
||||
isCurrent ? "현재 브랜치" : "이 브랜치로 전환",
|
||||
isCurrent,
|
||||
accentBrush,
|
||||
secondaryText,
|
||||
primaryText,
|
||||
isCurrent ? null : () => _ = SwitchGitBranchAsync(branch)));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query) && filteredBranches.Count == 0)
|
||||
{
|
||||
GitBranchItems.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "검색 결과가 없습니다.",
|
||||
FontSize = 11.5,
|
||||
Foreground = secondaryText,
|
||||
Margin = new Thickness(10, 6, 10, 10),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
GitBranchItems.Children.Add(CreatePopupSectionLabel("브랜치 작업", new Thickness(8, 10, 8, 4)));
|
||||
GitBranchItems.Children.Add(CreatePopupMenuRow(
|
||||
"\uE710",
|
||||
"새 브랜치 생성",
|
||||
"현재 작업 기준으로 새 브랜치를 만들고 전환합니다.",
|
||||
false,
|
||||
accentBrush,
|
||||
secondaryText,
|
||||
primaryText,
|
||||
() => _ = CreateGitBranchAsync()));
|
||||
}
|
||||
|
||||
private TextBlock CreatePopupSectionLabel(string text, Thickness? margin = null)
|
||||
{
|
||||
return new TextBlock
|
||||
{
|
||||
Text = text,
|
||||
FontSize = 10.5,
|
||||
FontWeight = FontWeights.SemiBold,
|
||||
Foreground = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray,
|
||||
Margin = margin ?? new Thickness(8, 8, 8, 4),
|
||||
};
|
||||
}
|
||||
|
||||
private UIElement CreatePopupSummaryStrip(IEnumerable<(string Label, string Value, string BgHex, string BorderHex, string FgHex)> items)
|
||||
{
|
||||
var wrap = new WrapPanel
|
||||
{
|
||||
Margin = new Thickness(8, 6, 8, 6),
|
||||
};
|
||||
|
||||
foreach (var item in items)
|
||||
wrap.Children.Add(CreateMetricPill($"{item.Label} {item.Value}", item.FgHex, item.BgHex, item.BorderHex));
|
||||
|
||||
return wrap;
|
||||
}
|
||||
|
||||
private Border CreateMetricPill(string text, string colorHex)
|
||||
=> CreateMetricPill(text, colorHex, $"{colorHex}18", $"{colorHex}44");
|
||||
|
||||
private Border CreateMetricPill(string text, string colorHex, string bgHex, string borderHex)
|
||||
{
|
||||
return new Border
|
||||
{
|
||||
Background = BrushFromHex(bgHex),
|
||||
BorderBrush = BrushFromHex(borderHex),
|
||||
BorderThickness = new Thickness(1),
|
||||
CornerRadius = new CornerRadius(999),
|
||||
Padding = new Thickness(8, 3, 8, 3),
|
||||
Margin = new Thickness(0, 0, 6, 0),
|
||||
Child = new TextBlock
|
||||
{
|
||||
Text = text,
|
||||
FontSize = 10.5,
|
||||
FontWeight = FontWeights.SemiBold,
|
||||
Foreground = BrushFromHex(colorHex),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Border CreateFlatPopupRow(string icon, string title, string description, string colorHex, bool clickable, Action? onClick)
|
||||
{
|
||||
var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.White;
|
||||
var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
|
||||
var hoverBrush = TryFindResource("ItemHoverBackground") as Brush ?? Brushes.LightGray;
|
||||
var borderColor = TryFindResource("BorderColor") as Brush ?? Brushes.Gray;
|
||||
|
||||
var border = new Border
|
||||
{
|
||||
Background = Brushes.Transparent,
|
||||
BorderBrush = borderColor,
|
||||
BorderThickness = new Thickness(0, 0, 0, 1),
|
||||
Padding = new Thickness(8, 9, 8, 9),
|
||||
Cursor = clickable ? Cursors.Hand : Cursors.Arrow,
|
||||
Focusable = clickable,
|
||||
};
|
||||
KeyboardNavigation.SetIsTabStop(border, clickable);
|
||||
|
||||
var grid = new Grid();
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
|
||||
|
||||
grid.Children.Add(new TextBlock
|
||||
{
|
||||
Text = icon,
|
||||
FontFamily = new FontFamily("Segoe MDL2 Assets"),
|
||||
FontSize = 11,
|
||||
Foreground = BrushFromHex(colorHex),
|
||||
VerticalAlignment = VerticalAlignment.Top,
|
||||
Margin = new Thickness(0, 1, 10, 0),
|
||||
});
|
||||
|
||||
var textStack = new StackPanel();
|
||||
textStack.Children.Add(new TextBlock
|
||||
{
|
||||
Text = title,
|
||||
FontSize = 12,
|
||||
FontWeight = FontWeights.SemiBold,
|
||||
Foreground = primaryText,
|
||||
});
|
||||
if (!string.IsNullOrWhiteSpace(description))
|
||||
{
|
||||
textStack.Children.Add(new TextBlock
|
||||
{
|
||||
Text = description,
|
||||
FontSize = 10.5,
|
||||
Foreground = secondaryText,
|
||||
Margin = new Thickness(0, 2, 0, 0),
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
});
|
||||
}
|
||||
|
||||
Grid.SetColumn(textStack, 1);
|
||||
grid.Children.Add(textStack);
|
||||
if (clickable)
|
||||
{
|
||||
var chevron = new TextBlock
|
||||
{
|
||||
Text = "\uE76C",
|
||||
FontFamily = new FontFamily("Segoe MDL2 Assets"),
|
||||
FontSize = 10,
|
||||
Foreground = secondaryText,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Margin = new Thickness(8, 0, 0, 0),
|
||||
};
|
||||
Grid.SetColumn(chevron, 2);
|
||||
grid.Children.Add(chevron);
|
||||
}
|
||||
|
||||
border.Child = grid;
|
||||
|
||||
if (clickable && onClick != null)
|
||||
{
|
||||
border.MouseEnter += (_, _) => border.Background = hoverBrush;
|
||||
border.MouseLeave += (_, _) => border.Background = Brushes.Transparent;
|
||||
border.MouseLeftButtonUp += (_, _) => onClick();
|
||||
border.KeyDown += (_, keyEvent) =>
|
||||
{
|
||||
if (keyEvent.Key is Key.Enter or Key.Space)
|
||||
{
|
||||
keyEvent.Handled = true;
|
||||
onClick();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return border;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user