feat(D.2b): UiChatView drives the shared UiScrollable model
Replace the ad-hoc _scroll float with a public UiScrollable instance. OnDraw feeds ContentHeight/ViewHeight/LineHeight into the model each frame and reads baseY = bottom - contentH + (MaxScroll - ScrollY) — the (MaxScroll-ScrollY) inversion reconciles UiScrollable's top-origin convention (0=oldest, MaxScroll=newest) with the visual layout (newest at bottom). The wheel handler routes through ScrollByLines with a sign flip so wheel-up still reveals older lines. _pinBottom tracks whether the view is at the end and calls ScrollToEnd() each draw to auto-scroll new messages. ClampScroll static method kept — referenced by existing tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9f273c9343
commit
0eaef67b9d
1 changed files with 23 additions and 9 deletions
|
|
@ -52,8 +52,12 @@ public sealed class UiChatView : UiElement
|
|||
/// <summary>Inner text inset from the view edges, px.</summary>
|
||||
public float Padding { get; set; } = 4f;
|
||||
|
||||
// Pixels the transcript is scrolled UP from the newest line (0 = pinned to bottom).
|
||||
private float _scroll;
|
||||
/// <summary>The scroll model — also read by the linked UiChatScrollbar.</summary>
|
||||
public UiScrollable Scroll { get; } = new();
|
||||
|
||||
/// <summary>True while the view is pinned to the newest line (auto-scrolls as content grows).</summary>
|
||||
private bool _pinBottom = true;
|
||||
|
||||
private const float WheelLines = 1f; // lines advanced per wheel notch (retail = 1 line per notch)
|
||||
|
||||
// ── Cached layout from the last OnDraw, so OnEvent hit-tests the SAME geometry ──
|
||||
|
|
@ -112,11 +116,19 @@ public sealed class UiChatView : UiElement
|
|||
float top = Padding, bottom = Height - Padding;
|
||||
float innerH = bottom - top;
|
||||
float contentH = lines.Count * lh;
|
||||
_scroll = ClampScroll(_scroll, contentH, innerH);
|
||||
|
||||
// Bottom-pin: with _scroll==0 the LAST line ends at `bottom`; scrolling up
|
||||
// shifts the whole block down so older lines are revealed at the top.
|
||||
float baseY = bottom - contentH + _scroll;
|
||||
// Drive the shared scroll model with the current geometry.
|
||||
Scroll.LineHeight = (int)MathF.Round(lh);
|
||||
Scroll.ContentHeight = (int)MathF.Ceiling(contentH);
|
||||
Scroll.ViewHeight = (int)MathF.Floor(innerH);
|
||||
if (_pinBottom) Scroll.ScrollToEnd();
|
||||
|
||||
// UiScrollable: ScrollY=0 is TOP/oldest, ScrollY=MaxScroll is BOTTOM/newest.
|
||||
// Visual layout: newest at bottom → baseY = bottom - contentH (ScrollY at max).
|
||||
// Invert: baseY = bottom - contentH + (MaxScroll - ScrollY).
|
||||
// With _pinBottom: ScrollY=MaxScroll → baseY=bottom-contentH → last line ends at bottom. ✓
|
||||
// Scrolled to top: ScrollY=0 → baseY=bottom-contentH+MaxScroll=bottom-innerH=top. ✓
|
||||
float baseY = bottom - contentH + (Scroll.MaxScroll - Scroll.ScrollY);
|
||||
_lastBaseY = baseY;
|
||||
|
||||
// Normalised selection span (start <= end), if any.
|
||||
|
|
@ -166,9 +178,11 @@ public sealed class UiChatView : UiElement
|
|||
{
|
||||
case UiEventType.Scroll:
|
||||
{
|
||||
float lh = DatFont?.LineHeight ?? (Font ?? _lastFont)?.LineHeight ?? 16f;
|
||||
// Silk wheel +Y = scroll up = reveal older = shift content down = larger _scroll.
|
||||
_scroll += e.Data0 * WheelLines * lh; // re-clamped next OnDraw against live content
|
||||
// Silk wheel +Y = scroll up = reveal older = toward the TOP = decrease ScrollY.
|
||||
// ScrollByLines sign: +down/newer, -up/older.
|
||||
// e.Data0 > 0 → wheel up → want older → ScrollByLines with negative lines.
|
||||
Scroll.ScrollByLines((int)(-e.Data0 * WheelLines));
|
||||
_pinBottom = Scroll.AtEnd;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue