From 7e9de9205333dd0daf6fecc2e7cf210402efb3e3 Mon Sep 17 00:00:00 2001 From: Lance Date: Sun, 24 May 2026 18:35:25 -0700 Subject: [PATCH 1/9] feat(minimap): clickable friends tooltip + row cap - Left-click whispers, Alt+Left invites; works for guild, BNet, and character friends (uses full name-realm or BNet account). - Tooltip stays open while cursor moves from button to row via a short hide grace period. - Replace hardcoded per-section caps with `friendsMaxRows` setting (0 = no cap), exposed as a new "Friends List Cap" slider. --- EllesmereUIMinimap/EUI_Minimap_Options.lua | 494 +++++++++++---------- EllesmereUIMinimap/EllesmereUIMinimap.lua | 256 +++++++---- 2 files changed, 431 insertions(+), 319 deletions(-) diff --git a/EllesmereUIMinimap/EUI_Minimap_Options.lua b/EllesmereUIMinimap/EUI_Minimap_Options.lua index dd3b6042..3b00075e 100644 --- a/EllesmereUIMinimap/EUI_Minimap_Options.lua +++ b/EllesmereUIMinimap/EUI_Minimap_Options.lua @@ -81,37 +81,37 @@ initFrame:SetScript("OnEvent", function(self) local PP = EllesmereUI.PP local function BuildVisibilityRow(W, parent, y, getCfg, refreshFn) local visRow, visH = W:DualRow(parent, y, - { type="dropdown", text="Visibility", - values = EllesmereUI.VIS_VALUES, - order = EllesmereUI.VIS_ORDER, - getValue=function() - local c = getCfg(); if not c then return "always" end - return c.visibility or "always" - end, - setValue=function(v) - local c = getCfg(); if not c then return end - c.visibility = v - if refreshFn then refreshFn() end - if _G._EBS_UpdateVisibility then _G._EBS_UpdateVisibility() end - EllesmereUI:RefreshPage() - end }, - { type="dropdown", text="Visibility Options", - values={ __placeholder = "..." }, order={ "__placeholder" }, - getValue=function() return "__placeholder" end, - setValue=function() end }) + { type="dropdown", text="Visibility", + values = EllesmereUI.VIS_VALUES, + order = EllesmereUI.VIS_ORDER, + getValue=function() + local c = getCfg(); if not c then return "always" end + return c.visibility or "always" + end, + setValue=function(v) + local c = getCfg(); if not c then return end + c.visibility = v + if refreshFn then refreshFn() end + if _G._EBS_UpdateVisibility then _G._EBS_UpdateVisibility() end + EllesmereUI:RefreshPage() + end }, + { type="dropdown", text="Visibility Options", + values={ __placeholder = "..." }, order={ "__placeholder" }, + getValue=function() return "__placeholder" end, + setValue=function() end }) do local rightRgn = visRow._rightRegion if rightRgn._control then rightRgn._control:Hide() end local cbDD, cbDDRefresh = EllesmereUI.BuildVisOptsCBDropdown( - rightRgn, 210, rightRgn:GetFrameLevel() + 2, - EllesmereUI.VIS_OPT_ITEMS, - function(k) local c = getCfg(); return c and c[k] or false end, - function(k, v) - local c = getCfg(); if not c then return end - c[k] = v - if _G._EBS_UpdateVisibility then _G._EBS_UpdateVisibility() end - EllesmereUI:RefreshPage() - end) + rightRgn, 210, rightRgn:GetFrameLevel() + 2, + EllesmereUI.VIS_OPT_ITEMS, + function(k) local c = getCfg(); return c and c[k] or false end, + function(k, v) + local c = getCfg(); if not c then return end + c[k] = v + if _G._EBS_UpdateVisibility then _G._EBS_UpdateVisibility() end + EllesmereUI:RefreshPage() + end) PP.Point(cbDD, "RIGHT", rightRgn, "RIGHT", -20, 0) rightRgn._control = cbDD rightRgn._lastInline = nil @@ -158,10 +158,10 @@ initFrame:SetScript("OnEvent", function(self) return ar, ag, ab end, setValue = function() end, - -- Flag name stays `useClassColor` for backwards compat with - -- users who already have it stamped in their SavedVariables. - -- Only the color resolution changes -- the flag now means - -- "use live accent" rather than "use class color". + -- Flag name stays `useClassColor` for backwards compat with + -- users who already have it stamped in their SavedVariables. + -- Only the color resolution changes -- the flag now means + -- "use live accent" rather than "use class color". onClick = function() local c = getCfg(); if not c then return end c.useClassColor = true @@ -192,64 +192,64 @@ initFrame:SetScript("OnEvent", function(self) _, h = W:SectionHeader(parent, SECTION_MINIMAP, y); y = y - h _, h = W:DualRow(parent, y, - { type="slider", text="Size", min=100, max=600, step=5, - getValue=function() local m = MinimapDB(); return m and m.mapSize or 140 end, - setValue=function(v) - local m = MinimapDB(); if not m then return end - m.mapSize = v - -- Cover the map render during drag to mask the zoom-nudge blink. - -- Borders, buttons, etc. remain visible above the overlay. - local minimap = _G.Minimap - if minimap then - if not minimap._dragOverlay then - local ov = minimap:CreateTexture(nil, "BACKGROUND", nil, 7) - ov:SetAllPoints(minimap) - minimap._dragOverlay = ov - end - local shape = m.shape or "square" - if shape == "circle" or shape == "textured_circle" then - minimap._dragOverlay:SetTexture("Interface\\Common\\CommonMaskCircle") - minimap._dragOverlay:SetVertexColor(0, 0, 0, 1) - else - minimap._dragOverlay:SetColorTexture(0, 0, 0, 1) - end - minimap._dragOverlay:Show() - end - RefreshMinimap() - if not _G._EBS_SizeDragTimer then - _G._EBS_SizeDragTimer = C_Timer.NewTimer(0, function() end) - end - _G._EBS_SizeDragTimer:Cancel() - _G._EBS_SizeDragTimer = C_Timer.NewTimer(0.15, function() - if minimap and minimap._dragOverlay then - minimap._dragOverlay:Hide() - end - end) - end }, - { type="multiSwatch", text="Accent Color", - swatches = MakeBorderSwatch(MinimapDB, RefreshMinimap) }) + { type="slider", text="Size", min=100, max=600, step=5, + getValue=function() local m = MinimapDB(); return m and m.mapSize or 140 end, + setValue=function(v) + local m = MinimapDB(); if not m then return end + m.mapSize = v + -- Cover the map render during drag to mask the zoom-nudge blink. + -- Borders, buttons, etc. remain visible above the overlay. + local minimap = _G.Minimap + if minimap then + if not minimap._dragOverlay then + local ov = minimap:CreateTexture(nil, "BACKGROUND", nil, 7) + ov:SetAllPoints(minimap) + minimap._dragOverlay = ov + end + local shape = m.shape or "square" + if shape == "circle" or shape == "textured_circle" then + minimap._dragOverlay:SetTexture("Interface\\Common\\CommonMaskCircle") + minimap._dragOverlay:SetVertexColor(0, 0, 0, 1) + else + minimap._dragOverlay:SetColorTexture(0, 0, 0, 1) + end + minimap._dragOverlay:Show() + end + RefreshMinimap() + if not _G._EBS_SizeDragTimer then + _G._EBS_SizeDragTimer = C_Timer.NewTimer(0, function() end) + end + _G._EBS_SizeDragTimer:Cancel() + _G._EBS_SizeDragTimer = C_Timer.NewTimer(0.15, function() + if minimap and minimap._dragOverlay then + minimap._dragOverlay:Hide() + end + end) + end }, + { type="multiSwatch", text="Accent Color", + swatches = MakeBorderSwatch(MinimapDB, RefreshMinimap) }) y = y - h h = BuildVisibilityRow(W, parent, y, MinimapDB, RefreshMinimap); y = y - h -- Shape | Border Thickness _, h = W:DualRow(parent, y, - { type="dropdown", text="Shape", - values = { square = "Square", circle = "Circle", textured_circle = "Textured Circle" }, - order = { "square", "circle", "textured_circle" }, - getValue=function() local m = MinimapDB(); return m and m.shape or "square" end, - setValue=function(v) - local m = MinimapDB(); if not m then return end - m.shape = v - RefreshMinimap() - end }, - { type="slider", text="Border Thickness", min=0, max=5, step=1, - getValue=function() local m = MinimapDB(); return m and m.borderSize or 1 end, - setValue=function(v) - local m = MinimapDB(); if not m then return end - m.borderSize = v - RefreshMinimap() - end } + { type="dropdown", text="Shape", + values = { square = "Square", circle = "Circle", textured_circle = "Textured Circle" }, + order = { "square", "circle", "textured_circle" }, + getValue=function() local m = MinimapDB(); return m and m.shape or "square" end, + setValue=function(v) + local m = MinimapDB(); if not m then return end + m.shape = v + RefreshMinimap() + end }, + { type="slider", text="Border Thickness", min=0, max=5, step=1, + getValue=function() local m = MinimapDB(); return m and m.borderSize or 1 end, + setValue=function(v) + local m = MinimapDB(); if not m then return end + m.borderSize = v + RefreshMinimap() + end } ); y = y - h y = y - 10 @@ -260,18 +260,18 @@ initFrame:SetScript("OnEvent", function(self) -- Ungroup Minimap Buttons | In-Group Button Size local ungroupRow ungroupRow, h = W:DualRow(parent, y, - { type="dropdown", text="Ungroup Minimap Buttons", - values = { __placeholder = "..." }, order = { "__placeholder" }, - getValue = function() return "__placeholder" end, - setValue = function() end }, - { type="slider", text="In-Group Button Size", min=14, max=40, step=1, - tooltip="Size of addon minimap buttons in the flyout grid", - getValue=function() local m = MinimapDB(); return m and m.addonBtnSize or 24 end, - setValue=function(v) - local m = MinimapDB(); if not m then return end - m.addonBtnSize = v - RefreshMinimap() - end } + { type="dropdown", text="Ungroup Minimap Buttons", + values = { __placeholder = "..." }, order = { "__placeholder" }, + getValue = function() return "__placeholder" end, + setValue = function() end }, + { type="slider", text="In-Group Button Size", min=14, max=40, step=1, + tooltip="Size of addon minimap buttons in the flyout grid", + getValue=function() local m = MinimapDB(); return m and m.addonBtnSize or 24 end, + setValue=function(v) + local m = MinimapDB(); if not m then return end + m.addonBtnSize = v + RefreshMinimap() + end } ); y = y - h -- Replace placeholder dropdown with checkbox dropdown @@ -296,26 +296,26 @@ initFrame:SetScript("OnEvent", function(self) end local cbDD, cbDDRefresh = EllesmereUI.BuildVisOptsCBDropdown( - leftRgn, 210, leftRgn:GetFrameLevel() + 2, - GetUngroupItems(), - function(k) - local m = MinimapDB(); if not m then return false end - return m.ungroupedButtons and m.ungroupedButtons[k] and true or false - end, - function(k, v) - local m = MinimapDB(); if not m then return end - if not m.ungroupedButtons then m.ungroupedButtons = {} end - if v then - local maxOrder = 0 - for _, ord in pairs(m.ungroupedButtons) do - if type(ord) == "number" and ord > maxOrder then maxOrder = ord end + leftRgn, 210, leftRgn:GetFrameLevel() + 2, + GetUngroupItems(), + function(k) + local m = MinimapDB(); if not m then return false end + return m.ungroupedButtons and m.ungroupedButtons[k] and true or false + end, + function(k, v) + local m = MinimapDB(); if not m then return end + if not m.ungroupedButtons then m.ungroupedButtons = {} end + if v then + local maxOrder = 0 + for _, ord in pairs(m.ungroupedButtons) do + if type(ord) == "number" and ord > maxOrder then maxOrder = ord end + end + m.ungroupedButtons[k] = maxOrder + 1 + else + m.ungroupedButtons[k] = nil end - m.ungroupedButtons[k] = maxOrder + 1 - else - m.ungroupedButtons[k] = nil - end - FullRebuildMinimap() - end) + FullRebuildMinimap() + end) local PP = EllesmereUI.PP PP.Point(cbDD, "RIGHT", leftRgn, "RIGHT", -20, 0) leftRgn._control = cbDD @@ -326,23 +326,23 @@ initFrame:SetScript("OnEvent", function(self) -- Interactable Button Size | Outer-Group MM Button Size (toggle + cog) local customBtnRow customBtnRow, h = W:DualRow(parent, y, - { type="slider", text="Interactable Button Size", min=16, max=40, step=1, - tooltip="Size of mail, calendar, tracking, and minimap button group toggle", - getValue=function() local m = MinimapDB(); return m and m.interactableBtnSize or 21 end, - setValue=function(v) - local m = MinimapDB(); if not m then return end - m.interactableBtnSize = v - RefreshMinimap() - end }, - { type="toggle", text="Outer-Group MM Button Size", - tooltip="Override the size of ungrouped minimap buttons independently from the interactable button size.", - getValue=function() local m = MinimapDB(); return m and m.customBtnSizeEnabled end, - setValue=function(v) - local m = MinimapDB(); if not m then return end - m.customBtnSizeEnabled = v - RefreshMinimap() - EllesmereUI:RefreshPage() - end } + { type="slider", text="Interactable Button Size", min=16, max=40, step=1, + tooltip="Size of mail, calendar, tracking, and minimap button group toggle", + getValue=function() local m = MinimapDB(); return m and m.interactableBtnSize or 21 end, + setValue=function(v) + local m = MinimapDB(); if not m then return end + m.interactableBtnSize = v + RefreshMinimap() + end }, + { type="toggle", text="Outer-Group MM Button Size", + tooltip="Override the size of ungrouped minimap buttons independently from the interactable button size.", + getValue=function() local m = MinimapDB(); return m and m.customBtnSizeEnabled end, + setValue=function(v) + local m = MinimapDB(); if not m then return end + m.customBtnSizeEnabled = v + RefreshMinimap() + EllesmereUI:RefreshPage() + end } ); y = y - h -- Inline cog on Outer-Group MM Button Size for size slider @@ -389,26 +389,26 @@ initFrame:SetScript("OnEvent", function(self) -- Free Move Buttons | Button Backgrounds local fmRow fmRow, h = W:DualRow(parent, y, - { type="toggle", text="Free Move Buttons", - tooltip="When enabled, Shift+Click any minimap button (mail, calendar, tracking, addon buttons) to drag it to a custom position.", - getValue=function() local m = MinimapDB(); return m and m.freeMoveBtns end, - setValue=function(v) - local m = MinimapDB(); if not m then return end - m.freeMoveBtns = v - if not v then - m.btnPositions = {} - end - RefreshMinimap() - EllesmereUI:RefreshPage() - end }, - { type="toggle", text="Button Backgrounds", - tooltip="Show black backgrounds behind minimap indicator buttons (tracking, calendar, mail, crafting, addon buttons, flyout toggle).", - getValue=function() local m = MinimapDB(); return m and m.btnBackgrounds ~= false end, - setValue=function(v) - local m = MinimapDB(); if not m then return end - m.btnBackgrounds = v - FullRebuildMinimap() - end } + { type="toggle", text="Free Move Buttons", + tooltip="When enabled, Shift+Click any minimap button (mail, calendar, tracking, addon buttons) to drag it to a custom position.", + getValue=function() local m = MinimapDB(); return m and m.freeMoveBtns end, + setValue=function(v) + local m = MinimapDB(); if not m then return end + m.freeMoveBtns = v + if not v then + m.btnPositions = {} + end + RefreshMinimap() + EllesmereUI:RefreshPage() + end }, + { type="toggle", text="Button Backgrounds", + tooltip="Show black backgrounds behind minimap indicator buttons (tracking, calendar, mail, crafting, addon buttons, flyout toggle).", + getValue=function() local m = MinimapDB(); return m and m.btnBackgrounds ~= false end, + setValue=function(v) + local m = MinimapDB(); if not m then return end + m.btnBackgrounds = v + FullRebuildMinimap() + end } ); y = y - h -- "Reset" label next to the Free Move toggle (only visible when enabled) @@ -442,11 +442,11 @@ initFrame:SetScript("OnEvent", function(self) -- Hide Extra Buttons (checkbox dropdown) local extraBtnRow extraBtnRow, h = W:DualRow(parent, y, - { type="dropdown", text="Hide Extra Buttons", - values = { __placeholder = "..." }, order = { "__placeholder" }, - getValue = function() return "__placeholder" end, - setValue = function() end }, - { type="label", text="" } + { type="dropdown", text="Hide Extra Buttons", + values = { __placeholder = "..." }, order = { "__placeholder" }, + getValue = function() return "__placeholder" end, + setValue = function() end }, + { type="label", text="" } ); y = y - h -- Replace placeholder with checkbox dropdown @@ -461,19 +461,19 @@ initFrame:SetScript("OnEvent", function(self) } local cbDD, cbDDRefresh = EllesmereUI.BuildVisOptsCBDropdown( - leftRgn, 210, leftRgn:GetFrameLevel() + 2, - EXTRA_BTN_ITEMS, - function(k) - local m = MinimapDB(); if not m then return false end - local heb = m.hideExtraBtns - return heb and heb[k] and true or false - end, - function(k, v) - local m = MinimapDB(); if not m then return end - if not m.hideExtraBtns then m.hideExtraBtns = {} end - m.hideExtraBtns[k] = v - RefreshMinimap() - end) + leftRgn, 210, leftRgn:GetFrameLevel() + 2, + EXTRA_BTN_ITEMS, + function(k) + local m = MinimapDB(); if not m then return false end + local heb = m.hideExtraBtns + return heb and heb[k] and true or false + end, + function(k, v) + local m = MinimapDB(); if not m then return end + if not m.hideExtraBtns then m.hideExtraBtns = {} end + m.hideExtraBtns[k] = v + RefreshMinimap() + end) local PP = EllesmereUI.PP PP.Point(cbDD, "RIGHT", leftRgn, "RIGHT", -20, 0) leftRgn._control = cbDD @@ -481,6 +481,18 @@ initFrame:SetScript("OnEvent", function(self) EllesmereUI.RegisterWidgetRefresh(cbDDRefresh) end + -- Friends Online: per-section row cap (0 = no cap) + _, h = W:DualRow(parent, y, + { type="slider", text="Friends List Cap", min=0, max=50, step=1, + tooltip="Max rows shown per section in the Friends Online tooltip. 0 = no cap.", + getValue=function() local m = MinimapDB(); return m and m.friendsMaxRows or 0 end, + setValue=function(v) + local m = MinimapDB(); if not m then return end + m.friendsMaxRows = v + end }, + { type="label", text="" } + ); y = y - h + y = y - 10 -- EXTRAS section header @@ -488,68 +500,68 @@ initFrame:SetScript("OnEvent", function(self) -- Show Zone | Show Clock _, h = W:DualRow(parent, y, - { type="toggle", text="Show Zone", - getValue=function() local m = MinimapDB(); return not (m and m.hideZoneText) end, - setValue=function(v) - local m = MinimapDB(); if not m then return end - m.hideZoneText = not v - RefreshMinimap() - EllesmereUI:RefreshPage() - end }, - { type="toggle", text="Show Clock", - getValue=function() local m = MinimapDB(); return m and m.showClock end, - setValue=function(v) - local m = MinimapDB(); if not m then return end - m.showClock = v - RefreshMinimap() - EllesmereUI:RefreshPage() - end } + { type="toggle", text="Show Zone", + getValue=function() local m = MinimapDB(); return not (m and m.hideZoneText) end, + setValue=function(v) + local m = MinimapDB(); if not m then return end + m.hideZoneText = not v + RefreshMinimap() + EllesmereUI:RefreshPage() + end }, + { type="toggle", text="Show Clock", + getValue=function() local m = MinimapDB(); return m and m.showClock end, + setValue=function(v) + local m = MinimapDB(); if not m then return end + m.showClock = v + RefreshMinimap() + EllesmereUI:RefreshPage() + end } ); y = y - h -- Zone Inside | Clock Inside _, h = W:DualRow(parent, y, - { type="toggle", text="Zone Inside", - tooltip="Display the zone text inside the minimap instead of below it", - disabled=function() local m = MinimapDB(); return m and (m.hideZoneText) end, - disabledTooltip="Enable Show Zone first", - getValue=function() local m = MinimapDB(); return m and m.zoneInside end, - setValue=function(v) - local m = MinimapDB(); if not m then return end - m.zoneInside = v - RefreshMinimap() - end }, - { type="toggle", text="Clock Inside", - tooltip="Display the clock inside the minimap instead of above it", - disabled=function() local m = MinimapDB(); return m and (not m.showClock) end, - disabledTooltip="Enable Show Clock first", - getValue=function() local m = MinimapDB(); return m and m.clockInside end, - setValue=function(v) - local m = MinimapDB(); if not m then return end - m.clockInside = v - RefreshMinimap() - end } + { type="toggle", text="Zone Inside", + tooltip="Display the zone text inside the minimap instead of below it", + disabled=function() local m = MinimapDB(); return m and (m.hideZoneText) end, + disabledTooltip="Enable Show Zone first", + getValue=function() local m = MinimapDB(); return m and m.zoneInside end, + setValue=function(v) + local m = MinimapDB(); if not m then return end + m.zoneInside = v + RefreshMinimap() + end }, + { type="toggle", text="Clock Inside", + tooltip="Display the clock inside the minimap instead of above it", + disabled=function() local m = MinimapDB(); return m and (not m.showClock) end, + disabledTooltip="Enable Show Clock first", + getValue=function() local m = MinimapDB(); return m and m.clockInside end, + setValue=function(v) + local m = MinimapDB(); if not m then return end + m.clockInside = v + RefreshMinimap() + end } ); y = y - h -- Scroll to Zoom | Clock Scale (with cog: X/Y offset) local clockScaleRow clockScaleRow, h = W:DualRow(parent, y, - { type="toggle", text="Scroll to Zoom", - getValue=function() local m = MinimapDB(); return m and m.scrollZoom end, - setValue=function(v) - local m = MinimapDB(); if not m then return end - m.scrollZoom = v - RefreshMinimap() - end }, - { type="slider", text="Clock Scale", min=0.5, max=2.0, step=0.01, - disabled=function() local m = MinimapDB(); return m and (not m.showClock) end, - disabledTooltip="Enable Show Clock first", - getValue=function() local m = MinimapDB(); return m and m.clockScale or 1.15 end, - setValue=function(v) - local m = MinimapDB(); if not m then return end - m.clockScale = v - local bg = _G._EBS_ClockBg - if bg then bg:SetScale(v) end - end } + { type="toggle", text="Scroll to Zoom", + getValue=function() local m = MinimapDB(); return m and m.scrollZoom end, + setValue=function(v) + local m = MinimapDB(); if not m then return end + m.scrollZoom = v + RefreshMinimap() + end }, + { type="slider", text="Clock Scale", min=0.5, max=2.0, step=0.01, + disabled=function() local m = MinimapDB(); return m and (not m.showClock) end, + disabledTooltip="Enable Show Clock first", + getValue=function() local m = MinimapDB(); return m and m.clockScale or 1.15 end, + setValue=function(v) + local m = MinimapDB(); if not m then return end + m.clockScale = v + local bg = _G._EBS_ClockBg + if bg then bg:SetScale(v) end + end } ); y = y - h -- Inline cog on Clock Scale for X/Y offset @@ -615,25 +627,25 @@ initFrame:SetScript("OnEvent", function(self) -- Location Scale (with cog: X/Y offset) | (spacer) local locScaleRow locScaleRow, h = W:DualRow(parent, y, - { type="slider", text="Location Scale", min=0.5, max=2.0, step=0.01, - disabled=function() local m = MinimapDB(); return m and (m.hideZoneText) end, - disabledTooltip="Enable Show Zone first", - getValue=function() local m = MinimapDB(); return m and m.locationScale or 1.15 end, - setValue=function(v) - local m = MinimapDB(); if not m then return end - m.locationScale = v - local bg = _G._EBS_LocationBg - if bg then bg:SetScale(v) end - end }, - { type="toggle", text="Show Coordinates Below Minimap", - tooltip="Always display player coordinates centered below the minimap instead of only on hover.", - getValue=function() local m = MinimapDB(); return m and m.coordsBelow end, - setValue=function(v) - local m = MinimapDB(); if not m then return end - m.coordsBelow = v - RefreshMinimap() - EllesmereUI:RefreshPage() - end } + { type="slider", text="Location Scale", min=0.5, max=2.0, step=0.01, + disabled=function() local m = MinimapDB(); return m and (m.hideZoneText) end, + disabledTooltip="Enable Show Zone first", + getValue=function() local m = MinimapDB(); return m and m.locationScale or 1.15 end, + setValue=function(v) + local m = MinimapDB(); if not m then return end + m.locationScale = v + local bg = _G._EBS_LocationBg + if bg then bg:SetScale(v) end + end }, + { type="toggle", text="Show Coordinates Below Minimap", + tooltip="Always display player coordinates centered below the minimap instead of only on hover.", + getValue=function() local m = MinimapDB(); return m and m.coordsBelow end, + setValue=function(v) + local m = MinimapDB(); if not m then return end + m.coordsBelow = v + RefreshMinimap() + EllesmereUI:RefreshPage() + end } ); y = y - h -- Inline directions icon on Show Coordinates Below for X/Y offset diff --git a/EllesmereUIMinimap/EllesmereUIMinimap.lua b/EllesmereUIMinimap/EllesmereUIMinimap.lua index 891999b8..a17a5e7c 100644 --- a/EllesmereUIMinimap/EllesmereUIMinimap.lua +++ b/EllesmereUIMinimap/EllesmereUIMinimap.lua @@ -43,6 +43,7 @@ local defaults = { hideRaidDifficulty = false, hideCraftingOrder = false, hideExtraBtns = { greatVault = false, portals = false, friendsOnline = false }, + friendsMaxRows = 0, -- 0 = no cap; else cap per section, show "...and N more" greatVaultExtraInfo = true, hideAddonCompartment = false, hideAddonButtons = false, @@ -362,7 +363,7 @@ local function LayoutFlyoutButtons() if not icon then for _, region in ipairs({ btn:GetRegions() }) do if region:IsObjectType("Texture") and region:IsShown() - and region:GetAlpha() > 0 and not IsJunkTexture(region) then + and region:GetAlpha() > 0 and not IsJunkTexture(region) then icon = region break end @@ -893,7 +894,7 @@ local function GatherMinimapButtons() elseif IsPinFrame(name) then -- skip pin/POI frames elseif child:IsObjectType("Button") and name - and not name:match("%d+$") then + and not name:match("%d+$") then local w = child:GetWidth() or 0 -- Width gate only for first discovery; once a button is -- tracked in _addonVisible it is always re-collected so @@ -1152,7 +1153,7 @@ local function GetVaultTooltip() if _vaultTT then return _vaultTT end local f = CreateFrame("Frame", nil, UIParent, "BackdropTemplate") f:SetBackdrop({ bgFile = "Interface\\ChatFrame\\ChatFrameBackground", - edgeFile = "Interface\\ChatFrame\\ChatFrameBackground", edgeSize = 1 }) + edgeFile = "Interface\\ChatFrame\\ChatFrameBackground", edgeSize = 1 }) f:SetBackdropColor(0.06, 0.06, 0.06, 0.90) f:SetBackdropBorderColor(0.25, 0.25, 0.25, 1) f:SetFrameStrata("TOOLTIP") @@ -1435,8 +1436,8 @@ local function CreateMinimapPortalFlyout() local btn = CreateFrame("Button", "EUIMinimapPortal" .. i, flyout, "SecureActionButtonTemplate") btn:SetSize(BTN_SIZE, BTN_SIZE) btn:SetPoint("TOPLEFT", flyout, "TOPLEFT", - PADDING + col * (BTN_SIZE + SPACING), - -(PADDING + row * (BTN_SIZE + SPACING))) + PADDING + col * (BTN_SIZE + SPACING), + -(PADDING + row * (BTN_SIZE + SPACING))) btn.spellID = spellID @@ -1504,8 +1505,8 @@ local function CreateMinimapPortalFlyout() local btn = CreateFrame("Button", "EUIMinimapHearth" .. i, flyout, "SecureActionButtonTemplate") btn:SetSize(HS_H, HS_H) btn:SetPoint("TOPLEFT", flyout, "TOPLEFT", - hsX, - -(PADDING + (i - 1) * (HS_H + SPACING))) + hsX, + -(PADDING + (i - 1) * (HS_H + SPACING))) local icon = btn:CreateTexture(nil, "ARTWORK") icon:SetAllPoints() @@ -1604,9 +1605,9 @@ local function CreateMinimapPortalFlyout() btn._hsID = id btn.icon:SetTexture(iconTex) btn.icon:SetTexCoord(aType == "housing" and 0 or 6/64, - aType == "housing" and 1 or 58/64, - aType == "housing" and 0 or 6/64, - aType == "housing" and 1 or 58/64) + aType == "housing" and 1 or 58/64, + aType == "housing" and 0 or 6/64, + aType == "housing" and 1 or 58/64) if aType == "housing" then btn:SetAttribute("type", nil) btn:SetAttribute("macrotext", nil) @@ -1773,7 +1774,7 @@ local function GatherOnlineFriends() if online and name then local short = name:match("^([^%-]+)") or name if short ~= myName then - guild[#guild + 1] = { name = short, class = classFile, zone = zone or "", level = level } + guild[#guild + 1] = { name = short, full = name, class = classFile, zone = zone or "", level = level } end end end @@ -1794,12 +1795,19 @@ local function GatherOnlineFriends() if ci and ci.classFile then classFile = ci.classFile end end local zone = gameInfo.areaName or "" + local realm = gameInfo.realmName + local full = charName + if charName and realm and realm ~= "" then + full = charName .. "-" .. realm + end local entry = { name = charName or acct.accountName or "???", + full = full, class = classFile, zone = zone, level = gameInfo.characterLevel, bnetTag = acct.accountName, + bnetID = acct.bnetAccountID, } if charName then seenBNet[charName] = true end if acct.isFavorite then @@ -1820,6 +1828,7 @@ local function GatherOnlineFriends() if charName and not seenBNet[charName] then friends[#friends + 1] = { name = charName:match("^([^%-]+)") or charName, + full = charName, class = info.className and info.className:upper():gsub(" ", ""), zone = info.area or "", level = info.level, @@ -1851,18 +1860,59 @@ local FTT_ROW_H = 14 local FTT_HDR_H = 16 local FTT_GAP = 2 local FTT_DIV_PAD = 5 -- padding above and below the divider line -local FTT_MAX_FAV = 20 -local FTT_MAX_GLD = 20 -local FTT_MAX_FRD = 15 local function FTT_FONT() return (EllesmereUI.GetFontPath and EllesmereUI.GetFontPath()) or EllesmereUI.EXPRESSWAY or "Fonts\\FRIZQT__.TTF" end +-- Hover-stable hide: small grace period so cursor can travel from button to tooltip +local _fttHideToken = 0 +local function CancelFTTHide() + _fttHideToken = _fttHideToken + 1 +end +local function ScheduleFTTHide() + _fttHideToken = _fttHideToken + 1 + local mine = _fttHideToken + C_Timer.After(0.15, function() + if mine ~= _fttHideToken then return end + if _friendsTT then _friendsTT:Hide() end + end) +end + +local function FTTWhisperEntry(e) + if not e then return end + if e.bnetTag and ChatFrame_SendBNetTell then + ChatFrame_SendBNetTell(e.bnetTag) + return + end + local target = e.full or e.name + if target and ChatFrame_OpenChat then + ChatFrame_OpenChat("/w " .. target .. " ") + end +end + +local function FTTInviteEntry(e) + if not e then return end + if InCombatLockdown() then + UIErrorsFrame:AddMessage(ERR_NOT_IN_COMBAT, 1.0, 0.3, 0.3, 1.0) + return + end + local target = e.full or e.name + if not target then return end + if C_PartyInfo and C_PartyInfo.InviteUnit then + C_PartyInfo.InviteUnit(target) + elseif InviteUnit then + InviteUnit(target) + end +end + local function GetFriendsTT() if _friendsTT then return _friendsTT end local f = CreateFrame("Frame", nil, UIParent) f:SetFrameStrata("TOOLTIP") f:SetFrameLevel(200) + f:EnableMouse(true) + f:SetScript("OnEnter", CancelFTTHide) + f:SetScript("OnLeave", ScheduleFTTHide) f:Hide() local bg = f:CreateTexture(nil, "BACKGROUND") bg:SetAllPoints() @@ -1875,13 +1925,44 @@ end local function EnsureFTTRow(idx) if _friendsTTRows[idx] then return _friendsTTRows[idx] end local tt = GetFriendsTT() - local nameFS = tt:CreateFontString(nil, "OVERLAY") + local btn = CreateFrame("Button", nil, tt) + btn:EnableMouse(true) + btn:RegisterForClicks("AnyUp") + btn:SetHeight(FTT_ROW_H) + local hl = btn:CreateTexture(nil, "BACKGROUND") + hl:SetAllPoints() + hl:SetColorTexture(1, 1, 1, 0.08) + hl:Hide() + btn._hl = hl + btn:SetScript("OnEnter", function(self) + CancelFTTHide() + if self._entry then self._hl:Show() end + end) + btn:SetScript("OnLeave", function(self) + self._hl:Hide() + ScheduleFTTHide() + end) + btn:SetScript("OnClick", function(self, mouseButton) + local e = self._entry + if not e then return end + if mouseButton ~= "LeftButton" then return end + if IsAltKeyDown() then + FTTInviteEntry(e) + else + FTTWhisperEntry(e) + end + CancelFTTHide() + if _friendsTT then _friendsTT:Hide() end + end) + local nameFS = btn:CreateFontString(nil, "OVERLAY") nameFS:SetFont(FTT_FONT(), 10, "") nameFS:SetJustifyH("LEFT") - local zoneFS = tt:CreateFontString(nil, "OVERLAY") + nameFS:SetPoint("LEFT", btn, "LEFT", 0, 0) + local zoneFS = btn:CreateFontString(nil, "OVERLAY") zoneFS:SetFont(FTT_FONT(), 10, "") zoneFS:SetJustifyH("RIGHT") - _friendsTTRows[idx] = { name = nameFS, zone = zoneFS } + zoneFS:SetPoint("RIGHT", btn, "RIGHT", 0, 0) + _friendsTTRows[idx] = { button = btn, name = nameFS, zone = zoneFS } return _friendsTTRows[idx] end @@ -1912,10 +1993,15 @@ local function EnsureFTTDivider(idx) end local function ShowFriendsTooltip(anchor) + CancelFTTHide() local guild, favorites, friends = GatherOnlineFriends() local tt = GetFriendsTT() local total = #guild + #favorites + #friends + local mp = EBS.db and EBS.db.profile and EBS.db.profile.minimap + local maxRows = mp and tonumber(mp.friendsMaxRows) or 0 + if maxRows and maxRows < 0 then maxRows = 0 end + -- Refresh fonts to match current global font setting local font = FTT_FONT() for i = 1, #_friendsTTRows do @@ -1926,10 +2012,16 @@ local function ShowFriendsTooltip(anchor) _friendsTTHeaders[i]:SetFont(font, 12, "") end - -- Hide all pooled elements + -- Hide all pooled elements and clear stale entry refs for i = 1, #_friendsTTRows do - _friendsTTRows[i].name:Hide() - _friendsTTRows[i].zone:Hide() + local r = _friendsTTRows[i] + r.name:Hide() + r.zone:Hide() + if r.button then + r.button:Hide() + r.button._entry = nil + if r.button._hl then r.button._hl:Hide() end + end end for i = 1, #_friendsTTHeaders do _friendsTTHeaders[i]:Hide() end for i = 1, #_friendsTTDividers do _friendsTTDividers[i]:Hide() end @@ -1940,8 +2032,11 @@ local function ShowFriendsTooltip(anchor) row.name:SetText("|cff888888No friends online|r") row.zone:SetText("") tt:SetSize(FTT_PAD * 2 + 140, FTT_PAD + FTT_ROW_H + FTT_PAD) - row.name:ClearAllPoints() - row.name:SetPoint("TOPLEFT", tt, "TOPLEFT", FTT_PAD, -FTT_PAD) + row.button:ClearAllPoints() + row.button:SetPoint("TOPLEFT", tt, "TOPLEFT", FTT_PAD, -FTT_PAD) + row.button:SetPoint("TOPRIGHT", tt, "TOPRIGHT", -FTT_PAD, -FTT_PAD) + row.button._entry = nil + row.button:Show() row.name:Show() tt:ClearAllPoints() tt:SetPoint("TOPRIGHT", anchor, "TOPLEFT", -4, 0) @@ -1950,9 +2045,9 @@ local function ShowFriendsTooltip(anchor) end local sections = {} - if #favorites > 0 then sections[#sections + 1] = { title = "Favorites", list = favorites, max = FTT_MAX_FAV } end - if #guild > 0 then sections[#sections + 1] = { title = "Guild", list = guild, max = FTT_MAX_GLD } end - if #friends > 0 then sections[#sections + 1] = { title = "Friends", list = friends, max = FTT_MAX_FRD } end + if #favorites > 0 then sections[#sections + 1] = { title = "Favorites", list = favorites } end + if #guild > 0 then sections[#sections + 1] = { title = "Guild", list = guild } end + if #friends > 0 then sections[#sections + 1] = { title = "Friends", list = friends } end local rowIdx = 0 local hdrIdx = 0 @@ -1986,7 +2081,8 @@ local function ShowFriendsTooltip(anchor) hdr:Show() curY = curY - FTT_HDR_H - 5 -- spacing below header - local shown = math.min(#sec.list, sec.max) + local shown = #sec.list + if maxRows > 0 and shown > maxRows then shown = maxRows end for i = 1, shown do local e = sec.list[i] rowIdx = rowIdx + 1 @@ -2006,10 +2102,11 @@ local function ShowFriendsTooltip(anchor) row.zone:SetText("") end - row.name:ClearAllPoints() - row.name:SetPoint("TOPLEFT", tt, "TOPLEFT", FTT_PAD, curY) - row.zone:ClearAllPoints() - row.zone:SetPoint("TOPRIGHT", tt, "TOPRIGHT", -FTT_PAD, curY) + row.button._entry = e + row.button:ClearAllPoints() + row.button:SetPoint("TOPLEFT", tt, "TOPLEFT", FTT_PAD, curY) + row.button:SetPoint("TOPRIGHT", tt, "TOPRIGHT", -FTT_PAD, curY) + row.button:Show() row.name:Show() row.zone:Show() @@ -2021,14 +2118,17 @@ local function ShowFriendsTooltip(anchor) curY = curY - (FTT_ROW_H + FTT_GAP) end - if #sec.list > sec.max then + if maxRows > 0 and #sec.list > maxRows then rowIdx = rowIdx + 1 local row = EnsureFTTRow(rowIdx) row.name:SetFont(font, 10, "") - row.name:SetText("|cff888888...and " .. (#sec.list - sec.max) .. " more|r") + row.name:SetText("|cff888888...and " .. (#sec.list - maxRows) .. " more|r") row.zone:SetText("") - row.name:ClearAllPoints() - row.name:SetPoint("TOPLEFT", tt, "TOPLEFT", FTT_PAD, curY) + row.button._entry = nil + row.button:ClearAllPoints() + row.button:SetPoint("TOPLEFT", tt, "TOPLEFT", FTT_PAD, curY) + row.button:SetPoint("TOPRIGHT", tt, "TOPRIGHT", -FTT_PAD, curY) + row.button:Show() row.name:Show() curY = curY - (FTT_ROW_H + FTT_GAP) end @@ -2055,7 +2155,7 @@ local function ShowFriendsTooltip(anchor) end local function HideFriendsTooltip() - if _friendsTT then _friendsTT:Hide() end + ScheduleFTTHide() end local function BuildCustomIndicators(minimap) @@ -2063,30 +2163,30 @@ local function BuildCustomIndicators(minimap) -- Tracking _customIndicators.tracking = CreateIndicatorBtn("_tracking", minimap, - "UI-HUD-Minimap-Tracking-Up", "UI-HUD-Minimap-Tracking-Mouseover", "UI-HUD-Minimap-Tracking-Down", - function(self) - local blizBtn = MinimapCluster and MinimapCluster.Tracking and MinimapCluster.Tracking.Button - if not blizBtn or not blizBtn.OpenMenu then return end - - -- Toggle: close if already open - if blizBtn.menu and blizBtn.menu:IsShown() then - blizBtn.menu:Hide() - return - end + "UI-HUD-Minimap-Tracking-Up", "UI-HUD-Minimap-Tracking-Mouseover", "UI-HUD-Minimap-Tracking-Down", + function(self) + local blizBtn = MinimapCluster and MinimapCluster.Tracking and MinimapCluster.Tracking.Button + if not blizBtn or not blizBtn.OpenMenu then return end + + -- Toggle: close if already open + if blizBtn.menu and blizBtn.menu:IsShown() then + blizBtn.menu:Hide() + return + end - -- Position hidden Blizzard button at our custom button - blizBtn:ClearAllPoints() - blizBtn:SetPoint("CENTER", self, "CENTER", 0, 0) - blizBtn:SetAlpha(0) - blizBtn:EnableMouse(false) - blizBtn:OpenMenu() - - -- Reposition menu so its top aligns with our button's top - if blizBtn.menu then - blizBtn.menu:ClearAllPoints() - blizBtn.menu:SetPoint("TOPRIGHT", self, "TOPLEFT", -4, 0) - end - end) + -- Position hidden Blizzard button at our custom button + blizBtn:ClearAllPoints() + blizBtn:SetPoint("CENTER", self, "CENTER", 0, 0) + blizBtn:SetAlpha(0) + blizBtn:EnableMouse(false) + blizBtn:OpenMenu() + + -- Reposition menu so its top aligns with our button's top + if blizBtn.menu then + blizBtn.menu:ClearAllPoints() + blizBtn.menu:SetPoint("TOPRIGHT", self, "TOPLEFT", -4, 0) + end + end) local trackBaseEnter = _customIndicators.tracking:GetScript("OnEnter") local trackBaseLeave = _customIndicators.tracking:GetScript("OnLeave") _customIndicators.tracking:SetScript("OnEnter", function(self) @@ -2104,10 +2204,10 @@ local function BuildCustomIndicators(minimap) local calDay = tonumber(date("%d")) or 1 local calPrefix = "UI-HUD-Calendar-" .. calDay _customIndicators.calendar = CreateIndicatorBtn("_gameTime", minimap, - calPrefix .. "-Up", calPrefix .. "-Mouseover", calPrefix .. "-Down", - function() - if ToggleCalendar then ToggleCalendar() end - end) + calPrefix .. "-Up", calPrefix .. "-Mouseover", calPrefix .. "-Down", + function() + if ToggleCalendar then ToggleCalendar() end + end) _customIndicators.calendar._calDay = calDay local calBaseEnter = _customIndicators.calendar:GetScript("OnEnter") local calBaseLeave = _customIndicators.calendar:GetScript("OnLeave") @@ -2124,7 +2224,7 @@ local function BuildCustomIndicators(minimap) -- Mail (informational, tooltip on hover, with hover atlas) _customIndicators.mail = CreateIndicatorBtn("_mail", minimap, - "UI-HUD-Minimap-Mail-Up", "UI-HUD-Minimap-Mail-Mouseover", nil, nil) + "UI-HUD-Minimap-Mail-Up", "UI-HUD-Minimap-Mail-Mouseover", nil, nil) local mailBaseEnter = _customIndicators.mail:GetScript("OnEnter") local mailBaseLeave = _customIndicators.mail:GetScript("OnLeave") _customIndicators.mail:SetScript("OnEnter", function(self) @@ -2140,7 +2240,7 @@ local function BuildCustomIndicators(minimap) -- Crafting Order (informational, tooltip on hover, with hover atlas) _customIndicators.crafting = CreateIndicatorBtn("_crafting", minimap, - "UI-HUD-Minimap-CraftingOrder-Up-2x", "UI-HUD-Minimap-CraftingOrder-Over-2x", "UI-HUD-Minimap-CraftingOrder-Down-2x", nil) + "UI-HUD-Minimap-CraftingOrder-Up-2x", "UI-HUD-Minimap-CraftingOrder-Over-2x", "UI-HUD-Minimap-CraftingOrder-Down-2x", nil) local craftBaseEnter = _customIndicators.crafting:GetScript("OnEnter") local craftBaseLeave = _customIndicators.crafting:GetScript("OnLeave") _customIndicators.crafting:SetScript("OnEnter", function(self) @@ -2173,14 +2273,14 @@ local function BuildCustomIndicators(minimap) -- Friends Online button _customIndicators.friends = CreateIndicatorBtn("_friends", minimap, - FRIENDS_ATLAS, FRIENDS_ATLAS, nil, - function() - if InCombatLockdown() then - UIErrorsFrame:AddMessage(ERR_NOT_IN_COMBAT, 1.0, 0.3, 0.3, 1.0) - return - end - ToggleFriendsFrame() - end) + FRIENDS_ATLAS, FRIENDS_ATLAS, nil, + function() + if InCombatLockdown() then + UIErrorsFrame:AddMessage(ERR_NOT_IN_COMBAT, 1.0, 0.3, 0.3, 1.0) + return + end + ToggleFriendsFrame() + end) -- Atlas is not in INDICATOR_ATLAS_RATIO so icon uses inset anchoring -- (TOPLEFT/BOTTOMRIGHT). Desaturate slightly for idle state. if _customIndicators.friends._icon then @@ -2443,8 +2543,8 @@ local function LayoutIndicatorFrames(minimap, p, circleMode) if not icon then for _, region in ipairs({ btn:GetRegions() }) do if region:IsObjectType("Texture") and region:IsShown() - and region:GetAlpha() > 0 and not IsJunkTexture(region) - and region ~= GetFFD(btn).ungroupBg then + and region:GetAlpha() > 0 and not IsJunkTexture(region) + and region ~= GetFFD(btn).ungroupBg then icon = region break end @@ -2526,7 +2626,7 @@ local function LayoutIndicatorFrames(minimap, p, circleMode) ci.friends:ClearAllPoints() if freeMove then local idx = #ungrouped + (flyoutVisible and 1 or 0) - + ((_greatVaultBtn and not heb.greatVault) and 1 or 0) + + ((_greatVaultBtn and not heb.greatVault) and 1 or 0) local yOff = idx * ungroupBtnSize ci.friends:SetPoint("BOTTOMRIGHT", minimap, "BOTTOMLEFT", 0, yOff) elseif anchor then @@ -2550,8 +2650,8 @@ local function LayoutIndicatorFrames(minimap, p, circleMode) _portalBtn:ClearAllPoints() if freeMove then local idx = #ungrouped + (flyoutVisible and 1 or 0) - + ((_greatVaultBtn and not heb.greatVault) and 1 or 0) - + ((ci.friends and not heb.friendsOnline) and 1 or 0) + + ((_greatVaultBtn and not heb.greatVault) and 1 or 0) + + ((ci.friends and not heb.friendsOnline) and 1 or 0) local yOff = idx * ungroupBtnSize _portalBtn:SetPoint("BOTTOMRIGHT", minimap, "BOTTOMLEFT", 0, yOff) elseif anchor then @@ -3273,7 +3373,7 @@ local function ApplyMinimap() if PPa and px and py then local es = minimap:GetEffectiveScale() local isCenterAnchor = (p.position.point == "CENTER") - and (p.position.relPoint == "CENTER" or p.position.relPoint == nil) + and (p.position.relPoint == "CENTER" or p.position.relPoint == nil) if isCenterAnchor and PPa.SnapCenterForDim then px = PPa.SnapCenterForDim(px, minimap:GetWidth() or 0, es) py = PPa.SnapCenterForDim(py, minimap:GetHeight() or 0, es) From 9d2017bc479411f8d26d94a0c6d6092d372e5f8d Mon Sep 17 00:00:00 2001 From: Lance Date: Sun, 24 May 2026 18:46:39 -0700 Subject: [PATCH 2/9] style(minimap): revert IDE autoformat to project indent style The prior commit accidentally let the IDE reformat continuation-line indentation across these two files. Reverts to the project's existing 4-space continuation style. No logic change. --- EllesmereUIMinimap/EUI_Minimap_Options.lua | 498 ++++++++++----------- EllesmereUIMinimap/EllesmereUIMinimap.lua | 106 ++--- 2 files changed, 302 insertions(+), 302 deletions(-) diff --git a/EllesmereUIMinimap/EUI_Minimap_Options.lua b/EllesmereUIMinimap/EUI_Minimap_Options.lua index 3b00075e..9aafc04f 100644 --- a/EllesmereUIMinimap/EUI_Minimap_Options.lua +++ b/EllesmereUIMinimap/EUI_Minimap_Options.lua @@ -81,37 +81,37 @@ initFrame:SetScript("OnEvent", function(self) local PP = EllesmereUI.PP local function BuildVisibilityRow(W, parent, y, getCfg, refreshFn) local visRow, visH = W:DualRow(parent, y, - { type="dropdown", text="Visibility", - values = EllesmereUI.VIS_VALUES, - order = EllesmereUI.VIS_ORDER, - getValue=function() - local c = getCfg(); if not c then return "always" end - return c.visibility or "always" - end, - setValue=function(v) - local c = getCfg(); if not c then return end - c.visibility = v - if refreshFn then refreshFn() end - if _G._EBS_UpdateVisibility then _G._EBS_UpdateVisibility() end - EllesmereUI:RefreshPage() - end }, - { type="dropdown", text="Visibility Options", - values={ __placeholder = "..." }, order={ "__placeholder" }, - getValue=function() return "__placeholder" end, - setValue=function() end }) + { type="dropdown", text="Visibility", + values = EllesmereUI.VIS_VALUES, + order = EllesmereUI.VIS_ORDER, + getValue=function() + local c = getCfg(); if not c then return "always" end + return c.visibility or "always" + end, + setValue=function(v) + local c = getCfg(); if not c then return end + c.visibility = v + if refreshFn then refreshFn() end + if _G._EBS_UpdateVisibility then _G._EBS_UpdateVisibility() end + EllesmereUI:RefreshPage() + end }, + { type="dropdown", text="Visibility Options", + values={ __placeholder = "..." }, order={ "__placeholder" }, + getValue=function() return "__placeholder" end, + setValue=function() end }) do local rightRgn = visRow._rightRegion if rightRgn._control then rightRgn._control:Hide() end local cbDD, cbDDRefresh = EllesmereUI.BuildVisOptsCBDropdown( - rightRgn, 210, rightRgn:GetFrameLevel() + 2, - EllesmereUI.VIS_OPT_ITEMS, - function(k) local c = getCfg(); return c and c[k] or false end, - function(k, v) - local c = getCfg(); if not c then return end - c[k] = v - if _G._EBS_UpdateVisibility then _G._EBS_UpdateVisibility() end - EllesmereUI:RefreshPage() - end) + rightRgn, 210, rightRgn:GetFrameLevel() + 2, + EllesmereUI.VIS_OPT_ITEMS, + function(k) local c = getCfg(); return c and c[k] or false end, + function(k, v) + local c = getCfg(); if not c then return end + c[k] = v + if _G._EBS_UpdateVisibility then _G._EBS_UpdateVisibility() end + EllesmereUI:RefreshPage() + end) PP.Point(cbDD, "RIGHT", rightRgn, "RIGHT", -20, 0) rightRgn._control = cbDD rightRgn._lastInline = nil @@ -158,10 +158,10 @@ initFrame:SetScript("OnEvent", function(self) return ar, ag, ab end, setValue = function() end, - -- Flag name stays `useClassColor` for backwards compat with - -- users who already have it stamped in their SavedVariables. - -- Only the color resolution changes -- the flag now means - -- "use live accent" rather than "use class color". + -- Flag name stays `useClassColor` for backwards compat with + -- users who already have it stamped in their SavedVariables. + -- Only the color resolution changes -- the flag now means + -- "use live accent" rather than "use class color". onClick = function() local c = getCfg(); if not c then return end c.useClassColor = true @@ -192,64 +192,64 @@ initFrame:SetScript("OnEvent", function(self) _, h = W:SectionHeader(parent, SECTION_MINIMAP, y); y = y - h _, h = W:DualRow(parent, y, - { type="slider", text="Size", min=100, max=600, step=5, - getValue=function() local m = MinimapDB(); return m and m.mapSize or 140 end, - setValue=function(v) - local m = MinimapDB(); if not m then return end - m.mapSize = v - -- Cover the map render during drag to mask the zoom-nudge blink. - -- Borders, buttons, etc. remain visible above the overlay. - local minimap = _G.Minimap - if minimap then - if not minimap._dragOverlay then - local ov = minimap:CreateTexture(nil, "BACKGROUND", nil, 7) - ov:SetAllPoints(minimap) - minimap._dragOverlay = ov - end - local shape = m.shape or "square" - if shape == "circle" or shape == "textured_circle" then - minimap._dragOverlay:SetTexture("Interface\\Common\\CommonMaskCircle") - minimap._dragOverlay:SetVertexColor(0, 0, 0, 1) - else - minimap._dragOverlay:SetColorTexture(0, 0, 0, 1) - end - minimap._dragOverlay:Show() - end - RefreshMinimap() - if not _G._EBS_SizeDragTimer then - _G._EBS_SizeDragTimer = C_Timer.NewTimer(0, function() end) - end - _G._EBS_SizeDragTimer:Cancel() - _G._EBS_SizeDragTimer = C_Timer.NewTimer(0.15, function() - if minimap and minimap._dragOverlay then - minimap._dragOverlay:Hide() - end - end) - end }, - { type="multiSwatch", text="Accent Color", - swatches = MakeBorderSwatch(MinimapDB, RefreshMinimap) }) + { type="slider", text="Size", min=100, max=600, step=5, + getValue=function() local m = MinimapDB(); return m and m.mapSize or 140 end, + setValue=function(v) + local m = MinimapDB(); if not m then return end + m.mapSize = v + -- Cover the map render during drag to mask the zoom-nudge blink. + -- Borders, buttons, etc. remain visible above the overlay. + local minimap = _G.Minimap + if minimap then + if not minimap._dragOverlay then + local ov = minimap:CreateTexture(nil, "BACKGROUND", nil, 7) + ov:SetAllPoints(minimap) + minimap._dragOverlay = ov + end + local shape = m.shape or "square" + if shape == "circle" or shape == "textured_circle" then + minimap._dragOverlay:SetTexture("Interface\\Common\\CommonMaskCircle") + minimap._dragOverlay:SetVertexColor(0, 0, 0, 1) + else + minimap._dragOverlay:SetColorTexture(0, 0, 0, 1) + end + minimap._dragOverlay:Show() + end + RefreshMinimap() + if not _G._EBS_SizeDragTimer then + _G._EBS_SizeDragTimer = C_Timer.NewTimer(0, function() end) + end + _G._EBS_SizeDragTimer:Cancel() + _G._EBS_SizeDragTimer = C_Timer.NewTimer(0.15, function() + if minimap and minimap._dragOverlay then + minimap._dragOverlay:Hide() + end + end) + end }, + { type="multiSwatch", text="Accent Color", + swatches = MakeBorderSwatch(MinimapDB, RefreshMinimap) }) y = y - h h = BuildVisibilityRow(W, parent, y, MinimapDB, RefreshMinimap); y = y - h -- Shape | Border Thickness _, h = W:DualRow(parent, y, - { type="dropdown", text="Shape", - values = { square = "Square", circle = "Circle", textured_circle = "Textured Circle" }, - order = { "square", "circle", "textured_circle" }, - getValue=function() local m = MinimapDB(); return m and m.shape or "square" end, - setValue=function(v) - local m = MinimapDB(); if not m then return end - m.shape = v - RefreshMinimap() - end }, - { type="slider", text="Border Thickness", min=0, max=5, step=1, - getValue=function() local m = MinimapDB(); return m and m.borderSize or 1 end, - setValue=function(v) - local m = MinimapDB(); if not m then return end - m.borderSize = v - RefreshMinimap() - end } + { type="dropdown", text="Shape", + values = { square = "Square", circle = "Circle", textured_circle = "Textured Circle" }, + order = { "square", "circle", "textured_circle" }, + getValue=function() local m = MinimapDB(); return m and m.shape or "square" end, + setValue=function(v) + local m = MinimapDB(); if not m then return end + m.shape = v + RefreshMinimap() + end }, + { type="slider", text="Border Thickness", min=0, max=5, step=1, + getValue=function() local m = MinimapDB(); return m and m.borderSize or 1 end, + setValue=function(v) + local m = MinimapDB(); if not m then return end + m.borderSize = v + RefreshMinimap() + end } ); y = y - h y = y - 10 @@ -260,18 +260,18 @@ initFrame:SetScript("OnEvent", function(self) -- Ungroup Minimap Buttons | In-Group Button Size local ungroupRow ungroupRow, h = W:DualRow(parent, y, - { type="dropdown", text="Ungroup Minimap Buttons", - values = { __placeholder = "..." }, order = { "__placeholder" }, - getValue = function() return "__placeholder" end, - setValue = function() end }, - { type="slider", text="In-Group Button Size", min=14, max=40, step=1, - tooltip="Size of addon minimap buttons in the flyout grid", - getValue=function() local m = MinimapDB(); return m and m.addonBtnSize or 24 end, - setValue=function(v) - local m = MinimapDB(); if not m then return end - m.addonBtnSize = v - RefreshMinimap() - end } + { type="dropdown", text="Ungroup Minimap Buttons", + values = { __placeholder = "..." }, order = { "__placeholder" }, + getValue = function() return "__placeholder" end, + setValue = function() end }, + { type="slider", text="In-Group Button Size", min=14, max=40, step=1, + tooltip="Size of addon minimap buttons in the flyout grid", + getValue=function() local m = MinimapDB(); return m and m.addonBtnSize or 24 end, + setValue=function(v) + local m = MinimapDB(); if not m then return end + m.addonBtnSize = v + RefreshMinimap() + end } ); y = y - h -- Replace placeholder dropdown with checkbox dropdown @@ -296,26 +296,26 @@ initFrame:SetScript("OnEvent", function(self) end local cbDD, cbDDRefresh = EllesmereUI.BuildVisOptsCBDropdown( - leftRgn, 210, leftRgn:GetFrameLevel() + 2, - GetUngroupItems(), - function(k) - local m = MinimapDB(); if not m then return false end - return m.ungroupedButtons and m.ungroupedButtons[k] and true or false - end, - function(k, v) - local m = MinimapDB(); if not m then return end - if not m.ungroupedButtons then m.ungroupedButtons = {} end - if v then - local maxOrder = 0 - for _, ord in pairs(m.ungroupedButtons) do - if type(ord) == "number" and ord > maxOrder then maxOrder = ord end - end - m.ungroupedButtons[k] = maxOrder + 1 - else - m.ungroupedButtons[k] = nil + leftRgn, 210, leftRgn:GetFrameLevel() + 2, + GetUngroupItems(), + function(k) + local m = MinimapDB(); if not m then return false end + return m.ungroupedButtons and m.ungroupedButtons[k] and true or false + end, + function(k, v) + local m = MinimapDB(); if not m then return end + if not m.ungroupedButtons then m.ungroupedButtons = {} end + if v then + local maxOrder = 0 + for _, ord in pairs(m.ungroupedButtons) do + if type(ord) == "number" and ord > maxOrder then maxOrder = ord end end - FullRebuildMinimap() - end) + m.ungroupedButtons[k] = maxOrder + 1 + else + m.ungroupedButtons[k] = nil + end + FullRebuildMinimap() + end) local PP = EllesmereUI.PP PP.Point(cbDD, "RIGHT", leftRgn, "RIGHT", -20, 0) leftRgn._control = cbDD @@ -326,23 +326,23 @@ initFrame:SetScript("OnEvent", function(self) -- Interactable Button Size | Outer-Group MM Button Size (toggle + cog) local customBtnRow customBtnRow, h = W:DualRow(parent, y, - { type="slider", text="Interactable Button Size", min=16, max=40, step=1, - tooltip="Size of mail, calendar, tracking, and minimap button group toggle", - getValue=function() local m = MinimapDB(); return m and m.interactableBtnSize or 21 end, - setValue=function(v) - local m = MinimapDB(); if not m then return end - m.interactableBtnSize = v - RefreshMinimap() - end }, - { type="toggle", text="Outer-Group MM Button Size", - tooltip="Override the size of ungrouped minimap buttons independently from the interactable button size.", - getValue=function() local m = MinimapDB(); return m and m.customBtnSizeEnabled end, - setValue=function(v) - local m = MinimapDB(); if not m then return end - m.customBtnSizeEnabled = v - RefreshMinimap() - EllesmereUI:RefreshPage() - end } + { type="slider", text="Interactable Button Size", min=16, max=40, step=1, + tooltip="Size of mail, calendar, tracking, and minimap button group toggle", + getValue=function() local m = MinimapDB(); return m and m.interactableBtnSize or 21 end, + setValue=function(v) + local m = MinimapDB(); if not m then return end + m.interactableBtnSize = v + RefreshMinimap() + end }, + { type="toggle", text="Outer-Group MM Button Size", + tooltip="Override the size of ungrouped minimap buttons independently from the interactable button size.", + getValue=function() local m = MinimapDB(); return m and m.customBtnSizeEnabled end, + setValue=function(v) + local m = MinimapDB(); if not m then return end + m.customBtnSizeEnabled = v + RefreshMinimap() + EllesmereUI:RefreshPage() + end } ); y = y - h -- Inline cog on Outer-Group MM Button Size for size slider @@ -389,26 +389,26 @@ initFrame:SetScript("OnEvent", function(self) -- Free Move Buttons | Button Backgrounds local fmRow fmRow, h = W:DualRow(parent, y, - { type="toggle", text="Free Move Buttons", - tooltip="When enabled, Shift+Click any minimap button (mail, calendar, tracking, addon buttons) to drag it to a custom position.", - getValue=function() local m = MinimapDB(); return m and m.freeMoveBtns end, - setValue=function(v) - local m = MinimapDB(); if not m then return end - m.freeMoveBtns = v - if not v then - m.btnPositions = {} - end - RefreshMinimap() - EllesmereUI:RefreshPage() - end }, - { type="toggle", text="Button Backgrounds", - tooltip="Show black backgrounds behind minimap indicator buttons (tracking, calendar, mail, crafting, addon buttons, flyout toggle).", - getValue=function() local m = MinimapDB(); return m and m.btnBackgrounds ~= false end, - setValue=function(v) - local m = MinimapDB(); if not m then return end - m.btnBackgrounds = v - FullRebuildMinimap() - end } + { type="toggle", text="Free Move Buttons", + tooltip="When enabled, Shift+Click any minimap button (mail, calendar, tracking, addon buttons) to drag it to a custom position.", + getValue=function() local m = MinimapDB(); return m and m.freeMoveBtns end, + setValue=function(v) + local m = MinimapDB(); if not m then return end + m.freeMoveBtns = v + if not v then + m.btnPositions = {} + end + RefreshMinimap() + EllesmereUI:RefreshPage() + end }, + { type="toggle", text="Button Backgrounds", + tooltip="Show black backgrounds behind minimap indicator buttons (tracking, calendar, mail, crafting, addon buttons, flyout toggle).", + getValue=function() local m = MinimapDB(); return m and m.btnBackgrounds ~= false end, + setValue=function(v) + local m = MinimapDB(); if not m then return end + m.btnBackgrounds = v + FullRebuildMinimap() + end } ); y = y - h -- "Reset" label next to the Free Move toggle (only visible when enabled) @@ -442,11 +442,11 @@ initFrame:SetScript("OnEvent", function(self) -- Hide Extra Buttons (checkbox dropdown) local extraBtnRow extraBtnRow, h = W:DualRow(parent, y, - { type="dropdown", text="Hide Extra Buttons", - values = { __placeholder = "..." }, order = { "__placeholder" }, - getValue = function() return "__placeholder" end, - setValue = function() end }, - { type="label", text="" } + { type="dropdown", text="Hide Extra Buttons", + values = { __placeholder = "..." }, order = { "__placeholder" }, + getValue = function() return "__placeholder" end, + setValue = function() end }, + { type="label", text="" } ); y = y - h -- Replace placeholder with checkbox dropdown @@ -461,19 +461,19 @@ initFrame:SetScript("OnEvent", function(self) } local cbDD, cbDDRefresh = EllesmereUI.BuildVisOptsCBDropdown( - leftRgn, 210, leftRgn:GetFrameLevel() + 2, - EXTRA_BTN_ITEMS, - function(k) - local m = MinimapDB(); if not m then return false end - local heb = m.hideExtraBtns - return heb and heb[k] and true or false - end, - function(k, v) - local m = MinimapDB(); if not m then return end - if not m.hideExtraBtns then m.hideExtraBtns = {} end - m.hideExtraBtns[k] = v - RefreshMinimap() - end) + leftRgn, 210, leftRgn:GetFrameLevel() + 2, + EXTRA_BTN_ITEMS, + function(k) + local m = MinimapDB(); if not m then return false end + local heb = m.hideExtraBtns + return heb and heb[k] and true or false + end, + function(k, v) + local m = MinimapDB(); if not m then return end + if not m.hideExtraBtns then m.hideExtraBtns = {} end + m.hideExtraBtns[k] = v + RefreshMinimap() + end) local PP = EllesmereUI.PP PP.Point(cbDD, "RIGHT", leftRgn, "RIGHT", -20, 0) leftRgn._control = cbDD @@ -483,14 +483,14 @@ initFrame:SetScript("OnEvent", function(self) -- Friends Online: per-section row cap (0 = no cap) _, h = W:DualRow(parent, y, - { type="slider", text="Friends List Cap", min=0, max=50, step=1, - tooltip="Max rows shown per section in the Friends Online tooltip. 0 = no cap.", - getValue=function() local m = MinimapDB(); return m and m.friendsMaxRows or 0 end, - setValue=function(v) - local m = MinimapDB(); if not m then return end - m.friendsMaxRows = v - end }, - { type="label", text="" } + { type="slider", text="Friends List Cap", min=0, max=50, step=1, + tooltip="Max rows shown per section in the Friends Online tooltip. 0 = no cap.", + getValue=function() local m = MinimapDB(); return m and m.friendsMaxRows or 0 end, + setValue=function(v) + local m = MinimapDB(); if not m then return end + m.friendsMaxRows = v + end }, + { type="label", text="" } ); y = y - h y = y - 10 @@ -500,68 +500,68 @@ initFrame:SetScript("OnEvent", function(self) -- Show Zone | Show Clock _, h = W:DualRow(parent, y, - { type="toggle", text="Show Zone", - getValue=function() local m = MinimapDB(); return not (m and m.hideZoneText) end, - setValue=function(v) - local m = MinimapDB(); if not m then return end - m.hideZoneText = not v - RefreshMinimap() - EllesmereUI:RefreshPage() - end }, - { type="toggle", text="Show Clock", - getValue=function() local m = MinimapDB(); return m and m.showClock end, - setValue=function(v) - local m = MinimapDB(); if not m then return end - m.showClock = v - RefreshMinimap() - EllesmereUI:RefreshPage() - end } + { type="toggle", text="Show Zone", + getValue=function() local m = MinimapDB(); return not (m and m.hideZoneText) end, + setValue=function(v) + local m = MinimapDB(); if not m then return end + m.hideZoneText = not v + RefreshMinimap() + EllesmereUI:RefreshPage() + end }, + { type="toggle", text="Show Clock", + getValue=function() local m = MinimapDB(); return m and m.showClock end, + setValue=function(v) + local m = MinimapDB(); if not m then return end + m.showClock = v + RefreshMinimap() + EllesmereUI:RefreshPage() + end } ); y = y - h -- Zone Inside | Clock Inside _, h = W:DualRow(parent, y, - { type="toggle", text="Zone Inside", - tooltip="Display the zone text inside the minimap instead of below it", - disabled=function() local m = MinimapDB(); return m and (m.hideZoneText) end, - disabledTooltip="Enable Show Zone first", - getValue=function() local m = MinimapDB(); return m and m.zoneInside end, - setValue=function(v) - local m = MinimapDB(); if not m then return end - m.zoneInside = v - RefreshMinimap() - end }, - { type="toggle", text="Clock Inside", - tooltip="Display the clock inside the minimap instead of above it", - disabled=function() local m = MinimapDB(); return m and (not m.showClock) end, - disabledTooltip="Enable Show Clock first", - getValue=function() local m = MinimapDB(); return m and m.clockInside end, - setValue=function(v) - local m = MinimapDB(); if not m then return end - m.clockInside = v - RefreshMinimap() - end } + { type="toggle", text="Zone Inside", + tooltip="Display the zone text inside the minimap instead of below it", + disabled=function() local m = MinimapDB(); return m and (m.hideZoneText) end, + disabledTooltip="Enable Show Zone first", + getValue=function() local m = MinimapDB(); return m and m.zoneInside end, + setValue=function(v) + local m = MinimapDB(); if not m then return end + m.zoneInside = v + RefreshMinimap() + end }, + { type="toggle", text="Clock Inside", + tooltip="Display the clock inside the minimap instead of above it", + disabled=function() local m = MinimapDB(); return m and (not m.showClock) end, + disabledTooltip="Enable Show Clock first", + getValue=function() local m = MinimapDB(); return m and m.clockInside end, + setValue=function(v) + local m = MinimapDB(); if not m then return end + m.clockInside = v + RefreshMinimap() + end } ); y = y - h -- Scroll to Zoom | Clock Scale (with cog: X/Y offset) local clockScaleRow clockScaleRow, h = W:DualRow(parent, y, - { type="toggle", text="Scroll to Zoom", - getValue=function() local m = MinimapDB(); return m and m.scrollZoom end, - setValue=function(v) - local m = MinimapDB(); if not m then return end - m.scrollZoom = v - RefreshMinimap() - end }, - { type="slider", text="Clock Scale", min=0.5, max=2.0, step=0.01, - disabled=function() local m = MinimapDB(); return m and (not m.showClock) end, - disabledTooltip="Enable Show Clock first", - getValue=function() local m = MinimapDB(); return m and m.clockScale or 1.15 end, - setValue=function(v) - local m = MinimapDB(); if not m then return end - m.clockScale = v - local bg = _G._EBS_ClockBg - if bg then bg:SetScale(v) end - end } + { type="toggle", text="Scroll to Zoom", + getValue=function() local m = MinimapDB(); return m and m.scrollZoom end, + setValue=function(v) + local m = MinimapDB(); if not m then return end + m.scrollZoom = v + RefreshMinimap() + end }, + { type="slider", text="Clock Scale", min=0.5, max=2.0, step=0.01, + disabled=function() local m = MinimapDB(); return m and (not m.showClock) end, + disabledTooltip="Enable Show Clock first", + getValue=function() local m = MinimapDB(); return m and m.clockScale or 1.15 end, + setValue=function(v) + local m = MinimapDB(); if not m then return end + m.clockScale = v + local bg = _G._EBS_ClockBg + if bg then bg:SetScale(v) end + end } ); y = y - h -- Inline cog on Clock Scale for X/Y offset @@ -627,25 +627,25 @@ initFrame:SetScript("OnEvent", function(self) -- Location Scale (with cog: X/Y offset) | (spacer) local locScaleRow locScaleRow, h = W:DualRow(parent, y, - { type="slider", text="Location Scale", min=0.5, max=2.0, step=0.01, - disabled=function() local m = MinimapDB(); return m and (m.hideZoneText) end, - disabledTooltip="Enable Show Zone first", - getValue=function() local m = MinimapDB(); return m and m.locationScale or 1.15 end, - setValue=function(v) - local m = MinimapDB(); if not m then return end - m.locationScale = v - local bg = _G._EBS_LocationBg - if bg then bg:SetScale(v) end - end }, - { type="toggle", text="Show Coordinates Below Minimap", - tooltip="Always display player coordinates centered below the minimap instead of only on hover.", - getValue=function() local m = MinimapDB(); return m and m.coordsBelow end, - setValue=function(v) - local m = MinimapDB(); if not m then return end - m.coordsBelow = v - RefreshMinimap() - EllesmereUI:RefreshPage() - end } + { type="slider", text="Location Scale", min=0.5, max=2.0, step=0.01, + disabled=function() local m = MinimapDB(); return m and (m.hideZoneText) end, + disabledTooltip="Enable Show Zone first", + getValue=function() local m = MinimapDB(); return m and m.locationScale or 1.15 end, + setValue=function(v) + local m = MinimapDB(); if not m then return end + m.locationScale = v + local bg = _G._EBS_LocationBg + if bg then bg:SetScale(v) end + end }, + { type="toggle", text="Show Coordinates Below Minimap", + tooltip="Always display player coordinates centered below the minimap instead of only on hover.", + getValue=function() local m = MinimapDB(); return m and m.coordsBelow end, + setValue=function(v) + local m = MinimapDB(); if not m then return end + m.coordsBelow = v + RefreshMinimap() + EllesmereUI:RefreshPage() + end } ); y = y - h -- Inline directions icon on Show Coordinates Below for X/Y offset diff --git a/EllesmereUIMinimap/EllesmereUIMinimap.lua b/EllesmereUIMinimap/EllesmereUIMinimap.lua index a17a5e7c..9ed400ec 100644 --- a/EllesmereUIMinimap/EllesmereUIMinimap.lua +++ b/EllesmereUIMinimap/EllesmereUIMinimap.lua @@ -363,7 +363,7 @@ local function LayoutFlyoutButtons() if not icon then for _, region in ipairs({ btn:GetRegions() }) do if region:IsObjectType("Texture") and region:IsShown() - and region:GetAlpha() > 0 and not IsJunkTexture(region) then + and region:GetAlpha() > 0 and not IsJunkTexture(region) then icon = region break end @@ -894,7 +894,7 @@ local function GatherMinimapButtons() elseif IsPinFrame(name) then -- skip pin/POI frames elseif child:IsObjectType("Button") and name - and not name:match("%d+$") then + and not name:match("%d+$") then local w = child:GetWidth() or 0 -- Width gate only for first discovery; once a button is -- tracked in _addonVisible it is always re-collected so @@ -1153,7 +1153,7 @@ local function GetVaultTooltip() if _vaultTT then return _vaultTT end local f = CreateFrame("Frame", nil, UIParent, "BackdropTemplate") f:SetBackdrop({ bgFile = "Interface\\ChatFrame\\ChatFrameBackground", - edgeFile = "Interface\\ChatFrame\\ChatFrameBackground", edgeSize = 1 }) + edgeFile = "Interface\\ChatFrame\\ChatFrameBackground", edgeSize = 1 }) f:SetBackdropColor(0.06, 0.06, 0.06, 0.90) f:SetBackdropBorderColor(0.25, 0.25, 0.25, 1) f:SetFrameStrata("TOOLTIP") @@ -1436,8 +1436,8 @@ local function CreateMinimapPortalFlyout() local btn = CreateFrame("Button", "EUIMinimapPortal" .. i, flyout, "SecureActionButtonTemplate") btn:SetSize(BTN_SIZE, BTN_SIZE) btn:SetPoint("TOPLEFT", flyout, "TOPLEFT", - PADDING + col * (BTN_SIZE + SPACING), - -(PADDING + row * (BTN_SIZE + SPACING))) + PADDING + col * (BTN_SIZE + SPACING), + -(PADDING + row * (BTN_SIZE + SPACING))) btn.spellID = spellID @@ -1505,8 +1505,8 @@ local function CreateMinimapPortalFlyout() local btn = CreateFrame("Button", "EUIMinimapHearth" .. i, flyout, "SecureActionButtonTemplate") btn:SetSize(HS_H, HS_H) btn:SetPoint("TOPLEFT", flyout, "TOPLEFT", - hsX, - -(PADDING + (i - 1) * (HS_H + SPACING))) + hsX, + -(PADDING + (i - 1) * (HS_H + SPACING))) local icon = btn:CreateTexture(nil, "ARTWORK") icon:SetAllPoints() @@ -1605,9 +1605,9 @@ local function CreateMinimapPortalFlyout() btn._hsID = id btn.icon:SetTexture(iconTex) btn.icon:SetTexCoord(aType == "housing" and 0 or 6/64, - aType == "housing" and 1 or 58/64, - aType == "housing" and 0 or 6/64, - aType == "housing" and 1 or 58/64) + aType == "housing" and 1 or 58/64, + aType == "housing" and 0 or 6/64, + aType == "housing" and 1 or 58/64) if aType == "housing" then btn:SetAttribute("type", nil) btn:SetAttribute("macrotext", nil) @@ -2163,30 +2163,30 @@ local function BuildCustomIndicators(minimap) -- Tracking _customIndicators.tracking = CreateIndicatorBtn("_tracking", minimap, - "UI-HUD-Minimap-Tracking-Up", "UI-HUD-Minimap-Tracking-Mouseover", "UI-HUD-Minimap-Tracking-Down", - function(self) - local blizBtn = MinimapCluster and MinimapCluster.Tracking and MinimapCluster.Tracking.Button - if not blizBtn or not blizBtn.OpenMenu then return end - - -- Toggle: close if already open - if blizBtn.menu and blizBtn.menu:IsShown() then - blizBtn.menu:Hide() - return - end + "UI-HUD-Minimap-Tracking-Up", "UI-HUD-Minimap-Tracking-Mouseover", "UI-HUD-Minimap-Tracking-Down", + function(self) + local blizBtn = MinimapCluster and MinimapCluster.Tracking and MinimapCluster.Tracking.Button + if not blizBtn or not blizBtn.OpenMenu then return end + + -- Toggle: close if already open + if blizBtn.menu and blizBtn.menu:IsShown() then + blizBtn.menu:Hide() + return + end - -- Position hidden Blizzard button at our custom button - blizBtn:ClearAllPoints() - blizBtn:SetPoint("CENTER", self, "CENTER", 0, 0) - blizBtn:SetAlpha(0) - blizBtn:EnableMouse(false) - blizBtn:OpenMenu() - - -- Reposition menu so its top aligns with our button's top - if blizBtn.menu then - blizBtn.menu:ClearAllPoints() - blizBtn.menu:SetPoint("TOPRIGHT", self, "TOPLEFT", -4, 0) - end - end) + -- Position hidden Blizzard button at our custom button + blizBtn:ClearAllPoints() + blizBtn:SetPoint("CENTER", self, "CENTER", 0, 0) + blizBtn:SetAlpha(0) + blizBtn:EnableMouse(false) + blizBtn:OpenMenu() + + -- Reposition menu so its top aligns with our button's top + if blizBtn.menu then + blizBtn.menu:ClearAllPoints() + blizBtn.menu:SetPoint("TOPRIGHT", self, "TOPLEFT", -4, 0) + end + end) local trackBaseEnter = _customIndicators.tracking:GetScript("OnEnter") local trackBaseLeave = _customIndicators.tracking:GetScript("OnLeave") _customIndicators.tracking:SetScript("OnEnter", function(self) @@ -2204,10 +2204,10 @@ local function BuildCustomIndicators(minimap) local calDay = tonumber(date("%d")) or 1 local calPrefix = "UI-HUD-Calendar-" .. calDay _customIndicators.calendar = CreateIndicatorBtn("_gameTime", minimap, - calPrefix .. "-Up", calPrefix .. "-Mouseover", calPrefix .. "-Down", - function() - if ToggleCalendar then ToggleCalendar() end - end) + calPrefix .. "-Up", calPrefix .. "-Mouseover", calPrefix .. "-Down", + function() + if ToggleCalendar then ToggleCalendar() end + end) _customIndicators.calendar._calDay = calDay local calBaseEnter = _customIndicators.calendar:GetScript("OnEnter") local calBaseLeave = _customIndicators.calendar:GetScript("OnLeave") @@ -2224,7 +2224,7 @@ local function BuildCustomIndicators(minimap) -- Mail (informational, tooltip on hover, with hover atlas) _customIndicators.mail = CreateIndicatorBtn("_mail", minimap, - "UI-HUD-Minimap-Mail-Up", "UI-HUD-Minimap-Mail-Mouseover", nil, nil) + "UI-HUD-Minimap-Mail-Up", "UI-HUD-Minimap-Mail-Mouseover", nil, nil) local mailBaseEnter = _customIndicators.mail:GetScript("OnEnter") local mailBaseLeave = _customIndicators.mail:GetScript("OnLeave") _customIndicators.mail:SetScript("OnEnter", function(self) @@ -2240,7 +2240,7 @@ local function BuildCustomIndicators(minimap) -- Crafting Order (informational, tooltip on hover, with hover atlas) _customIndicators.crafting = CreateIndicatorBtn("_crafting", minimap, - "UI-HUD-Minimap-CraftingOrder-Up-2x", "UI-HUD-Minimap-CraftingOrder-Over-2x", "UI-HUD-Minimap-CraftingOrder-Down-2x", nil) + "UI-HUD-Minimap-CraftingOrder-Up-2x", "UI-HUD-Minimap-CraftingOrder-Over-2x", "UI-HUD-Minimap-CraftingOrder-Down-2x", nil) local craftBaseEnter = _customIndicators.crafting:GetScript("OnEnter") local craftBaseLeave = _customIndicators.crafting:GetScript("OnLeave") _customIndicators.crafting:SetScript("OnEnter", function(self) @@ -2273,14 +2273,14 @@ local function BuildCustomIndicators(minimap) -- Friends Online button _customIndicators.friends = CreateIndicatorBtn("_friends", minimap, - FRIENDS_ATLAS, FRIENDS_ATLAS, nil, - function() - if InCombatLockdown() then - UIErrorsFrame:AddMessage(ERR_NOT_IN_COMBAT, 1.0, 0.3, 0.3, 1.0) - return - end - ToggleFriendsFrame() - end) + FRIENDS_ATLAS, FRIENDS_ATLAS, nil, + function() + if InCombatLockdown() then + UIErrorsFrame:AddMessage(ERR_NOT_IN_COMBAT, 1.0, 0.3, 0.3, 1.0) + return + end + ToggleFriendsFrame() + end) -- Atlas is not in INDICATOR_ATLAS_RATIO so icon uses inset anchoring -- (TOPLEFT/BOTTOMRIGHT). Desaturate slightly for idle state. if _customIndicators.friends._icon then @@ -2543,8 +2543,8 @@ local function LayoutIndicatorFrames(minimap, p, circleMode) if not icon then for _, region in ipairs({ btn:GetRegions() }) do if region:IsObjectType("Texture") and region:IsShown() - and region:GetAlpha() > 0 and not IsJunkTexture(region) - and region ~= GetFFD(btn).ungroupBg then + and region:GetAlpha() > 0 and not IsJunkTexture(region) + and region ~= GetFFD(btn).ungroupBg then icon = region break end @@ -2626,7 +2626,7 @@ local function LayoutIndicatorFrames(minimap, p, circleMode) ci.friends:ClearAllPoints() if freeMove then local idx = #ungrouped + (flyoutVisible and 1 or 0) - + ((_greatVaultBtn and not heb.greatVault) and 1 or 0) + + ((_greatVaultBtn and not heb.greatVault) and 1 or 0) local yOff = idx * ungroupBtnSize ci.friends:SetPoint("BOTTOMRIGHT", minimap, "BOTTOMLEFT", 0, yOff) elseif anchor then @@ -2650,8 +2650,8 @@ local function LayoutIndicatorFrames(minimap, p, circleMode) _portalBtn:ClearAllPoints() if freeMove then local idx = #ungrouped + (flyoutVisible and 1 or 0) - + ((_greatVaultBtn and not heb.greatVault) and 1 or 0) - + ((ci.friends and not heb.friendsOnline) and 1 or 0) + + ((_greatVaultBtn and not heb.greatVault) and 1 or 0) + + ((ci.friends and not heb.friendsOnline) and 1 or 0) local yOff = idx * ungroupBtnSize _portalBtn:SetPoint("BOTTOMRIGHT", minimap, "BOTTOMLEFT", 0, yOff) elseif anchor then @@ -3373,7 +3373,7 @@ local function ApplyMinimap() if PPa and px and py then local es = minimap:GetEffectiveScale() local isCenterAnchor = (p.position.point == "CENTER") - and (p.position.relPoint == "CENTER" or p.position.relPoint == nil) + and (p.position.relPoint == "CENTER" or p.position.relPoint == nil) if isCenterAnchor and PPa.SnapCenterForDim then px = PPa.SnapCenterForDim(px, minimap:GetWidth() or 0, es) py = PPa.SnapCenterForDim(py, minimap:GetHeight() or 0, es) From efad4f50c88db9eecdd40110ff519f87af026697 Mon Sep 17 00:00:00 2001 From: Lance Date: Mon, 25 May 2026 17:36:36 -0700 Subject: [PATCH 3/9] feat(minimap): right-click menu for friends tooltip rows Adds context menu via MenuUtil with Whisper, Invite, View House, Friends List, Set Note, Favorite toggle, Remove Friend, Report Player. Holds tooltip open while menu is active; falls back to poll when CreateContextMenu returns a non-frame proxy. --- EllesmereUIMinimap/EllesmereUIMinimap.lua | 270 +++++++++++++++++----- 1 file changed, 216 insertions(+), 54 deletions(-) diff --git a/EllesmereUIMinimap/EllesmereUIMinimap.lua b/EllesmereUIMinimap/EllesmereUIMinimap.lua index 9ed400ec..f654ee2b 100644 --- a/EllesmereUIMinimap/EllesmereUIMinimap.lua +++ b/EllesmereUIMinimap/EllesmereUIMinimap.lua @@ -363,7 +363,7 @@ local function LayoutFlyoutButtons() if not icon then for _, region in ipairs({ btn:GetRegions() }) do if region:IsObjectType("Texture") and region:IsShown() - and region:GetAlpha() > 0 and not IsJunkTexture(region) then + and region:GetAlpha() > 0 and not IsJunkTexture(region) then icon = region break end @@ -894,7 +894,7 @@ local function GatherMinimapButtons() elseif IsPinFrame(name) then -- skip pin/POI frames elseif child:IsObjectType("Button") and name - and not name:match("%d+$") then + and not name:match("%d+$") then local w = child:GetWidth() or 0 -- Width gate only for first discovery; once a button is -- tracked in _addonVisible it is always re-collected so @@ -1153,7 +1153,7 @@ local function GetVaultTooltip() if _vaultTT then return _vaultTT end local f = CreateFrame("Frame", nil, UIParent, "BackdropTemplate") f:SetBackdrop({ bgFile = "Interface\\ChatFrame\\ChatFrameBackground", - edgeFile = "Interface\\ChatFrame\\ChatFrameBackground", edgeSize = 1 }) + edgeFile = "Interface\\ChatFrame\\ChatFrameBackground", edgeSize = 1 }) f:SetBackdropColor(0.06, 0.06, 0.06, 0.90) f:SetBackdropBorderColor(0.25, 0.25, 0.25, 1) f:SetFrameStrata("TOOLTIP") @@ -1436,8 +1436,8 @@ local function CreateMinimapPortalFlyout() local btn = CreateFrame("Button", "EUIMinimapPortal" .. i, flyout, "SecureActionButtonTemplate") btn:SetSize(BTN_SIZE, BTN_SIZE) btn:SetPoint("TOPLEFT", flyout, "TOPLEFT", - PADDING + col * (BTN_SIZE + SPACING), - -(PADDING + row * (BTN_SIZE + SPACING))) + PADDING + col * (BTN_SIZE + SPACING), + -(PADDING + row * (BTN_SIZE + SPACING))) btn.spellID = spellID @@ -1505,8 +1505,8 @@ local function CreateMinimapPortalFlyout() local btn = CreateFrame("Button", "EUIMinimapHearth" .. i, flyout, "SecureActionButtonTemplate") btn:SetSize(HS_H, HS_H) btn:SetPoint("TOPLEFT", flyout, "TOPLEFT", - hsX, - -(PADDING + (i - 1) * (HS_H + SPACING))) + hsX, + -(PADDING + (i - 1) * (HS_H + SPACING))) local icon = btn:CreateTexture(nil, "ARTWORK") icon:SetAllPoints() @@ -1605,9 +1605,9 @@ local function CreateMinimapPortalFlyout() btn._hsID = id btn.icon:SetTexture(iconTex) btn.icon:SetTexCoord(aType == "housing" and 0 or 6/64, - aType == "housing" and 1 or 58/64, - aType == "housing" and 0 or 6/64, - aType == "housing" and 1 or 58/64) + aType == "housing" and 1 or 58/64, + aType == "housing" and 0 or 6/64, + aType == "housing" and 1 or 58/64) if aType == "housing" then btn:SetAttribute("type", nil) btn:SetAttribute("macrotext", nil) @@ -1774,7 +1774,7 @@ local function GatherOnlineFriends() if online and name then local short = name:match("^([^%-]+)") or name if short ~= myName then - guild[#guild + 1] = { name = short, full = name, class = classFile, zone = zone or "", level = level } + guild[#guild + 1] = { name = short, full = name, class = classFile, zone = zone or "", level = level, kind = "guild" } end end end @@ -1808,6 +1808,9 @@ local function GatherOnlineFriends() level = gameInfo.characterLevel, bnetTag = acct.accountName, bnetID = acct.bnetAccountID, + isFavorite = acct.isFavorite, + note = acct.note, + kind = "bnet", } if charName then seenBNet[charName] = true end if acct.isFavorite then @@ -1832,6 +1835,8 @@ local function GatherOnlineFriends() class = info.className and info.className:upper():gsub(" ", ""), zone = info.area or "", level = info.level, + note = info.notes, + kind = "char", } end end @@ -1866,18 +1871,70 @@ end -- Hover-stable hide: small grace period so cursor can travel from button to tooltip local _fttHideToken = 0 +local _fttMenuOpen = false local function CancelFTTHide() _fttHideToken = _fttHideToken + 1 end local function ScheduleFTTHide() _fttHideToken = _fttHideToken + 1 + if _fttMenuOpen then return end local mine = _fttHideToken C_Timer.After(0.15, function() if mine ~= _fttHideToken then return end + if _fttMenuOpen then return end if _friendsTT then _friendsTT:Hide() end end) end +StaticPopupDialogs["EBS_SET_FRIEND_NOTE"] = { + text = "Set note for %s:", + button1 = OKAY or "OK", + button2 = CANCEL or "Cancel", + hasEditBox = true, + maxLetters = 48, + OnShow = function(self, data) + self.editBox:SetText((data and data.note) or "") + self.editBox:HighlightText() + self.editBox:SetFocus() + end, + OnAccept = function(self, data) + local txt = self.editBox:GetText() or "" + if not data then return end + if data.kind == "bnet" and data.bnetID and BNSetFriendNote then + BNSetFriendNote(data.bnetID, txt) + elseif data.kind == "char" and C_FriendList and C_FriendList.SetFriendNotes then + C_FriendList.SetFriendNotes(data.name, txt) + end + end, + EditBoxOnEnterPressed = function(self) + local parent = self:GetParent() + if parent and parent.button1 then parent.button1:Click() end + end, + EditBoxOnEscapePressed = function(self) + self:GetParent():Hide() + end, + timeout = 0, + whileDead = true, + hideOnEscape = true, +} + +StaticPopupDialogs["EBS_REMOVE_FRIEND"] = { + text = "Remove %s from your friends list?", + button1 = YES or "Yes", + button2 = NO or "No", + OnAccept = function(_, data) + if not data then return end + if data.kind == "bnet" and data.bnetID and BNRemoveFriend then + BNRemoveFriend(data.bnetID) + elseif data.kind == "char" and C_FriendList and C_FriendList.RemoveFriend then + C_FriendList.RemoveFriend(data.name) + end + end, + timeout = 0, + whileDead = true, + hideOnEscape = true, +} + local function FTTWhisperEntry(e) if not e then return end if e.bnetTag and ChatFrame_SendBNetTell then @@ -1905,6 +1962,107 @@ local function FTTInviteEntry(e) end end +local function FTTVisitHouse(e) + if not e then return end + local target = e.full or e.name + if not target then return end + local eb = (ChatEdit_ChooseBoxForSend and ChatEdit_ChooseBoxForSend()) or ChatFrame1EditBox + if not eb or not ChatEdit_SendText then return end + eb:SetText("/visit " .. target) + ChatEdit_SendText(eb, 0) +end + +local function FTTReportEntry(e) + if not e then return end + local target = e.full or e.name + if not target then return end + local rt = PLAYER_REPORT_TYPE_NAME + if not rt and Enum and Enum.ReportType then rt = Enum.ReportType.PlayerName end + if C_ReportSystem and C_ReportSystem.OpenReportPlayerDialog then + C_ReportSystem.OpenReportPlayerDialog(rt or "name", target) + elseif ReportPlayer then + ReportPlayer("name", target) + end +end + +local function FTTToggleFavorite(e) + if not e or not e.bnetID or not BNSetFriendFavoriteFlag then return end + BNSetFriendFavoriteFlag(e.bnetID, not e.isFavorite) +end + +local function FTTViewFriendsFrame() + if ToggleFriendsFrame then ToggleFriendsFrame(1) end +end + +local function FTTPromptSetNote(e) + if not e then return end + StaticPopup_Show("EBS_SET_FRIEND_NOTE", e.name or "", nil, { + kind = e.kind, + bnetID = e.bnetID, + name = (e.kind == "char") and (e.full or e.name) or e.name, + note = e.note or "", + }) +end + +local function FTTPromptRemoveFriend(e) + if not e then return end + StaticPopup_Show("EBS_REMOVE_FRIEND", e.name or "", nil, { + kind = e.kind, + bnetID = e.bnetID, + name = (e.kind == "char") and (e.full or e.name) or e.name, + }) +end + +local function FTTShowRowMenu(rowBtn, e) + if not e or not e.kind then return end + if not MenuUtil or not MenuUtil.CreateContextMenu then return end + _fttMenuOpen = true + CancelFTTHide() + local menu = MenuUtil.CreateContextMenu(rowBtn, function(_, root) + local cc = e.class and RAID_CLASS_COLORS and RAID_CLASS_COLORS[e.class] + local title = cc and cc:WrapTextInColorCode(e.name or "") or (e.name or "") + root:CreateTitle(title) + + root:CreateButton("Whisper", function() FTTWhisperEntry(e) end) + root:CreateButton("Invite", function() FTTInviteEntry(e) end) + root:CreateButton("View House", function() FTTVisitHouse(e) end) + root:CreateButton("View Friends List", FTTViewFriendsFrame) + + if e.kind == "bnet" or e.kind == "char" then + root:CreateButton("Set Note", function() FTTPromptSetNote(e) end) + end + if e.kind == "bnet" and e.bnetID then + local label = e.isFavorite and "Remove Favorite" or "Add to Favorites" + root:CreateButton(label, function() FTTToggleFavorite(e) end) + end + if e.kind == "bnet" or e.kind == "char" then + root:CreateButton("Remove Friend", function() FTTPromptRemoveFriend(e) end) + end + + root:CreateButton("Report Player", function() FTTReportEntry(e) end) + end) + if menu and menu.HookScript then + menu:HookScript("OnHide", function() + _fttMenuOpen = false + ScheduleFTTHide() + end) + else + -- Fallback: poll menu manager for close, since some Blizzard versions + -- return a non-frame proxy from CreateContextMenu. + local function pollClose() + local mgr = Menu and Menu.GetManager and Menu:GetManager() + local open = mgr and mgr.IsMenuOpen and mgr:IsMenuOpen() + if open then + C_Timer.After(0.1, pollClose) + else + _fttMenuOpen = false + ScheduleFTTHide() + end + end + C_Timer.After(0.1, pollClose) + end +end + local function GetFriendsTT() if _friendsTT then return _friendsTT end local f = CreateFrame("Frame", nil, UIParent) @@ -1945,6 +2103,10 @@ local function EnsureFTTRow(idx) btn:SetScript("OnClick", function(self, mouseButton) local e = self._entry if not e then return end + if mouseButton == "RightButton" then + FTTShowRowMenu(self, e) + return + end if mouseButton ~= "LeftButton" then return end if IsAltKeyDown() then FTTInviteEntry(e) @@ -2163,30 +2325,30 @@ local function BuildCustomIndicators(minimap) -- Tracking _customIndicators.tracking = CreateIndicatorBtn("_tracking", minimap, - "UI-HUD-Minimap-Tracking-Up", "UI-HUD-Minimap-Tracking-Mouseover", "UI-HUD-Minimap-Tracking-Down", - function(self) - local blizBtn = MinimapCluster and MinimapCluster.Tracking and MinimapCluster.Tracking.Button - if not blizBtn or not blizBtn.OpenMenu then return end - - -- Toggle: close if already open - if blizBtn.menu and blizBtn.menu:IsShown() then - blizBtn.menu:Hide() - return - end + "UI-HUD-Minimap-Tracking-Up", "UI-HUD-Minimap-Tracking-Mouseover", "UI-HUD-Minimap-Tracking-Down", + function(self) + local blizBtn = MinimapCluster and MinimapCluster.Tracking and MinimapCluster.Tracking.Button + if not blizBtn or not blizBtn.OpenMenu then return end + + -- Toggle: close if already open + if blizBtn.menu and blizBtn.menu:IsShown() then + blizBtn.menu:Hide() + return + end - -- Position hidden Blizzard button at our custom button - blizBtn:ClearAllPoints() - blizBtn:SetPoint("CENTER", self, "CENTER", 0, 0) - blizBtn:SetAlpha(0) - blizBtn:EnableMouse(false) - blizBtn:OpenMenu() - - -- Reposition menu so its top aligns with our button's top - if blizBtn.menu then - blizBtn.menu:ClearAllPoints() - blizBtn.menu:SetPoint("TOPRIGHT", self, "TOPLEFT", -4, 0) - end - end) + -- Position hidden Blizzard button at our custom button + blizBtn:ClearAllPoints() + blizBtn:SetPoint("CENTER", self, "CENTER", 0, 0) + blizBtn:SetAlpha(0) + blizBtn:EnableMouse(false) + blizBtn:OpenMenu() + + -- Reposition menu so its top aligns with our button's top + if blizBtn.menu then + blizBtn.menu:ClearAllPoints() + blizBtn.menu:SetPoint("TOPRIGHT", self, "TOPLEFT", -4, 0) + end + end) local trackBaseEnter = _customIndicators.tracking:GetScript("OnEnter") local trackBaseLeave = _customIndicators.tracking:GetScript("OnLeave") _customIndicators.tracking:SetScript("OnEnter", function(self) @@ -2204,10 +2366,10 @@ local function BuildCustomIndicators(minimap) local calDay = tonumber(date("%d")) or 1 local calPrefix = "UI-HUD-Calendar-" .. calDay _customIndicators.calendar = CreateIndicatorBtn("_gameTime", minimap, - calPrefix .. "-Up", calPrefix .. "-Mouseover", calPrefix .. "-Down", - function() - if ToggleCalendar then ToggleCalendar() end - end) + calPrefix .. "-Up", calPrefix .. "-Mouseover", calPrefix .. "-Down", + function() + if ToggleCalendar then ToggleCalendar() end + end) _customIndicators.calendar._calDay = calDay local calBaseEnter = _customIndicators.calendar:GetScript("OnEnter") local calBaseLeave = _customIndicators.calendar:GetScript("OnLeave") @@ -2224,7 +2386,7 @@ local function BuildCustomIndicators(minimap) -- Mail (informational, tooltip on hover, with hover atlas) _customIndicators.mail = CreateIndicatorBtn("_mail", minimap, - "UI-HUD-Minimap-Mail-Up", "UI-HUD-Minimap-Mail-Mouseover", nil, nil) + "UI-HUD-Minimap-Mail-Up", "UI-HUD-Minimap-Mail-Mouseover", nil, nil) local mailBaseEnter = _customIndicators.mail:GetScript("OnEnter") local mailBaseLeave = _customIndicators.mail:GetScript("OnLeave") _customIndicators.mail:SetScript("OnEnter", function(self) @@ -2240,7 +2402,7 @@ local function BuildCustomIndicators(minimap) -- Crafting Order (informational, tooltip on hover, with hover atlas) _customIndicators.crafting = CreateIndicatorBtn("_crafting", minimap, - "UI-HUD-Minimap-CraftingOrder-Up-2x", "UI-HUD-Minimap-CraftingOrder-Over-2x", "UI-HUD-Minimap-CraftingOrder-Down-2x", nil) + "UI-HUD-Minimap-CraftingOrder-Up-2x", "UI-HUD-Minimap-CraftingOrder-Over-2x", "UI-HUD-Minimap-CraftingOrder-Down-2x", nil) local craftBaseEnter = _customIndicators.crafting:GetScript("OnEnter") local craftBaseLeave = _customIndicators.crafting:GetScript("OnLeave") _customIndicators.crafting:SetScript("OnEnter", function(self) @@ -2273,14 +2435,14 @@ local function BuildCustomIndicators(minimap) -- Friends Online button _customIndicators.friends = CreateIndicatorBtn("_friends", minimap, - FRIENDS_ATLAS, FRIENDS_ATLAS, nil, - function() - if InCombatLockdown() then - UIErrorsFrame:AddMessage(ERR_NOT_IN_COMBAT, 1.0, 0.3, 0.3, 1.0) - return - end - ToggleFriendsFrame() - end) + FRIENDS_ATLAS, FRIENDS_ATLAS, nil, + function() + if InCombatLockdown() then + UIErrorsFrame:AddMessage(ERR_NOT_IN_COMBAT, 1.0, 0.3, 0.3, 1.0) + return + end + ToggleFriendsFrame() + end) -- Atlas is not in INDICATOR_ATLAS_RATIO so icon uses inset anchoring -- (TOPLEFT/BOTTOMRIGHT). Desaturate slightly for idle state. if _customIndicators.friends._icon then @@ -2543,8 +2705,8 @@ local function LayoutIndicatorFrames(minimap, p, circleMode) if not icon then for _, region in ipairs({ btn:GetRegions() }) do if region:IsObjectType("Texture") and region:IsShown() - and region:GetAlpha() > 0 and not IsJunkTexture(region) - and region ~= GetFFD(btn).ungroupBg then + and region:GetAlpha() > 0 and not IsJunkTexture(region) + and region ~= GetFFD(btn).ungroupBg then icon = region break end @@ -2626,7 +2788,7 @@ local function LayoutIndicatorFrames(minimap, p, circleMode) ci.friends:ClearAllPoints() if freeMove then local idx = #ungrouped + (flyoutVisible and 1 or 0) - + ((_greatVaultBtn and not heb.greatVault) and 1 or 0) + + ((_greatVaultBtn and not heb.greatVault) and 1 or 0) local yOff = idx * ungroupBtnSize ci.friends:SetPoint("BOTTOMRIGHT", minimap, "BOTTOMLEFT", 0, yOff) elseif anchor then @@ -2650,8 +2812,8 @@ local function LayoutIndicatorFrames(minimap, p, circleMode) _portalBtn:ClearAllPoints() if freeMove then local idx = #ungrouped + (flyoutVisible and 1 or 0) - + ((_greatVaultBtn and not heb.greatVault) and 1 or 0) - + ((ci.friends and not heb.friendsOnline) and 1 or 0) + + ((_greatVaultBtn and not heb.greatVault) and 1 or 0) + + ((ci.friends and not heb.friendsOnline) and 1 or 0) local yOff = idx * ungroupBtnSize _portalBtn:SetPoint("BOTTOMRIGHT", minimap, "BOTTOMLEFT", 0, yOff) elseif anchor then @@ -3373,7 +3535,7 @@ local function ApplyMinimap() if PPa and px and py then local es = minimap:GetEffectiveScale() local isCenterAnchor = (p.position.point == "CENTER") - and (p.position.relPoint == "CENTER" or p.position.relPoint == nil) + and (p.position.relPoint == "CENTER" or p.position.relPoint == nil) if isCenterAnchor and PPa.SnapCenterForDim then px = PPa.SnapCenterForDim(px, minimap:GetWidth() or 0, es) py = PPa.SnapCenterForDim(py, minimap:GetHeight() or 0, es) From 4bd42cc883bb5982ad516a1375bc4e7ba2f00fbf Mon Sep 17 00:00:00 2001 From: Lance Date: Mon, 25 May 2026 18:10:05 -0700 Subject: [PATCH 4/9] feat(minimap): remove View House from friends menu --- EllesmereUIMinimap/EllesmereUIMinimap.lua | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/EllesmereUIMinimap/EllesmereUIMinimap.lua b/EllesmereUIMinimap/EllesmereUIMinimap.lua index f654ee2b..d907cac3 100644 --- a/EllesmereUIMinimap/EllesmereUIMinimap.lua +++ b/EllesmereUIMinimap/EllesmereUIMinimap.lua @@ -1962,16 +1962,6 @@ local function FTTInviteEntry(e) end end -local function FTTVisitHouse(e) - if not e then return end - local target = e.full or e.name - if not target then return end - local eb = (ChatEdit_ChooseBoxForSend and ChatEdit_ChooseBoxForSend()) or ChatFrame1EditBox - if not eb or not ChatEdit_SendText then return end - eb:SetText("/visit " .. target) - ChatEdit_SendText(eb, 0) -end - local function FTTReportEntry(e) if not e then return end local target = e.full or e.name @@ -2025,7 +2015,6 @@ local function FTTShowRowMenu(rowBtn, e) root:CreateButton("Whisper", function() FTTWhisperEntry(e) end) root:CreateButton("Invite", function() FTTInviteEntry(e) end) - root:CreateButton("View House", function() FTTVisitHouse(e) end) root:CreateButton("View Friends List", FTTViewFriendsFrame) if e.kind == "bnet" or e.kind == "char" then From 7eb67c10cebfd6531ce91337807e27fc46979ff5 Mon Sep 17 00:00:00 2001 From: Lance Date: Mon, 25 May 2026 18:48:20 -0700 Subject: [PATCH 5/9] feat(minimap): sort friends lists by zone then name --- EllesmereUIMinimap/EllesmereUIMinimap.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/EllesmereUIMinimap/EllesmereUIMinimap.lua b/EllesmereUIMinimap/EllesmereUIMinimap.lua index d907cac3..ccebad96 100644 --- a/EllesmereUIMinimap/EllesmereUIMinimap.lua +++ b/EllesmereUIMinimap/EllesmereUIMinimap.lua @@ -1852,6 +1852,17 @@ local function GatherOnlineFriends() if guildSet[favorites[i].name] then table.remove(favorites, i) end end + -- Sort by zone (empty zones last), then name -- groups same-location together + local function byZone(a, b) + local az, bz = a.zone or "", b.zone or "" + if (az == "") ~= (bz == "") then return az ~= "" end + if az ~= bz then return az < bz end + return (a.name or "") < (b.name or "") + end + table.sort(guild, byZone) + table.sort(favorites, byZone) + table.sort(friends, byZone) + return guild, favorites, friends end From e9e1ec3da310fa4baed21aba78c6662316ad17f2 Mon Sep 17 00:00:00 2001 From: Lance Date: Tue, 26 May 2026 18:25:02 -0700 Subject: [PATCH 6/9] fix right click smart whisper --- EllesmereUIMinimap/EllesmereUIMinimap.lua | 58 +++++++++++++++++++++-- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/EllesmereUIMinimap/EllesmereUIMinimap.lua b/EllesmereUIMinimap/EllesmereUIMinimap.lua index ccebad96..1c46c5ce 100644 --- a/EllesmereUIMinimap/EllesmereUIMinimap.lua +++ b/EllesmereUIMinimap/EllesmereUIMinimap.lua @@ -1946,15 +1946,65 @@ StaticPopupDialogs["EBS_REMOVE_FRIEND"] = { hideOnEscape = true, } +local function _matchGA(gi, charName, fullName) + if not gi or not gi.isOnline or gi.clientProgram ~= "WoW" then return false end + if not gi.characterName then return false end + if gi.characterName == charName then return true end + if fullName and gi.realmName and gi.realmName ~= "" then + if (gi.characterName .. "-" .. gi.realmName) == fullName then return true end + end + return false +end + +local function FindBNetTagForChar(charName, fullName) + if not charName then return nil end + local num = BNGetNumFriends and BNGetNumFriends() or 0 + for i = 1, num do + local acct = C_BattleNet and C_BattleNet.GetFriendAccountInfo and C_BattleNet.GetFriendAccountInfo(i) + if acct then + local numGA = C_BattleNet.GetFriendNumGameAccounts and C_BattleNet.GetFriendNumGameAccounts(i) or 0 + for j = 1, numGA do + local gi = C_BattleNet.GetFriendGameAccountInfo and C_BattleNet.GetFriendGameAccountInfo(i, j) + if _matchGA(gi, charName, fullName) then + return acct.accountName, acct.bnetAccountID + end + end + if numGA == 0 and _matchGA(acct.gameAccountInfo, charName, fullName) then + return acct.accountName, acct.bnetAccountID + end + end + end + return nil +end + +local function _OpenWhisperEditBox(chatType, target) + local eb = ChatEdit_ChooseBoxForSend and ChatEdit_ChooseBoxForSend() + or (DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.editBox) + if not eb then return false end + eb:SetAttribute("chatType", chatType) + eb:SetAttribute("tellTarget", target) + if ChatEdit_UpdateHeader then ChatEdit_UpdateHeader(eb) end + if ChatEdit_ActivateChat then ChatEdit_ActivateChat(eb) else eb:Show() end + return true +end + local function FTTWhisperEntry(e) if not e then return end - if e.bnetTag and ChatFrame_SendBNetTell then - ChatFrame_SendBNetTell(e.bnetTag) + local tag = e.bnetTag or FindBNetTagForChar(e.name, e.full) + if tag then + if ChatFrame_SendBNetTell then + ChatFrame_SendBNetTell(tag) + else + _OpenWhisperEditBox("BN_WHISPER", tag) + end return end local target = e.full or e.name - if target and ChatFrame_OpenChat then - ChatFrame_OpenChat("/w " .. target .. " ") + if not target then return end + if ChatFrame_SendTell then + ChatFrame_SendTell(target) + else + _OpenWhisperEditBox("WHISPER", target) end end From 0e860902a65f50cb5682fbf33150f690aef64195 Mon Sep 17 00:00:00 2001 From: Lance Date: Tue, 26 May 2026 19:30:09 -0700 Subject: [PATCH 7/9] fix: suggest invite --- EllesmereUIMinimap/EllesmereUIMinimap.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/EllesmereUIMinimap/EllesmereUIMinimap.lua b/EllesmereUIMinimap/EllesmereUIMinimap.lua index 1c46c5ce..35ea4c80 100644 --- a/EllesmereUIMinimap/EllesmereUIMinimap.lua +++ b/EllesmereUIMinimap/EllesmereUIMinimap.lua @@ -2075,7 +2075,11 @@ local function FTTShowRowMenu(rowBtn, e) root:CreateTitle(title) root:CreateButton("Whisper", function() FTTWhisperEntry(e) end) - root:CreateButton("Invite", function() FTTInviteEntry(e) end) + local canInviteDirectly = not IsInGroup() + or UnitIsGroupLeader("player") + or UnitIsGroupAssistant("player") + local inviteLabel = canInviteDirectly and "Invite" or "Suggest Invite" + root:CreateButton(inviteLabel, function() FTTInviteEntry(e) end) root:CreateButton("View Friends List", FTTViewFriendsFrame) if e.kind == "bnet" or e.kind == "char" then From e2d359b04c0a00906cefa4f6a1effddd5ddf6ce7 Mon Sep 17 00:00:00 2001 From: Lance Date: Thu, 28 May 2026 17:50:49 -0700 Subject: [PATCH 8/9] showing battle tag like normal friends list to left of name sorted a-z --- EllesmereUIMinimap/EllesmereUIMinimap.lua | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/EllesmereUIMinimap/EllesmereUIMinimap.lua b/EllesmereUIMinimap/EllesmereUIMinimap.lua index 1104fe08..6473e311 100644 --- a/EllesmereUIMinimap/EllesmereUIMinimap.lua +++ b/EllesmereUIMinimap/EllesmereUIMinimap.lua @@ -1800,13 +1800,17 @@ local function GatherOnlineFriends() if charName and realm and realm ~= "" then full = charName .. "-" .. realm end + -- Battle tag without the #discriminator (fall back to accountName/RealID) + local rawTag = acct.battleTag or acct.accountName + local tagName = rawTag and rawTag:match("^([^#]+)") or rawTag local entry = { - name = charName or acct.accountName or "???", + name = charName or tagName or "???", full = full, class = classFile, zone = zone, level = gameInfo.characterLevel, - bnetTag = acct.accountName, + bnetTag = tagName, + bnetName = acct.accountName or acct.battleTag, bnetID = acct.bnetAccountID, isFavorite = acct.isFavorite, note = acct.note, @@ -1859,9 +1863,13 @@ local function GatherOnlineFriends() if az ~= bz then return az < bz end return (a.name or "") < (b.name or "") end + -- Friends/favorites sort A-Z by battle tag (fall back to name); guild stays grouped by zone + local function byTag(a, b) + return (a.bnetTag or a.name or ""):lower() < (b.bnetTag or b.name or ""):lower() + end table.sort(guild, byZone) - table.sort(favorites, byZone) - table.sort(friends, byZone) + table.sort(favorites, byTag) + table.sort(friends, byTag) return guild, favorites, friends end @@ -1990,7 +1998,7 @@ end local function FTTWhisperEntry(e) if not e then return end - local tag = e.bnetTag or FindBNetTagForChar(e.name, e.full) + local tag = e.bnetName or e.bnetTag or FindBNetTagForChar(e.name, e.full) if tag then if ChatFrame_SendBNetTell then ChatFrame_SendBNetTell(tag) @@ -2308,6 +2316,10 @@ local function ShowFriendsTooltip(anchor) local cc = e.class and RAID_CLASS_COLORS and RAID_CLASS_COLORS[e.class] local colored = cc and cc:WrapTextInColorCode(e.name) or e.name + -- BNet friends: show battle tag before the in-game name, e.g. "Bigmacz (Unholyftw)" + if e.bnetTag then + colored = "|cffffd100" .. e.bnetTag .. "|r (" .. colored .. ")" + end row.name:SetText(colored) row.name:SetTextColor(1, 1, 1, 0.85) From 6131f60c4d1a2b8f4197bb484eb921cf994d9090 Mon Sep 17 00:00:00 2001 From: Lance Date: Sun, 31 May 2026 17:59:40 -0700 Subject: [PATCH 9/9] levels next to ign --- EllesmereUIMinimap/EllesmereUIMinimap.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/EllesmereUIMinimap/EllesmereUIMinimap.lua b/EllesmereUIMinimap/EllesmereUIMinimap.lua index 6473e311..3d06caea 100644 --- a/EllesmereUIMinimap/EllesmereUIMinimap.lua +++ b/EllesmereUIMinimap/EllesmereUIMinimap.lua @@ -2320,6 +2320,11 @@ local function ShowFriendsTooltip(anchor) if e.bnetTag then colored = "|cffffd100" .. e.bnetTag .. "|r (" .. colored .. ")" end + -- Level to the right of the in-game name, e.g. "Unholyftw 80" + local lvl = tonumber(e.level) + if lvl and lvl > 0 then + colored = colored .. " |cffb0b0b0" .. lvl .. "|r" + end row.name:SetText(colored) row.name:SetTextColor(1, 1, 1, 0.85)