Files
moon-well-ui/data/addon/ForeverUI/WorldMapZoom.lua
T
Adrestie 5842eb7a2b ForeverUI : code en anglais, commentaires resserres, code mort retire
Traduction
- Identifiants en anglais dans tout le module : addon, ecrans d'accueil,
  outils Python, banc d'essai et patcheur C#.
- Fichiers au nom francais renommes (Gabarits.lua -> Templates.lua,
  Textes_*.lua -> Texts_*.lua, deployer.py -> deploy.py,
  tools/patcheur -> tools/patcher, etc.).
- Commandes /fui et options de deploy.py en anglais (--check, --server,
  --remove-dbc).
- Cles des variables sauvegardees et de data/dbc/statistics.json
  traduites, sans reprise des anciennes cles.
- API de la micro-barre : ForeverUI.AddMicroButton(def) avec les champs
  name, atlasSet, after, tooltip, onClick, ready, et
  ForeverUI.UpdateMicro(name, isOpen).
- Commentaires reecrits en anglais, courts, sans dates ni historique ;
  les fonctions au nom peu parlant et leurs parametres sont expliques.

Code mort
- Exports, fonctions et variables jamais lus retires.
- Retires a la demande des modules : ForeverUI.PvPJauge, les exports
  FondEntete / TuilerEntete / Polices de TradeSkill, l'art legacy-up-c60,
  honorfill(half)blue et inv_misc_book_09, leurs entrees d'atlas et les
  parties d'outils correspondantes.

Verifications : liaisons des noms comparees avant et apres, banc vert,
chemins de textures inchanges, generateurs reproduisant les fichiers,
patcheur identique octet pour octet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 09:15:52 +02:00

379 lines
12 KiB
Lua

