AX Agent timeline 프레젠테이션 helper를 한 파일로 모아 렌더 책임을 더 줄인다
Some checks failed
Release Gate / gate (push) Has been cancelled
Some checks failed
Release Gate / gate (push) Has been cancelled
CreateTimelineLoadMoreCard, ToAgentEvent, IsCompactionMetaMessage, CreateCompactionMetaCard를 ChatWindow.TimelinePresentation.cs로 이동해 RenderMessages 주변의 timeline 관련 helper를 하나의 presentation surface로 정리했다. README와 DEVELOPMENT 문서에 2026-04-06 10:44 (KST) 기준 이력을 반영했고, dotnet build 검증 결과 경고 0 / 오류 0을 확인했다.
This commit is contained in:
@@ -1186,3 +1186,5 @@ MIT License
|
|||||||
- transcript 메시지 row 조립을 [ChatWindow.MessageBubblePresentation.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.MessageBubblePresentation.cs) 로 분리했다. `AddMessageBubble(...)`가 메인 [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 밖으로 이동해, 사용자/assistant bubble, 분기 컨텍스트 카드, 액션 바와 메타 row 조립이 별도 presentation surface에서 관리되게 정리했다.
|
- transcript 메시지 row 조립을 [ChatWindow.MessageBubblePresentation.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.MessageBubblePresentation.cs) 로 분리했다. `AddMessageBubble(...)`가 메인 [ChatWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml.cs) 밖으로 이동해, 사용자/assistant bubble, 분기 컨텍스트 카드, 액션 바와 메타 row 조립이 별도 presentation surface에서 관리되게 정리했다.
|
||||||
- 업데이트: 2026-04-06 10:36 (KST)
|
- 업데이트: 2026-04-06 10:36 (KST)
|
||||||
- timeline 조립 helper를 [ChatWindow.TimelinePresentation.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.TimelinePresentation.cs) 로 분리했다. `RenderMessages()`가 직접 처리하던 visible 메시지 필터링, execution event 노출 집계, timestamp/order 기반 timeline action 조립을 helper 메서드로 옮겨 메인 렌더 루프를 더 단순화했다.
|
- timeline 조립 helper를 [ChatWindow.TimelinePresentation.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.TimelinePresentation.cs) 로 분리했다. `RenderMessages()`가 직접 처리하던 visible 메시지 필터링, execution event 노출 집계, timestamp/order 기반 timeline action 조립을 helper 메서드로 옮겨 메인 렌더 루프를 더 단순화했다.
|
||||||
|
- 업데이트: 2026-04-06 10:44 (KST)
|
||||||
|
- timeline presentation 정리를 이어서 진행했다. [ChatWindow.TimelinePresentation.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.TimelinePresentation.cs) 에 `CreateTimelineLoadMoreCard`, `ToAgentEvent`, `IsCompactionMetaMessage`, `CreateCompactionMetaCard`까지 옮겨 `RenderMessages()` 주변의 timeline helper를 한 파일로 모았다.
|
||||||
|
|||||||
@@ -4927,3 +4927,5 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎.
|
|||||||
- Document update: 2026-04-06 10:27 (KST) - This keeps the main chat window more orchestration-focused while making transcript row rendering easier to tune and extend independently.
|
- Document update: 2026-04-06 10:27 (KST) - This keeps the main chat window more orchestration-focused while making transcript row rendering easier to tune and extend independently.
|
||||||
- Document update: 2026-04-06 10:36 (KST) - Split timeline visibility filtering and render-action assembly out of `RenderMessages()` into `ChatWindow.TimelinePresentation.cs`. Visible message/event selection and timestamp-ordered timeline action construction now live in dedicated helpers.
|
- Document update: 2026-04-06 10:36 (KST) - Split timeline visibility filtering and render-action assembly out of `RenderMessages()` into `ChatWindow.TimelinePresentation.cs`. Visible message/event selection and timestamp-ordered timeline action construction now live in dedicated helpers.
|
||||||
- Document update: 2026-04-06 10:36 (KST) - This keeps `RenderMessages()` closer to a simple orchestration loop and reduces mixed responsibilities inside the main chat window file.
|
- Document update: 2026-04-06 10:36 (KST) - This keeps `RenderMessages()` closer to a simple orchestration loop and reduces mixed responsibilities inside the main chat window file.
|
||||||
|
- Document update: 2026-04-06 10:44 (KST) - Continued timeline presentation cleanup by moving `CreateTimelineLoadMoreCard`, `ToAgentEvent`, `IsCompactionMetaMessage`, and `CreateCompactionMetaCard` into `ChatWindow.TimelinePresentation.cs`.
|
||||||
|
- Document update: 2026-04-06 10:44 (KST) - This consolidates timeline-related helpers in one place and leaves the main chat window file with less transcript-specific rendering logic around `RenderMessages()`.
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Media;
|
||||||
using AxCopilot.Models;
|
using AxCopilot.Models;
|
||||||
|
using AxCopilot.Services.Agent;
|
||||||
|
|
||||||
namespace AxCopilot.Views;
|
namespace AxCopilot.Views;
|
||||||
|
|
||||||
@@ -49,4 +53,182 @@ public partial class ChatWindow
|
|||||||
.ThenBy(x => x.Order)
|
.ThenBy(x => x.Order)
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Border CreateTimelineLoadMoreCard(int hiddenCount)
|
||||||
|
{
|
||||||
|
var hoverBg = TryFindResource("ItemHoverBackground") as Brush ?? BrushFromHex("#F8FAFC");
|
||||||
|
var borderBrush = TryFindResource("BorderColor") as Brush ?? BrushFromHex("#E2E8F0");
|
||||||
|
var primaryText = TryFindResource("PrimaryText") as Brush ?? BrushFromHex("#334155");
|
||||||
|
var secondaryText = TryFindResource("SecondaryText") as Brush ?? BrushFromHex("#64748B");
|
||||||
|
|
||||||
|
var loadMoreBtn = new Button
|
||||||
|
{
|
||||||
|
Background = Brushes.Transparent,
|
||||||
|
BorderBrush = borderBrush,
|
||||||
|
BorderThickness = new Thickness(1),
|
||||||
|
Padding = new Thickness(7, 3, 7, 3),
|
||||||
|
Cursor = System.Windows.Input.Cursors.Hand,
|
||||||
|
Foreground = primaryText,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Center,
|
||||||
|
};
|
||||||
|
loadMoreBtn.Template = BuildMinimalIconButtonTemplate();
|
||||||
|
loadMoreBtn.Content = new StackPanel
|
||||||
|
{
|
||||||
|
Orientation = Orientation.Horizontal,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new TextBlock
|
||||||
|
{
|
||||||
|
Text = "\uE70D",
|
||||||
|
FontFamily = new FontFamily("Segoe MDL2 Assets"),
|
||||||
|
FontSize = 8,
|
||||||
|
Foreground = secondaryText,
|
||||||
|
Margin = new Thickness(0, 0, 4, 0),
|
||||||
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
|
},
|
||||||
|
new TextBlock
|
||||||
|
{
|
||||||
|
Text = $"이전 대화 {hiddenCount:N0}개",
|
||||||
|
FontSize = 9.25,
|
||||||
|
Foreground = secondaryText,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loadMoreBtn.MouseEnter += (_, _) => loadMoreBtn.Background = hoverBg;
|
||||||
|
loadMoreBtn.MouseLeave += (_, _) => loadMoreBtn.Background = Brushes.Transparent;
|
||||||
|
loadMoreBtn.Click += (_, _) =>
|
||||||
|
{
|
||||||
|
_timelineRenderLimit += TimelineRenderPageSize;
|
||||||
|
RenderMessages(preserveViewport: true);
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Border
|
||||||
|
{
|
||||||
|
CornerRadius = new CornerRadius(10),
|
||||||
|
Margin = new Thickness(0, 2, 0, 8),
|
||||||
|
Padding = new Thickness(0),
|
||||||
|
Background = Brushes.Transparent,
|
||||||
|
BorderBrush = Brushes.Transparent,
|
||||||
|
BorderThickness = new Thickness(0),
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Center,
|
||||||
|
Child = loadMoreBtn,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AgentEvent ToAgentEvent(ChatExecutionEvent executionEvent)
|
||||||
|
{
|
||||||
|
var parsedType = Enum.TryParse<AgentEventType>(executionEvent.Type, out var eventType)
|
||||||
|
? eventType
|
||||||
|
: AgentEventType.Thinking;
|
||||||
|
|
||||||
|
return new AgentEvent
|
||||||
|
{
|
||||||
|
Timestamp = executionEvent.Timestamp,
|
||||||
|
RunId = executionEvent.RunId,
|
||||||
|
Type = parsedType,
|
||||||
|
ToolName = executionEvent.ToolName,
|
||||||
|
Summary = executionEvent.Summary,
|
||||||
|
FilePath = executionEvent.FilePath,
|
||||||
|
Success = executionEvent.Success,
|
||||||
|
StepCurrent = executionEvent.StepCurrent,
|
||||||
|
StepTotal = executionEvent.StepTotal,
|
||||||
|
Steps = executionEvent.Steps,
|
||||||
|
ElapsedMs = executionEvent.ElapsedMs,
|
||||||
|
InputTokens = executionEvent.InputTokens,
|
||||||
|
OutputTokens = executionEvent.OutputTokens,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsCompactionMetaMessage(ChatMessage? message)
|
||||||
|
{
|
||||||
|
var kind = message?.MetaKind ?? "";
|
||||||
|
return kind.Equals("microcompact_boundary", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| kind.Equals("session_memory_compaction", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| kind.Equals("collapsed_boundary", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Border CreateCompactionMetaCard(ChatMessage message, Brush primaryText, Brush secondaryText, Brush hintBg, Brush borderBrush, Brush accentBrush)
|
||||||
|
{
|
||||||
|
var icon = "\uE9CE";
|
||||||
|
var title = message.MetaKind switch
|
||||||
|
{
|
||||||
|
"session_memory_compaction" => "세션 메모리 압축",
|
||||||
|
"collapsed_boundary" => "압축 경계 병합",
|
||||||
|
_ => "Microcompact 경계",
|
||||||
|
};
|
||||||
|
|
||||||
|
var wrapper = new Border
|
||||||
|
{
|
||||||
|
Background = hintBg,
|
||||||
|
BorderBrush = borderBrush,
|
||||||
|
BorderThickness = new Thickness(1),
|
||||||
|
CornerRadius = new CornerRadius(12),
|
||||||
|
Padding = new Thickness(12, 10, 12, 10),
|
||||||
|
Margin = new Thickness(10, 4, 150, 4),
|
||||||
|
MaxWidth = GetMessageMaxWidth(),
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Left,
|
||||||
|
};
|
||||||
|
|
||||||
|
var stack = new StackPanel();
|
||||||
|
var header = new StackPanel
|
||||||
|
{
|
||||||
|
Orientation = Orientation.Horizontal,
|
||||||
|
Margin = new Thickness(0, 0, 0, 6),
|
||||||
|
};
|
||||||
|
header.Children.Add(new TextBlock
|
||||||
|
{
|
||||||
|
Text = icon,
|
||||||
|
FontFamily = new FontFamily("Segoe MDL2 Assets"),
|
||||||
|
FontSize = 11,
|
||||||
|
Foreground = accentBrush,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
|
});
|
||||||
|
header.Children.Add(new TextBlock
|
||||||
|
{
|
||||||
|
Text = title,
|
||||||
|
FontSize = 11,
|
||||||
|
FontWeight = FontWeights.SemiBold,
|
||||||
|
Foreground = primaryText,
|
||||||
|
Margin = new Thickness(6, 0, 0, 0),
|
||||||
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
|
});
|
||||||
|
stack.Children.Add(header);
|
||||||
|
|
||||||
|
var lines = (message.Content ?? "")
|
||||||
|
.Replace("\r\n", "\n")
|
||||||
|
.Split('\n', StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Select(line => line.Trim())
|
||||||
|
.Where(line => !string.IsNullOrWhiteSpace(line))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var line in lines)
|
||||||
|
{
|
||||||
|
var isHeaderLine = line.StartsWith("[", StringComparison.Ordinal);
|
||||||
|
stack.Children.Add(new TextBlock
|
||||||
|
{
|
||||||
|
Text = isHeaderLine ? line.Trim('[', ']') : line,
|
||||||
|
FontSize = 10.5,
|
||||||
|
FontWeight = isHeaderLine ? FontWeights.SemiBold : FontWeights.Normal,
|
||||||
|
Foreground = isHeaderLine ? primaryText : secondaryText,
|
||||||
|
TextWrapping = TextWrapping.Wrap,
|
||||||
|
Margin = isHeaderLine ? new Thickness(0, 0, 0, 3) : new Thickness(0, 0, 0, 2),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(message.MetaRunId))
|
||||||
|
{
|
||||||
|
stack.Children.Add(new TextBlock
|
||||||
|
{
|
||||||
|
Text = $"run {message.MetaRunId}",
|
||||||
|
FontSize = 9.5,
|
||||||
|
Foreground = secondaryText,
|
||||||
|
Opacity = 0.7,
|
||||||
|
Margin = new Thickness(0, 6, 0, 0),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapper.Child = stack;
|
||||||
|
return wrapper;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2770,184 +2770,6 @@ public partial class ChatWindow : Window
|
|||||||
}, DispatcherPriority.Background);
|
}, DispatcherPriority.Background);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Border CreateTimelineLoadMoreCard(int hiddenCount)
|
|
||||||
{
|
|
||||||
var hoverBg = TryFindResource("ItemHoverBackground") as Brush ?? BrushFromHex("#F8FAFC");
|
|
||||||
var borderBrush = TryFindResource("BorderColor") as Brush ?? BrushFromHex("#E2E8F0");
|
|
||||||
var primaryText = TryFindResource("PrimaryText") as Brush ?? BrushFromHex("#334155");
|
|
||||||
var secondaryText = TryFindResource("SecondaryText") as Brush ?? BrushFromHex("#64748B");
|
|
||||||
|
|
||||||
var loadMoreBtn = new Button
|
|
||||||
{
|
|
||||||
Background = Brushes.Transparent,
|
|
||||||
BorderBrush = borderBrush,
|
|
||||||
BorderThickness = new Thickness(1),
|
|
||||||
Padding = new Thickness(7, 3, 7, 3),
|
|
||||||
Cursor = System.Windows.Input.Cursors.Hand,
|
|
||||||
Foreground = primaryText,
|
|
||||||
HorizontalAlignment = HorizontalAlignment.Center,
|
|
||||||
};
|
|
||||||
loadMoreBtn.Template = BuildMinimalIconButtonTemplate();
|
|
||||||
loadMoreBtn.Content = new StackPanel
|
|
||||||
{
|
|
||||||
Orientation = Orientation.Horizontal,
|
|
||||||
Children =
|
|
||||||
{
|
|
||||||
new TextBlock
|
|
||||||
{
|
|
||||||
Text = "\uE70D",
|
|
||||||
FontFamily = new FontFamily("Segoe MDL2 Assets"),
|
|
||||||
FontSize = 8,
|
|
||||||
Foreground = secondaryText,
|
|
||||||
Margin = new Thickness(0, 0, 4, 0),
|
|
||||||
VerticalAlignment = VerticalAlignment.Center,
|
|
||||||
},
|
|
||||||
new TextBlock
|
|
||||||
{
|
|
||||||
Text = $"이전 대화 {hiddenCount:N0}개",
|
|
||||||
FontSize = 9.25,
|
|
||||||
Foreground = secondaryText,
|
|
||||||
VerticalAlignment = VerticalAlignment.Center,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
loadMoreBtn.MouseEnter += (_, _) => loadMoreBtn.Background = hoverBg;
|
|
||||||
loadMoreBtn.MouseLeave += (_, _) => loadMoreBtn.Background = Brushes.Transparent;
|
|
||||||
loadMoreBtn.Click += (_, _) =>
|
|
||||||
{
|
|
||||||
_timelineRenderLimit += TimelineRenderPageSize;
|
|
||||||
RenderMessages(preserveViewport: true);
|
|
||||||
};
|
|
||||||
|
|
||||||
return new Border
|
|
||||||
{
|
|
||||||
CornerRadius = new CornerRadius(10),
|
|
||||||
Margin = new Thickness(0, 2, 0, 8),
|
|
||||||
Padding = new Thickness(0),
|
|
||||||
Background = Brushes.Transparent,
|
|
||||||
BorderBrush = Brushes.Transparent,
|
|
||||||
BorderThickness = new Thickness(0),
|
|
||||||
HorizontalAlignment = HorizontalAlignment.Center,
|
|
||||||
Child = loadMoreBtn,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static AgentEvent ToAgentEvent(ChatExecutionEvent executionEvent)
|
|
||||||
{
|
|
||||||
var parsedType = Enum.TryParse<AgentEventType>(executionEvent.Type, out var eventType)
|
|
||||||
? eventType
|
|
||||||
: AgentEventType.Thinking;
|
|
||||||
|
|
||||||
return new AgentEvent
|
|
||||||
{
|
|
||||||
Timestamp = executionEvent.Timestamp,
|
|
||||||
RunId = executionEvent.RunId,
|
|
||||||
Type = parsedType,
|
|
||||||
ToolName = executionEvent.ToolName,
|
|
||||||
Summary = executionEvent.Summary,
|
|
||||||
FilePath = executionEvent.FilePath,
|
|
||||||
Success = executionEvent.Success,
|
|
||||||
StepCurrent = executionEvent.StepCurrent,
|
|
||||||
StepTotal = executionEvent.StepTotal,
|
|
||||||
Steps = executionEvent.Steps,
|
|
||||||
ElapsedMs = executionEvent.ElapsedMs,
|
|
||||||
InputTokens = executionEvent.InputTokens,
|
|
||||||
OutputTokens = executionEvent.OutputTokens,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsCompactionMetaMessage(ChatMessage? message)
|
|
||||||
{
|
|
||||||
var kind = message?.MetaKind ?? "";
|
|
||||||
return kind.Equals("microcompact_boundary", StringComparison.OrdinalIgnoreCase)
|
|
||||||
|| kind.Equals("session_memory_compaction", StringComparison.OrdinalIgnoreCase)
|
|
||||||
|| kind.Equals("collapsed_boundary", StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Border CreateCompactionMetaCard(ChatMessage message, Brush primaryText, Brush secondaryText, Brush hintBg, Brush borderBrush, Brush accentBrush)
|
|
||||||
{
|
|
||||||
var icon = "\uE9CE";
|
|
||||||
var title = message.MetaKind switch
|
|
||||||
{
|
|
||||||
"session_memory_compaction" => "세션 메모리 압축",
|
|
||||||
"collapsed_boundary" => "압축 경계 병합",
|
|
||||||
_ => "Microcompact 경계",
|
|
||||||
};
|
|
||||||
|
|
||||||
var wrapper = new Border
|
|
||||||
{
|
|
||||||
Background = hintBg,
|
|
||||||
BorderBrush = borderBrush,
|
|
||||||
BorderThickness = new Thickness(1),
|
|
||||||
CornerRadius = new CornerRadius(12),
|
|
||||||
Padding = new Thickness(12, 10, 12, 10),
|
|
||||||
Margin = new Thickness(10, 4, 150, 4),
|
|
||||||
MaxWidth = GetMessageMaxWidth(),
|
|
||||||
HorizontalAlignment = HorizontalAlignment.Left,
|
|
||||||
};
|
|
||||||
|
|
||||||
var stack = new StackPanel();
|
|
||||||
var header = new StackPanel
|
|
||||||
{
|
|
||||||
Orientation = Orientation.Horizontal,
|
|
||||||
Margin = new Thickness(0, 0, 0, 6),
|
|
||||||
};
|
|
||||||
header.Children.Add(new TextBlock
|
|
||||||
{
|
|
||||||
Text = icon,
|
|
||||||
FontFamily = new FontFamily("Segoe MDL2 Assets"),
|
|
||||||
FontSize = 11,
|
|
||||||
Foreground = accentBrush,
|
|
||||||
VerticalAlignment = VerticalAlignment.Center,
|
|
||||||
});
|
|
||||||
header.Children.Add(new TextBlock
|
|
||||||
{
|
|
||||||
Text = title,
|
|
||||||
FontSize = 11,
|
|
||||||
FontWeight = FontWeights.SemiBold,
|
|
||||||
Foreground = primaryText,
|
|
||||||
Margin = new Thickness(6, 0, 0, 0),
|
|
||||||
VerticalAlignment = VerticalAlignment.Center,
|
|
||||||
});
|
|
||||||
stack.Children.Add(header);
|
|
||||||
|
|
||||||
var lines = (message.Content ?? "")
|
|
||||||
.Replace("\r\n", "\n")
|
|
||||||
.Split('\n', StringSplitOptions.RemoveEmptyEntries)
|
|
||||||
.Select(line => line.Trim())
|
|
||||||
.Where(line => !string.IsNullOrWhiteSpace(line))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
foreach (var line in lines)
|
|
||||||
{
|
|
||||||
var isHeaderLine = line.StartsWith("[", StringComparison.Ordinal);
|
|
||||||
stack.Children.Add(new TextBlock
|
|
||||||
{
|
|
||||||
Text = isHeaderLine ? line.Trim('[', ']') : line,
|
|
||||||
FontSize = isHeaderLine ? 10.5 : 10.5,
|
|
||||||
FontWeight = isHeaderLine ? FontWeights.SemiBold : FontWeights.Normal,
|
|
||||||
Foreground = isHeaderLine ? primaryText : secondaryText,
|
|
||||||
TextWrapping = TextWrapping.Wrap,
|
|
||||||
Margin = isHeaderLine ? new Thickness(0, 0, 0, 3) : new Thickness(0, 0, 0, 2),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(message.MetaRunId))
|
|
||||||
{
|
|
||||||
stack.Children.Add(new TextBlock
|
|
||||||
{
|
|
||||||
Text = $"run {message.MetaRunId}",
|
|
||||||
FontSize = 9.5,
|
|
||||||
Foreground = secondaryText,
|
|
||||||
Opacity = 0.7,
|
|
||||||
Margin = new Thickness(0, 6, 0, 0),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
wrapper.Child = stack;
|
|
||||||
return wrapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── 커스텀 체크 아이콘 (모든 팝업 메뉴 공통) ─────────────────────────
|
// ─── 커스텀 체크 아이콘 (모든 팝업 메뉴 공통) ─────────────────────────
|
||||||
|
|
||||||
/// <summary>커스텀 체크/미선택 아이콘을 생성합니다. Path 도형 기반, 선택 시 스케일 바운스 애니메이션.</summary>
|
/// <summary>커스텀 체크/미선택 아이콘을 생성합니다. Path 도형 기반, 선택 시 스케일 바운스 애니메이션.</summary>
|
||||||
|
|||||||
Reference in New Issue
Block a user