You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

388 lines
14 KiB

local COMPAT, _, T = select(4, GetBuildInfo()), ...
local XU, MODERN, type = T.exUI, COMPAT > 10e4, type
local assert, getWidgetData, newWidgetData, setWidgetData, AddObjectMethods, CallObjectScript = XU:GetImpl()
local HOLD_HOVER_HINT_DURATION, ICON_FILE_NAMES, LookupIconName = 0.2, nil
local IconSelector, IconSelectorData, internal = {}, {}, {}
local IconSelectorProps = {
api=IconSelector,
scripts={"OnIconSelect", "OnEditFocusGained", "OnEditFocusLost"},
lastVisibleIcon=-1,
selectedAsset=nil,
viewIndexOffset=0,
firstAsset=nil,
firstAssetValue=nil,
firstAssetIsAtlas=false,
columns=14,
rows=7,
cellWidth=36,
cellHeight=36,
pendingGridSync=true,
manualInputHintText="",
}
AddObjectMethods({"IconSelector"}, IconSelectorProps)
function IconSelector:SetSelectedAsset()
local d = assert(getWidgetData(self, IconSelectorData), "Invalid object type")
return d.selectedAsset
end
function IconSelector:SetSelectedAsset(asset)
local d = assert(getWidgetData(self, IconSelectorData), "Invalid object type")
assert(asset == nil or type(asset) == "string" or type(asset) == "number", 'Syntax: IconSelector:SetSelectedAsset(asset)')
d.selectedAsset = asset
internal.RenderView(d, d.viewIndexOffset)
internal.SyncHintText(d)
end
function IconSelector:SetFirstAsset(value, overrideAsset)
local d = assert(getWidgetData(self, IconSelectorData), "Invalid object type")
local asset = overrideAsset or value
assert(asset == nil or type(asset) == "number" or type(asset) == "string", 'Syntax: IconSelector:SetFirstAsset(value[, overrideAsset])')
d.firstAssetIsAtlas = type(asset) == "string" and not GetFileIDFromPath(asset) and C_Texture.GetAtlasInfo(asset) and true
d.firstAssetValue, d.firstAsset = value, asset
internal.RenderView(d, d.viewIndexOffset)
end
function IconSelector:SetManualInputHintText(text)
local d = assert(getWidgetData(self, IconSelectorData), "Invalid object type")
assert(type(text) == "string", 'Syntax: IconSelector:SetManualInputHintText("text")')
d.manualInputHintText = text
internal.SyncHintText(d)
end
function IconSelector:SetGridSize(rows, cols)
local d = assert(getWidgetData(self, IconSelectorData), "Invalid object type")
assert(type(rows) == "number" and rows > 0 and rows % 1 == 0
and type(cols) == "number" and cols > 0 and cols % 1 == 0
, 'Syntax: IconSelector:SetGridSize(rows, cols)')
d.pendingGridSync, d.rows, d.columns = true, rows, cols
if d.proto.super.IsShown(d.self) then
internal.ConfigureIconGrid(d)
end
end
function IconSelector:FocusManualInput()
local d = assert(getWidgetData(self, IconSelectorData), "Invalid object type")
d.manualInput:SetFocus()
end
function IconSelector:IsSearchPossible()
return not not (ICON_FILE_NAMES or LookupIconName and LookupIconName(0) or ICON_FILE_NAMES)
end
local GetAllIcons do
local function GetAbilityIcons(into)
local AB = T.ActionBook and T.ActionBook:compatible(2,40)
local cc, ni, _, ic = AB and AB:GetNumCategories() >= 1 and AB:GetCategoryContents(1), #into+1
for i=1, cc and #cc or 0 do
_, _, ic = AB:GetActionDescription(cc(i))
if ic then
into[ni], ni = ic, ni + 1
end
end
end
function GetAllIcons()
local a, m, ni = {}, {}, 1
for i=1,5 do
local fixLoose = i == 3 or i == 5
select(i, GetAbilityIcons, GetMacroIcons, GetLooseMacroIcons, GetMacroItemIcons, GetLooseMacroItemIcons)(a)
for i=ni, #a do
local ai = a[i]
local fid = type(ai) == "string" and GetFileIDFromPath(ai) or ai
if fixLoose and not fid then
local c1 = ai:gsub("%.$", "")
local c2, c3 = "Interface/Icons/" .. c1, "Interface/Icons/" .. ai
ai = GetFileIDFromPath(c1) and c1 or GetFileIDFromPath(c2) and c2 or GetFileIDFromPath(c3) and c3
fid = ai and GetFileIDFromPath(ai)
end
if fid and m[fid] == nil then
a[ni], m[fid], m[ai], ni = ai, ni, ni, ni + 1
end
end
for i=#a, ni, -1 do
a[i] = nil
end
end
GetAllIcons = function() return a end
return a
end
end
function LookupIconName(fid)
LookupIconName = nil
if select(5, (C_AddOns.GetAddOnInfo or GetAddOnInfo)("IconFileNames")) == "DEMAND_LOADED"
and not (C_AddOns.IsAddOnLoaded or IsAddOnLoaded)("IconFileNames") then
(C_AddOns.LoadAddOn or LoadAddOn)("IconFileNames")
end
ICON_FILE_NAMES = _G.ICON_FILE_NAMES
ICON_FILE_NAMES = type(ICON_FILE_NAMES) == "table" and ICON_FILE_NAMES or nil
return ICON_FILE_NAMES and ICON_FILE_NAMES[fid]
end
function internal:OnHide()
local d = getWidgetData(self, IconSelectorData)
d.hoverIcon, d.hoverIconLT = nil
self:GetParent():Hide()
end
function internal:OnIconClick(_button, _down)
local d, checked = getWidgetData(self:GetParent(), IconSelectorData), self:GetChecked()
local idx = checked and self:GetID()+d.viewIndexOffset or nil
local tex = idx and (idx == 0 and d.firstAssetValue or idx > 0 and d.iconList[idx] or nil)
if d.selectedButton then
d.selectedButton:SetChecked(nil)
end
d.selectedAsset, d.selectedButton = tex, checked and self or nil
PlaySound(SOUNDKIT.U_CHAT_SCROLL_BUTTON)
CallObjectScript(d.self, "OnIconSelect", tex)
end
function internal:OnIconEnter()
local d = getWidgetData(self:GetParent(), IconSelectorData)
d.hoverIcon, d.hoverIconLT = self, nil
internal.SyncHintText(d)
end
function internal:OnUpdateTick()
local d = getWidgetData(self, IconSelectorData)
if d.hoverIconLT and d.hoverIconLT + HOLD_HOVER_HINT_DURATION > GetTime() then
return
elseif d.hoverIconLT then
d.hoverIcon = nil
end
self:SetScript("OnUpdate", nil)
internal.SyncHintText(d)
end
function internal:OnIconLeave()
local d = getWidgetData(self:GetParent(), IconSelectorData)
if d.hoverIcon == self then
d.hoverIconLT = GetTime()
d.clipRoot:SetScript("OnUpdate", internal.OnUpdateTick)
end
end
function internal.CreateIconButton(parent, pool, id)
local f, sz = CreateFrame("CheckButton", nil, parent, nil, id), 32
f:SetSize(sz, sz)
f:SetNormalTexture("")
f:SetHighlightTexture("Interface/Buttons/ButtonHilight-Square")
f:GetHighlightTexture():SetBlendMode("ADD")
f:SetCheckedTexture("Interface/Buttons/CheckButtonHilight")
f:GetCheckedTexture():SetBlendMode("ADD")
f:SetPushedTexture("Interface/Buttons/UI-Quickslot-Depress")
f:SetScript("OnClick", internal.OnIconClick)
f:SetScript("OnEnter", internal.OnIconEnter)
f:SetScript("OnLeave", internal.OnIconLeave)
local tex = f:CreateTexture(nil, "ARTWORK")
tex:SetAllPoints()
local bg = f:CreateTexture(nil, "BACKGROUND", nil, -1)
bg:SetTexture("Interface/Buttons/UI-EmptySlot-Disabled")
bg:SetPoint("CENTER", tex, "CENTER")
bg:SetSize(1.5*sz, 1.5*sz)
local edge = f:CreateTexture(nil, "OVERLAY", nil, -1)
edge:SetTexture("Interface/Buttons/UI-Quickslot2")
edge:SetSize(1.625*sz, 1.625*sz)
edge:SetPoint("CENTER", tex, "CENTER", 0.25, -0.25)
if MODERN then
local m = f:CreateMaskTexture()
m:SetTexture("Interface/FrameGeneral/UIFrameIconMask")
m:SetAllPoints(tex)
tex:AddMaskTexture(m)
end
f.tex, f.background, f.edge = tex, bg, edge
pool[id] = f
return f
end
function internal:OnScroll(value, interaction)
local d, co = getWidgetData(self, IconSelectorData), value % 1
d.origin:SetPoint("TOPLEFT", 0, co*d.cellHeight)
internal.RenderView(d, (value - co) * d.columns, true)
if interaction and d.hoverIconLT then
internal.SyncHintText(d)
end
end
function internal.RenderView(d, value, allowSkip)
if allowSkip and d.viewIndexOffset == value then return end
local icons, icontex, sel, selectedButton = d.pool, d.iconList, d.selectedAsset
for i=0, d.lastVisibleIcon do
local ico, tex = icons[i].tex, i == 0 and value == 0 and (d.firstAsset or "Interface/Icons/INV_Misc_QuestionMark") or icontex[i+value]
icons[i]:SetShown(not not tex)
if tex then
if i == 0 and value == 0 and d.firstAssetIsAtlas then
ico:SetAtlas(tex)
else
ico:SetTexture(tex)
end
local check = sel and (tex == sel or ico:GetTexture() == sel)
icons[i]:SetChecked(check)
selectedButton = check and icons[i] or selectedButton
end
end
d.viewIndexOffset, d.selectedButton = value, selectedButton
end
function internal.ConfigureIconGrid(d)
local sb, pool, icontex, origin = d.scrollBar, d.pool, d.iconList, d.origin
local rows, cols, cw, ch = d.rows, d.columns, d.cellWidth, d.cellHeight
sb:SetStepsPerPage(rows, math.min(math.max(1, rows-2), 5))
sb:SetWindowRange(rows)
sb:SetMinMaxValues(0, math.ceil((#icontex-cols*rows)/cols))
d.self:SetSize(42+cols*cw, 40+ch*rows)
local usedIcons = (cols*(rows+1))-1
for i=0, usedIcons do
local w = pool[i] or internal.CreateIconButton(d.clipRoot, d.pool, i)
w:SetPoint("TOPLEFT", origin, (i % cols)*cw, - ch*math.floor(i / cols))
end
for i=usedIcons+1, d.lastVisibleIcon do
pool[i]:Hide()
end
d.lastVisibleIcon, d.pendingGridSync = usedIcons, nil
end
function internal:OnShow()
local d = getWidgetData(self, IconSelectorData)
internal.SetIconList(d, GetAllIcons(), nil, -1)
if d.pendingGridSync then
internal.ConfigureIconGrid(d)
end
d.scrollBar:SetValue(0, true)
local p, kbf = d.self:GetParent(), GetCurrentKeyBoardFocus()
d.self:SetFrameLevel(math.max(d.self:GetFrameLevel(), p and p:GetFrameLevel()+200))
if kbf then
kbf:ClearFocus()
end
end
function internal.SyncHintText(d)
if d.manualInput:HasFocus() then
d.manualInputHint:Hide()
d.manualInputFS:Show()
elseif d.hoverIcon and d.hoverIcon == GetMouseFocus() then
d.manualInputHint:Show()
local idx = d.hoverIcon:GetID() + d.viewIndexOffset
local asset = idx == 0 and d.firstAsset or d.iconList[idx]
local fn = ICON_FILE_NAMES and ICON_FILE_NAMES[asset] or LookupIconName and securecall(LookupIconName, asset)
d.manualInputHint:SetText(fn and asset .. " |cff606060[|r" .. fn .. "|cff606060]|r" or asset or "")
d.manualInputFS:Hide()
elseif d.selectedAsset then
d.manualInput:SetText(d.selectedAsset)
d.manualInputFS:Show()
d.manualInputHint:Hide()
else
d.manualInputHint:SetText(d.manualInputHintText)
d.manualInputHint:Show()
d.manualInput:SetText("")
end
end
function internal:OnEditFocusChange()
local d = getWidgetData(self, IconSelectorData)
internal.SyncHintText(d)
CallObjectScript(d.self, self:HasFocus() and "OnEditFocusGained" or "OnEditFocusLost", self)
end
function internal:OnEnterPressed()
local d, text = getWidgetData(self, IconSelectorData), self:GetText()
if IsAltKeyDown() and (ICON_FILE_NAMES or LookupIconName and securecall(LookupIconName, 0) or ICON_FILE_NAMES) then
return internal.FilterIcons(d, text, self)
end
if text:match("%S") then
local fid0, nt = GetFileIDFromPath(text), tonumber(text) or 0
local fid1 = not fid0 and GetFileIDFromPath("Interface/Icons/" .. text)
local path = fid0 and (fid0 < 0 and text or fid0) or
fid1 and (fid1 < 0 and "Interface/Icons/" .. text or fid1) or
C_Texture.GetAtlasInfo(text) and text or
nt > 0 and nt or
GetSpellTexture(text)
if not path then
return self:HighlightText()
end
d.selectedAsset = path
CallObjectScript(d.self, "OnIconSelect", path)
end
self:ClearFocus()
end
function internal:OnEscapePressed()
self:ClearFocus()
end
function internal.FilterIcons(d, query, _editbox)
local of, nf = d.filter, query:lower():gsub("[.%%%[%]%-+*?()]", ""):match(".+")
if of == nf then
return
elseif not nf then
internal.SetIconList(d, GetAllIcons(), nil)
else
local t, ni, p, smatch = {}, 1, nf:gsub(" +", ".*"), nf.match
local ot = nf:match(of or "") and d.iconList or GetAllIcons()
for i=1, #ot do
local fn = ICON_FILE_NAMES[ot[i]]
if fn and smatch(fn, p) then
t[ni], ni = ot[i], ni + 1
end
end
internal.SetIconList(d, t, nf)
end
internal.RenderView(d, d.viewIndexOffset)
end
function internal.SetIconList(d, iconList, filter, viewIndexOffset)
d.iconList, d.filter, d.viewIndexOffset = iconList, filter, viewIndexOffset or d.viewIndexOffset
d.scrollBar:SetMinMaxValues(0, math.ceil((#d.iconList-d.columns*d.rows)/d.columns))
end
local function CreateIconSelector(name, parent, outerTemplate, id)
local f, d, t, a = CreateFrame("Frame", name, parent, outerTemplate, id)
d = newWidgetData(f, IconSelectorData, IconSelectorProps)
d.pool, d.backdrop = {}, XU:Create("Backdrop", f, {bgFile = "Interface/ChatFrame/ChatFrameBackground", edgeFile = "Interface/DialogFrame/UI-DialogBox-Border", tile = true, tileSize = 32, edgeSize = 32, insets = { left = 11, right = 11, top = 11, bottom = 10 }, bgColor=0xd8000000})
f:EnableMouse(1)
f:SetToplevel(1)
f:Hide()
t = CreateFrame("Frame", nil, f)
t:SetPoint("TOPLEFT", 12, -32)
t:SetPoint("BOTTOMRIGHT", -31, 12)
t:SetClipsChildren(true)
t:SetScript("OnHide", internal.OnHide)
t:SetScript("OnShow", internal.OnShow)
setWidgetData(t, IconSelectorData, d)
t, d.clipRoot = CreateFrame("Frame", nil, t), t
t:SetSize(1,1)
t:SetPoint("TOPLEFT")
t:Hide()
t, d.origin = XU:Create("ScrollBar", nil, f), t
t:SetStyle("common")
t:SetPoint("TOPRIGHT", -9, -28)
t:SetPoint("BOTTOMRIGHT", -9, 9)
t:SetWheelScrollTarget(d.clipRoot, -2, -5, -2, -1)
t:SetCoverTarget(d.clipRoot)
t:SetScript("OnValueChanged", internal.OnScroll)
setWidgetData(t, IconSelectorData, d)
t, d.scrollBar = XU:Create("LineInput", nil, f), t
t:SetStyle("chat")
t:SetPoint("TOPLEFT", 11, -7)
t:SetPoint("TOPRIGHT", -34, -7)
setWidgetData(t, IconSelectorData, d)
t:SetScript("OnEditFocusGained", internal.OnEditFocusChange)
t:SetScript("OnEditFocusLost", internal.OnEditFocusChange)
t:SetScript("OnEnterPressed", internal.OnEnterPressed)
t:SetScript("OnEscapePressed", internal.OnEscapePressed)
d.manualInputFS = t:GetRegions()
a, d.manualInput = t:CreateFontString(nil, "OVERLAY", "GameFontHighlight"), t
a:SetPoint("CENTER")
a:SetTextColor(0.85, 0.85, 0.85)
d.manualInputHint = a
t = CreateFrame("Button", nil, f, "UIPanelCloseButton")
if MODERN then
t:SetPoint("TOPRIGHT", -6.5, -5.5)
t:SetSize(24,24)
t:SetHitRectInsets(2, 2, 2, 3)
a = t:CreateMaskTexture()
a:SetTexture("Interface/common/common-iconmask")
a:SetPoint("TOPLEFT", 2.4, -2.4)
a:SetPoint("BOTTOMRIGHT", -2.8, 4)
t:GetNormalTexture():AddMaskTexture(a)
t:GetPushedTexture():AddMaskTexture(a)
t:GetHighlightTexture():AddMaskTexture(a)
a = t:CreateMaskTexture()
a:SetTexture("Interface/common/common-mask-diamond")
a:SetSize(30,30)
a:SetPoint("RIGHT", 3, 0.5)
t:GetNormalTexture():AddMaskTexture(a)
t:GetPushedTexture():AddMaskTexture(a)
t:GetHighlightTexture():AddMaskTexture(a)
else
t:SetPoint("TOPRIGHT", -2, -1)
t:SetHitRectInsets(4, 4, 4, 6)
end
t, d.closeButton = t:CreateTexture(nil, "BACKGROUND", nil, -5), t
t:SetTexture("Interface/ChatFrame/UI-ChatInputBorder-Mid2")
t:SetPoint("BOTTOMLEFT", d.manualInput, "BOTTOMRIGHT", 7, -6.25)
t:SetSize(18.75, 8)
t:SetTexCoord(0,1, 0.75,1)
return f
end
XU:RegisterFactory("IconSelector", CreateIconSelector)