---@type detailsframework local detailsFramework = _G["DetailsFramework"] if (not detailsFramework or not DetailsFrameworkCanLoad) then return end local CreateFrame = CreateFrame local GetSpellInfo = GetSpellInfo or function(spellID) if not spellID then return nil end local si = C_Spell.GetSpellInfo(spellID) if si then return si.name, nil, si.iconID, si.castTime, si.minRange, si.maxRange, si.spellID, si.originalIconID end end local GameTooltip = GameTooltip local unpack = unpack ---mixin to use with DetailsFramework:Mixin(table, detailsFramework.SortFunctions) ---add methods to be used on scrollframes ---@class df_scrollboxmixin detailsFramework.ScrollBoxFunctions = { --set a function to run right before the refresh function (scroll:Refresh()) --this function receives the same parameters as the refresh function SetPreRefreshFunction = function(self, func) self.pre_refresh_func = func end, ---refresh the scrollbox by resetting all lines created with :CreateLine(), then calling the refresh_func which was set at :CreateScrollBox() ---@param self table ---@return table Refresh = function(self) --hide all frames and tag as not in use self._LinesInUse = 0 --self.Frames has a list of frames used by the scrollbox for index, frame in ipairs(self.Frames) do if (not self.DontHideChildrenOnPreRefresh) then frame:Hide() end --set the frame as not in use frame._InUse = nil end local offset = 0 if (self.IsFauxScroll) then self:UpdateFaux(#self.data, self.LineAmount, self.LineHeight) offset = self:GetOffsetFaux() end --before starting the refresh, check if there's a pre refresh function and call it if (self.pre_refresh_func) then detailsFramework:Dispatch(self.pre_refresh_func, self, self.data, offset, self.LineAmount) end --call the refresh function detailsFramework:Dispatch(self.refresh_func, self, self.data, offset, self.LineAmount) --hide all frames that are not in use for index, frame in ipairs(self.Frames) do --the member _InUse is true when the line is used by the refresh function --this member is set to true when the code calls scrollBox:GetLine(index) if (not frame._InUse) then frame:Hide() else frame:Show() end end self:Show() local frameName = self:GetName() if (frameName) then if (self.HideScrollBar) then local scrollBar = _G[frameName .. "ScrollBar"] if (scrollBar) then scrollBar:Hide() end else --[=[ --maybe in the future I visit this again local scrollBar = _G[frameName .. "ScrollBar"] local height = self:GetHeight() local totalLinesRequired = #self.data local linesShown = self._LinesInUse local percent = linesShown / totalLinesRequired local thumbHeight = height * percent scrollBar.ThumbTexture:SetSize(12, thumbHeight) print("thumbHeight:", thumbHeight) --]=] end end return self.Frames end, ---@param self df_scrollbox ---@param offset number ---@return boolean OnVerticalScroll = function(self, offset) self:OnVerticalScrollFaux(offset, self.LineHeight, self.Refresh) return true end, ---create a line within the scrollbox ---@param self table is the scrollbox ---@param func function|nil function to create the line object, this function will receive the line index as argument and return a table with the line object ---@return table line object (table) CreateLine = function(self, func) if (not func) then func = self.CreateLineFunc end local okay, newLine = xpcall(func, geterrorhandler(), self, #self.Frames+1) if (okay) then if (not newLine) then error("ScrollFrame:CreateLine() function did not returned a line, use: 'return line'") end table.insert(self.Frames, newLine) newLine.Index = #self.Frames return newLine end return newLine end, ---Creates multiple lines in the scroll box. ---@param self df_scrollbox The DF_ScrollBox object. ---@param callback function The callback function to be called for each line. ---@param lineAmount number The number of lines to create. CreateLines = function(self, callback, lineAmount) for i = 1, lineAmount do self:CreateLine(callback) end end, ---Retrieves a specific line from the scroll box. ---@param self df_scrollbox The DF_ScrollBox object. ---@param lineIndex number The index of the line to retrieve. ---@return frame line The line object at the specified index. GetLine = function(self, lineIndex) local line = self.Frames[lineIndex] --print(self, line, line and line:GetName(), lineIndex, self:GetName()) if (line) then line._InUse = true end self._LinesInUse = self._LinesInUse + 1 return line end, ---Sets the data for the scroll box. ---@param data table The data to be set. SetData = function(self, data) self.data = data self.data_original = data if (self.OnSetData) then detailsFramework:CoreDispatch((self:GetName() or "ScrollBox") .. ":OnSetData()", self.OnSetData, self, self.data) end end, ---Retrieves the data associated with the scrollbox. ---@param self df_scrollbox ---@return table The data associated with the scrollbox. GetData = function(self) return self.data end, ---Retrieves the frames contained within the scrollbox. ---@param self df_scrollbox ---@return table The frames contained within the scrollbox. GetFrames = function(self) return self.Frames end, ---Retrieves the lines contained within the scrollbox. ---This is an alias of GetFrames. ---@param self df_scrollbox ---@return table The lines contained within the scrollbox. GetLines = function(self) return self.Frames end, ---Retrieves the number of frames created within the scrollbox. ---@param self df_scrollbox ---@return number The number of frames created within the scrollbox. GetNumFramesCreated = function(self) return #self.Frames end, ---get the amount of lines the scroll is currently showing ---@param self df_scrollbox ---@return number amountOfLines GetNumFramesShown = function(self) return self.LineAmount end, ---set the max amount of lines the scroll can show ---@param self df_scrollbox ---@param newAmount number SetNumFramesShown = function(self, newAmount) --hide frames which won't be used if (newAmount < #self.Frames) then for i = newAmount+1, #self.Frames do self.Frames[i]:Hide() end end --set the new amount self.LineAmount = newAmount end, SetFramesHeight = function(self, height) self.LineHeight = height self:OnSizeChanged() self:Refresh() end, OnSizeChanged = function(self) if (self.ReajustNumFrames) then --how many lines the scroll can show local amountOfFramesToShow = math.floor(self:GetHeight() / self.LineHeight) --how many lines the scroll already have local totalFramesCreated = self:GetNumFramesCreated() --how many lines are current shown local totalFramesShown = self:GetNumFramesShown() --the amount of frames increased if (amountOfFramesToShow > totalFramesShown) then for i = totalFramesShown+1, amountOfFramesToShow do --check if need to create a new line if (i > totalFramesCreated) then self:CreateLine(self.CreateLineFunc) end end --the amount of frames decreased elseif (amountOfFramesToShow < totalFramesShown) then --hide all frames above the new amount to show for i = totalFramesCreated, amountOfFramesToShow, -1 do if (self.Frames[i]) then self.Frames[i]:Hide() end end end --set the new amount of frames self:SetNumFramesShown(amountOfFramesToShow) --refresh lines self:Refresh() end end, --moved functions from blizzard faux scroll that are called from insecure code environment --this reduces the amount of taints while using the faux scroll frame GetOffsetFaux = function(self) return self.offset or 0 end, OnVerticalScrollFaux = function(self, value, lineHeight, updateFunction) local scrollbar = self:GetChildFramesFaux() scrollbar:SetValue(value) self.offset = math.floor((value / lineHeight) + 0.5) if (updateFunction) then updateFunction(self) end end, GetChildFramesFaux = function(frame) local frameName = frame:GetName(); if frameName then return _G[ frameName.."ScrollBar" ], _G[ frameName.."ScrollChildFrame" ], _G[ frameName.."ScrollBarScrollUpButton" ], _G[ frameName.."ScrollBarScrollDownButton" ]; else return frame.ScrollBar, frame.ScrollChildFrame, frame.ScrollBar.ScrollUpButton, frame.ScrollBar.ScrollDownButton; end end, UpdateFaux = function(frame, numItems, numToDisplay, buttonHeight, button, smallWidth, bigWidth, highlightFrame, smallHighlightWidth, bigHighlightWidth, alwaysShowScrollBar) local scrollBar, scrollChildFrame, scrollUpButton, scrollDownButton = frame:GetChildFramesFaux(); -- If more than one screen full of items then show the scrollbar local showScrollBar; if ( numItems > numToDisplay or alwaysShowScrollBar ) then frame:Show(); showScrollBar = 1; else scrollBar:SetValue(0); frame:Hide(); end if ( frame:IsShown() ) then local scrollFrameHeight = 0; local scrollChildHeight = 0; if ( numItems > 0 ) then scrollFrameHeight = (numItems - numToDisplay) * buttonHeight; scrollChildHeight = numItems * buttonHeight; if ( scrollFrameHeight < 0 ) then scrollFrameHeight = 0; end scrollChildFrame:Show(); else scrollChildFrame:Hide(); end local maxRange = (numItems - numToDisplay) * buttonHeight; if (maxRange < 0) then maxRange = 0; end scrollBar:SetMinMaxValues(0, maxRange); scrollBar:SetValueStep(buttonHeight); scrollBar:SetStepsPerPage(numToDisplay-1); scrollChildFrame:SetHeight(scrollChildHeight); -- Arrow button handling if ( scrollBar:GetValue() == 0 ) then scrollUpButton:Disable(); else scrollUpButton:Enable(); end if ((scrollBar:GetValue() - scrollFrameHeight) == 0) then scrollDownButton:Disable(); else scrollDownButton:Enable(); end -- Shrink because scrollbar is shown if ( highlightFrame ) then highlightFrame:SetWidth(smallHighlightWidth); end if ( button ) then for i=1, numToDisplay do _G[button..i]:SetWidth(smallWidth); end end else -- Widen because scrollbar is hidden if ( highlightFrame ) then highlightFrame:SetWidth(bigHighlightWidth); end if ( button ) then for i=1, numToDisplay do _G[button..i]:SetWidth(bigWidth); end end end return showScrollBar; end, } ---@class df_gridscrollbox_options : table ---@field width number? ---@field height number? ---@field line_amount number? ---@field line_height number? ---@field columns_per_line number? ---@field auto_amount boolean? ---@field no_scroll boolean? ---@field vertical_padding number? ---@field no_backdrop boolean? ---@type df_gridscrollbox_options local grid_scrollbox_options = { width = 600, height = 400, line_amount = 10, line_height = 30, columns_per_line = 4, no_scroll = false, vertical_padding = 1, no_backdrop = false, } ---@class df_gridscrollbox : df_scrollbox ---@field RefreshMe fun(self:df_gridscrollbox) ---create a scrollbox with a grid layout ---@param parent frame ---@param name string ---@param refreshFunc fun(button:frame, data:table) ---@param data table ---@param createColumnFrameFunc fun(line:frame, lineIndex:number, columnIndex:number) ---@param options df_gridscrollbox_options? ---@return df_gridscrollbox function detailsFramework:CreateGridScrollBox(parent, name, refreshFunc, data, createColumnFrameFunc, options) options = options or {} --check values passed, get defaults and cast values due to the scrollbox require some values to be numbers local width = type(options.width) == "number" and options.width or grid_scrollbox_options.width ---@cast width number local height = type(options.height) == "number" and options.height or grid_scrollbox_options.height ---@cast height number local lineAmount = type(options.line_amount) == "number" and options.line_amount or grid_scrollbox_options.line_amount ---@cast lineAmount number local lineHeight = type(options.line_height) == "number" and options.line_height or grid_scrollbox_options.line_height ---@cast lineHeight number local columnsPerLine = options.columns_per_line or grid_scrollbox_options.columns_per_line local autoAmount = options.auto_amount local noScroll = options.no_scroll local noBackdrop = options.no_backdrop local verticalPadding = options.vertical_padding or grid_scrollbox_options.vertical_padding local createLineFunc = function(scrollBox, lineIndex) local line = CreateFrame("frame", "$parentLine" .. lineIndex, scrollBox) line:SetSize(width, lineHeight) line:SetPoint("top", scrollBox, "top", 0, -((lineIndex-1) * (lineHeight + verticalPadding))) line.optionFrames = {} for columnIndex = 1, columnsPerLine do --dispatch payload: line, lineIndex, columnIndex local optionFrame = createColumnFrameFunc(line, lineIndex, columnIndex) line.optionFrames[columnIndex] = optionFrame optionFrame:SetPoint("left", line, "left", (columnIndex-1) * (width/columnsPerLine), 0) end return line end local onSetData = function(self, data) self.data_original = data local newData = {} for i = 1, #data, columnsPerLine do local thisColumnData = {} for o = 1, columnsPerLine do local index = i + (o-1) local thisData = data[index] if (thisData) then thisColumnData[#thisColumnData+1] = thisData end end newData[#newData+1] = thisColumnData end self.data = newData end local refreshGrid = function(scrollBox, thisData, offset, totalLines) for i = 1, totalLines do local index = i + offset local lineData = thisData[index] if (lineData) then local line = scrollBox:GetLine(i) for o = 1, columnsPerLine do local optionFrame = line.optionFrames[o] local data = lineData[o] if (data) then detailsFramework:Dispatch(refreshFunc, optionFrame, data) optionFrame:Show() line:Show() else optionFrame:Hide() end end end end end if (not name) then name = "DetailsFrameworkAuraScrollBox" .. math.random(1, 9999999) end local scrollBox = detailsFramework:CreateScrollBox(parent, name, refreshGrid, data, width, height, lineAmount, lineHeight, createLineFunc, autoAmount, noScroll, noBackdrop) scrollBox:CreateLines(createLineFunc, lineAmount) detailsFramework:ReskinSlider(scrollBox) scrollBox.OnSetData = onSetData onSetData(scrollBox, data) ---@cast scrollBox df_gridscrollbox return scrollBox end ---@class df_gridscrollbox_menu : df_gridscrollbox ---@field data_original table the data passed into :SetData() ---@field searchBox df_searchbox ---@field Select fun(self:df_gridscrollbox_menu, value:any, key:string) --select a line by a value on a key, example: :Select("Power Infusion", "spellName") ---create a scrollbox with a grid layout to be used as a menu ---@param parent frame ---@param name string? ---@param refreshMeFunc fun(gridScrollBox:df_gridscrollbox, searchText:string) ---@param refreshButtonFunc fun(button:button, data:table) ---@param clickFunc fun(button:button, data:table) ---@param onCreateButton fun(button:button, lineIndex:number, columnIndex:number) ---@param gridScrollBoxOptions df_gridscrollbox_options ---@return df_gridscrollbox_menu function detailsFramework:CreateMenuWithGridScrollBox(parent, name, refreshMeFunc, refreshButtonFunc, clickFunc, onCreateButton, gridScrollBoxOptions) local dataSelected = nil local gridScrollBox local onClickButtonSelectorButton = function(blizzButton, buttonDown, dfButton, data) dataSelected = data gridScrollBox:Refresh() xpcall(clickFunc, geterrorhandler(), dfButton, data) end --create a search bar to filter the auras local searchText = "" local onSearchTextChangedCallback = function(self, ...) local text = self:GetText() searchText = string.lower(text) dataSelected = nil gridScrollBox:RefreshMe() end local searchBox = detailsFramework:CreateSearchBox(parent, onSearchTextChangedCallback) ---when the scroll is refreshing the line, the line will call this function for each selection button on it ---@param button df_button ---@param data table local refreshLine = function(button, data) button.data = data if (data.tooltip) then button.tooltip = data.tooltip end --set what happen when the user clicks the button button:SetClickFunction(onClickButtonSelectorButton, button, data) if (button.data == dataSelected) then button.widget:SetBorderCornerColor(.9, .9, .9) else button.widget:SetBorderCornerColor(unpack(gridScrollBoxOptions.roundedFramePreset.border_color)) end xpcall(refreshButtonFunc, geterrorhandler(), button, data) end --create a line local createButton = function(line, lineIndex, columnIndex) local width = gridScrollBoxOptions.width / gridScrollBoxOptions.columns_per_line - 5 local height = gridScrollBoxOptions.line_height if (not height) then height = 30 end local button = detailsFramework:CreateButton(line, onClickButtonSelectorButton, width, height) detailsFramework:AddRoundedCornersToFrame(button.widget, gridScrollBoxOptions.roundedFramePreset) button.textsize = 11 line.button = button button:SetHook("OnEnter", function(self) local dfButton = self:GetObject() GameCooltip:Reset() if (dfButton.spellId) then GameCooltip:SetSpellByID(dfButton.spellId) GameCooltip:SetOwner(self) GameCooltip:Show() end self:SetBorderCornerColor(.9, .9, .9) end) button:SetHook("OnLeave", function(self) GameCooltip:Hide() local dfButton = self:GetObject() if (dfButton.data == dataSelected) then self:SetBorderCornerColor(.9, .9, .9) else self:SetBorderCornerColor(unpack(gridScrollBoxOptions.roundedFramePreset.border_color)) end end) xpcall(onCreateButton, geterrorhandler(), button, lineIndex, columnIndex) return button end gridScrollBox = detailsFramework:CreateGridScrollBox(parent, name, refreshLine, {}, createButton, gridScrollBoxOptions) ---@cast gridScrollBox df_gridscrollbox_menu gridScrollBox:SetBackdrop({}) gridScrollBox:SetBackdropColor(0, 0, 0, 0) gridScrollBox:SetBackdropBorderColor(0, 0, 0, 0) gridScrollBox.__background:Hide() gridScrollBox:Show() gridScrollBox.searchBox = searchBox searchBox:SetPoint("bottomleft", gridScrollBox, "topleft", 0, 2) searchBox:SetWidth(gridScrollBoxOptions.width) function gridScrollBox:Select(value, key) local bFoundResult = false local originalData for _, data in ipairs(gridScrollBox.data_original) do originalData = data if (type(value) == string) then value = value:lower() local dataValue = data[key]:lower() if (dataValue == value) then dataSelected = originalData bFoundResult = true break end else if (data[key] == value) then dataSelected = originalData bFoundResult = true break end end end if (bFoundResult) then for _, line in ipairs(gridScrollBox:GetFrames()) do local button = line.button if (button.data == originalData) then gridScrollBox:Refresh() onClickButtonSelectorButton(nil, nil, button, originalData) break end end end end function gridScrollBox:RefreshMe() xpcall(refreshMeFunc, geterrorhandler(), gridScrollBox, searchBox:GetText()) end return gridScrollBox end --Need to test this and check the "same_name_spells_add(value)" on the OnEnter function --also need to make sure this can work with any data (global, class, spec) and aura type (buff, debuff) --aura scroll box ---@class df_aurascrollbox_options : table ---@field line_height number? ---@field line_amount number? ---@field width number? ---@field height number? ---@field vertical_padding number? ---@field show_spell_tooltip boolean ---@field remove_icon_border boolean ---@field no_scroll boolean ---@field no_backdrop boolean ---@field backdrop_onenter number[]? ---@field backdrop_onleave number[]? ---@field font_size number? ---@field title_text string? local auraScrollDefaultSettings = { line_height = 18, line_amount = 18, width = 300, height = 500, vertical_padding = 1, show_spell_tooltip = false, remove_icon_border = true, no_scroll = false, no_backdrop = false, backdrop_onenter = {.8, .8, .8, 0.4}, backdrop_onleave = {.8, .8, .8, 0.2}, font_size = 12, title_text = "", } ---@param parent frame ---@param name string? ---@param data table? --can be set later with :SetData() ---@param onAuraRemoveCallback function? ---@param options df_aurascrollbox_options? ---@param onSetupAuraClick function? function detailsFramework:CreateAuraScrollBox(parent, name, data, onAuraRemoveCallback, options, onSetupAuraClick) --hack the construction of the options table here, as the scrollbox is created much later options = options or {} local scrollOptions = {} detailsFramework.OptionsFunctions.BuildOptionsTable(scrollOptions, auraScrollDefaultSettings, options) options = scrollOptions.options local refreshAuraLines = function(self, data, offset, totalLines) for i = 1, totalLines do local index = i + offset local auraTable = data[index] if (auraTable) then local line = self:GetLine(i) local spellId, spellName, spellIcon, lowerSpellName, bAddedBySpellName = unpack(auraTable) line.SpellID = spellId line.SpellName = spellName line.SpellNameLower = lowerSpellName line.SpellIcon = spellIcon line.Flag = bAddedBySpellName if (bAddedBySpellName) then line.name:SetText(spellName) else line.name:SetText(spellName .. " (" .. spellId .. ")") end line.icon:SetTexture(spellIcon) if (options.remove_icon_border) then line.icon:SetTexCoord(.1, .9, .1, .9) else line.icon:SetTexCoord(0, 1, 0, 1) end end end end local onLeaveAuraLine = function(self) self:SetBackdropColor(unpack(options.backdrop_onleave)) GameTooltip:Hide() GameCooltip:Hide() end local onEnterAuraLine = function(line) if (options.show_spell_tooltip and line.SpellID and GetSpellInfo(line.SpellID)) then GameTooltip:SetOwner(line, "ANCHOR_CURSOR") GameTooltip:SetSpellByID(line.SpellID) GameTooltip:AddLine(" ") GameTooltip:Show() end if (line.setupbutton:IsShown()) then end line:SetBackdropColor(unpack(options.backdrop_onenter)) local bTrackByName = line.Flag --the user entered the spell name to track the spell (and not a spellId) local spellId = line.SpellID if (bTrackByName) then --the user entered the spell name to track the spell local spellsHashMap, spellsIndexTable, spellsWithSameName = detailsFramework:GetSpellCaches() if (spellsWithSameName) then local spellName, _, spellIcon = GetSpellInfo(spellId) if (spellName) then local spellNameLower = spellName:lower() local sameNameSpells = spellsWithSameName[spellNameLower] if (sameNameSpells) then GameCooltip:Preset(2) GameCooltip:SetOwner(line, "left", "right", 2, 0) GameCooltip:SetOption("TextSize", 10) for i, thisSpellId in ipairs(sameNameSpells) do GameCooltip:AddLine(spellName .. " (" .. thisSpellId .. ")") GameCooltip:AddIcon(spellIcon, 1, 1, 14, 14, .1, .9, .1, .9) end GameCooltip:Show() end end end else --the user entered the spellId to track the spell GameCooltip:Preset(2) GameCooltip:SetOwner(line, "left", "right", 2, 0) GameCooltip:SetOption("TextSize", 10) local spellName, _, spellIcon = GetSpellInfo(spellId) if (spellName) then GameCooltip:AddLine(spellName .. " (" .. spellId .. ")") GameCooltip:AddIcon(spellIcon, 1, 1, 14, 14, .1, .9, .1, .9) end GameCooltip:Show() end end local onClickAuraRemoveButton = function(self) local spellId = tonumber(self:GetParent().SpellID) if (spellId and type(spellId) == "number") then --button > line > scrollbox local scrollBox = self:GetParent():GetParent() scrollBox.data_original[spellId] = nil scrollBox.data_original["" .. (spellId or "")] = nil -- cleanup... scrollBox:TransformAuraData() scrollBox:Refresh() if (onAuraRemoveCallback) then --upvalue detailsFramework:QuickDispatch(onAuraRemoveCallback, spellId) end end end local createLineFunc = function(self, index) local line = CreateFrame("button", "$parentLine" .. index, self, "BackdropTemplate") local scrollBoxWidth = options.width local lineHeight = options.line_height local verticalPadding = options.vertical_padding line:SetPoint("topleft", self, "topleft", 1, -((index-1) * (lineHeight + verticalPadding)) - 1) line:SetSize(scrollBoxWidth - 2, lineHeight) line:SetScript("OnEnter", onEnterAuraLine) line:SetScript("OnLeave", onLeaveAuraLine) line:SetBackdrop({bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true}) line:SetBackdropColor(unpack(options.backdrop_onleave)) local iconTexture = line:CreateTexture("$parentIcon", "overlay") iconTexture:SetSize(lineHeight - 2, lineHeight - 2) local spellNameFontString = line:CreateFontString("$parentName", "overlay", "GameFontNormal") detailsFramework:SetFontSize(spellNameFontString, options.font_size) local removeButton = CreateFrame("button", "$parentRemoveButton", line, "UIPanelCloseButton") removeButton:SetSize(16, 16) removeButton:SetScript("OnClick", onClickAuraRemoveButton) removeButton:SetPoint("topright", line, "topright", 0, 0) removeButton:GetNormalTexture():SetDesaturated(true) local setupAuraButton = CreateFrame("button", "$parentSetupButton", line) setupAuraButton:SetSize(16, 16) setupAuraButton:SetPoint("right", removeButton, "left", -4, 0) setupAuraButton:SetScript("OnClick", onSetupAuraClick) line:SetScript("OnMouseUp", function(self, button) if (onSetupAuraClick) then setupAuraButton:Click() end end) local clickToSetupText = setupAuraButton:CreateFontString("$parentText", "overlay", "GameFontNormal") clickToSetupText:SetText("click to setup") clickToSetupText:SetPoint("right", setupAuraButton, "left", -2, 0) detailsFramework:SetFontSize(clickToSetupText, 9) local setupAuraTexture = setupAuraButton:CreateTexture(nil, "overlay") setupAuraTexture:SetAllPoints() setupAuraTexture:SetTexture([[Interface\ICONS\INV_Misc_Wrench_01.blp]]) setupAuraTexture:SetTexCoord(0.1, 0.9, 0.1, 0.9) setupAuraButton.Texture = setupAuraTexture setupAuraButton.Text = clickToSetupText if (not onSetupAuraClick) then setupAuraButton:Hide() end iconTexture:SetPoint("left", line, "left", 2, 0) spellNameFontString:SetPoint("left", iconTexture, "right", 3, 0) line.icon = iconTexture line.name = spellNameFontString line.removebutton = removeButton line.setupbutton = setupAuraButton line.clicktosetuptext = clickToSetupText return line end ---@class df_aurascrollbox : df_scrollbox ---@field data_original table ---@field refresh_original function ---@field TitleLabel fontstring ---@field TransformAuraData fun(self:df_aurascrollbox) ---@field GetTitleFontString fun(self:df_aurascrollbox): fontstring data = data or {} if (not name) then name = "DetailsFrameworkAuraScrollBox" .. math.random(1, 9999999) end local auraScrollBox = detailsFramework:CreateScrollBox(parent, name, refreshAuraLines, data, options.width, options.height, options.line_amount, options.line_height) detailsFramework:ReskinSlider(auraScrollBox) ---@cast auraScrollBox df_aurascrollbox auraScrollBox.data_original = data local titleLabel = auraScrollBox:CreateFontString("$parentTitleLabel", "overlay", "GameFontNormal") titleLabel:SetPoint("bottomleft", auraScrollBox, "topleft", 0, 2) detailsFramework:SetFontColor(titleLabel, "silver") detailsFramework:SetFontSize(titleLabel, 10) auraScrollBox.TitleLabel = titleLabel function auraScrollBox:GetTitleFontString() return self.TitleLabel end for i = 1, options.line_amount do auraScrollBox:CreateLine(createLineFunc) end function auraScrollBox:TransformAuraData() local newData = {} local added = {} for spellId, bAddedBySpellName in pairs(self.data_original) do local spellName, _, spellIcon = GetSpellInfo(spellId) if (spellName and not added[tonumber(spellId) or 0]) then local lowerSpellName = spellName:lower() table.insert(newData, {spellId, spellName, spellIcon, lowerSpellName, bAddedBySpellName}) added[tonumber(spellId) or 0] = true end end table.sort(newData, function(t1, t2) return t1[4] < t2[4] end) self.data = newData end auraScrollBox.SetData = function(self, data) self.data_original = data self.data = data auraScrollBox:TransformAuraData() end auraScrollBox.GetData = function(self) return self.data_original end auraScrollBox.refresh_original = auraScrollBox.Refresh auraScrollBox.Refresh = function() auraScrollBox:TransformAuraData() auraScrollBox:refresh_original() end auraScrollBox:SetData(data) return auraScrollBox end detailsFramework.CanvasScrollBoxMixin = { SetScrollSpeed = function(self, speed) assert(type(speed) == "number", "CanvasScrollBox:SetScrollSpeed(speed): speed must be a number.") self.scrollStep = speed end, GetScrollSpeed = function(self) return self.scrollStep end, OnVerticalScroll = function(self, delta) local scrollStep = self:GetScrollSpeed() if (delta > 0) then self:SetVerticalScroll(math.max(self:GetVerticalScroll() - scrollStep, 0)) else self:SetVerticalScroll(math.min(self:GetVerticalScroll() + scrollStep, self:GetVerticalScrollRange())) end end, } local canvasScrollBoxDefaultOptions = { width = 600, height = 400, reskin_slider = true, } ---@class df_canvasscrollbox : scrollframe, df_optionsmixin ---@field child frame ---@param parent frame ---@param child frame? ---@param name string? ---@param options table? ---@return df_canvasscrollbox function detailsFramework:CreateCanvasScrollBox(parent, child, name, options) ---@type df_canvasscrollbox local canvasScrollBox = CreateFrame("scrollframe", name or ("DetailsFrameworkCanvasScroll" .. math.random(50000, 10000000)), parent, "BackdropTemplate, UIPanelScrollFrameTemplate") canvasScrollBox.scrollStep = 20 canvasScrollBox.minValue = 0 canvasScrollBox:SetScript("OnMouseWheel", detailsFramework.CanvasScrollBoxMixin.OnVerticalScroll) detailsFramework:Mixin(canvasScrollBox, detailsFramework.CanvasScrollBoxMixin) detailsFramework:Mixin(canvasScrollBox, detailsFramework.OptionsFunctions) options = options or {} canvasScrollBox:BuildOptionsTable(canvasScrollBoxDefaultOptions, options) canvasScrollBox:SetSize(canvasScrollBox.options.width, canvasScrollBox.options.height) if (not child) then child = CreateFrame("frame", "$parentChild", canvasScrollBox) end canvasScrollBox:SetScrollChild(child) canvasScrollBox:EnableMouseWheel(true) canvasScrollBox.child = child if (canvasScrollBox.options.reskin_slider) then detailsFramework:ReskinSlider(canvasScrollBox) end return canvasScrollBox end