컨텍스트 사용량 링 정렬 회귀 수정

원인: 하단 컨텍스트 사용량 트랙은 22x22 원형인데 arc는 center=15, radius=11 하드코딩으로 더 크게 그려져 오른쪽으로 밀려 보였습니다.

수정: TokenUsageTrack과 TokenUsageArc를 같은 크기와 가운데 정렬로 맞추고, 실제 트랙 지름과 스트로크 두께를 기준으로 중심점·반지름을 계산하는 helper를 추가했습니다. 크기 변경 시에도 arc가 다시 그려지도록 캐시 조건도 보강했습니다.

검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify_token_ring_alignment\ -p:IntermediateOutputPath=obj\verify_token_ring_alignment\ (경고 0 / 오류 0), dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter ChatWindowSlashPolicyTests -p:OutputPath=bin\verify_token_ring_alignment_tests\ -p:IntermediateOutputPath=obj\verify_token_ring_alignment_tests\ (통과 53)
This commit is contained in:
2026-04-15 20:14:20 +09:00
parent bea9335ec0
commit 9ad587d230
6 changed files with 72 additions and 5 deletions

View File

@@ -198,4 +198,21 @@ public class ChatWindowSlashPolicyTests
var result = method!.Invoke(null, new object?[] { runTab });
result.Should().Be(expected);
}
[Theory]
[InlineData(22, 2, 11, 10)]
[InlineData(28, 2, 14, 13)]
[InlineData(22, 0, 11, 11)]
[InlineData(0, 2, 0, 0)]
public void CalculateCircularRingMetrics_ShouldAlignCenterlineToTrack(
double diameter,
double strokeThickness,
double expectedCenter,
double expectedRadius)
{
var (center, radius) = ChatWindow.CalculateCircularRingMetrics(diameter, strokeThickness);
center.Should().Be(expectedCenter);
radius.Should().Be(expectedRadius);
}
}

View File

@@ -138,7 +138,24 @@ public partial class ChatWindow
TokenUsageCard.ToolTip = null;
UpdateCircularUsageArc(TokenUsageArc, usageRatio, 15, 15, 11);
var ringDiameter = ResolveTokenUsageRingDiameter();
TokenUsageArc.Width = ringDiameter;
TokenUsageArc.Height = ringDiameter;
var (center, radius) = CalculateCircularRingMetrics(ringDiameter, TokenUsageArc.StrokeThickness);
UpdateCircularUsageArc(TokenUsageArc, usageRatio, center, center, radius);
}
private double ResolveTokenUsageRingDiameter()
{
var actual = TokenUsageTrack?.ActualWidth ?? 0;
if (actual > 0)
return actual;
var declared = TokenUsageTrack?.Width ?? 0;
if (declared > 0)
return declared;
return TokenUsageArc?.Width > 0 ? TokenUsageArc.Width : 22;
}
private void TokenUsageCard_MouseEnter(object sender, MouseEventArgs e)

View File

@@ -2673,7 +2673,8 @@
MouseLeave="TokenUsageCard_MouseLeave">
<Grid Width="28" Height="28">
<!-- Ring track (subtle background ring) -->
<Ellipse Width="22" Height="22"
<Ellipse x:Name="TokenUsageTrack"
Width="22" Height="22"
Stroke="{DynamicResource BorderColor}"
StrokeThickness="2"
Opacity="0.45"
@@ -2681,10 +2682,16 @@
VerticalAlignment="Center"/>
<!-- Usage arc (progress) -->
<Path x:Name="TokenUsageArc"
Width="22"
Height="22"
Stroke="{DynamicResource AccentColor}"
StrokeThickness="2"
StrokeStartLineCap="Round"
StrokeEndLineCap="Round"/>
StrokeEndLineCap="Round"
Stretch="None"
SnapsToDevicePixels="True"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
<!-- Hidden data elements (referenced in code) -->
<StackPanel Visibility="Collapsed">
<TextBlock x:Name="TokenUsagePercentText"

View File

@@ -7347,16 +7347,32 @@ public partial class ChatWindow : Window
return value.ToString("N0");
}
internal static (double Center, double Radius) CalculateCircularRingMetrics(double diameter, double strokeThickness)
{
var safeDiameter = Math.Max(0, diameter);
var safeStrokeThickness = Math.Max(0, strokeThickness);
var center = safeDiameter / 2;
var radius = Math.Max(0, center - (safeStrokeThickness / 2));
return (center, radius);
}
private double _lastArcRatio = -1;
private double _lastArcDiameter = -1;
private double _lastArcStrokeThickness = -1;
private void UpdateCircularUsageArc(System.Windows.Shapes.Path path, double ratio, double centerX, double centerY, double radius)
{
ratio = Math.Clamp(ratio, 0, 0.9999);
var effectiveDiameter = Math.Max(0, radius * 2 + path.StrokeThickness);
// 비율 변화가 1% 미만이면 렌더링 생략 (250ms마다 호출되므로 불필요한 재생성 방지)
if (Math.Abs(ratio - _lastArcRatio) < 0.01)
// 비율 변화가 1% 미만이어도 크기나 두께가 바뀌면 다시 그립니다.
if (Math.Abs(ratio - _lastArcRatio) < 0.01
&& Math.Abs(effectiveDiameter - _lastArcDiameter) < 0.01
&& Math.Abs(path.StrokeThickness - _lastArcStrokeThickness) < 0.01)
return;
_lastArcRatio = ratio;
_lastArcDiameter = effectiveDiameter;
_lastArcStrokeThickness = path.StrokeThickness;
if (ratio <= 0)
{