Files
moon-well-ui/data/addon/ForeverUI/TradeSkill.lua
T
Adrestie ae87393c0c ForeverUI : textes de la fiche de fabrication repliés
Le nom du résultat et les outils débordaient de la fiche (largeur de 800
de la fiche camelot normale) : ils suivent la règle de la fiche réduite de
camelot, 250 de large sur plusieurs lignes, et ce qui suit descend sous eux
quand ils dépassent sa place. Les composants passent au pas de 163 pour que
la deuxième colonne reste dans la fiche de 360. Écart noté dans NOTES.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 01:42:35 +02:00

1380 lines
51 KiB
Lua

-- ForeverUI: the profession crafting page (TradeSkillFrame, from Blizzard_TradeSkillUI) with
-- Camelot's ProfessionsFrame layout; the overview page and side tabs are in ProfessionsBook.lua.
-- The client's TradeSkillFrame stays the host (panel, events, selection): its widgets are hidden
-- and mouse-less, and our list, schematic, rank bar and buttons run on its data. Favorites,
-- tracking, qualities, concentration and orders do not exist in 3.3.5 and are left out.
local ForeverUI = ForeverUI or {}
_G.ForeverUI = ForeverUI
local Tpl = ForeverUI.Templates
local L = ForeverUI.L
local M = {}
ForeverUI.Professions = M
local SEP = string.char(92)
local FONT = "Fonts" .. SEP .. "FRIZQT__.TTF"
local NUMBER_FONT = "Fonts" .. SEP .. "ARIALN.TTF"
local BUTTONS = "Interface" .. SEP .. "Buttons" .. SEP
local N = {
window = { 673, 594 },
portrait = { side = 48, x = 1, y = 1.5 },
background = { 2, -21, -2, 2 }, -- Bg of PortraitFrameTemplate
page = { 3, -21 }, -- Profession-Background-Template2
list = { 5, -72, 304, down = 5 },
filter = { -8, -9, 18 },
search = { 13, -8, 20, gap = -4 },
zone = { 8, -35, -20, 5 },
tree = { indent = 10, top = 5, down = 5, right = 5, space = 1, hovered = 1, below = 10 },
category = { h = 25, text = 8, button = 20, buttonX = -6 },
recipe = { h = 20, progress = { 26, 15, -9 }, nameX = 4, margin = 10 },
none = { 0, -60, 200 },
step = 21, -- one wheel step: one recipe
rank = { 110, -40, 453, 18, background = { 451, 29 }, filled = { 441, 18, 5, -3 }, mask = 1,
flare = { 53, 16 }, text = -3 },
link = { -2, -4, 23, background = 34, icon = 25 },
card = { 2, 0, 360, 484 },
result = { 28, -28, 47, icon = 53, outline = 68, glow = 66, count = { -4, 1 } },
name = { 14, 17 },
tools = { 0, -4 },
textWidth = 250, -- name and tools: camelot's minimized schematic, wrapped
organizer = { -1, -12, 4 },
cooldown = 400, description = 305,
-- slot 158 wide (camelot 180): the schematic is 360 wide (camelot 493), the second column
-- has to stay inside it
reagents = { 0, -20, tag = { 180, 20 }, slot = { 158, 50 }, start = { 1, -20 },
gap = { 5, 5 }, column = 4, button = 39, nameX = 46, nameSize = { 108, 36 } },
create = { -9, 7, 80, 28, margin = 30 },
createAll = { -362, 7 },
counter = { -185, 11, 31, 20, arrow = { 23, 22 }, minusGap = -6 },
levels = { page = 1, list = 2, rows = 3, card = 2, rank = 5, closeButton = 22 },
}
-- Camelot's GlobalColor
local COLORS = {
recipe = { 0.8863, 0.8627, 0.8392 }, -- PROFESSION_RECIPE_COLOR
missingReagent = { 0.6275, 0.6275, 0.6275 }, -- DISABLED_REAGENT_COLOR
}
local PROGRESS = {
optimal = { "professions-icon-skill-high", 1, L.TRADESKILL_SKILL_UP_OPTIMAL },
medium = { "professions-icon-skill-medium", 0, L.TRADESKILL_SKILL_UP_MEDIUM },
easy = { "professions-icon-skill-low", 0, L.TRADESKILL_SKILL_UP_EASY },
}
-- Profession, found by its spell icon (3.3.5 Spell.dbc), the same in every language: background
-- card, rank fill and flare (c60 variant when Camelot has one). Jewelcrafting and inscription have
-- no card in Camelot: Professions-Recipe-Background is used.
local PROFESSIONS = {
["trade_alchemy"] = { map = "profession-background-card-alchemy", rank = "alchemy_c60" },
["trade_blacksmithing"] = { map = "profession-background-card-blacksmithing", rank = "blacksmithing" },
["trade_engraving"] = { map = "profession-background-card-enchanting", rank = "enchanting_c60" },
["trade_engineering"] = { map = "profession-background-card-engineering", rank = "engineering" },
["inv_inscription_tradeskill01"] = { rank = "inscription" },
["inv_misc_gem_01"] = { rank = "jewelcrafting" },
["inv_misc_gem_02"] = { rank = "jewelcrafting" },
["inv_misc_armorkit_17"] = { map = "profession-background-card-leatherworking", rank = "leatherworking" },
["trade_tailoring"] = { map = "profession-background-card-tailoring", rank = "tailoring" },
["inv_misc_food_15"] = { map = "profession-background-card-cooking", rank = "cooking" },
["spell_holy_sealofsacrifice"] = { map = "profession-background-card-firstaid", rank = "firstaid_c60" },
["trade_mining"] = { map = "profession-background-card-mining", rank = "mining" },
}
-- AUCTION_HOUSE_ITEM_QUALITY_ICON_BORDER_ATLASES / PROFESSIONS_ITEM_QUALITY_-
-- ICON_BORDER_ATLASES (blizzard_colors)
local RESULT_OUTLINE = { [0] = "gray", [1] = "white", [2] = "green", [3] = "blue", [4] = "purple",
[5] = "orange", [6] = "artifact", [7] = "account" }
local REAGENT_OUTLINE = { [1] = "professions-slot-frame", [2] = "professions-slot-frame-green",
[3] = "professions-slot-frame-blue", [4] = "professions-slot-frame-epic", [5] = "professions-slot-frame-legendary" }
local function place(r, ...)
r:ClearAllPoints()
r:SetPoint(...)
end
local function atlas(t, name, size)
return ForeverUI.SetAtlas(t, name, not size)
end
-- Camelot fonts missing in 3.3.5
local function font(name, path, size, outline, shadow, r, g, b)
local p = _G[name] or CreateFont(name)
p:SetFont(path, size, outline or "")
if shadow then
p:SetShadowOffset(1, -1)
p:SetShadowColor(0, 0, 0, 1)
else
p:SetShadowOffset(0, 0)
p:SetShadowColor(0, 0, 0, 0)
end
p:SetTextColor(r or 1, g or 1, b or 1)
return p
end
local FONTS = {
row = font("ForeverUIFontHighlightNoShadow12", FONT, 12), -- GameFontHighlight_NoShadow
category = font("ForeverUIFontGame15Shadow", FONT, 15, nil, true), -- Game15Font_Shadow
med2 = font("ForeverUIFontHighlightMed2", FONT, 14, nil, true), -- GameFontHighlightMed2
small2 = font("ForeverUIFontHighlightSmall2", FONT, 11), -- GameFontHighlightSmall2
rank = font("ForeverUIFontNumber12Outline", NUMBER_FONT, 12, "OUTLINE"), -- Number12FontOutline
}
local function truthy(v)
return v and v ~= 0 and true or false
end
-- ------------------------------------------------------------ profession
-- Current profession's PROFESSIONS entry, and its spell icon
local function profession()
local name = GetTradeSkillLine()
local icon = name and GetSpellTexture(name)
local key = icon and string.lower(string.match(icon, "([^" .. SEP .. SEP .. "/]+)$") or "")
return key and PROFESSIONS[key], icon
end
-- ------------------------------------------------------------ recipe list
-- Header background: common-button-list-collapseExpand, 18-wide ends and a middle
-- (28 texels of 64) laid tile by tile (see QuestLog.lua)
local HEADER_SLICE = { side = 18, image = 64 }
local function headerBackground(b, layer, mode, alpha)
local e = ForeverUI.AtlasEntry("common-button-list-collapseexpand")
if not e then return {} end
local du = (e[3] - e[2]) / HEADER_SLICE.image
local side = HEADER_SLICE.side
local middle = HEADER_SLICE.image - 2 * side
local pieces = {}
local function piece(u1, u2)
local t = b:CreateTexture(nil, layer)
t:SetTexture(e[1])
t:SetTexCoord(u1, u2, e[4], e[5])
if mode then t:SetBlendMode(mode) end
if alpha then t:SetAlpha(alpha) end
pieces[#pieces + 1] = t
return t
end
local left = piece(e[2], e[2] + side * du)
left:SetWidth(side)
left:SetPoint("TOPLEFT", b, "TOPLEFT", 0, 0)
left:SetPoint("BOTTOMLEFT", b, "BOTTOMLEFT", 0, 0)
local right = piece(e[3] - side * du, e[3])
right:SetWidth(side)
right:SetPoint("TOPRIGHT", b, "TOPRIGHT", 0, 0)
right:SetPoint("BOTTOMRIGHT", b, "BOTTOMRIGHT", 0, 0)
b.foreverTiles = b.foreverTiles or {}
b.foreverTiles[layer] = { left = left, du = du, e = e, middle = middle, side = side, pieces = {}, mode = mode, alpha = alpha }
return pieces
end
-- Middle tiles, to the row width
local function tileHeader(b, width)
for layer, T in pairs(b.foreverTiles or {}) do
for _, t in ipairs(T.pieces) do t:Hide() end
local rest, x, n = width - 2 * T.side, 0, 0
while rest > 0 do
n = n + 1
local t = T.pieces[n]
if not t then
t = b:CreateTexture(nil, layer)
t:SetTexture(T.e[1])
if T.mode then t:SetBlendMode(T.mode) end
if T.alpha then t:SetAlpha(T.alpha) end
T.pieces[n] = t
end
local l = math.min(T.middle, rest)
local u1 = T.e[2] + T.side * T.du
t:SetTexCoord(u1, u1 + l * T.du, T.e[4], T.e[5])
t:SetWidth(l)
place(t, "TOPLEFT", T.left, "TOPRIGHT", x, 0)
t:SetPoint("BOTTOMLEFT", T.left, "BOTTOMRIGHT", x, 0)
t:Show()
x = x + l
rest = rest - l
end
end
end
local function createCategory(n)
local h = M.skin
local C = N.category
local b = CreateFrame("Button", "ForeverUITradeSkillCategory" .. n, h.child)
b:SetHeight(C.h)
b:RegisterForClicks("LeftButtonUp")
headerBackground(b, "BACKGROUND")
headerBackground(b, "HIGHLIGHT", "ADD", 0.4)
local plus = CreateFrame("Frame", nil, b)
plus:SetWidth(C.button)
plus:SetHeight(C.button)
plus:SetPoint("RIGHT", b, "RIGHT", C.buttonX, 0)
plus.Icon = plus:CreateTexture(nil, "ARTWORK")
plus.Icon:SetPoint("CENTER", plus, "CENTER", 0, 0)
b.plus = plus
local text = b:CreateFontString(nil, "OVERLAY")
text:SetFontObject(FONTS.category)
text:SetJustifyH("LEFT")
text:SetPoint("LEFT", b, "LEFT", C.text, 0)
text:SetPoint("RIGHT", plus, "LEFT", -4, 0)
b.text = text
local n_ = NORMAL_FONT_COLOR
text:SetTextColor(n_.r, n_.g, n_.b)
b:SetScript("OnEnter", function(self) self.text:SetTextColor(1, 1, 1) end)
b:SetScript("OnLeave", function(self) self.text:SetTextColor(n_.r, n_.g, n_.b) end)
b:SetScript("OnMouseDown", function(self)
place(self.text, "LEFT", self, "LEFT", C.text + 1, -1)
self.text:SetPoint("RIGHT", self.plus, "LEFT", -4, 0)
place(self.plus.Icon, "CENTER", self.plus, "CENTER", 1, -1)
end)
b:SetScript("OnMouseUp", function(self)
place(self.text, "LEFT", self, "LEFT", C.text, 0)
self.text:SetPoint("RIGHT", self.plus, "LEFT", -4, 0)
place(self.plus.Icon, "CENTER", self.plus, "CENTER", 0, 0)
end)
b:SetScript("OnClick", function(self)
PlaySound("igMainMenuOptionCheckBoxOn")
if self.expanded then
CollapseTradeSkillSubClass(self:GetID())
else
ExpandTradeSkillSubClass(self:GetID())
end
end)
return b
end
local function createRecipe(n)
local h = M.skin
local R = N.recipe
local b = CreateFrame("Button", "ForeverUITradeSkillRecipe" .. n, h.child)
b:SetHeight(R.h)
b:RegisterForClicks("LeftButtonUp", "RightButtonUp")
local progress = CreateFrame("Frame", nil, b)
progress:SetWidth(R.progress[1])
progress:SetHeight(R.progress[2])
progress:SetPoint("LEFT", b, "LEFT", R.progress[3], 0)
progress:EnableMouse(true)
progress.Icon = progress:CreateTexture(nil, "OVERLAY")
progress.Icon:SetPoint("RIGHT", progress, "RIGHT", 0, -1)
b.progress = progress
local name = b:CreateFontString(nil, "OVERLAY")
name:SetFontObject(FONTS.row)
name:SetJustifyH("LEFT")
name:SetHeight(12)
name:SetPoint("LEFT", progress, "RIGHT", R.nameX, 0)
b.name = name
local count = b:CreateFontString(nil, "OVERLAY")
count:SetFontObject(FONTS.row)
count:SetJustifyH("LEFT")
count:SetHeight(12)
count:SetPoint("LEFT", name, "RIGHT", 0, 0)
b.count = count
-- Selected (above the name) and hover (HIGHLIGHT layer)
local choice = CreateFrame("Frame", nil, b)
choice:SetAllPoints(b)
choice:SetFrameLevel(b:GetFrameLevel() + 1)
choice:EnableMouse(false)
local selectedItem = choice:CreateTexture(nil, "OVERLAY")
atlas(selectedItem, "professions_recipe_active", true)
selectedItem:SetPoint("CENTER", b, "CENTER", 0, -1)
selectedItem:Hide()
b.selectedItem = selectedItem
local hover = b:CreateTexture(nil, "HIGHLIGHT")
atlas(hover, "professions_recipe_hover", true)
hover:SetPoint("CENTER", b, "CENTER", 0, -1)
hover:SetAlpha(0.5)
b.hover = hover
local function colors(self, white)
local c = white and { 1, 1, 1 } or COLORS.recipe
self.name:SetTextColor(c[1], c[2], c[3])
self.count:SetTextColor(c[1], c[2], c[3])
end
b.colors = colors
local function onEnter(self)
colors(b, true)
if b.isTruncated then
-- 3.3.5 takes only a frame as owner: the row, with the tooltip where ANCHOR_RIGHT
-- puts it on the name
GameTooltip:SetOwner(b, "ANCHOR_NONE")
GameTooltip:ClearAllPoints()
GameTooltip:SetPoint("BOTTOMLEFT", b.name, "TOPRIGHT")
GameTooltip:AddLine(b.fullName, 1, 1, 1, false)
GameTooltip:Show()
end
end
local function exit()
colors(b, false)
GameTooltip:Hide()
end
b:SetScript("OnEnter", onEnter)
b:SetScript("OnLeave", exit)
progress:SetScript("OnLeave", exit)
progress:SetScript("OnMouseUp", function(_, button) b:Click(button) end)
b:SetScript("OnClick", function(self, button)
if button ~= "LeftButton" then return end
if IsModifiedClick() then
HandleModifiedItemClick(GetTradeSkillRecipeLink(self:GetID()))
return
end
PlaySound("igMainMenuOptionCheckBoxOn")
TradeSkillFrame_SetSelection(self:GetID())
TradeSkillFrame_Update()
end)
return b
end
-- List elements in order: the 3.3.5 flat list made into a tree (a header, its recipes);
-- the "Has skill up" filter, missing in 3.3.5, drops trivial recipes
function M.Elements()
local elements = {}
local category
local queued = {}
local function close()
if category then
local recipes = category.recipes
if category.expanded and #recipes > 0 then
elements[#elements + 1] = { space = N.tree.hovered }
for _, r in ipairs(recipes) do elements[#elements + 1] = r end
elements[#elements + 1] = { space = N.tree.below }
end
end
end
for i = 1, GetNumTradeSkills() or 0 do
local name, kind, numCraftable, expanded = GetTradeSkillInfo(i)
if name then
if kind == "header" then
close()
category = { index = i, name = name, expanded = expanded and true or false, recipes = {}, category = true }
queued[#queued + 1] = category
elements[#elements + 1] = category
elseif not (M.skillUpsOnly and kind == "trivial") then
local r = { index = i, name = name, kind = kind, numCraftable = numCraftable or 0, indent = category and true or false }
if category then
table.insert(category.recipes, r)
else
elements[#elements + 1] = r
end
end
end
end
close()
-- A category emptied by the filter disappears (camelot filters before building the tree);
-- a collapsed category keeps its header
if M.skillUpsOnly then
local keep = {}
for _, e in ipairs(elements) do
if not (e.category and e.expanded and #e.recipes == 0) then keep[#keep + 1] = e end
end
elements = keep
end
return elements
end
local function placeBar(hasBar)
local h = M.skin
local Z = N.zone
h.zone:ClearAllPoints()
h.zone:SetPoint("TOPLEFT", h.list, "TOPLEFT", Z[1], Z[2])
-- Without a bar: the same margin on the right as on the left
h.zone:SetPoint("BOTTOMRIGHT", h.list, "BOTTOMRIGHT", hasBar and Z[3] or -Z[1], Z[4])
end
function M.UpdateList()
local h = M.skin
if not h then return end
local A = N.tree
local elements = M.Elements()
-- Content height decides whether the bar is needed
local height = A.top + A.down
for i, e in ipairs(elements) do
height = height + (e.category and N.category.h or e.space or N.recipe.h) + (i > 1 and A.space or 0)
end
local view = h.zone:GetHeight()
if not view or view <= 0 then view = N.window[2] + N.list[2] - N.list.down + N.zone[2] - N.zone[4] end
local hasBar = height > view
if hasBar ~= h.hasBar then
h.hasBar = hasBar
placeBar(hasBar)
end
local width = N.list[3] - N.zone[1] + (hasBar and N.zone[3] or -N.zone[1])
h.child:SetWidth(width)
h.child:SetHeight(math.max(height, 1))
-- Scroll position first: only the elements in view get a row. A profession has hundreds of
-- recipes, and every shown row is laid out again each frame the window moves.
local total = math.ceil(height / N.step)
local visibleCount = math.floor(view / N.step)
h.offset = math.max(0, math.min(h.offset or 0, total - visibleCount))
local scroll = math.min(h.offset * N.step, math.max(0, height - view))
local selected = GetTradeSkillSelectionIndex()
local y, nc, nr = A.top, 0, 0
for i, e in ipairs(elements) do
if i > 1 then y = y + A.space end
local size = e.category and N.category.h or e.space or N.recipe.h
local inView = y + size > scroll and y < scroll + view
if e.category and inView then
nc = nc + 1
local b = h.categories[nc] or createCategory(nc)
h.categories[nc] = b
b:SetID(e.index)
b.expanded = e.expanded
b.text:SetText(e.name)
local ic = e.expanded and "common-button-list-minus" or "common-button-list-plus"
atlas(b.plus.Icon, ic, true)
local l = width - A.right
b:SetWidth(l)
tileHeader(b, l)
place(b, "TOPLEFT", h.child, "TOPLEFT", 0, -y)
b:Show()
elseif not e.category and not e.space and inView then
nr = nr + 1
local b = h.recipes[nr] or createRecipe(nr)
h.recipes[nr] = b
b:SetID(e.index)
local indent = e.indent and A.indent or 0
local l = width - A.right - indent
b:SetWidth(l)
place(b, "TOPLEFT", h.child, "TOPLEFT", indent, -y)
M.FillRecipe(b, e, l, selected == e.index)
b:Show()
end
y = y + size
end
for i = nc + 1, #h.categories do h.categories[i]:Hide() end
for i = nr + 1, #h.recipes do h.recipes[i]:Hide() end
Tpl.SetShown(h.none, #elements == 0)
h.bar:Configure(total, visibleCount, h.offset)
h.zone:SetVerticalScroll(scroll)
end
-- Name width. A row is reused for several recipes: its name keeps the previous width, and the
-- client measures the text within that width (GetStringWidth returns the shown width), so a
-- longer name would be cut. We measure on a separate, unbounded, invisible font string.
local function nameWidth(text)
local m = M.measure
if not m then
m = M.skin.child:CreateFontString(nil, "OVERLAY")
m:SetFontObject(FONTS.row)
m:SetPoint("TOPLEFT", M.skin.child, "TOPLEFT", 0, 0)
m:SetAlpha(0)
M.measure = m
end
m:SetText(text)
return m:GetStringWidth()
end
function M.FillRecipe(b, e, width, selectedItem)
local R = N.recipe
b.fullName = e.name
b.name:SetText(e.name)
b.colors(b, false)
local P = PROGRESS[e.kind]
if P then
atlas(b.progress.Icon, P[1], true)
place(b.progress, "LEFT", b, "LEFT", R.progress[3], P[2])
b.progress:Show()
b.progress:SetScript("OnEnter", function()
b.colors(b, true)
GameTooltip:SetOwner(b.progress, "ANCHOR_RIGHT")
local t = P[3]
local n_ = NORMAL_FONT_COLOR
-- 3.3.5 does not give the skill points gained: 1 is shown
GameTooltip:AddLine(string.format(t, 1), n_.r, n_.g, n_.b, true)
GameTooltip:Show()
end)
else
b.progress:Hide()
end
local hasCount = (e.numCraftable or 0) > 0
if hasCount then
b.count:SetFormattedText(" [%d] ", e.numCraftable)
b.count:Show()
else
b.count:SetText("")
b.count:Hide()
end
-- Room for the name: the row minus the count, the margin and the progress frame
local position = width - ((hasCount and b.count:GetStringWidth() or 0) + R.margin + R.progress[1])
local full = nameWidth(e.name)
b.name:SetWidth(math.max(1, math.min(position, full)))
b.isTruncated = full > position
Tpl.SetShown(b.selectedItem, selectedItem)
Tpl.SetShown(b.hover, not selectedItem)
end
-- ------------------------------------------------------------ rank bar
function M.UpdateRank()
local h = M.skin
local r = h.rank
local name, rank, maxValue, bonus = GetTradeSkillLine()
if not name or not maxValue or maxValue <= 0 then
r:Hide()
return
end
r:Show()
if bonus and bonus > 0 then
r.text:SetFormattedText(L.TRADESKILL_NAME_RANK_MODIFIER, name, rank, bonus, maxValue)
else
r.text:SetFormattedText(L.TRADESKILL_NAME_RANK, name, rank, maxValue)
end
local M_ = profession()
local strip = M_ and ("skillbar_fill_flipbook_" .. M_.rank) or "skillbar_fill_flipbook_defaultblue"
local e = ForeverUI.AtlasEntry(strip) or ForeverUI.AtlasEntry("skillbar_fill_flipbook_defaultblue")
local flare = M_ and ForeverUI.AtlasEntry("skillbar_flare_" .. M_.rank)
local R = N.rank
local part = math.min(rank / maxValue, 1)
-- Mask: from 1 after the fill start, over 453 x the ratio; the fill (441) shows only below it.
-- Camelot's fill is a 60-frame flipbook 1712 wide that 3.3.5 cannot show: the first frame is used.
local found = math.min(R.filled[1] - R.mask, R[3] * part)
if e and found >= 1 then
r.filled:SetTexture(e[1])
local du = (e[3] - e[2]) / R.filled[1]
r.filled:SetTexCoord(e[2] + du * R.mask, e[2] + du * (R.mask + found), e[4], e[5])
r.filled:SetWidth(found)
r.filled:Show()
else
r.filled:Hide()
end
-- Camelot masks the flare too (MaskedTexture Flare): it shows only over the mask width, from the
-- fill start. 3.3.5 has no mask, so we keep the flare's right part, cut to the visible width;
-- otherwise it overhangs the bar on the left at low rank.
local mask = R[3] * part
if flare and mask >= 1 then
local l = math.min(R.flare[1], mask)
local du = (flare[3] - flare[2]) / R.flare[1]
r.flare:SetTexture(flare[1])
r.flare:SetTexCoord(flare[3] - du * l, flare[3], flare[4], flare[5])
r.flare:SetWidth(l)
place(r.flare, "RIGHT", r, "TOPLEFT", R.filled[3] + R.mask + mask, R.filled[4] - R.filled[2] / 2)
r.flare:SetAlpha(rank >= maxValue and 0 or 1)
r.flare:Show()
else
r.flare:Hide()
end
end
-- ------------------------------------------------------------ schematic
-- Sizes fs to its text at maxValue wide: on several lines (multiline), otherwise one line as
-- wide as the text (camelot SetTextToFit)
local function fitText(fs, text, maxValue, multiline)
fs:SetHeight(200)
fs:SetText(text)
fs:SetWidth(maxValue)
if not multiline then fs:SetWidth(fs:GetStringWidth()) end
fs:SetHeight(fs:GetStringHeight())
end
-- Quality and name of an item link; 0 and nil for anything else
local function quality(link)
if link and string.find(link, "|Hitem:", 1, true) then
local name, _, q = GetItemInfo(link)
return q or 0, name
end
return 0, nil
end
local function createReagent(n)
local h = M.skin
local Rg = N.reagents
local s = CreateFrame("Frame", nil, h.reagents)
s:SetWidth(Rg.slot[1])
s:SetHeight(Rg.slot[2])
local b = CreateFrame("Button", "ForeverUITradeSkillReagent" .. n, s)
b:SetWidth(Rg.button)
b:SetHeight(Rg.button)
b:SetPoint("LEFT", s, "LEFT", 0, 0)
local background = b:CreateTexture(nil, "BACKGROUND")
atlas(background, "professions-slot-bg", true)
background:SetAllPoints(b)
local icon = b:CreateTexture(nil, "ARTWORK")
icon:SetAllPoints(b)
local outline = b:CreateTexture(nil, "OVERLAY")
outline:SetPoint("TOPLEFT", icon, "TOPLEFT", -5, 4)
outline:SetPoint("BOTTOMRIGHT", icon, "BOTTOMRIGHT", 4, -5)
-- UI-Quickslot2 (SetModifyingRequired(false)), BORDER layer, on the button
b:SetNormalTexture(BUTTONS .. "UI-Quickslot2")
local frame = b:GetNormalTexture()
frame:SetDrawLayer("BORDER")
frame:ClearAllPoints()
frame:SetAllPoints(b)
b.icon, b.outline = icon, outline
local name = s:CreateFontString(nil, "BORDER")
name:SetFontObject(FONTS.row)
name:SetJustifyH("LEFT")
name:SetWidth(Rg.nameSize[1])
name:SetHeight(Rg.nameSize[2])
name:SetPoint("LEFT", s, "LEFT", Rg.nameX, 0)
s.button, s.name = b, name
b:SetScript("OnEnter", function(self)
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:SetTradeSkillItem(M.selected, self:GetID())
GameTooltip:Show()
end)
b:SetScript("OnLeave", function() GameTooltip:Hide() end)
b:SetScript("OnClick", function(self)
if IsModifiedClick() then
HandleModifiedItemClick(GetTradeSkillReagentItemLink(M.selected, self:GetID()))
end
end)
return s
end
-- After TradeSkillFrame_SetSelection: the selected recipe's schematic
function M.UpdateCard()
local h = M.skin
if not h then return end
local id = GetTradeSkillSelectionIndex()
local name, kind, numCraftable, _, verb = GetTradeSkillInfo(id)
local F = h.card
-- Profession card
local M_ = profession()
local map = (M_ and M_.map and ForeverUI.AtlasEntry(M_.map)) and M_.map or "professions-recipe-background"
atlas(F.map, map, true)
if not name or kind == "header" then
M.selected = nil
F.content:Hide()
M.UpdateButtons(nil)
return
end
M.selected = id
F.content:Show()
-- Result
local link = GetTradeSkillItemLink(id)
local q, itemName = quality(link)
SetPortraitToTexture(F.icon, GetTradeSkillIcon(id))
local outline = RESULT_OUTLINE[q]
if outline then
atlas(F.outline, "auctionhouse-itemicon-border-" .. outline)
F.outline:Show()
else
F.outline:Hide()
end
local minValue, maxValue = GetTradeSkillNumMade(id)
if maxValue and maxValue > 1 then
if minValue == maxValue then F.count:SetText(minValue) else F.count:SetFormattedText("%d-%d", minValue, maxValue) end
if F.count:GetWidth() > 39 then F.count:SetFormattedText("~%d", math.floor((minValue + maxValue) / 2)) end
F.shadow:Show()
else
F.count:SetText("")
F.shadow:Hide()
end
local nameText
if itemName then
local c = ITEM_QUALITY_COLORS[q]
local hex = c and (c.hex or string.format("|cff%02x%02x%02x", math.floor(c.r * 255 + 0.5), math.floor(c.g * 255 + 0.5), math.floor(c.b * 255 + 0.5)))
nameText = hex and (hex .. itemName .. "|r") or itemName
else
nameText = NORMAL_FONT_COLOR_CODE .. name .. "|r"
end
fitText(F.name, nameText, N.textWidth, true)
-- Tools
local tools = BuildColoredListString(GetTradeSkillTools(id))
if tools then
fitText(F.tools, NORMAL_FONT_COLOR_CODE .. REQUIRES_LABEL .. "|r " .. tools, N.textWidth, true)
F.tools:Show()
else
F.tools:SetText("")
F.tools:Hide()
end
-- Cooldown, then description (vertical layout)
local O = N.organizer
-- Wrapped, the name and tools may reach below the place of what follows (12 under the
-- result): it then starts 4 under the text. Heights from the result's centre, the name 17
-- above it.
local textBottom = N.name[2] - F.name:GetHeight() / 2
if tools then textBottom = textBottom + N.tools[2] - F.tools:GetHeight() end
local shift = math.min(0, textBottom - O[3] - (O[2] - N.result[3] / 2))
local cooldown = GetTradeSkillCooldown(id)
local previous
if cooldown then
F.cooldown:SetText(COOLDOWN_REMAINING .. " " .. SecondsToTime(cooldown))
place(F.cooldown, "TOPLEFT", F.result, "BOTTOMLEFT", O[1], O[2] + shift)
F.cooldown:Show()
previous = F.cooldown
else
F.cooldown:Hide()
end
local description = GetTradeSkillDescription(id)
F.description:SetWidth(N.description)
F.description:SetHeight(600)
F.description:SetText(description or "")
F.description:SetHeight((description and description ~= "") and (F.description:GetStringHeight() + 1) or 1)
if previous then
place(F.description, "TOPLEFT", previous, "BOTTOMLEFT", 0, -(O[3] + 5))
else
place(F.description, "TOPLEFT", F.result, "BOTTOMLEFT", O[1], O[2] + shift)
end
-- Reagents
local Rg = N.reagents
local n = GetTradeSkillNumReagents(id) or 0
Tpl.SetShown(h.reagents, n > 0)
place(h.reagents, "TOPLEFT", F.description, "BOTTOMLEFT", Rg[1], Rg[2])
local shownItems, possible = 0, true
for i = 1, n do
local rName, rIcon, required, owned = GetTradeSkillReagentInfo(id, i)
local enough = (owned or 0) >= (required or 0)
if not enough then possible = false end
if rName and rIcon then
shownItems = shownItems + 1
local s = h.slots[shownItems] or createReagent(shownItems)
h.slots[shownItems] = s
s.button:SetID(i)
s.button.icon:SetTexture(rIcon)
local rq = quality(GetTradeSkillReagentItemLink(id, i))
local ct = REAGENT_OUTLINE[rq]
if ct then
atlas(s.button.outline, ct, true)
s.button.outline:Show()
else
s.button.outline:Hide()
end
local c = enough and { 1, 1, 1 } or COLORS.missingReagent
s.name:SetFormattedText(L.TRADESKILL_REAGENT_COUNT .. " %s", tostring(owned or 0), required or 0, rName)
s.name:SetTextColor(c[1], c[2], c[3])
local column = math.floor((shownItems - 1) / Rg.column)
local rowLine = (shownItems - 1) % Rg.column
place(s, "TOPLEFT", h.reagents, "TOPLEFT", Rg.start[1] + column * (Rg.slot[1] + Rg.gap[1]),
Rg.start[2] - rowLine * (Rg.slot[2] + Rg.gap[2]))
s:Show()
end
end
for i = shownItems + 1, #h.slots do h.slots[i]:Hide() end
M.UpdateButtons(id, name, verb, numCraftable, possible)
end
-- ------------------------------------------------------------ buttons
-- Sets the button text and widens it to fit (never narrower)
local function fitButtonText(b, text)
b:SetText(text)
local fs = b:GetFontString()
local width = (fs and fs:GetStringWidth() or 0) + N.create.margin
b:SetWidth(math.max(b:GetWidth(), width))
end
-- possible: every reagent is there (UpdateCard's reagent loop)
function M.UpdateButtons(id, name, verb, numCraftable, possible)
local h = M.skin
local B = h.buttons
if not id or truthy(IsTradeSkillLinked()) then
B.create:Hide()
B.all:Hide()
B.counter:Hide()
return
end
-- Create is enabled when the reagents are there (the 3.3.5 rule); Create All and the counter
-- do not exist for a spell with a verb (enchanting), as in 3.3.5
local maxValue = math.abs(numCraftable or 0)
B.create:Show()
fitButtonText(B.create, verb or CREATE)
if possible then B.create:Enable() else B.create:Disable() end
local multiple = not verb
Tpl.SetShown(B.all, multiple)
Tpl.SetShown(B.counter, multiple)
if multiple then
fitButtonText(B.all, string.format(L.TRADESKILL_CREATE_ALL_FORMAT, CREATE_ALL, maxValue))
if possible and maxValue > 0 then B.all:Enable() else B.all:Disable() end
B.counter.maxValue = maxValue
if maxValue > 0 then
B.counter:SetNumber(math.max(1, math.min(GetTradeskillRepeatCount() or 1, maxValue)))
B.counter:EnableMouse(true)
B.counter.active = true
else
B.counter:SetNumber(0)
B.counter:EnableMouse(false)
B.counter.active = false
end
M.UpdateArrows()
end
end
function M.UpdateArrows()
local c = M.skin.buttons.counter
local v = c:GetNumber()
local function state(b, yes) if yes then b:Enable() else b:Disable() end end
state(c.plus, c.active and v < (c.maxValue or 0))
state(c.minus, c.active and v > 1)
end
local function changeCounter(step)
local c = M.skin.buttons.counter
if not c.active then return end
c:SetNumber(math.max(1, math.min((c:GetNumber() or 1) + step, c.maxValue or 1)))
M.UpdateArrows()
end
local function createButtons(page)
local h = M.skin
local C, T, K = N.create, N.createAll, N.counter
local fonts = { GameFontNormal, GameFontHighlight, GameFontDisable }
local function button(name)
local b = CreateFrame("Button", name, page)
b:SetWidth(C[3])
b:SetHeight(C[4])
local fs = b:CreateFontString(nil, "OVERLAY", "GameFontNormal")
b:SetFontString(fs)
Tpl.ThreeSliceButton(b, "128-redbutton", fonts)
return b
end
local create = button("ForeverUITradeSkillCreateButton")
create:SetPoint("BOTTOMRIGHT", page, "BOTTOMRIGHT", C[1], C[2])
create:SetScript("OnClick", function()
if not M.selected then return end
-- A spell with a verb (enchanting) has no counter, and the hidden one keeps the previous
-- recipe's number (0 when it could not be made): one cast, as 3.3.5's hidden input box
-- reset to GetTradeskillRepeatCount gives
local c = h.buttons.counter
DoTradeSkill(M.selected, c:IsShown() and c:GetNumber() or 1)
end)
local all = button("ForeverUITradeSkillCreateAllButton")
all:SetPoint("BOTTOMLEFT", page, "BOTTOMRIGHT", T[1], T[2])
all:SetScript("OnClick", function()
local c = h.buttons.counter
if M.selected and (c.maxValue or 0) > 0 then
c:SetNumber(c.maxValue)
DoTradeSkill(M.selected, c.maxValue)
end
end)
-- Counter (NumericInputSpinnerTemplate < InputBoxTemplate)
local c = CreateFrame("EditBox", "ForeverUITradeSkillInputBox", page)
c:SetWidth(K[3])
c:SetHeight(K[4])
c:SetPoint("BOTTOMLEFT", page, "BOTTOMRIGHT", K[1], K[2])
c:SetAutoFocus(false)
c:SetNumeric(true)
c:SetMaxLetters(3)
c:SetFontObject(ChatFontNormal)
c:SetJustifyH("CENTER")
local edge = "Interface" .. SEP .. "Common" .. SEP .. "Common-Input-Border"
local g = c:CreateTexture(nil, "BACKGROUND")
g:SetTexture(edge) g:SetTexCoord(0, 0.0625, 0, 0.625)
g:SetWidth(8) g:SetHeight(20)
g:SetPoint("LEFT", c, "LEFT", -5, 0)
local d = c:CreateTexture(nil, "BACKGROUND")
d:SetTexture(edge) d:SetTexCoord(0.9375, 1, 0, 0.625)
d:SetWidth(8) d:SetHeight(20)
d:SetPoint("RIGHT", c, "RIGHT", 0, 0)
local m = c:CreateTexture(nil, "BACKGROUND")
m:SetTexture(edge) m:SetTexCoord(0.0625, 0.9375, 0, 0.625)
m:SetHeight(20)
m:SetPoint("LEFT", g, "RIGHT", 0, 0)
m:SetPoint("RIGHT", d, "LEFT", 0, 0)
c:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
c:SetScript("OnEnterPressed", function(self) self:ClearFocus() end)
c:SetScript("OnEditFocusLost", function() changeCounter(0) end)
c:SetScript("OnTextChanged", function() M.UpdateArrows() end)
local function arrow(name, file)
local f = CreateFrame("Button", name, c)
f:SetWidth(K.arrow[1])
f:SetHeight(K.arrow[2])
f:SetNormalTexture(BUTTONS .. file .. "-Up")
f:SetPushedTexture(BUTTONS .. file .. "-Down")
f:SetDisabledTexture(BUTTONS .. file .. "-Disabled")
f:SetHighlightTexture(BUTTONS .. "UI-Common-MouseHilight")
f:GetHighlightTexture():SetBlendMode("ADD")
return f
end
local plus = arrow("ForeverUITradeSkillIncrementButton", "UI-SpellbookIcon-NextPage")
plus:SetPoint("LEFT", c, "RIGHT", 0, 0)
plus:SetScript("OnClick", function()
PlaySound("igMainMenuOptionCheckBoxOn")
changeCounter(1)
end)
local minus = arrow("ForeverUITradeSkillDecrementButton", "UI-SpellbookIcon-PrevPage")
minus:SetPoint("RIGHT", c, "LEFT", K.minusGap, 0)
minus:SetScript("OnClick", function()
PlaySound("igMainMenuOptionCheckBoxOn")
changeCounter(-1)
end)
c.plus, c.minus = plus, minus
local wheel = CreateFrame("Frame", nil, c)
wheel:SetPoint("TOPLEFT", minus, "TOPLEFT")
wheel:SetPoint("BOTTOMRIGHT", plus, "BOTTOMRIGHT")
wheel:EnableMouseWheel(true)
wheel:SetScript("OnMouseWheel", function(_, direction)
changeCounter((IsShiftKeyDown() and 10 or 1) * (direction > 0 and 1 or -1))
c:ClearFocus()
end)
h.buttons = { create = create, all = all, counter = c }
end
-- ------------------------------------------------------------ filter
-- 3.3.5 calls it with (frame, level) (UIDropDownMenu.lua);
-- ToggleDropDownMenu also sets UIDROPDOWNMENU_MENU_LEVEL
local function initFilter(_, level)
level = level or UIDROPDOWNMENU_MENU_LEVEL or 1
if level == 1 then
local info = UIDropDownMenu_CreateInfo()
info.text = L.TRADESKILL_FILTER_SKILL_UP
info.checked = M.skillUpsOnly
info.keepShownOnClick = 1
info.func = function()
M.skillUpsOnly = not M.skillUpsOnly
M.UpdateList()
end
UIDropDownMenu_AddButton(info, level)
info = UIDropDownMenu_CreateInfo()
info.text = CRAFT_IS_MAKEABLE
info.checked = truthy(TradeSkillFrameAvailableFilterCheckButton:GetChecked())
info.keepShownOnClick = 1
info.func = function()
local yes = not truthy(TradeSkillFrameAvailableFilterCheckButton:GetChecked())
TradeSkillFrameAvailableFilterCheckButton:SetChecked(yes)
TradeSkillOnlyShowMakeable(yes)
end
UIDropDownMenu_AddButton(info, level)
info = UIDropDownMenu_CreateInfo()
info.text = L.TRADESKILL_FILTER_SLOTS
info.hasArrow = 1
info.notCheckable = 1
info.value = "slots"
UIDropDownMenu_AddButton(info, level)
elseif UIDROPDOWNMENU_MENU_VALUE == "slots" then
local info = UIDropDownMenu_CreateInfo()
info.text = L.TRADESKILL_CHECK_ALL
info.notCheckable = 1
info.func = function()
SetTradeSkillInvSlotFilter(0, 1, 1)
CloseDropDownMenus()
end
UIDropDownMenu_AddButton(info, level)
info = UIDropDownMenu_CreateInfo()
info.text = L.TRADESKILL_UNCHECK_ALL
info.notCheckable = 1
info.func = function()
for i = 1, select("#", GetTradeSkillInvSlots()) do SetTradeSkillInvSlotFilter(i, 0, 0) end
CloseDropDownMenus()
end
UIDropDownMenu_AddButton(info, level)
local all = truthy(GetTradeSkillInvSlotFilter(0))
for i = 1, select("#", GetTradeSkillInvSlots()) do
info = UIDropDownMenu_CreateInfo()
info.text = select(i, GetTradeSkillInvSlots())
info.checked = all or truthy(GetTradeSkillInvSlotFilter(i))
info.keepShownOnClick = 1
local index = i
info.func = function(self)
local checkMark = truthy(GetTradeSkillInvSlotFilter(0)) or truthy(GetTradeSkillInvSlotFilter(index))
SetTradeSkillInvSlotFilter(index, checkMark and 0 or 1, 0)
end
UIDropDownMenu_AddButton(info, level)
end
end
end
-- ------------------------------------------------------------ window
-- The client's frame: invisible and mouse-less; it stays the host
local function suppress(f)
for _, r in ipairs({ f:GetRegions() }) do
if r:GetObjectType() == "Texture" then r:SetAlpha(0) end
end
TradeSkillFrameTitleText:SetAlpha(0)
TradeSkillFrameDummyString:SetAlpha(0)
for i = 1, TRADE_SKILLS_DISPLAYED or 8 do
local b = _G["TradeSkillSkill" .. i]
if b then b:SetAlpha(0) b:EnableMouse(false) end
end
for _, name in ipairs({ "TradeSkillListScrollFrame", "TradeSkillDetailScrollFrame", "TradeSkillHighlightFrame" }) do
local fx = _G[name]
if fx then
fx:SetAlpha(0)
fx:EnableMouse(false)
if fx.EnableMouseWheel then fx:EnableMouseWheel(false) end
for _, s in ipairs({ "ScrollBar", "ScrollBarScrollUpButton", "ScrollBarScrollDownButton" }) do
local c = _G[name .. s]
if c then c:EnableMouse(false) end
end
end
end
for i = 1, MAX_TRADE_SKILL_REAGENTS or 8 do
local r = _G["TradeSkillReagent" .. i]
if r then r:EnableMouse(false) end
end
if TradeSkillSkillIcon then TradeSkillSkillIcon:EnableMouse(false) end
for _, c in ipairs({ TradeSkillLinkButton, TradeSkillFrameAvailableFilterCheckButton, TradeSkillRankFrame,
TradeSkillFrameEditBox, TradeSkillExpandButtonFrame, TradeSkillInvSlotDropDown, TradeSkillSubClassDropDown,
TradeSkillCreateButton, TradeSkillCreateAllButton, TradeSkillDecrementButton, TradeSkillInputBox,
TradeSkillIncrementButton, TradeSkillCancelButton }) do
if c then ForeverUI.Suppress(c) end
end
end
-- After TradeSkillFrame_Update: title, portrait, rank, list. Below 75 skill (unless linked),
-- TradeSkillFrame_Update clears the name filter on each update, so the search box is hidden
-- as in 3.3.5 (forcing it would update forever).
function M.Update()
local f = TradeSkillFrame
local h = f and f.foreverSkin
if not h then return end
local name = GetTradeSkillLine()
local linked, player = IsTradeSkillLinked()
if truthy(linked) and player then
h.title:SetText(string.format("%s %s[%s]|r", string.format(TRADE_SKILL_TITLE, name or ""), HIGHLIGHT_FONT_COLOR_CODE, player))
else
h.title:SetText(string.format(TRADE_SKILL_TITLE, name or ""))
end
local _, icon = profession()
if icon then
SetPortraitToTexture(h.portrait, icon)
else
SetPortraitTexture(h.portrait, "player")
end
M.UpdateRank()
Tpl.SetShown(h.link, not truthy(linked) and GetTradeSkillListLink() ~= nil)
local _, rank = GetTradeSkillLine()
local search = truthy(linked) or (rank or 0) >= 75
Tpl.SetShown(h.search, search)
if not search and h.search:GetText() ~= "" then h.search:SetText("") end
M.UpdateList()
M.UpdateCard()
end
-- While a profession's items load, the client sends TRADE_SKILL_UPDATE once per item received,
-- often many in one frame, and each one runs TradeSkillFrame_SetSelection and
-- TradeSkillFrame_Update. The first call of a frame refreshes the page at once; the calls after
-- it in that frame give a single refresh on the next frame.
local refresher = CreateFrame("Frame")
M.refresher = refresher
local function onNextFrame(self)
self:SetScript("OnUpdate", nil)
M.busy = nil
if M.pending then
M.pending = nil
M.Refresh()
end
end
function M.Refresh()
if M.busy then
M.pending = true
return
end
M.busy = true
refresher:SetScript("OnUpdate", onNextFrame)
M.Update()
end
function M.Skin()
local f = TradeSkillFrame
if not f or f.foreverSkin then return end
suppress(f)
f:SetWidth(N.window[1])
f:SetHeight(N.window[2])
f:SetHitRectInsets(0, 0, 0, 0)
local skin = Tpl.PortraitWindow(f, {
portraitSide = N.portrait.side, portraitX = N.portrait.x, portraitY = N.portrait.y,
})
f.foreverSkin = skin
M.skin = skin
-- OverrideArt: Profession-Background-Overview instead of the rock, no stripes
if skin.rock.SetHorizTile then
skin.rock:SetHorizTile(false)
skin.rock:SetVertTile(false)
end
atlas(skin.rock, "profession-background-overview", true)
skin.stripes:Hide()
local base = f:GetFrameLevel()
local NV = N.levels
-- Crafting page
local page = CreateFrame("Frame", "ForeverUITradeSkillPage", f)
page:SetAllPoints(f)
page:SetFrameLevel(base + NV.page)
skin.page = page
local pageBackground = page:CreateTexture(nil, "BACKGROUND")
atlas(pageBackground, "profession-background-template2", true)
pageBackground:SetPoint("TOPLEFT", f, "TOPLEFT", N.page[1], N.page[2])
skin.pageBackground = pageBackground
-- Recipe list
local Li = N.list
local list = CreateFrame("Frame", "ForeverUITradeSkillRecipeList", page)
list:SetWidth(Li[3])
list:SetPoint("TOPLEFT", page, "TOPLEFT", Li[1], Li[2])
list:SetPoint("BOTTOMLEFT", page, "BOTTOMLEFT", Li[1], Li.down)
list:SetFrameLevel(base + NV.list)
skin.list = list
local listBackground = list:CreateTexture(nil, "BACKGROUND")
atlas(listBackground, "professions-background-summarylist", true)
listBackground:SetAllPoints(list)
local none = list:CreateFontString(nil, "ARTWORK", "GameFontNormal")
none:SetWidth(N.none[3])
none:SetPoint("TOP", list, "TOP", N.none[1], N.none[2])
none:SetText(L.TRADESKILL_NO_RESULTS)
none:Hide()
skin.none = none
-- Filter
local Fi = N.filter
local dd = CreateFrame("Frame", "ForeverUITradeSkillFilter", list, "UIDropDownMenuTemplate")
UIDropDownMenu_Initialize(dd, initFilter)
UIDropDownMenu_SetText(dd, FILTER)
Tpl.FilterMenu(dd, Fi[3])
-- The menu list is at least the button width, wider if its entries need it
-- (camelot SetMinimumWidth)
dd.foreverFloor = true
place(dd, "TOPRIGHT", list, "TOPRIGHT", Fi[1], Fi[2])
skin.filter = dd
-- Search (SearchBoxTemplate, as in the quest log)
local Sr = N.search
local r = CreateFrame("EditBox", "ForeverUITradeSkillSearchBox", list)
r:SetHeight(Sr[3])
r:SetPoint("TOPLEFT", list, "TOPLEFT", Sr[1], Sr[2])
r:SetPoint("RIGHT", dd, "LEFT", Sr.gap, 0)
r:SetAutoFocus(false)
r:SetMaxLetters(60)
r:SetFontObject("GameFontHighlightSmall")
r:SetTextInsets(16, 20, 0, 0)
local function edge(prefix)
local g = r:CreateTexture(nil, "BACKGROUND")
atlas(g, prefix .. "-left", true)
g:SetWidth(8) g:SetHeight(20)
g:SetPoint("LEFT", r, "LEFT", -5, 0)
local d = r:CreateTexture(nil, "BACKGROUND")
atlas(d, prefix .. "-right", true)
d:SetWidth(8) d:SetHeight(20)
d:SetPoint("RIGHT", r, "RIGHT", 0, 0)
local m = r:CreateTexture(nil, "BACKGROUND")
atlas(m, prefix .. "-middle", true)
m:SetPoint("TOPLEFT", g, "TOPRIGHT")
m:SetPoint("BOTTOMRIGHT", d, "BOTTOMLEFT")
end
edge("common-search-border")
local magnifier = r:CreateTexture(nil, "OVERLAY")
atlas(magnifier, "common-search-magnifyingglass", true)
magnifier:SetWidth(10) magnifier:SetHeight(10)
magnifier:SetPoint("LEFT", r, "LEFT", 1, -1)
local instruction = r:CreateFontString(nil, "ARTWORK", "GameFontDisableSmall")
instruction:SetPoint("TOPLEFT", r, "TOPLEFT", 16, 0)
instruction:SetPoint("BOTTOMRIGHT", r, "BOTTOMRIGHT", -20, 0)
instruction:SetJustifyH("LEFT")
instruction:SetTextColor(0.35, 0.35, 0.35)
instruction:SetText(SEARCH)
local clear = CreateFrame("Button", nil, r)
clear:SetWidth(17) clear:SetHeight(17)
clear:SetPoint("RIGHT", r, "RIGHT", -3, 0)
local closeButton = clear:CreateTexture(nil, "ARTWORK")
atlas(closeButton, "common-search-clearbutton", true)
closeButton:SetWidth(10) closeButton:SetHeight(10)
closeButton:SetPoint("CENTER", clear, "CENTER", 0, 0)
closeButton:SetAlpha(0.5)
clear:SetScript("OnEnter", function() closeButton:SetAlpha(1) end)
clear:SetScript("OnLeave", function() closeButton:SetAlpha(0.5) end)
clear:SetScript("OnClick", function()
r:SetText("")
r:ClearFocus()
end)
clear:Hide()
r:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
r:SetScript("OnEnterPressed", function(self) self:ClearFocus() end)
r:SetScript("OnEditFocusGained", function() instruction:Hide() end)
r:SetScript("OnEditFocusLost", function(self)
if self:GetText() == "" then instruction:Show() end
end)
r:SetScript("OnTextChanged", function(self)
local t = self:GetText()
if t == "" then clear:Hide() else clear:Show() instruction:Hide() end
SetTradeSkillItemNameFilter(t)
end)
skin.search = r
-- Scrolling list area
local zone = CreateFrame("ScrollFrame", "ForeverUITradeSkillListScroll", list)
skin.zone = zone
placeBar(false)
skin.hasBar = false
local child = CreateFrame("Frame", nil, zone)
child:SetWidth(1)
child:SetHeight(1)
zone:SetScrollChild(child)
child:SetFrameLevel(base + NV.rows)
skin.child = child
skin.categories, skin.recipes, skin.offset = {}, {}, 0
local bar = ForeverUI.CreateScrollBar("ForeverUITradeSkillScrollBar", list, zone)
bar:ClearAllPoints()
bar:SetPoint("TOPLEFT", zone, "TOPRIGHT", 0, 0)
bar:SetPoint("BOTTOMLEFT", zone, "BOTTOMRIGHT", 0, 0)
bar.onScroll = function(step)
skin.offset = step
M.UpdateList()
end
skin.bar = bar
zone:EnableMouseWheel(true)
zone:SetScript("OnMouseWheel", function(_, direction)
if bar:IsShown() then bar:MoveTo(bar.offset - direction * 3) end
end)
-- Rank bar
local R = N.rank
local rank = CreateFrame("Frame", "ForeverUITradeSkillRankBar", page)
rank:SetWidth(R[3])
rank:SetHeight(R[4])
rank:SetPoint("TOPLEFT", page, "TOPLEFT", R[1], R[2])
rank:SetFrameLevel(base + NV.rank)
local rankBackground = rank:CreateTexture(nil, "BACKGROUND")
atlas(rankBackground, "professions-skillbar-bg", true)
rankBackground:SetWidth(R.background[1]) rankBackground:SetHeight(R.background[2])
rankBackground:SetPoint("TOPLEFT", rank, "TOPLEFT", 0, 0)
local filled = rank:CreateTexture(nil, "BORDER")
filled:SetHeight(R.filled[2])
filled:SetPoint("TOPLEFT", rank, "TOPLEFT", R.filled[3] + R.mask, R.filled[4])
local flare = rank:CreateTexture(nil, "ARTWORK")
flare:SetWidth(R.flare[1]) flare:SetHeight(R.flare[2])
flare:SetBlendMode("ADD")
local rankFrame = rank:CreateTexture(nil, "OVERLAY")
atlas(rankFrame, "professions-skillbar-frame", true)
rankFrame:SetWidth(R.background[1]) rankFrame:SetHeight(R.background[2])
rankFrame:SetPoint("TOPLEFT", rank, "TOPLEFT", 0, 0)
local rankText = CreateFrame("Frame", nil, rank)
rankText:SetHeight(R[4])
rankText:SetPoint("LEFT", rank, "LEFT", 0, R.text)
rankText:SetPoint("RIGHT", rank, "RIGHT", 0, R.text)
rankText:SetFrameLevel(rank:GetFrameLevel() + 1)
local t = rankText:CreateFontString(nil, "ARTWORK")
t:SetFontObject(FONTS.rank)
t:SetPoint("CENTER", rankText, "CENTER", 0, 0)
rank.filled, rank.flare, rank.text = filled, flare, t
skin.rank = rank
-- Link button
local Ln = N.link
local link = CreateFrame("Button", "ForeverUITradeSkillLinkButton", page)
link:SetWidth(Ln[3]) link:SetHeight(Ln[3])
link:SetPoint("LEFT", rank, "RIGHT", Ln[1], Ln[2])
link:SetFrameLevel(base + NV.rank)
local linkBackground = link:CreateTexture(nil, "BACKGROUND")
linkBackground:SetWidth(Ln.background) linkBackground:SetHeight(Ln.background)
linkBackground:SetPoint("CENTER", link, "CENTER", 0, 0)
local linkIcon = link:CreateTexture(nil, "ARTWORK")
atlas(linkIcon, "common-icon-chatlink", true)
linkIcon:SetWidth(Ln.icon) linkIcon:SetHeight(Ln.icon)
linkIcon:SetPoint("CENTER", link, "CENTER", 0, 0)
local function applyLinkState(state) atlas(linkBackground, "common-button-tertiary-square-" .. state, true) end
applyLinkState("normal")
link:SetScript("OnEnter", function(self)
applyLinkState("hover")
GameTooltip:SetOwner(self, "ANCHOR_TOPLEFT")
GameTooltip:SetText(LINK_TRADESKILL_TOOLTIP, nil, nil, nil, nil, 1)
GameTooltip:Show()
end)
link:SetScript("OnLeave", function()
applyLinkState("normal")
GameTooltip:Hide()
end)
link:SetScript("OnMouseDown", function() applyLinkState("pressed") end)
link:SetScript("OnMouseUp", function(self) applyLinkState(self:IsMouseOver() and "hover" or "normal") end)
link:SetScript("OnClick", function()
local l = GetTradeSkillListLink()
if l and not ChatEdit_InsertLink(l) then
ChatEdit_GetLastActiveWindow():Show()
ChatEdit_InsertLink(l)
end
PlaySound("igMainMenuOptionCheckBoxOn")
end)
skin.link = link
-- Schematic
local Cd = N.card
local card = CreateFrame("Frame", "ForeverUITradeSkillSchematic", page)
card:SetWidth(Cd[3])
card:SetHeight(Cd[4])
card:SetPoint("TOPLEFT", list, "TOPRIGHT", Cd[1], Cd[2])
card:SetFrameLevel(base + NV.card)
local F = { frame = card }
F.map = card:CreateTexture(nil, "BACKGROUND")
F.map:SetAllPoints(card)
local cardEdge = Tpl.StretchedAtlas(card, "common-insideframe", "BORDER")
cardEdge.rect:SetAllPoints(card)
-- Recipe content (hidden without a recipe)
local content = CreateFrame("Frame", nil, card)
content:SetAllPoints(card)
F.content = content
local Rs = N.result
local result = CreateFrame("Button", "ForeverUITradeSkillOutputIcon", content)
result:SetWidth(Rs[3]) result:SetHeight(Rs[3])
result:SetPoint("TOPLEFT", card, "TOPLEFT", Rs[1], Rs[2])
result:RegisterForClicks("LeftButtonUp", "RightButtonUp")
F.result = result
F.icon = result:CreateTexture(nil, "BORDER")
F.icon:SetWidth(Rs.icon) F.icon:SetHeight(Rs.icon)
F.icon:SetPoint("CENTER", result, "CENTER", 0, 0)
F.outline = result:CreateTexture(nil, "OVERLAY")
F.outline:SetWidth(Rs.outline) F.outline:SetHeight(Rs.outline)
F.outline:SetPoint("CENTER", result, "CENTER", 0, 0)
local glow = result:CreateTexture(nil, "HIGHLIGHT")
atlas(glow, "auctionhouse-itemicon-border-white", true)
glow:SetWidth(Rs.glow) glow:SetHeight(Rs.glow)
glow:SetPoint("CENTER", result, "CENTER", 0, 0)
glow:SetBlendMode("ADD")
glow:SetAlpha(0.2)
local resultOverlay = CreateFrame("Frame", nil, result)
resultOverlay:SetAllPoints(result)
resultOverlay:SetFrameLevel(result:GetFrameLevel() + 1)
F.count = resultOverlay:CreateFontString(nil, "OVERLAY", "NumberFontNormalLarge")
F.count:SetJustifyH("RIGHT")
F.count:SetPoint("BOTTOM", result, "BOTTOMRIGHT", Rs.count[1], Rs.count[2])
F.shadow = resultOverlay:CreateTexture(nil, "ARTWORK")
atlas(F.shadow, "battlebar-swappetshadow", true)
F.shadow:SetAlpha(0.8)
F.shadow:SetPoint("TOPLEFT", F.count, "TOPLEFT", -10, 10)
F.shadow:SetPoint("BOTTOMRIGHT", F.count, "BOTTOMRIGHT", 10, -10)
F.shadow:Hide()
result:SetScript("OnEnter", function(self)
if not M.selected then return end
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:SetTradeSkillItem(M.selected)
GameTooltip:Show()
end)
result:SetScript("OnLeave", function() GameTooltip:Hide() end)
result:SetScript("OnClick", function()
if M.selected then HandleModifiedItemClick(GetTradeSkillItemLink(M.selected)) end
end)
F.name = content:CreateFontString(nil, "ARTWORK")
F.name:SetFontObject(FONTS.med2)
F.name:SetJustifyH("LEFT")
F.name:SetJustifyV("TOP")
F.name:SetPoint("LEFT", result, "RIGHT", N.name[1], N.name[2])
F.tools = content:CreateFontString(nil, "ARTWORK")
F.tools:SetFontObject(FONTS.small2)
F.tools:SetJustifyH("LEFT")
F.tools:SetJustifyV("TOP")
F.tools:SetPoint("TOPLEFT", F.name, "BOTTOMLEFT", N.tools[1], N.tools[2])
F.cooldown = content:CreateFontString(nil, "ARTWORK", "GameFontRedSmall")
F.cooldown:SetJustifyH("LEFT")
F.cooldown:SetWidth(N.cooldown)
F.description = content:CreateFontString(nil, "ARTWORK")
F.description:SetFontObject(FONTS.small2)
F.description:SetJustifyH("LEFT")
F.description:SetJustifyV("TOP")
skin.card = F
-- Reagents
local Rg = N.reagents
local reagents = CreateFrame("Frame", "ForeverUITradeSkillReagents", content)
reagents:SetWidth(Rg.tag[1])
reagents:SetHeight(Rg.tag[2])
local tag = reagents:CreateFontString(nil, "BACKGROUND", "GameFontNormalSmall")
tag:SetJustifyH("LEFT")
tag:SetWidth(Rg.tag[1])
tag:SetHeight(Rg.tag[2])
tag:SetPoint("TOPLEFT", reagents, "TOPLEFT", 0, 0)
-- SPELL_REAGENTS, the label of 3.3.5's page in the client's language; it ends with a line
-- break meant for spell tooltips
tag:SetText((string.gsub(SPELL_REAGENTS, "|n$", "")))
skin.reagents, skin.slots = reagents, {}
-- Buttons
createButtons(page)
Tpl.CloseButton(TradeSkillFrameCloseButton, f)
TradeSkillFrameCloseButton:SetFrameLevel(base + NV.closeButton)
hooksecurefunc("TradeSkillFrame_Update", M.Refresh)
hooksecurefunc("TradeSkillFrame_SetSelection", M.Refresh)
end
M.Skin()
local watcher = CreateFrame("Frame")
watcher:RegisterEvent("ADDON_LOADED")
watcher:RegisterEvent("UPDATE_TRADESKILL_RECAST")
watcher:SetScript("OnEvent", function(_, event, name)
if event == "ADDON_LOADED" then
if name == "Blizzard_TradeSkillUI" then M.Skin() end
elseif M.skin and TradeSkillFrame:IsShown() and M.skin.buttons.counter.active then
M.skin.buttons.counter:SetNumber(GetTradeskillRepeatCount())
M.UpdateArrows()
end
end)