From 3c3faab5289f1abd4a714ba14da5f4bca65de329 Mon Sep 17 00:00:00 2001 From: lacvet Date: Mon, 6 Apr 2026 12:09:18 +0900 Subject: [PATCH] =?UTF-8?q?AX=20Agent=20surface=20visual=20language=20?= =?UTF-8?q?=EA=B3=B5=ED=86=B5=ED=99=94=20=EB=B0=8F=20preview/file=20browse?= =?UTF-8?q?r=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ChatWindow에 공통 popup container/menu item/separator/file tree header helper를 추가 - Preview와 FileBrowser presentation이 같은 surface 스타일을 사용하도록 정리 - README와 DEVELOPMENT 문서에 2026-04-06 11:20 (KST) 기준 visual polish 1차 반영 - dotnet build 검증 경고 0, 오류 0 확인 --- README.md | 3 + docs/DEVELOPMENT.md | 2 + .../ChatWindow.FileBrowserPresentation.cs | 98 +------------- .../Views/ChatWindow.PreviewPresentation.cs | 64 +-------- .../ChatWindow.SurfaceVisualPresentation.cs | 121 ++++++++++++++++++ 5 files changed, 139 insertions(+), 149 deletions(-) create mode 100644 src/AxCopilot/Views/ChatWindow.SurfaceVisualPresentation.cs diff --git a/README.md b/README.md index d3272ae..65c6e54 100644 --- a/README.md +++ b/README.md @@ -1197,3 +1197,6 @@ MIT License - 업데이트: 2026-04-06 11:11 (KST) - 좌측 대화 목록의 필터/정렬 interaction을 [ChatWindow.ConversationFilterPresentation.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.ConversationFilterPresentation.cs) 로 분리했다. 실행 중 보기, 최근/활동 정렬, 대화 목록 선호 저장/복원, 관련 버튼 UI 상태 갱신이 메인 [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 밖으로 이동해 sidebar 상태 표현 책임이 더 응집도 있게 정리됐다. - 이 단계까지 누적 완료된 구조 개선은 상태선/권한/도구 결과 카탈로그화, inline ask/plan 분리, footer/Git/preset/list/message/timeline/conversation management/sidebar interaction/filter 분리까지다. 이제 남는 건 큰 분리가 아니라 실제 시나리오 기반 polish와 공통 시각 언어 고도화다. +- 업데이트: 2026-04-06 11:20 (KST) + - preview/file browser의 popup과 row 스타일을 공통 surface helper로 통일했다. [ChatWindow.SurfaceVisualPresentation.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.SurfaceVisualPresentation.cs)를 추가해 popup container, popup menu item, separator, file tree header를 공통 helper로 만들고, [ChatWindow.PreviewPresentation.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.PreviewPresentation.cs) 와 [ChatWindow.FileBrowserPresentation.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.FileBrowserPresentation.cs) 가 같은 surface 언어를 쓰도록 맞췄다. + - 이 단계는 큰 구조 분리 이후의 visual language polish 1차로, preview와 file browser가 서로 다른 위젯처럼 보이던 차이를 줄이고 이후 공통 popup/surface 확장을 쉽게 하는 기반을 마련했다. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 4ae5489..86b4928 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -4935,3 +4935,5 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎. - Document update: 2026-04-06 11:03 (KST) - This leaves the main chat window even more orchestration-focused and effectively closes the last obvious sidebar interaction block from the large structure-improvement plan. Remaining work is now follow-up UX polish rather than major decomposition. - Document update: 2026-04-06 11:11 (KST) - Split conversation list filter/sort interactions and preference persistence out of `ChatWindow.xaml.cs` into `ChatWindow.ConversationFilterPresentation.cs`. Running-only toggles, recent/activity sort toggles, UI-state refresh, and conversation-list preference apply/persist logic now live in a dedicated presentation partial. - Document update: 2026-04-06 11:11 (KST) - This keeps sidebar state presentation more cohesive and further narrows the main chat window file toward orchestration-only responsibilities. Remaining work is now post-plan polish rather than another major structural split. +- Document update: 2026-04-06 11:20 (KST) - Added `ChatWindow.SurfaceVisualPresentation.cs` and introduced shared surface helpers for popup containers, popup menu rows, separators, and file-tree headers. `ChatWindow.PreviewPresentation.cs` and `ChatWindow.FileBrowserPresentation.cs` now use the same popup/surface language instead of duplicating slightly different visual styles. +- Document update: 2026-04-06 11:20 (KST) - This is the first visual-language polish pass after the large structure split. It reduces the feeling that preview and file browser are separate widgets and makes future popup/surface refinements reusable. diff --git a/src/AxCopilot/Views/ChatWindow.FileBrowserPresentation.cs b/src/AxCopilot/Views/ChatWindow.FileBrowserPresentation.cs index a1c82dc..75a2cc4 100644 --- a/src/AxCopilot/Views/ChatWindow.FileBrowserPresentation.cs +++ b/src/AxCopilot/Views/ChatWindow.FileBrowserPresentation.cs @@ -94,7 +94,7 @@ public partial class ChatWindow count++; var dirItem = new TreeViewItem { - Header = CreateFileTreeHeader("\uED25", subDir.Name, null), + Header = CreateSurfaceFileTreeHeader("\uED25", subDir.Name, null), Tag = subDir.FullName, IsExpanded = depth < 1, }; @@ -140,7 +140,7 @@ public partial class ChatWindow var fileItem = new TreeViewItem { - Header = CreateFileTreeHeader(icon, file.Name, size), + Header = CreateSurfaceFileTreeHeader(icon, file.Name, size), Tag = file.FullName, }; @@ -167,38 +167,6 @@ public partial class ChatWindow } } - private static StackPanel CreateFileTreeHeader(string icon, string name, string? sizeText) - { - var sp = new StackPanel { Orientation = Orientation.Horizontal }; - sp.Children.Add(new TextBlock - { - Text = icon, - FontFamily = new FontFamily("Segoe MDL2 Assets"), - FontSize = 11, - Foreground = new SolidColorBrush(Color.FromRgb(0x9C, 0xA3, 0xAF)), - VerticalAlignment = VerticalAlignment.Center, - Margin = new Thickness(0, 0, 5, 0), - }); - sp.Children.Add(new TextBlock - { - Text = name, - FontSize = 11.5, - VerticalAlignment = VerticalAlignment.Center, - }); - if (sizeText != null) - { - sp.Children.Add(new TextBlock - { - Text = $" {sizeText}", - FontSize = 10, - Foreground = new SolidColorBrush(Color.FromRgb(0x6B, 0x72, 0x80)), - VerticalAlignment = VerticalAlignment.Center, - }); - } - - return sp; - } - private static string GetFileIcon(string ext) => ext switch { ".html" or ".htm" => "\uEB41", @@ -224,11 +192,8 @@ public partial class ChatWindow private void ShowFileTreeContextMenu(string filePath) { - var bg = TryFindResource("LauncherBackground") as Brush ?? new SolidColorBrush(Color.FromRgb(0x1E, 0x1E, 0x2E)); - var borderBrush = TryFindResource("BorderColor") as Brush ?? Brushes.Gray; var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.White; var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray; - var hoverBg = TryFindResource("HintBackground") as Brush ?? new SolidColorBrush(Color.FromArgb(0x18, 0xFF, 0xFF, 0xFF)); var dangerBrush = new SolidColorBrush(Color.FromRgb(0xE7, 0x4C, 0x3C)); var popup = new Popup @@ -239,73 +204,22 @@ public partial class ChatWindow Placement = PlacementMode.MousePoint, }; var panel = new StackPanel { Margin = new Thickness(2) }; - var container = new Border - { - Background = bg, - BorderBrush = borderBrush, - BorderThickness = new Thickness(1), - CornerRadius = new CornerRadius(10), - Padding = new Thickness(6), - MinWidth = 200, - Effect = new System.Windows.Media.Effects.DropShadowEffect - { - BlurRadius = 16, - ShadowDepth = 4, - Opacity = 0.3, - Color = Colors.Black, - Direction = 270, - }, - Child = panel, - }; + var container = CreateSurfacePopupContainer(panel, 200, new Thickness(6)); popup.Child = container; void AddItem(string icon, string label, Action action, Brush? labelColor = null, Brush? iconColor = null) { - var sp = new StackPanel { Orientation = Orientation.Horizontal }; - sp.Children.Add(new TextBlock - { - Text = icon, - FontFamily = new FontFamily("Segoe MDL2 Assets"), - FontSize = 13, - Foreground = iconColor ?? secondaryText, - VerticalAlignment = VerticalAlignment.Center, - Margin = new Thickness(0, 0, 10, 0), - }); - sp.Children.Add(new TextBlock - { - Text = label, - FontSize = 12.5, - Foreground = labelColor ?? primaryText, - VerticalAlignment = VerticalAlignment.Center, - }); - var item = new Border - { - Child = sp, - Background = Brushes.Transparent, - CornerRadius = new CornerRadius(7), - Cursor = Cursors.Hand, - Padding = new Thickness(10, 8, 14, 8), - Margin = new Thickness(0, 1, 0, 1), - }; - item.MouseEnter += (s, _) => { if (s is Border b) b.Background = hoverBg; }; - item.MouseLeave += (s, _) => { if (s is Border b) b.Background = Brushes.Transparent; }; - item.MouseLeftButtonUp += (_, _) => + var item = CreateSurfacePopupMenuItem(icon, iconColor ?? secondaryText, label, () => { popup.IsOpen = false; action(); - }; + }, labelColor ?? primaryText); panel.Children.Add(item); } void AddSep() { - panel.Children.Add(new Border - { - Height = 1, - Margin = new Thickness(10, 4, 10, 4), - Background = borderBrush, - Opacity = 0.3, - }); + panel.Children.Add(CreateSurfacePopupSeparator()); } var ext = Path.GetExtension(filePath).ToLowerInvariant(); diff --git a/src/AxCopilot/Views/ChatWindow.PreviewPresentation.cs b/src/AxCopilot/Views/ChatWindow.PreviewPresentation.cs index 153ce30..1d111a6 100644 --- a/src/AxCopilot/Views/ChatWindow.PreviewPresentation.cs +++ b/src/AxCopilot/Views/ChatWindow.PreviewPresentation.cs @@ -514,61 +514,27 @@ public partial class ChatWindow if (_previewTabPopup != null) _previewTabPopup.IsOpen = false; - var bg = TryFindResource("LauncherBackground") as Brush ?? new SolidColorBrush(Color.FromRgb(0x1E, 0x1E, 0x2E)); - var borderBrush = TryFindResource("BorderColor") as Brush ?? Brushes.Gray; var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.White; var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray; - var hoverBg = TryFindResource("ItemHoverBackground") as Brush ?? new SolidColorBrush(Color.FromArgb(0x18, 0xFF, 0xFF, 0xFF)); var stack = new StackPanel(); void AddItem(string icon, string iconColor, string label, Action action) { - var itemBorder = new Border - { - Background = Brushes.Transparent, - CornerRadius = new CornerRadius(6), - Padding = new Thickness(10, 7, 16, 7), - Cursor = Cursors.Hand, - }; - var sp = new StackPanel { Orientation = Orientation.Horizontal }; - sp.Children.Add(new TextBlock - { - Text = icon, - FontFamily = new FontFamily("Segoe MDL2 Assets"), - FontSize = 12, - Foreground = string.IsNullOrEmpty(iconColor) - ? secondaryText - : new SolidColorBrush((Color)ColorConverter.ConvertFromString(iconColor)), - VerticalAlignment = VerticalAlignment.Center, - Margin = new Thickness(0, 0, 8, 0), - }); - sp.Children.Add(new TextBlock - { - Text = label, - FontSize = 13, - Foreground = primaryText, - VerticalAlignment = VerticalAlignment.Center, - }); - itemBorder.Child = sp; - itemBorder.MouseEnter += (s, _) => { if (s is Border b) b.Background = hoverBg; }; - itemBorder.MouseLeave += (s, _) => { if (s is Border b) b.Background = Brushes.Transparent; }; - itemBorder.MouseLeftButtonUp += (_, _) => + var iconBrush = string.IsNullOrEmpty(iconColor) + ? secondaryText + : new SolidColorBrush((Color)ColorConverter.ConvertFromString(iconColor)); + var itemBorder = CreateSurfacePopupMenuItem(icon, iconBrush, label, () => { _previewTabPopup!.IsOpen = false; action(); - }; + }, primaryText, 13); stack.Children.Add(itemBorder); } void AddSeparator() { - stack.Children.Add(new Border - { - Height = 1, - Background = borderBrush, - Margin = new Thickness(8, 3, 8, 3), - }); + stack.Children.Add(CreateSurfacePopupSeparator()); } AddItem("\uE8A7", "#64B5F6", "외부 프로그램으로 열기", () => @@ -628,23 +594,7 @@ public partial class ChatWindow }); } - var popupBorder = new Border - { - Background = bg, - BorderBrush = borderBrush, - BorderThickness = new Thickness(1), - CornerRadius = new CornerRadius(12), - Padding = new Thickness(4, 6, 4, 6), - MinWidth = 180, - Effect = new System.Windows.Media.Effects.DropShadowEffect - { - BlurRadius = 16, - Opacity = 0.4, - ShadowDepth = 4, - Color = Colors.Black, - }, - Child = stack, - }; + var popupBorder = CreateSurfacePopupContainer(stack, 180, new Thickness(4, 6, 4, 6)); _previewTabPopup = new Popup { diff --git a/src/AxCopilot/Views/ChatWindow.SurfaceVisualPresentation.cs b/src/AxCopilot/Views/ChatWindow.SurfaceVisualPresentation.cs new file mode 100644 index 0000000..b6b0ae1 --- /dev/null +++ b/src/AxCopilot/Views/ChatWindow.SurfaceVisualPresentation.cs @@ -0,0 +1,121 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Input; +using System.Windows.Media; + +namespace AxCopilot.Views; + +public partial class ChatWindow +{ + private Border CreateSurfacePopupContainer(UIElement content, double minWidth = 180, Thickness? padding = null) + { + var bg = TryFindResource("LauncherBackground") as Brush ?? new SolidColorBrush(Color.FromRgb(0x1E, 0x1E, 0x2E)); + var borderBrush = TryFindResource("BorderColor") as Brush ?? Brushes.Gray; + + return new Border + { + Background = bg, + BorderBrush = borderBrush, + BorderThickness = new Thickness(1), + CornerRadius = new CornerRadius(12), + Padding = padding ?? new Thickness(6), + MinWidth = minWidth, + Effect = new System.Windows.Media.Effects.DropShadowEffect + { + BlurRadius = 16, + ShadowDepth = 4, + Opacity = 0.34, + Color = Colors.Black, + }, + Child = content, + }; + } + + private Border CreateSurfacePopupMenuItem(string icon, Brush iconBrush, string label, Action onClick, Brush? labelBrush = null, double fontSize = 12.5) + { + var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.White; + var hoverBg = TryFindResource("ItemHoverBackground") as Brush ?? new SolidColorBrush(Color.FromArgb(0x18, 0xFF, 0xFF, 0xFF)); + + var item = new Border + { + Background = Brushes.Transparent, + CornerRadius = new CornerRadius(8), + Padding = new Thickness(10, 8, 14, 8), + Margin = new Thickness(0, 1, 0, 1), + Cursor = Cursors.Hand, + }; + + var row = new StackPanel { Orientation = Orientation.Horizontal }; + row.Children.Add(new TextBlock + { + Text = icon, + FontFamily = new FontFamily("Segoe MDL2 Assets"), + FontSize = 13, + Foreground = iconBrush, + VerticalAlignment = VerticalAlignment.Center, + Margin = new Thickness(0, 0, 10, 0), + }); + row.Children.Add(new TextBlock + { + Text = label, + FontSize = fontSize, + Foreground = labelBrush ?? primaryText, + VerticalAlignment = VerticalAlignment.Center, + }); + item.Child = row; + + item.MouseEnter += (s, _) => { if (s is Border b) b.Background = hoverBg; }; + item.MouseLeave += (s, _) => { if (s is Border b) b.Background = Brushes.Transparent; }; + item.MouseLeftButtonUp += (_, _) => onClick(); + return item; + } + + private Border CreateSurfacePopupSeparator() + { + var borderBrush = TryFindResource("BorderColor") as Brush ?? Brushes.Gray; + return new Border + { + Height = 1, + Margin = new Thickness(10, 4, 10, 4), + Background = borderBrush, + Opacity = 0.3, + }; + } + + private StackPanel CreateSurfaceFileTreeHeader(string icon, string name, string? sizeText) + { + var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray; + + var sp = new StackPanel { Orientation = Orientation.Horizontal }; + sp.Children.Add(new TextBlock + { + Text = icon, + FontFamily = new FontFamily("Segoe MDL2 Assets"), + FontSize = 11, + Foreground = secondaryText, + VerticalAlignment = VerticalAlignment.Center, + Margin = new Thickness(0, 0, 5, 0), + }); + sp.Children.Add(new TextBlock + { + Text = name, + FontSize = 11.5, + Foreground = TryFindResource("PrimaryText") as Brush ?? Brushes.White, + VerticalAlignment = VerticalAlignment.Center, + }); + if (!string.IsNullOrEmpty(sizeText)) + { + sp.Children.Add(new TextBlock + { + Text = $" {sizeText}", + FontSize = 10, + Foreground = secondaryText, + VerticalAlignment = VerticalAlignment.Center, + }); + } + + return sp; + } +}