-- World map zoom: the mouse wheel scales the map toward the cursor (x 1.25 per step, 1 to 4)
-- and a left-button drag pans the zoomed map; a click without drag keeps its meaning.
-- Camelot zooms through map art layers (mapcanvas_scrollcontainermixin.lua), which 3.3.5
-- lacks. The zoom returns to 1 when the map changes, changes mode or closes.
local W = ForeverUI.WorldMap
local Z = { z = 1, cx = 0.5, cy = 0.5, active = false, STEP = 1.25, MAX = 4, THRESHOLD = 4 }
W.zoom = Z
local CARD_W, CARD_H = 1002, 668
local TILE, COLUMNS, ROWS = 256, 4, 3
local function tile(k)
return _G["WorldMapDetailTile" .. k]
end
local function ready()
return W.built and W.canvas and WorldMapDetailFrame and WorldMapButton and WORLDMAP_SETTINGS
end
-- The view: the canvas rectangle in WorldMapDetailFrame units (WorldMapButton has the same
-- scale and corner), origin at the map's top left
local function viewSize()
local c, d = W.canvas, WorldMapDetailFrame
local k = c:GetEffectiveScale() / d:GetEffectiveScale()
return c:GetWidth() * k, c:GetHeight() * k
end
local function view()
local vw, vh = viewSize()
local x, y = Z.cx * CARD_W, Z.cy * CARD_H
return x - vw / 2, y - vh / 2, x + vw / 2, y + vh / 2
end
Z.view = view
-- Keep the center where the view stays inside the map
local function clampView()
local vw, vh = viewSize()
local halfX, halfY = vw / 2 / CARD_W, vh / 2 / CARD_H
if halfX >= 0.5 then Z.cx = 0.5 else Z.cx = math.max(halfX, math.min(1 - halfX, Z.cx)) end
if halfY >= 0.5 then Z.cy = 0.5 else Z.cy = math.max(halfY, math.min(1 - halfY, Z.cy)) end
end
-- ---------------------------------------------------------------- Tiles
-- 3.3.5 clips nothing outside a ScrollFrame and client frames are never reparented, so each
-- tile is cropped to the view: its visible part, at its place, with matching tex coords
local function cropTexture()
local d = WorldMapDetailFrame
local x0, y0, x1, y1 = view()
for rowLine = 1, ROWS do
for column = 1, COLUMNS do
local t = tile((rowLine - 1) * COLUMNS + column)
if t then
local tx, ty = (column - 1) * TILE, (rowLine - 1) * TILE
local ix0, iy0 = math.max(tx, x0), math.max(ty, y0)
local ix1 = math.min(tx + TILE, CARD_W, x1)
local iy1 = math.min(ty + TILE, CARD_H, y1)
t:ClearAllPoints()
if ix1 - ix0 < 0.01 or iy1 - iy0 < 0.01 then
t:SetPoint("TOPLEFT", d, "TOPLEFT", tx, -ty)
t:SetWidth(1)
t:SetHeight(1)
t:SetAlpha(0)
else
t:SetPoint("TOPLEFT", d, "TOPLEFT", ix0, -iy0)
t:SetWidth(ix1 - ix0)
t:SetHeight(iy1 - iy0)
t:SetTexCoord((ix0 - tx) / TILE, (ix1 - tx) / TILE, (iy0 - ty) / TILE, (iy1 - ty) / TILE)
t:SetAlpha(1)
end
end
end
end
end
-- Tile anchors from WorldMapFrame.xml (541-636): the first at the corner, each right of the
-- previous one, each row under the first tile of the row above; then the small-window crop
-- (WorldMap.lua)
local function rechainTiles()
for k = 1, COLUMNS * ROWS do
local t = tile(k)
if t then
t:ClearAllPoints()
t:SetAlpha(1)
if k == 1 then
t:SetPoint("TOPLEFT", WorldMapDetailFrame, "TOPLEFT", 0, 0)
elseif (k - 1) % COLUMNS == 0 then
t:SetPoint("TOPLEFT", tile(k - COLUMNS), "BOTTOMLEFT", 0, 0)
else
t:SetPoint("TOPLEFT", tile(k - 1), "TOPRIGHT", 0, 0)
end
end
end
if W.cropTiles then W.cropTiles(true) end
end
-- --------------------------------------------------------------- Markers
-- A marker out of the view fades out and releases the mouse (invisible, it would still
-- steal hover and clicks from the quest log or the frame); back in view, it gets its alpha
-- and mouse back
local function turnOff(f)
f.foreverZoomAlpha = f:GetAlpha()
f.foreverZoomMouse = f.IsMouseEnabled and f:IsMouseEnabled() and true or false
f:SetAlpha(0)
if f.foreverZoomMouse then f:EnableMouse(false) end
end
local function restore(f)
f:SetAlpha(f.foreverZoomAlpha)
if f.foreverZoomMouse then f:EnableMouse(true) end
f.foreverZoomAlpha, f.foreverZoomMouse = nil, nil
end
-- Fade out the markers outside the view, restore those back inside
local function sortBy()
local c = W.canvas
local e = c:GetEffectiveScale()
local left, down, right, top = c:GetLeft(), c:GetBottom(), c:GetRight(), c:GetTop()
if not (left and down and right and top) then return end
left, down, right, top = left * e, down * e, right * e, top * e
for _, parent in ipairs({ WorldMapButton, WorldMapPOIFrame }) do
if parent then
for _, f in ipairs({ parent:GetChildren() }) do
if f ~= WorldMapFrameAreaFrame and f:IsShown() then
local x, y = f:GetCenter()
local fe = f:GetEffectiveScale()
local inside = not x or (x * fe >= left and x * fe <= right and y * fe >= down and y * fe <= top)
if inside then
if f.foreverZoomAlpha then restore(f) end
elseif not f.foreverZoomAlpha then
turnOff(f)
end
end
end
end
end
end
local function restoreChildren()
for _, parent in ipairs({ WorldMapButton, WorldMapPOIFrame }) do
if parent then
for _, f in ipairs({ parent:GetChildren() }) do
if f.foreverZoomAlpha then restore(f) end
end
end
end
end
-- ------------------------------------------------------------- Apply
-- Current map key: a map change resets the zoom to 1
function Z.mapKey()
return tostring(GetCurrentMapAreaID and GetCurrentMapAreaID()) .. ":" ..
tostring(GetCurrentMapDungeonLevel and GetCurrentMapDungeonLevel())
end
-- Scales of the zoomed frames: { frame, zoomed scale, normal scale }. Markers, party and
-- quest numbers are placed in WorldMapButton / WorldMapPOIFrame units, so they follow.
local function frameScales()
local base = WORLDMAP_SETTINGS.size
return {
{ WorldMapDetailFrame, base * Z.z, base },
{ WorldMapButton, base * Z.z, base },
{ WorldMapBlobFrame, base * Z.z, base },
{ WorldMapPOIFrame, Z.z, 1 },
{ WorldMapFrameAreaFrame, (Z.areaScale or 1) / Z.z, Z.areaScale or 1 },
}
end
-- The quest blob (WorldMapBlobFrame) is drawn by the engine in one piece and would overflow,
-- so it hides while zoomed; WorldMapButton's hit rect is limited to the view.
function Z.apply()
local d = WorldMapDetailFrame
if not Z.active then
Z.active = true
Z.points = {}
for i = 1, d:GetNumPoints() do
Z.points[i] = { d:GetPoint(i) }
end
Z.areaScale = WorldMapFrameAreaFrame and WorldMapFrameAreaFrame:GetScale() or 1
Z.key = Z.mapKey()
end
for _, e in ipairs(frameScales()) do
if e[1] then e[1]:SetScale(e[2]) end
end
clampView()
d:ClearAllPoints()
d:SetPoint("CENTER", W.canvas, "CENTER", (0.5 - Z.cx) * CARD_W, (Z.cy - 0.5) * CARD_H)
if WorldMapBlobFrame then
WorldMapBlobFrame:SetAlpha(0)
if WorldMapBlobFrame_CalculateHitTranslations then WorldMapBlobFrame_CalculateHitTranslations() end
end
local a = WorldMapFrameAreaFrame
if a then
a:ClearAllPoints()
a:SetPoint("TOP", W.canvas, "TOP", 0, -10)
end
local x0, y0, x1, y1 = view()
WorldMapButton:SetHitRectInsets(math.max(0, x0), math.max(0, CARD_W - x1),
math.max(0, y0), math.max(0, CARD_H - y1))
cropTexture()
sortBy()
end
-- Back to the whole map. After a mode change, the client and WorldMap.lua have just set
-- their own scales and anchors: only frames still carrying our scale are restored.
function Z.reset(isModeChange)
if not Z.active then
Z.z = 1
return
end
local zoomed = frameScales()
Z.z = 1
local restore = frameScales()
for i, e in ipairs(zoomed) do
if e[1] and math.abs(e[1]:GetScale() - e[2]) < 1e-6 then
e[1]:SetScale(restore[i][3])
end
end
Z.active = false
local d = WorldMapDetailFrame
if not isModeChange and Z.points then
d:ClearAllPoints()
for _, p in ipairs(Z.points) do
d:SetPoint(p[1], p[2], p[3], p[4], p[5])
end
end
if WorldMapBlobFrame then
WorldMapBlobFrame:SetAlpha(1)
if WorldMapBlobFrame_CalculateHitTranslations then WorldMapBlobFrame_CalculateHitTranslations() end
end
local a = WorldMapFrameAreaFrame
if a then
a:ClearAllPoints()
a:SetPoint("TOP", WorldMapButton, "TOP", 0, -10)
end
WorldMapButton:SetHitRectInsets(0, 0, 0, 0)
WorldMapButton:EnableMouse(true)
Z.pressPoint, Z.dragging = nil, false
rechainTiles()
restoreChildren()
Z.cx, Z.cy = 0.5, 0.5
end
-- ------------------------------------------------------------- Mouse wheel
-- Map point under the cursor, and the cursor offset from the canvas center in screen pixels
local function pointUnderCursor()
local x, y = GetCursorPosition()
local c = W.canvas
local cx, cy = c:GetCenter()
if not cx then return nil end
local ce = c:GetEffectiveScale()
local dx, dy = x - cx * ce, y - cy * ce
local de = WorldMapDetailFrame:GetEffectiveScale()
return Z.cx + dx / (CARD_W * de), Z.cy - dy / (CARD_H * de), dx, dy, de
end
function Z.wheel(direction)
if not ready() then return end
local old = Z.z
local new = direction > 0 and old * Z.STEP or old / Z.STEP
if new > Z.MAX then new = Z.MAX end
if new < 1.0001 then new = 1 end
if new == old then return end
if new == 1 then
Z.reset(false)
return
end
if not Z.active then Z.cx, Z.cy = 0.5, 0.5 end
-- Keep the point under the cursor in place (camelot: SetPanTarget)
local fx, fy, dx, dy, de = pointUnderCursor()
Z.z = new
if fx then
local de2 = de * new / old
Z.cx = fx - dx / (CARD_W * de2)
Z.cy = fy + dy / (CARD_H * de2)
end
Z.apply()
end
-- --------------------------------------------------------------- Watcher
local mapKey = Z.mapKey
-- Child of the map frame, so it only runs while the map is open
local watcher = CreateFrame("Frame", nil, WorldMapFrame)
watcher:SetScript("OnUpdate", function()
if not (Z.active and ready()) then
Z.key = ready() and mapKey() or nil
return
end
if mapKey() ~= Z.key then
Z.reset(false)
Z.key = mapKey()
return
end
-- Drag: past THRESHOLD pixels the map follows the cursor. WorldMapButton_OnClick navigates
-- on mouse up, so WorldMapButton releases the mouse until the button is up. The client code
-- is not wrapped: it closes dropdowns and would taint their state if called from the addon.
local pressPoint = Z.pressPoint
if pressPoint then
local x, y = GetCursorPosition()
if not IsMouseButtonDown("LeftButton") then
Z.pressPoint = nil
if Z.dragging then
Z.dragging = false
WorldMapButton:EnableMouse(true)
end
else
if not Z.dragging and math.abs(x - pressPoint.x) + math.abs(y - pressPoint.y) > Z.THRESHOLD then
Z.dragging = true
WorldMapButton:EnableMouse(false)
end
if Z.dragging then
local de = WorldMapDetailFrame:GetEffectiveScale()
Z.cx = pressPoint.cx - (x - pressPoint.x) / (CARD_W * de)
Z.cy = pressPoint.cy + (y - pressPoint.y) / (CARD_H * de)
Z.apply()
return
end
end
end
sortBy()
end)
Z.watcher = watcher
-- ------------------------------------------------------------ Hooks
if WorldMapButton then
WorldMapButton:EnableMouseWheel(true)
WorldMapButton:SetScript("OnMouseWheel", function(_, direction)
Z.wheel(direction)
end)
WorldMapButton:HookScript("OnMouseDown", function(_, button)
if button == "LeftButton" and Z.active then
local x, y = GetCursorPosition()
Z.pressPoint = { x = x, y = y, cx = Z.cx, cy = Z.cy }
Z.dragging = false
end
end)
-- Player arrow: WorldMapButton_OnUpdate places it at the WotLK scale (position x
-- WORLDMAP_SETTINGS.size); place it again x zoom, or hide it out of the view
WorldMapButton:HookScript("OnUpdate", function()
if not Z.active then return end
local x, y = GetPlayerMapPosition("player")
if not x or (x == 0 and y == 0) then return end
local px, py = x * CARD_W, y * CARD_H
local x0, y0, x1, y1 = view()
if px < x0 or px > x1 or py < y0 or py > y1 then
if ShowWorldMapArrowFrame then ShowWorldMapArrowFrame(nil) end
elseif PositionWorldMapArrowFrame then
local s = WORLDMAP_SETTINGS.size * Z.z
PositionWorldMapArrowFrame("CENTER", "WorldMapDetailFrame", "TOPLEFT", px * s, -py * s)
end
end)
end
if WorldMapFrame then
WorldMapFrame:HookScript("OnHide", function() Z.reset(false) end)
end
-- WorldMapFrame_Update reloads the tiles (WORLD_MAP_UPDATE): crop them again while zoomed
if WorldMapFrame_Update then
hooksecurefunc("WorldMapFrame_Update", function()
if Z.active then cropTexture() end
end)
end
-- Mode or view change: back to 1, without undoing what the client has just set
for _, name in ipairs({ "WorldMap_ToggleSizeDown", "WorldMap_ToggleSizeUp", "WorldMapFrame_SetMiniMode",
"WorldMapFrame_SetQuestMapView", "WorldMapFrame_SetFullMapView" }) do
if _G[name] then
hooksecurefunc(name, function() Z.reset(true) end)
end
end