fix(vendor): gate-findings pass — the X button HIDES like retail, clicks return, the dropdown scrolls, pyreal suffix, staged-tab slots
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run
The user's connected gate found five issues; each fixed at the root: G4 (the discovery): retail's vendor X button calls only SetVisible(0) (pc:204147-204182) — the SESSION stays open and re-using the vendor lands on the same-session refresh; the range watcher remains the sole real close. Our port invented a full teardown on X, which is exactly why reopening died. The Runtime fixture proves the wire dispatch was never the problem; ACE has no already-open short-circuit. G3 (regression from the drag-suppression fix): denying IsDragSource also dropped press capture, so clicks fell through to window-drag. UiItemSlot.HandlesClick now claims presses for any occupied cell independent of drag eligibility — clickable and draggable are separate concerns. G5: the authored popup 0x21000043 is ONE scrollable column with a real scrollbar subtree (live-dat scan: ListBox 0x10000350 + scrollbar 0x10000351), not a 3x6 grid. UiMenu gains an authored-driven Scrollable mode (wheel, thumb drag, track paging, up/down buttons); chat's menu is untouched and its ten tests prove it. G1: retail's cost format is "%s %hsp (you have %hsp)" — the p after each %hs is a LITERAL pyreal suffix the port swallowed as part of the specifier. Restored. G2: the Buying/Selling pages' authored lists (same cell template as Items) get the empty-slot fill, presentation-only until staging. Clean-room complete solution: 11,390 passed / 4 skipped / 0 failed. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
58c8de264e
commit
5224e43890
8 changed files with 1061 additions and 51 deletions
|
|
@ -34,10 +34,70 @@ public sealed class UiMenu : UiElement
|
|||
/// <summary>Button-face caption (the active target). Null ⇒ blank face.</summary>
|
||||
public Func<string>? ButtonLabelProvider { get; set; }
|
||||
|
||||
public int RowsPerColumn { get; set; } = 7; // items per column (dat item template)
|
||||
public int RowsPerColumn { get; set; } = 7; // items per column (dat item template);
|
||||
// ALSO the visible-row window height when Scrollable
|
||||
public float RowHeight { get; set; } = 17f; // dat item template 0x1000001E H=17
|
||||
public float ColumnWidth { get; set; } = 191f; // dat item template W=191
|
||||
|
||||
/// <summary>
|
||||
/// G5 (vendor gate finding): retail's authored vendor category popup
|
||||
/// (LayoutDesc <c>0x21000043</c>, root <c>0x1000034F</c>) pairs its
|
||||
/// ListBox (element <c>0x10000350</c>, type <c>0x5</c>) with a SIBLING
|
||||
/// <c>UIElement_Scrollbar</c> (element <c>0x10000351</c>, type <c>0xB</c>,
|
||||
/// 16px wide, docked immediately right of the list at x=100) — verified
|
||||
/// via a live-dat scan (<c>tools/VendorLayoutScan</c>) against
|
||||
/// <c>client_local_English.dat</c>: the ListBox reads a single-column
|
||||
/// shape (attributes resolving to <c>m_nCols=1</c>/<c>m_nRows=6</c>) and
|
||||
/// the row template (<c>0x10000352</c>) is 100×18 — a SCROLLABLE single
|
||||
/// column with 6 visible rows, not our earlier column-major grid
|
||||
/// approximation (which showed all 18 categories at once across 3
|
||||
/// columns, never matching the retail screenshot's ~one-column-with-
|
||||
/// scrollbar look). <see cref="RowsPerColumn"/> becomes the VISIBLE ROW
|
||||
/// COUNT in this mode (still authored-driven — 108px ListBox height / 18px
|
||||
/// row height = 6). Chat's own popup (LayoutDesc <c>0x21000006</c>) has
|
||||
/// NO sibling scrollbar element and keeps the class default false — the
|
||||
/// legacy column-major grid path below is untouched for it.
|
||||
/// </summary>
|
||||
public bool Scrollable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Vertical scroll model for the popup when <see cref="Scrollable"/> is
|
||||
/// set. Content/view/line extents are (re)computed every draw from
|
||||
/// <see cref="Items"/>.Count / <see cref="RowsPerColumn"/> /
|
||||
/// <see cref="RowHeight"/>, mirroring how <see cref="UiItemList"/>
|
||||
/// configures its own <c>Scroll</c> before every layout pass. Exposed so
|
||||
/// a controller/test can assert or drive scroll position directly (the
|
||||
/// popup owns no separate live <see cref="UiScrollbar"/> CHILD widget —
|
||||
/// see the scrollbar chrome properties below for why).
|
||||
/// </summary>
|
||||
public UiScrollable PopupScroll { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Authored width of the popup's docked scrollbar (16px, element
|
||||
/// <c>0x10000351</c>'s own Width).
|
||||
/// </summary>
|
||||
public float ScrollbarWidth { get; set; } = 16f;
|
||||
/// <summary>Authored extent of the up/down buttons along the scrollbar's
|
||||
/// own axis (16px, elements <c>0x10000071</c>/<c>0x10000072</c>'s own Height —
|
||||
/// same convention as <see cref="UiScrollbar.DecrementButtonExtent"/>).</summary>
|
||||
public float ScrollButtonExtent { get; set; } = 16f;
|
||||
|
||||
// Scrollbar chrome sprites. UiMenu draws these itself (rather than hosting a
|
||||
// live UiScrollbar child) because the popup renders in the OVERLAY pass (see
|
||||
// OnDrawOverlay's doc comment) — a normal child widget would draw in the
|
||||
// regular main pass and suffer the exact translucent-sibling artifact that
|
||||
// pass exists to avoid. The geometry math is shared with UiScrollbar via its
|
||||
// public static ThumbRect helper, so both draw identical thumbs.
|
||||
public uint ScrollTrackSprite { get; set; }
|
||||
public uint ScrollThumbSprite { get; set; }
|
||||
public uint ScrollThumbTopSprite { get; set; }
|
||||
public uint ScrollThumbBottomSprite { get; set; }
|
||||
public uint ScrollUpSprite { get; set; }
|
||||
public uint ScrollDownSprite { get; set; }
|
||||
|
||||
private bool _draggingPopupThumb;
|
||||
private float _popupThumbDragOffset;
|
||||
|
||||
private const int Border = RetailChromeSprites.Border; // 8-piece bevel thickness (5px)
|
||||
// The row sprites 0x0600124E/4D bake a checkbox/checkmark into the leftmost ~17px
|
||||
// square; the label starts just past it (box width + small gap) so text aligns with
|
||||
|
|
@ -72,8 +132,14 @@ public sealed class UiMenu : UiElement
|
|||
|
||||
private bool _open;
|
||||
// Interior = the row content; Outer = interior + the 8-piece bevel ring.
|
||||
private int ColumnCount => (Items.Count + RowsPerColumn - 1) / System.Math.Max(1, RowsPerColumn);
|
||||
private float InteriorW => ColumnCount * ColumnWidth;
|
||||
// Scrollable: always exactly one column (RowsPerColumn is the VISIBLE window,
|
||||
// not a wrap threshold), widened by the docked scrollbar's own authored width.
|
||||
private int ColumnCount => Scrollable
|
||||
? 1
|
||||
: (Items.Count + RowsPerColumn - 1) / System.Math.Max(1, RowsPerColumn);
|
||||
private float InteriorW => Scrollable
|
||||
? ColumnWidth + ScrollbarWidth
|
||||
: ColumnCount * ColumnWidth;
|
||||
private float InteriorH => RowsPerColumn * RowHeight;
|
||||
private float OuterW => InteriorW + 2 * Border;
|
||||
private float OuterH => InteriorH + 2 * Border;
|
||||
|
|
@ -130,41 +196,142 @@ public sealed class UiMenu : UiElement
|
|||
var resolve = SpriteResolve;
|
||||
if (!_open || resolve is null) return;
|
||||
|
||||
// Column-major popup opening UPWARD from the button, wrapped in the universal
|
||||
// 8-piece window bevel (retail UIElement_Menu::MakePopup spawns the popup as a
|
||||
// bevelled floating window). Force OPAQUE (a menu reads solid even though the
|
||||
// chat window is translucent). Draw bevel → panel fill → row sprites → labels,
|
||||
// all through the sprite bucket in submission order so labels land on top.
|
||||
// Force OPAQUE (a menu reads solid even though the chat window is translucent).
|
||||
// Draw bevel → panel fill → row sprites → labels, all through the sprite bucket
|
||||
// in submission order so labels land on top.
|
||||
ctx.PushAlphaAbsolute(1f);
|
||||
try
|
||||
{
|
||||
float outerTop = -OuterH; // popup bottom sits at the button top (y=0)
|
||||
float inX = Border, inY = outerTop + Border; // interior origin (inside the bevel)
|
||||
|
||||
DrawBevel(ctx, resolve, 0f, outerTop, OuterW, OuterH);
|
||||
DrawSprite(ctx, resolve, PopupBgSprite, inX, inY, InteriorW, InteriorH); // panel fill behind rows
|
||||
|
||||
for (int i = 0; i < Items.Count; i++)
|
||||
{
|
||||
int col = i / RowsPerColumn, row = i % RowsPerColumn;
|
||||
float x = inX + col * ColumnWidth, y = inY + row * RowHeight;
|
||||
bool selected = Equals(Items[i].Payload, Selected);
|
||||
DrawSprite(ctx, resolve, selected ? ItemHighlightSprite : ItemNormalSprite, x, y, ColumnWidth, RowHeight);
|
||||
}
|
||||
|
||||
float textY = (RowHeight - LineH()) * 0.5f; // center the label in its row
|
||||
for (int i = 0; i < Items.Count; i++)
|
||||
{
|
||||
int col = i / RowsPerColumn, row = i % RowsPerColumn;
|
||||
// Items grey out when unavailable; when EnabledProvider is null all items are enabled.
|
||||
bool avail = EnabledProvider?.Invoke(Items[i].Payload) ?? true;
|
||||
DrawLabel(ctx, Items[i].Label, inX + col * ColumnWidth + TextIndent, inY + row * RowHeight + textY,
|
||||
avail ? TextColorAvailable : TextColorGhosted);
|
||||
}
|
||||
if (Scrollable)
|
||||
DrawScrollablePopup(ctx, resolve);
|
||||
else
|
||||
DrawGridPopup(ctx, resolve);
|
||||
}
|
||||
finally { ctx.PopAlpha(); }
|
||||
}
|
||||
|
||||
/// <summary>Legacy column-major popup (chat's own shape — no authored sibling
|
||||
/// scrollbar element; see <see cref="Scrollable"/>'s doc comment). Unchanged from
|
||||
/// before G5.</summary>
|
||||
private void DrawGridPopup(UiRenderContext ctx, Func<uint, (uint tex, int w, int h)> resolve)
|
||||
{
|
||||
float outerTop = -OuterH; // popup bottom sits at the button top (y=0)
|
||||
float inX = Border, inY = outerTop + Border; // interior origin (inside the bevel)
|
||||
|
||||
DrawBevel(ctx, resolve, 0f, outerTop, OuterW, OuterH);
|
||||
DrawSprite(ctx, resolve, PopupBgSprite, inX, inY, InteriorW, InteriorH); // panel fill behind rows
|
||||
|
||||
for (int i = 0; i < Items.Count; i++)
|
||||
{
|
||||
int col = i / RowsPerColumn, row = i % RowsPerColumn;
|
||||
float x = inX + col * ColumnWidth, y = inY + row * RowHeight;
|
||||
bool selected = Equals(Items[i].Payload, Selected);
|
||||
DrawSprite(ctx, resolve, selected ? ItemHighlightSprite : ItemNormalSprite, x, y, ColumnWidth, RowHeight);
|
||||
}
|
||||
|
||||
float textY = (RowHeight - LineH()) * 0.5f; // center the label in its row
|
||||
for (int i = 0; i < Items.Count; i++)
|
||||
{
|
||||
int col = i / RowsPerColumn, row = i % RowsPerColumn;
|
||||
// Items grey out when unavailable; when EnabledProvider is null all items are enabled.
|
||||
bool avail = EnabledProvider?.Invoke(Items[i].Payload) ?? true;
|
||||
DrawLabel(ctx, Items[i].Label, inX + col * ColumnWidth + TextIndent, inY + row * RowHeight + textY,
|
||||
avail ? TextColorAvailable : TextColorGhosted);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// G5: single-column popup with a docked scrollbar — port of the vendor category
|
||||
/// dropdown's authored shape (LayoutDesc <c>0x21000043</c>, see <see cref="Scrollable"/>'s
|
||||
/// doc comment). Draws exactly <see cref="RowsPerColumn"/> rows (the authored visible
|
||||
/// window), sliced from <see cref="Items"/> starting at <see cref="VisibleTopRow"/>, plus
|
||||
/// the scrollbar chrome using the SAME thumb geometry <see cref="UiScrollbar"/> itself
|
||||
/// uses (<see cref="UiScrollbar.ThumbRect"/>).
|
||||
/// </summary>
|
||||
private void DrawScrollablePopup(UiRenderContext ctx, Func<uint, (uint tex, int w, int h)> resolve)
|
||||
{
|
||||
ConfigurePopupScroll();
|
||||
|
||||
float outerTop = -OuterH;
|
||||
float inX = Border, inY = outerTop + Border;
|
||||
|
||||
DrawBevel(ctx, resolve, 0f, outerTop, OuterW, OuterH);
|
||||
DrawSprite(ctx, resolve, PopupBgSprite, inX, inY, ColumnWidth, InteriorH);
|
||||
|
||||
int start = VisibleTopRow;
|
||||
int count = System.Math.Min(RowsPerColumn, Items.Count - start);
|
||||
float textY = (RowHeight - LineH()) * 0.5f;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
int idx = start + i;
|
||||
float y = inY + i * RowHeight;
|
||||
bool selected = Equals(Items[idx].Payload, Selected);
|
||||
DrawSprite(ctx, resolve, selected ? ItemHighlightSprite : ItemNormalSprite, inX, y, ColumnWidth, RowHeight);
|
||||
}
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
int idx = start + i;
|
||||
float y = inY + i * RowHeight;
|
||||
bool avail = EnabledProvider?.Invoke(Items[idx].Payload) ?? true;
|
||||
DrawLabel(ctx, Items[idx].Label, inX + TextIndent, y + textY,
|
||||
avail ? TextColorAvailable : TextColorGhosted);
|
||||
}
|
||||
|
||||
DrawPopupScrollbar(ctx, resolve, inX + ColumnWidth, inY);
|
||||
}
|
||||
|
||||
/// <summary>Recomputes <see cref="PopupScroll"/>'s extents from the current item
|
||||
/// count/geometry — mirrors <see cref="UiItemList.LayoutCells"/>'s own "configure the
|
||||
/// shared scroll model right before using it" pattern.</summary>
|
||||
private void ConfigurePopupScroll()
|
||||
{
|
||||
int lineHeight = System.Math.Max(1, (int)MathF.Round(RowHeight));
|
||||
PopupScroll.LineHeight = lineHeight;
|
||||
PopupScroll.SetExtents(Items.Count * lineHeight, RowsPerColumn * lineHeight);
|
||||
}
|
||||
|
||||
/// <summary>Index of the first visible row — nearest-row snap of the (possibly
|
||||
/// mid-drag, pixel-continuous) scroll offset, so drawn rows never render partially
|
||||
/// clipped.</summary>
|
||||
private int VisibleTopRow
|
||||
{
|
||||
get
|
||||
{
|
||||
int lineHeight = System.Math.Max(1, (int)MathF.Round(RowHeight));
|
||||
int maxStart = System.Math.Max(0, Items.Count - RowsPerColumn);
|
||||
int row = (int)MathF.Round((float)PopupScroll.ScrollY / lineHeight);
|
||||
return System.Math.Clamp(row, 0, maxStart);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawPopupScrollbar(
|
||||
UiRenderContext ctx, Func<uint, (uint tex, int w, int h)> resolve, float x, float y)
|
||||
{
|
||||
DrawSprite(ctx, resolve, ScrollTrackSprite, x, y, ScrollbarWidth, InteriorH);
|
||||
|
||||
float decExtent = System.Math.Clamp(ScrollButtonExtent, 0f, InteriorH);
|
||||
float incExtent = System.Math.Clamp(ScrollButtonExtent, 0f, InteriorH - decExtent);
|
||||
DrawSprite(ctx, resolve, ScrollUpSprite, x, y, ScrollbarWidth, decExtent);
|
||||
DrawSprite(ctx, resolve, ScrollDownSprite, x, y + InteriorH - incExtent, ScrollbarWidth, incExtent);
|
||||
|
||||
if (!PopupScroll.HasOverflow) return;
|
||||
|
||||
float trackTop = decExtent;
|
||||
float trackLen = MathF.Max(0f, InteriorH - decExtent - incExtent);
|
||||
var (ty, th) = UiScrollbar.ThumbRect(PopupScroll, trackTop, trackLen);
|
||||
const float capH = 3f;
|
||||
if (ScrollThumbTopSprite != 0 && ScrollThumbBottomSprite != 0 && th >= 2f * capH)
|
||||
{
|
||||
DrawSprite(ctx, resolve, ScrollThumbTopSprite, x, y + ty, ScrollbarWidth, capH);
|
||||
DrawSprite(ctx, resolve, ScrollThumbSprite, x, y + ty + capH, ScrollbarWidth, th - 2f * capH);
|
||||
DrawSprite(ctx, resolve, ScrollThumbBottomSprite, x, y + ty + th - capH, ScrollbarWidth, capH);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawSprite(ctx, resolve, ScrollThumbSprite, x, y + ty, ScrollbarWidth, th);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Draw the universal 8-piece retail window bevel (corners + tiled edges +
|
||||
/// tiled centre fill) framing the rect (<paramref name="x"/>,<paramref name="y"/>,
|
||||
/// <paramref name="w"/>,<paramref name="h"/>). Reuses the same geometry +
|
||||
|
|
@ -210,6 +377,31 @@ public sealed class UiMenu : UiElement
|
|||
|
||||
public override bool OnEvent(in UiEvent e)
|
||||
{
|
||||
// G5: scrollbar drag/wheel handling for the scrollable popup. Checked BEFORE
|
||||
// the MouseDown-only early return below since these span MouseMove/Scroll too.
|
||||
if (Scrollable && _open)
|
||||
{
|
||||
if (e.Type == UiEventType.MouseMove && _draggingPopupThumb)
|
||||
{
|
||||
DragPopupThumb(e.Data2);
|
||||
return true;
|
||||
}
|
||||
if (e.Type == UiEventType.MouseUp && _draggingPopupThumb)
|
||||
{
|
||||
// Ending a thumb drag must not also close the popup — UiRoot fires a
|
||||
// trailing Click on the same target after MouseUp, which this class
|
||||
// does not handle (falls through as a no-op), so the popup stays open.
|
||||
_draggingPopupThumb = false;
|
||||
return true;
|
||||
}
|
||||
if (e.Type == UiEventType.Scroll)
|
||||
{
|
||||
ConfigurePopupScroll();
|
||||
PopupScroll.ScrollByLines(-e.Data0);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (e.Type != UiEventType.MouseDown) return false;
|
||||
|
||||
float lx = e.Data1, ly = e.Data2;
|
||||
|
|
@ -218,6 +410,9 @@ public sealed class UiMenu : UiElement
|
|||
// Map into the bevel interior, then to (col,row). Clicks in the bevel ring
|
||||
// (outside the interior) just close the menu.
|
||||
float ix = lx - Border, iy = ly - (-OuterH + Border);
|
||||
if (Scrollable)
|
||||
return HandleScrollablePopupMouseDown(ix, iy);
|
||||
|
||||
if (ix >= 0 && ix < InteriorW && iy >= 0 && iy < InteriorH)
|
||||
{
|
||||
int col = (int)(ix / ColumnWidth);
|
||||
|
|
@ -243,4 +438,73 @@ public sealed class UiMenu : UiElement
|
|||
_open = !_open; // toggle on button click
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// G5: mouse-down dispatch for the scrollable popup — a click on the item
|
||||
/// column picks a row (offset by the current scroll position, closing the
|
||||
/// popup exactly like the grid path); a click on the scrollbar's up/down
|
||||
/// buttons, track, or thumb drives scrolling and does NOT close the popup
|
||||
/// (mirrors <see cref="UiScrollbar.OnEvent"/>'s own MouseDown shape).
|
||||
/// </summary>
|
||||
private bool HandleScrollablePopupMouseDown(float ix, float iy)
|
||||
{
|
||||
if (ix >= 0 && ix < ColumnWidth && iy >= 0 && iy < InteriorH)
|
||||
{
|
||||
int row = (int)(iy / RowHeight);
|
||||
int idx = VisibleTopRow + row;
|
||||
if (row >= 0 && row < RowsPerColumn && idx >= 0 && idx < Items.Count
|
||||
&& (EnabledProvider?.Invoke(Items[idx].Payload) ?? true))
|
||||
{
|
||||
OnSelect?.Invoke(Items[idx].Payload);
|
||||
}
|
||||
_open = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
float scrollbarX = ColumnWidth;
|
||||
if (ix >= scrollbarX && ix < scrollbarX + ScrollbarWidth && iy >= 0 && iy < InteriorH)
|
||||
{
|
||||
ConfigurePopupScroll();
|
||||
float decExtent = System.Math.Clamp(ScrollButtonExtent, 0f, InteriorH);
|
||||
float incExtent = System.Math.Clamp(ScrollButtonExtent, 0f, InteriorH - decExtent);
|
||||
|
||||
if (iy < decExtent) { PopupScroll.ScrollByLines(-1); return true; }
|
||||
if (iy >= InteriorH - incExtent) { PopupScroll.ScrollByLines(1); return true; }
|
||||
|
||||
float trackTop = decExtent;
|
||||
float trackLen = MathF.Max(0f, InteriorH - decExtent - incExtent);
|
||||
var (ty, th) = UiScrollbar.ThumbRect(PopupScroll, trackTop, trackLen);
|
||||
if (iy >= ty && iy <= ty + th)
|
||||
{
|
||||
_draggingPopupThumb = true;
|
||||
_popupThumbDragOffset = iy - ty;
|
||||
}
|
||||
else
|
||||
{
|
||||
PopupScroll.ScrollByPage(iy < ty ? -1 : 1);
|
||||
}
|
||||
return true; // scrollbar interaction never closes the popup
|
||||
}
|
||||
|
||||
// Clicked the bevel ring — close, matching the grid path.
|
||||
_open = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Continues an in-progress thumb drag (<see cref="_draggingPopupThumb"/>);
|
||||
/// mirrors <see cref="UiScrollbar.OnEvent"/>'s own <c>MouseMove when _draggingThumb</c>
|
||||
/// case, reusing <see cref="UiScrollbar.ThumbRect"/> for the exact same thumb height.</summary>
|
||||
private void DragPopupThumb(float ly)
|
||||
{
|
||||
float iy = ly - (-OuterH + Border);
|
||||
ConfigurePopupScroll();
|
||||
float decExtent = System.Math.Clamp(ScrollButtonExtent, 0f, InteriorH);
|
||||
float incExtent = System.Math.Clamp(ScrollButtonExtent, 0f, InteriorH - decExtent);
|
||||
float trackTop = decExtent;
|
||||
float trackLen = MathF.Max(0f, InteriorH - decExtent - incExtent);
|
||||
var (_, thumbH) = UiScrollbar.ThumbRect(PopupScroll, trackTop, trackLen);
|
||||
float travel = MathF.Max(1f, trackLen - thumbH);
|
||||
float ratio = (iy - _popupThumbDragOffset - trackTop) / travel;
|
||||
PopupScroll.SetPositionRatio(ratio);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue