From dad21cf8f19b1aeae25f7380674d6a24d1666fde Mon Sep 17 00:00:00 2001 From: Antonio Lobato Date: Sat, 7 Sep 2024 14:44:08 -0700 Subject: [PATCH] New Config and More (#691) * Brand new configuration screen and user experience. --- BetterBags.toc | 9 +- BetterBags_Cata.toc | 10 +- BetterBags_Vanilla.toc | 10 +- annotations.lua | 17 + config/bags.lua | 410 ---------------------- config/classic/bags.lua | 386 --------------------- config/config.lua | 669 +++++++++++++++++++++++++----------- config/customcat.lua | 110 ------ config/era/bags.lua | 386 --------------------- config/era/config.lua | 98 ------ config/help.lua | 162 --------- core/constants.lua | 15 + core/database.lua | 4 + core/init.lua | 16 + data/refresh.lua | 13 +- forms/form.lua | 153 +++++++++ forms/layouts/layout.lua | 80 +++++ forms/layouts/stacked.lua | 504 +++++++++++++++++++++++++++ forms/layouts/twocolumn.lua | 4 + themes/gw2.lua | 12 +- 20 files changed, 1292 insertions(+), 1776 deletions(-) delete mode 100644 config/bags.lua delete mode 100644 config/classic/bags.lua delete mode 100644 config/customcat.lua delete mode 100644 config/era/bags.lua delete mode 100644 config/era/config.lua delete mode 100644 config/help.lua create mode 100644 forms/form.lua create mode 100644 forms/layouts/layout.lua create mode 100644 forms/layouts/stacked.lua create mode 100644 forms/layouts/twocolumn.lua diff --git a/BetterBags.toc b/BetterBags.toc index 6408eae4..def2785e 100644 --- a/BetterBags.toc +++ b/BetterBags.toc @@ -77,6 +77,12 @@ util\bucket.lua util\windowgroup.lua util\movementflow.lua +forms\layouts\layout.lua +forms\layouts\stacked.lua +forms\layouts\twocolumn.lua + +forms\form.lua + frames\searchcategory.lua frames\question.lua frames\search.lua @@ -111,9 +117,6 @@ themes\gw2.lua themes\elvui.lua config\config.lua -config\help.lua -config\bags.lua -config\customcat.lua integrations\consoleport.lua integrations\pawn.lua diff --git a/BetterBags_Cata.toc b/BetterBags_Cata.toc index d94c0be2..1344e0f2 100644 --- a/BetterBags_Cata.toc +++ b/BetterBags_Cata.toc @@ -79,6 +79,12 @@ util\bucket.lua util\windowgroup.lua util\movementflow.lua +forms\layouts\layout.lua +forms\layouts\stacked.lua +forms\layouts\twocolumn.lua + +forms\form.lua + frames\searchcategory.lua frames\question.lua frames\search.lua @@ -119,10 +125,6 @@ themes\gw2.lua themes\elvui.lua config\config.lua -config\era\config.lua -config\help.lua -config\classic\bags.lua -config\customcat.lua integrations\consoleport.lua integrations\pawn.lua diff --git a/BetterBags_Vanilla.toc b/BetterBags_Vanilla.toc index 98253345..f6126bbb 100644 --- a/BetterBags_Vanilla.toc +++ b/BetterBags_Vanilla.toc @@ -79,6 +79,12 @@ util\bucket.lua util\windowgroup.lua util\movementflow.lua +forms\layouts\layout.lua +forms\layouts\stacked.lua +forms\layouts\twocolumn.lua + +forms\form.lua + frames\searchcategory.lua frames\question.lua frames\search.lua @@ -119,10 +125,6 @@ themes\gw2.lua themes\elvui.lua config\config.lua -config\era\config.lua -config\help.lua -config\era\bags.lua -config\customcat.lua integrations\consoleport.lua integrations\pawn.lua diff --git a/annotations.lua b/annotations.lua index 6e15a69f..07271417 100644 --- a/annotations.lua +++ b/annotations.lua @@ -113,6 +113,14 @@ MainMenuBarBackpackButton = {} ---@class BagBarExpandToggle: Button BagBarExpandToggle = {} +---@class DropdownButton: Button +local dropdownButton = {} + +---@param setupFunction fun(dropdown: DropdownButton, root: table) +function dropdownButton:SetupMenu(setupFunction) end + +function dropdownButton:GenerateMenu() end + ---@class BetterBagsDebugListButton: Button ---@field RowNumber FontString ---@field Category FontString @@ -228,6 +236,14 @@ function WowScrollBox:GetUpperShadowTexture() end ---@return Texture function WowScrollBox:GetLowerShadowTexture() end function WowScrollBox:SetDataProvider(provider) end +function WowScrollBox:ScrollToOffset(offset) end +function WowScrollBox:RegisterCallback(event, callback) end +---@return number +function WowScrollBox:GetDerivedScrollOffset() end +---@return number +function WowScrollBox:GetDerivedScrollRange() end +---@param percent number +function WowScrollBox:SetScrollPercentage(percent) end ---@class InputScrollFrameTemplate: Frame ---@field EditBox EditBox @@ -303,6 +319,7 @@ local frameProto = {} ---@class MinimalScrollBar: Frame local MinimalScrollBar = {} function MinimalScrollBar:SetInterpolateScroll(interpolate) end +function MinimalScrollBar:SetHideIfUnscrollable(hide) end ---@class EventFrame local EventFrame = {} diff --git a/config/bags.lua b/config/bags.lua deleted file mode 100644 index dc8ac5ba..00000000 --- a/config/bags.lua +++ /dev/null @@ -1,410 +0,0 @@ ----@diagnostic disable: duplicate-set-field,duplicate-doc-field,duplicate-doc-alias -local addonName = ... ---@type string - ----@class BetterBags: AceAddon -local addon = LibStub('AceAddon-3.0'):GetAddon(addonName) - ----@class Localization: AceModule -local L = addon:GetModule('Localization') - ----@class Database: AceModule -local DB = addon:GetModule('Database') - ----@class Constants: AceModule -local const = addon:GetModule('Constants') - ----@class Bucket: AceModule -local bucket = addon:GetModule('Bucket') - ----@class Categories: AceModule -local categories = addon:GetModule('Categories') - ----@class Events: AceModule -local events = addon:GetModule('Events') - ----@class Items: AceModule -local items = addon:GetModule('Items') - ----@class Themes: AceModule -local themes = addon:GetModule('Themes') - ----@class Config: AceModule -local config = addon:GetModule('Config') - ----@class Context: AceModule -local context = addon:GetModule('Context') - ----@param kind BagKind ----@return AceConfig.OptionsTable -function config:GetCustomCategoryOptions(kind) - _ = kind - return { - type = "group", - name = L:G("Custom Categories"), - order = -1, - inline = true, - args = { - noCategories = { - type = "description", - name = L:G("Configure custom categories in the Configure Categories window from the bag menu."), - order = 1, - } - } - } -end - ----@param kind BagKind ----@return AceConfig.OptionsTable -function config:GetBagOptions(kind) - ---@type AceConfig.OptionsTable - local options = { - type = "group", - name = kind == const.BAG_KIND.BACKPACK and L:G("Backpack") or L:G("Bank"), - args = { - - categories = { - type = "group", - name = L:G("Categories"), - order = 0, - args = { - categories = { - type = "multiselect", - name = L:G("Categories"), - desc = L:G("Select which categories to show in this bag. If an option is checked, items that belong to the checked category will be put into a section for that category."), - order = 1, - get = function(_, value) - return DB:GetCategoryFilter(kind, value) - end, - set = function(_, value) - DB:SetCategoryFilter(kind, value, not DB:GetCategoryFilter(kind, value)) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - values = { - ["RecentItems"] = L:G("Recent Items"), - ["Type"] = L:G("Type"), - ["Subtype"] = L:G("Subtype"), - ["Expansion"] = L:G("Expansion"), - ["TradeSkill"] = L:G("Trade Skill"), - ["GearSet"] = L:G("Gear Set"), - ["EquipmentLocation"] = L:G("Equipment Location"), - } - }, - customCategories = config:GetCustomCategoryOptions(kind), - } - }, - - sectionSorting = { - type = "select", - name = L:G("Section Sorting"), - desc = L:G("Select how sections should be sorted."), - order = 4, - style = "radio", - get = function() - return DB:GetSectionSortType(kind, DB:GetBagView(kind)) - end, - set = function(_, value) - DB:SetSectionSortType(kind, DB:GetBagView(kind), value) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - values = { - [const.SECTION_SORT_TYPE.ALPHABETICALLY] = L:G("Alphabetically"), - [const.SECTION_SORT_TYPE.SIZE_DESCENDING] = L:G("Size Descending"), - [const.SECTION_SORT_TYPE.SIZE_ASCENDING] = L:G("Size Ascending"), - } - }, - - itemSorting = { - type = "select", - name = L:G("Item Sorting"), - desc = L:G("Select how items should be sorted."), - order = 5, - style = "radio", - get = function() - return DB:GetItemSortType(kind, DB:GetBagView(kind)) - end, - set = function(_, value) - DB:SetItemSortType(kind, DB:GetBagView(kind), value) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - values = { - [const.ITEM_SORT_TYPE.QUALITY_THEN_ALPHABETICALLY] = L:G("Quality, then Alphabetically"), - [const.ITEM_SORT_TYPE.ALPHABETICALLY_THEN_QUALITY] = L:G("Alphabetically, then Quality"), - [const.ITEM_SORT_TYPE.ITEM_LEVEL] = L:G("Item Level"), - } - }, - newItems = { - type = "group", - name = L:G("New Items"), - order = 6, - inline = true, - args = { - markRecentItems = { - type = "toggle", - name = L:G("Incoming items are recent"), - desc = L:G("If enabled, all new items added to the bag will be marked as recent, i.e. bank -> backpack."), - order = 1, - get = function() - return DB:GetMarkRecentItems(kind) - end, - set = function(_, value) - DB:SetMarkRecentItems(kind, value) - end, - }, - showNewItemFlash = { - type = "toggle", - name = L:G("Flash new items in stacks"), - desc = L:G("If enabled, stacks that receive new items will flash to indicate the addition of a new item."), - order = 2, - get = function() - return DB:GetShowNewItemFlash(kind) - end, - set = function(_, value) - DB:SetShowNewItemFlash(kind, value) - end, - }, - } - }, - stacking = { - type = "group", - name = L:G("Stacking"), - order = 7, - inline = true, - args = { - mergeStacks = { - type = "toggle", - name = L:G("Merge Stacks"), - desc = L:G("Merge stacks of the same item into a single stack, i.e. 20x [Linen Cloth] + 20x [Linen Cloth] = 40x [Linen Cloth] in one stack."), - order = 1, - get = function() - return DB:GetStackingOptions(kind).mergeStacks - end, - set = function(_, value) - DB:SetMergeItems(kind, value) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - }, - mergeUnstackable = { - type = "toggle", - name = L:G("Merge Unstackable"), - desc = L:G("Merge unstackable items of the same kind into a single stack, such as armors, bags, etc."), - order = 2, - get = function() - return DB:GetStackingOptions(kind).mergeUnstackable - end, - set = function(_, value) - DB:SetMergeUnstackable(kind, value) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - }, - unmergeAtShop = { - type = "toggle", - name = L:G("Unmerge at Shop"), - desc = L:G("Unmerge all items when visiting a vendor."), - order = 3, - get = function() - return DB:GetStackingOptions(kind).unmergeAtShop - end, - set = function(_, value) - DB:SetUnmergeAtShop(kind, value) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - }, - dontMergePartial = { - type = "toggle", - name = L:G("Don't Merge Partial"), - desc = L:G("Don't merge stacks of items that aren't full stacks."), - order = 3, - get = function() - return DB:GetStackingOptions(kind).dontMergePartial - end, - set = function(_, value) - DB:SetDontMergePartial(kind, value) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - }, - dontMergeTransmog = { - type = "toggle", - name = L:G("Don't Merge Transmog"), - desc = L:G("Don't merge stacks of items that have different transmogs on them."), - order = 3, - get = function() - return DB:GetStackingOptions(kind).dontMergeTransmog - end, - set = function(_, value) - DB:SetDontMergeTransmog(kind, value) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - }, - } - }, - itemLevel = { - type = "group", - name = L:G("Item Level"), - order = 8, - inline = true, - args = { - enabled = { - type = "toggle", - name = L:G("Enabled"), - desc = L:G("Show the item level of items in this bag."), - order = 1, - get = function() - return DB:GetItemLevelOptions(kind).enabled - end, - set = function(_, value) - DB:SetItemLevelEnabled(kind, value) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - }, - color = { - type = "toggle", - name = L:G("Color"), - desc = L:G("Color the item level text based on the item's quality."), - order = 2, - get = function() - return DB:GetItemLevelOptions(kind).color - end, - set = function(_, value) - DB:SetItemLevelColorEnabled(kind, value) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - }, - } - }, - display = { - type = "group", - name = L:G("Display"), - order = 10, - inline = true, - args = { - showFullSectionNames = { - type = "toggle", - name = L:G("Show Full Section Names"), - desc = L:G("Show the full section in the bag window without truncating it with '...'"), - order = 0, - width = "full", - get = function() - return DB:GetShowFullSectionNames(kind) - end, - set = function(_, value) - DB:SetShowFullSectionNames(kind, value) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - }, - showAllFreeSpace = { - type = "toggle", - name = L:G("Show All Free Space Slots"), - desc = L:G("Show all free space slots in the bag window."), - order = 1, - width = "full", - get = function() - return DB:GetShowAllFreeSpace(kind) - end, - set = function(_, value) - DB:SetShowAllFreeSpace(kind, value) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - }, - showExtraGlowyButtons = { - type = "toggle", - name = L:G("Use Extra Glowy Item Buttons"), - desc = L:G("Use extra glowy item buttons for items in this bag."), - order = 2, - width = "full", - get = function() - return DB:GetExtraGlowyButtons(kind) - end, - set = function(_, value) - DB:SetExtraGlowyButtons(kind, value) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - }, - itemsPerRow = { - type = "range", - name = L:G("Items Per Row"), - desc = L:G("Set the number of items per row in this bag."), - order = 3, - min = 3, - max = 20, - step = 1, - get = function() - return DB:GetBagSizeInfo(kind, DB:GetBagView(kind)).itemsPerRow > 20 and 20 or DB:GetBagSizeInfo(kind, DB:GetBagView(kind)).itemsPerRow - end, - set = function(_, value) - DB:SetBagViewSizeItems(kind, DB:GetBagView(kind), value) - bucket:Later("setItemsPerRow", 0.2, function() - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end) - end, - }, - opacity = { - type = "range", - name = L:G("Opacity"), - desc = L:G("Set the opacity of this bag."), - order = 4, - min = 60, - max = 100, - step = 1, - get = function() - return DB:GetBagSizeInfo(kind, DB:GetBagView(kind)).opacity - end, - set = function(_, value) - DB:SetBagViewSizeOpacity(kind, DB:GetBagView(kind), value) - themes:UpdateOpacity() - end, - }, - sectionsPerRow = { - type = "range", - name = L:G("Columns"), - desc = L:G("Set the number of columns sections will fit into."), - order = 5, - min = 1, - max = 20, - step = 1, - get = function() - return DB:GetBagSizeInfo(kind, DB:GetBagView(kind)).columnCount > 20 and 20 or DB:GetBagSizeInfo(kind, DB:GetBagView(kind)).columnCount - end, - set = function(_, value) - DB:SetBagViewSizeColumn(kind, DB:GetBagView(kind), value) - bucket:Later("setSectionsPerRow", 0.2, function() - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end) - end, - }, - scale = { - type = "range", - name = L:G("Scale"), - desc = L:G("Set the scale of this bag."), - order = 6, - min = 60, - max = 160, - step = 1, - get = function() - return DB:GetBagSizeInfo(kind, DB:GetBagView(kind)).scale - end, - set = function(_, value) - config:GetBag(kind).frame:SetScale(value / 100) - DB:SetBagViewSizeScale(kind, DB:GetBagView(kind), value) - end, - }, - } - }, - } - } - return options -end \ No newline at end of file diff --git a/config/classic/bags.lua b/config/classic/bags.lua deleted file mode 100644 index 051151fb..00000000 --- a/config/classic/bags.lua +++ /dev/null @@ -1,386 +0,0 @@ ----@diagnostic disable: duplicate-set-field,duplicate-doc-field,duplicate-doc-alias -local addonName = ... ---@type string - ----@class BetterBags: AceAddon -local addon = LibStub('AceAddon-3.0'):GetAddon(addonName) - ----@class Localization: AceModule -local L = addon:GetModule('Localization') - ----@class Database: AceModule -local DB = addon:GetModule('Database') - ----@class Constants: AceModule -local const = addon:GetModule('Constants') - ----@class Bucket: AceModule -local bucket = addon:GetModule('Bucket') - ----@class Categories: AceModule -local categories = addon:GetModule('Categories') - ----@class Items: AceModule -local items = addon:GetModule('Items') - ----@class Events: AceModule -local events = addon:GetModule('Events') - ----@class Themes: AceModule -local themes = addon:GetModule('Themes') - ----@class Config: AceModule -local config = addon:GetModule('Config') - ----@class Context: AceModule -local context = addon:GetModule('Context') - ----@param kind BagKind ----@return AceConfig.OptionsTable -function config:GetCustomCategoryOptions(kind) - if categories:GetCategoryCount() == 0 then - return { - type = "group", - name = L:G("Custom Categories"), - order = -1, - inline = true, - args = { - noCategories = { - type = "description", - name = L:G("No custom categories have been created yet."), - order = 1, - } - } - } - end - ---@type AceConfig.OptionsTable - local options = { - type = "multiselect", - name = L:G("Custom Categories"), - desc = L:G("Select which custom categories to show in this bag. If an option is checked, items that belong to the checked category will be put into a section for that category."), - order = -1, - get = function(_, value) - return categories:IsCategoryEnabled(kind, value) - end, - set = function(_, value) - categories:SetCategoryState(kind, value, not categories:IsCategoryEnabled(kind, value)) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - values = {} - } - for category, _ in pairs(categories:GetAllCategories()) do - if type(category) == "string" then - options.values[category] = category - else - DB:DeleteItemCategory(category) - end - end - return options -end - ----@param kind BagKind ----@return AceConfig.OptionsTable -function config:GetBagOptions(kind) - ---@type AceConfig.OptionsTable - local options = { - type = "group", - name = kind == const.BAG_KIND.BACKPACK and L:G("Backpack") or L:G("Bank"), - args = { - - categories = { - type = "group", - name = L:G("Categories"), - order = 0, - args = { - categories = { - type = "multiselect", - name = L:G("Categories"), - desc = L:G("Select which categories to show in this bag. If an option is checked, items that belong to the checked category will be put into a section for that category."), - order = 1, - get = function(_, value) - return DB:GetCategoryFilter(kind, value) - end, - set = function(_, value) - DB:SetCategoryFilter(kind, value, not DB:GetCategoryFilter(kind, value)) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - values = { - ["RecentItems"] = L:G("Recent Items"), - ["Type"] = L:G("Type"), - ["TradeSkill"] = L:G("Trade Skill"), - ["GearSet"] = L:G("Gear Set"), - ["EquipmentLocation"] = L:G("Equipment Location"), - } - }, - customCategories = config:GetCustomCategoryOptions(kind), - } - }, - sectionSorting = { - type = "select", - name = L:G("Section Sorting"), - desc = L:G("Select how sections should be sorted."), - order = 4, - style = "radio", - get = function() - return DB:GetSectionSortType(kind, DB:GetBagView(kind)) - end, - set = function(_, value) - DB:SetSectionSortType(kind, DB:GetBagView(kind), value) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - values = { - [const.SECTION_SORT_TYPE.ALPHABETICALLY] = L:G("Alphabetically"), - [const.SECTION_SORT_TYPE.SIZE_DESCENDING] = L:G("Size Descending"), - [const.SECTION_SORT_TYPE.SIZE_ASCENDING] = L:G("Size Ascending"), - } - }, - - itemSorting = { - type = "select", - name = L:G("Item Sorting"), - desc = L:G("Select how items should be sorted."), - order = 5, - style = "radio", - get = function() - return DB:GetItemSortType(kind, DB:GetBagView(kind)) - end, - set = function(_, value) - DB:SetItemSortType(kind, DB:GetBagView(kind), value) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - values = { - [const.ITEM_SORT_TYPE.QUALITY_THEN_ALPHABETICALLY] = L:G("Quality, then Alphabetically"), - [const.ITEM_SORT_TYPE.ALPHABETICALLY_THEN_QUALITY] = L:G("Alphabetically, then Quality"), - [const.ITEM_SORT_TYPE.ITEM_LEVEL] = L:G("Item Level"), - } - }, - stacking = { - type = "group", - name = L:G("Stacking"), - order = 6, - inline = true, - args = { - mergeStacks = { - type = "toggle", - name = L:G("Merge Stacks"), - desc = L:G("Merge stacks of the same item into a single stack."), - order = 1, - get = function() - return DB:GetStackingOptions(kind).mergeStacks - end, - set = function(_, value) - DB:SetMergeItems(kind, value) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - }, - mergeUnstackable = { - type = "toggle", - name = L:G("Merge Unstackable"), - desc = L:G("Merge unstackable items of the same kind into a single stack, such as armors, bags, etc."), - order = 2, - get = function() - return DB:GetStackingOptions(kind).mergeUnstackable - end, - set = function(_, value) - DB:SetMergeUnstackable(kind, value) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - }, - unmergeAtShop = { - type = "toggle", - name = L:G("Unmerge at Shop"), - desc = L:G("Unmerge all items when visiting a vendor."), - order = 3, - get = function() - return DB:GetStackingOptions(kind).unmergeAtShop - end, - set = function(_, value) - DB:SetUnmergeAtShop(kind, value) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - }, - dontMergePartial = { - type = "toggle", - name = L:G("Don't Merge Partial"), - desc = L:G("Don't merge stacks of items that aren't full stacks."), - order = 3, - get = function() - return DB:GetStackingOptions(kind).dontMergePartial - end, - set = function(_, value) - DB:SetDontMergePartial(kind, value) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - }, - } - }, - itemLevel = { - type = "group", - name = L:G("Item Level"), - order = 6, - inline = true, - args = { - enabled = { - type = "toggle", - name = L:G("Enabled"), - desc = L:G("Show the item level of items in this bag."), - order = 1, - get = function() - return DB:GetItemLevelOptions(kind).enabled - end, - set = function(_, value) - DB:SetItemLevelEnabled(kind, value) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - }, - color = { - type = "toggle", - name = L:G("Color"), - desc = L:G("Color the item level text based on the item's quality."), - order = 2, - get = function() - return DB:GetItemLevelOptions(kind).color - end, - set = function(_, value) - DB:SetItemLevelColorEnabled(kind, value) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - }, - } - }, - display = { - type = "group", - name = L:G("Display"), - order = 8, - inline = true, - args = { - showFullSectionNames = { - type = "toggle", - name = L:G("Show Full Section Names"), - desc = L:G("Show the full section in the bag window without truncating it with '...'"), - order = 0, - width = "full", - get = function() - return DB:GetShowFullSectionNames(kind) - end, - set = function(_, value) - DB:SetShowFullSectionNames(kind, value) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - }, - showAllFreeSpace = { - type = "toggle", - name = L:G("Show All Free Space Slots"), - desc = L:G("Show all free space slots in the bag window."), - order = 1, - width = "full", - get = function() - return DB:GetShowAllFreeSpace(kind) - end, - set = function(_, value) - DB:SetShowAllFreeSpace(kind, value) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - }, - showExtraGlowyButtons = { - type = "toggle", - name = L:G("Use Extra Glowy Item Buttons"), - desc = L:G("Use extra glowy item buttons for items in this bag."), - order = 2, - width = "full", - get = function() - return DB:GetExtraGlowyButtons(kind) - end, - set = function(_, value) - DB:SetExtraGlowyButtons(kind, value) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - }, - itemsPerRow = { - type = "range", - name = L:G("Items Per Row"), - desc = L:G("Set the number of items per row in this bag."), - order = 3, - min = 3, - max = 20, - step = 1, - get = function() - return DB:GetBagSizeInfo(kind, DB:GetBagView(kind)).itemsPerRow - end, - set = function(_, value) - DB:SetBagViewSizeItems(kind, DB:GetBagView(kind), value) - bucket:Later("setItemsPerRow", 0.2, function() - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end) - end, - }, - opacity = { - type = "range", - name = L:G("Opacity"), - desc = L:G("Set the opacity of this bag."), - order = 4, - min = 60, - max = 100, - step = 1, - get = function() - return DB:GetBagSizeInfo(kind, DB:GetBagView(kind)).opacity - end, - set = function(_, value) - DB:SetBagViewSizeOpacity(kind, DB:GetBagView(kind), value) - themes:UpdateOpacity() - end, - }, - sectionsPerRow = { - type = "range", - name = L:G("Columns"), - desc = L:G("Set the number of columns sections will fit into."), - order = 5, - min = 1, - max = 20, - step = 1, - get = function() - return DB:GetBagSizeInfo(kind, DB:GetBagView(kind)).columnCount - end, - set = function(_, value) - DB:SetBagViewSizeColumn(kind, DB:GetBagView(kind), value) - bucket:Later("setSectionsPerRow", 0.2, function() - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end) - end, - }, - scale = { - type = "range", - name = L:G("Scale"), - desc = L:G("Set the scale of this bag."), - order = 6, - min = 60, - max = 160, - step = 1, - get = function() - return DB:GetBagSizeInfo(kind, DB:GetBagView(kind)).scale - end, - set = function(_, value) - config:GetBag(kind).frame:SetScale(value / 100) - DB:SetBagViewSizeScale(kind, DB:GetBagView(kind), value) - end, - }, - } - }, - } - } - return options -end \ No newline at end of file diff --git a/config/config.lua b/config/config.lua index a776353b..84042061 100644 --- a/config/config.lua +++ b/config/config.lua @@ -1,4 +1,3 @@ ----@diagnostic disable: duplicate-set-field,duplicate-doc-field,duplicate-doc-alias local addonName = ... ---@type string ---@class BetterBags: AceAddon @@ -8,7 +7,7 @@ local addon = LibStub('AceAddon-3.0'):GetAddon(addonName) local L = addon:GetModule('Localization') ---@class Database: AceModule -local DB = addon:GetModule('Database') +local db = addon:GetModule('Database') ---@class Constants: AceModule local const = addon:GetModule('Constants') @@ -16,224 +15,479 @@ local const = addon:GetModule('Constants') ---@class Context: AceModule local context = addon:GetModule('Context') ----@class HelpText ----@field title string ----@field text string ----@field group string +---@class Events: AceModule +local events = addon:GetModule('Events') + +---@class Themes: AceModule +local themes = addon:GetModule('Themes') + +---@class Debug: AceModule +local debug = addon:GetModule('Debug') + +---@class Bucket: AceModule +local bucket = addon:GetModule('Bucket') + +---@class Form: AceModule +local form = addon:GetModule('Form') ---@class Config: AceModule ----@field frame Frame ----@field category string ----@field helpText HelpText[] ----@field private pluginOptions table +---@field configFrame FormFrame local config = addon:NewModule('Config') ----@class Events: AceModule -local events = addon:GetModule('Events') ----@param info table ----@return any, string, string -function config:ResolvePath(info) - ---@type string|tablelib - local path = info[#info] - - local db = DB:GetData().profile - if type(path) == "string" then - return db, path, path - elseif type(path) == "table" then - local n = #path - for i = 1, n-1 do - ---@type table - db = db[path[i]] - end - return db, path[n], strjoin('.', unpack(path)) - else - error("Invalid config option table -- this is a bug, please report it to github.com/Cidan/BetterBags", 2) - end -end ----@param kind BagKind ----@return Bag -function config:GetBag(kind) - return kind == const.BAG_KIND.BACKPACK and addon.Bags.Backpack or addon.Bags.Bank -end +function config:CreateConfig() + local f = form:Create({ + title = 'BetterBags Settings', + layout = const.FORM_LAYOUT.STACKED, + index = true + }) + f:AddSection({ + title = 'General', + description = 'General settings for BetterBags.', + }) + f:AddCheckbox({ + title = 'Enable In-Bag Search', + description = 'If enabled, a search bar will appear at the top of your bags.', + getValue = function(_) + return db:GetInBagSearch() + end, + setValue = function(ctx, value) + db:SetInBagSearch(value) + events:SendMessage(ctx, 'search/SetInFrame', value) + end + }) + f:AddCheckbox({ + title = 'Enable Enter to Make Category', + description = 'If enabled, pressing Enter with a search query will open the make category menu.', + getValue = function(_) + return db:GetEnterToMakeCategory() + end, + setValue = function(_, value) + db:SetEnterToMakeCategory(value) + end + }) + f:AddCheckbox({ + title = 'Enable Category Sell and Deposit', + description = 'If enabled, right-clicking a category header at an NPC shop will sell all its contents, or deposit to bank.', + getValue = function(_) + return db:GetCategorySell() + end, + setValue = function(_, value) + db:SetCategorySell(value) + end + }) + f:AddCheckbox({ + title = 'Show Blizzard Bag Button', + description = 'Show or hide the default Blizzard bag button.', + getValue = function(_) + return db:GetShowBagButton() + end, + setValue = function(_, value) + db:SetShowBagButton(value) + end + }) ----@return AceConfig.OptionsTable -function config:GetGeneralOptions() - ---@type AceConfig.OptionsTable - local options = { - type = "group", - name = L:G("General"), - order = 0, - args = { - inBagSearch = { - type = "toggle", - width = "full", - order = 0, - name = L:G("Enable In-Bag Search"), - desc = L:G("If enabled, a search bar will appear at the top of your bags."), - get = function() - return DB:GetInBagSearch() - end, - set = function(_, value) - DB:SetInBagSearch(value) - events:SendMessage(context:New('OnClick_InBagSearch'), 'search/SetInFrame', value) - end, - }, - enableEnterToMakeCategory = { - type = "toggle", - width = "full", - order = 1, - name = L:G("Enable Enter to Make Category"), - desc = L:G("If enabled, pressing Enter with a search query will open the make category menu."), - get = function() - return DB:GetEnterToMakeCategory() - end, - set = function(_, value) - DB:SetEnterToMakeCategory(value) - end, - }, - categorySell = { - type = "toggle", - width = "full", - order = 2, - name = L:G("Enable Category Sell"), - desc = L:G("If enabled, right-clicking a category header at a NPC shop will sell all its contents (limited to 10 stacks to allow buy-backs)."), - get = function() - return DB:GetCategorySell() - end, - set = function(_, value) - DB:SetCategorySell(value) - end, - }, - showBagButton = { - type = "toggle", - width = "full", - order = 3, - name = L:G("Show Blizzard Bag Button"), - desc = L:G("Show or hide the default Blizzard bag button."), - get = DB.GetShowBagButton, - set = function(_, value) - local sneakyFrame = _G["BetterBagsSneakyFrame"] ---@type Frame - if value then - BagsBar:SetParent(UIParent) - else - BagsBar:SetParent(sneakyFrame) - end - DB:SetShowBagButton(value) - end, - }, - upgradeIconProvider = { - type = "select", - width = "double", - order = 4, - name = L:G("Upgrade Icon Provider"), - desc = L:G("Select the provider for the upgrade icon."), - values = { - ["None"] = L:G("None"), - ["BetterBags"] = L:G("BetterBags"), - }, - get = function() - return DB:GetUpgradeIconProvider() - end, - set = function(_, value) - DB:SetUpgradeIconProvider(value) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bag/RedrawIcons') - end, - }, - newItemTime = { - type = "range", - order = 5, - name = L:G("New Item Duration"), - desc = L:G("The time, in minutes, to consider an item a new item."), - min = 0, - max = 240, - step = 1, - bigStep = 5, - get = function() - return DB:GetData().profile.newItemTime / 60 - end, - set = function(_, value) - DB:GetData().profile.newItemTime = value * 60 - end, - } - } - } - return options -end + f:AddDropdown({ + title = 'Upgrade Icon Provider', + description = 'Select the icon provider for item upgrades.', + items = {'None', 'BetterBags'}, + getValue = function(_, value) + return value == db:GetUpgradeIconProvider() + end, + setValue = function(ctx, value) + db:SetUpgradeIconProvider(value) + events:SendMessage(ctx, 'bag/RedrawIcons') + end, + }) --- AddPluginConfig adds a plugin's configuration to the BetterBags configuration. ----@param name string ----@param opts AceConfig.OptionsTable -function config:AddPluginConfig(name, opts) - assert(self.pluginOptions[name] == nil, "Plugin option already exists, did you call AddPluginConfig twice?") - self.pluginOptions[name] = opts -end + f:AddSlider({ + title = 'New Item Duration', + description = 'The duration in minutes that an item is considered new.', + min = 1, + max = 120, + step = 1, + getValue = function(_) + return db:GetData().profile.newItemTime / 60 + end, + setValue = function(_, value) + db:GetData().profile.newItemTime = value * 60 + end, + }) ----@return AceConfig.OptionsTable -function config:GetPluginsOptions() - local options = { - type = "group", - name = L:G("Plugins"), - order = 100, - args = { - header = { - name = L:G("Plugins"), - type = "group", - inline = true, - order = 0, - args = { - help = { - type = "description", - name = L:G("Plugin configuration options can be accessed on the left by expanding the 'Plugins' menu option."), - order = 0, - } - } - }, - }, + local bagTypes = { + {name = 'Backpack', kind = const.BAG_KIND.BACKPACK}, + {name = 'Bank', kind = const.BAG_KIND.BANK} } + for _, bagType in ipairs(bagTypes) do - for name, opts in pairs(self.pluginOptions) do - options.args[name] = { - name = name, - type = 'group', - args = opts, + f:AddSection({ + title = bagType.name, + description = 'Settings for the ' .. string.lower(bagType.name) .. '.', + }) + local sectionOrders = { + ["Alphabetically"] = const.SECTION_SORT_TYPE.ALPHABETICALLY, + ["Size Descending"] = const.SECTION_SORT_TYPE.SIZE_DESCENDING, + ["Size Ascending"] = const.SECTION_SORT_TYPE.SIZE_ASCENDING, } - end - - return options -end + f:AddDropdown({ + title = 'Section Order', + description = 'The order of sections in the backpack when not pinned.', + items = {'Alphabetically', 'Size Descending', 'Size Ascending'}, + getValue = function(_, value) + return sectionOrders[value] == db:GetSectionSortType(bagType.kind, db:GetBagView(bagType.kind)) + end, + setValue = function(ctx, value) + db:SetSectionSortType(bagType.kind, db:GetBagView(bagType.kind), sectionOrders[value]) + events:SendMessage(ctx, 'bags/FullRefreshAll') + end, + }) -function config:GetOptions() - ---@type AceConfig.OptionsTable - local options = { - type = "group", - name = L:G("BetterBags"), - args = { - general = self:GetGeneralOptions(), - customCategories = self:GetCustomCategoryConfig(), - backpack = self:GetBagOptions(const.BAG_KIND.BACKPACK), - bank = self:GetBagOptions(const.BAG_KIND.BANK), - help = self:GenerateHelp(), - plugins = self:GetPluginsOptions(), + local itemOrders = { + ["Alphabetically"] = const.ITEM_SORT_TYPE.ALPHABETICALLY_THEN_QUALITY, + ["Quality"] = const.ITEM_SORT_TYPE.QUALITY_THEN_ALPHABETICALLY, + ["Item Level"] = const.ITEM_SORT_TYPE.ITEM_LEVEL, } - } - return options + f:AddDropdown({ + title = 'Item Order', + description = 'The default order of items within each section.', + items = {'Alphabetically', 'Quality', 'Item Level'}, + getValue = function(_, value) + return itemOrders[value] == db:GetItemSortType(bagType.kind, db:GetBagView(bagType.kind)) + end, + setValue = function(ctx, value) + db:SetItemSortType(bagType.kind, db:GetBagView(bagType.kind), itemOrders[value]) + events:SendMessage(ctx, 'bags/FullRefreshAll') + end, + }) + + f:AddSubSection({ + title = 'Categories', + description = 'Settings for Blizzard item categories in the backpack.', + }) + + f:AddCheckbox({ + title = 'Equipment Location', + description = 'Sort items into categories based on equipment location (Main Hand, Head, etc).', + getValue = function(_) + return db:GetCategoryFilters(bagType.kind).EquipmentLocation + end, + setValue = function(ctx, value) + db:GetCategoryFilters(bagType.kind).EquipmentLocation = value + events:SendMessage(ctx, 'bags/FullRefreshAll') + end + }) + + f:AddCheckbox({ + title = 'Expansion', + description = 'Sort items into categories based on their expansion.', + getValue = function(_) + return db:GetCategoryFilters(bagType.kind).Expansion + end, + setValue = function(ctx, value) + db:GetCategoryFilters(bagType.kind).Expansion = value + events:SendMessage(ctx, 'bags/FullRefreshAll') + end + }) + + f:AddCheckbox({ + title = 'Equipment Set', + description = 'Sort items into categories based on equipment sets.', + getValue = function(_) + return db:GetCategoryFilters(bagType.kind).GearSet + end, + setValue = function(ctx, value) + db:GetCategoryFilters(bagType.kind).GearSet = value + events:SendMessage(ctx, 'bags/FullRefreshAll') + end + }) + + f:AddCheckbox({ + title = 'Recent Items', + description = 'Enable the Recent Items category for new items.', + getValue = function(_) + return db:GetCategoryFilters(bagType.kind).RecentItems + end, + setValue = function(ctx, value) + db:GetCategoryFilters(bagType.kind).RecentItems = value + events:SendMessage(ctx, 'bags/FullRefreshAll') + end + }) + + f:AddCheckbox({ + title = 'Trade Skill', + description = 'Sort items into categories based on their trade skill usage.', + getValue = function(_) + return db:GetCategoryFilters(bagType.kind).TradeSkill + end, + setValue = function(ctx, value) + db:GetCategoryFilters(bagType.kind).TradeSkill = value + events:SendMessage(ctx, 'bags/FullRefreshAll') + end + }) + + f:AddCheckbox({ + title = 'Type', + description = 'Sort items into categories based on their equipment type (Consumable, Quest, etc).', + getValue = function(_) + return db:GetCategoryFilters(bagType.kind).Type + end, + setValue = function(ctx, value) + db:GetCategoryFilters(bagType.kind).Type = value + events:SendMessage(ctx, 'bags/FullRefreshAll') + end + }) + + f:AddCheckbox({ + title = 'Sub Type', + description = 'Sort items into categories based on sub type (Potions, Bandages, etc).', + getValue = function(_) + return db:GetCategoryFilters(bagType.kind).Subtype + end, + setValue = function(ctx, value) + db:GetCategoryFilters(bagType.kind).Subtype = value + events:SendMessage(ctx, 'bags/FullRefreshAll') + end + }) + + f:AddSubSection({ + title = 'Item Stacking', + description = 'Settings for item stacking in the backpack.', + }) + + f:AddCheckbox({ + title = 'All Items Recent', + description = 'All new items you loot, pickup, or move into the bag will be marked as recent.', + getValue = function(_) + return db:GetMarkRecentItems(bagType.kind) + end, + setValue = function(_, value) + db:SetMarkRecentItems(bagType.kind, value) + end + }) + + f:AddCheckbox({ + title = 'Flash Stacks', + description = 'When a stack of items gets a new item, the stack will flash.', + getValue = function(_) + return db:GetShowNewItemFlash(bagType.kind) + end, + setValue = function(_, value) + db:SetShowNewItemFlash(bagType.kind, value) + end + }) + + f:AddCheckbox({ + title = 'Merge Stacks', + description = 'Stackable items will merge into a single item button in your backpack.', + getValue = function(_) + return db:GetStackingOptions(bagType.kind).mergeStacks + end, + setValue = function(ctx, value) + db:GetStackingOptions(bagType.kind).mergeStacks = value + events:SendMessage(ctx, 'bags/FullRefreshAll') + end + }) + + f:AddCheckbox({ + title = 'Merge Unstackable', + description = 'Unstackable items, such as armor and weapons, will merge into a single item button in your backpack.', + getValue = function(_) + return db:GetStackingOptions(bagType.kind).mergeUnstackable + end, + setValue = function(ctx, value) + db:GetStackingOptions(bagType.kind).mergeUnstackable = value + events:SendMessage(ctx, 'bags/FullRefreshAll') + end + }) + + f:AddCheckbox({ + title = "Don't Merge Partial Stacks", + description = 'Partial stacks of items will not merge with other partial or full stacks.', + getValue = function(_) + return db:GetStackingOptions(bagType.kind).dontMergePartial + end, + setValue = function(ctx, value) + db:GetStackingOptions(bagType.kind).dontMergePartial = value + events:SendMessage(ctx, 'bags/FullRefreshAll') + end + }) + + f:AddCheckbox({ + title = "Split Transmogged Items", + description = 'Transmogged items will be split into a separate, stackable button in your backpack.', + getValue = function(_) + return db:GetStackingOptions(bagType.kind).dontMergeTransmog + end, + setValue = function(ctx, value) + db:GetStackingOptions(bagType.kind).dontMergeTransmog = value + events:SendMessage(ctx, 'bags/FullRefreshAll') + end + }) + + f:AddCheckbox({ + title = 'Unmerge on Interactions', + description = 'When you interact a vendor, mailbox, auction house, etc, all merged items will unmerge.', + getValue = function(_) + return db:GetStackingOptions(bagType.kind).unmergeAtShop + end, + setValue = function(ctx, value) + db:GetStackingOptions(bagType.kind).unmergeAtShop = value + events:SendMessage(ctx, 'bags/FullRefreshAll') + end + }) + + + f:AddSubSection({ + title = 'Item Level', + description = 'Settings for item level in the backpack.', + }) + + f:AddCheckbox({ + title = 'Show Item Level', + description = 'Show the item level on item buttons in the backpack.', + getValue = function(_) + return db:GetItemLevelOptions(bagType.kind).enabled + end, + setValue = function(ctx, value) + db:GetItemLevelOptions(bagType.kind).enabled = value + events:SendMessage(ctx, 'bags/FullRefreshAll') + end + }) + + f:AddCheckbox({ + title = 'Show Item Level Color', + description = 'Show the item level in color on item buttons in the backpack.', + getValue = function(_) + return db:GetItemLevelOptions(bagType.kind).color + end, + setValue = function(ctx, value) + db:GetItemLevelOptions(bagType.kind).color = value + events:SendMessage(ctx, 'bags/FullRefreshAll') + end + }) + + + f:AddSubSection({ + title = 'Display', + description = 'Settings that adjust layout and visual aspects of the backpack.', + }) + + f:AddCheckbox({ + title = 'Show Full Section Names', + description = 'Show the full section names for each section and do not cut them off.', + getValue = function(_) + return db:GetShowFullSectionNames(bagType.kind) + end, + setValue = function(ctx, value) + db:SetShowFullSectionNames(bagType.kind, value) + events:SendMessage(ctx, 'bags/FullRefreshAll') + end + }) + + f:AddCheckbox({ + title = 'Show All Free Space Slots', + description = 'Show all free space slots, individually, at the bottom of the backpack.', + getValue = function(_) + return db:GetShowAllFreeSpace(bagType.kind) + end, + setValue = function(ctx, value) + db:SetShowAllFreeSpace(bagType.kind, value) + events:SendMessage(ctx, 'bags/FullRefreshAll') + end + }) + + f:AddCheckbox({ + title = 'Extra Glowy Item Buttons', + description = 'Item buttons will have an enhanced glow effect using the item quality color.', + getValue = function(_) + return db:GetExtraGlowyButtons(bagType.kind) + end, + setValue = function(ctx, value) + db:SetExtraGlowyButtons(bagType.kind, value) + events:SendMessage(ctx, 'bags/FullRefreshAll') + end + }) + + f:AddSlider({ + title = 'Items Per Row', + description = 'The number of items per row in each section.', + min = 3, + max = 20, + step = 1, + getValue = function(_) + return db:GetBagSizeInfo(bagType.kind, db:GetBagView(bagType.kind)).itemsPerRow > 20 and 20 or db:GetBagSizeInfo(bagType.kind, db:GetBagView(bagType.kind)).itemsPerRow + end, + setValue = function(ctx, value) + db:SetBagViewSizeItems(bagType.kind, db:GetBagView(bagType.kind), value) + bucket:Later("setItemsPerRow", 0.2, function() + events:SendMessage(ctx, 'bags/FullRefreshAll') + end) + end, + }) + + f:AddSlider({ + title = 'Columns', + description = 'The number of columns in the backpack.', + min = 1, + max = 20, + step = 1, + getValue = function(_) + return db:GetBagSizeInfo(bagType.kind, db:GetBagView(bagType.kind)).columnCount > 20 and 20 or db:GetBagSizeInfo(bagType.kind, db:GetBagView(bagType.kind)).columnCount + end, + setValue = function(ctx, value) + db:SetBagViewSizeColumn(bagType.kind, db:GetBagView(bagType.kind), value) + bucket:Later("setSectionsPerRow", 0.2, function() + events:SendMessage(ctx, 'bags/FullRefreshAll') + end) + end, + }) + + f:AddSlider({ + title = 'Opacity', + description = 'The opacity of the background of the backpack.', + min = 0, + max = 100, + step = 1, + getValue = function(_) + return db:GetBagSizeInfo(bagType.kind, db:GetBagView(bagType.kind)).opacity + end, + setValue = function(_, value) + db:SetBagViewSizeOpacity(bagType.kind, db:GetBagView(bagType.kind), value) + themes:UpdateOpacity() + end, + }) + + f:AddSlider({ + title = 'Scale', + description = 'The scale of the backpack.', + min = 50, + max = 200, + step = 1, + getValue = function(_) + return db:GetBagSizeInfo(bagType.kind, db:GetBagView(bagType.kind)).scale + end, + setValue = function(_, value) + -- TODO(lobato): This should be an event. + local bag = addon:GetBagFromKind(bagType.kind) + if not bag then return end + bag.frame:SetScale(value / 100) + db:SetBagViewSizeScale(bagType.kind, db:GetBagView(bagType.kind), value) + end, + }) + end + + f:GetFrame():SetSize(600, 800) + f:GetFrame():SetPoint("CENTER") + self.configFrame = f end +---@diagnostic disable-next-line: duplicate-set-field function config:Open() - LibStub("AceConfigDialog-3.0"):Open(addonName) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'config/Opened') + self.configFrame:Show() end -function config:OnEnable() - self.helpText = {} - self:CreateAllHelp() - LibStub('AceConfig-3.0'):RegisterOptionsTable(addonName, function() return self:GetOptions() end) - self.frame, self.category = LibStub("AceConfigDialog-3.0"):AddToBlizOptions(addonName, "BetterBags") - LibStub("AceConfigDialog-3.0"):SetDefaultSize(addonName, 700, 800) +function config:RegisterSettings() LibStub('AceConsole-3.0'):RegisterChatCommand("bb", function() self:Open() end) @@ -246,7 +500,7 @@ function config:OnEnable() end) events:RegisterMessage('categories/Changed', function() - LibStub('AceConfigRegistry-3.0'):NotifyChange(addonName) + --LibStub('AceConfigRegistry-3.0'):NotifyChange(addonName) end) events:RegisterMessage('config/Open', function() @@ -254,12 +508,13 @@ function config:OnEnable() end) LibStub('AceConsole-3.0'):RegisterChatCommand("bbdb", function() - DB:SetDebugMode(not DB:GetDebugMode()) + db:SetDebugMode(not db:GetDebugMode()) local ctx = context:New('on_click') - events:SendMessage(ctx, 'config/DebugMode', DB:GetDebugMode()) + events:SendMessage(ctx, 'config/DebugMode', db:GetDebugMode()) end) end -function config:OnInitialize() - self.pluginOptions = {} -end \ No newline at end of file +function config:OnEnable() + self:CreateConfig() + self:RegisterSettings() +end diff --git a/config/customcat.lua b/config/customcat.lua deleted file mode 100644 index 93f5e7fb..00000000 --- a/config/customcat.lua +++ /dev/null @@ -1,110 +0,0 @@ -local addonName = ... ---@type string - ----@class BetterBags: AceAddon -local addon = LibStub('AceAddon-3.0'):GetAddon(addonName) - ----@class Categories: AceModule -local categories = addon:GetModule('Categories') - ----@class Database: AceModule -local DB = addon:GetModule('Database') - ----@class Constants: AceModule -local const = addon:GetModule('Constants') - ----@class Config: AceModule -local config = addon:GetModule('Config') - ----@class Localization: AceModule -local L = addon:GetModule('Localization') - ----@class Context: AceModule -local context = addon:GetModule('Context') - ----@param category string ----@return AceConfig.OptionsTable -function config:CreateCustomCategoryConfig(category) - ---@type AceConfig.OptionsTable - local options = { - name = category, - type = "group", - args = { - items = { - type = "multiselect", - name = L:G("Items"), - dialogControl = "ItemList", - order = 1, - values = categories:GetMergedCategory(category) - }, - delete = { - type = "execute", - name = L:G("Delete Category"), - confirm = true, - confirmText = L:G("Are you sure you want to delete this category?"), - order = 2, - func = function() - local ctx = context:New('DeleteCategory_Menu') - categories:DeleteCategory(ctx, category) - end, - } - }, - } - return options -end - ----@return AceConfig.OptionsTable -function config:GetCustomCategoryConfig() - ---@type AceConfig.OptionsTable - local options = { - name = L:G("Custom Categories"), - type = "group", - args = { - createCategory = { - name = L:G("Create Category"), - type = "group", - inline = true, - args = { - createHelp = { - type = "description", - name = L:G("Custom categories allow you to create your own categories for items. Type the name of the category you want to create in the box below and press enter to create an empty category."), - order = 0, - }, - useHelp = { - type = "description", - name = L:G("Categories you create can be enabled and disabled just like the default categories in the configuration menu option for the bag (Backpack or Bank) on the left. Once you have created a category, you can configure it by selecting it on the menu on the left."), - order = 1, - }, - afterHelp = { - type = "description", - name = L:G("After creating a category, you can use the side menu via the bag menu, Configure Categories, to add or remove items"), - order = 2, - }, - name = { - name = L:G("New Category Name"), - type = "input", - width = "full", - order = 2, - get = function() - return "" - end, - set = function(_, value) - if value == "" then return end - local ctx = context:New('CreateCategory_Menu') - categories:CreateCategory(ctx, { - name = value, - save = true, - enabled = { - [const.BAG_KIND.BACKPACK] = true, - [const.BAG_KIND.BANK] = true, - }, - itemList = {}, - readOnly = false, - }) - end, - } - } - } - }, - } - return options -end \ No newline at end of file diff --git a/config/era/bags.lua b/config/era/bags.lua deleted file mode 100644 index 9d3e6432..00000000 --- a/config/era/bags.lua +++ /dev/null @@ -1,386 +0,0 @@ ----@diagnostic disable: duplicate-set-field,duplicate-doc-field,duplicate-doc-alias -local addonName = ... ---@type string - ----@class BetterBags: AceAddon -local addon = LibStub('AceAddon-3.0'):GetAddon(addonName) - ----@class Localization: AceModule -local L = addon:GetModule('Localization') - ----@class Database: AceModule -local DB = addon:GetModule('Database') - ----@class Constants: AceModule -local const = addon:GetModule('Constants') - ----@class Bucket: AceModule -local bucket = addon:GetModule('Bucket') - ----@class Categories: AceModule -local categories = addon:GetModule('Categories') - ----@class Items: AceModule -local items = addon:GetModule('Items') - ----@class Events: AceModule -local events = addon:GetModule('Events') - ----@class Themes: AceModule -local themes = addon:GetModule('Themes') - ----@class Config: AceModule -local config = addon:GetModule('Config') - ----@class Context: AceModule -local context = addon:GetModule('Context') - ----@param kind BagKind ----@return AceConfig.OptionsTable -function config:GetCustomCategoryOptions(kind) - if categories:GetCategoryCount() == 0 then - return { - type = "group", - name = L:G("Custom Categories"), - order = -1, - inline = true, - args = { - noCategories = { - type = "description", - name = L:G("No custom categories have been created yet."), - order = 1, - } - } - } - end - ---@type AceConfig.OptionsTable - local options = { - type = "multiselect", - name = L:G("Custom Categories"), - desc = L:G("Select which custom categories to show in this bag. If an option is checked, items that belong to the checked category will be put into a section for that category."), - order = -1, - get = function(_, value) - return categories:IsCategoryEnabled(kind, value) - end, - set = function(_, value) - categories:SetCategoryState(kind, value, not categories:IsCategoryEnabled(kind, value)) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - values = {} - } - for category, _ in pairs(categories:GetAllCategories()) do - if type(category) == "string" then - options.values[category] = category - else - DB:DeleteItemCategory(category) - end - end - return options -end - ----@param kind BagKind ----@return AceConfig.OptionsTable -function config:GetBagOptions(kind) - ---@type AceConfig.OptionsTable - local options = { - type = "group", - name = kind == const.BAG_KIND.BACKPACK and L:G("Backpack") or L:G("Bank"), - args = { - - categories = { - type = "group", - name = L:G("Categories"), - order = 0, - args = { - categories = { - type = "multiselect", - name = L:G("Categories"), - desc = L:G("Select which categories to show in this bag. If an option is checked, items that belong to the checked category will be put into a section for that category."), - order = 1, - get = function(_, value) - return DB:GetCategoryFilter(kind, value) - end, - set = function(_, value) - DB:SetCategoryFilter(kind, value, not DB:GetCategoryFilter(kind, value)) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - values = { - ["RecentItems"] = L:G("Recent Items"), - ["Type"] = L:G("Type"), - ["TradeSkill"] = L:G("Trade Skill"), - ["EquipmentLocation"] = L:G("Equipment Location"), - } - }, - customCategories = config:GetCustomCategoryOptions(kind), - } - }, - - sectionSorting = { - type = "select", - name = L:G("Section Sorting"), - desc = L:G("Select how sections should be sorted."), - order = 4, - style = "radio", - get = function() - return DB:GetSectionSortType(kind, DB:GetBagView(kind)) - end, - set = function(_, value) - DB:SetSectionSortType(kind, DB:GetBagView(kind), value) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - values = { - [const.SECTION_SORT_TYPE.ALPHABETICALLY] = L:G("Alphabetically"), - [const.SECTION_SORT_TYPE.SIZE_DESCENDING] = L:G("Size Descending"), - [const.SECTION_SORT_TYPE.SIZE_ASCENDING] = L:G("Size Ascending"), - } - }, - - itemSorting = { - type = "select", - name = L:G("Item Sorting"), - desc = L:G("Select how items should be sorted."), - order = 5, - style = "radio", - get = function() - return DB:GetItemSortType(kind, DB:GetBagView(kind)) - end, - set = function(_, value) - DB:SetItemSortType(kind, DB:GetBagView(kind), value) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - values = { - [const.ITEM_SORT_TYPE.QUALITY_THEN_ALPHABETICALLY] = L:G("Quality, then Alphabetically"), - [const.ITEM_SORT_TYPE.ALPHABETICALLY_THEN_QUALITY] = L:G("Alphabetically, then Quality"), - [const.ITEM_SORT_TYPE.ITEM_LEVEL] = L:G("Item Level"), - } - }, - stacking = { - type = "group", - name = L:G("Stacking"), - order = 6, - inline = true, - args = { - mergeStacks = { - type = "toggle", - name = L:G("Merge Stacks"), - desc = L:G("Merge stacks of the same item into a single stack."), - order = 1, - get = function() - return DB:GetStackingOptions(kind).mergeStacks - end, - set = function(_, value) - DB:SetMergeItems(kind, value) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - }, - mergeUnstackable = { - type = "toggle", - name = L:G("Merge Unstackable"), - desc = L:G("Merge unstackable items of the same kind into a single stack, such as armors, bags, etc."), - order = 2, - get = function() - return DB:GetStackingOptions(kind).mergeUnstackable - end, - set = function(_, value) - DB:SetMergeUnstackable(kind, value) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - }, - unmergeAtShop = { - type = "toggle", - name = L:G("Unmerge at Shop"), - desc = L:G("Unmerge all items when visiting a vendor."), - order = 3, - get = function() - return DB:GetStackingOptions(kind).unmergeAtShop - end, - set = function(_, value) - DB:SetUnmergeAtShop(kind, value) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - }, - dontMergePartial = { - type = "toggle", - name = L:G("Don't Merge Partial"), - desc = L:G("Don't merge stacks of items that aren't full stacks."), - order = 3, - get = function() - return DB:GetStackingOptions(kind).dontMergePartial - end, - set = function(_, value) - DB:SetDontMergePartial(kind, value) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - }, - } - }, - itemLevel = { - type = "group", - name = L:G("Item Level"), - order = 6, - inline = true, - args = { - enabled = { - type = "toggle", - name = L:G("Enabled"), - desc = L:G("Show the item level of items in this bag."), - order = 1, - get = function() - return DB:GetItemLevelOptions(kind).enabled - end, - set = function(_, value) - DB:SetItemLevelEnabled(kind, value) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - }, - color = { - type = "toggle", - name = L:G("Color"), - desc = L:G("Color the item level text based on the item's quality."), - order = 2, - get = function() - return DB:GetItemLevelOptions(kind).color - end, - set = function(_, value) - DB:SetItemLevelColorEnabled(kind, value) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - }, - } - }, - display = { - type = "group", - name = L:G("Display"), - order = 8, - inline = true, - args = { - showFullSectionNames = { - type = "toggle", - name = L:G("Show Full Section Names"), - desc = L:G("Show the full section in the bag window without truncating it with '...'"), - order = 0, - width = "full", - get = function() - return DB:GetShowFullSectionNames(kind) - end, - set = function(_, value) - DB:SetShowFullSectionNames(kind, value) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - }, - showAllFreeSpace = { - type = "toggle", - name = L:G("Show All Free Space Slots"), - desc = L:G("Show all free space slots in the bag window."), - order = 1, - width = "full", - get = function() - return DB:GetShowAllFreeSpace(kind) - end, - set = function(_, value) - DB:SetShowAllFreeSpace(kind, value) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - }, - showExtraGlowyButtons = { - type = "toggle", - name = L:G("Use Extra Glowy Item Buttons"), - desc = L:G("Use extra glowy item buttons for items in this bag."), - order = 2, - width = "full", - get = function() - return DB:GetExtraGlowyButtons(kind) - end, - set = function(_, value) - DB:SetExtraGlowyButtons(kind, value) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end, - }, - itemsPerRow = { - type = "range", - name = L:G("Items Per Row"), - desc = L:G("Set the number of items per row in this bag."), - order = 3, - min = 3, - max = 20, - step = 1, - get = function() - return DB:GetBagSizeInfo(kind, DB:GetBagView(kind)).itemsPerRow - end, - set = function(_, value) - DB:SetBagViewSizeItems(kind, DB:GetBagView(kind), value) - bucket:Later("setItemsPerRow", 0.2, function() - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end) - end, - }, - opacity = { - type = "range", - name = L:G("Opacity"), - desc = L:G("Set the opacity of this bag."), - order = 4, - min = 60, - max = 100, - step = 1, - get = function() - return DB:GetBagSizeInfo(kind, DB:GetBagView(kind)).opacity - end, - set = function(_, value) - DB:SetBagViewSizeOpacity(kind, DB:GetBagView(kind), value) - themes:UpdateOpacity() - end, - }, - sectionsPerRow = { - type = "range", - name = L:G("Columns"), - desc = L:G("Set the number of columns sections will fit into."), - order = 5, - min = 1, - max = 20, - step = 1, - get = function() - return DB:GetBagSizeInfo(kind, DB:GetBagView(kind)).columnCount - end, - set = function(_, value) - DB:SetBagViewSizeColumn(kind, DB:GetBagView(kind), value) - bucket:Later("setSectionsPerRow", 0.2, function() - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bags/FullRefreshAll') - end) - end, - }, - scale = { - type = "range", - name = L:G("Scale"), - desc = L:G("Set the scale of this bag."), - order = 6, - min = 60, - max = 160, - step = 1, - get = function() - return DB:GetBagSizeInfo(kind, DB:GetBagView(kind)).scale - end, - set = function(_, value) - config:GetBag(kind).frame:SetScale(value / 100) - DB:SetBagViewSizeScale(kind, DB:GetBagView(kind), value) - end, - }, - } - }, - } - } - return options -end \ No newline at end of file diff --git a/config/era/config.lua b/config/era/config.lua deleted file mode 100644 index 1ba652e6..00000000 --- a/config/era/config.lua +++ /dev/null @@ -1,98 +0,0 @@ ----@diagnostic disable: duplicate-set-field,duplicate-doc-field,duplicate-doc-alias -local addonName = ... ---@type string - ----@class BetterBags: AceAddon -local addon = LibStub('AceAddon-3.0'):GetAddon(addonName) - ----@class Localization: AceModule -local L = addon:GetModule('Localization') - ----@class Database: AceModule -local DB = addon:GetModule('Database') - ----@class Constants: AceModule -local const = addon:GetModule('Constants') - ----@class Config: AceModule -local config = addon:GetModule('Config') - ----@class Events: AceModule -local events = addon:GetModule('Events') - ----@class Context: AceModule -local context = addon:GetModule('Context') - ----@return AceConfig.OptionsTable -function config:GetGeneralOptions() - ---@type AceConfig.OptionsTable - local options = { - type = "group", - name = L:G("General"), - order = 0, - args = { - inBagSearch = { - type = "toggle", - width = "full", - order = 0, - name = L:G("Enable In-Bag Search"), - desc = L:G("If enabled, a search bar will appear at the top of your bags."), - get = function() - return DB:GetInBagSearch() - end, - set = function(_, value) - DB:SetInBagSearch(value) - events:SendMessage(context:New('OnClick_InBagSearch'), 'search/SetInFrame', value) - end, - }, - categorySell = { - type = "toggle", - width = "full", - order = 1, - name = L:G("Enable Category Sell"), - desc = L:G("If enabled, right-clicking a category header at a NPC shop will sell all its contents (limited to 10 stacks to allow buy-backs)."), - get = function() - return DB:GetCategorySell() - end, - set = function(_, value) - DB:SetCategorySell(value) - end, - }, - upgradeIconProvider = { - type = "select", - width = "double", - order = 4, - name = L:G("Upgrade Icon Provider"), - desc = L:G("Select the provider for the upgrade icon."), - values = { - ["None"] = L:G("None"), - ["BetterBags"] = L:G("BetterBags"), - }, - get = function() - return DB:GetUpgradeIconProvider() - end, - set = function(_, value) - DB:SetUpgradeIconProvider(value) - local ctx = context:New('on_click') - events:SendMessage(ctx, 'bag/RedrawIcons') - end, - }, - newItemTime = { - type = "range", - order = 2, - name = L:G("New Item Duration"), - desc = L:G("The time, in minutes, to consider an item a new item."), - min = 0, - max = 240, - step = 1, - bigStep = 5, - get = function() - return DB:GetData().profile.newItemTime / 60 - end, - set = function(_, value) - DB:GetData().profile.newItemTime = value * 60 - end, - } - } - } - return options -end diff --git a/config/help.lua b/config/help.lua deleted file mode 100644 index bbbd0eea..00000000 --- a/config/help.lua +++ /dev/null @@ -1,162 +0,0 @@ -local addonName = ... ---@type string - ----@class BetterBags: AceAddon -local addon = LibStub('AceAddon-3.0'):GetAddon(addonName) - ----@class Config: AceModule -local config = addon:GetModule('Config') - ----@class Localization: AceModule -local L = addon:GetModule('Localization') - ----@return AceConfig.OptionsTable -function config:GenerateHelp() - ---@type AceConfig.OptionsTable - local options = { - type = "group", - name = L:G("Help"), - args = { - mainHelp = { - type = "group", - name = L:G("Help"), - inline = true, - order = 1, - args = { - text = { - type = "description", - name = L:G("Welcome to Better Bags! Please select a help item from the left menu for FAQ's and other information."), - order = 1, - } - } - } - } - } - for _, helpItem in pairs(self.helpText) do - if not options.args[helpItem.group] then - options.args[helpItem.group] = { - type = "group", - name = helpItem.group, - order = 1, - args = {} - } - end - options.args[helpItem.group].args[helpItem.title] = { - type = "group", - name = helpItem.title, - order = 1, - inline = true, - args = { - text = { - type = "description", - name = helpItem.text, - order = 1, - } - } - } - end - return options -end - ----@param helpItem HelpText -function config:AddHelp(helpItem) - table.insert(self.helpText, helpItem) -end - -function config:CreateAllHelp() - self:AddHelp({ - group = L:G("Custom Categories"), - title = L:G("Why are some of my items not showing up in my custom categories?"), - text = L:G("Items can only be in one category at a time. If you have a category that is missing items, it is likely that the items in that category are already in another category.") - }) - self:AddHelp({ - group = L:G("Custom Categories"), - title = L:G("Why does a custom category reappear after I delete it?"), - text = L:G("If you delete a custom category that was created by another addon/plugin, it will reappear the next time you log in/reload. To permanently delete a custom category created by a plugin/another addon, you must disable the addon creating the category and then delete the category in the UI.") - }) - self:AddHelp({ - group = L:G("Custom Categories"), - title = L:G("How do I delete an item from a custom category?"), - text = L:G("When viewing a custom category configuration, you can right click on an item to open it's menu and select 'delete' to delete it from the category.") - }) - self:AddHelp({ - group = L:G("Search"), - title = L:G("How do I search for items?"), - text = L:G([[ - You can bind a key to open the search bar in the World of Warcraft keybindings menu. You can also open the search bar by shift clicking on the bag button at the top of the bag frame. - Once the search bar is open, you can type in the name of an item to search for it. - The BetterBags search engine is extremly comprehensive and supports many different search operators and grouping. - You can search for items by a number of fields, such as name, type, subtype, expansion, and more, or combine multiple fields to create complex searches. - - For a more complete breakdown of the available search terms, please visit the README at https://github.com/Cidan/BetterBags. - - - When searching for bare words (eg. "Signet"), the search will match any item that has the word in the entire text of the default indices, which includes `name`, `type`, `subtype`, `category`, `equipmentLocation`, and `binding`. - When searching in an index (eg. "name = Signet"), using the `=` operator will search in a prefix style, meaning the things you are searching for must start with what you type. To support full-text searching of each field, use the `%=` operator. - - If you would like to exclude items in gear sets from your searches, the easiest way to do so is add `and not category = "gear:"` to the end of your search. (eg. `slot = finger or slot = neck and not (category = 'gear:')` will highlight all rings and necklaces that are NOT part of a gear set) - - -Search allows you to find items by the following indices: - -Strings: - - `name` (name = "Tome of Unstable Power") The name of the item. - - `type` (type = Armor) The type of the item. - - `subtype` (subtype = Miscellaneous) The subtype of the item. - - `category` (category = "Gear: MW DPS") The category that the item is sorted - into in BetterBags. - - `equipmentLocation` or `slot` (slot = Trinket) The item slot that the gear is for. - - `expansion` or `exp` (expansion = DF) The expansion that the item is from. - Values are: Classic, BC, WotLK, Cata, MoP, WoD, Legion, BfA, SL, DF, TWW. - - `equipmentSet` (equipmentSet = "MW DPS") The equipment set(s) that a piece - of gear is part of. - - `guid` (guid = 'item-60-0-4000000CAEA5CBE3') The GUID of the item. - - `binding` (binding = boe) The text description of the binding type of the item. - Values include nonbinding, boe, bou, quest, soulbound, refundable, - warbound, bnet, or wue. - Note: soulbound and warbound are only available on Retail. - -Numbers: - - `level` or `ilvl` (ilvl = 528) The item level of the item. - - `rarity` (rarity = epic or rarity = 4) The rarity of the item. - Poor = 0, Common = 1, Uncommon = 2, Rare = 3, Epic = 4, Legendary = 5. - - `id` (id = 212685) The internal itemID of the item. - - `stackCount` or `count` (stackCount = 1) The number of items in the stack. - NOTE: Currently count only takes into account the stack size of the - "base" stack when using virtual stacks. - - `class` (class = 4) The numeric representation of itemType. - - `subclass` (subclass = 0) The numeric representation of itemSubtype. - - `bagid` (bagid = 2) The location of the bag containing the item. - BagID is 0 for the main backpack, 1-4 for the bags, - 5 for a reagent bag (Retail only), -1 for the main bank window, - 6-12 for bank bags, -3 for the reagent bank, - -2 for the keyring (Classic only), and 13-17 for warbank tabs. - - `slotid` (slotid = 11) The slot that the item is in, in the bag that contains it. - - `bindtype` (bindtype = 1) The binding type returned by GetItemInfo(). - 1 = BoP, 2 = BoE, 3 = BoU, 4 = Quest, 7 = BtA, 8 = BtW, 9 = WuE. - -Booleans: - - `reagent` (reagent = false) Is the item classified as a reagent? - - `bound` (bound = true) Is the item bound to the character or warband? - - `isbound` (isbound = true) Is the item bound to the character or account? - - `quest` (quest = false) Is the item for a non-active quest? - - `activeQuest` (activeQuest = false) Is the item for an active quest? - -Logical Operators: - - `AND` (slot = Legs AND ilvl > 500) Items must match both sides of the AND. - - `OR` (slot = Legs OR ilvl > 500) Items can match either or both sides of the OR. - - `NOT` (NOT slot = Legs) Items must not match the NOT. - - `(` and `)` To group search terms. - - `=` (type = Armor, ilvl = 506) Items with a value equal to the right side. - - `%=` (slot %= Hand) Items with the value in the full text of the field(s). - - `!=` (expansion != DF) Items with a value not equal to the right side. - - `"` or `'` (slot = "Main Hand") To search for a multi-word string. - -Comparison Operators: - Note: These only work on numerical fields. Rarity can be used with these. - - `>` Items with a value greater than the number on the right side. - - `<` Items with a value less than the number on the right side. - - `>=` Items with a value greater than or equal to the number on the right side. - - `<=` Items with a value less than or equal to the number on the right side. - ]]) - }) -end diff --git a/core/constants.lua b/core/constants.lua index 59b18b2f..60982930 100644 --- a/core/constants.lua +++ b/core/constants.lua @@ -490,6 +490,19 @@ const.INVENTORY_TYPE_TO_INVENTORY_SLOTS = { [Enum.InventoryType.IndexRangedrightType] = {INVSLOT_MAINHAND}, } +-- FormLayout defines the layout type for a form. +---@enum FormLayoutType +const.FORM_LAYOUT = { + -- TwoColumn is a form layout that has the section titles + -- on the left and form elements on the right. The section + -- title is pinned to the top as you scroll. + TWO_COLUMN = 1, + -- A stacked form is a simple form layout with all form + -- elements stacked on top of each other. Section titles + -- pin to the top as you scroll. + STACKED = 2, +} + ---@class SizeInfo ---@field columnCount number ---@field itemsPerRow number @@ -710,6 +723,7 @@ const.DATABASE_DEFAULTS = { categoryFilters = { [const.BAG_KIND.BACKPACK] = { Type = true, + Subtype = false, Expansion = false, TradeSkill = false, RecentItems = true, @@ -718,6 +732,7 @@ const.DATABASE_DEFAULTS = { }, [const.BAG_KIND.BANK] = { Type = true, + Subtype = false, Expansion = false, TradeSkill = false, RecentItems = true, diff --git a/core/database.lua b/core/database.lua index 87072bcf..83de1151 100644 --- a/core/database.lua +++ b/core/database.lua @@ -95,6 +95,10 @@ function DB:SetCategoryFilter(kind, filter, value) DB.data.profile.categoryFilters[kind][filter] = value end +function DB:GetCategoryFilters(kind) + return DB.data.profile.categoryFilters[kind] +end + ---@param show boolean function DB:SetShowBagButton(show) DB.data.profile.showBagButton = show diff --git a/core/init.lua b/core/init.lua index 5760214d..39673276 100644 --- a/core/init.lua +++ b/core/init.lua @@ -84,6 +84,9 @@ local context = addon:GetModule('Context') ---@class Debug: AceModule local debug = addon:GetModule('Debug') +---@class Form: AceModule +local form = addon:GetModule('Form') + ---@class BagFrames ---@field Backpack Bag ---@field Bank Bag @@ -175,6 +178,18 @@ function addon:GetBagFromBagID(bagid) end end +---@param kind BagKind +---@return Bag +function addon:GetBagFromKind(kind) + if kind == const.BAG_KIND.BACKPACK then + return addon.Bags.Backpack + elseif kind == const.BAG_KIND.BANK then + return addon.Bags.Bank + else + error("invalid kind") + end +end + -- HideBlizzardBags will hide the default Blizzard bag frames. function addon:HideBlizzardBags() local sneakyFrame = CreateFrame("Frame", "BetterBagsSneakyFrame") @@ -245,6 +260,7 @@ function addon:OnEnable() views:Enable() searchCategoryConfig:Enable() async:Enable() + form:Enable() self:HideBlizzardBags() local rootctx = context:New('addon_enable') diff --git a/data/refresh.lua b/data/refresh.lua index 41ee3b1e..cebc2d1f 100644 --- a/data/refresh.lua +++ b/data/refresh.lua @@ -81,6 +81,7 @@ function refresh:StartUpdate(ctx) local updateBackpack = false local updateBank = false local sortBackpack = false + local wipeAndRefreshAll = false for _, event in pairs(self.UpdateQueue) do if event.ctx:GetBool("wipe") then -- Prevent full wipes from happening in combat. @@ -103,6 +104,8 @@ function refresh:StartUpdate(ctx) end elseif event.eventName == 'BAG_UPDATE_BANK' then updateBank = true + elseif event.eventName == 'WIPE_AND_REFRESH_ALL' then + wipeAndRefreshAll = true elseif const.BANK_BAGS[event.args[1]] then updateBank = true elseif const.REAGENTBANK_BAGS[event.args[1]] then @@ -115,6 +118,13 @@ function refresh:StartUpdate(ctx) end wipe(self.UpdateQueue) + if wipeAndRefreshAll then + items:ClearItemCache(ctx) + ctx:Set('wipe', true) + updateBackpack = true + updateBank = true + end + if sortBackpack then self.isUpdateRunning = false self.isSorting = true @@ -235,7 +245,8 @@ function refresh:OnEnable() -- Register when all bags should be wiped and reloaded. events:RegisterMessage('bags/FullRefreshAll', function(ctx) - items:WipeAndRefreshAll(ctx) + table.insert(refresh.UpdateQueue, {eventName = 'WIPE_AND_REFRESH_ALL', args = {}, ctx = ctx}) + self:StartUpdate(ctx) end) -- Register for when bags are done drawing. diff --git a/forms/form.lua b/forms/form.lua new file mode 100644 index 00000000..685c59ee --- /dev/null +++ b/forms/form.lua @@ -0,0 +1,153 @@ +local addonName = ... ---@type string + +---@class BetterBags: AceAddon +local addon = LibStub('AceAddon-3.0'):GetAddon(addonName) + +---@class Constants: AceModule +local const = addon:GetModule('Constants') + +---@class Themes: AceModule +local themes = addon:GetModule('Themes') + +---@class Debug: AceModule +local debug = addon:GetModule('Debug') + +---@class Database: AceModule +local db = addon:GetModule('Database') + +---@class Events: AceModule +local events = addon:GetModule('Events') + +---@class Bucket: AceModule +local bucket = addon:GetModule('Bucket') + +---@class Animations: AceModule +local animations = addon:GetModule('Animations') + +---@class FormLayouts: AceModule +local layouts = addon:GetModule('FormLayouts') + +---@class (exact) Form: AceModule +local form = addon:NewModule('Form') + +---@class (exact) FormFrame: AceModule +---@field layout FormLayout +---@field frame Frame This is the container frame for the form window. +---@field fadeIn AnimationGroup +---@field fadeOut AnimationGroup +---@field ScrollBox WowScrollBox This is the scroll box that contains the inner frame. +---@field ScrollBar MinimalScrollBar This is the scrollbar that controls the scroll box. +---@field inner Frame This is the inner frame that contains the form elements. +local formFrame = {} + +---@class FormCreateOptions +---@field title string +---@field layout FormLayoutType +---@field index boolean + +local formCounter = 0 +-- Create will create a new form with the given layout. +---@param opts FormCreateOptions +---@return FormFrame +function form:Create(opts) + local l = setmetatable({}, {__index = formFrame}) --[[@as FormFrame]] + l.frame = CreateFrame('Frame', format("BetterBagsForm%d%s", formCounter, opts.title), UIParent) + formCounter = formCounter + 1 + + l.frame:SetFrameStrata("DIALOG") + l.frame:SetFrameLevel(500) + + l.ScrollBox = CreateFrame("Frame", nil, l.frame, "WowScrollBox") --[[@as WowScrollBox]] + l.ScrollBox:SetPoint("TOPLEFT", l.frame, "TOPLEFT", 4, -22) + l.ScrollBox:SetPoint("BOTTOMRIGHT", l.frame, "BOTTOMRIGHT", 0, 4) + + l.ScrollBar = CreateFrame("EventFrame", nil, l.ScrollBox, "MinimalScrollBar") --[[@as MinimalScrollBar]] + l.ScrollBar:SetPoint("TOPLEFT", l.frame, "TOPRIGHT", -16, -28) + l.ScrollBar:SetPoint("BOTTOMLEFT", l.frame, "BOTTOMRIGHT", -16, 6) + + l.ScrollBox:SetInterpolateScroll(true) + l.ScrollBar:SetInterpolateScroll(true) + l.ScrollBar:SetHideIfUnscrollable(true) + + local view = CreateScrollBoxLinearView() + view:SetPanExtent(60) + + l.inner = CreateFrame('Frame', nil, l.ScrollBox) + l.inner.scrollable = true + + l.frame:EnableMouse(true) + l.frame:SetMovable(true) + l.frame:SetScript("OnMouseDown", l.frame.StartMoving) + l.frame:SetScript("OnMouseUp", l.frame.StopMovingOrSizing) + + ScrollUtil.InitScrollBoxWithScrollBar(l.ScrollBox, l.ScrollBar, view) + themes:RegisterSimpleWindow(l.frame, opts.title) + + if opts.layout == const.FORM_LAYOUT.STACKED then + l.layout = layouts:NewStackedLayout(l.inner, l.frame, l.ScrollBox, opts.index) + end + + l.fadeIn, l.fadeOut = animations:AttachFadeGroup(l.frame) + l.frame:Hide() + return l +end + +function formFrame:Refresh() + self.inner:SetHeight(self.layout.height + 25) + self.inner:SetWidth(self.ScrollBox:GetWidth() - 18) +end + +---@param opts FormSectionOptions +function formFrame:AddSection(opts) + self.layout:AddSection(opts) + self:Refresh() +end + +---@param opts FormSubSectionOptions +function formFrame:AddSubSection(opts) + self.layout:AddSubSection(opts) +end + +---@param opts FormSliderOptions +function formFrame:AddSlider(opts) + self.layout:AddSlider(opts) + self:Refresh() +end + +function formFrame:AddInputBoxGroup(opts) + _ = opts +end + +---@param opts FormDropdownOptions +function formFrame:AddDropdown(opts) + self.layout:AddDropdown(opts) + self:Refresh() +end + +function formFrame:AddTextArea(opts) + _ = opts +end + +---@param opts FormCheckboxOptions +function formFrame:AddCheckbox(opts) + self.layout:AddCheckbox(opts) + self:Refresh() +end + +function formFrame:AddButtonGroup(opts) + _ = opts +end + +---@return Frame +function formFrame:GetFrame() + return self.frame +end + +function formFrame:Show() + self.fadeIn:Play() + self.layout:UpdateUnderline() +end + +function formFrame:Hide() + self.fadeOut:Play() +end \ No newline at end of file diff --git a/forms/layouts/layout.lua b/forms/layouts/layout.lua new file mode 100644 index 00000000..27ec1919 --- /dev/null +++ b/forms/layouts/layout.lua @@ -0,0 +1,80 @@ +local addonName = ... ---@type string + +---@class BetterBags: AceAddon +local addon = LibStub('AceAddon-3.0'):GetAddon(addonName) + +---@class FormLayouts: AceModule +local layouts = addon:NewModule('FormLayouts') + +--[[ +-- Widgets +]]-- + +---@class (exact) FormSection: Frame +---@field title FontString +---@field description FontString + +---@class (exact) FormSubSection: Frame +---@field title FontString +---@field description FontString + +---@class (exact) FormCheckbox: Frame +---@field title FontString +---@field description FontString +---@field checkbox CheckButton + +---@class (exact) FormDropdown: Frame +---@field title FontString +---@field description FontString +---@field dropdown DropdownButton +---@field classicDropdown Frame + +---@class (exact) FormSlider: Frame +---@field title FontString +---@field description FontString +---@field slider Slider +---@field input EditBox + +--[[ +-- Widget Options +]]-- + +---@class (exact) FormSectionOptions +---@field title string +---@field description string + +---@class (exact) FormSubSectionOptions +---@field title string +---@field description string + +---@class (exact) FormCheckboxOptions +---@field title string +---@field description string +---@field getValue fun(ctx: Context): boolean +---@field setValue fun(ctx: Context, value: boolean) + +---@class (exact) FormDropdownOptions +---@field title string +---@field description string +---@field items string[] +---@field getValue fun(ctx: Context, value: string): boolean +---@field setValue fun(ctx: Context, value: string) + +---@class (exact) FormSliderOptions +---@field title string +---@field description string +---@field min number +---@field max number +---@field step number +---@field getValue fun(ctx: Context): number +---@field setValue fun(ctx: Context, value: number) + +---@class (exact) FormLayout +---@field targetFrame Frame +---@field height number +---@field UpdateUnderline fun(self: FormLayout) +---@field AddSection fun(self: FormLayout, opts: FormSectionOptions) +---@field AddCheckbox fun(self: FormLayout, opts: FormCheckboxOptions) +---@field AddDropdown fun(self: FormLayout, opts: FormDropdownOptions) +---@field AddSubSection fun(self: FormLayout, opts: FormSubSectionOptions) +---@field AddSlider fun(self: FormLayout, opts: FormSliderOptions) \ No newline at end of file diff --git a/forms/layouts/stacked.lua b/forms/layouts/stacked.lua new file mode 100644 index 00000000..e7f05e04 --- /dev/null +++ b/forms/layouts/stacked.lua @@ -0,0 +1,504 @@ +local addonName = ... ---@type string + +---@class BetterBags: AceAddon +local addon = LibStub('AceAddon-3.0'):GetAddon(addonName) + +---@class Debug: AceModule +local debug = addon:GetModule('Debug') + +---@class Context: AceModule +local context = addon:GetModule('Context') + +---@class (exact) FormLayouts: AceModule +local layouts = addon:GetModule('FormLayouts') + +---@class (exact) StackedLayout: FormLayout +---@field nextFrame Frame +---@field nextIndex Frame +---@field baseFrame Frame +---@field indexFrame Frame +---@field underline Frame +---@field sections {point: Frame, button: Button}[] +---@field scrollBox WowScrollBox +---@field height number +---@field index boolean +local stackedLayout = {} + +---@param targetFrame Frame +---@param baseFrame Frame +---@param scrollBox WowScrollBox +---@param index boolean +---@return FormLayout +function layouts:NewStackedLayout(targetFrame, baseFrame, scrollBox, index) + local l = setmetatable({}, {__index = stackedLayout}) --[[@as StackedLayout]] + l.targetFrame = targetFrame + l.nextFrame = targetFrame + l.height = 0 + l.index = index + l.baseFrame = baseFrame + l.scrollBox = scrollBox + l.sections = {} + if index then + l:setupIndex() + end + return l +end + +---@package +function stackedLayout:setupIndex() + self.indexFrame = CreateFrame("Frame", nil, self.baseFrame) --[[@as Frame]] + self.indexFrame:SetPoint("TOPLEFT", self.baseFrame, "TOPLEFT", 10, -20) + self.indexFrame:SetPoint("BOTTOM", self.baseFrame, "BOTTOM", 0, 0) + self.indexFrame:SetWidth(120) + + local underline = self:createDividerLineLeft(self.indexFrame) + self.underline = underline + self.scrollBox:RegisterCallback(BaseScrollBoxEvents.OnScroll, function() + self:UpdateUnderline() + end) + self.nextIndex = self.indexFrame +end + +---@private +---@param offset number +function stackedLayout:scrollToOffset(offset) + local scrollRange = self.scrollBox:GetDerivedScrollRange() + if scrollRange > 0 then + local scrollPercentage = offset / scrollRange + self.scrollBox:SetScrollPercentage(scrollPercentage) + end +end + +---@package +---@param title string +---@param point Frame +---@param sub? boolean +function stackedLayout:addIndex(title, point, sub) + if not self.index then return end + local indexButton = CreateFrame("Button", nil, self.indexFrame) --[[@as Button]] + indexButton:SetSize(100, 24) + if sub then + indexButton:SetNormalFontObject("GameFontNormal") + else + indexButton:SetNormalFontObject("GameFontNormalLarge") + end + local fs = indexButton:GetNormalFontObject() + fs:SetTextColor(1, 1, 1) + fs:SetJustifyH("LEFT") + indexButton:SetText(sub and " " .. title or title) + + indexButton:SetScript("OnClick", function() + local targetTop = point:GetTop() + local parentTop = self.targetFrame:GetTop() + if addon.isRetail then + self.scrollBox:ScrollToOffset((parentTop - targetTop) + 1) + else + self:scrollToOffset((parentTop - targetTop) + 1) + end + end) + + if self.nextIndex == self.indexFrame then + indexButton:SetPoint("TOPLEFT", self.indexFrame, "TOPLEFT", 5, -10) + else + indexButton:SetPoint("TOPLEFT", self.nextIndex, "BOTTOMLEFT", 0, -5) + end + table.insert(self.sections, {point = point, button = indexButton}) + self:UpdateUnderline() + self.nextIndex = indexButton +end + +function stackedLayout:UpdateUnderline() + if not self.index then return end + for i, section in ipairs(self.sections) do + local targetTop = section.point:GetTop() + local parentTop = self.targetFrame:GetTop() + if parentTop == nil then break end + if i == #self.sections and self.scrollBox:GetDerivedScrollOffset() + 100 > parentTop - targetTop then + local uSection = self.sections[i] + self.underline:SetPoint("TOPLEFT", uSection.button, "BOTTOMLEFT", 0, 0) + self.underline:SetPoint("TOPRIGHT", uSection.button, "BOTTOMRIGHT", 0, 0) + break + end + if self.scrollBox:GetDerivedScrollOffset() + 100 <= parentTop - targetTop then + local uSection = i == 1 and section or self.sections[i - 1] + self.underline:SetPoint("TOPLEFT", uSection.button, "BOTTOMLEFT", 0, 0) + self.underline:SetPoint("TOPRIGHT", uSection.button, "BOTTOMRIGHT", 0, 0) + break + end + end + self.underline:Show() + self.underline:GetTop() +end + +---@private +---@param t Frame +---@param container Frame +---@param indent? number +function stackedLayout:alignFrame(t, container, indent) + indent = indent or 0 + if t == self.targetFrame then + if self.index then + container:SetPoint("TOPLEFT", t, "TOPLEFT", self.indexFrame:GetWidth() + 10 + indent, -10) + else + container:SetPoint("TOPLEFT", t, "TOPLEFT", 10 + indent, -10) + end + container:SetPoint("TOPRIGHT", t, "TOPRIGHT", -20, -10) + self.height = self.height + 10 + else + container:SetPoint("TOPLEFT", t, "BOTTOMLEFT", 0 + indent, -20) + container:SetPoint("RIGHT", self.targetFrame, "RIGHT", -20, 0) + self.height = self.height + 20 + end +end + +---@private +---@param container Frame +---@param title string +---@param color? table +---@return FontString +function stackedLayout:createTitle(container, title, color) + local titleFont = container:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge") + if color then + titleFont:SetTextColor(unpack(color)) + else + titleFont:SetTextColor(1, 1, 1) + end + titleFont:SetJustifyH("LEFT") + titleFont:SetText(title) + return titleFont +end + +---@private +---@param container Frame +---@param description string +---@param color? table +---@return FontString +function stackedLayout:createDescription(container, description, color) + local descriptionFont = container:CreateFontString(nil, "OVERLAY", "GameFontHighlight") + if color then + descriptionFont:SetTextColor(unpack(color)) + else + descriptionFont:SetTextColor(1, 1, 1) + end + descriptionFont:SetJustifyH("LEFT") + descriptionFont:SetText(description) + descriptionFont:SetWordWrap(true) + descriptionFont:SetNonSpaceWrap(true) + return descriptionFont +end + +---@private +---@param parent Frame +---@return Frame +function stackedLayout:createDividerLineMiddle(parent) + local container = CreateFrame("Frame", nil, parent) + local white = CreateColor(1, 1, 1, 1) + local faded = CreateColor(1, 1, 1, 0.2) + local left = container:CreateTexture(nil, "ARTWORK") + + left:SetGradient("HORIZONTAL", faded, white) + left:SetColorTexture(1, 1, 1, 1) + left:SetHeight(1) + left:SetWidth(100) + + local middle = container:CreateTexture(nil, "ARTWORK") + middle:SetColorTexture(1, 1, 1, 1) + middle:SetHeight(1) + + local right = container:CreateTexture(nil, "ARTWORK") + right:SetGradient("HORIZONTAL", white, faded) + right:SetColorTexture(1, 1, 1, 1) + right:SetHeight(1) + right:SetWidth(100) + + left:SetPoint("LEFT", container, "LEFT") + middle:SetPoint("LEFT", left, "RIGHT") + middle:SetPoint("RIGHT", right, "LEFT") + right:SetPoint("RIGHT", container, "RIGHT") + container:SetHeight(3) + return container +end + +---@private +---@param parent Frame +---@return Frame +function stackedLayout:createDividerLineLeft(parent) + local container = CreateFrame("Frame", nil, parent) + local white = CreateColor(1, 1, 1, 1) + local faded = CreateColor(1, 1, 1, 0.2) + + local left = container:CreateTexture(nil, "ARTWORK") + left:SetColorTexture(1, 1, 1, 1) + left:SetHeight(1) + + local right = container:CreateTexture(nil, "ARTWORK") + right:SetGradient("HORIZONTAL", white, faded) + right:SetColorTexture(1, 1, 1, 1) + right:SetHeight(1) + right:SetWidth(100) + + left:SetPoint("LEFT", container, "LEFT") + right:SetPoint("LEFT", left, "RIGHT") + right:SetPoint("RIGHT", container, "RIGHT") + + container:SetHeight(3) + return container +end + +---@param opts FormSectionOptions +function stackedLayout:AddSection(opts) + local t = self.nextFrame + local container = CreateFrame("Frame", nil, t) --[[@as FormSection]] + self:alignFrame(t, container) + + container.title = self:createTitle(container, opts.title) + container.title:SetPoint("TOPLEFT", container, "TOPLEFT") + + container.description = self:createDescription(container, opts.description) + container.description:SetPoint("TOPLEFT", container.title, "BOTTOMLEFT", 0, -5) + + local div = self:createDividerLineLeft(container) + div:SetPoint("TOPLEFT", container.description, "BOTTOMLEFT", 0, -5) + div:SetPoint("RIGHT", container, "RIGHT", -10, 0) + + container:SetHeight(container.title:GetHeight() + container.description:GetHeight() + 18) + + self:addIndex(opts.title, container) + self.nextFrame = container + self.height = self.height + container:GetHeight() +end + +---@param opts FormSubSectionOptions +function stackedLayout:AddSubSection(opts) + local t = self.nextFrame + local container = CreateFrame("Frame", nil, t) --[[@as FormSubSection]] + self:alignFrame(t, container) + + local titleContainer = CreateFrame("Frame", nil, container) + titleContainer:SetPoint("TOPLEFT", container, "TOPLEFT", 37, 0) + titleContainer:SetPoint("RIGHT", container, "RIGHT", 0, 0) + container.title = self:createTitle(titleContainer, opts.title) + container.title:SetPoint("TOPLEFT", titleContainer, "TOPLEFT") + container.description = self:createDescription(container, opts.description) + container.description:SetPoint("TOPLEFT", container.title, "BOTTOMLEFT", 0, -5) + local div = self:createDividerLineMiddle(container) + div:SetPoint("TOPLEFT", container.description, "BOTTOMLEFT", 0, -5) + div:SetPoint("RIGHT", container, "RIGHT", -10, 0) + container:SetHeight(container.title:GetLineHeight() + container.description:GetLineHeight() + 33) + self:addIndex(opts.title, container, true) + self.nextFrame = container + self.height = self.height + container:GetHeight() +end + +---@param opts FormCheckboxOptions +function stackedLayout:AddCheckbox(opts) + local t = self.nextFrame + local container = CreateFrame("Frame", nil, t) --[[@as FormCheckbox]] + self:alignFrame(t, container) + + container.checkbox = CreateFrame("CheckButton", nil, container, "UICheckButtonTemplate") --[[@as CheckButton]] + container.checkbox:SetPoint("TOPLEFT", container, "TOPLEFT") + addon.SetScript(container.checkbox, "OnClick", function(ctx) + opts.setValue(ctx, container.checkbox:GetChecked()) + end) + container.checkbox:SetChecked(opts.getValue(context:New('Checkbox_Load'))) + + container.title = self:createTitle(container, opts.title, {0.75, 0.75, 0.75}) + container.title:SetPoint("LEFT", container.checkbox, "RIGHT", 5, 0) + container.title:SetPoint("RIGHT", container, "RIGHT", 0, 0) + + container.description = self:createDescription(container, opts.description, {0.75, 0.75, 0.75}) + container.description:SetPoint("TOPLEFT", container.title, "BOTTOMLEFT", 0, -5) + container.description:SetPoint("RIGHT", container, "RIGHT", 0, 0) + + container:SetHeight(container.title:GetLineHeight() + container.description:GetLineHeight() + 25) + self.nextFrame = container + self.height = self.height + container:GetHeight() +end + +---@private +---@param opts FormDropdownOptions +function stackedLayout:addDropdownRetail(opts) + local t = self.nextFrame + local container = CreateFrame("Frame", nil, t) --[[@as FormDropdown]] + self:alignFrame(t, container) + + container.title = self:createTitle(container, opts.title, {0.75, 0.75, 0.75}) + container.title:SetPoint("TOPLEFT", container, "TOPLEFT", 37, 0) + + container.description = self:createDescription(container, opts.description, {0.75, 0.75, 0.75}) + container.description:SetPoint("TOPLEFT", container.title, "BOTTOMLEFT", 0, -5) + + container.dropdown = CreateFrame("DropdownButton", nil, container, "WowStyle1DropdownTemplate") --[[@as DropdownButton]] + container.dropdown:SetPoint("TOPLEFT", container.description, "BOTTOMLEFT", 0, -5) + container.dropdown:SetPoint("RIGHT", container, "RIGHT", 0, 0) + + container.dropdown:SetupMenu(function(_, root) + for _, item in ipairs(opts.items) do + root:CreateCheckbox(item, function(value) + local ctx = context:New('Dropdown_Get') + return opts.getValue(ctx, value) + end, + function(value) + local ctx = context:New('Dropdown_Set') + opts.setValue(ctx, value) + end, item) + end + end) + + container.dropdown:GenerateMenu() + + container:SetHeight( + container.title:GetLineHeight() + + container.description:GetLineHeight() + + container.dropdown:GetHeight() + + 25 + ) + self.nextFrame = container + self.height = self.height + container:GetHeight() +end + +---@private +---@param opts FormDropdownOptions +function stackedLayout:addDropdownClassic(opts) + local t = self.nextFrame + local container = CreateFrame("Frame", nil, t) --[[@as FormDropdown]] + self:alignFrame(t, container) + + container.title = self:createTitle(container, opts.title, {0.75, 0.75, 0.75}) + container.title:SetPoint("TOPLEFT", container, "TOPLEFT", 37, 0) + + container.description = self:createDescription(container, opts.description, {0.75, 0.75, 0.75}) + container.description:SetPoint("TOPLEFT", container.title, "BOTTOMLEFT", 0, -5) + + container.classicDropdown = CreateFrame("Frame", nil, container, "UIDropDownMenuTemplate") --[[@as Frame]] + container.classicDropdown:SetPoint("TOPLEFT", container.description, "BOTTOMLEFT", 0, -5) + container.classicDropdown:SetPoint("RIGHT", container, "RIGHT", 0, 0) + + -- Create and bind the initialization function to the dropdown menu + UIDropDownMenu_Initialize(container.classicDropdown, function(_, level, _) + for _, item in ipairs(opts.items) do + local info = UIDropDownMenu_CreateInfo() + info.text = item + info.checked = function() + local ctx = context:New('Dropdown_Get') + return opts.getValue(ctx, item) + end + info.func = function() + UIDropDownMenu_SetText(container.classicDropdown, item) + local ctx = context:New('Dropdown_Set') + opts.setValue(ctx, item) + end + UIDropDownMenu_AddButton(info, level) + end + end) + + for _, item in ipairs(opts.items) do + local ctx = context:New('Dropdown_Load') + if opts.getValue(ctx, item) then + UIDropDownMenu_SetText(container.classicDropdown, item) + break + end + end + + container:SetHeight( + container.title:GetLineHeight() + + container.description:GetLineHeight() + + container.classicDropdown:GetHeight() + + 25 + ) + self.nextFrame = container + self.height = self.height + container:GetHeight() +end + +---@param opts FormDropdownOptions +function stackedLayout:AddDropdown(opts) + if addon.isRetail then + self:addDropdownRetail(opts) + return + end + self:addDropdownClassic(opts) +end + +---@param opts FormSliderOptions +function stackedLayout:AddSlider(opts) + local t = self.nextFrame + local container = CreateFrame("Frame", nil, t) --[[@as FormSlider]] + self:alignFrame(t, container) + + container.title = self:createTitle(container, opts.title, {0.75, 0.75, 0.75}) + container.title:SetPoint("TOPLEFT", container, "TOPLEFT", 37, 0) + + container.description = self:createDescription(container, opts.description, {0.75, 0.75, 0.75}) + container.description:SetPoint("TOPLEFT", container.title, "BOTTOMLEFT", 0, -5) + + if addon.isRetail then + container.slider = CreateFrame("Slider", nil, container, "UISliderTemplate") --[[@as Slider]] + else + container.slider = CreateFrame("Slider", nil, container, "HorizontalSliderTemplate") --[[@as Slider]] + end + container.slider:SetPoint("TOPLEFT", container.description, "BOTTOMLEFT", 0, -5) + container.slider:SetPoint("RIGHT", container, "RIGHT", 0, 0) + container.slider:SetOrientation("HORIZONTAL") + container.slider:SetHeight(20) + container.slider:SetMinMaxValues(opts.min, opts.max) + container.slider:SetValueStep(opts.step) + container.slider:SetObeyStepOnDrag(true) + addon.SetScript(container.slider, "OnValueChanged", function(ctx, _, value, user) + opts.setValue(ctx, value) + if user then + container.input:SetText(tostring(value)) + end + end) + + container.input = CreateFrame("EditBox", nil, container, "InputBoxTemplate") --[[@as EditBox]] + container.input:SetSize(50, 20) + container.input:SetPoint("TOP", container.slider, "BOTTOM", 0, -5) + container.input:SetNumeric(true) + container.input:SetAutoFocus(false) + addon.SetScript(container.input, "OnEditFocusLost", function(_) + local value = tonumber(container.input:GetText()) + if value then + if value < opts.min then + value = opts.min + elseif value > opts.max then + value = opts.max + end + container.slider:SetValue(value) + container.input:SetText(tostring(value)) + else + value = opts.min + end + container.input:SetText(tostring(container.slider:GetValue())) + end) + addon.SetScript(container.input, "OnTextChanged", function(_, _, user) + if user then + local value = tonumber(container.input:GetText()) + if value then + if value < opts.min then + value = opts.min + elseif value > opts.max then + value = opts.max + end + container.slider:SetValue(value) + container.input:SetText(tostring(value)) + else + value = opts.min + end + else + container.input:SetText(tostring(container.slider:GetValue())) + end + end) + + container.slider:SetValue(opts.getValue(context:New('Slider_Load'))) + + container:SetHeight( + container.title:GetLineHeight() + + container.description:GetLineHeight() + + container.slider:GetHeight() + + container.input:GetHeight() + + 30 + ) + self.nextFrame = container + self.height = self.height + container:GetHeight() +end \ No newline at end of file diff --git a/forms/layouts/twocolumn.lua b/forms/layouts/twocolumn.lua new file mode 100644 index 00000000..889165d1 --- /dev/null +++ b/forms/layouts/twocolumn.lua @@ -0,0 +1,4 @@ +local addonName = ... ---@type string + +---@class BetterBags: AceAddon +local addon = LibStub('AceAddon-3.0'):GetAddon(addonName) \ No newline at end of file diff --git a/themes/gw2.lua b/themes/gw2.lua index 9a07092b..91ee2a98 100644 --- a/themes/gw2.lua +++ b/themes/gw2.lua @@ -173,7 +173,8 @@ local gw2Theme = { decoration.title = decoration:CreateFontString(frame:GetName().."GW2_title", "OVERLAY", "GameFontNormal") decoration:SetAllPoints() - decoration:SetFrameStrata("BACKGROUND") + decoration:SetFrameStrata(frame:GetFrameStrata()) + decoration:SetFrameLevel(frame:GetFrameLevel() - 1) decoration:SetBackdrop(gw.BackdropTemplates.Default) decoration.title:ClearAllPoints() @@ -184,9 +185,9 @@ local gw2Theme = { decoration.title:SetText(themes.titles[frame:GetName()]) local close = CreateFrame("Button", nil, decoration, "UIPanelCloseButtonNoScripts") - close:SetPoint("TOPRIGHT", decoration.gwHeader, "TOPRIGHT", -5, -25) - addon.SetScript(close, "OnClick", function(ctx) - frame.Owner:Hide(ctx) + close:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -1, -1) + addon.SetScript(close, "OnClick", function() + frame:Hide() end) close:GwSkinButton(true) else @@ -201,7 +202,8 @@ local gw2Theme = { decoration.title = decoration:CreateFontString(frame:GetName().."GW2_title", "OVERLAY", "GameFontNormal") decoration:SetAllPoints() - decoration:SetFrameStrata("BACKGROUND") + decoration:SetFrameStrata(frame:GetFrameStrata()) + decoration:SetFrameLevel(frame:GetFrameLevel() - 1) decoration:SetBackdrop(gw.BackdropTemplates.Default) decoration.title:ClearAllPoints()