ui: a Slider's thumb had no rect, so no scrollbar could be skinned or seen
"Half of the info on the right is out of the frame" was a scroll frame with 699 of content in a 451 window, a scroll range of 228, and a scrollbar with nothing on it. The thumb's rect was only ever computed inside the draw pass and never published: the region sweep skips anchor-less regions and a thumb is anchor-less by construction, because its slider places it. That is invisible until something anchors to the thumb, and pfUI does -- SkinScrollbar clears the stock knob and hangs its own off the thumb's two corners, so every pfUI-skinned scrollbar in the interface was a bare track, which is a scroll frame that silently cannot be scrolled. The thumb is on the layout roster now, survives the unanchored prune, and carries its slider's (vertical, fraction) in its node hash -- without the last one a named touch moved no hash and seeded nothing, which WOW_LAYOUT_VERIFY caught as an incremental pass disagreeing with a full derivation. The draw pass keeps computing its own rect: extract takes &self and cannot resolve, so a SetValue with no resolve behind it still has to draw where the value now is. map: the window has two layouts and the canvas only knew the maximized one Pressing maximize left the art a 1002x668 patch in a full-screen window and minimize could not put it back, because the container was anchored once at build to WorldMapPositioningGuide TOP -502,-69 -- 1.12's maximized anchor against a fixed 1024x768 guide. Read as insets instead (15/4/33/20 windowed, 10/12/69/31 maximized) the same numbers reproduce 1.12 at 1.12's sizes and fit any other window, which is also the left-edge bleed: -502 against a guide stretched to pfUI's 1017-wide window starts the art 6.5 px in, not 10. The zoom ladder's floor follows: it was the constant 1, true only while the container was the art's own size, and is the measured fit now. A view outside a rebuilt ladder is pulled onto its nearest end, since ZoomIn and ZoomOut walk it by value -- OpenMapClassic's whole ladder sits below ours. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
@@ -2628,3 +2628,100 @@ anchor is `WorldMapFrame TOPLEFT 15,-33` at scale 0.7, which would fill 70% of h
|
||||
pfUI sizes that window from the **unscaled** `WorldMapButton` — it is written for the maximized
|
||||
map and he is running the windowed one. Flipping the map to maximized is the one-click test of
|
||||
that reading.
|
||||
|
||||
## No scrollbar in the interface had a thumb anything could anchor to
|
||||
|
||||
grom: "half of the info on the right is out of the frame" about the character panel. It was not
|
||||
out of the frame. `BCPPFUIStatsContent` is 699 tall inside a 451-tall scroll frame, with a
|
||||
scrollbar and a scroll range of 228 — the information was behind a scrollbar he could not see,
|
||||
could not grab, and had no reason to think was there.
|
||||
|
||||
A Slider's thumb texture had **no resolved rect**. Measured, with his own addons loaded:
|
||||
|
||||
```
|
||||
bar 16x419 L=487.0 B=881.0
|
||||
realthumb 16x16 L=nil B=nil
|
||||
pfUIthumb 8x8 L=nil B=nil <- pfUI's own knob, anchored to the thumb
|
||||
anchored-to-thumb 0x0 L=nil <- a fresh texture, same anchors, same answer
|
||||
```
|
||||
|
||||
The rect was only ever computed inside the **draw** pass (`extract`'s thumb branch, from
|
||||
`slider::thumb_rect`), never published, and the region sweep skipped the thumb outright: it
|
||||
carries no anchors, because its slider places it, and anchor-less regions are not layout inputs.
|
||||
A thumb that draws but has no rect is invisible for exactly as long as nobody anchors to it —
|
||||
|
||||
and pfUI anchors to it. `SkinScrollbar` clears the stock knob's texture and hangs its own off the
|
||||
thumb's two corners (`pfUI/api/ui-widgets.lua:813-820`). Stock knob blanked, replacement
|
||||
unplaceable: **every pfUI-skinned scrollbar in the interface was a bare track**, which is a scroll
|
||||
frame that silently cannot be scrolled. The character panel is where he noticed; the Cine settings
|
||||
page he reported two rounds ago is the same fault.
|
||||
|
||||
### What it took
|
||||
|
||||
Three things, because the thumb had fallen out of the layout at three separate points:
|
||||
|
||||
1. **The roster.** `if data.anchors.is_empty() { continue; }` in the region walk — the thumb is
|
||||
anchor-less by construction, so it never entered the sweep. It is on the roster now.
|
||||
2. **The prune.** `region_resolved.retain(…)` drops unanchored regions, which would have taken
|
||||
the rect back off the thumb the moment the sweep gave it one.
|
||||
3. **The node hash.** The incremental pass re-hashes only the ids a write *named*, and seeds only
|
||||
those whose hash moved — "a named write that did not move the hash seeds nothing". The thumb's
|
||||
hash is over its anchors, size and measured extent, and a `SetValue` moves none of them, so a
|
||||
named touch alone changed nothing. The slider's `(vertical, fraction)` is now part of that
|
||||
hash, for thumbs only, and `Slider:SetValue`/`SetMinMaxValues`/`SetValueStep`/`SetOrientation`
|
||||
and the drag name the thumb through `touch_layout_region`.
|
||||
|
||||
`WOW_LAYOUT_VERIFY=1` caught stage 3 outright — it panicked with "an INCREMENTAL layout pass
|
||||
disagreed with a full derivation" — which is exactly what that gate is for and the reason the
|
||||
first two stages alone looked like they had worked.
|
||||
|
||||
**The draw pass still computes its own rect, and that is deliberate.** `extract` takes `&self` and
|
||||
cannot resolve, so a `SetValue` with no resolve behind it must still draw the thumb where the
|
||||
value now is; the published rect is for `GetLeft` and for anchoring, and is refreshed on the next
|
||||
resolve, which the client runs every frame. Two call sites of one formula, like the StatusBar fill
|
||||
beside it. Reading the published rect in `extract` instead broke
|
||||
`slider_thumb_draws_at_value_fraction_along_the_track`, which is the test that says so.
|
||||
|
||||
Measured after, on the same scrollbar: thumb at the track top for value 0, and for value 200 of
|
||||
0..228, `thumbB=930.49` against 881 + (1 − 200/228) × (419 − 16) = 930.5 by hand, with pfUI's
|
||||
replacement knob 4 px inside it.
|
||||
|
||||
## The map window has two layouts, and the canvas only knew one
|
||||
|
||||
grom pressed the maximize button and "it got worse, doesn't go back to what it was". Both halves
|
||||
are one bug: the canvas container was anchored once, at build, to `WorldMapPositioningGuide TOP
|
||||
-502,-69` — 1.12's **maximized** anchor, against a guide that is a fixed 1024x768 frame.
|
||||
|
||||
1.12 lays the map art out twice and the two have nothing in common:
|
||||
|
||||
| | window | art anchor | art size |
|
||||
|---|---|---|---|
|
||||
| `WorldMapFrame_Minimize` | 720x521 | `WorldMapFrame TOPLEFT 15,-33` | 1002x668 at scale **0.7** |
|
||||
| `WorldMapFrame_Maximize` | the whole screen | guide `TOP -502,-69` | 1002x668 at scale 1, inside the fixed 1024x768 guide, `BlackoutWorld` around it |
|
||||
|
||||
So maximize left the art a 1002x668 patch in a full-screen window, and minimize put the window
|
||||
back at 720x521 with the art still where the maximized sheet had it.
|
||||
|
||||
Read as **insets** rather than as the one size each produces — 15/4/33/20 windowed, 10/12/69/31
|
||||
maximized — the same two numbers reproduce 1.12 exactly at 1.12's sizes and do the obvious thing
|
||||
at any other, which matters because pfUI resizes the window to `WorldMapButton`'s size plus its own
|
||||
chrome (`pfUI/modules/map.lua:111`). That is also the answer to the left-edge bleed left open in
|
||||
the previous section: it was the maximized `-502` being read against a guide stretched to a
|
||||
1017-wide window, so the art started 6.5 px in instead of 10.
|
||||
|
||||
Two consequences had to follow it:
|
||||
|
||||
- **The zoom ladder's floor was the constant 1**, and the comment there said why: the canvas is
|
||||
sized to the art layer and the container *was* the rect that layer filled, so 1 was the whole
|
||||
map by construction. The container is the window's content area now, so the floor is measured
|
||||
(`GetFitScale`) — and comes out exactly 1 when the window is 1.12's own size.
|
||||
- **A view outside the new ladder is pulled onto its nearest end.** `ZoomIn`/`ZoomOut` walk the
|
||||
ladder by value, so a scale outside it cannot be climbed back onto by scrolling. Both ends
|
||||
happen: a window that grew raises the floor, and a canvas addon that replaces the ladder
|
||||
wholesale lowers the ceiling — OpenMapClassic's stitched world is reached by zooming out far
|
||||
past this file's own scale 1, so its entire ladder sits below ours. Measured with OpenMap
|
||||
loaded: `minZoom=0.0125 maxZoom=0.899 current=1.0`, a view above its own ceiling.
|
||||
|
||||
The no-art-layer fallback changed with them: the canvas used to take the **container's** size when
|
||||
a map had no art layer, which was safe only while the container was 1002x668. It is 1.12's own art
|
||||
rect now in every case, and the fitted floor is what keeps it visible in a smaller window.
|
||||
|
||||
Reference in New Issue
Block a user