afdd314370
Listes de réputation, compétences et statistiques : une ligne qui ne tenait plus était cachée, mais la boucle continuait, et une ligne plus courte plus loin (un en-tête après une entrée) prenait sa place, hors d'ordre. Rien n'est plus placé après la première ligne qui ne tient pas. Statistiques : la page possédait les douze lignes et les réaffichait toutes après chaque mise à jour, y compris la sixième que le client cache pour la catégorie Distance (les chasseurs par défaut), avec le contenu de la catégorie d'avant. Elle possède désormais leur parent, CharacterAttributesFrame. Ensembles : le panneau était créé visible, et GearManagerDialog_Update (ensemble porté depuis une barre, /equipset) pouvait le construire avant la première ouverture de sa page, par-dessus les statistiques. Il naît caché, et sa page le connaît même ouverte en combat. L'ordre d'affichage et le dernier ensemble porté étaient au compte, alors que les ensembles sont à un personnage : ouvrir la page sur un autre personnage élaguait l'ordre du premier. Ils sont rangés par personnage, et le premier usage reprend les anciennes valeurs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KxRZNHkq453dyUMcWsyUf1
632 lines
18 KiB
Lua
632 lines
18 KiB
Lua
-- Skills tab of the character frame (camelot SkillsFrame.xml / skillsframe.lua), the same
|
|
-- screen as the reputation tab. Headers are 26 high (not 28), entries 30. The entry bar
|
|
-- (SkillsBarTemplate 160 x 29) is blue through its sprite, not a tint, and always shows
|
|
-- the progress. Right pane: SkillDetailFrameMixin with a 180 x 29 RankBar.
|
|
-- 3.3.5 differences: GetSkillLineInfo has one level, so there are no sub-headers; weapon
|
|
-- lines get no hit / crit detail (no WEAPON_SKILL_DETAIL_* strings); the description does
|
|
-- not scroll (no ScrollingFontTemplate); the scroll bar is MinimalScrollBar from ScrollBar.lua.
|
|
|
|
local ForeverUI = ForeverUI or {}
|
|
_G.ForeverUI = ForeverUI
|
|
local L = ForeverUI.L
|
|
|
|
local LIST_X, LIST_Y = 10, -40
|
|
local LIST_X2, LIST_Y2 = -25, 15
|
|
|
|
local ENTRY_H = 30 -- SkillsEntryTemplate
|
|
local HEADER_H = 26 -- SkillsHeaderTemplate
|
|
local MARGIN = 10 -- SetPadding
|
|
local GAP = 3 -- elementSpacing
|
|
local HEADER_INDENT = 0
|
|
local OTHER_INDENT = 2
|
|
|
|
local BAR_W, BAR_H = 160, 29
|
|
local BAR_X = -3
|
|
local FILL_H = 15
|
|
local NAME_H = 15
|
|
local NAME_X = 2 -- SkillsEntryTemplate
|
|
local NAME_GAP = -10
|
|
local HEADER_NAME_X = 10
|
|
local ARROW_X, ARROW_Y = -8, -1
|
|
local ARROW_SPACE = 16
|
|
local SIDE = 6
|
|
|
|
local PLATE_CORNER = 12 -- same as the reputation tab
|
|
local GAUGE_CORNER = 10
|
|
|
|
local ATLAS_BAR_BACKGROUND = "common-stat-bar-bg"
|
|
local ATLAS_HEADER = "common-button-list-collapseexpand"
|
|
local ATLAS_PLUS = "common-button-list-plus"
|
|
local ATLAS_MINUS = "common-button-list-minus"
|
|
local ATLAS_LINE = "ui-character-info-scrollline-long"
|
|
local ATLAS_HOVER_SIDE = "charactercreate-customize-dropdown-linemouseover-side"
|
|
local ATLAS_HOVER_MIDDLE = "charactercreate-customize-dropdown-linemouseover-middle"
|
|
local ATLAS_SEPARATOR = "ui-character-info-scrollline"
|
|
|
|
-- Blue fill, baked with the mask like the reputation's white one: same slicing as the
|
|
-- background, so the outlines overlap.
|
|
local FILL_PATH = "Interface\\ForeverUI\\Bars\\statbarfillblue"
|
|
|
|
-- Right pane: CharacterFrameSidePaneTemplate.
|
|
local RIGHT_PANE_X, RIGHT_PANE_Y = 16, -14
|
|
local RIGHT_PANE_X2, RIGHT_PANE_Y2 = -12, 14
|
|
local TITLE_W = 195
|
|
local SUBTITLE_Y = -3
|
|
local SEPARATOR_Y = -4
|
|
local RANK_W, RANK_H = 180, 29
|
|
local RANK_Y = -6
|
|
local DESCRIPTION_Y = -8 -- not -6: SkillDetailFrameMixin
|
|
local DESCRIPTION_X2 = -14
|
|
local DESCRIPTION_Y2 = 6
|
|
|
|
-- Pane size, used while the host has no size yet.
|
|
local PANE_W, PANE_H = 398, 464
|
|
|
|
local rows = {}
|
|
local panel, offset = nil, 0
|
|
local visibleCount = 0
|
|
local selectedItem -- the NAME, the only stable key
|
|
local detail, lastData
|
|
|
|
-- --------------------------------------------------------------- Data
|
|
|
|
-- GetSkillLineInfo returns, in order: name, header, expanded, rank, temporary points, bonus,
|
|
-- max rank, abandonable, step cost, rank cost, min level, cost type, description.
|
|
local function readSkill(rank)
|
|
local name, header, expanded, value, temporary, bonus, maximum, _, _, _,
|
|
_, _, description = GetSkillLineInfo(rank)
|
|
if not name or name == "" then
|
|
return nil
|
|
end
|
|
|
|
value = (value or 0) + (temporary or 0)
|
|
bonus = bonus or 0
|
|
maximum = maximum or 0
|
|
|
|
-- InitializeBarForStandardSkill: "rank / max", or "rank (+bonus) / max" with the bonus
|
|
-- in green.
|
|
local text
|
|
if bonus == 0 then
|
|
text = tostring(value) .. " / " .. tostring(maximum)
|
|
else
|
|
text = tostring(value) .. " |cff00ff00(+" .. tostring(bonus)
|
|
.. ")|r / " .. tostring(maximum)
|
|
end
|
|
|
|
return {
|
|
index = rank,
|
|
name = name,
|
|
header = header,
|
|
collapsed = (header and not expanded) or false,
|
|
value = value,
|
|
maximum = maximum,
|
|
text = text,
|
|
description = description or "",
|
|
}
|
|
end
|
|
|
|
-- Skill line index of a skill name, or nil
|
|
local function indexOf(name)
|
|
if not name then
|
|
return nil
|
|
end
|
|
for rank = 1, (GetNumSkillLines and GetNumSkillLines()) or 0 do
|
|
if GetSkillLineInfo(rank) == name then
|
|
return rank
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
-- --------------------------------------------------------------- Row
|
|
|
|
local function createRow(index, width)
|
|
local row = CreateFrame("Button", "ForeverUISkillRow" .. index, panel)
|
|
row:SetWidth(width)
|
|
row:SetHeight(ENTRY_H)
|
|
|
|
row.plate = ForeverUI.CreateNineSlice(row, ATLAS_HEADER, PLATE_CORNER,
|
|
{ 0, 0, 0, 0 }, "BACKGROUND") or {}
|
|
for _, slice in ipairs(row.plate) do
|
|
slice:Hide()
|
|
end
|
|
|
|
local hover = CreateFrame("Frame", nil, row)
|
|
hover:SetAllPoints(row)
|
|
hover:SetAlpha(0)
|
|
row.hover = hover
|
|
|
|
local left = hover:CreateTexture(nil, "BACKGROUND")
|
|
ForeverUI.SetAtlas(left, ATLAS_HOVER_SIDE, true)
|
|
left:SetWidth(SIDE)
|
|
left:SetPoint("TOPLEFT", hover, "TOPLEFT", 0, 0)
|
|
left:SetPoint("BOTTOMLEFT", hover, "BOTTOMLEFT", 0, 0)
|
|
|
|
local right = hover:CreateTexture(nil, "BACKGROUND")
|
|
if ForeverUI.SetAtlas(right, ATLAS_HOVER_SIDE, true) then
|
|
local e = ForeverUI.AtlasEntry(ATLAS_HOVER_SIDE)
|
|
right:SetTexCoord(e[3], e[2], e[4], e[5])
|
|
end
|
|
right:SetWidth(SIDE)
|
|
right:SetPoint("TOPRIGHT", hover, "TOPRIGHT", 0, 0)
|
|
right:SetPoint("BOTTOMRIGHT", hover, "BOTTOMRIGHT", 0, 0)
|
|
|
|
local middle = hover:CreateTexture(nil, "BACKGROUND")
|
|
ForeverUI.SetAtlas(middle, ATLAS_HOVER_MIDDLE, true)
|
|
middle:SetPoint("TOPLEFT", left, "TOPRIGHT", 0, 0)
|
|
middle:SetPoint("BOTTOMRIGHT", right, "BOTTOMLEFT", 0, 0)
|
|
|
|
local bar = CreateFrame("Frame", nil, row)
|
|
bar:SetWidth(BAR_W)
|
|
bar:SetHeight(BAR_H)
|
|
bar:SetPoint("RIGHT", row, "RIGHT", BAR_X, 0)
|
|
row.bar = bar
|
|
|
|
ForeverUI.CreateNineSlice(bar, ATLAS_BAR_BACKGROUND, GAUGE_CORNER,
|
|
{ 0, 0, 0, 0 }, "BACKGROUND")
|
|
|
|
bar.fill = bar:CreateTexture(nil, "BORDER")
|
|
bar.fill:SetPoint("LEFT", bar, "LEFT", 0, 0)
|
|
|
|
bar.text = bar:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
|
|
bar.text:SetPoint("LEFT", bar, "LEFT", 0, 0)
|
|
bar.text:SetPoint("RIGHT", bar, "RIGHT", 0, 0)
|
|
bar.text:SetJustifyH("CENTER")
|
|
|
|
local name = row:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
|
|
name:SetHeight(NAME_H)
|
|
name:SetJustifyH("LEFT")
|
|
row.name = name
|
|
|
|
local arrow = row:CreateTexture(nil, "OVERLAY")
|
|
arrow:SetPoint("RIGHT", row, "RIGHT", ARROW_X, ARROW_Y)
|
|
arrow:Hide()
|
|
row.arrow = arrow
|
|
|
|
row:RegisterForClicks("LeftButtonUp")
|
|
return row
|
|
end
|
|
|
|
-- ----------------------------------------------------------- Fill
|
|
|
|
-- SetFillPercent: width = fraction x bar width, texture cropped the same.
|
|
local function placeBarIn(bar, data, width)
|
|
local fraction = 0
|
|
if data.maximum and data.maximum > 0 then
|
|
fraction = data.value / data.maximum
|
|
end
|
|
if fraction < 0 then
|
|
fraction = 0
|
|
elseif fraction > 1 then
|
|
fraction = 1
|
|
end
|
|
|
|
if fraction * width < 1 then
|
|
bar.fill:Hide()
|
|
else
|
|
bar.fill:SetTexture(FILL_PATH)
|
|
bar.fill:SetTexCoord(0, fraction, 0, 1)
|
|
bar.fill:SetWidth(width * fraction)
|
|
bar.fill:SetHeight(FILL_H)
|
|
bar.fill:Show()
|
|
end
|
|
|
|
bar.text:SetText(data.text or "")
|
|
end
|
|
|
|
-- RefreshBackgroundHighlightOpacity, without the "at war" case; the tint is always white.
|
|
local function placeHover(row)
|
|
if row.header then
|
|
row.hover:SetAlpha(0)
|
|
return
|
|
end
|
|
|
|
local hovered = row:IsMouseOver()
|
|
row.hover:SetAlpha((row.selectedItem and 0.20) or (hovered and 0.10) or 0)
|
|
end
|
|
|
|
-- SetFontObject resets the justification to the font's own (GameFontHighlight is centered
|
|
-- in the client's FontStyles.xml), so SetJustifyH must come after it, as camelot sets
|
|
-- justifyH="LEFT" on top of the font object.
|
|
local function populateRow(row, data)
|
|
row.skillIndex = data.index
|
|
row.skillName = data.name
|
|
row.header = data.header
|
|
row.collapsed = data.collapsed
|
|
|
|
row.name:SetText(data.name or "")
|
|
row.name:ClearAllPoints()
|
|
|
|
for _, slice in ipairs(row.plate) do
|
|
if data.header then slice:Show() else slice:Hide() end
|
|
end
|
|
|
|
if data.header then
|
|
row:SetHeight(HEADER_H)
|
|
row.bar:Hide()
|
|
row.name:SetFontObject(GameFontNormalLeft or GameFontNormal)
|
|
row.name:SetJustifyH("LEFT")
|
|
row.name:SetPoint("LEFT", row, "LEFT", HEADER_NAME_X, 0)
|
|
row.name:SetPoint("RIGHT", row, "RIGHT", ARROW_X - ARROW_SPACE, 0)
|
|
|
|
ForeverUI.SetAtlas(row.arrow, data.collapsed and ATLAS_PLUS or ATLAS_MINUS)
|
|
row.arrow:Show()
|
|
else
|
|
row:SetHeight(ENTRY_H)
|
|
row.bar:Show()
|
|
row.arrow:Hide()
|
|
row.name:SetFontObject(GameFontHighlight or GameFontNormal)
|
|
row.name:SetJustifyH("LEFT")
|
|
row.name:SetPoint("LEFT", row, "LEFT", NAME_X, 0)
|
|
row.name:SetPoint("RIGHT", row.bar, "LEFT", NAME_GAP, 0)
|
|
placeBarIn(row.bar, data, BAR_W)
|
|
end
|
|
|
|
row.selectedItem = (selectedItem ~= nil and selectedItem == data.name)
|
|
placeHover(row)
|
|
row:Show()
|
|
end
|
|
|
|
-- ------------------------------------------------------------- Layout
|
|
|
|
local function indentOf(data)
|
|
return data.header and HEADER_INDENT or OTHER_INDENT
|
|
end
|
|
|
|
local function heightOf(data)
|
|
return data.header and HEADER_H or ENTRY_H
|
|
end
|
|
|
|
-- Hides everything the client's SkillFrame holds except our panel: its regions
|
|
-- (GetRegions) and child frames (GetChildren). Runs on every pass, since
|
|
-- SkillFrame_UpdateSkills shows them again.
|
|
local function suppressClientScreen()
|
|
local frame = _G["SkillFrame"]
|
|
if not frame then
|
|
return
|
|
end
|
|
|
|
for _, region in ipairs({ frame:GetRegions() }) do
|
|
if region.Hide then
|
|
region:Hide()
|
|
end
|
|
end
|
|
|
|
if frame.GetChildren then
|
|
for _, childFrame in ipairs({ frame:GetChildren() }) do
|
|
if childFrame ~= panel and childFrame.Hide then
|
|
childFrame:Hide()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function layout()
|
|
local total = (GetNumSkillLines and GetNumSkillLines()) or 0
|
|
|
|
local usableHeight = (panel:GetHeight() or 0)
|
|
if usableHeight < 50 then
|
|
usableHeight = PANE_H + LIST_Y - LIST_Y2
|
|
end
|
|
local usableWidth = (panel:GetWidth() or 0)
|
|
if usableWidth < 50 then
|
|
usableWidth = PANE_W + LIST_X2 - LIST_X
|
|
end
|
|
|
|
local y = MARGIN
|
|
local placedCount = 0
|
|
local full = false
|
|
for rank, row in ipairs(rows) do
|
|
local index = offset + rank
|
|
local data = (not full and index <= total) and readSkill(index) or nil
|
|
local height = data and heightOf(data) or 0
|
|
if data and y + height <= usableHeight - MARGIN then
|
|
local indent = indentOf(data)
|
|
row:SetWidth(usableWidth - 2 * MARGIN - indent)
|
|
populateRow(row, data)
|
|
row:ClearAllPoints()
|
|
row:SetPoint("TOPLEFT", panel, "TOPLEFT", MARGIN + indent, -y)
|
|
y = y + height + GAP
|
|
placedCount = placedCount + 1
|
|
else
|
|
-- Past the first row that does not fit nothing is placed: a shorter row further down
|
|
-- (a header after an entry) would otherwise take its slot, out of order
|
|
full = full or data ~= nil
|
|
row:Hide()
|
|
end
|
|
end
|
|
return placedCount
|
|
end
|
|
|
|
local function layoutList()
|
|
if not panel then
|
|
return
|
|
end
|
|
|
|
suppressClientScreen()
|
|
|
|
local total = (GetNumSkillLines and GetNumSkillLines()) or 0
|
|
local placedCount = layout()
|
|
|
|
if offset > 0 and offset + placedCount > total then
|
|
offset = math.max(0, total - placedCount)
|
|
placedCount = layout()
|
|
end
|
|
|
|
visibleCount = placedCount
|
|
|
|
if panel.bar then
|
|
panel.bar:Configure(total, placedCount, offset)
|
|
end
|
|
|
|
if ForeverUI.SkillsDetail then
|
|
ForeverUI.SkillsDetail()
|
|
end
|
|
end
|
|
ForeverUI.SkillsLayout = layoutList
|
|
|
|
-- --------------------------------------------------------------- Detail
|
|
|
|
local function clearDetail()
|
|
detail.title:SetText("")
|
|
detail.subtitle:SetText("")
|
|
-- SKILL_DETAIL_SELECT_PROMPT does not exist in 3.3.5: left empty.
|
|
detail.description:SetText("")
|
|
detail.separator:Hide()
|
|
detail.gauge:Hide()
|
|
end
|
|
|
|
local function updateDetail()
|
|
if not detail then
|
|
return
|
|
end
|
|
|
|
if not selectedItem then
|
|
lastData = nil
|
|
clearDetail()
|
|
return
|
|
end
|
|
|
|
local index = indexOf(selectedItem)
|
|
local data
|
|
if index then
|
|
if SetSelectedSkill and GetSelectedSkill and GetSelectedSkill() ~= index then
|
|
SetSelectedSkill(index)
|
|
end
|
|
data = readSkill(index)
|
|
lastData = data
|
|
else
|
|
data = lastData
|
|
end
|
|
|
|
if not data or data.header then
|
|
clearDetail()
|
|
return
|
|
end
|
|
|
|
detail.title:SetText(data.name or "")
|
|
detail.subtitle:SetText("")
|
|
detail.separator:Show()
|
|
detail.gauge:Show()
|
|
placeBarIn(detail.gauge, data, RANK_W)
|
|
detail.description:SetText(data.description or "")
|
|
end
|
|
ForeverUI.SkillsDetail = updateDetail
|
|
|
|
local function choose(name)
|
|
selectedItem = name
|
|
local index = indexOf(name)
|
|
if index and SetSelectedSkill then
|
|
SetSelectedSkill(index)
|
|
end
|
|
layoutList()
|
|
end
|
|
ForeverUI.SkillsSelect = choose
|
|
|
|
-- SelectFirstSkillIfNoneSelected: on opening, select the first non-header skill so the
|
|
-- right pane is not empty.
|
|
local function selectFirst()
|
|
if selectedItem then
|
|
return
|
|
end
|
|
for rank = 1, (GetNumSkillLines and GetNumSkillLines()) or 0 do
|
|
local data = readSkill(rank)
|
|
if data and not data.header then
|
|
choose(data.name)
|
|
return
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Builds the right pane (skill detail) in host
|
|
local function buildDetail(host)
|
|
if detail then
|
|
return detail, {}
|
|
end
|
|
|
|
local frame = CreateFrame("Frame", "ForeverUISkillDetail", host)
|
|
frame:SetPoint("TOPLEFT", host, "TOPLEFT", RIGHT_PANE_X, RIGHT_PANE_Y)
|
|
frame:SetPoint("BOTTOMRIGHT", host, "BOTTOMRIGHT", RIGHT_PANE_X2, RIGHT_PANE_Y2)
|
|
detail = frame
|
|
|
|
detail.title = frame:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge")
|
|
detail.title:SetWidth(TITLE_W)
|
|
detail.title:SetJustifyH("CENTER")
|
|
detail.title:SetPoint("TOP", frame, "TOP", 0, 0)
|
|
|
|
detail.subtitle = frame:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
|
|
detail.subtitle:SetWidth(TITLE_W)
|
|
detail.subtitle:SetJustifyH("CENTER")
|
|
detail.subtitle:SetPoint("TOP", detail.title, "BOTTOM", 0, SUBTITLE_Y)
|
|
|
|
detail.separator = frame:CreateTexture(nil, "BORDER")
|
|
ForeverUI.SetAtlas(detail.separator, ATLAS_SEPARATOR)
|
|
detail.separator:SetPoint("TOP", detail.subtitle, "BOTTOM", 0, SEPARATOR_Y)
|
|
|
|
local gauge = CreateFrame("Frame", "ForeverUISkillRank", frame)
|
|
gauge:SetWidth(RANK_W)
|
|
gauge:SetHeight(RANK_H)
|
|
gauge:SetPoint("TOP", detail.separator, "BOTTOM", 0, RANK_Y)
|
|
ForeverUI.CreateNineSlice(gauge, ATLAS_BAR_BACKGROUND, GAUGE_CORNER,
|
|
{ 0, 0, 0, 0 }, "BACKGROUND")
|
|
gauge.fill = gauge:CreateTexture(nil, "BORDER")
|
|
gauge.fill:SetPoint("LEFT", gauge, "LEFT", 0, 0)
|
|
gauge.text = gauge:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
|
|
gauge.text:SetPoint("LEFT", gauge, "LEFT", 0, 0)
|
|
gauge.text:SetPoint("RIGHT", gauge, "RIGHT", 0, 0)
|
|
gauge.text:SetJustifyH("CENTER")
|
|
detail.gauge = gauge
|
|
|
|
-- Description in a box: without a bottom anchor, a long text disappears.
|
|
detail.description = frame:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
|
|
detail.description:SetPoint("TOPLEFT", gauge, "BOTTOMLEFT", 0, DESCRIPTION_Y)
|
|
detail.description:SetPoint("TOPRIGHT", gauge, "BOTTOMRIGHT", 0, DESCRIPTION_Y)
|
|
detail.description:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 0, DESCRIPTION_Y2)
|
|
detail.description:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT",
|
|
DESCRIPTION_X2, DESCRIPTION_Y2)
|
|
detail.description:SetJustifyH("LEFT")
|
|
detail.description:SetJustifyV("TOP")
|
|
if detail.description.SetWordWrap then
|
|
detail.description:SetWordWrap(true)
|
|
end
|
|
|
|
updateDetail()
|
|
return frame, {}
|
|
end
|
|
|
|
-- --------------------------------------------------------- Build
|
|
|
|
local function trackHover()
|
|
for _, row in ipairs(rows) do
|
|
if row:IsShown() and not row.header then
|
|
placeHover(row)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Builds the skill list in host over the client's SkillFrame
|
|
local function build(host)
|
|
local frame = _G["SkillFrame"]
|
|
if not frame or not host then
|
|
return nil, {}
|
|
end
|
|
|
|
frame:ClearAllPoints()
|
|
frame:SetPoint("TOPLEFT", host, "TOPLEFT", 0, 0)
|
|
frame:SetPoint("BOTTOMRIGHT", host, "BOTTOMRIGHT", 0, 0)
|
|
|
|
if panel then
|
|
selectFirst()
|
|
return nil, { frame }
|
|
end
|
|
|
|
-- The 3.3.5 art and rows go away; only the frame is kept.
|
|
if frame.SetBackdrop then
|
|
frame:SetBackdrop(nil)
|
|
end
|
|
|
|
local height = host:GetHeight() or 0
|
|
if height < 100 then
|
|
height = PANE_H
|
|
end
|
|
local width = host:GetWidth() or 0
|
|
if width < 100 then
|
|
width = PANE_W
|
|
end
|
|
width = width + LIST_X2 - LIST_X
|
|
|
|
panel = CreateFrame("Frame", "ForeverUISkillList", frame)
|
|
panel:SetPoint("TOPLEFT", host, "TOPLEFT", LIST_X, LIST_Y)
|
|
panel:SetPoint("BOTTOMRIGHT", host, "BOTTOMRIGHT", LIST_X2, LIST_Y2)
|
|
panel:SetWidth(width)
|
|
|
|
local position = math.floor((height + LIST_Y - LIST_Y2) / (HEADER_H + GAP))
|
|
if position < 1 then
|
|
position = 1
|
|
end
|
|
for index = 1, position do
|
|
local row = createRow(index, width)
|
|
row:SetScript("OnClick", function(self)
|
|
if not self.skillIndex then
|
|
return
|
|
end
|
|
if self.header then
|
|
if self.collapsed then
|
|
ExpandSkillHeader(self.skillIndex)
|
|
else
|
|
CollapseSkillHeader(self.skillIndex)
|
|
end
|
|
layoutList()
|
|
else
|
|
ForeverUI.SkillsSelect(self.skillName)
|
|
end
|
|
end)
|
|
rows[index] = row
|
|
end
|
|
|
|
-- Both lines live on our panel, not on the client's frame: its regions are all hidden
|
|
-- on every pass.
|
|
local top = panel:CreateTexture(nil, "ARTWORK")
|
|
ForeverUI.SetAtlas(top, ATLAS_LINE)
|
|
top:SetPoint("CENTER", panel, "TOP", 0, 0)
|
|
|
|
local down = panel:CreateTexture(nil, "ARTWORK")
|
|
ForeverUI.SetAtlas(down, ATLAS_LINE)
|
|
down:SetPoint("CENTER", panel, "BOTTOM", 0, 0)
|
|
|
|
-- Camelot's scroll bar, right of the list, as in the reputation tab. It does not know the
|
|
-- list: it gets three numbers and returns the new offset.
|
|
panel.bar = ForeverUI.CreateScrollBar("ForeverUISkillsScrollBar",
|
|
frame, panel)
|
|
panel.bar.onScroll = function(new)
|
|
offset = new
|
|
layoutList()
|
|
end
|
|
-- Without a bar the list takes its place, as in the reputation tab: right edge from -25
|
|
-- to -10, rows laid out again.
|
|
panel.bar.onVisibility = function(hasBar)
|
|
local x2 = hasBar and LIST_X2 or -LIST_X
|
|
panel:SetPoint("BOTTOMRIGHT", host, "BOTTOMRIGHT", x2, LIST_Y2)
|
|
panel:SetWidth(width + x2 - LIST_X2)
|
|
layout()
|
|
end
|
|
|
|
panel:SetScript("OnUpdate", trackHover)
|
|
panel:EnableMouseWheel(true)
|
|
panel:SetScript("OnMouseWheel", function(self, direction)
|
|
local total = (GetNumSkillLines and GetNumSkillLines()) or 0
|
|
offset = math.max(0, math.min(offset - direction, total - visibleCount))
|
|
layoutList()
|
|
end)
|
|
|
|
layoutList()
|
|
selectFirst()
|
|
return nil, { frame }
|
|
end
|
|
|
|
ForeverUI.SkillsTab = { Build = build, BuildRight = buildDetail, Rows = rows }
|
|
|
|
-- Debug output: /fui skills.
|
|
function ForeverUI.SkillsDebug()
|
|
local say = function(text)
|
|
DEFAULT_CHAT_FRAME:AddMessage("|cff66ccffForeverUI|r " .. text)
|
|
end
|
|
|
|
local total = (GetNumSkillLines and GetNumSkillLines()) or 0
|
|
say(string.format(L.SKILLSTAB_DEBUG_SUMMARY,
|
|
total, offset, visibleCount, tostring(selectedItem)))
|
|
for rank = 1, total do
|
|
local d = readSkill(rank)
|
|
if d then
|
|
DEFAULT_CHAT_FRAME:AddMessage(string.format(
|
|
L.SKILLSTAB_DEBUG_ROW,
|
|
rank, d.name, tostring(d.header), tostring(d.collapsed), d.text))
|
|
end
|
|
end
|
|
end
|
|
|
|
-- The client rebuilds its list in SkillFrame_UpdateSkills: run after it.
|
|
if hooksecurefunc and type(_G["SkillFrame_UpdateSkills"]) == "function" then
|
|
hooksecurefunc("SkillFrame_UpdateSkills", layoutList)
|
|
end
|