feat(D.2b): UiChatScrollbar — track/thumb/buttons driving UiScrollable
Implements the right-side chat scrollbar widget. Ports retail UIElement_Scrollbar::UpdateLayout @0x4710d0 (thumb sizing + placement) and HandleButtonClick @0x470e90 (step ±1 line, page on track click). Dat element ids sourced from chat LayoutDesc 0x21000006 (base layout 0x2100003E): up-button sprite 0x06004C69, down-button 0x06004C6C, track 0x06004C5F, thumb middle 0x06004C63. Up/down buttons occupy the top and bottom ButtonH (16px) regions of the widget height, matching element positions Y=0 and Y=32 in the base scrollbar template. Adds 6 pure ThumbRect tests (no GL): sizing, clamping to MinThumb, position at start/mid/end, no-overflow full-fill. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0eaef67b9d
commit
2940b4e3b2
2 changed files with 245 additions and 0 deletions
81
tests/AcDream.App.Tests/UI/UiChatScrollbarTests.cs
Normal file
81
tests/AcDream.App.Tests/UI/UiChatScrollbarTests.cs
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
using AcDream.App.UI;
|
||||
using Xunit;
|
||||
|
||||
namespace AcDream.App.Tests.UI;
|
||||
|
||||
/// <summary>
|
||||
/// Pure unit tests for <see cref="UiChatScrollbar.ThumbRect"/> — no GL dependency.
|
||||
/// </summary>
|
||||
public class UiChatScrollbarTests
|
||||
{
|
||||
// Model: content=400, view=100, trackLen=200.
|
||||
// ThumbRatio = 100/400 = 0.25 → thumbH = max(8, 200*0.25) = 50.
|
||||
// Travel = 200 - 50 = 150.
|
||||
|
||||
[Fact]
|
||||
public void ThumbRect_AtStart_HasCorrectSizeAndZeroOffset()
|
||||
{
|
||||
var m = new UiScrollable { ContentHeight = 400, ViewHeight = 100 };
|
||||
// PositionRatio = 0 (start).
|
||||
var (y, h) = UiChatScrollbar.ThumbRect(m, trackTop: 0f, trackLen: 200f);
|
||||
Assert.Equal(50f, h, 3f);
|
||||
Assert.Equal(0f, y, 3f);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThumbRect_AtEnd_PinsToBottomOfTrack()
|
||||
{
|
||||
var m = new UiScrollable { ContentHeight = 400, ViewHeight = 100 };
|
||||
m.ScrollToEnd(); // PositionRatio = 1.
|
||||
float trackTop = 16f, trackLen = 200f;
|
||||
var (y, h) = UiChatScrollbar.ThumbRect(m, trackTop, trackLen);
|
||||
Assert.Equal(50f, h, 3f);
|
||||
// y = trackTop + travel * 1 = 16 + 150 = 166.
|
||||
Assert.Equal(166f, y, 3f);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThumbRect_WithButtonH_CorrectlyOffsetsFromTrackTop()
|
||||
{
|
||||
// Matches task spec: content=400, view=100, trackLen=200, PositionRatio=1.
|
||||
// thumbH=50; travel=150; y = trackTop + 150 = trackTop + 150.
|
||||
var m = new UiScrollable { ContentHeight = 400, ViewHeight = 100 };
|
||||
m.ScrollToEnd();
|
||||
var (y, h) = UiChatScrollbar.ThumbRect(m, trackTop: 16f, trackLen: 200f);
|
||||
Assert.Equal(50f, h, 3f);
|
||||
Assert.Equal(166f, y, 3f); // 16 + 150
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThumbRect_MidScroll_InterpolatesPosition()
|
||||
{
|
||||
// content=400 view=100 → MaxScroll=300; ScrollY=150 → PositionRatio=0.5.
|
||||
var m = new UiScrollable { ContentHeight = 400, ViewHeight = 100 };
|
||||
m.SetScrollY(150);
|
||||
Assert.Equal(0.5f, m.PositionRatio, 3);
|
||||
|
||||
var (y, h) = UiChatScrollbar.ThumbRect(m, trackTop: 0f, trackLen: 200f);
|
||||
Assert.Equal(50f, h, 3f);
|
||||
// y = 0 + 150 * 0.5 = 75.
|
||||
Assert.Equal(75f, y, 3f);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThumbRect_SmallContent_EnforcesMinThumb()
|
||||
{
|
||||
// content=1000, view=10, trackLen=200 → ThumbRatio=0.01 → raw=2 < 8 → clamp to 8.
|
||||
var m = new UiScrollable { ContentHeight = 1000, ViewHeight = 10 };
|
||||
var (_, h) = UiChatScrollbar.ThumbRect(m, trackTop: 0f, trackLen: 200f);
|
||||
Assert.Equal(8f, h, 3f);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThumbRect_NoOverflow_ThumbFillsTrack()
|
||||
{
|
||||
// content <= view → ThumbRatio = 1 → thumbH = trackLen.
|
||||
var m = new UiScrollable { ContentHeight = 50, ViewHeight = 100 };
|
||||
var (y, h) = UiChatScrollbar.ThumbRect(m, trackTop: 16f, trackLen: 100f);
|
||||
Assert.Equal(100f, h, 3f);
|
||||
Assert.Equal(16f, y, 3f); // travel = 0 → y = trackTop
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue