TradeSkillMaster/Core/UI/Elements/GroupSelector.lua

361 lines
12 KiB
Lua

-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- https://tradeskillmaster.com --
-- All Rights Reserved - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
--- Group Selector UI Element Class.
-- A group selector is an element which can be used to prompt the user to select a list of groups, usually for
-- filtering. It is a subclass of the @{Element} class.
-- @classmod GroupSelector
local _, TSM = ...
local L = TSM.Include("Locale").GetTable()
local Table = TSM.Include("Util.Table")
local Analytics = TSM.Include("Util.Analytics")
local Theme = TSM.Include("Util.Theme")
local NineSlice = TSM.Include("Util.NineSlice")
local ScriptWrapper = TSM.Include("Util.ScriptWrapper")
local UIElements = TSM.Include("UI.UIElements")
local GroupSelector = TSM.Include("LibTSMClass").DefineClass("GroupSelector", TSM.UI.Element)
UIElements.Register(GroupSelector)
TSM.UI.GroupSelector = GroupSelector
local private = {}
local TEXT_MARGIN = 8
local ICON_MARGIN = 8
local DEFAULT_CONTEXT = { selected = {}, collapsed = {} }
-- ============================================================================
-- Public Class Methods
-- ============================================================================
function GroupSelector.__init(self)
local frame = UIElements.CreateFrame(self, "Button")
ScriptWrapper.Set(frame, "OnClick", private.OnClick, self)
self.__super:__init(frame)
frame.text = UIElements.CreateFontString(self, frame)
frame.text:SetPoint("TOPLEFT", TEXT_MARGIN, 0)
frame.text:SetPoint("BOTTOMRIGHT", -ICON_MARGIN - TSM.UI.TexturePacks.GetWidth("iconPack.18x18/Add/Default") - TEXT_MARGIN, 0)
frame.text:SetJustifyH("LEFT")
frame.text:SetJustifyV("MIDDLE")
frame.icon = frame:CreateTexture(nil, "ARTWORK")
frame.icon:SetPoint("RIGHT", -ICON_MARGIN, 0)
frame.iconBtn = CreateFrame("Button", nil, frame)
frame.iconBtn:SetAllPoints(frame.icon)
ScriptWrapper.Set(frame.iconBtn, "OnClick", private.OnIconClick, self)
self._nineSlice = NineSlice.New(frame)
self._groupTreeContext = CopyTable(DEFAULT_CONTEXT)
self._hintText = ""
self._selectedText = L["%d groups"]
self._singleSelection = nil
self._onSelectionChanged = nil
self._customQueryFunc = nil
self._showCreateNew = false
end
function GroupSelector.Release(self)
wipe(self._groupTreeContext.collapsed)
wipe(self._groupTreeContext.selected)
self._hintText = ""
self._selectedText = L["%d groups"]
self._singleSelection = nil
self._onSelectionChanged = nil
self._customQueryFunc = nil
self._showCreateNew = false
self.__super:Release()
end
--- Sets the hint text.
-- @tparam GroupSelector self The group selector object
-- @tparam string text The hint text
-- @treturn GroupSelector The group selector object
function GroupSelector.SetHintText(self, text)
assert(type(text) == "string")
self._hintText = text
return self
end
--- Sets the selected text.
-- @tparam GroupSelector self The group selector object
-- @tparam string text The selected text (with a %d formatter for the number of groups)
-- @treturn GroupSelector The group selector object
function GroupSelector.SetSelectedText(self, text)
assert(type(text) == "string" and strmatch(text, "%%d"))
self._selectedText = text
return self
end
--- Registers a script handler.
-- @tparam GroupSelector self The group selector object
-- @tparam string script The script to register for (supported scripts: `OnSelectionChanged`)
-- @tparam function handler The script handler which will be called with the group selector object followed by any
-- arguments to the script
-- @treturn GroupSelector The group selector object
function GroupSelector.SetScript(self, script, handler)
if script == "OnSelectionChanged" then
self._onSelectionChanged = handler
else
error("Unknown GroupSelector script: "..tostring(script))
end
return self
end
--- Sets a function to generate a custom query to use for the group tree
-- @tparam GroupSelector self The group selector object
-- @tparam function func A function to call to create the custom query (gets auto-released by the GroupTree)
-- @treturn GroupSelector The group selector object
function GroupSelector.SetCustomQueryFunc(self, func)
self._customQueryFunc = func
return self
end
--- Adds the "Create New Group" option to the group tree
-- @tparam GroupSelector self The group selector object
-- @treturn GroupSelector The group selector object
function GroupSelector.AddCreateNew(self)
self._showCreateNew = true
return self
end
--- Sets the selection to only handle single selection.
-- @tparam GroupSelector self The group selector object
-- @tparam boolean enabled The state of the single selection
-- @treturn GroupSelector The group selector object
function GroupSelector.SetSingleSelection(self, enabled)
self._singleSelection = enabled
return self
end
--- Returns the single selected group path.
-- @tparam GroupSelector self The group selector object
function GroupSelector.GetSelection(self)
assert(self._singleSelection)
return next(self._groupTreeContext.selected)
end
--- Sets the single selected group path.
-- @tparam GroupSelector self The group selector object
-- @tparam string|table selection The selected group(s) or nil if nothing should be selected
-- @treturn GroupSelector The group selector object
function GroupSelector.SetSelection(self, selection)
wipe(self._groupTreeContext.selected)
if not selection then
return self
end
if self._singleSelection then
self._groupTreeContext.selected[selection] = true
else
for groupPath in pairs(selection) do
self._groupTreeContext.selected[groupPath] = true
end
end
return self
end
--- Returns an iterator for all selected groups.
-- @tparam GroupSelector self The group selector object
-- @return An iterator which iterates over the selected groups and has the following values: `groupPath`
function GroupSelector.SelectedGroupIterator(self)
return pairs(self._groupTreeContext.selected)
end
--- Clears all selected groups.
-- @tparam GroupSelector self The group selector object
-- @tparam boolean silent Don't call the selection changed callback
-- @treturn GroupSelector The group selector object
function GroupSelector.ClearSelectedGroups(self, silent)
wipe(self._groupTreeContext.selected)
if not silent and self._onSelectionChanged then
self:_onSelectionChanged()
end
return self
end
function GroupSelector.Draw(self)
self.__super:Draw()
local frame = self:_GetBaseFrame()
frame.text:SetFont(Theme.GetFont("BODY_BODY2"):GetWowFont())
local numGroups = Table.Count(self._groupTreeContext.selected)
frame.text:SetText(numGroups == 0 and self._hintText or (self._singleSelection and TSM.Groups.Path.Format(next(self._groupTreeContext.selected)) or format(self._selectedText, numGroups)))
TSM.UI.TexturePacks.SetTextureAndSize(frame.icon, numGroups == 0 and "iconPack.18x18/Add/Default" or "iconPack.18x18/Close/Default")
self._nineSlice:SetStyle("rounded")
self._nineSlice:SetVertexColor(Theme.GetColor("ACTIVE_BG"):GetFractionalRGBA())
end
-- ============================================================================
-- Private Class Methods
-- ============================================================================
function GroupSelector._CreateQuery(self)
local query = nil
if self._customQueryFunc then
query = self._customQueryFunc()
else
query = TSM.Groups.CreateQuery()
end
if self._singleSelection then
query:NotEqual("groupPath", TSM.CONST.ROOT_GROUP_PATH)
end
return query
end
-- ============================================================================
-- Private Helper Functions
-- ============================================================================
function private.OnClick(self)
self:GetBaseElement():ShowDialogFrame(UIElements.New("Frame", "frame", "DIALOG")
:SetLayout("VERTICAL")
:SetSize(464, 500)
:SetPadding(8)
:AddAnchor("CENTER")
:SetBackgroundColor("FRAME_BG", true)
:SetMouseEnabled(true)
:AddChild(UIElements.New("Frame", "header")
:SetLayout("HORIZONTAL")
:SetHeight(24)
:SetMargin(0, 0, 0, 8)
:AddChild(UIElements.New("Text", "title")
:SetMargin(32, 8, 0, 0)
:SetFont("BODY_BODY2_MEDIUM")
:SetJustifyH("CENTER")
:SetText(L["Select Group"])
)
:AddChild(UIElements.New("Button", "closeBtn")
:SetBackgroundAndSize("iconPack.24x24/Close/Default")
:SetScript("OnClick", private.DialogCloseBtnOnClick)
)
)
:AddChild(UIElements.New("Frame", "container")
:SetLayout("VERTICAL")
:SetPadding(2)
:SetBackgroundColor("PRIMARY_BG")
:SetBorderColor("ACTIVE_BG")
:AddChild(UIElements.New("Frame", "header")
:SetLayout("HORIZONTAL")
:SetHeight(24)
:SetMargin(8)
:AddChild(UIElements.New("Input", "input")
:AllowItemInsert(true)
:SetIconTexture("iconPack.18x18/Search")
:SetClearButtonEnabled(true)
:SetHintText(L["Search Groups"])
:SetScript("OnValueChanged", private.DialogFilterOnValueChanged)
)
:AddChild(UIElements.New("Button", "expandAllBtn")
:SetSize(24, 24)
:SetMargin(8, 0, 0, 0)
:SetBackground("iconPack.18x18/Expand All")
:SetScript("OnClick", private.ExpandAllGroupsOnClick)
:SetTooltip(L["Expand / Collapse All Groups"])
)
:AddChildIf(not self._singleSelection, UIElements.New("Button", "selectAllBtn")
:SetSize(24, 24)
:SetMargin(8, 0, 0, 0)
:SetBackground("iconPack.18x18/Select All")
:SetScript("OnClick", private.SelectAllGroupsOnClick)
:SetTooltip(L["Select / Deselect All Groups"])
)
)
:AddChildIf(self._showCreateNew, UIElements.New("Button", "createGroup")
:SetHeight(24)
:SetMargin(8, 8, 0, 0)
:SetFont("BODY_BODY2_MEDIUM")
:SetJustifyH("LEFT")
:SetIcon("iconPack.14x14/Add/Circle", "LEFT")
:SetText(L["Create New Group"])
:SetScript("OnClick", private.CreateGroupOnClick)
)
:AddChild(UIElements.New(self._singleSelection and "SelectionGroupTree" or "ApplicationGroupTree", "groupTree")
:SetContext(self)
:SetContextTable(self._groupTreeContext, DEFAULT_CONTEXT)
:SetQuery(self:_CreateQuery())
)
)
:AddChild(UIElements.New("ActionButton", "groupBtn")
:SetHeight(24)
:SetMargin(0, 0, 8, 0)
:SetContext(self)
:SetText(L["Select Group"])
:SetScript("OnClick", private.DialogSelectOnClick)
)
)
end
function private.OnIconClick(self)
if Table.Count(self._groupTreeContext.selected) > 0 then
self:ClearSelectedGroups()
self:Draw()
if self._onSelectionChanged then
self:_onSelectionChanged()
end
else
private.OnClick(self)
end
end
function private.DialogCloseBtnOnClick(button)
local self = button:GetElement("__parent.__parent.groupBtn"):GetContext()
button:GetBaseElement():HideDialog()
self:Draw()
if self._onSelectionChanged then
self:_onSelectionChanged()
end
end
function private.DialogFilterOnValueChanged(input)
input:GetElement("__parent.__parent.groupTree")
:SetSearchString(strlower(input:GetValue()))
:Draw()
end
function private.ExpandAllGroupsOnClick(button)
button:GetElement("__parent.__parent.groupTree")
:ToggleExpandAll()
end
function private.SelectAllGroupsOnClick(button)
button:GetElement("__parent.__parent.groupTree")
:ToggleSelectAll()
end
function private.DialogSelectOnClick(button)
local self = button:GetContext()
button:GetBaseElement():HideDialog()
self:Draw()
if self._onSelectionChanged then
self:_onSelectionChanged()
end
end
function private.CreateGroupOnClick(button)
local newGroupPath = L["New Group"]
if TSM.Groups.Exists(newGroupPath) then
local num = 1
while TSM.Groups.Exists(newGroupPath.." "..num) do
num = num + 1
end
newGroupPath = newGroupPath.." "..num
end
TSM.Groups.Create(newGroupPath)
Analytics.Action("CREATED_GROUP", newGroupPath)
button:GetElement("__parent.groupTree")
:UpdateData()
:SetSelection(newGroupPath)
:Draw()
end