Puppeteer/gui/Settings.lua
2025-08-28 20:02:32 -07:00

1128 lines
48 KiB
Lua

PTSettingsGui = {}
PTUtil.SetEnvironment(PTSettingsGui, PuppeteerSettings)
local util = PTUtil
local colorize = util.Colorize
local compost = AceLibrary("Compost-2.0")
local GetOption = PuppeteerSettings.GetOption
local SetOption = PuppeteerSettings.SetOption
TabFrame = PTGuiLib.Get("tab_frame"):Hide()
function Init()
TabFrame:SetPoint("CENTER")
:SetSize(425, 475)
:SetSimpleBackground(PTGuiComponent.BACKGROUND_DIALOG)
:SetSpecial()
if PTOptions.Debug2 then
TabFrame:Show()
end
local title = PTGuiLib.Get("title", TabFrame)
title:SetPoint("TOP", TabFrame, "TOP", 0, 22)
title:SetHeight(38)
title:SetWidth(170)
title:SetText("Puppeteer Settings")
local closeButton = CreateFrame("Button", nil, TabFrame:GetHandle(), "UIPanelCloseButton")
closeButton:SetPoint("TOPRIGHT", TabFrame:GetHandle(), "TOPRIGHT", 0, 0)
closeButton:SetScript("OnClick", function() TabFrame:Hide() end)
CreateTab_Bindings()
CreateTab_Options()
CreateTab_Customize()
CreateTab_About()
end
OverlayStack = {}
OverlayBlockInputs = {}
function AddOverlayFrame(overlayFrame)
table.insert(OverlayStack, overlayFrame)
local block = PTGuiLib.Get("puppeteer_input_block", TabFrame)
block:SetScript("OnMouseDown", TabFrame:GetScript("OnMouseDown"), true)
block:SetScript("OnMouseUp", TabFrame:GetScript("OnMouseUp"), true)
table.insert(OverlayBlockInputs, block)
block:SetFrameLevel(overlayFrame:GetFrameLevel() + (table.getn(OverlayStack) * 200) - 100)
overlayFrame:SetFrameLevel(overlayFrame:GetFrameLevel() + (table.getn(OverlayStack) * 200))
PTUtil.FixFrameLevels(overlayFrame:GetHandle())
end
function PopOverlayFrame()
local index = table.getn(OverlayStack)
table.remove(OverlayStack, index)
local block = table.remove(OverlayBlockInputs, index)
block:Dispose()
end
EditedBindings = {}
BindingsContext = {Target = "Friendly", Modifier = "None"}
function CreateTab_Bindings()
local container = TabFrame:CreateTab("Bindings")
local selectLoadoutLabel = CreateLabel(container, "Select Loadout")
:SetPoint("TOPLEFT", container, "TOPLEFT", 30, -40)
local selectLoadoutDropdown = CreateDropdown(container, 130)
:SetPoint("LEFT", selectLoadoutLabel, "RIGHT", 5, 0)
:SetDynamicOptions(function(addOption, level, args)
for _, name in ipairs(Puppeteer.GetBindingLoadoutNames()) do
addOption("text", name,
"checked", Puppeteer.GetSelectedBindingsLoadoutName() == name,
"func", args.func)
end
end, {
func = function(self)
local loadoutName = self.text
if Puppeteer.LoadoutEquals(Puppeteer.GetBindings(), EditedBindings) then
Puppeteer.SetSelectedBindingsLoadout(loadoutName)
else
local dialog
dialog = PTGuiLib.Get("simple_dialog", TabFrame)
:SetPoint("CENTER", TabFrame, "CENTER")
:SetTitle("Unsaved Changes")
:SetText("You have unsaved changes to your bindings. What would you like to do?")
:AddButton("Save changes & switch", function()
SaveBindings()
Puppeteer.SetSelectedBindingsLoadout(loadoutName)
PopOverlayFrame()
dialog:Dispose()
end)
:AddButton("Discard changes & switch", function()
Puppeteer.SetSelectedBindingsLoadout(loadoutName)
PopOverlayFrame()
dialog:Dispose()
end)
:AddButton("Carry changes over", function()
local editedBindings = EditedBindings
Puppeteer.SetSelectedBindingsLoadout(loadoutName)
EditedBindings = editedBindings
UpdateBindingsInterface()
PopOverlayFrame()
dialog:Dispose()
end)
:AddButton("Cancel", function()
PopOverlayFrame()
dialog:Dispose()
end)
AddOverlayFrame(dialog)
PlaySound("igMainMenuOpen")
end
end
})
:SetTextUpdater(function(self)
self:SetText(Puppeteer.GetSelectedBindingsLoadoutName())
end)
LoadoutsDropdown = selectLoadoutDropdown
local newLoadout = PTGuiLib.Get("button", container)
:SetPoint("LEFT", selectLoadoutDropdown, "RIGHT", 5, 0)
:SetSize(60, 22)
:SetText("New")
:OnClick(function(self)
if util.GetTableSize(Puppeteer.GetBindingLoadouts()) >= 20 then
DEFAULT_CHAT_FRAME:AddMessage("You cannot create any more loadouts!")
return
end
if Puppeteer.LoadoutEquals(Puppeteer.GetBindings(), EditedBindings) then
PromptNewLoadout()
else
local dialog
dialog = PTGuiLib.Get("simple_dialog", TabFrame)
:SetPoint("CENTER", TabFrame, "CENTER")
:SetTitle("Unsaved Changes")
:SetText("You have unsaved changes to your bindings. What would you like to do?")
:AddButton("Save changes", function()
SaveBindings()
PopOverlayFrame()
dialog:Dispose()
PromptNewLoadout()
end)
:AddButton("Discard changes", function()
LoadBindings()
PopOverlayFrame()
dialog:Dispose()
PromptNewLoadout()
end)
:AddButton("Cancel", function()
PopOverlayFrame()
dialog:Dispose()
end)
AddOverlayFrame(dialog)
PlaySound("igMainMenuOpen")
end
end)
local deleteLoadout = PTGuiLib.Get("button", container)
:SetPoint("LEFT", newLoadout, "RIGHT", 5, 0)
:SetSize(60, 22)
:SetText("Delete")
:OnClick(function()
local loadouts = Puppeteer.GetBindingLoadouts()
local currentLoadoutName = Puppeteer.GetSelectedBindingsLoadoutName()
local anotherLoadoutName
for k, v in pairs(loadouts) do
if k ~= Puppeteer.GetSelectedBindingsLoadoutName() then
anotherLoadoutName = k
break
end
end
if not anotherLoadoutName then
DEFAULT_CHAT_FRAME:AddMessage("Cannot delete the only loadout")
return
end
local dialog = PTGuiLib.Get("simple_dialog", TabFrame)
:SetPoint("CENTER", TabFrame, "CENTER", 0, 40)
dialog:SetTitle("Confirm Delete")
dialog:SetText("Are you sure you want to delete binding loadout '"..currentLoadoutName.."'?")
dialog:AddButton("Yes, delete loadout", function()
dialog:Dispose()
PopOverlayFrame()
Puppeteer.SetSelectedBindingsLoadout(anotherLoadoutName)
loadouts[currentLoadoutName] = nil
end)
dialog:AddButton("No, keep loadout", function()
dialog:Dispose()
PopOverlayFrame()
end)
dialog:PlayOpenSound()
AddOverlayFrame(dialog)
end)
local bindingsForLabel = CreateLabel(container, "Bindings For")
:SetPoint("TOPLEFT", container, "TOPLEFT", 40, -95)
local bindingsForDropdown = CreateDropdown(container, 100)
:SetPoint("LEFT", bindingsForLabel, "RIGHT", 5, 0)
:SetDynamicOptions(function(addOption, level, args)
if not EditedBindings.UseFriendlyForHostile then
for _, option in ipairs(args.options) do
addOption("text", option,
"dropdownText", option,
"initFunc", args.initFunc,
"func", args.func)
end
end
end, {
options = {"Friendly", "Hostile"},
initFunc = function(self, gui)
self.checked = self.text == gui:GetText()
end,
func = function(self)
SetTargetContext(self.text)
UpdateBindingsInterface()
end
})
:SetTextUpdater(function(self)
self:SetText(EditedBindings.UseFriendlyForHostile and "All Targets" or BindingsContext.Target)
end)
BindingsForDropdown = bindingsForDropdown
local useSame = CreateLabel(container, "Universal Bindings")
:SetPoint("LEFT", bindingsForDropdown, "RIGHT", 10, 0)
:ApplyTooltip("Use the same bindings for both friendly and hostile targets")
local universalBindingsCheckbox = CreateCheckbox(container, 20, 20)
:SetPoint("LEFT", useSame, "RIGHT", 5, 0)
:ApplyTooltip("Use the same bindings for both friendly and hostile targets")
:OnClick(function(self)
EditedBindings.UseFriendlyForHostile = self:GetChecked() == 1
SetTargetContext("Friendly")
UpdateBindingsInterface()
end)
UniversalBindingsCheckbox = universalBindingsCheckbox
local keyLabel = CreateLabel(container, "Key")
:SetPoint("TOPLEFT", container, "TOPLEFT", 120, -125)
local keyDropdown = CreateDropdown(container, 150)
:SetPoint("LEFT", keyLabel, "RIGHT", 5, 0)
:SetSimpleOptions(util.GetKeyModifiers(), function(modifier)
return {
text = modifier,
initFunc = function(self, gui)
self.checked = self.text == gui:GetText()
end,
func = function(self, gui)
gui:SetText(self.text)
SetModifierContext(self.text)
UpdateBindingsInterface()
end
}
end, "None")
local interface = PTGuiLib.Get("puppeteer_spell_bind_interface", container)
:SetPoint("TOPLEFT", container, "TOPLEFT", 5, -160)
:SetPoint("BOTTOMRIGHT", container, "BOTTOMRIGHT", -5, 80)
SpellBindInterface = interface
LoadBindings()
local addButton = PTGuiLib.Get("button", container)
:SetPoint("TOP", container, "TOP", 0, -440)
:SetSize(200, 25)
:SetText("Edit Buttons")
:ApplyTooltip("Edit what buttons you use and their names")
:OnClick(function()
local editor = PTGuiLib.Get("puppeteer_button_editor", TabFrame)
:SetPoint("CENTER", TabFrame, "CENTER")
AddOverlayFrame(editor)
end)
local discardButton = PTGuiLib.Get("button", container)
:SetPoint("BOTTOMLEFT", container, "BOTTOMLEFT", 10, 50)
:SetSize(125, 25)
:SetText("Discard Changes")
:OnClick(function()
LoadBindings()
end)
local saveAndCloseButton = PTGuiLib.Get("button", container)
:SetPoint("BOTTOM", container, "BOTTOM", 0, 50)
:SetSize(125, 25)
:SetText("Save & Close")
:OnClick(function()
SaveBindings()
TabFrame:Hide()
end)
local saveButton = PTGuiLib.Get("button", container)
:SetPoint("BOTTOMRIGHT", container, "BOTTOMRIGHT", -10, 50)
:SetSize(125, 25)
:SetText("Save Changes")
:OnClick(function()
SaveBindings()
end)
end
function PromptNewLoadout()
local newLoadout = PTGuiLib.Get("puppeteer_new_loadout", TabFrame)
:SetPoint("CENTER", TabFrame, "CENTER")
AddOverlayFrame(newLoadout)
PlaySound("igMainMenuOpen")
end
function SetTargetContext(friendlyOrHostile)
BindingsContext.Target = friendlyOrHostile
BindingsForDropdown:UpdateText()
end
function SetModifierContext(modifier)
BindingsContext.Modifier = modifier
end
function SetBindingsContext(friendlyOrHostile, modifier)
SetTargetContext(friendlyOrHostile)
SetModifierContext(modifier)
end
function GetBindingsContext()
local targetBindings = EditedBindings.Bindings[BindingsContext.Target]
if not targetBindings then
targetBindings = {}
EditedBindings.Bindings[BindingsContext.Target] = targetBindings
end
local bindings = EditedBindings.Bindings[BindingsContext.Target][BindingsContext.Modifier]
if not bindings then
bindings = {}
EditedBindings.Bindings[BindingsContext.Target][BindingsContext.Modifier] = bindings
end
return bindings
end
function UpdateBindingsInterface()
local bindings = GetBindingsContext()
for _, button in ipairs(PTOptions.Buttons) do
if not bindings[button] then
bindings[button] = {}
end
end
SpellBindInterface:SetBindings(bindings)
end
function ReloadBindingLines()
SpellBindInterface:ClearSpellLines()
for _, button in ipairs(PTOptions.Buttons) do
SpellBindInterface:AddSpellLine(button, PTOptions.ButtonInfo[button].Name or button)
end
end
function LoadBindings()
ReloadBindingLines()
LoadoutsDropdown:UpdateText()
EditedBindings = util.CloneTable(Puppeteer.GetBindings(), true)
UniversalBindingsCheckbox:SetChecked(EditedBindings.UseFriendlyForHostile)
if EditedBindings.UseFriendlyForHostile then
SetTargetContext("Friendly")
end
BindingsForDropdown:UpdateText()
UpdateBindingsInterface()
end
function SaveBindings()
Puppeteer.GetBindingLoadouts()[Puppeteer.GetSelectedBindingsLoadoutName()] = Puppeteer.PruneLoadout(EditedBindings)
LoadBindings()
end
function CreateTab_Options()
local container = TabFrame:CreateTab("Options")
local tabPanel = PTGuiLib.Get("tab_panel", container)
tabPanel:SetPoint("TOPLEFT", container, "TOPLEFT", 5, -28 - 50)
tabPanel:SetPoint("BOTTOMRIGHT", container, "BOTTOMRIGHT", -5, 5)
tabPanel:SetSimpleBackground()
container.TabPanel = tabPanel
CreateTab_Options_Casting(tabPanel)
CreateTab_Options_SpellsTooltip(tabPanel)
CreateTab_Options_Other(tabPanel)
CreateTab_Options_Advanced(tabPanel)
CreateTab_Options_Mods(tabPanel)
end
function CreateTab_Options_Casting(panel)
local container = panel:CreateTab("Casting")
local layout = NewLabeledColumnLayout(container, {150, 310}, -20, 10)
local factory = NewComponentFactory(container, layout)
container.factory = factory
factory:dropdown("Cast When (Mouse)", "What mouse button state to start casting spells at", "CastWhen", {"Mouse Up", "Mouse Down"}, function()
for _, ui in ipairs(Puppeteer.AllUnitFrames) do
ui:RegisterClicks()
end
end)
factory:dropdown("Cast When (Keys)", "What key state to start casting spells at", "CastWhenKey", {"Key Up", "Key Down"})
local resSpell = Puppeteer.ResurrectionSpells[util.GetClass("player")]
local autoResInfo = not resSpell and "This does nothing for your class" or {"Replaces your bound spells with "..resSpell..
" when clicking on a dead ally", "All other types of binds, such as Actions, will not be replaced"}
factory:checkbox("Auto Resurrect", autoResInfo, "AutoResurrect")
factory:checkbox("Target While Casting", {"Target the unit while most bindings run",
"Note that these binding types override this rule:",
"Spell - Always targets unless using SuperWoW",
"Action - Never targets unless specified by action",
"Item - Always targets",
"Multi - Never targets"}, "TargetWhileCasting")
factory:checkbox("Target After Casting", {"Target the unit after most bindings run",
"Note that these binding types override this rule:",
"Multi - Never targets"}, "TargetAfterCasting")
end
function CreateTab_Options_SpellsTooltip(panel)
local container = panel:CreateTab("Spells Tooltip")
local layout = NewLabeledColumnLayout(container, {150, 310}, -20, 10)
local factory = NewComponentFactory(container, layout)
container.factory = factory
factory:checkbox("Enable Spells Tooltip", {"Show the spells tooltip when hovering over unit frames"}, "SpellsTooltip.Enabled")
factory:checkbox("Show % Mana Cost", {"Show the percent mana cost in the spells tooltip",
"Does nothing for non-mana users"}, "SpellsTooltip.ShowManaPercentCost")
layout:column(2):levelAt(1)
factory:checkbox("Show # Mana Cost", {"Show the number mana cost in the spells tooltip",
"Does nothing for non-mana users"}, "SpellsTooltip.ShowManaCost")
layout:column(1)
factory:slider("Hide Casts Above", "Hide cast count if above this threshold", "SpellsTooltip.HideCastsAbove", 0, 50)
factory:slider("Critical Casts Level", "Show yellow text at this threshold", "SpellsTooltip.CriticalCastsLevel", 0, 50)
factory:checkbox("Shortened Keys", "Shortens keys to 1 letter", "SpellsTooltip.AbbreviatedKeys")
layout:column(2):levelAt(1)
factory:checkbox("Colored Keys", "Color code the keys as opposed to all being white", "SpellsTooltip.ColoredKeys")
layout:column(1)
factory:checkbox("Show Power Bar", "Show a power bar in the spells tooltip", "SpellsTooltip.ShowPowerBar", function()
if PTOptions.SpellsTooltip.ShowPowerBar then
Puppeteer.SpellsTooltipPowerBar:Show()
else
Puppeteer.SpellsTooltipPowerBar:Hide()
end
end)
factory:dropdown("Show Power As", "What type of information to show for power amounts", "SpellsTooltip.ShowPowerAs",
{"Power", "Power/Max Power", "Power %"})
factory:dropdown("Attach To", "What the tooltip should be attached to", "SpellsTooltip.AttachTo",
{"Button", "Frame", "Group", "Screen"})
layout:offset(0, 10)
factory:dropdown("Anchor", "Where the tooltip should be anchored", "SpellsTooltip.Anchor",
{"Top Left", "Top Right", "Bottom Left", "Bottom Right"})
factory:checkbox("Show Item Count", {"Show the amount of your bound items", colorize("Warning: This causes lag!", 1, 0.4, 0.4)},
"SpellsTooltip.ShowItemCount")
end
function CreateTab_Options_Other(panel)
local container = panel:CreateTab("Other")
local layout = NewLabeledColumnLayout(container, {150, 220, 300}, -20, 10)
local factory = NewComponentFactory(container, layout)
container.factory = factory
factory:checkbox("Always Show Target", "Always show the target frame, regardless of whether you have a target or not",
"AlwaysShowTargetFrame", function() Puppeteer.CheckTarget() end)
layout:offset(0, 10)
factory:label("Show Targets:")
layout:column(2):levelAt(1)
factory:checkbox("Friendly", {"Show the Target frame when targeting friendlies", "No effect if Always Show Target is checked"},
"ShowTargets.Friendly", function() Puppeteer.CheckTarget() end)
layout:column(3):levelAt(2)
factory:checkbox("Hostile", {"Show the Target frame when targeting hostiles", "No effect if Always Show Target is checked"},
"ShowTargets.Hostile", function() Puppeteer.CheckTarget() end)
layout:column(1)
factory:label("Hide Party Frames:")
layout:column(2):levelAt(1)
factory:checkbox("In Party", {"Hide default party frames while in party", "This may cause issues with other addons"},
"DisablePartyFrames.InParty", function() Puppeteer.CheckPartyFramesEnabled() end)
layout:column(3):levelAt(2)
factory:checkbox("In Raid", {"Hide default party frames while in raid", "This may cause issues with other addons"},
"DisablePartyFrames.InRaid", function() Puppeteer.CheckPartyFramesEnabled() end)
layout:column(1)
factory:checkbox("Hide While Solo", "If enabled, all Puppeteer frames will be hidden when not in a party or raid",
"HideWhileSolo", function() Puppeteer.CheckGroup() end)
local dragAllCheckbox = factory:checkbox("Drag All Frames", {"If enabled, all frames will be moved when dragging",
"Use the inverse key to move a single frame; Opposite effect if disabled"}, "FrameDrag.MoveAll")
layout:ignoreNext()
local inverseDropdown = factory:dropdown("Inverse Key", {"This key will be used to do the opposite of the default drag operation"},
"FrameDrag.AltMoveKey", {"Shift", "Control", "Alt"})
inverseDropdown:SetWidth(80)
inverseDropdown:SetPoint("LEFT", dragAllCheckbox, "RIGHT", 90, 0)
factory:checkbox("Show Heal Predictions", {"See predictions on incoming healing", "Improved predictions if using SuperWoW"},
"UseHealPredictions", function() Puppeteer.UpdateAllIncomingHealing() end)
factory:checkbox("(TWoW) LFT Auto Role", {"Automatically assign roles when joining LFT groups",
"This functionality was tested for 1.18.0 and may break in future updates"}, "LFTAutoRole",
function() Puppeteer.SetLFTAutoRoleEnabled(PTOptions.LFTAutoRole) end)
end
function CreateTab_Options_Advanced(panel)
local container = panel:CreateTab("Advanced")
local layout = NewLabeledColumnLayout(container, {150, 220, 300}, 0, 10)
local factory = NewComponentFactory(container, layout)
container.factory = factory
local TEXT_WIDTH = 370
local scriptsLabel = CreateLabel(container, "Load & Postload Scripts")
:SetPoint("TOP", container, "TOP", 0, -10)
:SetFontSize(14)
local loadScriptInfo = CreateLabel(container, "The Load Script runs after profiles are initialized, but before UIs are created, "..
"making it good for editing profile attributes. GetProfile and CreateProfile are defined locals.")
:SetWidth(TEXT_WIDTH)
:SetPoint("TOP", scriptsLabel, "BOTTOM", 0, -10)
local loadScriptButton = PTGuiLib.Get("button", container)
:SetPoint("TOP", loadScriptInfo, "BOTTOM", 0, -5)
:SetSize(150, 20)
:SetText("Edit Load Script")
:OnClick(function()
local editor
editor = PTGuiLib.Get("puppeteer_load_script_editor", TabFrame)
:SetPoint("CENTER", TabFrame, "CENTER")
:SetTitle("Edit Load Script")
editor:GetEditbox():SetText(PTOptions.Scripts.OnLoad or "")
editor:SetCallback(function(save, data)
if save then
PTOptions.Scripts.OnLoad = data
end
editor:Dispose()
PopOverlayFrame()
end)
editor:GetEditbox():SetFocus()
AddOverlayFrame(editor)
end)
local postLoadScriptInfo = CreateLabel(container, "The Postload Script runs after everything is initialized. "..
"GetProfile and CreateProfile are defined locals.")
:SetWidth(TEXT_WIDTH)
:SetPoint("TOP", loadScriptButton, "BOTTOM", 0, -10)
local postLoadScriptButton = PTGuiLib.Get("button", container)
:SetPoint("TOP", postLoadScriptInfo, "BOTTOM", 0, -5)
:SetSize(150, 20)
:SetText("Edit Postload Script")
:OnClick(function()
local editor
editor = PTGuiLib.Get("puppeteer_load_script_editor", TabFrame)
:SetPoint("CENTER", TabFrame, "CENTER")
:SetTitle("Edit Postload Script")
editor:GetEditbox():SetText(PTOptions.Scripts.OnPostLoad or "")
editor:SetCallback(function(save, data)
if save then
PTOptions.Scripts.OnPostLoad = data
end
editor:Dispose()
PopOverlayFrame()
end)
editor:GetEditbox():SetFocus()
AddOverlayFrame(editor)
end)
local reloadInfo = CreateLabel(container, "A reload or relog is required for any changes to take effect.")
:SetWidth(TEXT_WIDTH)
:SetPoint("TOP", postLoadScriptButton, "BOTTOM", 0, -20)
local reloadButton = PTGuiLib.Get("button", container)
:SetPoint("TOP", reloadInfo, "BOTTOM", 0, -5)
:SetSize(120, 20)
:SetText("Reload UI")
:OnClick(function()
ReloadUI()
end)
local experimentsLabel = CreateLabel(container, "Experiments")
:SetPoint("TOP", reloadButton, "BOTTOM", 0, -20)
:SetFontSize(14)
layout:offset(0, -260)
factory:checkbox("(TWoW) Auto Role", {"If enabled, the Role Action menu shows auto role detection options",
colorize("Using this functionality WILL cause errors and other unexpected behavior", 1, 0.4, 0.4)}, "Experiments.AutoRole",
Puppeteer.InitRoleDropdown)
end
function CreateTab_Options_Mods(panel)
local container, scrollFrame = panel:CreateTab("Mods", true)
local layout = NewLabeledColumnLayout(container, {150, 310}, -20, 10)
local factory = NewComponentFactory(container, layout)
container.factory = factory
local TEXT_WIDTH = 370
local generalInfo = CreateLabel(container, "Some client mods enhance your experience with Puppeteer by enabling additional functionality.")
:SetWidth(TEXT_WIDTH)
:SetPoint("TOP", container, "TOP", 0, -10)
local superWowDetected = util.IsSuperWowPresent()
local unitXPDetected = util.IsUnitXPSP3Present()
local nampowerDetected = util.IsNampowerPresent()
local detectedTexts = {
[true] = colorize("Mod Detected", 0.2, 1, 0.2),
[false] = colorize("Mod Not Detected", 1, 0.2, 0.2)
}
local superWowLabel = CreateLabel(container, "SuperWoW")
:SetPoint("TOP", generalInfo, "BOTTOM", 0, -20)
:SetFontSize(14)
local superWowDetectedLabel = CreateLabel(container, detectedTexts[superWowDetected])
:SetPoint("TOP", superWowLabel, "BOTTOM", 0, -5)
:SetFontSize(10)
:SetFontFlags("OUTLINE")
local superWowInfo = CreateLabel(container, "SuperWoW provides the following enhancements:\n\n"..
"• Enables tracking of many class buff and debuff timers\n"..
"• Enhances spell casting by directly casting on targets rather than split-second target switching tricks\n"..
"• Allows you to see accurate distance to other friendly players and NPCs\n"..
"• Mousing over unit frames properly sets your mouseover target\n"..
"• Shows incoming healing from players that do not have HealComm and predicts more accurate numbers")
:SetJustifyH("LEFT")
:SetWidth(TEXT_WIDTH)
:SetPoint("TOP", superWowDetectedLabel, "BOTTOM", 0, -10)
local superWowLink = CreateLinkEditbox(container, "https://github.com/balakethelock/SuperWoW")
:SetPoint("TOP", superWowInfo, "BOTTOM", 0, -5)
:SetSize(300, 20)
local superWowLinkLabel = CreateLabel(container, "Link:")
:SetPoint("RIGHT", superWowLink, "LEFT", -5, 0)
layout:ignoreNext()
local setMouseoverCheckbox = factory:checkbox("Set Mouseover", {"Requires SuperWoW Mod To Work",
"If enabled, hovering over frames will set your mouseover target"}, "SetMouseover")
:SetPoint("TOP", superWowLink, "BOTTOM", 0, -10)
if not superWowDetected then
setMouseoverCheckbox:Disable()
end
-- UnitXP SP3
local unitXPLabel = CreateLabel(container, "UnitXP SP3")
:SetPoint("TOP", setMouseoverCheckbox, "BOTTOM", 0, -20)
:SetFontSize(14)
local unitXPDetectedLabel = CreateLabel(container, detectedTexts[unitXPDetected])
:SetPoint("TOP", unitXPLabel, "BOTTOM", 0, -5)
:SetFontSize(10)
:SetFontFlags("OUTLINE")
local unitXPInfo = CreateLabel(container, "UnitXP SP3 provides the following enhancements:\n\n"..
"• Displays when units are out of line-of-sight\n"..
"• Allows you to see more accurate distance than SuperWoW and also see distance to enemies")
:SetJustifyH("LEFT")
:SetWidth(TEXT_WIDTH)
:SetPoint("TOP", unitXPDetectedLabel, "BOTTOM", 0, -10)
local unitXPLink = CreateLinkEditbox(container, "https://github.com/allfoxwy/UnitXP_SP3")
:SetPoint("TOP", unitXPInfo, "BOTTOM", 0, -5)
:SetSize(300, 20)
local unitXPLinkLabel = CreateLabel(container, "Link:")
:SetPoint("RIGHT", unitXPLink, "LEFT", -5, 0)
-- Nampower
local nampowerLabel = CreateLabel(container, "Nampower")
:SetPoint("TOP", unitXPLink, "BOTTOM", 0, -20)
:SetFontSize(14)
local nampowerDetectedLabel = CreateLabel(container, detectedTexts[nampowerDetected])
:SetPoint("TOP", nampowerLabel, "BOTTOM", 0, -5)
:SetFontSize(10)
:SetFontFlags("OUTLINE")
local nampowerInfo = CreateLabel(container, "Nampower provides the following enhancements:\n\n"..
"• Allows you to queue spell casts like in modern versions of WoW, drastically increasing casting efficiency")
:SetJustifyH("LEFT")
:SetWidth(TEXT_WIDTH)
:SetPoint("TOP", nampowerDetectedLabel, "BOTTOM", 0, -10)
local nampowerLink = CreateLinkEditbox(container, "https://github.com/pepopo978/nampower")
:SetPoint("TOP", nampowerInfo, "BOTTOM", 0, -5)
:SetSize(300, 20)
local nampowerLinkLabel = CreateLabel(container, "Link:")
:SetPoint("RIGHT", nampowerLink, "LEFT", -5, 0)
scrollFrame:UpdateScrollRange()
end
function CreateTab_Customize()
local container = TabFrame:CreateTab("Customize")
local frameStyleContainer = PTGuiLib.Get("container", container)
:SetSimpleBackground()
:SetPoint("TOPLEFT", container, "TOPLEFT", 5, -26)
:SetPoint("BOTTOMRIGHT", container, "TOPRIGHT", -5, -120)
local layout = NewLabeledColumnLayout(frameStyleContainer, {100, 340}, -35, 10)
local frameSettingsText = CreateLabel(frameStyleContainer, "Frame Group Settings")
:SetPoint("TOP", frameStyleContainer, "TOP", 0, -5)
:SetFontSize(14)
local preferredFrameOrder = {"Party", "Pets", "Raid", "Raid Pets", "Target", "Focus"}
local frameDropdown = CreateLabeledDropdown(frameStyleContainer, "Select Frame", "The frame to edit the attributes of")
:SetWidth(150)
:SetDynamicOptions(function(addOption, level, args)
for _, name in ipairs(preferredFrameOrder) do
if Puppeteer.UnitFrameGroups[name] then
addOption("text", name,
"dropdownText", name,
"initFunc", args.initFunc,
"func", args.func)
end
end
for name, group in pairs(Puppeteer.UnitFrameGroups) do
if not util.ArrayContains(preferredFrameOrder, name) then
addOption("text", name,
"dropdownText", name,
"initFunc", args.initFunc,
"func", args.func)
end
end
end, {
initFunc = function(self, gui)
self.checked = self.text == gui:GetText()
end,
func = function(self, gui)
StyleDropdown:UpdateText()
UpdateFrameOptions()
end
})
:SetText("Party")
FrameDropdown = frameDropdown
layout:layoutComponent(frameDropdown)
local GetSelectedProfileName = PuppeteerSettings.GetSelectedProfileName
local styleDropdown = CreateLabeledDropdown(frameStyleContainer, "Choose Style", "The style of the frame")
:SetWidth(150)
:SetDynamicOptions(function(addOption, level, args)
local profiles = PTProfileManager.GetProfileNames()
for _, profile in ipairs(profiles) do
addOption("text", profile,
"checked", GetSelectedProfileName(frameDropdown:GetText()) == profile,
"func", args.func)
end
end, {
func = function(self, gui)
local selectedFrame = frameDropdown:GetText()
PTOptions.ChosenProfiles[selectedFrame] = self.text
if selectedFrame == "Focus" and not util.IsSuperWowPresent() then
return
end
-- Here's some probably buggy profile hotswapping
local group = Puppeteer.UnitFrameGroups[selectedFrame]
group.profile = GetSelectedProfile(selectedFrame)
local oldUIs = group.uis
group.uis = {}
group:ResetFrameLevel() -- Need to lower frame or the added UIs are somehow under it
for unit, ui in pairs(oldUIs) do
ui:GetRootContainer():SetParent(nil)
-- Forget about the old UI, and cause a fat memory leak why not
ui:GetRootContainer():Hide()
local newUI = PTUnitFrame:New(unit, ui.isCustomUnit)
util.RemoveElement(Puppeteer.AllUnitFrames, ui)
table.insert(Puppeteer.AllUnitFrames, newUI)
local unitUIs = Puppeteer.GetUnitFrames(unit)
util.RemoveElement(unitUIs, ui)
table.insert(unitUIs, newUI)
group:AddUI(newUI, true)
if ui.guidUnit then
newUI.guidUnit = ui.guidUnit
elseif unit ~= "target" then
newUI:Hide()
end
end
Puppeteer.CheckGroup()
group:UpdateUIPositions()
group:ApplyProfile()
gui:UpdateText()
end
})
:SetTextUpdater(function(self)
self:SetText(GetSelectedProfileName(frameDropdown:GetText()))
end)
StyleDropdown = styleDropdown
layout:offset(0, 10):layoutComponent(styleDropdown)
local lockFrameCheckbox = CreateLabeledCheckbox(frameStyleContainer, "Lock Frame", {"If checked, this frame will not be movable",
"Note: This setting is also accessible by right-clicking the group title bar"})
:OnClick(function(self)
local frameName = frameDropdown:GetText()
PuppeteerSettings.SetFrameLocked(frameName, self:GetChecked() == 1)
end)
layout:column(2):layoutComponent(lockFrameCheckbox)
LockFrameCheckbox = lockFrameCheckbox
local hideFrameCheckbox = CreateLabeledCheckbox(frameStyleContainer, "Hide Frame", "If checked, this frame will not be visible")
:OnClick(function(self)
local frameName = frameDropdown:GetText()
PuppeteerSettings.SetFrameHidden(frameName, self:GetChecked() == 1)
Puppeteer.CheckGroup()
end)
layout:layoutComponent(hideFrameCheckbox)
HideFrameCheckbox = hideFrameCheckbox
UpdateFrameOptions()
local overrideContainer = PTGuiLib.Get("scroll_frame", container)
:SetPoint("TOPLEFT", frameStyleContainer, "BOTTOMLEFT", 0, -5)
:SetPoint("BOTTOMRIGHT", container, "BOTTOMRIGHT", -5, 5)
:SetSimpleBackground()
StyleOverrideContainer = overrideContainer
local container = overrideContainer
local styleOverridesText = PTGuiLib.GetText(container, "Style Overrides")
:SetFontSize(14)
:SetPoint("TOP", container, "TOP", 0, -5)
local explainer = PTGuiLib.GetText(container, "Style Overrides are an inelegant, temporary measure to enable customization "..
"of commonly requested unit frame edits. Some settings may be visually incompatible with one another. "..
"This will be replaced in a future date with a system that is much more robust.\n\n"..
colorize("You must reload or relog for any changes to take effect!", 1, 0.5, 0.5))
:SetPoint("TOP", styleOverridesText, "TOP", 0, -20)
:SetWidth(360)
local layout = NewLabeledColumnLayout(container, {160}, -120, 5)
local styleDropdown = CreateLabeledDropdown(container, "Edit Style", "The style to edit the overrides of")
:SetWidth(160)
:SetText("Default")
:SetDynamicOptions(function(addOption, level, args)
local profiles = PTProfileManager.GetProfileNames()
for _, profile in ipairs(profiles) do
addOption("text", profile,
"initFunc", args.initFunc,
"func", args.func)
end
end, {
initFunc = function(self, gui)
self.checked = gui:GetText() == self.text
end,
func = function(self)
SetSelectedStyleOverride(self.text)
end
})
layout:offset(-65, 0):layoutComponent(styleDropdown)
layout:offset(65, 0)
StyleOverrideDropdown = styleDropdown
SetSelectedStyleOverride("Default")
local reloadUI = PTGuiLib.Get("button", container)
:SetPoint("LEFT", styleDropdown, "RIGHT", 10, 0)
:SetSize(100, 20)
:SetText("Reload UI")
:OnClick(ReloadUI)
local function add(component, offsetY)
layout:offset(0, offsetY or 0):layoutComponent(component)
end
local createDropdown = CreateStyleOverrideDropdown
local barStyles = util.ToArray(Puppeteer.BarStyles)
table.sort(barStyles)
add(createDropdown("Health Bar Color", nil, "HealthBarColor", {"Green To Red", "Green", "Class"}), -10)
add(createDropdown("Health Bar Texture", nil, "HealthBarStyle", barStyles))
add(createDropdown("Health Display", "What kind of text is displayed as health", "HealthDisplay", {"Health", "Health/Max Health", "% Health", "Hidden"}))
add(createDropdown("Missing Health Display", "What kind of text is displayed as missing health", "MissingHealthDisplay", {"Hidden", "-Health", "-% Health"}))
add(createDropdown("Power Bar Texture", nil, "PowerBarStyle", barStyles))
add(createDropdown("Power Display", "What kind of text is displayed as power", "PowerDisplay", {"Power", "Power/Max Power", "% Power", "Hidden"}))
add(createDropdown("Name Text Color", "'Default' is default Blizzard yellow text", "NameText.Color", {"Class", "Default"}))
add(createDropdown("Show Debuff Colors On", nil, "ShowDebuffColorsOn", {"Health Bar", "Name", "Health", "Hidden"}))
add(createDropdown("Sort Units By", "The sorting algorithm for units in a group", "SortUnitsBy", {"ID", "Name", "Class Name"}))
add(createDropdown("Growth Direction", "Vertical grows units down, Horizontal grows units right", "Orientation", {"Vertical", "Horizontal"}))
add(createDropdown("Border Style", "The border of the group", "BorderStyle", {"Tooltip", "Dialog Box", "Borderless"}))
add(createDropdown("Max Units In Axis", "The maximum number of units in the growth axis until it must shift down", "MaxUnitsInAxis", {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}))
add(createDropdown("Min Units X", "The minimum amount of unit space to take on the X-axis", "MinUnitsX", {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}))
add(createDropdown("Min Units Y", "The minimum amount of unit space to take on the Y-axis", "MinUnitsY", {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}))
add(createDropdown("Horizontal Spacing", "The number of pixels between units", "HorizontalSpacing", {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}))
add(createDropdown("Vertical Spacing", "The number of pixels between units", "VerticalSpacing", {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}))
end
function UpdateFrameOptions()
LockFrameCheckbox:SetChecked(PuppeteerSettings.IsFrameLocked(FrameDropdown:GetText()))
HideFrameCheckbox:SetChecked(PuppeteerSettings.IsFrameHidden(FrameDropdown:GetText()))
end
StyleOverrideComponents = {}
CurrentStyleOverride = nil
function CreateStyleOverrideDropdown(text, tooltip, optionLoc, options)
local dropdown, label = CreateLabeledDropdown(StyleOverrideContainer, text, tooltip)
dropdown:SetWidth(200)
dropdown:SetDynamicOptions(function(addOption, level, args)
addOption("text", "Use Style Default",
"initFunc", args.initFunc,
"func", args.func)
for _, option in ipairs(options) do
addOption("text", option,
"option", option,
"initFunc", args.initFunc,
"func", args.func)
end
end, {
initFunc = function(self, gui)
self.checked = gui:GetText() == self.text
end,
func = function(self, gui)
SetStyleOverride(GetSelectedStyleOverride(), optionLoc, self.option)
gui:UpdateText()
end
})
:SetTextUpdater(function(gui)
gui:SetText(GetStyleOverride(GetSelectedStyleOverride(), optionLoc) or "Use Style Default")
end)
StyleOverrideComponents[optionLoc] = dropdown
return dropdown
end
function GetSelectedStyleOverride()
return PTOptions.StyleOverrides[CurrentStyleOverride]
end
function SetSelectedStyleOverride(style)
CurrentStyleOverride = style
if not PTOptions.StyleOverrides[CurrentStyleOverride] then
PTOptions.StyleOverrides[CurrentStyleOverride] = {}
end
StyleOverrideDropdown:SetText(style)
PopulateStyleOverrides()
end
function PopulateStyleOverrides()
for _, dropdown in pairs(StyleOverrideComponents) do
dropdown:UpdateText()
end
end
function TraverseOverride(style, location)
local path = util.SplitString(location, ".")
local currentTable = style
for i = 1, table.getn(path) - 1 do
if not currentTable[path[i]] then
currentTable[path[i]] = {}
end
currentTable = currentTable[path[i]]
end
return currentTable, path[table.getn(path)]
end
function GetStyleOverride(style, location)
local optionTable, location = TraverseOverride(style, location)
return optionTable[location]
end
function SetStyleOverride(style, location, value)
local optionTable, location = TraverseOverride(style, location)
optionTable[location] = value
end
function CreateTab_About()
local container = TabFrame:CreateTab("About")
local text = PTGuiLib.GetText(container,
"Puppeteer Version "..Puppeteer.VERSION..
"\n\n\nPuppeteer Author: OldManAlpha\nTurtle Nordanaar IGN: Oldmana, Lowall, Jmdruid"..
"\n\nHealersMate Original Author: i2ichardt\nEmail: rj299@yahoo.com"..
"\n\nAdditional Contributors"..
"\nTurtle WoW Community: Answers to addon development questions"..
"\nShagu: Utility functions & providing a wealth of research material"..
"\nChatGPT: Utility functions"..
"\n\n\nCheck For Updates, Report Issues, Make Suggestions:\n",
12)
:SetPoint("TOP", container, "TOP", 0, -80)
CreateLinkEditbox(container, "https://github.com/OldManAlpha/Puppeteer")
:SetPoint("TOP", text, "BOTTOM", 0, -10)
:SetSize(300, 20)
end
-- Factory-related functions
function NewLabeledColumnLayout(container, columns, startY, spacing)
local layout = {}
layout.lastAdded = {}
layout.params = {}
layout.selectedColumn = 1
function layout:getNextPoint(columnIndex)
local offsetX, offsetY = self.params.offsetX or 0, self.params.offsetY or 0
if self.lastAdded[columnIndex] then
return "TOPLEFT", self.lastAdded[columnIndex], "BOTTOMLEFT", offsetX, -spacing + offsetY
end
return "TOPLEFT", container, "TOPLEFT", columns[columnIndex] + offsetX, startY + offsetY
end
function layout:layoutComponent(component)
if not self.params.ignoreNext then
local columnIndex = self.selectedColumn or 1
component:SetPoint(self:getNextPoint(columnIndex))
self.lastAdded[columnIndex] = component
end
util.ClearTable(self.params)
end
function layout:column(columnIndex)
self.selectedColumn = columnIndex
return self
end
function layout:offset(offsetX, offsetY)
self.params.offsetX = (self.params.offsetX or 0) + offsetX
self.params.offsetY = (self.params.offsetY or 0) + offsetY
return self
end
function layout:levelAt(columnIndex)
local lastAdded = self.lastAdded[columnIndex]
self.lastAdded[self.selectedColumn] = lastAdded
self:offset(columns[self.selectedColumn] - columns[columnIndex], spacing + lastAdded:GetHeight())
return self
end
function layout:setLastAdded(columnIndex, component)
self.lastAdded[columnIndex] = component
return self
end
function layout:ignoreNext()
self.params.ignoreNext = true
return self
end
return layout
end
function NewComponentFactory(container, layout)
return {
["layout"] = layout,
["doLayout"] = function(self, component)
if self.layout then
self.layout:layoutComponent(component)
end
end,
["checkbox"] = function(self, text, tooltipText, optionLoc, clickFunc)
local checkbox, label = CreateLabeledCheckbox(container, text, tooltipText)
self:doLayout(checkbox)
checkbox:SetScript("OnClick", function(self)
SetOption(optionLoc, this:GetChecked() == 1)
if clickFunc then
clickFunc(self)
end
end)
checkbox:SetChecked(GetOption(optionLoc))
return checkbox, label
end,
["dropdown"] = function(self, text, tooltipText, optionLoc, options, selectFunc)
local dropdown, label = CreateLabeledDropdown(container, text, tooltipText)
self:doLayout(dropdown)
dropdown:SetSimpleOptions(options, function(option)
return {
text = option,
dropdownText = option,
initFunc = function(self)
self.checked = GetOption(optionLoc) == self.text
end,
func = function(self)
SetOption(optionLoc, self.text)
if selectFunc then
selectFunc(self)
end
end
}
end, GetOption(optionLoc))
return dropdown, label
end,
["slider"] = function(self, text, tooltipText, optionLoc, minValue, maxValue)
local slider, label = CreateLabeledSlider(container, text, tooltipText)
slider:SetMinMaxValues(minValue, maxValue)
slider:SetValue(GetOption(optionLoc))
slider:GetSlider():SetNumberedText()
local script = slider:GetSlider():GetScript("OnValueChanged")
slider:GetSlider():SetScript("OnValueChanged", function(self)
script()
SetOption(optionLoc, self:GetValue())
end)
self:doLayout(slider)
return slider, label
end,
["label"] = function(self, text, tooltipText)
-- Dummy frame
local frame = PTGuiLib.Get("container", container)
:SetSize(20, 20)
local label = CreateLabel(container, text)
:SetPoint(GetLabelPoint(frame))
self:doLayout(frame)
return label, frame
end
}
end
function GetLabelPoint(relative)
return "RIGHT", relative, "LEFT", -5, 0
end
function CreateLabeledCheckbox(parent, text, tooltipText)
local checkbox = CreateCheckbox(parent)
local label = CreateLabel(parent, text)
label:SetPoint(GetLabelPoint(checkbox))
checkbox:ApplyTooltip(tooltipText)
label:ApplyTooltip(tooltipText)
return checkbox, label
end
function CreateLabeledDropdown(parent, text, tooltipText)
local dropdown = CreateDropdown(parent)
local label = CreateLabel(parent, text)
label:SetPoint(GetLabelPoint(dropdown))
dropdown:ApplyTooltip(tooltipText)
label:ApplyTooltip(tooltipText)
return dropdown, label
end
function CreateLabeledSlider(parent, text, tooltipText)
local slider = CreateSlider(parent)
local label = CreateLabel(parent, text)
label:SetPoint(GetLabelPoint(slider))
slider:ApplyTooltip(tooltipText)
label:ApplyTooltip(tooltipText)
return slider, label
end
function CreateLinkEditbox(parent, site)
return PTGuiLib.Get("editbox", parent)
:SetText(site)
:SetJustifyH("CENTER")
:SetScript("OnTextChanged", function(self)
self:SetText(site)
end)
end
function CreateDropdown(parent, width)
return PTGuiLib.Get("dropdown", parent)
:SetSize(width or 140, 25)
end
function CreateSlider(parent, width, height)
return PTGuiLib.Get("editbox_slider", parent)
:SetSize(width or 160, height or 36)
end
function CreateCheckbox(parent, width, height)
return PTGuiLib.Get("checkbox", parent)
:SetSize(width or 20, height or 20)
end
function CreateLabel(parent, text)
return PTGuiLib.GetText(parent, text)
end