- git mv UiChatScrollbar.cs → UiScrollbar.cs; rename class + update doc summary to "Generic scrollbar. Ports retail UIElement_Scrollbar (RegisterElementClass(0xb) @ acclient_2013_pseudo_c.txt:124137); thumb size = trackLen * ThumbRatio (min 8px); step ±1 line." - git mv UiChatScrollbarTests.cs → UiScrollbarTests.cs; rename test class + replace every UiChatScrollbar reference with UiScrollbar (bodies unchanged). - DatWidgetFactory: register Type 11 → new UiScrollbar() before the _ fallback case. - ChatWindowController: change Scrollbar property type to UiScrollbar; replace the old "construct-remove-add" block with a "find factory-built UiScrollbar and bind in place" block (no RemoveChild/AddChild); keep `var track` assignment in scope so the Max/Min block's track.Left/track.Width reads still compile against UiElement?. - AP-41 divergence register: update file:line to UiScrollbar.cs:35; narrow wording to "fallback only — single-tile drawn only when cap ids are unset; the chat controller passes all three cap ids so the 3-slice path is the active code path." - Update inline UiChatScrollbar doc-comment references in UiScrollable.cs + UiChatView.cs. - Full suite: 399 passed, 2 skipped (dat/tower fixture skips), 0 failed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
57 lines
2.8 KiB
C#
57 lines
2.8 KiB
C#
using System;
|
|
|
|
namespace AcDream.App.UI;
|
|
|
|
/// <summary>
|
|
/// Pixel-based vertical scroll model. Port of retail <c>UIElement_Scrollable</c>:
|
|
/// the scroll offset is an integer pixel value (<c>m_iScrollableY</c>) clamped to
|
|
/// [0, ContentHeight - ViewHeight]; the thumb ratio is view/content; the position
|
|
/// ratio is scroll/(content-view). Pure (no GL) so it is fully unit-tested and
|
|
/// shared by the transcript (UiChatView) and the scrollbar (UiScrollbar).
|
|
/// Decomp anchors: SetScrollableXY @0x4740c0, UpdateScrollbarSize_ @0x4741a0,
|
|
/// UpdateScrollbarPosition_ @0x473f20, UIElement_Text::InqScrollDelta @0x4689b0.
|
|
/// </summary>
|
|
public sealed class UiScrollable
|
|
{
|
|
/// <summary>Total wrapped content height in px (m_iScrollableHeight).</summary>
|
|
public int ContentHeight { get; set; }
|
|
/// <summary>Visible viewport height in px.</summary>
|
|
public int ViewHeight { get; set; }
|
|
/// <summary>Pixels per text line (scroll quantum). InqScrollDelta line case.</summary>
|
|
public int LineHeight { get; set; } = 16;
|
|
|
|
private int _scrollY;
|
|
/// <summary>Current scroll offset in px from the top of the content.</summary>
|
|
public int ScrollY => _scrollY;
|
|
|
|
/// <summary>Max scroll = max(0, content - view).</summary>
|
|
public int MaxScroll => Math.Max(0, ContentHeight - ViewHeight);
|
|
|
|
/// <summary>True when content exceeds the view (a scrollbar is warranted).</summary>
|
|
public bool HasOverflow => ContentHeight > ViewHeight;
|
|
|
|
/// <summary>True when the offset is at (or past) the bottom — used for bottom-pin.</summary>
|
|
public bool AtEnd => _scrollY >= MaxScroll;
|
|
|
|
/// <summary>Set the offset, clamped to [0, MaxScroll] (SetScrollableXY clamp).</summary>
|
|
public void SetScrollY(int y) => _scrollY = Math.Clamp(y, 0, MaxScroll);
|
|
|
|
/// <summary>Pin to the bottom (newest content visible).</summary>
|
|
public void ScrollToEnd() => _scrollY = MaxScroll;
|
|
|
|
/// <summary>Thumb size ratio = view/content, clamped to 1 (UpdateScrollbarSize_).</summary>
|
|
public float ThumbRatio => ContentHeight <= 0 ? 1f : Math.Min(1f, (float)ViewHeight / ContentHeight);
|
|
|
|
/// <summary>Position ratio = scroll/(content-view) in [0,1] (UpdateScrollbarPosition_).</summary>
|
|
public float PositionRatio => MaxScroll <= 0 ? 0f : (float)_scrollY / MaxScroll;
|
|
|
|
/// <summary>Inverse of PositionRatio — used when the user drags the thumb.</summary>
|
|
public void SetPositionRatio(float ratio)
|
|
=> SetScrollY((int)MathF.Round(Math.Clamp(ratio, 0f, 1f) * MaxScroll));
|
|
|
|
/// <summary>Scroll by whole lines (sign: +down/newer, -up/older).</summary>
|
|
public void ScrollByLines(int lines) => SetScrollY(_scrollY + lines * LineHeight);
|
|
|
|
/// <summary>Scroll by a page = one view height (InqScrollDelta page case).</summary>
|
|
public void ScrollByPage(int pages) => SetScrollY(_scrollY + pages * ViewHeight);
|
|
}
|