ca2b78e217
/fui keeps edit mode, reset and list. The thirty diagnostic and tuning functions behind the other subcommands (bar, bags, character, tabs, close, model, minimap, mouse, map, questlog, chat, chatlines, tabard...) go with them, with Panes.Report, the bags read-back and the 205 texts they alone used. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1659 lines
60 KiB
Lua
1659 lines
60 KiB
Lua
-- ForeverUI: WotLK's Dungeon Finder in a movable window styled like camelot's LFGParentFrame
|
|
-- (Blizzard_GroupFinder_VanillaStyle; camelot has no dungeon finder), with the raid browser
|
|
-- as a second side tab (GroupFinderRaid.lua).
|
|
--
|
|
-- All data and actions come from WotLK (LFDFrame.lua, LFGFrame.lua). LFDParentFrame stays the
|
|
-- client's: the micro button, /lfd, the key binding, the minimap eye, Escape and NPC gossip
|
|
-- open and close it. Its content (LFDQueueFrame) stays shown but transparent under our window,
|
|
-- so its logic keeps running; we read its state after each update and act through its
|
|
-- functions (LFDQueueFrame_SetType, list checkboxes and +/-, role checkboxes, search button).
|
|
--
|
|
-- Layout from blizzard_lfgvanilla_parentframe.xml, blizzard_lfgvanilla_listing.xml/.lua,
|
|
-- lfgvanilla_constants.lua, sharedutils.lua and blizzard_sharedxml (values in G).
|
|
-- camelot -> WotLK:
|
|
-- categories the "Type" menu: one button per displayable random dungeon, then
|
|
-- "Specific Dungeons"; the chosen type's button shows the selection
|
|
-- list the specific dungeon list, drawn as the category list (F.createCatList)
|
|
-- details text and rewards of the chosen random dungeon, under the buttons
|
|
-- (camelot has none)
|
|
-- Back, Post Back returns to the categories (list only); Post clicks the client's
|
|
-- search button (Find Group / Join as Party / Leave Queue)
|
|
-- veils WotLK's cooldown, backfill and "no LFD while in LFR" veils, over the
|
|
-- details or the whole list
|
|
-- Differences: no options button, no comment under the list, WotLK's lock replaces a locked
|
|
-- dungeon's checkbox, no heroic icon on headers (their name says it).
|
|
|
|
local ForeverUI = ForeverUI or {}
|
|
_G.ForeverUI = ForeverUI
|
|
|
|
local F = {}
|
|
ForeverUI.GroupFinder = F
|
|
|
|
local SEP = string.char(92)
|
|
local L = ForeverUI.L
|
|
local LFG = "Interface" .. SEP .. "LFGFrame" .. SEP
|
|
local BUTTONS = "Interface" .. SEP .. "Buttons" .. SEP
|
|
|
|
-- Layout values in one table: Lua 5.1 allows a function at most 60 upvalues.
|
|
local G = {
|
|
width = 458, height = 535,
|
|
rock = "interface" .. SEP .. "ForeverUI" .. SEP .. "framegeneral" .. SEP .. "ui-background-rock",
|
|
portraitSide = 62, portraitX = -5, portraitY = 7,
|
|
titleX1 = 58, titleX2 = -24, titleY = -1, titleH = 20, titleTextY = -5,
|
|
closeButton = 24, closeButtonX = 0, closeButtonY = 0,
|
|
blue = "interface" .. SEP .. "ForeverUI" .. SEP .. "lfgframe" .. SEP .. "ui-lfg-bluebg",
|
|
blueX1 = 2, blueY1 = -25, blueX2 = 248, blueY2 = -169,
|
|
role = 64, roleY = -41, roleBackground = 80, roleCheckbox = 24, roleCheckboxX = -5, roleCheckboxY = -5,
|
|
roleIcons = LFG .. "UI-LFG-ICON-ROLES",
|
|
roleBackgrounds = LFG .. "UI-LFG-ICONS-ROLEBACKGROUNDS",
|
|
frameBoxX1 = 4, frameBoxY1 = -118, frameBoxX2 = -4, frameBoxY2 = 37,
|
|
backgroundTop = 0.093,
|
|
catX1 = 8, catY1 = -124, catX2 = -8, catY2 = 40, catFirst = -4, catStep = 50,
|
|
-- rule under the buttons, then the details; the details edges follow the 380-wide buttons
|
|
-- centered in the 442-wide area (8 + (442 - 380) / 2 = 39), left edge 6 further in
|
|
ruleGap = 6, ruleH = 16, detailsGap = 6, detailsPadding = 6, tolerance = 2, detailsX1 = 45, detailsX2 = -39, detailsBottom = 50,
|
|
scrollStep = 20,
|
|
catW = 380, catH = 50, catSelW = 366, catSelH = 40,
|
|
-- image in the button: camelot's (5,-8 / -5,5), scaled to a 50-high button
|
|
catImageTop = -6, catImageBottom = 4,
|
|
mega = "interface" .. SEP .. "ForeverUI" .. SEP .. "pvpframe" .. SEP .. "pvpmegaqueue",
|
|
pieces = "Interface" .. SEP .. "Icons" .. SEP .. "inv_misc_coin_02",
|
|
listX1 = 7, listY1 = -125, listX2 = -7, listY2 = 40, margin = 4, barRight = -28,
|
|
row = 22, plus = 16, checkbox = 30, levelL = 60,
|
|
backL = 111, postW = 109, buttonH = 28, buttonBottom = 6, buttonSide = 4,
|
|
tab = 55, tabY = -60, tabGap = -2, tabIcon = 50, tabIconX = -3, crop = 0.03125,
|
|
veilW = 330, veilH = 257,
|
|
}
|
|
|
|
-- texcoords from sharedutils.lua (GetTexCoordsForRole: 256 x 256, 67 px cells;
|
|
-- GetBackgroundTexCoordsForRole: 256 x 128, 75 px cells)
|
|
local ROLES = {
|
|
{ key = "tank", client = "LFDQueueFrameRoleButtonTank", id = 2, x = 70, alpha = 0.6,
|
|
icon = { 0, 0.26171875, 0.26171875, 0.5234375 }, background = { 0.29296875, 0.5859375, 0, 0.5859375 } },
|
|
{ key = "healer", client = "LFDQueueFrameRoleButtonHealer", id = 3, x = 174, alpha = 0.4,
|
|
icon = { 0.26171875, 0.5234375, 0, 0.26171875 }, background = { 0, 0.29296875, 0, 0.5859375 } },
|
|
{ key = "damage", client = "LFDQueueFrameRoleButtonDPS", id = 1, x = 278, alpha = 0.6,
|
|
icon = { 0.26171875, 0.5234375, 0.26171875, 0.5234375 }, background = { 0.5859375, 0.87890625, 0, 0.5859375 } },
|
|
-- camelot's fourth slot (TOPRIGHT -20,-41) holds WotLK's leader role, without background
|
|
{ key = "leader", client = "LFDQueueFrameRoleButtonLeader", id = 4, right = -20,
|
|
icon = { 0, 0.26171875, 0, 0.26171875 } },
|
|
}
|
|
local ROLE_VEIL = { 0, 0.2617, 0.5234, 0.7851 }
|
|
|
|
-- colors of the fontstyles.xml fonts (LFGActivityHeader, LFGActivityEntry...)
|
|
local COLORS = {
|
|
header = { 0.7, 0.7, 0.7 },
|
|
entry = { 1.0, 0.82, 0 },
|
|
easy = { 0.5, 0.5, 0.5 },
|
|
hard = { 1.0, 0.5, 0.25 },
|
|
}
|
|
|
|
-- category button image
|
|
local ART = {
|
|
random = "groupfinder-button-dungeons",
|
|
holiday = "groupfinder-button-questing",
|
|
specific = "groupfinder-button-custom-pve",
|
|
}
|
|
|
|
local function txt(key)
|
|
return _G[key] or key
|
|
end
|
|
|
|
-- 3.3.5 returns 1 or nil (the test bench 0 or 1), so compare to 1
|
|
local function active(b)
|
|
return b ~= nil and b.IsEnabled ~= nil and b:IsEnabled() == 1
|
|
end
|
|
|
|
-- truly visible: the frame and its parents up to LFDParentFrame are shown
|
|
local function display(c)
|
|
while c do
|
|
if not c:IsShown() then return false end
|
|
if c == LFDParentFrame then return true end
|
|
c = c:GetParent()
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- ------------------------------------------------------------------ Frame
|
|
|
|
local METAL = {
|
|
{ key = "topLeft", name = "ui-frame-portraitmetal-cornertopleft", point = "TOPLEFT", x = -13, y = 16 },
|
|
{ key = "topRight", name = "ui-frame-metal-cornertopright", point = "TOPRIGHT", x = 2, y = 16 },
|
|
{ key = "bottomLeft", name = "ui-frame-metal-cornerbottomleft", point = "BOTTOMLEFT", x = -13, y = -8 },
|
|
{ key = "bottomRight", name = "ui-frame-metal-cornerbottomright", point = "BOTTOMRIGHT", x = 2, y = -8 },
|
|
}
|
|
|
|
local function redCloseButton(b)
|
|
for _, state in ipairs({
|
|
{ "SetNormalTexture", "GetNormalTexture", "redbutton-exit" },
|
|
{ "SetPushedTexture", "GetPushedTexture", "redbutton-exit-pressed" },
|
|
{ "SetHighlightTexture", "GetHighlightTexture", "redbutton-highlight" },
|
|
}) do
|
|
local e = ForeverUI.AtlasEntry(state[3])
|
|
b[state[1]](b, e and e[1] or "")
|
|
local t = b[state[2]](b)
|
|
if t then
|
|
ForeverUI.SetAtlas(t, state[3], true)
|
|
t:ClearAllPoints()
|
|
t:SetAllPoints(b)
|
|
if state[3] == "redbutton-highlight" then t:SetBlendMode("ADD") end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function buildFrame(f)
|
|
local rock = f:CreateTexture(nil, "BACKGROUND")
|
|
rock:SetTexture(G.rock, true)
|
|
if rock.SetHorizTile then rock:SetHorizTile(true) rock:SetVertTile(true) end
|
|
rock:SetPoint("TOPLEFT", f, "TOPLEFT", 2, -21)
|
|
rock:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -2, 2)
|
|
|
|
local stripes = f:CreateTexture(nil, "BORDER")
|
|
ForeverUI.SetAtlas(stripes, "_ui-frame-toptilestreaks", true)
|
|
stripes:SetHeight(43)
|
|
stripes:SetPoint("TOPLEFT", f, "TOPLEFT", 6, -21)
|
|
stripes:SetPoint("TOPRIGHT", f, "TOPRIGHT", -2, -21)
|
|
|
|
local metal = CreateFrame("Frame", nil, f)
|
|
metal:SetAllPoints(f)
|
|
metal:SetFrameLevel(f:GetFrameLevel() + 20)
|
|
local p = {}
|
|
for _, corner in ipairs(METAL) do
|
|
local t = metal:CreateTexture(nil, "OVERLAY")
|
|
ForeverUI.SetAtlas(t, corner.name)
|
|
t:SetPoint(corner.point, metal, corner.point, corner.x, corner.y)
|
|
p[corner.key] = t
|
|
end
|
|
local function edge(name, a1, c1, r1, a2, c2, r2)
|
|
local t = metal:CreateTexture(nil, "OVERLAY")
|
|
ForeverUI.SetAtlas(t, name)
|
|
t:SetPoint(a1, c1, r1)
|
|
t:SetPoint(a2, c2, r2)
|
|
end
|
|
edge("_ui-frame-metal-edgetop", "TOPLEFT", p.topLeft, "TOPRIGHT", "TOPRIGHT", p.topRight, "TOPLEFT")
|
|
edge("_ui-frame-metal-edgebottom", "BOTTOMLEFT", p.bottomLeft, "BOTTOMRIGHT", "BOTTOMRIGHT", p.bottomRight, "BOTTOMLEFT")
|
|
edge("!ui-frame-metal-edgeleft", "TOPLEFT", p.topLeft, "BOTTOMLEFT", "BOTTOMLEFT", p.bottomLeft, "TOPLEFT")
|
|
edge("!ui-frame-metal-edgeright", "TOPRIGHT", p.topRight, "BOTTOMRIGHT", "BOTTOMRIGHT", p.bottomRight, "TOPRIGHT")
|
|
|
|
-- portrait: camelot's eye (SetPortraitAtlasRaw, 62 x 62)
|
|
local portraitFrame = CreateFrame("Frame", nil, f)
|
|
portraitFrame:SetAllPoints(f)
|
|
portraitFrame:SetFrameLevel(f:GetFrameLevel() + 19)
|
|
local portrait = portraitFrame:CreateTexture(nil, "OVERLAY")
|
|
ForeverUI.SetAtlas(portrait, "groupfinder-eye-frame", true)
|
|
portrait:SetWidth(G.portraitSide)
|
|
portrait:SetHeight(G.portraitSide)
|
|
portrait:SetPoint("TOPLEFT", f, "TOPLEFT", G.portraitX, G.portraitY)
|
|
f.portrait = portrait
|
|
|
|
local banner = CreateFrame("Frame", nil, f)
|
|
banner:SetFrameLevel(f:GetFrameLevel() + 21)
|
|
banner:SetHeight(G.titleH)
|
|
banner:SetPoint("TOPLEFT", f, "TOPLEFT", G.titleX1, G.titleY)
|
|
banner:SetPoint("TOPRIGHT", f, "TOPRIGHT", G.titleX2, G.titleY)
|
|
f.title = banner:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
|
f.title:SetPoint("TOP", banner, "TOP", 0, G.titleTextY)
|
|
f.title:SetText(txt("LFG_TITLE"))
|
|
f.banner = banner
|
|
|
|
-- The close button hides the client panel that holds the window, like the client's own.
|
|
local closeButton = CreateFrame("Button", "ForeverUIGroupFinderCloseButton", f)
|
|
closeButton:SetWidth(G.closeButton)
|
|
closeButton:SetHeight(G.closeButton)
|
|
closeButton:SetFrameLevel(f:GetFrameLevel() + 22)
|
|
closeButton:SetPoint("TOPRIGHT", f, "TOPRIGHT", G.closeButtonX, G.closeButtonY)
|
|
redCloseButton(closeButton)
|
|
closeButton:SetScript("OnClick", function() HideUIPanel(F.frame:GetParent()) end)
|
|
f.closeButton = closeButton
|
|
end
|
|
|
|
-- ------------------------------------------------------------ Tabs
|
|
-- LargeSideTabButtonTemplate, as on the character sheet (CharacterFrame.lua): common-sidetab
|
|
-- background, icon baked by tools/bake_masks.py (50 x 50 at (-3, 0), cropped by 0.03125),
|
|
-- common-sidetab-selected marker, common-sidetab-hover highlight.
|
|
local ICONS = "Interface" .. SEP .. "ForeverUI" .. SEP .. "tabicons" .. SEP
|
|
local TABS = {
|
|
{ key = "dungeons", text = "LOOKING_FOR_DUNGEON", icon = ICONS .. "inv_helmet_08" },
|
|
-- a single tab for the raid browser
|
|
{ key = "raid", text = "LOOKING_FOR_RAID", raid = true,
|
|
icon = ICONS .. "achievement_general_stayclassy" },
|
|
}
|
|
|
|
local function createTab(f, def, n)
|
|
local o = CreateFrame("Button", "ForeverUIGroupFinderTab" .. n, f)
|
|
o:SetWidth(G.tab)
|
|
o:SetHeight(G.tab)
|
|
local background = o:CreateTexture(nil, "BACKGROUND")
|
|
ForeverUI.SetAtlas(background, "common-sidetab", true)
|
|
background:SetAllPoints(o)
|
|
local icon = o:CreateTexture(nil, "ARTWORK")
|
|
icon:SetWidth(G.tabIcon)
|
|
icon:SetHeight(G.tabIcon)
|
|
icon:SetPoint("CENTER", o, "CENTER", G.tabIconX, 0)
|
|
icon:SetTexCoord(G.crop, 1 - G.crop, G.crop, 1 - G.crop)
|
|
icon:SetTexture(def.icon)
|
|
o.icon = icon
|
|
local selected = o:CreateTexture(nil, "OVERLAY")
|
|
ForeverUI.SetAtlas(selected, "common-sidetab-selected", true)
|
|
selected:SetAllPoints(o)
|
|
selected:Hide()
|
|
o.selected = selected
|
|
local hover = o:CreateTexture(nil, "HIGHLIGHT")
|
|
ForeverUI.SetAtlas(hover, "common-sidetab-hover", true)
|
|
hover:SetAllPoints(o)
|
|
o:SetScript("OnEnter", function(self)
|
|
GameTooltip:SetOwner(self, "ANCHOR_RIGHT", -4, -4)
|
|
GameTooltip:SetText(txt(def.text))
|
|
if def.detail then GameTooltip:AddLine(txt(def.detail), 1, 1, 1) end
|
|
GameTooltip:Show()
|
|
end)
|
|
o:SetScript("OnLeave", function() GameTooltip:Hide() end)
|
|
o:SetScript("OnClick", function()
|
|
PlaySound("igCharacterInfoTab")
|
|
F.goTo(def.key)
|
|
end)
|
|
o.key = def.key
|
|
return o
|
|
end
|
|
|
|
function F.selectTab(key)
|
|
F.tab = key
|
|
for _, o in ipairs(F.tabs or {}) do
|
|
if o.key == key then o.selected:Show() else o.selected:Hide() end
|
|
end
|
|
end
|
|
|
|
-- ------------------------------------------------------------ Roles
|
|
|
|
-- Role button with its checkbox; r: a ROLES entry (r.page: optional name suffix).
|
|
local function createRole(parent, r)
|
|
local b = CreateFrame("Button", "ForeverUIGroupFinderRole" .. (r.page or "") .. r.key, parent)
|
|
b:SetWidth(G.role)
|
|
b:SetHeight(G.role)
|
|
if r.right then
|
|
b:SetPoint("TOPRIGHT", F.frame, "TOPRIGHT", r.right, G.roleY)
|
|
else
|
|
b:SetPoint("TOPLEFT", F.frame, "TOPLEFT", r.x, G.roleY)
|
|
end
|
|
if r.background then
|
|
local background = b:CreateTexture(nil, "BACKGROUND")
|
|
background:SetTexture(G.roleBackgrounds)
|
|
background:SetTexCoord(r.background[1], r.background[2], r.background[3], r.background[4])
|
|
background:SetWidth(G.roleBackground)
|
|
background:SetHeight(G.roleBackground)
|
|
background:SetPoint("CENTER", b, "CENTER", 0, 0)
|
|
background:SetAlpha(r.alpha)
|
|
b.background = background
|
|
end
|
|
local icon = b:CreateTexture(nil, "ARTWORK")
|
|
icon:SetTexture(G.roleIcons)
|
|
icon:SetTexCoord(r.icon[1], r.icon[2], r.icon[3], r.icon[4])
|
|
icon:SetAllPoints(b)
|
|
b.icon = icon
|
|
local veil = b:CreateTexture(nil, "OVERLAY")
|
|
veil:SetTexture(G.roleIcons)
|
|
veil:SetTexCoord(ROLE_VEIL[1], ROLE_VEIL[2], ROLE_VEIL[3], ROLE_VEIL[4])
|
|
veil:SetAllPoints(b)
|
|
veil:SetAlpha(0.5)
|
|
veil:Hide()
|
|
b.veil = veil
|
|
|
|
local c = CreateFrame("CheckButton", "ForeverUIGroupFinderRole" .. (r.page or "") .. r.key .. "Check", b)
|
|
c:SetWidth(G.roleCheckbox)
|
|
c:SetHeight(G.roleCheckbox)
|
|
c:SetPoint("BOTTOMLEFT", b, "BOTTOMLEFT", G.roleCheckboxX, G.roleCheckboxY)
|
|
c:SetNormalTexture(BUTTONS .. "UI-CheckBox-Up")
|
|
c:SetPushedTexture(BUTTONS .. "UI-CheckBox-Down")
|
|
c:SetHighlightTexture(BUTTONS .. "UI-CheckBox-Highlight")
|
|
local s = c:GetHighlightTexture()
|
|
if s then s:SetBlendMode("ADD") end
|
|
c:SetCheckedTexture(BUTTONS .. "UI-CheckBox-Check")
|
|
c:SetDisabledCheckedTexture(BUTTONS .. "UI-CheckBox-Check-Disabled")
|
|
b.checkbox = c
|
|
|
|
-- The client checkbox does the work: its Click toggles it and runs its OnClick
|
|
-- (sound, LFDQueueFrame_SetRoles). Ours then follows.
|
|
c:SetScript("OnClick", function(self)
|
|
local client = _G[r.client]
|
|
self:SetChecked(not self:GetChecked())
|
|
if client and client.checkButton and active(client.checkButton) then
|
|
client.checkButton:Click()
|
|
end
|
|
F.updateRoles()
|
|
end)
|
|
b:SetScript("OnClick", function()
|
|
if active(c) then c:Click() end
|
|
end)
|
|
b:SetScript("OnEnter", function(self)
|
|
local client = _G[r.client]
|
|
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
|
|
if r.key == "leader" then
|
|
GameTooltip:SetText(txt("GUIDE_TOOLTIP"), nil, nil, nil, nil, 1)
|
|
else
|
|
GameTooltip:SetText(txt("ROLE_DESCRIPTION" .. r.id), nil, nil, nil, nil, 1)
|
|
if client and client.permDisabled then
|
|
local red = RED_FONT_COLOR or { r = 1, g = 0.1, b = 0.1 }
|
|
GameTooltip:AddLine(txt("YOUR_CLASS_MAY_NOT_PERFORM_ROLE"), red.r, red.g, red.b, 1)
|
|
end
|
|
end
|
|
GameTooltip:Show()
|
|
if active(c) then c:LockHighlight() end
|
|
end)
|
|
b:SetScript("OnLeave", function()
|
|
GameTooltip:Hide()
|
|
c:UnlockHighlight()
|
|
end)
|
|
b.def = r
|
|
return b
|
|
end
|
|
|
|
F.createRole = createRole
|
|
|
|
-- Copies the state of each client role button to ours (on every page).
|
|
function F.updateRoles()
|
|
for _, b in ipairs(F.allRoles or {}) do
|
|
local client = _G[b.def.client]
|
|
if client and client.checkButton then
|
|
b.checkbox:SetChecked(client.checkButton:GetChecked())
|
|
if client.checkButton:IsShown() then b.checkbox:Show() else b.checkbox:Hide() end
|
|
if active(client.checkButton) then b.checkbox:Enable() else b.checkbox:Disable() end
|
|
if client.cover and client.cover:IsShown() then
|
|
b.veil:SetAlpha(client.cover:GetAlpha())
|
|
b.veil:Show()
|
|
else
|
|
b.veil:Hide()
|
|
end
|
|
if b.background then
|
|
if client.background and client.background:IsShown() then b.background:Show() else b.background:Hide() end
|
|
end
|
|
b.icon:SetDesaturated(client.permDisabled and true or false)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- ------------------------------------------------------------ Categories
|
|
|
|
-- isRandomDungeonDisplayable from LFDFrame.lua (local there)
|
|
local function isRandomEligible(id)
|
|
local _, _, minimum, maximum, _, _, _, extension = GetLFGDungeonInfo(id)
|
|
local level = UnitLevel("player")
|
|
local ext = (GetExpansionLevel and GetExpansionLevel()) or 2
|
|
return minimum and level >= minimum and level <= maximum and ext >= (extension or 0)
|
|
end
|
|
|
|
-- LFDQueueFrameTypeDropDown_Initialize: "specific" first, then each displayable random
|
|
-- dungeon, joinable or not
|
|
function F.categories()
|
|
local l = { { value = "specific", text = txt("SPECIFIC_DUNGEONS"), art = ART.specific, available = true } }
|
|
for i = 1, (GetNumRandomDungeons and GetNumRandomDungeons() or 0) do
|
|
local id, name = GetLFGRandomDungeonInfo(i)
|
|
if id and isRandomEligible(id) then
|
|
local holiday = select(14, GetLFGDungeonInfo(id))
|
|
table.insert(l, { value = id, text = name, art = holiday and ART.holiday or ART.random,
|
|
available = IsLFGDungeonJoinable(id) and true or false })
|
|
end
|
|
end
|
|
return l
|
|
end
|
|
|
|
local function createCategory(row)
|
|
local b = CreateFrame("Button", nil, row)
|
|
b:SetWidth(G.catW)
|
|
b:SetHeight(G.catH)
|
|
b:SetPoint("TOP", row, "TOP", 0, 0)
|
|
local image = b:CreateTexture(nil, "BACKGROUND")
|
|
image:SetPoint("TOPLEFT", b, "TOPLEFT", 5, G.catImageTop)
|
|
image:SetPoint("BOTTOMRIGHT", b, "BOTTOMRIGHT", -5, G.catImageBottom)
|
|
b.image = image
|
|
local coverTexture = b:CreateTexture(nil, "ARTWORK")
|
|
ForeverUI.SetAtlas(coverTexture, "groupfinder-button-cover", true)
|
|
coverTexture:SetAllPoints(b)
|
|
local choice = b:CreateTexture(nil, "OVERLAY")
|
|
choice:SetTexture(G.mega)
|
|
choice:SetTexCoord(0.00195313, 0.63867188, 0.76953125, 0.83007813)
|
|
choice:SetBlendMode("ADD")
|
|
choice:SetWidth(G.catSelW)
|
|
choice:SetHeight(G.catSelH)
|
|
choice:SetPoint("CENTER", b, "CENTER", 0, 0)
|
|
choice:Hide()
|
|
b.choice = choice
|
|
b:SetHighlightTexture(G.mega)
|
|
local s = b:GetHighlightTexture()
|
|
if s then
|
|
s:SetTexCoord(0.00195313, 0.63867188, 0.70703125, 0.76757813)
|
|
s:SetBlendMode("ADD")
|
|
s:ClearAllPoints()
|
|
s:SetWidth(G.catSelW)
|
|
s:SetHeight(G.catSelH)
|
|
s:SetPoint("CENTER", b, "CENTER", 0, 0)
|
|
end
|
|
local text = b:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
|
|
text:SetPoint("LEFT", b, "LEFT", 20, 0)
|
|
text:SetJustifyH("LEFT")
|
|
b.text = text
|
|
b:SetScript("OnClick", function(self)
|
|
local c = self.category
|
|
if not c or not c.available then return end
|
|
PlaySound("igMainMenuOptionCheckBoxOn")
|
|
LFDQueueFrame_SetType(c.value)
|
|
F.view = (c.value == "specific") and "list" or "categories"
|
|
F.update()
|
|
end)
|
|
b:SetScript("OnEnter", function(self)
|
|
local c = self.category
|
|
if c and not c.available then
|
|
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
|
|
GameTooltip:SetText(txt("YOU_MAY_NOT_QUEUE_FOR_THIS"), 1, 1, 1)
|
|
local reason = LFDConstructDeclinedMessage and LFDConstructDeclinedMessage(c.value)
|
|
if reason then GameTooltip:AddLine(reason, nil, nil, nil, 1) end
|
|
GameTooltip:Show()
|
|
end
|
|
end)
|
|
b:SetScript("OnLeave", function() GameTooltip:Hide() end)
|
|
row.button = b
|
|
end
|
|
|
|
local function fillCategory(row, index)
|
|
local c = F.category_list[index]
|
|
local b = row.button
|
|
b.category = c
|
|
ForeverUI.SetAtlas(b.image, c.art, true)
|
|
b.image:SetDesaturated(not c.available)
|
|
b.text:SetText(c.text)
|
|
if c.available then
|
|
b.text:SetTextColor(1, 0.82, 0)
|
|
else
|
|
b.text:SetTextColor(0.5, 0.5, 0.5)
|
|
end
|
|
if LFDQueueFrame.type == c.value then b.choice:Show() else b.choice:Hide() end
|
|
end
|
|
|
|
-- ------------------------------------------------------------ List
|
|
-- Dungeon and raid rows are the same; what differs (the list, the client functions, the
|
|
-- radio buttons of a raid in a group) comes from the list source (zone.src).
|
|
|
|
-- LFGSpecificChoiceEnableButton_SetIsRadio (LFGFrame.lua): in a group a raid is chosen with a
|
|
-- radio button (UI-RadioButton in four quarters, 17 x 17); solo, with a checkbox.
|
|
-- Sources with style "camelot" use checkbox-minimal / checkmark-minimal, as on the character
|
|
-- sheet and Social (Social.skinCell), and common-radiobutton-circle / -dot in a group.
|
|
-- 20 x 20 in a 22-high row (camelot scales its 30 x 29 box by 0.7); the radio is 16.
|
|
local CAMELOT_CHECKBOX, CAMELOT_RADIO = 20, 16
|
|
|
|
local function skinCamelotCheckbox(c, radio)
|
|
if not radio then
|
|
ForeverUI.Social.skinCell(c)
|
|
return
|
|
end
|
|
local function place(set, get, atlas, grayed)
|
|
local e = ForeverUI.AtlasEntry(atlas)
|
|
c[set](c, e and e[1] or "")
|
|
local t = c[get](c)
|
|
if t then
|
|
ForeverUI.SetAtlas(t, atlas, true)
|
|
t:ClearAllPoints()
|
|
t:SetWidth(CAMELOT_RADIO)
|
|
t:SetHeight(CAMELOT_RADIO)
|
|
t:SetPoint("CENTER", c, "CENTER", 0, 0)
|
|
if grayed then t:SetDesaturated(true) end
|
|
end
|
|
end
|
|
place("SetNormalTexture", "GetNormalTexture", "common-radiobutton-circle")
|
|
place("SetPushedTexture", "GetPushedTexture", "common-radiobutton-circle")
|
|
place("SetCheckedTexture", "GetCheckedTexture", "common-radiobutton-dot")
|
|
place("SetDisabledCheckedTexture", "GetDisabledCheckedTexture", "common-radiobutton-dot", true)
|
|
c:SetHighlightTexture("")
|
|
end
|
|
|
|
-- Switches checkbox c between checkbox and radio art; radio: true for a raid in a group.
|
|
local function setRadioStyle(c, radio)
|
|
if c.radio == radio then return end
|
|
c.radio = radio
|
|
if c.style == "camelot" then
|
|
skinCamelotCheckbox(c, radio)
|
|
return
|
|
end
|
|
local states = {
|
|
{ "SetNormalTexture", "GetNormalTexture", "UI-CheckBox-Up", 0, 0.25 },
|
|
{ "SetPushedTexture", "GetPushedTexture", "UI-CheckBox-Down", 0, 0.25 },
|
|
{ "SetHighlightTexture", "GetHighlightTexture", "UI-CheckBox-Highlight", 0.5, 0.75 },
|
|
{ "SetCheckedTexture", "GetCheckedTexture", "UI-CheckBox-Check", 0.25, 0.5 },
|
|
}
|
|
for _, e in ipairs(states) do
|
|
c[e[1]](c, BUTTONS .. (radio and "UI-RadioButton" or e[3]))
|
|
local t = c[e[2]](c)
|
|
if t then
|
|
t:ClearAllPoints()
|
|
if radio then
|
|
t:SetTexCoord(e[4], e[5], 0, 1)
|
|
t:SetWidth(17)
|
|
t:SetHeight(17)
|
|
t:SetPoint("CENTER", c, "CENTER", 0, 0)
|
|
else
|
|
t:SetTexCoord(0, 1, 0, 1)
|
|
t:SetAllPoints(c)
|
|
end
|
|
if e[1] == "SetHighlightTexture" then t:SetBlendMode("ADD") end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- dungeon source (LFDFrame.lua): camelot checkboxes, and headers keep WotLK's
|
|
-- "check all" box
|
|
local SOURCE_LFD = {
|
|
style = "camelot",
|
|
headerCheckbox = true,
|
|
collapse = function(id, collapsed) LFDList_SetHeaderCollapsed(id, collapsed) end,
|
|
list = function() return LFDDungeonList end,
|
|
empowered = function() return LFD_IsEmpowered() end,
|
|
plus = function(b) LFDQueueFrameExpandOrCollapseButton_OnClick(b) end,
|
|
checkbox = function(c) LFDQueueFrameDungeonChoiceEnableButton_OnClick(c) end,
|
|
multiple = function() return true end,
|
|
lock = function(id) return LFGLockList[id] end,
|
|
state = function(id, mode)
|
|
if mode == "queued" or mode == "listed" then return LFGQueuedForList[id] end
|
|
return LFGEnabledList[id]
|
|
end,
|
|
}
|
|
F.SOURCE_LFD = SOURCE_LFD
|
|
|
|
local function createRow(row)
|
|
row.src = row:GetParent().src
|
|
local plus = CreateFrame("Button", nil, row)
|
|
plus:SetWidth(G.plus)
|
|
plus:SetHeight(G.plus)
|
|
plus:SetPoint("LEFT", row, "LEFT", 0, 0)
|
|
plus:SetHitRectInsets(1, -4, -2, -2)
|
|
plus:SetNormalTexture(BUTTONS .. "UI-MinusButton-UP")
|
|
local n = plus:GetNormalTexture()
|
|
if n then
|
|
n:ClearAllPoints()
|
|
n:SetWidth(20)
|
|
n:SetHeight(20)
|
|
n:SetPoint("LEFT", plus, "LEFT", 3, 0)
|
|
end
|
|
plus:SetHighlightTexture(BUTTONS .. "UI-PlusButton-Hilight")
|
|
local s = plus:GetHighlightTexture()
|
|
if s then
|
|
s:SetBlendMode("ADD")
|
|
s:ClearAllPoints()
|
|
s:SetWidth(20)
|
|
s:SetHeight(20)
|
|
s:SetPoint("LEFT", plus, "LEFT", 3, 0)
|
|
end
|
|
-- client +/-: LFDQueueFrameExpandOrCollapseButton_OnClick reads
|
|
-- self:GetParent().id and .isCollapsed
|
|
plus:SetScript("OnClick", function(self)
|
|
PlaySound("igMainMenuOptionCheckBoxOn")
|
|
row.src.plus(self)
|
|
end)
|
|
row.plus = plus
|
|
|
|
local c = CreateFrame("CheckButton", nil, row)
|
|
c.style = row.src.style
|
|
local side = (c.style == "camelot") and CAMELOT_CHECKBOX or G.checkbox
|
|
c:SetWidth(side)
|
|
c:SetHeight(side)
|
|
c:SetPoint("LEFT", plus, "RIGHT", 4, 0)
|
|
c:SetNormalTexture(BUTTONS .. "UI-CheckBox-Up")
|
|
c:SetPushedTexture(BUTTONS .. "UI-CheckBox-Down")
|
|
c:SetHighlightTexture(BUTTONS .. "UI-CheckBox-Highlight")
|
|
local h = c:GetHighlightTexture()
|
|
if h then h:SetBlendMode("ADD") end
|
|
c:SetCheckedTexture(BUTTONS .. "UI-CheckBox-Check")
|
|
c:SetDisabledCheckedTexture(BUTTONS .. "UI-CheckBox-Check-Disabled")
|
|
-- client handler: LFDQueueFrameDungeonChoiceEnableButton_OnClick reads
|
|
-- self:GetParent().id and self:GetChecked()
|
|
c:SetScript("OnClick", function(self)
|
|
row.src.checkbox(self)
|
|
end)
|
|
row.checkbox = c
|
|
|
|
-- WotLK lock, in place of the checkbox
|
|
local lock = row:CreateTexture(nil, "ARTWORK")
|
|
lock:SetTexture(LFG .. "UI-LFG-ICON-LOCK")
|
|
lock:SetTexCoord(0, 0.71875, 0, 0.875)
|
|
lock:SetWidth(12)
|
|
lock:SetHeight(14)
|
|
lock:SetPoint("CENTER", c, "CENTER", 0, 0)
|
|
lock:Hide()
|
|
row.lockedIndicator = lock
|
|
|
|
local level = row:CreateFontString(nil, "ARTWORK", "GameFontNormal")
|
|
level:SetWidth(G.levelL)
|
|
level:SetHeight(G.row)
|
|
level:SetPoint("RIGHT", row, "RIGHT", 0, 0)
|
|
level:SetJustifyH("LEFT")
|
|
row.level = level
|
|
|
|
local name = row:CreateFontString(nil, "ARTWORK", "GameFontNormal")
|
|
name:SetHeight(G.row)
|
|
name:SetPoint("LEFT", c, "RIGHT", 0, 0)
|
|
name:SetPoint("RIGHT", level, "LEFT", -4, 0)
|
|
name:SetJustifyH("LEFT")
|
|
row.name = name
|
|
|
|
-- the name clicks and highlights the checkbox (camelot's NameButton);
|
|
-- the lock tooltip is the client's
|
|
row:EnableMouse(true)
|
|
row:SetScript("OnEnter", function(self)
|
|
if active(self.checkbox) and self.checkbox:IsShown() then self.checkbox:LockHighlight() end
|
|
LFDQueueFrameDungeonListButton_OnEnter(self)
|
|
end)
|
|
row:SetScript("OnLeave", function(self)
|
|
self.checkbox:UnlockHighlight()
|
|
GameTooltip:Hide()
|
|
end)
|
|
row:SetScript("OnClick", function(self)
|
|
if self.checkbox:IsShown() and active(self.checkbox) then self.checkbox:Click() end
|
|
end)
|
|
end
|
|
|
|
local function color(fs, c)
|
|
fs:SetTextColor(c[1], c[2], c[3])
|
|
end
|
|
|
|
-- LFDQueueFrameSpecificListButton_SetDungeon (and the raid one), camelot style
|
|
local function populateRow(row, index)
|
|
local src = row.src
|
|
local id = src.list()[index]
|
|
local info = LFGDungeonInfo and LFGDungeonInfo[id] or {}
|
|
local mode = GetLFGMode()
|
|
local locked = mode == "rolecheck" or mode == "queued" or mode == "listed" or not src.empowered()
|
|
row.id = id
|
|
row.name:SetText(info[1] or "")
|
|
if id < 0 then
|
|
row.plus:Show()
|
|
row.isCollapsed = LFGCollapseList[id]
|
|
row.plus:SetNormalTexture(BUTTONS .. (row.isCollapsed and "UI-PlusButton-UP" or "UI-MinusButton-UP"))
|
|
local n = row.plus:GetNormalTexture()
|
|
if n then
|
|
n:ClearAllPoints()
|
|
n:SetWidth(20)
|
|
n:SetHeight(20)
|
|
n:SetPoint("LEFT", row.plus, "LEFT", 3, 0)
|
|
end
|
|
row.level:Hide()
|
|
color(row.name, COLORS.header)
|
|
else
|
|
row.plus:Hide()
|
|
row.isCollapsed = false
|
|
local minimum, maximum = info[3] or 0, info[4] or 0
|
|
if minimum == maximum then
|
|
row.level:SetText(format(txt("LFD_LEVEL_FORMAT_SINGLE"), minimum))
|
|
else
|
|
row.level:SetText(format(txt("LFD_LEVEL_FORMAT_RANGE"), minimum, maximum))
|
|
end
|
|
row.level:Show()
|
|
local player = UnitLevel("player")
|
|
local c = COLORS.entry
|
|
if maximum ~= 0 and maximum < player then
|
|
c = COLORS.easy
|
|
elseif minimum ~= 0 and minimum > player then
|
|
c = COLORS.hard
|
|
end
|
|
color(row.level, c)
|
|
color(row.name, locked and COLORS.header or c)
|
|
end
|
|
|
|
local multiple = src.multiple()
|
|
if src.lock(id) then
|
|
row.checkbox:Hide()
|
|
row.lockedIndicator:Show()
|
|
else
|
|
-- in a group, a raid header has no button
|
|
if multiple or id >= 0 then row.checkbox:Show() else row.checkbox:Hide() end
|
|
row.lockedIndicator:Hide()
|
|
end
|
|
setRadioStyle(row.checkbox, not multiple)
|
|
local state = src.state(id, mode)
|
|
if multiple and row.checkbox.style == "camelot" then
|
|
row.checkbox:SetChecked(state and state ~= 0)
|
|
elseif multiple then
|
|
if state == 1 then
|
|
row.checkbox:SetCheckedTexture(BUTTONS .. "UI-MultiCheck-Up")
|
|
row.checkbox:SetDisabledCheckedTexture(BUTTONS .. "UI-MultiCheck-Disabled")
|
|
else
|
|
row.checkbox:SetCheckedTexture(BUTTONS .. "UI-CheckBox-Check")
|
|
row.checkbox:SetDisabledCheckedTexture(BUTTONS .. "UI-CheckBox-Check-Disabled")
|
|
end
|
|
row.checkbox:SetChecked(state and state ~= 0)
|
|
else
|
|
row.checkbox:SetChecked(state)
|
|
end
|
|
if locked then row.checkbox:Disable() else row.checkbox:Enable() end
|
|
end
|
|
F.createRow = createRow
|
|
F.populateRow = populateRow
|
|
|
|
-- ------------------------------------------------------------ Category list
|
|
-- Category list, shared by the raid browser (GroupFinderRaid.lua) and the specific dungeons.
|
|
-- Headers follow the character sheet (TokensTab.lua, TokenHeaderTemplate): nine-sliced
|
|
-- common-button-list-collapseExpand plate, minus / plus arrow on the right, plate in ADD at
|
|
-- 0.3 on hover. Entries use camelot checkboxes and the sheet's hover rectangle
|
|
-- (charactercreate-customize-dropdown-linemouseover, right side mirrored).
|
|
-- Clicking a header collapses or expands it (src.collapse). Rows have two heights, so they
|
|
-- are stacked by hand and the scroll offset counts rows.
|
|
-- With src.headerCheckbox (dungeons), the header keeps WotLK's "check all" box before the
|
|
-- name; partly checked (LFGEnabledList at 1) shows a gray check mark, like the AddOns list.
|
|
-- A locked header shows WotLK's lock and its tooltip instead.
|
|
local LC = {
|
|
header = 26, entry = 22, gap = 3, indent = 2, margin = 4, corner = 12, nameX = 10, nameH = 15,
|
|
arrowX = -8, arrowY = -1, arrowSpace = 16, checkboxGap = 2, side = 6, selectedItem = 0.20, hover = 0.10,
|
|
plate = "common-button-list-collapseexpand",
|
|
hoverSide = "charactercreate-customize-dropdown-linemouseover-side",
|
|
hoverMiddle = "charactercreate-customize-dropdown-linemouseover-middle",
|
|
}
|
|
F.LC = LC
|
|
|
|
local function createCatHeader(zone, n)
|
|
local b = CreateFrame("Button", zone.prefix .. "Header" .. n, zone)
|
|
b:SetHeight(LC.header)
|
|
ForeverUI.CreateNineSlice(b, LC.plate, LC.corner, { 0, 0, 0, 0 }, "BACKGROUND")
|
|
for _, t in ipairs(ForeverUI.CreateNineSlice(b, LC.plate, LC.corner, { 0, 0, 0, 0 }, "HIGHLIGHT") or {}) do
|
|
t:SetBlendMode("ADD")
|
|
t:SetAlpha(0.3)
|
|
end
|
|
local name = b:CreateFontString(nil, "OVERLAY", "GameFontNormalLeft")
|
|
name:SetHeight(LC.nameH)
|
|
name:SetJustifyH("LEFT")
|
|
name:SetPoint("RIGHT", b, "RIGHT", -LC.arrowSpace, 0)
|
|
b.name = name
|
|
local arrow = b:CreateTexture(nil, "OVERLAY")
|
|
arrow:SetPoint("RIGHT", b, "RIGHT", LC.arrowX, LC.arrowY)
|
|
b.arrow = arrow
|
|
if zone.src.headerCheckbox then
|
|
-- client handler: LFDQueueFrameDungeonChoiceEnableButton_OnClick reads
|
|
-- self:GetParent().id and self:GetChecked()
|
|
local c = CreateFrame("CheckButton", nil, b)
|
|
c.style = "camelot"
|
|
c:SetWidth(CAMELOT_CHECKBOX)
|
|
c:SetHeight(CAMELOT_CHECKBOX)
|
|
c:SetPoint("LEFT", b, "LEFT", LC.nameX, 0)
|
|
skinCamelotCheckbox(c, false)
|
|
c:SetScript("OnClick", function(self) zone.src.checkbox(self) end)
|
|
b.checkbox = c
|
|
local lock = b:CreateTexture(nil, "OVERLAY")
|
|
lock:SetTexture(LFG .. "UI-LFG-ICON-LOCK")
|
|
lock:SetTexCoord(0, 0.71875, 0, 0.875)
|
|
lock:SetWidth(12)
|
|
lock:SetHeight(14)
|
|
lock:SetPoint("CENTER", c, "CENTER", 0, 0)
|
|
lock:Hide()
|
|
b.lockedIndicator = lock
|
|
name:SetPoint("LEFT", c, "RIGHT", LC.checkboxGap, 0)
|
|
b:SetScript("OnEnter", function(self) LFDQueueFrameDungeonListButton_OnEnter(self) end)
|
|
b:SetScript("OnLeave", function() GameTooltip:Hide() end)
|
|
else
|
|
name:SetPoint("LEFT", b, "LEFT", LC.nameX, 0)
|
|
end
|
|
b:SetScript("OnClick", function(self)
|
|
PlaySound("igMainMenuOptionCheckBoxOn")
|
|
zone.src.collapse(self.id, not LFGCollapseList[self.id])
|
|
F.request()
|
|
end)
|
|
return b
|
|
end
|
|
|
|
local function fillCatHeader(b, id, src)
|
|
local info = LFGDungeonInfo and LFGDungeonInfo[id] or {}
|
|
b.id = id
|
|
b.name:SetText(info[1] or "")
|
|
ForeverUI.SetAtlas(b.arrow, LFGCollapseList[id] and "common-button-list-plus" or "common-button-list-minus", false)
|
|
local c = b.checkbox
|
|
if not c then return end
|
|
local mode = GetLFGMode()
|
|
local locked = mode == "rolecheck" or mode == "queued" or mode == "listed" or not src.empowered()
|
|
if src.lock(id) then
|
|
c:Hide()
|
|
b.lockedIndicator:Show()
|
|
else
|
|
c:Show()
|
|
b.lockedIndicator:Hide()
|
|
end
|
|
local state = src.state(id, mode)
|
|
c:SetChecked(state and state ~= 0)
|
|
local checkMark = c:GetCheckedTexture()
|
|
if checkMark then checkMark:SetDesaturated(state == 1) end
|
|
if locked then c:Disable() else c:Enable() end
|
|
end
|
|
|
|
local function createCatEntry(zone, n)
|
|
local l = CreateFrame("Button", zone.prefix .. "Row" .. n, zone)
|
|
l:SetHeight(LC.entry)
|
|
-- the sheet's hover rectangle, under the row
|
|
local hover = CreateFrame("Frame", nil, l)
|
|
hover:SetAllPoints(l)
|
|
hover:SetAlpha(0)
|
|
local g = hover:CreateTexture(nil, "BACKGROUND")
|
|
ForeverUI.SetAtlas(g, LC.hoverSide, true)
|
|
g:SetWidth(LC.side)
|
|
g:SetPoint("TOPLEFT", hover, "TOPLEFT", 0, 0)
|
|
g:SetPoint("BOTTOMLEFT", hover, "BOTTOMLEFT", 0, 0)
|
|
local d = hover:CreateTexture(nil, "BACKGROUND")
|
|
if ForeverUI.SetAtlas(d, LC.hoverSide, true) then
|
|
local e = ForeverUI.AtlasEntry(LC.hoverSide)
|
|
d:SetTexCoord(e[3], e[2], e[4], e[5])
|
|
end
|
|
d:SetWidth(LC.side)
|
|
d:SetPoint("TOPRIGHT", hover, "TOPRIGHT", 0, 0)
|
|
d:SetPoint("BOTTOMRIGHT", hover, "BOTTOMRIGHT", 0, 0)
|
|
local m = hover:CreateTexture(nil, "BACKGROUND")
|
|
ForeverUI.SetAtlas(m, LC.hoverMiddle, true)
|
|
m:SetPoint("TOPLEFT", g, "TOPRIGHT", 0, 0)
|
|
m:SetPoint("BOTTOMRIGHT", d, "BOTTOMLEFT", 0, 0)
|
|
l.hover = hover
|
|
createRow(l)
|
|
return l
|
|
end
|
|
|
|
-- rectangle opacity: 0.20 checked, 0.10 hovered, 0 otherwise
|
|
local function placeCatHover(l)
|
|
local checkMark = l.checkbox:IsShown() and l.checkbox:GetChecked()
|
|
l.hover:SetAlpha((checkMark and LC.selectedItem) or (l:IsMouseOver() and LC.hover) or 0)
|
|
end
|
|
|
|
-- number of rows that fit starting at fromIndex; top: available height
|
|
local function countFittingCats(l, fromIndex, top)
|
|
local y, n = LC.margin, 0
|
|
for i = fromIndex, #l do
|
|
local h = (l[i] < 0) and LC.header or LC.entry
|
|
if y + h > top then break end
|
|
y = y + h + LC.gap
|
|
n = n + 1
|
|
end
|
|
return n
|
|
end
|
|
|
|
-- zone: from the top of the list (G.listY1, margin included) to zone.down above the window
|
|
-- bottom; the scroll bar takes its room on the right (G.barRight), else it goes to the margin
|
|
local function placeCatZone(zone)
|
|
local f = F.frame
|
|
zone:ClearAllPoints()
|
|
zone:SetPoint("TOPLEFT", f, "TOPLEFT", G.listX1 + G.margin, G.listY1 - G.margin)
|
|
zone:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", zone.hasBar and (G.listX2 + G.barRight - G.margin)
|
|
or (G.listX2 - G.margin), zone.down)
|
|
end
|
|
|
|
function F.updateCategoryList(zone)
|
|
local l = zone.src.list() or {}
|
|
local total = #l
|
|
local top = G.height + (G.listY1 - G.margin) - zone.down
|
|
-- the maximum offset: the smallest one at which the end of the list fits
|
|
local maxValue = 0
|
|
for d = 0, total do
|
|
if countFittingCats(l, d + 1, top) >= total - d then
|
|
maxValue = d
|
|
break
|
|
end
|
|
end
|
|
zone.maxValue = maxValue
|
|
zone.offset = math.max(0, math.min(zone.offset or 0, maxValue))
|
|
local hasBar = maxValue > 0
|
|
if hasBar ~= zone.hasBar then
|
|
zone.hasBar = hasBar
|
|
placeCatZone(zone)
|
|
end
|
|
local y = LC.margin
|
|
local ne, nn = 0, 0
|
|
for i = zone.offset + 1, total do
|
|
local id = l[i]
|
|
local h = (id < 0) and LC.header or LC.entry
|
|
if y + h > top then break end
|
|
local row
|
|
if id < 0 then
|
|
ne = ne + 1
|
|
row = zone.headers[ne] or createCatHeader(zone, ne)
|
|
zone.headers[ne] = row
|
|
fillCatHeader(row, id, zone.src)
|
|
row:ClearAllPoints()
|
|
row:SetPoint("TOPLEFT", zone, "TOPLEFT", 0, -y)
|
|
row:SetPoint("TOPRIGHT", zone, "TOPRIGHT", 0, -y)
|
|
else
|
|
nn = nn + 1
|
|
row = zone.entries[nn] or createCatEntry(zone, nn)
|
|
zone.entries[nn] = row
|
|
populateRow(row, i)
|
|
placeCatHover(row)
|
|
row:ClearAllPoints()
|
|
row:SetPoint("TOPLEFT", zone, "TOPLEFT", LC.indent, -y)
|
|
row:SetPoint("TOPRIGHT", zone, "TOPRIGHT", 0, -y)
|
|
end
|
|
row:Show()
|
|
y = y + h + LC.gap
|
|
end
|
|
for i = ne + 1, #zone.headers do zone.headers[i]:Hide() end
|
|
for i = nn + 1, #zone.entries do zone.entries[i]:Hide() end
|
|
zone.bar:Configure(maxValue + 1, 1, zone.offset)
|
|
end
|
|
|
|
-- Category list in page p. name: zone frame name; prefix: row name prefix; src: list source;
|
|
-- down: zone bottom above the window bottom; frameBox: the inset it sits on
|
|
function F.createCatList(p, name, prefix, src, down, frameBox)
|
|
local zone = CreateFrame("Frame", name, p)
|
|
zone.src = src
|
|
zone.prefix = prefix
|
|
zone.headers, zone.entries = {}, {}
|
|
zone.offset = 0
|
|
zone.down = down
|
|
zone:SetFrameLevel(frameBox:GetFrameLevel() + 1)
|
|
zone.hasBar = false
|
|
placeCatZone(zone)
|
|
local bar = ForeverUI.CreateScrollBar(name .. "ScrollBar", p, zone)
|
|
bar:SetFrameLevel(frameBox:GetFrameLevel() + 2)
|
|
bar:ClearAllPoints()
|
|
bar:SetPoint("TOPLEFT", zone, "TOPRIGHT", 13 + G.margin, 0)
|
|
bar:SetPoint("BOTTOMLEFT", zone, "BOTTOMRIGHT", 13 + G.margin, -2)
|
|
bar.onScroll = function(new)
|
|
zone.offset = new
|
|
F.updateCategoryList(zone)
|
|
end
|
|
zone.bar = bar
|
|
zone:EnableMouseWheel(true)
|
|
zone:SetScript("OnMouseWheel", function(self, direction)
|
|
self.offset = math.max(0, math.min((self.offset or 0) - direction, self.maxValue or 0))
|
|
F.updateCategoryList(self)
|
|
end)
|
|
-- hover tracked every frame, as on the character sheet (trackHover)
|
|
zone:SetScript("OnUpdate", function(self)
|
|
for _, l in ipairs(self.entries) do
|
|
if l:IsShown() then placeCatHover(l) end
|
|
end
|
|
end)
|
|
return zone
|
|
end
|
|
|
|
-- ------------------------------------------------------------ Rewards
|
|
|
|
|
|
-- Reward item button; i: reward index for the tooltip and link (0 for money).
|
|
local function createItem(parent, i)
|
|
local b = CreateFrame("Button", "ForeverUIGroupFinderReward" .. i, parent)
|
|
b:SetWidth(147)
|
|
b:SetHeight(41)
|
|
local icon = b:CreateTexture(nil, "BORDER")
|
|
icon:SetWidth(39)
|
|
icon:SetHeight(39)
|
|
icon:SetPoint("TOPLEFT", b, "TOPLEFT", 0, 0)
|
|
b.icon = icon
|
|
local nameFrame = b:CreateTexture(nil, "BACKGROUND")
|
|
nameFrame:SetTexture("Interface" .. SEP .. "QuestFrame" .. SEP .. "UI-QuestItemNameFrame")
|
|
nameFrame:SetWidth(128)
|
|
nameFrame:SetHeight(64)
|
|
nameFrame:SetPoint("LEFT", icon, "RIGHT", -10, 0)
|
|
local name = b:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
|
|
name:SetWidth(90)
|
|
name:SetHeight(36)
|
|
name:SetPoint("LEFT", icon, "RIGHT", 8, 0)
|
|
name:SetJustifyH("LEFT")
|
|
b.name = name
|
|
local count = b:CreateFontString(nil, "OVERLAY", "NumberFontNormal")
|
|
count:SetPoint("BOTTOMRIGHT", icon, "BOTTOMRIGHT", -5, 2)
|
|
b.count = count
|
|
b:SetID(i)
|
|
-- LFDRandomDungeonLootTemplate
|
|
b:SetScript("OnEnter", function(self)
|
|
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
|
|
GameTooltip:SetLFGDungeonReward(LFDQueueFrame.type, self:GetID())
|
|
GameTooltip:Show()
|
|
end)
|
|
b:SetScript("OnLeave", function() GameTooltip:Hide() end)
|
|
b:SetScript("OnClick", function(self)
|
|
HandleModifiedItemClick(GetLFGDungeonRewardLink(LFDQueueFrame.type, self:GetID()))
|
|
end)
|
|
return b
|
|
end
|
|
|
|
-- Details: a scroll area under the rule. Each piece is placed at its height from the top of
|
|
-- the content (not from the previous piece), which gives the total height for the bar.
|
|
local function buildRewards(p)
|
|
local view = CreateFrame("ScrollFrame", "ForeverUIGroupFinderDetails", p)
|
|
view.offset = 0
|
|
local r = CreateFrame("Frame", "ForeverUIGroupFinderRewards", view)
|
|
r:SetWidth(G.width + G.detailsX2 - G.detailsX1)
|
|
r:SetHeight(1)
|
|
view:SetScrollChild(r)
|
|
local width = G.width + G.detailsX2 - G.detailsX1
|
|
local function text(font)
|
|
local fs = r:CreateFontString(nil, "ARTWORK", font)
|
|
fs:SetWidth(width)
|
|
fs:SetJustifyH("LEFT")
|
|
return fs
|
|
end
|
|
r.description = text("GameFontHighlight")
|
|
r.tag = text("GameFontNormalLarge")
|
|
r.tag:SetText(txt("LFD_REWARDS"))
|
|
r.explanation = text("GameFontHighlight")
|
|
r.pug = text("GameFontHighlight")
|
|
-- money: an item cell, without tooltip or link
|
|
local money = createItem(r, 0)
|
|
money:SetScript("OnEnter", nil)
|
|
money:SetScript("OnLeave", nil)
|
|
money:SetScript("OnClick", nil)
|
|
money.icon:SetTexture(G.pieces)
|
|
money.count:Hide()
|
|
r.money = money
|
|
r.xp = text("GameFontNormal")
|
|
r.objects = {}
|
|
F.rewards = r
|
|
|
|
local bar = ForeverUI.CreateScrollBar("ForeverUIGroupFinderDetailsScrollBar", p, view)
|
|
bar.onScroll = function(new)
|
|
view.offset = new
|
|
view:SetVerticalScroll(math.min(new * G.scrollStep, math.max(0, (view.content or 0) - (view.visible or 0))))
|
|
end
|
|
view:EnableMouseWheel(true)
|
|
view:SetScript("OnMouseWheel", function(self, direction)
|
|
bar:MoveTo(self.offset - direction)
|
|
end)
|
|
view.bar = bar
|
|
F.details = view
|
|
|
|
-- Second measure, on the next frame: from the content top to the bottom of its last piece.
|
|
-- Wrapped text can measure short at first; if taller, everything is laid out again.
|
|
local remeasure = CreateFrame("Frame")
|
|
remeasure:Hide()
|
|
remeasure:SetScript("OnUpdate", function(self)
|
|
self:Hide()
|
|
local piece = F.lastPiece
|
|
if not (view:IsShown() and piece and piece:IsShown()) then return end
|
|
local top, down = r:GetTop(), piece:GetBottom()
|
|
if not (top and down) then return end
|
|
local actual = top - down
|
|
if actual > (view.content or 0) + 0.5 then F.layoutContent(actual) end
|
|
end)
|
|
F.remeasure = remeasure
|
|
end
|
|
|
|
-- Rule and details. The details end at the bottom of the frame box; the rule sits just
|
|
-- above their content, never higher than F.ruleMax (6 below the last button). Without
|
|
-- content it stays there.
|
|
local function placeRule(content)
|
|
local y = F.ruleMax or 0
|
|
if content and content > 0 then
|
|
local wanted = -(G.height - G.detailsBottom) + content + G.detailsPadding + G.detailsGap + G.ruleH / 2
|
|
if wanted < y then y = wanted end
|
|
end
|
|
-- visible height of the details, from our own gaps: GetHeight right after re-anchoring
|
|
-- can still return the old height
|
|
F.details.visible = (y - G.ruleH / 2 - G.detailsGap) + (G.height - G.detailsBottom)
|
|
F.rule:ClearAllPoints()
|
|
F.rule:SetPoint("LEFT", F.frame, "TOPLEFT", G.listX1, y)
|
|
F.rule:SetPoint("RIGHT", F.frame, "TOPRIGHT", G.listX2, y)
|
|
F.details:ClearAllPoints()
|
|
F.details:SetPoint("TOPLEFT", F.frame, "TOPLEFT", G.detailsX1, y - G.ruleH / 2 - G.detailsGap)
|
|
F.details:SetPoint("BOTTOMRIGHT", F.frame, "BOTTOMRIGHT", G.detailsX2, G.detailsBottom)
|
|
-- the detail veils start at the rule
|
|
F.detailsTop = y - G.ruleH / 2 - G.frameBoxY1
|
|
end
|
|
|
|
-- height of a wrapped text: GetHeight, as in the client's QuestInfo.lua
|
|
local function height(fs)
|
|
local h = fs:GetHeight() or 0
|
|
if h <= 0 and fs.GetStringHeight then h = fs:GetStringHeight() or 0 end
|
|
return h
|
|
end
|
|
|
|
-- LFDQueueFrameRandom_UpdateFrame has set the texts on the client regions; we copy them.
|
|
-- The title is skipped: the chosen button already shows it.
|
|
local function updateRewards()
|
|
local r = F.rewards
|
|
local view = F.details
|
|
local client = LFDQueueFrameRandomScrollFrameChildFrame
|
|
local id = LFDQueueFrame.type
|
|
if not client or type(id) ~= "number" then
|
|
placeRule(nil)
|
|
view:Hide()
|
|
view.bar:Hide()
|
|
return
|
|
end
|
|
view:Show()
|
|
local y = 0
|
|
local last
|
|
local function place(piece, x, gap)
|
|
piece:ClearAllPoints()
|
|
piece:SetPoint("TOPLEFT", r, "TOPLEFT", x or 0, -(y + (gap or 0)))
|
|
piece:Show()
|
|
y = y + (gap or 0) + height(piece)
|
|
last = piece
|
|
end
|
|
r.description:SetText(client.description:GetText() or "")
|
|
place(r.description)
|
|
if client.rewardsLabel:IsShown() then
|
|
r.explanation:SetText(client.rewardsDescription:GetText() or "")
|
|
place(r.tag, 0, 10)
|
|
place(r.explanation, 0, 6)
|
|
else
|
|
r.tag:Hide()
|
|
r.explanation:Hide()
|
|
end
|
|
local _, baseMoney, moneyVar, baseXp, xpVar, count = GetLFGDungeonRewards(id)
|
|
count = count or 0
|
|
-- money and experience: the LFDQueueFrameRandom_UpdateFrame formula
|
|
local missingMembers = 4 - GetNumPartyMembers()
|
|
local money = (baseMoney or 0) + (moneyVar or 0) * missingMembers
|
|
local xp = (baseXp or 0) + (xpVar or 0) * missingMembers
|
|
local top = y + 8
|
|
local function checkbox(b, i)
|
|
local rank, column = math.floor((i - 1) / 2), (i - 1) % 2
|
|
b:ClearAllPoints()
|
|
b:SetPoint("TOPLEFT", r, "TOPLEFT", column * 157, -(top + rank * 44))
|
|
b:Show()
|
|
y = top + rank * 44 + 41
|
|
last = b
|
|
end
|
|
for i = 1, count do
|
|
local b = r.objects[i] or createItem(r, i)
|
|
r.objects[i] = b
|
|
local name, image, quantity = GetLFGDungeonRewardInfo(id, i)
|
|
b.name:SetText(name or "")
|
|
b.icon:SetTexture(image)
|
|
if quantity and quantity > 1 then b.count:SetText(quantity) b.count:Show() else b.count:Hide() end
|
|
checkbox(b, i)
|
|
end
|
|
for i = count + 1, #r.objects do r.objects[i]:Hide() end
|
|
if money > 0 then
|
|
r.money.name:SetText(GetCoinTextureString(money))
|
|
checkbox(r.money, count + 1)
|
|
else
|
|
r.money:Hide()
|
|
end
|
|
if client.pugDescription:IsShown() then
|
|
r.pug:SetText(client.pugDescription:GetText() or "")
|
|
place(r.pug, 0, 5)
|
|
else
|
|
r.pug:Hide()
|
|
end
|
|
if xp > 0 then
|
|
r.xp:SetText(txt("EXPERIENCE_COLON") .. " |cffffffff" .. xp .. "|r")
|
|
place(r.xp, 0, 8)
|
|
else
|
|
r.xp:Hide()
|
|
end
|
|
-- the rule moves down onto the content, then the scroll bar
|
|
F.lastPiece = last
|
|
F.layoutContent(y)
|
|
-- second measure, once the client has set the texts
|
|
F.remeasure:Show()
|
|
end
|
|
|
|
-- Sizes the details content to height y, places the rule and sets up the scroll bar.
|
|
function F.layoutContent(y)
|
|
local view = F.details
|
|
F.rewards:SetHeight(math.max(1, y))
|
|
view.content = y
|
|
placeRule(y)
|
|
local visible = view.visible or 0
|
|
if visible <= 0 or y <= visible + G.tolerance then
|
|
view.offset = 0
|
|
view:SetVerticalScroll(0)
|
|
view.bar:Configure(0, 0, 0)
|
|
else
|
|
-- steps of 20: the last step shows the end of the content
|
|
local total, visibleCount = math.ceil((y - visible) / G.scrollStep) + 1, 1
|
|
view.offset = math.min(view.offset, total - visibleCount)
|
|
view.bar:Configure(total, visibleCount, view.offset)
|
|
view.bar.onScroll(view.offset)
|
|
end
|
|
end
|
|
|
|
-- ------------------------------------------------------------ Veils
|
|
-- WotLK's veils (330 x 257, black at 0.93), over the frame box in the same order: cooldown
|
|
-- (11), party backfill (14), "no LFD while in LFR" (16).
|
|
|
|
-- WotLK-style veil. level: frame level above the frame box; page, frameBox: default to
|
|
-- the dungeon page and its frame box
|
|
local function createVeil(name, level, page, frameBox)
|
|
local v = CreateFrame("Frame", name, page or F.dungeonsPage)
|
|
v:SetFrameLevel((frameBox or F.frameBox):GetFrameLevel() + level)
|
|
v:EnableMouse(true)
|
|
local black = v:CreateTexture(nil, "BACKGROUND")
|
|
black:SetTexture(0, 0, 0, 0.93)
|
|
black:SetAllPoints(v)
|
|
v.description = v:CreateFontString(nil, "ARTWORK", "GameFontNormal")
|
|
v.description:SetWidth(300)
|
|
v.description:SetPoint("TOP", v, "TOP", 0, -70)
|
|
v:Hide()
|
|
return v
|
|
end
|
|
F.createVeil = createVeil
|
|
|
|
local function buildVeils()
|
|
local pending = createVeil("ForeverUIGroupFinderCooldown", 11)
|
|
pending.clock = pending:CreateFontString(nil, "ARTWORK", "GameFontHighlightLarge")
|
|
pending.clock:SetPoint("TOP", pending.description, "BOTTOM", 0, -10)
|
|
pending.names, pending.states = {}, {}
|
|
for i = 1, 4 do
|
|
local n = pending:CreateFontString(nil, "ARTWORK", "GameFontNormal")
|
|
n:SetWidth(120)
|
|
n:SetJustifyH("LEFT")
|
|
if i == 1 then
|
|
n:SetPoint("TOPLEFT", pending.description, "BOTTOMLEFT", 25, -60)
|
|
else
|
|
n:SetPoint("TOP", pending.names[i - 1], "BOTTOM", 0, -5)
|
|
end
|
|
local e = pending:CreateFontString(nil, "ARTWORK", "GameFontNormal")
|
|
e:SetWidth(110)
|
|
e:SetJustifyH("RIGHT")
|
|
e:SetPoint("TOPLEFT", n, "TOPRIGHT", 0, 0)
|
|
pending.names[i], pending.states[i] = n, e
|
|
end
|
|
-- the client updates the remaining time every frame
|
|
pending:SetScript("OnUpdate", function(self)
|
|
local c = LFDQueueFrameCooldownFrame
|
|
if c and c.time then
|
|
self.clock:SetText(c.time:GetText() or "")
|
|
end
|
|
end)
|
|
F.pending = pending
|
|
|
|
local backfill = createVeil("ForeverUIGroupFinderBackfill", 14)
|
|
backfill.yes = ForeverUI.CreatePanelButton(backfill, txt("YES"), 153, 22, nil, "GameFontNormal")
|
|
backfill.yes:SetPoint("TOPRIGHT", backfill.description, "BOTTOM", -5, -10)
|
|
backfill.yes:SetScript("OnClick", function()
|
|
if LFDQueueFramePartyBackfillBackfillButton then LFDQueueFramePartyBackfillBackfillButton:Click() end
|
|
end)
|
|
backfill.no = ForeverUI.CreatePanelButton(backfill, txt("HIDE"), 153, 22, nil, "GameFontNormal")
|
|
backfill.no:SetPoint("TOPLEFT", backfill.description, "BOTTOM", 5, -10)
|
|
backfill.no:SetScript("OnClick", function()
|
|
if LFDQueueFramePartyBackfillNoBackfillButton then LFDQueueFramePartyBackfillNoBackfillButton:Click() end
|
|
end)
|
|
F.backfill = backfill
|
|
|
|
local raid = createVeil("ForeverUIGroupFinderNoLFDWhileLFR", 16)
|
|
raid.quit = ForeverUI.CreatePanelButton(raid, "", 153, 22, nil, "GameFontNormal")
|
|
raid.quit:SetPoint("TOP", raid.description, "BOTTOM", 0, -10)
|
|
raid.quit:SetScript("OnClick", function()
|
|
if LFDQueueFrameNoLFDWhileLFRLeaveQueueButton then LFDQueueFrameNoLFDWhileLFRLeaveQueueButton:Click() end
|
|
end)
|
|
F.raid = raid
|
|
end
|
|
|
|
local function textOf(name)
|
|
local r = _G[name]
|
|
return r and r.GetText and r:GetText() or ""
|
|
end
|
|
|
|
-- area they cover: the details under the rule, or the whole list
|
|
local function placeVeils()
|
|
for _, v in ipairs({ F.pending, F.backfill, F.raid }) do
|
|
v:ClearAllPoints()
|
|
if F.view == "list" then
|
|
v:SetAllPoints(F.frameBox)
|
|
else
|
|
v:SetPoint("TOPLEFT", F.frameBox, "TOPLEFT", 0, F.detailsTop)
|
|
v:SetPoint("BOTTOMRIGHT", F.frameBox, "BOTTOMRIGHT", 0, 0)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function updateVeils()
|
|
placeVeils()
|
|
-- cooldown: shown when visible in the client (its parent depends on deserter state);
|
|
-- texts copied
|
|
local c = LFDQueueFrameCooldownFrame
|
|
if c and display(c) then
|
|
F.pending.description:SetText(c.description and c.description:GetText() or "")
|
|
F.pending.description:ClearAllPoints()
|
|
F.pending.description:SetPoint("TOP", F.pending, "TOP", 0, GetNumPartyMembers() == 0 and -85 or -30)
|
|
F.pending.clock:SetText(c.time and c.time:GetText() or "")
|
|
if c.time and c.time:IsShown() then F.pending.clock:Show() else F.pending.clock:Hide() end
|
|
for i = 1, 4 do
|
|
local n, e = _G["LFDQueueFrameCooldownFrameName" .. i], _G["LFDQueueFrameCooldownFrameStatus" .. i]
|
|
if n and n:IsShown() then
|
|
F.pending.names[i]:SetText(n:GetText() or "")
|
|
F.pending.states[i]:SetText(e and e:GetText() or "")
|
|
F.pending.names[i]:Show()
|
|
F.pending.states[i]:Show()
|
|
else
|
|
F.pending.names[i]:Hide()
|
|
F.pending.states[i]:Hide()
|
|
end
|
|
end
|
|
F.pending:Show()
|
|
else
|
|
F.pending:Hide()
|
|
end
|
|
|
|
local b = LFDQueueFramePartyBackfill
|
|
if b and display(b) then
|
|
F.backfill.description:SetText(textOf("LFDQueueFramePartyBackfillDescription"))
|
|
F.backfill.yes:Activate(active(LFDQueueFramePartyBackfillBackfillButton))
|
|
F.backfill:Show()
|
|
else
|
|
F.backfill:Hide()
|
|
end
|
|
|
|
local l = LFDQueueFrameNoLFDWhileLFR
|
|
if l and display(l) then
|
|
F.raid.description:SetText(textOf("LFDQueueFrameNoLFDWhileLFRDescription"))
|
|
F.raid.quit:SetText(textOf("LFDQueueFrameNoLFDWhileLFRLeaveQueueButton"))
|
|
F.raid.quit:Activate(active(LFDQueueFrameNoLFDWhileLFRLeaveQueueButton))
|
|
F.raid:Show()
|
|
else
|
|
F.raid:Hide()
|
|
end
|
|
end
|
|
|
|
-- ------------------------------------------------------------ Page
|
|
|
|
-- Role banner of a page: a child frame above the stripes (camelot's RolesSection),
|
|
-- with the blue background and the role buttons
|
|
function F.buildBanner(p, roles)
|
|
local f = F.frame
|
|
local banner = CreateFrame("Frame", nil, p)
|
|
banner:SetAllPoints(f)
|
|
banner:SetFrameLevel(f:GetFrameLevel() + 1)
|
|
local blue = banner:CreateTexture(nil, "BACKGROUND")
|
|
blue:SetTexture(G.blue)
|
|
blue:SetPoint("TOPLEFT", f, "TOPLEFT", G.blueX1, G.blueY1)
|
|
blue:SetPoint("BOTTOMRIGHT", f, "TOPRIGHT", G.blueX2, G.blueY2)
|
|
local l = {}
|
|
F.allRoles = F.allRoles or {}
|
|
for _, r in ipairs(roles) do
|
|
local b = createRole(banner, r)
|
|
table.insert(l, b)
|
|
table.insert(F.allRoles, b)
|
|
end
|
|
return blue, l
|
|
end
|
|
|
|
-- Frame box of a page, above the banner: no marble, camelot's background 3 px from the
|
|
-- edge. top, down: y offsets of its edges; trim: crop the background top (0.093) under a
|
|
-- role banner
|
|
function F.buildFrameBox(p, name, top, trim, down)
|
|
local f = F.frame
|
|
local frameBox = ForeverUI.CreateInset(p, name)
|
|
frameBox:SetFrameLevel(f:GetFrameLevel() + 2)
|
|
frameBox:SetPoint("TOPLEFT", f, "TOPLEFT", G.frameBoxX1, top)
|
|
frameBox:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", G.frameBoxX2, down or G.frameBoxY2)
|
|
frameBox.background:Hide()
|
|
local background = frameBox:CreateTexture(nil, "BACKGROUND")
|
|
ForeverUI.SetAtlas(background, "groupfinder-background", true)
|
|
local e = ForeverUI.AtlasEntry("groupfinder-background")
|
|
if e and trim then
|
|
background:SetTexCoord(e[2], e[3], e[4] + (e[5] - e[4]) * G.backgroundTop, e[5])
|
|
end
|
|
background:SetPoint("TOPLEFT", frameBox, "TOPLEFT", 3, -3)
|
|
background:SetPoint("BOTTOMRIGHT", frameBox, "BOTTOMRIGHT", -3, 3)
|
|
return frameBox
|
|
end
|
|
|
|
local function buildPage(f)
|
|
local p = CreateFrame("Frame", "ForeverUIGroupFinderDungeons", f)
|
|
p:SetAllPoints(f)
|
|
F.dungeonsPage = p
|
|
|
|
F.blue, F.roles = F.buildBanner(p, ROLES)
|
|
-- its camelot background covers the bottom of the blue, as in the source
|
|
local frameBox = F.buildFrameBox(p, "ForeverUIGroupFinderInset", G.frameBoxY1, true)
|
|
F.frameBox = frameBox
|
|
|
|
-- categories: stacked from the top, no scrolling; the rule and details follow the
|
|
-- last button
|
|
local cats = CreateFrame("Frame", "ForeverUIGroupFinderCategories", p)
|
|
cats:SetFrameLevel(frameBox:GetFrameLevel() + 1)
|
|
cats:SetPoint("TOPLEFT", f, "TOPLEFT", G.catX1, G.catY1)
|
|
cats:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", G.catX2, G.catY2)
|
|
cats.rows = {}
|
|
F.viewCategories = cats
|
|
local rule = cats:CreateTexture(nil, "OVERLAY")
|
|
ForeverUI.SetAtlas(rule, "shop-list-rule", true)
|
|
rule:SetHeight(G.ruleH)
|
|
F.rule = rule
|
|
|
|
-- specific dungeon list: the category list, down to the frame box bottom margin
|
|
F.listView = F.createCatList(p, "ForeverUIGroupFinderList", "ForeverUIGroupFinder", SOURCE_LFD,
|
|
G.listY2 + G.margin, frameBox)
|
|
|
|
buildRewards(p)
|
|
F.details:SetFrameLevel(frameBox:GetFrameLevel() + 1)
|
|
F.details.bar:SetFrameLevel(frameBox:GetFrameLevel() + 2)
|
|
buildVeils()
|
|
|
|
-- bottom buttons
|
|
local backButton = ForeverUI.CreatePanelButton(p, txt("BACK"), G.backL, G.buttonH, "ForeverUIGroupFinderBackButton", "GameFontNormal")
|
|
backButton:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", G.buttonSide, G.buttonBottom)
|
|
backButton:SetScript("OnClick", function()
|
|
PlaySound("igMainMenuOptionCheckBoxOn")
|
|
F.view = "categories"
|
|
F.update()
|
|
end)
|
|
F.backButton = backButton
|
|
local find = ForeverUI.CreatePanelButton(p, txt("FIND_A_GROUP"), G.postW, G.buttonH, "ForeverUIGroupFinderFindButton", "GameFontNormal")
|
|
find:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -G.buttonSide, G.buttonBottom)
|
|
-- the client button: its OnClick picks LeaveLFG or LFDQueueFrame_Join
|
|
find:SetScript("OnClick", function()
|
|
if LFDQueueFrameFindGroupButton and active(LFDQueueFrameFindGroupButton) then
|
|
LFDQueueFrameFindGroupButton:Click()
|
|
end
|
|
end)
|
|
F.find = find
|
|
end
|
|
|
|
-- ------------------------------------------------------------ Update
|
|
|
|
local function isQueued()
|
|
local mode = GetLFGMode()
|
|
return mode == "queued" or mode == "rolecheck" or mode == "proposal"
|
|
end
|
|
|
|
-- category buttons, the rule under the last one, the details below
|
|
local function placeCategories()
|
|
local cats = F.viewCategories
|
|
local n = #F.category_list
|
|
for i = 1, n do
|
|
local l = cats.rows[i]
|
|
if not l then
|
|
l = CreateFrame("Frame", nil, cats)
|
|
l:SetHeight(G.catH)
|
|
createCategory(l)
|
|
cats.rows[i] = l
|
|
end
|
|
l:ClearAllPoints()
|
|
l:SetPoint("TOPLEFT", cats, "TOPLEFT", 0, G.catFirst - (i - 1) * G.catStep)
|
|
l:SetPoint("TOPRIGHT", cats, "TOPRIGHT", 0, G.catFirst - (i - 1) * G.catStep)
|
|
fillCategory(l, i)
|
|
l:Show()
|
|
end
|
|
for i = n + 1, #cats.rows do cats.rows[i]:Hide() end
|
|
-- bottom of the last button, from the window top
|
|
local down = G.catY1 + G.catFirst - (n - 1) * G.catStep - G.catH
|
|
F.ruleMax = down - G.ruleGap
|
|
placeRule(nil)
|
|
end
|
|
|
|
local function updateDungeons()
|
|
local view = F.view or "categories"
|
|
if view == "list" and LFDQueueFrame.type ~= "specific" then view = "categories" end
|
|
F.view = view
|
|
|
|
F.category_list = F.categories()
|
|
placeCategories()
|
|
if view == "categories" then
|
|
F.viewCategories:Show()
|
|
updateRewards()
|
|
else
|
|
F.viewCategories:Hide()
|
|
F.details:Hide()
|
|
F.details.bar:Hide()
|
|
end
|
|
if view == "list" then
|
|
F.listView:Show()
|
|
F.updateCategoryList(F.listView)
|
|
else
|
|
F.listView:Hide()
|
|
F.listView.bar:Hide()
|
|
end
|
|
updateVeils()
|
|
|
|
-- Back exists only in the "Specific Dungeons" list
|
|
if view == "list" then F.backButton:Show() else F.backButton:Hide() end
|
|
local client = LFDQueueFrameFindGroupButton
|
|
if client then
|
|
F.find:SetText(client:GetText() or txt("FIND_A_GROUP"))
|
|
F.find:Activate(active(client))
|
|
end
|
|
end
|
|
|
|
-- Pages: one per tab; the dungeon page here, the raid page in GroupFinderRaid.lua
|
|
-- (F.registerPage)
|
|
F.pages = {}
|
|
function F.registerPage(key, frame, update)
|
|
F.pages[key] = { frame = frame, update = update }
|
|
end
|
|
|
|
function F.update()
|
|
if not F.frame then return end
|
|
F.updateRoles()
|
|
local pg = F.pages[F.tab or "dungeons"]
|
|
if pg then pg.update() end
|
|
end
|
|
|
|
-- show a page: select its tab, hide the other pages
|
|
function F.show(key)
|
|
F.selectTab(key)
|
|
for k, pg in pairs(F.pages) do
|
|
if k == key then pg.frame:Show() else pg.frame:Hide() end
|
|
end
|
|
F.update()
|
|
end
|
|
|
|
-- several client updates in one frame give a single update here
|
|
local waiter = CreateFrame("Frame")
|
|
waiter:Hide()
|
|
waiter:SetScript("OnUpdate", function(self)
|
|
self:Hide()
|
|
F.update()
|
|
end)
|
|
F.waiter = waiter
|
|
F.G, F.txt, F.active, F.color, F.COLORS, F.ROLES = G, txt, active, color, COLORS, ROLES
|
|
|
|
-- Schedules one update on the next frame while the window is shown.
|
|
function F.request()
|
|
if F.frame and F.frame:IsShown() then waiter:Show() end
|
|
end
|
|
|
|
-- ------------------------------------------------------------ Client panel
|
|
|
|
-- Silence the client panel: hide its regions and children except its content and our
|
|
-- window, and stop it catching the mouse. Its content stays shown (its logic depends on
|
|
-- it) but transparent and under our window, so its invisible buttons catch nothing.
|
|
local function suppressClient()
|
|
local p = LFDParentFrame
|
|
for _, r in ipairs({ p:GetRegions() }) do r:Hide() end
|
|
for _, c in ipairs({ p:GetChildren() }) do
|
|
if c ~= F.frame and c ~= LFDQueueFrame then c:Hide() end
|
|
end
|
|
p:EnableMouse(false)
|
|
LFDQueueFrame:SetAlpha(0)
|
|
LFDQueueFrame:ClearAllPoints()
|
|
LFDQueueFrame:SetPoint("TOPLEFT", F.frame, "TOPLEFT", 0, 0)
|
|
LFDQueueFrame:SetWidth(p:GetWidth())
|
|
LFDQueueFrame:SetHeight(p:GetHeight())
|
|
end
|
|
|
|
-- Our window moves between client panels: child of the open one, above its content;
|
|
-- at its saved position if moved, else at the panel's.
|
|
function F.attach(panel)
|
|
local f = F.frame
|
|
if f:GetParent() ~= panel then f:SetParent(panel) end
|
|
f:SetFrameLevel(panel:GetFrameLevel() + 30)
|
|
local pos = ForeverUIDB and ForeverUIDB.positions and ForeverUIDB.positions.finder
|
|
if not pos then
|
|
f:ClearAllPoints()
|
|
f:SetPoint("TOPLEFT", panel, "TOPLEFT", 0, 0)
|
|
end
|
|
end
|
|
|
|
-- A tab opens its client panel and closes the other. Dungeons stay closed below
|
|
-- SHOW_LFD_LEVEL, like the micro button.
|
|
function F.goTo(key)
|
|
if key == "dungeons" then
|
|
if UnitLevel("player") < (SHOW_LFD_LEVEL or 15) then return end
|
|
if LFRParentFrame and LFRParentFrame:IsShown() then HideUIPanel(LFRParentFrame) end
|
|
if not LFDParentFrame:IsShown() then ShowUIPanel(LFDParentFrame) end
|
|
elseif LFRParentFrame then
|
|
if LFDParentFrame:IsShown() then HideUIPanel(LFDParentFrame) end
|
|
if LFRParentFrame:IsShown() then F.show(key) else ShowUIPanel(LFRParentFrame) end
|
|
end
|
|
end
|
|
|
|
local function open()
|
|
if LFRParentFrame and LFRParentFrame:IsShown() then HideUIPanel(LFRParentFrame) end
|
|
F.attach(LFDParentFrame)
|
|
suppressClient()
|
|
-- on open, the categories; the list when queued for specific dungeons
|
|
if isQueued() and LFDQueueFrame.type == "specific" then
|
|
F.view = "list"
|
|
else
|
|
F.view = "categories"
|
|
end
|
|
F.frame:Show()
|
|
F.show("dungeons")
|
|
end
|
|
|
|
local function build()
|
|
if F.frame or not LFDParentFrame or not LFDQueueFrame then return end
|
|
local f = CreateFrame("Frame", "ForeverUIGroupFinderFrame", LFDParentFrame)
|
|
f:SetWidth(G.width)
|
|
f:SetHeight(G.height)
|
|
f:SetPoint("TOPLEFT", LFDParentFrame, "TOPLEFT", 0, 0)
|
|
-- above all client content (its veils go up to +16)
|
|
f:SetFrameLevel(LFDParentFrame:GetFrameLevel() + 30)
|
|
f:EnableMouse(true)
|
|
F.frame = f
|
|
buildFrame(f)
|
|
buildPage(f)
|
|
|
|
F.registerPage("dungeons", F.dungeonsPage, updateDungeons)
|
|
F.tabs = {}
|
|
local previous
|
|
for n, def in ipairs(TABS) do
|
|
if def.raid and not LFRParentFrame then break end
|
|
local o = createTab(f, def, n)
|
|
if previous then
|
|
o:SetPoint("TOPLEFT", previous, "BOTTOMLEFT", 0, G.tabGap)
|
|
else
|
|
o:SetPoint("TOPLEFT", f, "TOPRIGHT", 0, G.tabY)
|
|
end
|
|
F.tabs[n] = o
|
|
previous = o
|
|
end
|
|
f:Hide()
|
|
end
|
|
|
|
build()
|
|
|
|
if F.frame then
|
|
LFDParentFrame:HookScript("OnShow", open)
|
|
LFDParentFrame:HookScript("OnHide", function()
|
|
if F.frame:GetParent() == LFDParentFrame then F.frame:Hide() end
|
|
end)
|
|
-- each client update and type change (NPC gossip also calls it): the view follows
|
|
-- the chosen type
|
|
for _, name in ipairs({ "LFDQueueFrameSpecificList_Update", "LFDQueueFrameRandom_UpdateFrame",
|
|
"LFDQueueFrameFindGroupButton_Update", "LFG_UpdateRolesChangeable", "LFG_UpdateRoleCheckboxes",
|
|
"LFG_UpdateLockedOutPanels", "LFDFrame_UpdateBackfill", "LFDQueueFrameRandomCooldownFrame_Update" }) do
|
|
if _G[name] then hooksecurefunc(name, F.request) end
|
|
end
|
|
if LFDQueueFrame_SetType then
|
|
hooksecurefunc("LFDQueueFrame_SetType", function(value)
|
|
F.view = (value == "specific") and "list" or "categories"
|
|
F.request()
|
|
end)
|
|
end
|
|
local listener = CreateFrame("Frame")
|
|
for _, ev in ipairs({ "LFG_UPDATE", "LFG_ROLE_UPDATE", "LFG_LOCK_INFO_RECEIVED",
|
|
"LFG_UPDATE_RANDOM_INFO", "PARTY_MEMBERS_CHANGED" }) do
|
|
listener:RegisterEvent(ev)
|
|
end
|
|
listener:SetScript("OnEvent", F.request)
|
|
-- movable by its title; raised when opened or clicked
|
|
ForeverUI.WindowStack.makeMovable(F.frame, F.frame.banner, "finder")
|
|
ForeverUI.WindowStack.register("finder", LFDParentFrame, function()
|
|
local z = { F.frame }
|
|
for _, o in ipairs(F.tabs) do z[#z + 1] = o end
|
|
return z
|
|
end)
|
|
end
|