The map's mouse surface shrank with the zoom, and four things fell through the hole
The canvas gave the map no mouse surface of its own, on the reasoning that 1.12's
`WorldMapButton` covers the same rect and sits above it. The button is pinned INSIDE the canvas
child, so it scales with it: measured at the world view with OpenMapClassic driving, at grom's
own 2560x1440, a 23x16 px button inside a 701x468 px window. Every press on the other 99%
reached the next frame that takes the mouse — `WorldMapFrame`, which pfUI makes a
`RegisterForDrag("LeftButton")` handle for the whole window. So dragging the map moved the
window, and a click never reached `ProcessCanvasClickHandlers`, which is the only way a map pin
is ever dropped.
The container takes the mouse and the wheel now and drives the same controller. The frame level
mattered as much: the hit sweep probes one `(strata, level)` plane in link order and a parent is
linked before its children (`0x764aa0`), so a container tied with `WorldMapFrame` still lost
every press to its own parent. It is re-seated to `map:GetFrameLevel() + 1` on every show,
because the window is level 0 while the canvas builds and level 1 once `ShowUIPanel` has raised
it.
With the click arriving, a pin still stored nothing: `Map/Pins.lua:57` trims the note with
`strtrim`, which was deliberately absent. `script/stdlib.rs` still refuses it on the 1.12
surface, for the reason written there; this build claims ClassicAPI, whose client does have it,
so it lands in `compat/classic_api/string_trim.rs` with both spellings. Eight bare call sites in
the corpus, one of them in pfUI's search box on every keystroke.
`SCALE_RATE` 10 -> 24. A 0.7 s half-life was tolerable only while a wheel notch could not
accumulate; now that they add up the glide is what you feel ("scrolls past where I stop
scrolling").
`BenillaMapTextures(uiMapID)` is `GetExploredMapTextures` without the explored filter. Reveal
mode draws unexplored overlays out of a retail FileDataID table this client resolves none of —
captured with the fog default and with reveal, the same view came back pixel-identical, 0 by
`compare -metric AE`. The overlays were never missing, only their names: `Map/RevealFromClient.lua`
in `Addon_Ports/openmap_port` rebuilds the table from this, with paths.
Also in the port, not here: the arrow is placed through OpenMap's own coordinates, because
1.12 measures `GetPlayerMapPosition` against Blizzard's world and continent art and the stitched
map is a different projection.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
@@ -54,9 +54,17 @@ MAP_CANVAS_MOUSE_WHEEL_ZOOM_BEHAVIOR_SMOOTH = 2
|
||||
-- values; small enough that the snap is invisible and large enough that the OnUpdate stops.
|
||||
local PAN_SNAP = 0.0005
|
||||
local SCALE_SNAP = 0.001
|
||||
-- How fast the view approaches its target, per second, as a fraction closed.
|
||||
-- How fast the view approaches its target, per second, as a fraction closed: the ease closes
|
||||
-- `1 - 0.5^(elapsed * RATE / 7)` of the remaining gap, so the half-life is `7 / RATE` seconds.
|
||||
--
|
||||
-- The scale's was 10 — a 0.7 s half-life, still visibly moving two seconds after the last wheel
|
||||
-- notch. That was tolerable only while a notch could not accumulate, because the target was never
|
||||
-- more than one rung away; once notches add up the glide is the thing you feel. grom, 2026-11-04:
|
||||
-- *"scroll is better but feels pretty slippery, like scrolls past where I stop scrolling."* At 24
|
||||
-- the half-life is 0.29 s and a multi-rung spin lands about where the wheel stopped. The pan's is
|
||||
-- left alone: dragging writes the scroll directly and never eases.
|
||||
local PAN_RATE = 10
|
||||
local SCALE_RATE = 10
|
||||
local SCALE_RATE = 24
|
||||
-- A drag under this many pixels is still a click, which is what `WouldCursorPositionBeClick`
|
||||
-- answers; Era's own threshold.
|
||||
local CLICK_SLOP = 5
|
||||
@@ -1842,10 +1850,24 @@ local function BuildCanvas()
|
||||
sc:SetPoint("TOPLEFT", map, "TOPLEFT", 0, 0)
|
||||
end
|
||||
end
|
||||
-- No mouse of its own: the map's mouse surface is 1.12's `WorldMapButton`, which covers the
|
||||
-- same rect and is above this in the tree, so a mouse-enabled container here would never see
|
||||
-- a press anyway. The hooks below drive the controller from that button instead.
|
||||
sc:EnableMouse(false)
|
||||
-- The container takes the mouse as well, UNDER 1.12's `WorldMapButton`.
|
||||
--
|
||||
-- The button was the whole mouse surface here, on the reasoning that it covers the same rect
|
||||
-- and sits above this in the tree. That holds at rest and stops holding the moment a canvas
|
||||
-- addon scales the canvas: the button is pinned to the canvas child, so at a scale below 1 it
|
||||
-- shrinks away from the container and leaves the rest of the visible map with no mouse
|
||||
-- surface at all. A press there reaches the next frame that takes the mouse, which with pfUI
|
||||
-- loaded is `WorldMapFrame` itself -- `modules/map.lua:45` makes the whole window a
|
||||
-- `RegisterForDrag("LeftButton")` handle -- so dragging the map moved the window instead of
|
||||
-- panning, and whether it did depended on the zoom and where you grabbed. grom, 2026-11-04:
|
||||
-- *"sometimes its panning the map as it should and sometimes its grabbing and moving the
|
||||
-- whole window, this should only happen on the borders."*
|
||||
--
|
||||
-- The button still wins wherever it reaches, being deeper; this only catches what used to
|
||||
-- fall through the floor, and it drives the same controller, so a pan begun on either is one
|
||||
-- gesture. It takes no clicks (`RegisterForClicks` is never called), so it cannot swallow a
|
||||
-- click the map's own surface would have had.
|
||||
sc:EnableMouse(true)
|
||||
|
||||
-- ...and the container crops everything below it as well, which the ScrollFrame alone does
|
||||
-- not: that crops the scroll child's subtree, and an addon is free to parent a layer to the
|
||||
@@ -1997,6 +2019,43 @@ local function BuildCanvas()
|
||||
button:EnableMouseWheel(true)
|
||||
button:RegisterForClicks("LeftButtonUp", "RightButtonUp", "MiddleButtonUp")
|
||||
|
||||
-- ...and the same three off the container, for the part of the window the button has shrunk
|
||||
-- away from. `OnUpdate` is the button's alone: the controller eases once a frame, and two
|
||||
-- callers would ease it twice.
|
||||
--
|
||||
-- The container must out-level the map window, and the button must out-level the container.
|
||||
--
|
||||
-- The hit sweep probes one `(strata, level)` plane in **link** order, and a parent is linked
|
||||
-- before its children (`0x764aa0`; `TargetFrame.lua:33` relies on it), so a frame tied with
|
||||
-- its own parent loses every press to it. Tied with `WorldMapFrame` the container took
|
||||
-- nothing, which is a mouse-enabled container that still handed the drag to pfUI.
|
||||
--
|
||||
-- Re-asserted on every show, not seated once: `WorldMapFrame` is level 0 while this builds
|
||||
-- and 1 once `ShowUIPanel` has raised it, so a level computed at build time ties the moment
|
||||
-- the window is first opened.
|
||||
local function SeatLevels()
|
||||
local base = map:GetFrameLevel() or 0
|
||||
sc:SetFrameLevel(base + 1)
|
||||
if canvas:GetFrameLevel() <= sc:GetFrameLevel() then
|
||||
canvas:SetFrameLevel(sc:GetFrameLevel() + 1)
|
||||
end
|
||||
if button:GetFrameLevel() <= canvas:GetFrameLevel() then
|
||||
button:SetFrameLevel(canvas:GetFrameLevel() + 1)
|
||||
end
|
||||
end
|
||||
SeatLevels()
|
||||
map:HookScript("OnShow", SeatLevels)
|
||||
sc:SetScript("OnMouseDown", function()
|
||||
sc:OnMouseDown(arg1)
|
||||
end)
|
||||
sc:SetScript("OnMouseUp", function()
|
||||
sc:OnMouseUp(arg1)
|
||||
end)
|
||||
sc:SetScript("OnMouseWheel", function()
|
||||
sc:OnMouseWheel(arg1)
|
||||
end)
|
||||
sc:EnableMouseWheel(true)
|
||||
|
||||
-- Seat the first map before anything reads `GetMapID()`: the stock window has already resolved
|
||||
-- the player's zone by the time this runs, so the canvas starts where the window is.
|
||||
map.mapID = BenillaMapIDForSelection() or WORLD_MAP_ID
|
||||
|
||||
@@ -538,3 +538,51 @@ fn wheel_notches_accumulate_while_the_zoom_is_still_easing() {
|
||||
.unwrap();
|
||||
assert_eq!((lo, hi), (1.0, 10.0), "the ladder's own ends hold");
|
||||
}
|
||||
|
||||
/// The container takes the mouse, and out-levels the map window so it actually gets it.
|
||||
///
|
||||
/// The map's mouse surface used to be 1.12's `WorldMapButton` alone, which is pinned to the
|
||||
/// canvas child — so once a canvas addon scales the canvas down, the button shrinks away from
|
||||
/// the container and the rest of the visible map has no mouse surface at all. Measured at the
|
||||
/// world view with OpenMapClassic driving: a 23x16 px button inside a 701x468 px window. A press
|
||||
/// on the other 99% reached the next frame that takes the mouse, which with pfUI loaded is
|
||||
/// `WorldMapFrame` itself — a `RegisterForDrag("LeftButton")` handle for the whole window.
|
||||
///
|
||||
/// The level matters as much as the mouse bit: the hit sweep probes one `(strata, level)` plane
|
||||
/// in link order and a parent is linked before its children, so a container tied with its own
|
||||
/// parent loses every press to it.
|
||||
#[test]
|
||||
fn the_container_takes_the_mouse_and_out_levels_the_window() {
|
||||
let Some(mut s) = harness() else { return };
|
||||
s.run("ShowUIPanel(WorldMapFrame)").unwrap();
|
||||
s.resolve();
|
||||
let (mouse, wheel, has_down, sc_level, map_level, button_level): (
|
||||
bool,
|
||||
bool,
|
||||
bool,
|
||||
i64,
|
||||
i64,
|
||||
i64,
|
||||
) = s
|
||||
.eval(
|
||||
"local sc = WorldMapFrame.ScrollContainer \
|
||||
return sc:IsMouseEnabled() and true or false, \
|
||||
sc:IsMouseWheelEnabled() and true or false, \
|
||||
sc:GetScript(\"OnMouseDown\") ~= nil, \
|
||||
sc:GetFrameLevel(), WorldMapFrame:GetFrameLevel(), WorldMapButton:GetFrameLevel()",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(mouse, "the container takes the mouse");
|
||||
assert!(wheel, "...and the wheel, for the same bare area");
|
||||
assert!(has_down, "...and drives the controller from it");
|
||||
assert!(
|
||||
sc_level > map_level,
|
||||
"the container ({sc_level}) must out-level the map window ({map_level}), or the sweep \
|
||||
gives the press to the parent"
|
||||
);
|
||||
assert!(
|
||||
button_level > sc_level,
|
||||
"and 1.12's own surface ({button_level}) stays above the container ({sc_level}), so it \
|
||||
keeps every press it does reach"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -85,6 +85,7 @@ mod spellbook;
|
||||
mod state;
|
||||
mod stdlib_backport;
|
||||
mod string_methods;
|
||||
mod string_trim;
|
||||
mod table_util;
|
||||
mod taxi_map;
|
||||
mod timer;
|
||||
@@ -165,6 +166,7 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
|
||||
g.set("C_Map", c_map)?;
|
||||
g.set("C_MapExplorationInfo", uimap::install_exploration(lua)?)?;
|
||||
uimap::install_globals(lua)?;
|
||||
uimap::install_all_textures(lua)?;
|
||||
taxi_map::install(lua)?;
|
||||
g.set("C_PlayerCache", player_cache::install(lua)?)?;
|
||||
g.set("C_PlayerInfo", player_info::install(lua)?)?;
|
||||
@@ -200,6 +202,8 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
|
||||
tooltip::install(lua)?;
|
||||
stdlib_backport::install(lua)?;
|
||||
string_methods::install(lua)?;
|
||||
// After `string_methods`: `string.trim` is reached as `("x"):trim()` through that metatable.
|
||||
string_trim::install(lua)?;
|
||||
table_util::install(lua)?;
|
||||
// After `item::install`: the mixin's `IsValid` calls `C_Item.DoesItemExist`. Only at *call*
|
||||
// time, as `color_mixin.rs` explains for `CreateColor`, so the order is for the reader rather
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
//! `strtrim` and `string.trim`, which 1.12 has and this client deliberately did not.
|
||||
//!
|
||||
//! `script/stdlib.rs` lists `strtrim` under "NOT here", with the reason: 1.12 has no such global,
|
||||
//! no modified-1.12 UI ships a caller, and "an addon reaching for one of these is reaching for a
|
||||
//! later client's API, and it should find what it would find there: nothing." That reasoning is
|
||||
//! about the **1.12** surface and it still holds there, which is why this is in `compat/` instead
|
||||
//! — on the client those addons are written for, the name is present.
|
||||
//!
|
||||
//! The evidence is a live defect rather than a survey. OpenMapClassic's `Map/Pins.lua:57` is
|
||||
//! `text = strtrim(text)` on the path that stores a dropped map pin, so with the global nil
|
||||
//! *every pin drop raised and no pin was ever saved* — grom, 2026-11-04: *"still can't drop a pin
|
||||
//! like intended."* The rest of the corpus, bare calls only (a library that defines its own under
|
||||
//! its namespace does not count):
|
||||
//!
|
||||
//! | caller | sites |
|
||||
//! |---|---|
|
||||
//! | `OpenMapClassic/Libs/AceConfig-3.0/AceConfigCmd-3.0` | 5, via `string.trim` |
|
||||
//! | `OpenMapClassic/Map/Pins.lua`, `Map/Paint.lua` | 2 |
|
||||
//! | `pfUI/api/ui-widgets.lua:1472` | 1, every keystroke in a search box |
|
||||
//!
|
||||
//! Both spellings, because `AceConfigCmd-3.0.lua:31` takes the member (`local … strtrim =
|
||||
//! string.trim`) while everything else calls the global. The member also gives `("x"):trim()` for
|
||||
//! free through [`super::string_methods`]'s metatable, which is how Ace3 reaches it in later
|
||||
//! versions.
|
||||
//!
|
||||
//! The two-argument form is Blizzard's: `strtrim(s, chars)` strips any character of `chars` from
|
||||
//! both ends, and with no `chars` it strips whitespace. `script/stdlib.rs`'s own TurtleWoW `trim`
|
||||
//! is the one-argument shape and is left exactly as it is — this does not touch it.
|
||||
|
||||
use mlua::Lua;
|
||||
|
||||
/// Written in Lua rather than Rust so the pattern engine is the client's own: a Rust
|
||||
/// `trim_matches` would be a second, subtly different implementation of the same thing, and
|
||||
/// `%s` here must mean what every other pattern in the VM means by it.
|
||||
const STRING_TRIM: &str = r#"
|
||||
function strtrim(s, chars)
|
||||
if s == nil then return nil end
|
||||
s = tostring(s)
|
||||
if chars == nil or chars == "" then
|
||||
-- The shape every corpus caller writes by hand when it defines its own.
|
||||
return (gsub(s, "^%s*(.-)%s*$", "%1"))
|
||||
end
|
||||
-- A character class built from the argument, with every non-alphanumeric escaped so a
|
||||
-- trimmed "]" or "%" is a character and not pattern syntax.
|
||||
local set = "[" .. gsub(chars, "(%W)", "%%%1") .. "]"
|
||||
s = gsub(s, "^" .. set .. "*", "")
|
||||
return (gsub(s, set .. "*$", ""))
|
||||
end
|
||||
|
||||
-- The member form, which is the same function: `strtrim(s)` already takes the string first, so
|
||||
-- `("x "):trim()` and `string.trim("x ")` and `strtrim("x ")` are one call.
|
||||
string.trim = strtrim
|
||||
"#;
|
||||
|
||||
pub(super) fn install(lua: &Lua) -> mlua::Result<()> {
|
||||
lua.load(STRING_TRIM)
|
||||
.set_name("classic_api/string_trim")
|
||||
.exec()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::script::UiScript;
|
||||
|
||||
/// OpenMapClassic's own line, the one that ate every dropped pin.
|
||||
#[test]
|
||||
fn strtrim_strips_whitespace_from_both_ends() {
|
||||
let s = UiScript::new().unwrap();
|
||||
assert_eq!(
|
||||
s.eval::<String>(r#"return strtrim(" a pin note ")"#)
|
||||
.unwrap(),
|
||||
"a pin note"
|
||||
);
|
||||
assert_eq!(s.eval::<String>(r#"return strtrim("")"#).unwrap(), "");
|
||||
assert_eq!(s.eval::<String>(r#"return strtrim(" ")"#).unwrap(), "");
|
||||
// Inner whitespace is not touched, which is what makes it a trim and not a squeeze.
|
||||
assert_eq!(
|
||||
s.eval::<String>(r#"return strtrim(" a b ")"#).unwrap(),
|
||||
"a b"
|
||||
);
|
||||
}
|
||||
|
||||
/// The `chars` form, and that a pattern character in it is a character.
|
||||
#[test]
|
||||
fn the_second_argument_names_the_characters_to_strip() {
|
||||
let s = UiScript::new().unwrap();
|
||||
assert_eq!(
|
||||
s.eval::<String>(r#"return strtrim("xxhelloxx", "x")"#)
|
||||
.unwrap(),
|
||||
"hello"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<String>(r#"return strtrim("%%hi%%", "%")"#)
|
||||
.unwrap(),
|
||||
"hi"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<String>(r#"return strtrim("]]hi]]", "]")"#)
|
||||
.unwrap(),
|
||||
"hi"
|
||||
);
|
||||
}
|
||||
|
||||
/// Both spellings are the one function, which is what `AceConfigCmd-3.0.lua:31` needs when it
|
||||
/// binds `local strtrim = string.trim` and then calls it with the string first.
|
||||
#[test]
|
||||
fn the_global_the_member_and_the_method_are_one_call() {
|
||||
let s = UiScript::new().unwrap();
|
||||
assert!(s.eval::<bool>("return string.trim == strtrim").unwrap());
|
||||
assert_eq!(s.eval::<String>(r#"return (" m "):trim()"#).unwrap(), "m");
|
||||
}
|
||||
}
|
||||
@@ -661,6 +661,60 @@ pub(super) fn install_exploration(lua: &Lua) -> mlua::Result<Table> {
|
||||
Ok(t)
|
||||
}
|
||||
|
||||
/// `BenillaMapTextures(uiMapID)` — every overlay a map has, explored or not, in
|
||||
/// `GetExploredMapTextures`' own shape with an `explored` flag added.
|
||||
///
|
||||
/// Era's API has no such call because Era's client has no use for one: a modern map addon that
|
||||
/// wants to show unexplored ground ships a **table of FileDataIDs** scraped from the retail
|
||||
/// client (OpenMapClassic's `ns.MapReveal`, 41 maps of them) and hands those straight to
|
||||
/// `SetTexture`. Those ids name retail files; this client has none of them, so reveal mode drew
|
||||
/// exactly nothing and "show unexplored areas" was a switch with no effect — measured, the same
|
||||
/// view with the fog `default` and `reveal` is pixel-identical.
|
||||
///
|
||||
/// The overlays themselves are not missing: `WorldMapOverlay.dbc` has every one of them with its
|
||||
/// texture and its rect, and the explored half already draws from exactly this function. All
|
||||
/// that was missing was a way to ask for the other half. Named for this client rather than
|
||||
/// dressed as a `C_` member, as `BenillaMapIDForSelection` is, so no addon mistakes it for an
|
||||
/// API it can expect elsewhere; the port that uses it feature-tests the name.
|
||||
pub(super) fn install_all_textures(lua: &Lua) -> mlua::Result<()> {
|
||||
lua.globals().set(
|
||||
"BenillaMapTextures",
|
||||
lua.create_function(|lua, ui_map_id: Option<u32>| {
|
||||
let model = lua.app_data_ref::<Model>().expect("model app_data");
|
||||
let wm = &model.worldmap;
|
||||
let Some(node) = ui_map_id.and_then(|id| uimap::node(wm, id)) else {
|
||||
return Ok(Value::Nil);
|
||||
};
|
||||
let out = lua.create_table()?;
|
||||
let Some((_, zone)) = views(wm, node) else {
|
||||
return Ok(Value::Table(out));
|
||||
};
|
||||
for o in &zone.overlays {
|
||||
let row = lua.create_table()?;
|
||||
row.set("textureWidth", o.width)?;
|
||||
row.set("textureHeight", o.height)?;
|
||||
row.set("offsetX", o.offset_x)?;
|
||||
row.set("offsetY", o.offset_y)?;
|
||||
let (cols, rows) = (o.width.div_ceil(TILE), o.height.div_ceil(TILE));
|
||||
let files = lua.create_table()?;
|
||||
for n in 1..=(cols * rows) {
|
||||
files.set(n, format!("{}{n}", o.texture))?;
|
||||
}
|
||||
row.set("fileDataIDs", files)?;
|
||||
row.set("isShownByMouseOver", false)?;
|
||||
row.set(
|
||||
"explored",
|
||||
o.explore_bits
|
||||
.iter()
|
||||
.any(|&b| explored_bit(&wm.explored, b)),
|
||||
)?;
|
||||
out.set(out.raw_len() + 1, row)?;
|
||||
}
|
||||
Ok(Value::Table(out))
|
||||
})?,
|
||||
)
|
||||
}
|
||||
|
||||
/// Bit `n` of the `PLAYER_EXPLORED_ZONES_1` bitset, the same read the window's own overlay gate
|
||||
/// makes.
|
||||
fn explored_bit(explored: &[u32], n: u32) -> bool {
|
||||
|
||||
@@ -306,6 +306,12 @@ fn allowed_beyond_1_12() -> HashSet<&'static str> {
|
||||
// `SlashCmdList` outright and would drop entries made before it.
|
||||
"FocusUnit",
|
||||
"ClearFocus",
|
||||
// ClassicAPI's `strtrim` (script/compat/classic_api/string_trim.rs). 1.12 has no such
|
||||
// global and `script/stdlib.rs` still refuses it on the 1.12 surface for that reason;
|
||||
// this build claims ClassicAPI, whose client does have it, and the corpus calls it bare
|
||||
// in eight places. The one that forced it: OpenMapClassic's `Map/Pins.lua:57` trims a
|
||||
// pin's note, so with the global nil every dropped map pin raised and none was stored.
|
||||
"strtrim",
|
||||
// SuperWoW's UnitPosition/CursorPosition (script/compat/superwow.rs): world XYZ, not a
|
||||
// 1.12 verb.
|
||||
"UnitPosition",
|
||||
|
||||
@@ -3372,3 +3372,104 @@ the fog `default` and `reveal` is pixel-indistinguishable — and switching `lod
|
||||
`zoneart` off one at a time changed nothing in a capture either (those take a rebuild the slash
|
||||
command alone does not force). What a capture cannot reproduce is which zones the player has
|
||||
actually explored, and that is the one input the complaint turns on.
|
||||
|
||||
## The map's mouse surface shrank with the zoom, and four things fell through the hole
|
||||
|
||||
grom, 2026-11-04, after the rail tabs came back: *"still can't drop a pin like intended … scroll
|
||||
is better but feels pretty slippery, like scrolls past where I stop scrolling, and my arrow does
|
||||
not stay where it should on scroll … drag on the map is weird, sometimes its panning the map as
|
||||
it should and sometimes its grabbing and moving the whole window, this should only happen on the
|
||||
borders."*
|
||||
|
||||
### One cause behind the drag and the pin: `WorldMapButton` is pinned to the canvas child
|
||||
|
||||
The canvas was built with the container taking no mouse at all, on the stated reasoning that
|
||||
1.12's `WorldMapButton` "covers the same rect and is above this in the tree, so a mouse-enabled
|
||||
container here would never see a press anyway". That holds at rest and stops holding the moment
|
||||
a canvas addon scales the canvas: the button is **inside** the child, so it scales with it.
|
||||
Measured at the world view with OpenMapClassic driving, at grom's own 2560x1440:
|
||||
|
||||
```
|
||||
container SCREEN L=695 R=1396 B=552 T=1020 (701 x 468)
|
||||
child SCREEN L=1242 R=1265 B=926 T=942 (23 x 16) scale 0.033
|
||||
```
|
||||
|
||||
A 23x16 mouse surface in a 701x468 window. Every press on the other 99% reached the next frame
|
||||
that takes the mouse — `WorldMapFrame`, which pfUI's `modules/map.lua:45-47` makes a
|
||||
`RegisterForDrag("LeftButton")` handle for the whole window. So dragging the map moved the
|
||||
window, and whether it did depended on the zoom and where you grabbed; and a click never reached
|
||||
`ProcessCanvasClickHandlers`, which is the only way a pin is ever dropped.
|
||||
|
||||
The container now takes the mouse and the wheel and drives the same controller. **The frame level
|
||||
mattered as much as the mouse bit**: the hit sweep probes one `(strata, level)` plane in link
|
||||
order and a parent is linked before its children (`0x764aa0`, `TargetFrame.lua:33` relies on it),
|
||||
so the container tied with `WorldMapFrame` at level 1 still lost every press to its own parent.
|
||||
It is re-seated to `map:GetFrameLevel() + 1` on every show, because the window is level 0 while
|
||||
the canvas builds and level 1 once `ShowUIPanel` has raised it — a level computed once at build
|
||||
time ties again the first time the map opens.
|
||||
|
||||
Measured after: a drag on the bare map area gives `WorldMapFrame` nothing, the container's
|
||||
`OnMouseDown` once, and the scroll moves.
|
||||
|
||||
### `strtrim` was nil, and that ate every pin
|
||||
|
||||
With the click arriving, dropping a pin still stored nothing. `Map/Pins.lua:57` is
|
||||
`text = strtrim(text)` and `strtrim` was deliberately absent — `script/stdlib.rs` lists it under
|
||||
"NOT here" with the reasoning that an addon reaching for a later client's API should find what it
|
||||
would find there: nothing. That reasoning is about the **1.12** surface and still holds there,
|
||||
which is why the name is now in `compat/classic_api/string_trim.rs` instead: this build sets
|
||||
`CLASSIC_API_VERSION`, and on the client these addons are written for the name exists. Both
|
||||
spellings, because `AceConfigCmd-3.0.lua:31` binds `string.trim` while everything else calls the
|
||||
global; the string metatable then gives `("x"):trim()` for free. Corpus call sites, bare ones
|
||||
only: 5 in OpenMapClassic's AceConfigCmd, 2 in its own map files, 1 in
|
||||
`pfUI/api/ui-widgets.lua:1472` on every keystroke of a search box.
|
||||
|
||||
Pins now store: `pins 0 → 1` on a driven click over land.
|
||||
|
||||
### The zoom's glide
|
||||
|
||||
`SCALE_RATE` was 10 — a 0.7 s half-life, still visibly moving two seconds after the last notch.
|
||||
That was tolerable only while a notch could not accumulate, because the target was never more
|
||||
than one rung away. Now that notches add up, the glide is the thing you feel, so the rate is 24
|
||||
(a 0.29 s half-life). The pan's is untouched: a drag writes the scroll directly and never eases.
|
||||
|
||||
### The player arrow is on the wrong projection
|
||||
|
||||
`WorldMapUnit_Update` places `WorldMapPlayer` at `GetPlayerMapPosition("player")` as a fraction of
|
||||
`WorldMapDetailFrame`, which the canvas pins to the canvas child. Both halves of that check out —
|
||||
the arrow sits at exactly the fraction the game reports (measured: `arrowFrac 0.7351,0.6348` for
|
||||
`GetPlayerMapPosition 0.7351,0.6348`), and the child **is** the host map's rect, confirmed against
|
||||
OpenMap's own coordinates by mapping the view centre through `MapFromWorld` and landing on the
|
||||
container's centre to the pixel.
|
||||
|
||||
What is wrong is which map that fraction is *of*. On a continent or the world, 1.12 measures
|
||||
against Blizzard's `WorldMap\World` and `WorldMapContinent` art — an illustration, not OpenMap's
|
||||
grid — so the arrow lands somewhere plausible and wrong; and over a zone the player is not in,
|
||||
1.12 answers (0, 0) and hides it. `Addon_Ports/openmap_port`'s `Map/StockArrow.lua` places both
|
||||
the anchor and the Model through OpenMap's own chain instead: the player's zone and his position
|
||||
in it (`C_Map.GetPlayerMapPosition`, which takes a map id and does not care what the window has
|
||||
selected) → `WorldFromMap` → `MapFromWorld`. The offsets are scaled by `child / self`, because a
|
||||
`SetPoint` offset is in the **anchored** frame's units and the Model hangs off `WorldMapFrame`
|
||||
while the child is scaled by the zoom — without that the error grows with the zoom, which is the
|
||||
shape of "my arrow does not stay where it should on scroll". Every step is guarded, so a client
|
||||
that cannot answer leaves 1.12's own placement alone. **Not verified offline**: a capture's player
|
||||
has no zone (`GetBestMapForUnit` answers the continent), so this one needs a play test.
|
||||
|
||||
### "Show unexplored areas" had no art to show
|
||||
|
||||
Reveal mode draws the overlays the client has not reported as explored, out of `ns.MapReveal` —
|
||||
a table scraped from the retail client, `"w:h:x:y"` → a list of **FileDataIDs**. Those ids name
|
||||
retail files; this client resolves none of them (`SetTexture(1396762)` → nil), so every reveal
|
||||
overlay asked for a texture that does not exist. Captured with the fog `default` and with
|
||||
`reveal`, the same view came back **pixel-identical** — `compare -metric AE` says 0.
|
||||
|
||||
The overlays are not missing, only their names: `WorldMapOverlay.dbc` carries every one with its
|
||||
texture and rect, and the explored half already draws from exactly that data through
|
||||
`C_MapExplorationInfo.GetExploredMapTextures`. `BenillaMapTextures(uiMapID)` is the same list
|
||||
without the explored filter (benilla-named, as `BenillaMapIDForSelection` is, so no addon mistakes
|
||||
it for an API it can expect elsewhere), and `Map/RevealFromClient.lua` rebuilds `ns.MapReveal`
|
||||
from it with paths in place of ids. One line of `Map/Stitch.lua` had to stop `tonumber`-ing every
|
||||
entry. Measured: Dun Morogh has 20 overlays, 9 unexplored, and `ns.MapReveal[1426]` now carries
|
||||
those 9 as `Interface\WorldMap\DunMorogh\…` paths where it used to carry retail ids. The render
|
||||
is **not** verified — the stitch is built before a capture's setup chunk can run, and the addon's
|
||||
own switch says `/reload required (overlays built on map open)`.
|
||||
|
||||
Reference in New Issue
Block a user