Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local MAJOR = "LibQTip-1.0"
- local MINOR = 47 -- Should be manually increased
- local LibStub = _G.LibStub
- assert(LibStub, MAJOR .. " requires LibStub")
- local lib, oldMinor = LibStub:NewLibrary(MAJOR, MINOR)
- if not lib then
- return
- end -- No upgrade needed
- ------------------------------------------------------------------------------
- -- Upvalued globals
- ------------------------------------------------------------------------------
- local table = _G.table
- local tinsert = table.insert
- local tremove = table.remove
- local wipe = table.wipe
- local error = error
- local math = math
- local min, max = math.min, math.max
- local next = next
- local pairs, ipairs = pairs, ipairs
- local select = select
- local setmetatable = setmetatable
- local tonumber, tostring = tonumber, tostring
- local type = type
- local CreateFrame = _G.CreateFrame
- local GameTooltip = _G.GameTooltip
- local UIParent = _G.UIParent
- local geterrorhandler = _G.geterrorhandler
- ------------------------------------------------------------------------------
- -- Tables and locals
- ------------------------------------------------------------------------------
- lib.frameMetatable = lib.frameMetatable or {__index = CreateFrame("Frame")}
- lib.tipPrototype = lib.tipPrototype or setmetatable({}, lib.frameMetatable)
- lib.tipMetatable = lib.tipMetatable or {__index = lib.tipPrototype}
- lib.providerPrototype = lib.providerPrototype or {}
- lib.providerMetatable = lib.providerMetatable or {__index = lib.providerPrototype}
- lib.cellPrototype = lib.cellPrototype or setmetatable({}, lib.frameMetatable)
- lib.cellMetatable = lib.cellMetatable or {__index = lib.cellPrototype}
- lib.activeTooltips = lib.activeTooltips or {}
- lib.tooltipHeap = lib.tooltipHeap or {}
- lib.frameHeap = lib.frameHeap or {}
- lib.tableHeap = lib.tableHeap or {}
- lib.onReleaseHandlers = lib.onReleaseHandlers or {}
- local tipPrototype = lib.tipPrototype
- local tipMetatable = lib.tipMetatable
- local providerPrototype = lib.providerPrototype
- local providerMetatable = lib.providerMetatable
- local cellPrototype = lib.cellPrototype
- local cellMetatable = lib.cellMetatable
- local activeTooltips = lib.activeTooltips
- local highlightFrame = CreateFrame("Frame", nil, UIParent)
- highlightFrame:SetFrameStrata("TOOLTIP")
- highlightFrame:Hide()
- local DEFAULT_HIGHLIGHT_TEXTURE_PATH = [[Interface\QuestFrame\UI-QuestTitleHighlight]]
- local highlightTexture = highlightFrame:CreateTexture(nil, "OVERLAY")
- highlightTexture:SetTexture(DEFAULT_HIGHLIGHT_TEXTURE_PATH)
- highlightTexture:SetBlendMode("ADD")
- highlightTexture:SetAllPoints(highlightFrame)
- ------------------------------------------------------------------------------
- -- Private methods for Caches and Tooltip
- ------------------------------------------------------------------------------
- local AcquireTooltip, ReleaseTooltip
- local AcquireCell, ReleaseCell
- local AcquireTable, ReleaseTable
- local InitializeTooltip, SetTooltipSize, ResetTooltipSize, FixCellSizes
- local ClearTooltipScripts
- local SetFrameScript, ClearFrameScripts
- ------------------------------------------------------------------------------
- -- Cache debugging.
- ------------------------------------------------------------------------------
- -- @debug @
- local usedTables, usedFrames, usedTooltips = 0, 0, 0
- --@end-debug@
- ------------------------------------------------------------------------------
- -- Internal constants to tweak the layout
- ------------------------------------------------------------------------------
- local TOOLTIP_PADDING = 10
- local CELL_MARGIN_H = 6
- local CELL_MARGIN_V = 3
- ------------------------------------------------------------------------------
- -- Public library API
- ------------------------------------------------------------------------------
- --- Create or retrieve the tooltip with the given key.
- -- If additional arguments are passed, they are passed to :SetColumnLayout for the acquired tooltip.
- -- @name LibQTip:Acquire(key[, numColumns, column1Justification, column2justification, ...])
- -- @param key string or table - the tooltip key. Any value that can be used as a table key is accepted though you should try to provide unique keys to avoid conflicts.
- -- Numbers and booleans should be avoided and strings should be carefully chosen to avoid namespace clashes - no "MyTooltip" - you have been warned!
- -- @return tooltip Frame object - the acquired tooltip.
- -- @usage Acquire a tooltip with at least 5 columns, justification : left, center, left, left, left
- -- <pre>local tip = LibStub('LibQTip-1.0'):Acquire('MyFooBarTooltip', 5, "LEFT", "CENTER")</pre>
- function lib:Acquire(key, ...)
- if key == nil then
- error("attempt to use a nil key", 2)
- end
- local tooltip = activeTooltips[key]
- if not tooltip then
- tooltip = AcquireTooltip()
- InitializeTooltip(tooltip, key)
- activeTooltips[key] = tooltip
- end
- if select("#", ...) > 0 then
- -- Here we catch any error to properly report it for the calling code
- local ok, msg = pcall(tooltip.SetColumnLayout, tooltip, ...)
- if not ok then
- error(msg, 2)
- end
- end
- return tooltip
- end
- function lib:Release(tooltip)
- local key = tooltip and tooltip.key
- if not key or activeTooltips[key] ~= tooltip then
- return
- end
- ReleaseTooltip(tooltip)
- activeTooltips[key] = nil
- end
- function lib:IsAcquired(key)
- if key == nil then
- error("attempt to use a nil key", 2)
- end
- return not (not activeTooltips[key])
- end
- function lib:IterateTooltips()
- return pairs(activeTooltips)
- end
- ------------------------------------------------------------------------------
- -- Frame cache
- ------------------------------------------------------------------------------
- local frameHeap = lib.frameHeap
- local function AcquireFrame(parent)
- local frame = tremove(frameHeap) or CreateFrame("Frame")
- frame:SetParent(parent)
- --[===[@debug@
- usedFrames = usedFrames + 1
- --@end-debug@]===]
- return frame
- end
- local function ReleaseFrame(frame)
- frame:Hide()
- frame:SetParent(nil)
- frame:ClearAllPoints()
- if(frame.SetBackdrop) then --**
- frame:SetBackdrop(nil)
- end
- ClearFrameScripts(frame)
- tinsert(frameHeap, frame)
- --[===[@debug@
- usedFrames = usedFrames - 1
- --@end-debug@]===]
- end
- ------------------------------------------------------------------------------
- -- Dirty layout handler
- ------------------------------------------------------------------------------
- lib.layoutCleaner = lib.layoutCleaner or CreateFrame("Frame")
- local layoutCleaner = lib.layoutCleaner
- layoutCleaner.registry = layoutCleaner.registry or {}
- function layoutCleaner:RegisterForCleanup(tooltip)
- self.registry[tooltip] = true
- self:Show()
- end
- function layoutCleaner:CleanupLayouts()
- self:Hide()
- for tooltip in pairs(self.registry) do
- FixCellSizes(tooltip)
- end
- wipe(self.registry)
- end
- layoutCleaner:SetScript("OnUpdate", layoutCleaner.CleanupLayouts)
- ------------------------------------------------------------------------------
- -- CellProvider and Cell
- ------------------------------------------------------------------------------
- function providerPrototype:AcquireCell()
- local cell = tremove(self.heap)
- if not cell then
- --** cell = setmetatable(CreateFrame("Frame", nil, UIParent), self.cellMetatable)
- cell = setmetatable(CreateFrame("Frame", nil, UIParent, "BackdropTemplate"), self.cellMetatable)
- if type(cell.InitializeCell) == "function" then
- cell:InitializeCell()
- end
- end
- self.cells[cell] = true
- return cell
- end
- function providerPrototype:ReleaseCell(cell)
- if not self.cells[cell] then
- return
- end
- if type(cell.ReleaseCell) == "function" then
- cell:ReleaseCell()
- end
- self.cells[cell] = nil
- tinsert(self.heap, cell)
- end
- function providerPrototype:GetCellPrototype()
- return self.cellPrototype, self.cellMetatable
- end
- function providerPrototype:IterateCells()
- return pairs(self.cells)
- end
- function lib:CreateCellProvider(baseProvider)
- local cellBaseMetatable, cellBasePrototype
- if baseProvider and baseProvider.GetCellPrototype then
- cellBasePrototype, cellBaseMetatable = baseProvider:GetCellPrototype()
- else
- cellBaseMetatable = cellMetatable
- end
- local newCellPrototype = setmetatable({}, cellBaseMetatable)
- local newCellProvider = setmetatable({}, providerMetatable)
- newCellProvider.heap = {}
- newCellProvider.cells = {}
- newCellProvider.cellPrototype = newCellPrototype
- newCellProvider.cellMetatable = {__index = newCellPrototype}
- return newCellProvider, newCellPrototype, cellBasePrototype
- end
- ------------------------------------------------------------------------------
- -- Basic label provider
- ------------------------------------------------------------------------------
- if not lib.LabelProvider then
- lib.LabelProvider, lib.LabelPrototype = lib:CreateCellProvider()
- end
- local labelProvider = lib.LabelProvider
- local labelPrototype = lib.LabelPrototype
- function labelPrototype:InitializeCell()
- self.fontString = self:CreateFontString()
- self.fontString:SetFontObject(_G.GameTooltipText)
- end
- function labelPrototype:SetupCell(tooltip, value, justification, font, leftPadding, rightPadding, maxWidth, minWidth, ...)
- local fontString = self.fontString
- local line = tooltip.lines[self._line]
- -- detatch fs from cell for size calculations
- fontString:ClearAllPoints()
- fontString:SetFontObject(font or (line.is_header and tooltip:GetHeaderFont() or tooltip:GetFont()))
- fontString:SetJustifyH(justification)
- fontString:SetText(tostring(value))
- leftPadding = leftPadding or 0
- rightPadding = rightPadding or 0
- local width = fontString:GetStringWidth() + leftPadding + rightPadding
- if maxWidth and minWidth and (maxWidth < minWidth) then
- error("maximum width cannot be lower than minimum width: " .. tostring(maxWidth) .. " < " .. tostring(minWidth), 2)
- end
- if maxWidth and (maxWidth < (leftPadding + rightPadding)) then
- error("maximum width cannot be lower than the sum of paddings: " .. tostring(maxWidth) .. " < " .. tostring(leftPadding) .. " + " .. tostring(rightPadding), 2)
- end
- if minWidth and width < minWidth then
- width = minWidth
- end
- if maxWidth and maxWidth < width then
- width = maxWidth
- end
- fontString:SetWidth(width - (leftPadding + rightPadding))
- -- Use GetHeight() instead of GetStringHeight() so lines which are longer than width will wrap.
- local height = fontString:GetHeight()
- -- reanchor fs to cell
- fontString:SetWidth(0)
- fontString:SetPoint("TOPLEFT", self, "TOPLEFT", leftPadding, 0)
- fontString:SetPoint("BOTTOMRIGHT", self, "BOTTOMRIGHT", -rightPadding, 0)
- --~ fs:SetPoint("TOPRIGHT", self, "TOPRIGHT", -r_pad, 0)
- self._paddingL = leftPadding
- self._paddingR = rightPadding
- return width, height
- end
- function labelPrototype:getContentHeight()
- local fontString = self.fontString
- fontString:SetWidth(self:GetWidth() - (self._paddingL + self._paddingR))
- local height = self.fontString:GetHeight()
- fontString:SetWidth(0)
- return height
- end
- function labelPrototype:GetPosition()
- return self._line, self._column
- end
- ------------------------------------------------------------------------------
- -- Tooltip cache
- ------------------------------------------------------------------------------
- local tooltipHeap = lib.tooltipHeap
- -- Returns a tooltip
- function AcquireTooltip()
- local tooltip = tremove(tooltipHeap)
- if not tooltip then
- --** tooltip = CreateFrame("Frame", nil, UIParent)
- tooltip = CreateFrame("Frame", nil, UIParent, "BackdropTemplate")
- local scrollFrame = CreateFrame("ScrollFrame", nil, tooltip)
- scrollFrame:SetPoint("TOP", tooltip, "TOP", 0, -TOOLTIP_PADDING)
- scrollFrame:SetPoint("BOTTOM", tooltip, "BOTTOM", 0, TOOLTIP_PADDING)
- scrollFrame:SetPoint("LEFT", tooltip, "LEFT", TOOLTIP_PADDING, 0)
- scrollFrame:SetPoint("RIGHT", tooltip, "RIGHT", -TOOLTIP_PADDING, 0)
- tooltip.scrollFrame = scrollFrame
- local scrollChild = CreateFrame("Frame", nil, tooltip.scrollFrame)
- scrollFrame:SetScrollChild(scrollChild)
- tooltip.scrollChild = scrollChild
- setmetatable(tooltip, tipMetatable)
- end
- --[===[@debug@
- usedTooltips = usedTooltips + 1
- --@end-debug@]===]
- return tooltip
- end
- -- Cleans the tooltip and stores it in the cache
- function ReleaseTooltip(tooltip)
- if tooltip.releasing then
- return
- end
- tooltip.releasing = true
- tooltip:Hide()
- local releaseHandler = lib.onReleaseHandlers[tooltip]
- if releaseHandler then
- lib.onReleaseHandlers[tooltip] = nil
- local success, errorMessage = pcall(releaseHandler, tooltip)
- if not success then
- geterrorhandler()(errorMessage)
- end
- elseif tooltip.OnRelease then
- local success, errorMessage = pcall(tooltip.OnRelease, tooltip)
- if not success then
- geterrorhandler()(errorMessage)
- end
- tooltip.OnRelease = nil
- end
- tooltip.releasing = nil
- tooltip.key = nil
- tooltip.step = nil
- ClearTooltipScripts(tooltip)
- tooltip:SetAutoHideDelay(nil)
- tooltip:ClearAllPoints()
- tooltip:Clear()
- if tooltip.slider then
- tooltip.slider:SetValue(0)
- tooltip.slider:Hide()
- tooltip.scrollFrame:SetPoint("RIGHT", tooltip, "RIGHT", -TOOLTIP_PADDING, 0)
- tooltip:EnableMouseWheel(false)
- end
- for i, column in ipairs(tooltip.columns) do
- tooltip.columns[i] = ReleaseFrame(column)
- end
- tooltip.columns = ReleaseTable(tooltip.columns)
- tooltip.lines = ReleaseTable(tooltip.lines)
- tooltip.colspans = ReleaseTable(tooltip.colspans)
- layoutCleaner.registry[tooltip] = nil
- tinsert(tooltipHeap, tooltip)
- highlightTexture:SetTexture(DEFAULT_HIGHLIGHT_TEXTURE_PATH)
- highlightTexture:SetTexCoord(0, 1, 0, 1)
- --[===[@debug@
- usedTooltips = usedTooltips - 1
- --@end-debug@]===]
- end
- ------------------------------------------------------------------------------
- -- Cell 'cache' (just a wrapper to the provider's cache)
- ------------------------------------------------------------------------------
- -- Returns a cell for the given tooltip from the given provider
- function AcquireCell(tooltip, provider)
- local cell = provider:AcquireCell(tooltip)
- cell:SetParent(tooltip.scrollChild)
- cell:SetFrameLevel(tooltip.scrollChild:GetFrameLevel() + 3)
- cell._provider = provider
- return cell
- end
- -- Cleans the cell hands it to its provider for storing
- function ReleaseCell(cell)
- if cell.fontString and cell.r then
- cell.fontString:SetTextColor(cell.r, cell.g, cell.b, cell.a)
- end
- cell._font = nil
- cell._justification = nil
- cell._colSpan = nil
- cell._line = nil
- cell._column = nil
- cell:Hide()
- cell:ClearAllPoints()
- cell:SetParent(nil)
- cell:SetBackdrop(nil)
- ClearFrameScripts(cell)
- cell._provider:ReleaseCell(cell)
- cell._provider = nil
- end
- ------------------------------------------------------------------------------
- -- Table cache
- ------------------------------------------------------------------------------
- local tableHeap = lib.tableHeap
- -- Returns a table
- function AcquireTable()
- local tbl = tremove(tableHeap) or {}
- --[===[@debug@
- usedTables = usedTables + 1
- --@end-debug@]===]
- return tbl
- end
- -- Cleans the table and stores it in the cache
- function ReleaseTable(tableInstance)
- wipe(tableInstance)
- tinsert(tableHeap, tableInstance)
- --[===[@debug@
- usedTables = usedTables - 1
- --@end-debug@]===]
- end
- ------------------------------------------------------------------------------
- -- Tooltip prototype
- ------------------------------------------------------------------------------
- function InitializeTooltip(tooltip, key)
- ----------------------------------------------------------------------
- -- (Re)set frame settings
- ----------------------------------------------------------------------
- local backdrop = GameTooltip:GetBackdrop()
- if not tooltip.SetBackdrop then --**
- Mixin(tooltip, BackdropTemplateMixin)
- end
- tooltip:SetBackdrop(backdrop)
- if backdrop then
- tooltip:SetBackdropColor(GameTooltip:GetBackdropColor())
- tooltip:SetBackdropBorderColor(GameTooltip:GetBackdropBorderColor())
- end
- tooltip:SetScale(GameTooltip:GetScale())
- tooltip:SetAlpha(1)
- tooltip:SetFrameStrata("TOOLTIP")
- tooltip:SetClampedToScreen(false)
- ----------------------------------------------------------------------
- -- Internal data. Since it's possible to Acquire twice without calling
- -- release, check for pre-existence.
- ----------------------------------------------------------------------
- tooltip.key = key
- tooltip.columns = tooltip.columns or AcquireTable()
- tooltip.lines = tooltip.lines or AcquireTable()
- tooltip.colspans = tooltip.colspans or AcquireTable()
- tooltip.regularFont = _G.GameTooltipText
- tooltip.headerFont = _G.GameTooltipHeaderText
- tooltip.labelProvider = labelProvider
- tooltip.cell_margin_h = tooltip.cell_margin_h or CELL_MARGIN_H
- tooltip.cell_margin_v = tooltip.cell_margin_v or CELL_MARGIN_V
- ----------------------------------------------------------------------
- -- Finishing procedures
- ----------------------------------------------------------------------
- tooltip:SetAutoHideDelay(nil)
- tooltip:Hide()
- ResetTooltipSize(tooltip)
- end
- function tipPrototype:SetDefaultProvider(myProvider)
- if not myProvider then
- return
- end
- self.labelProvider = myProvider
- end
- function tipPrototype:GetDefaultProvider()
- return self.labelProvider
- end
- local function checkJustification(justification, level, silent)
- if justification ~= "LEFT" and justification ~= "CENTER" and justification ~= "RIGHT" then
- if silent then
- return false
- end
- error("invalid justification, must one of LEFT, CENTER or RIGHT, not: " .. tostring(justification), level + 1)
- end
- return true
- end
- function tipPrototype:SetColumnLayout(numColumns, ...)
- if type(numColumns) ~= "number" or numColumns < 1 then
- error("number of columns must be a positive number, not: " .. tostring(numColumns), 2)
- end
- for i = 1, numColumns do
- local justification = select(i, ...) or "LEFT"
- checkJustification(justification, 2)
- if self.columns[i] then
- self.columns[i].justification = justification
- else
- self:AddColumn(justification)
- end
- end
- end
- function tipPrototype:AddColumn(justification)
- justification = justification or "LEFT"
- checkJustification(justification, 2)
- local colNum = #self.columns + 1
- local column = self.columns[colNum] or AcquireFrame(self.scrollChild)
- column:SetFrameLevel(self.scrollChild:GetFrameLevel() + 1)
- column.justification = justification
- column.width = 0
- column:SetWidth(1)
- column:SetPoint("TOP", self.scrollChild)
- column:SetPoint("BOTTOM", self.scrollChild)
- if colNum > 1 then
- local h_margin = self.cell_margin_h or CELL_MARGIN_H
- column:SetPoint("LEFT", self.columns[colNum - 1], "RIGHT", h_margin, 0)
- SetTooltipSize(self, self.width + h_margin, self.height)
- else
- column:SetPoint("LEFT", self.scrollChild)
- end
- column:Show()
- self.columns[colNum] = column
- return colNum
- end
- ------------------------------------------------------------------------------
- -- Convenient methods
- ------------------------------------------------------------------------------
- function tipPrototype:Release()
- lib:Release(self)
- end
- function tipPrototype:IsAcquiredBy(key)
- return key ~= nil and self.key == key
- end
- ------------------------------------------------------------------------------
- -- Script hooks
- ------------------------------------------------------------------------------
- local RawSetScript = lib.frameMetatable.__index.SetScript
- function ClearTooltipScripts(tooltip)
- if tooltip.scripts then
- for scriptType in pairs(tooltip.scripts) do
- RawSetScript(tooltip, scriptType, nil)
- end
- tooltip.scripts = ReleaseTable(tooltip.scripts)
- end
- end
- function tipPrototype:SetScript(scriptType, handler)
- RawSetScript(self, scriptType, handler)
- if handler then
- if not self.scripts then
- self.scripts = AcquireTable()
- end
- self.scripts[scriptType] = true
- elseif self.scripts then
- self.scripts[scriptType] = nil
- end
- end
- -- That might break some addons ; those addons were breaking other
- -- addons' tooltip though.
- function tipPrototype:HookScript()
- geterrorhandler()(":HookScript is not allowed on LibQTip tooltips")
- end
- ------------------------------------------------------------------------------
- -- Scrollbar data and functions
- ------------------------------------------------------------------------------
- local sliderBackdrop = {
- bgFile = [[Interface\Buttons\UI-SliderBar-Background]],
- edgeFile = [[Interface\Buttons\UI-SliderBar-Border]],
- tile = true,
- edgeSize = 8,
- tileSize = 8,
- insets = {
- left = 3,
- right = 3,
- top = 3,
- bottom = 3
- }
- }
- local function slider_OnValueChanged(self)
- self.scrollFrame:SetVerticalScroll(self:GetValue())
- end
- local function tooltip_OnMouseWheel(self, delta)
- local slider = self.slider
- local currentValue = slider:GetValue()
- local minValue, maxValue = slider:GetMinMaxValues()
- local stepValue = self.step or 10
- if delta < 0 and currentValue < maxValue then
- slider:SetValue(min(maxValue, currentValue + stepValue))
- elseif delta > 0 and currentValue > minValue then
- slider:SetValue(max(minValue, currentValue - stepValue))
- end
- end
- -- Set the step size for the scroll bar
- function tipPrototype:SetScrollStep(step)
- self.step = step
- end
- -- will resize the tooltip to fit the screen and show a scrollbar if needed
- function tipPrototype:UpdateScrolling(maxheight)
- self:SetClampedToScreen(false)
- -- all data is in the tooltip; fix colspan width and prevent the layout cleaner from messing up the tooltip later
- FixCellSizes(self)
- layoutCleaner.registry[self] = nil
- local scale = self:GetScale()
- local topside = self:GetTop()
- local bottomside = self:GetBottom()
- local screensize = UIParent:GetHeight() / scale
- local tipsize = (topside - bottomside)
- -- if the tooltip would be too high, limit its height and show the slider
- if bottomside < 0 or topside > screensize or (maxheight and tipsize > maxheight) then
- local shrink = (bottomside < 0 and (5 - bottomside) or 0) + (topside > screensize and (topside - screensize + 5) or 0)
- if maxheight and tipsize - shrink > maxheight then
- shrink = tipsize - maxheight
- end
- self:SetHeight(2 * TOOLTIP_PADDING + self.height - shrink)
- self:SetWidth(2 * TOOLTIP_PADDING + self.width + 20)
- self.scrollFrame:SetPoint("RIGHT", self, "RIGHT", -(TOOLTIP_PADDING + 20), 0)
- if not self.slider then
- local slider = CreateFrame("Slider", nil, self)
- slider.scrollFrame = self.scrollFrame
- slider:SetOrientation("VERTICAL")
- slider:SetPoint("TOPRIGHT", self, "TOPRIGHT", -TOOLTIP_PADDING, -TOOLTIP_PADDING)
- slider:SetPoint("BOTTOMRIGHT", self, "BOTTOMRIGHT", -TOOLTIP_PADDING, TOOLTIP_PADDING)
- slider:SetBackdrop(sliderBackdrop)
- slider:SetThumbTexture([[Interface\Buttons\UI-SliderBar-Button-Vertical]])
- slider:SetMinMaxValues(0, 1)
- slider:SetValueStep(1)
- slider:SetWidth(12)
- slider:SetScript("OnValueChanged", slider_OnValueChanged)
- slider:SetValue(0)
- self.slider = slider
- end
- self.slider:SetMinMaxValues(0, shrink)
- self.slider:Show()
- self:EnableMouseWheel(true)
- self:SetScript("OnMouseWheel", tooltip_OnMouseWheel)
- else
- self:SetHeight(2 * TOOLTIP_PADDING + self.height)
- self:SetWidth(2 * TOOLTIP_PADDING + self.width)
- self.scrollFrame:SetPoint("RIGHT", self, "RIGHT", -TOOLTIP_PADDING, 0)
- if self.slider then
- self.slider:SetValue(0)
- self.slider:Hide()
- self:EnableMouseWheel(false)
- self:SetScript("OnMouseWheel", nil)
- end
- end
- end
- ------------------------------------------------------------------------------
- -- Tooltip methods for changing its contents.
- ------------------------------------------------------------------------------
- function tipPrototype:Clear()
- for i, line in ipairs(self.lines) do
- for _, cell in pairs(line.cells) do
- if cell then
- ReleaseCell(cell)
- end
- end
- ReleaseTable(line.cells)
- line.cells = nil
- line.is_header = nil
- ReleaseFrame(line)
- self.lines[i] = nil
- end
- for _, column in ipairs(self.columns) do
- column.width = 0
- column:SetWidth(1)
- end
- wipe(self.colspans)
- self.cell_margin_h = nil
- self.cell_margin_v = nil
- ResetTooltipSize(self)
- end
- function tipPrototype:SetCellMarginH(size)
- if #self.lines > 0 then
- error("Unable to set horizontal margin while the tooltip has lines.", 2)
- end
- if not size or type(size) ~= "number" or size < 0 then
- error("Margin size must be a positive number or zero.", 2)
- end
- self.cell_margin_h = size
- end
- function tipPrototype:SetCellMarginV(size)
- if #self.lines > 0 then
- error("Unable to set vertical margin while the tooltip has lines.", 2)
- end
- if not size or type(size) ~= "number" or size < 0 then
- error("Margin size must be a positive number or zero.", 2)
- end
- self.cell_margin_v = size
- end
- function SetTooltipSize(tooltip, width, height)
- tooltip.height = height
- tooltip.width = width
- tooltip:SetHeight(2 * TOOLTIP_PADDING + height)
- tooltip:SetWidth(2 * TOOLTIP_PADDING + width)
- tooltip.scrollChild:SetHeight(height)
- tooltip.scrollChild:SetWidth(width)
- end
- -- Add 2 pixels to height so dangling letters (g, y, p, j, etc) are not clipped.
- function ResetTooltipSize(tooltip)
- local h_margin = tooltip.cell_margin_h or CELL_MARGIN_H
- SetTooltipSize(tooltip, max(0, (h_margin * (#tooltip.columns - 1)) + (h_margin / 2)), 2)
- end
- local function EnlargeColumn(tooltip, column, width)
- if width > column.width then
- SetTooltipSize(tooltip, tooltip.width + width - column.width, tooltip.height)
- column.width = width
- column:SetWidth(width)
- end
- end
- local function ResizeLine(tooltip, line, height)
- SetTooltipSize(tooltip, tooltip.width, tooltip.height + height - line.height)
- line.height = height
- line:SetHeight(height)
- end
- function FixCellSizes(tooltip)
- local columns = tooltip.columns
- local colspans = tooltip.colspans
- local lines = tooltip.lines
- local h_margin = tooltip.cell_margin_h or CELL_MARGIN_H
- -- resize columns to make room for the colspans
- while next(colspans) do
- local maxNeedCols
- local maxNeedWidthPerCol = 0
- -- calculate the colspan with the highest additional width need per column
- for colRange, width in pairs(colspans) do
- local left, right = colRange:match("^(%d+)%-(%d+)$")
- left, right = tonumber(left), tonumber(right)
- for col = left, right - 1 do
- width = width - columns[col].width - h_margin
- end
- width = width - columns[right].width
- if width <= 0 then
- colspans[colRange] = nil
- else
- width = width / (right - left + 1)
- if width > maxNeedWidthPerCol then
- maxNeedCols = colRange
- maxNeedWidthPerCol = width
- end
- end
- end
- -- resize all columns for that colspan
- if maxNeedCols then
- local left, right = maxNeedCols:match("^(%d+)%-(%d+)$")
- for col = left, right do
- EnlargeColumn(tooltip, columns[col], columns[col].width + maxNeedWidthPerCol)
- end
- colspans[maxNeedCols] = nil
- end
- end
- --now that the cell width is set, recalculate the rows' height
- for _, line in ipairs(lines) do
- if #(line.cells) > 0 then
- local lineheight = 0
- for _, cell in pairs(line.cells) do
- if cell then
- lineheight = max(lineheight, cell:getContentHeight())
- end
- end
- if lineheight > 0 then
- ResizeLine(tooltip, line, lineheight)
- end
- end
- end
- end
- local function _SetCell(tooltip, lineNum, colNum, value, font, justification, colSpan, provider, ...)
- local line = tooltip.lines[lineNum]
- local cells = line.cells
- -- Unset: be quick
- if value == nil then
- local cell = cells[colNum]
- if cell then
- for i = colNum, colNum + cell._colSpan - 1 do
- cells[i] = nil
- end
- ReleaseCell(cell)
- end
- return lineNum, colNum
- end
- font = font or (line.is_header and tooltip.headerFont or tooltip.regularFont)
- -- Check previous cell
- local cell
- local prevCell = cells[colNum]
- if prevCell then
- -- There is a cell here
- justification = justification or prevCell._justification
- colSpan = colSpan or prevCell._colSpan
- -- Clear the currently marked colspan
- for i = colNum + 1, colNum + prevCell._colSpan - 1 do
- cells[i] = nil
- end
- if provider == nil or prevCell._provider == provider then
- -- Reuse existing cell
- cell = prevCell
- provider = cell._provider
- else
- -- A new cell is required
- cells[colNum] = ReleaseCell(prevCell)
- end
- elseif prevCell == nil then
- -- Creating a new cell, using meaningful defaults.
- provider = provider or tooltip.labelProvider
- justification = justification or tooltip.columns[colNum].justification or "LEFT"
- colSpan = colSpan or 1
- else
- error("overlapping cells at column " .. colNum, 3)
- end
- local tooltipWidth = #tooltip.columns
- local rightColNum
- if colSpan > 0 then
- rightColNum = colNum + colSpan - 1
- if rightColNum > tooltipWidth then
- error("ColSpan too big, cell extends beyond right-most column", 3)
- end
- else
- -- Zero or negative: count back from right-most columns
- rightColNum = max(colNum, tooltipWidth + colSpan)
- -- Update colspan to its effective value
- colSpan = 1 + rightColNum - colNum
- end
- -- Cleanup colspans
- for i = colNum + 1, rightColNum do
- local columnCell = cells[i]
- if columnCell then
- ReleaseCell(columnCell)
- elseif columnCell == false then
- error("overlapping cells at column " .. i, 3)
- end
- cells[i] = false
- end
- -- Create the cell
- if not cell then
- cell = AcquireCell(tooltip, provider)
- cells[colNum] = cell
- end
- -- Anchor the cell
- cell:SetPoint("LEFT", tooltip.columns[colNum])
- cell:SetPoint("RIGHT", tooltip.columns[rightColNum])
- cell:SetPoint("TOP", line)
- cell:SetPoint("BOTTOM", line)
- -- Store the cell settings directly into the cell
- -- That's a bit risky but is really cheap compared to other ways to do it
- cell._font, cell._justification, cell._colSpan, cell._line, cell._column = font, justification, colSpan, lineNum, colNum
- -- Setup the cell content
- local width, height = cell:SetupCell(tooltip, value, justification, font, ...)
- cell:Show()
- if colSpan > 1 then
- -- Postpone width changes until the tooltip is shown
- local colRange = colNum .. "-" .. rightColNum
- tooltip.colspans[colRange] = max(tooltip.colspans[colRange] or 0, width)
- layoutCleaner:RegisterForCleanup(tooltip)
- else
- -- Enlarge the column and tooltip if need be
- EnlargeColumn(tooltip, tooltip.columns[colNum], width)
- end
- -- Enlarge the line and tooltip if need be
- if height > line.height then
- SetTooltipSize(tooltip, tooltip.width, tooltip.height + height - line.height)
- line.height = height
- line:SetHeight(height)
- end
- if rightColNum < tooltipWidth then
- return lineNum, rightColNum + 1
- else
- return lineNum, nil
- end
- end
- do
- local function CreateLine(tooltip, font, ...)
- if #tooltip.columns == 0 then
- error("column layout should be defined before adding line", 3)
- end
- local lineNum = #tooltip.lines + 1
- local line = tooltip.lines[lineNum] or AcquireFrame(tooltip.scrollChild)
- line:SetFrameLevel(tooltip.scrollChild:GetFrameLevel() + 2)
- line:SetPoint("LEFT", tooltip.scrollChild)
- line:SetPoint("RIGHT", tooltip.scrollChild)
- if lineNum > 1 then
- local v_margin = tooltip.cell_margin_v or CELL_MARGIN_V
- line:SetPoint("TOP", tooltip.lines[lineNum - 1], "BOTTOM", 0, -v_margin)
- SetTooltipSize(tooltip, tooltip.width, tooltip.height + v_margin)
- else
- line:SetPoint("TOP", tooltip.scrollChild)
- end
- tooltip.lines[lineNum] = line
- line.cells = line.cells or AcquireTable()
- line.height = 0
- line:SetHeight(1)
- line:Show()
- local colNum = 1
- for i = 1, #tooltip.columns do
- local value = select(i, ...)
- if value ~= nil then
- lineNum, colNum = _SetCell(tooltip, lineNum, i, value, font, nil, 1, tooltip.labelProvider)
- end
- end
- return lineNum, colNum
- end
- function tipPrototype:AddLine(...)
- return CreateLine(self, self.regularFont, ...)
- end
- function tipPrototype:AddHeader(...)
- local line, col = CreateLine(self, self.headerFont, ...)
- self.lines[line].is_header = true
- return line, col
- end
- end -- do-block
- local GenericBackdrop = {
- bgFile = "Interface\\Tooltips\\UI-Tooltip-Background"
- }
- function tipPrototype:AddSeparator(height, r, g, b, a)
- local lineNum, colNum = self:AddLine()
- local line = self.lines[lineNum]
- local color = _G.NORMAL_FONT_COLOR
- height = height or 1
- SetTooltipSize(self, self.width, self.height + height)
- line.height = height
- line:SetHeight(height)
- if not line.SetBackdrop then --**
- Mixin(line, BackdropTemplateMixin)
- end
- line:SetBackdrop(GenericBackdrop)
- line:SetBackdropColor(r or color.r, g or color.g, b or color.b, a or 1)
- return lineNum, colNum
- end
- function tipPrototype:SetCellColor(lineNum, colNum, r, g, b, a)
- local cell = self.lines[lineNum].cells[colNum]
- if not cell.SetBackdrop then --**
- Mixin(cell, BackdropTemplateMixin)
- end
- if cell then
- local sr, sg, sb, sa = self:GetBackdropColor()
- cell:SetBackdrop(GenericBackdrop)
- cell:SetBackdropColor(r or sr, g or sg, b or sb, a or sa)
- end
- end
- function tipPrototype:SetColumnColor(colNum, r, g, b, a)
- local column = self.columns[colNum]
- if not colum.SetBackdrop then --**
- Mixin(colum, BackdropTemplateMixin)
- end
- if column then
- local sr, sg, sb, sa = self:GetBackdropColor()
- column:SetBackdrop(GenericBackdrop)
- column:SetBackdropColor(r or sr, g or sg, b or sb, a or sa)
- end
- end
- function tipPrototype:SetLineColor(lineNum, r, g, b, a)
- local line = self.lines[lineNum]
- if not line.SetBackdrop then
- Mixin(line, BackdropTemplateMixin)
- end
- if line then
- local sr, sg, sb, sa = self:GetBackdropColor()
- line:SetBackdrop(GenericBackdrop)
- line:SetBackdropColor(r or sr, g or sg, b or sb, a or sa)
- end
- end
- function tipPrototype:SetCellTextColor(lineNum, colNum, r, g, b, a)
- local line = self.lines[lineNum]
- local column = self.columns[colNum]
- if not line or not column then
- return
- end
- local cell = self.lines[lineNum].cells[colNum]
- if cell then
- if not cell.fontString then
- error("cell's label provider did not assign a fontString field", 2)
- end
- if not cell.r then
- cell.r, cell.g, cell.b, cell.a = cell.fontString:GetTextColor()
- end
- cell.fontString:SetTextColor(r or cell.r, g or cell.g, b or cell.b, a or cell.a)
- end
- end
- function tipPrototype:SetColumnTextColor(colNum, r, g, b, a)
- if not self.columns[colNum] then
- return
- end
- for lineIndex = 1, #self.lines do
- self:SetCellTextColor(lineIndex, colNum, r, g, b, a)
- end
- end
- function tipPrototype:SetLineTextColor(lineNum, r, g, b, a)
- local line = self.lines[lineNum]
- if not line then
- return
- end
- for cellIndex = 1, #line.cells do
- self:SetCellTextColor(lineNum, line.cells[cellIndex]._column, r, g, b, a)
- end
- end
- function tipPrototype:SetHighlightTexture(...)
- return highlightTexture:SetTexture(...)
- end
- function tipPrototype:SetHighlightTexCoord(...)
- highlightTexture:SetTexCoord(...)
- end
- do
- local function checkFont(font, level, silent)
- local bad = false
- if not font then
- bad = true
- elseif type(font) == "string" then
- local ref = _G[font]
- if not ref or type(ref) ~= "table" or type(ref.IsObjectType) ~= "function" or not ref:IsObjectType("Font") then
- bad = true
- end
- elseif type(font) ~= "table" or type(font.IsObjectType) ~= "function" or not font:IsObjectType("Font") then
- bad = true
- end
- if bad then
- if silent then
- return false
- end
- error("font must be a Font instance or a string matching the name of a global Font instance, not: " .. tostring(font), level + 1)
- end
- return true
- end
- function tipPrototype:SetFont(font)
- local is_string = type(font) == "string"
- checkFont(font, 2)
- self.regularFont = is_string and _G[font] or font
- end
- function tipPrototype:SetHeaderFont(font)
- local is_string = type(font) == "string"
- checkFont(font, 2)
- self.headerFont = is_string and _G[font] or font
- end
- -- TODO: fixed argument positions / remove checks for performance?
- function tipPrototype:SetCell(lineNum, colNum, value, ...)
- -- Mandatory argument checking
- if type(lineNum) ~= "number" then
- error("line number must be a number, not: " .. tostring(lineNum), 2)
- elseif lineNum < 1 or lineNum > #self.lines then
- error("line number out of range: " .. tostring(lineNum), 2)
- elseif type(colNum) ~= "number" then
- error("column number must be a number, not: " .. tostring(colNum), 2)
- elseif colNum < 1 or colNum > #self.columns then
- error("column number out of range: " .. tostring(colNum), 2)
- end
- -- Variable argument checking
- local font, justification, colSpan, provider
- local i, arg = 1, ...
- if arg == nil or checkFont(arg, 2, true) then
- i, font, arg = 2, ...
- end
- if arg == nil or checkJustification(arg, 2, true) then
- i, justification, arg = i + 1, select(i, ...)
- end
- if arg == nil or type(arg) == "number" then
- i, colSpan, arg = i + 1, select(i, ...)
- end
- if arg == nil or type(arg) == "table" and type(arg.AcquireCell) == "function" then
- i, provider = i + 1, arg
- end
- return _SetCell(self, lineNum, colNum, value, font, justification, colSpan, provider, select(i, ...))
- end
- end -- do-block
- function tipPrototype:GetFont()
- return self.regularFont
- end
- function tipPrototype:GetHeaderFont()
- return self.headerFont
- end
- function tipPrototype:GetLineCount()
- return #self.lines
- end
- function tipPrototype:GetColumnCount()
- return #self.columns
- end
- ------------------------------------------------------------------------------
- -- Frame Scripts
- ------------------------------------------------------------------------------
- local scripts = {
- OnEnter = function(frame, ...)
- highlightFrame:SetParent(frame)
- highlightFrame:SetAllPoints(frame)
- highlightFrame:Show()
- if frame._OnEnter_func then
- frame:_OnEnter_func(frame._OnEnter_arg, ...)
- end
- end,
- OnLeave = function(frame, ...)
- highlightFrame:Hide()
- highlightFrame:ClearAllPoints()
- highlightFrame:SetParent(nil)
- if frame._OnLeave_func then
- frame:_OnLeave_func(frame._OnLeave_arg, ...)
- end
- end,
- OnMouseDown = function(frame, ...)
- frame:_OnMouseDown_func(frame._OnMouseDown_arg, ...)
- end,
- OnMouseUp = function(frame, ...)
- frame:_OnMouseUp_func(frame._OnMouseUp_arg, ...)
- end,
- OnReceiveDrag = function(frame, ...)
- frame:_OnReceiveDrag_func(frame._OnReceiveDrag_arg, ...)
- end
- }
- function SetFrameScript(frame, script, func, arg)
- if not scripts[script] then
- return
- end
- frame["_" .. script .. "_func"] = func
- frame["_" .. script .. "_arg"] = arg
- if script == "OnMouseDown" or script == "OnMouseUp" or script == "OnReceiveDrag" then
- if func then
- frame:SetScript(script, scripts[script])
- else
- frame:SetScript(script, nil)
- end
- end
- -- if at least one script is set, set the OnEnter/OnLeave scripts for the highlight
- if frame._OnEnter_func or frame._OnLeave_func or frame._OnMouseDown_func or frame._OnMouseUp_func or frame._OnReceiveDrag_func then
- frame:EnableMouse(true)
- frame:SetScript("OnEnter", scripts.OnEnter)
- frame:SetScript("OnLeave", scripts.OnLeave)
- else
- frame:EnableMouse(false)
- frame:SetScript("OnEnter", nil)
- frame:SetScript("OnLeave", nil)
- end
- end
- function ClearFrameScripts(frame)
- if frame._OnEnter_func or frame._OnLeave_func or frame._OnMouseDown_func or frame._OnMouseUp_func or frame._OnReceiveDrag_func then
- frame:EnableMouse(false)
- frame:SetScript("OnEnter", nil)
- frame._OnEnter_func = nil
- frame._OnEnter_arg = nil
- frame:SetScript("OnLeave", nil)
- frame._OnLeave_func = nil
- frame._OnLeave_arg = nil
- frame:SetScript("OnReceiveDrag", nil)
- frame._OnReceiveDrag_func = nil
- frame._OnReceiveDrag_arg = nil
- frame:SetScript("OnMouseDown", nil)
- frame._OnMouseDown_func = nil
- frame._OnMouseDown_arg = nil
- frame:SetScript("OnMouseUp", nil)
- frame._OnMouseUp_func = nil
- frame._OnMouseUp_arg = nil
- end
- end
- function tipPrototype:SetLineScript(lineNum, script, func, arg)
- SetFrameScript(self.lines[lineNum], script, func, arg)
- end
- function tipPrototype:SetColumnScript(colNum, script, func, arg)
- SetFrameScript(self.columns[colNum], script, func, arg)
- end
- function tipPrototype:SetCellScript(lineNum, colNum, script, func, arg)
- local cell = self.lines[lineNum].cells[colNum]
- if cell then
- SetFrameScript(cell, script, func, arg)
- end
- end
- ------------------------------------------------------------------------------
- -- Auto-hiding feature
- ------------------------------------------------------------------------------
- -- Script of the auto-hiding child frame
- local function AutoHideTimerFrame_OnUpdate(self, elapsed)
- self.checkElapsed = self.checkElapsed + elapsed
- if self.checkElapsed > 0.1 then
- if self.parent:IsMouseOver() or (self.alternateFrame and self.alternateFrame:IsMouseOver()) then
- self.elapsed = 0
- else
- self.elapsed = self.elapsed + self.checkElapsed
- if self.elapsed >= self.delay then
- lib:Release(self.parent)
- end
- end
- self.checkElapsed = 0
- end
- end
- -- Usage:
- -- :SetAutoHideDelay(0.25) => hides after 0.25sec outside of the tooltip
- -- :SetAutoHideDelay(0.25, someFrame) => hides after 0.25sec outside of both the tooltip and someFrame
- -- :SetAutoHideDelay() => disable auto-hiding (default)
- function tipPrototype:SetAutoHideDelay(delay, alternateFrame, releaseHandler)
- local timerFrame = self.autoHideTimerFrame
- delay = tonumber(delay) or 0
- if releaseHandler then
- if type(releaseHandler) ~= "function" then
- error("releaseHandler must be a function", 2)
- end
- lib.onReleaseHandlers[self] = releaseHandler
- end
- if delay > 0 then
- if not timerFrame then
- timerFrame = AcquireFrame(self)
- timerFrame:SetScript("OnUpdate", AutoHideTimerFrame_OnUpdate)
- self.autoHideTimerFrame = timerFrame
- end
- timerFrame.parent = self
- timerFrame.checkElapsed = 0
- timerFrame.elapsed = 0
- timerFrame.delay = delay
- timerFrame.alternateFrame = alternateFrame
- timerFrame:Show()
- elseif timerFrame then
- self.autoHideTimerFrame = nil
- timerFrame.alternateFrame = nil
- timerFrame:SetScript("OnUpdate", nil)
- ReleaseFrame(timerFrame)
- end
- end
- ------------------------------------------------------------------------------
- -- "Smart" Anchoring
- ------------------------------------------------------------------------------
- local function GetTipAnchor(frame)
- local x, y = frame:GetCenter()
- if not x or not y then
- return "TOPLEFT", "BOTTOMLEFT"
- end
- local hhalf = (x > UIParent:GetWidth() * 2 / 3) and "RIGHT" or (x < UIParent:GetWidth() / 3) and "LEFT" or ""
- local vhalf = (y > UIParent:GetHeight() / 2) and "TOP" or "BOTTOM"
- return vhalf .. hhalf, frame, (vhalf == "TOP" and "BOTTOM" or "TOP") .. hhalf
- end
- function tipPrototype:SmartAnchorTo(frame)
- if not frame then
- error("Invalid frame provided.", 2)
- end
- self:ClearAllPoints()
- self:SetClampedToScreen(true)
- self:SetPoint(GetTipAnchor(frame))
- end
- ------------------------------------------------------------------------------
- -- Debug slashcmds
- ------------------------------------------------------------------------------
- -- @debug @
- local print = print
- local function PrintStats()
- local tipCache = tostring(#tooltipHeap)
- local frameCache = tostring(#frameHeap)
- local tableCache = tostring(#tableHeap)
- local header = false
- print("Tooltips used: " .. usedTooltips .. ", Cached: " .. tipCache .. ", Total: " .. tipCache + usedTooltips)
- print("Frames used: " .. usedFrames .. ", Cached: " .. frameCache .. ", Total: " .. frameCache + usedFrames)
- print("Tables used: " .. usedTables .. ", Cached: " .. tableCache .. ", Total: " .. tableCache + usedTables)
- for k in pairs(activeTooltips) do
- if not header then
- print("Active tooltips:")
- header = true
- end
- print("- " .. k)
- end
- end
- SLASH_LibQTip1 = "/qtip"
- _G.SlashCmdList["LibQTip"] = PrintStats
- --@end-debug@
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement