diff --git a/crates/benilla-app/assets/ui/MapCanvas.lua b/crates/benilla-app/assets/ui/MapCanvas.lua index 84fb06ec..f0a2d1cd 100644 --- a/crates/benilla-app/assets/ui/MapCanvas.lua +++ b/crates/benilla-app/assets/ui/MapCanvas.lua @@ -706,6 +706,88 @@ function MapCanvasScrollControllerMixin:ResetZoom() self:InstantPanAndZoom(scale, 0.5, 0.5) end +-------------------------------------------------------------------------------- +-- The stock art, at a canvas size that is not 1.12's +-- +-- 1.12's `WorldMapDetailFrame` is always 1002x668, so everything drawn into it is placed in +-- absolute pixels against that: twelve 256x256 tiles anchor-chained from its TOPLEFT (1024x768 of +-- art, the overhang clipped), each exploration overlay at the offset and size +-- `WorldMapOverlay.dbc` gives it, and the hover highlight likewise. The canvas breaks that +-- assumption the moment it sizes the frame to anything else — and it does, every time +-- `OnCanvasSizeChanged` has no art layer to read and falls back to the container's own rect. +-- +-- Measured in grom's 2026-11-02 run, through `WOW_MAP_TRACE=1`: +-- +-- detail 1431x954 @scale 0.70 | 1 256x256 @(10,1122) | 2 256x256 @(266,1122) | ... +-- +-- A 1024x768 sheet of art pinned to the top-left of a 1431x954 frame: it covered 72% of the width +-- and 80% of the height and the rest of the map drew **nothing**, which is the grey he reported, +-- with the art magnified relative to what was left. +-- +-- So the art is rescaled to whatever size the frame has, by the same factors on each axis. The +-- tiles need only their size set, because 1.12's XML chains them TOPRIGHT-to-TOPLEFT and +-- BOTTOMLEFT-to-TOPLEFT — resize one and the grid follows. The overlays and the highlight carry +-- absolute offsets, so those are scaled from the values the stock repaint last wrote, kept per +-- texture so a second pass cannot compound. +-------------------------------------------------------------------------------- + +local DETAIL_BASE_W, DETAIL_BASE_H = 1002, 668 +local DETAIL_TILE, DETAIL_TILES = 256, 12 + +-- Base geometry per overlay texture, as `WorldMapFrame_Update` wrote it: [texture] = {x,y,w,h}. +local overlayBase = {} + +-- `w`,`h` are the size the detail frame is ABOUT to have, passed by the caller that just set it. +-- Reading the frame instead would answer the old size: the detail frame is `SetAllPoints` to the +-- canvas, so its own size is derived, and a derived size does not resolve until the layout pass +-- — measured, a child answered `0x0` immediately after its parent was resized. Without them the +-- frame's current size is read, which is right for a caller that is not resizing it (the overlay +-- capture after a repaint). +local function ScaleDetailArt(w, h) + local d = WorldMapDetailFrame + if not d then + return + end + w = w or d:GetWidth() + h = h or d:GetHeight() + if not w or not h or w <= 0 or h <= 0 then + return + end + local sx, sy = w / DETAIL_BASE_W, h / DETAIL_BASE_H + for i = 1, DETAIL_TILES do + local tile = _G["WorldMapDetailTile" .. i] + if tile then + tile:SetWidth(DETAIL_TILE * sx) + tile:SetHeight(DETAIL_TILE * sy) + end + end + for tex, base in pairs(overlayBase) do + tex:SetWidth(base.w * sx) + tex:SetHeight(base.h * sy) + tex:ClearAllPoints() + tex:SetPoint("TOPLEFT", WorldMapDetailFrame, "TOPLEFT", base.x * sx, base.y * sy) + end +end + +-- After the stock repaint: every overlay it just placed is at its 1002x668 offset and size, which +-- is the only moment those base values are readable. Captured, then scaled. +local function CaptureOverlays() + for tex in pairs(overlayBase) do + overlayBase[tex] = nil + end + local n = NUM_WORLDMAP_OVERLAYS or 0 + for i = 1, n do + local tex = _G["WorldMapOverlay" .. i] + if tex and tex:IsShown() then + local _, _, _, x, y = tex:GetPoint() + if x then + overlayBase[tex] = { x = x, y = y, w = tex:GetWidth(), h = tex:GetHeight() } + end + end + end + ScaleDetailArt() +end + -- Called when the container or the canvas changes size, and on the first show: the canvas is -- sized from the map's own art layer, the zoom ladder rebuilt and the view reset. function MapCanvasScrollControllerMixin:OnCanvasSizeChanged() @@ -729,6 +811,10 @@ function MapCanvasScrollControllerMixin:OnCanvasSizeChanged() end child:SetWidth(w) child:SetHeight(h) + -- The stock art is placed in 1002x668 pixels; this is where the frame holding it stops being + -- that size (see "The stock art, at a canvas size that is not 1.12's"). The size just set is + -- passed in, because the detail frame will not answer it until the layout pass. + ScaleDetailArt(w, h) self:CreateZoomLevels() self:CalculateScrollExtents() self.currentScrollX, self.currentScrollY = self:ClampScrollToExtents( @@ -1562,6 +1648,27 @@ local function BuildCanvas() detail:SetAllPoints(canvas) button:SetParent(canvas) + -- The stock repaint places the exploration overlays in 1002x668 pixels and is the only moment + -- those base offsets are readable; after it, they are scaled to whatever size the canvas has + -- given the detail frame (see "The stock art, at a canvas size that is not 1.12's"). + if WorldMapFrame_Update then + hooksecurefunc("WorldMapFrame_Update", CaptureOverlays) + end + CaptureOverlays() + + -- The container resizing IS a canvas size change, and until now nothing said so: the canvas + -- was re-sized only when the map changed (`SetMapID`, `SyncFromSelection`), so a window + -- resized while the map stayed put left the canvas at the old size. openmap resizes this + -- container explicitly, which is the case that reached grom. + -- + -- Only an explicit `SetWidth`/`SetHeight` is covered, because that is all this client fires + -- `OnSizeChanged` for — measured: a child `SetAllPoints` to a parent that is then resized + -- gets no event at all. That is why the art is rescaled inside `OnCanvasSizeChanged` rather + -- than hung off the detail frame's own resize, which would never run. + sc:SetScript("OnSizeChanged", function(self) + self:OnCanvasSizeChanged() + end) + -- Everything the button carries is map content except one frame: `WorldMapFrameAreaFrame`, -- the hovered-zone name. 1.12's XML hangs it off the mouse surface (TOP, 400x128) because -- that is what covers the art, but it is chrome — it names what the cursor is over and sits