Quantcast
--[===[ File
Creates the following frames:
- TitanPanelBarButton : Main Titan frame handling events
- TitanPanelTooltip : Used? Inherits from GameTooltipTemplate

Contains the routines to control a Titan template frame.
These could be:
- A Titan bar (full or short)
- A Titan plugin (built-in, thrid party, or LDB)

The Titan templates are defined in .xml.
There appears to be no other way to make frame templates - virtual="true".
--]===]

--[===[ Var API TitanPanelTemplate overview
See TitanPanelButtonTemplate.xml also for more detail.

TitanTemplate (Lua and XML) contain the basics for Titan plugin frames.
Titan templates contain elements used by Titan.

A Titan plugin is a frame created using one of the button types in TitanPanelButtonTemplate.xml.
The available plugin types are:
- TitanPanelTextTemplate - * A frame that only displays text ("$parentText")
- TitanPanelIconTemplate - * A frame that only displays an icon ("$parentIcon")
- TitanPanelComboTemplate - * A frame that displays an icon then text ("$parentIcon"  "$parentText")
- TitanOptionsSliderTemplate - A frame that contains the basics for a vertical slider control. See TitanVolume for an example.
- TitanPanelChildButtonTemplate - A frame that allows a plugin within a plugin. !!! Not be used anymore!!!

* Templates inherit TitanPanelButtonTemplate for common elements.

Most plugins use the combo template.

TitanPanelButtonTemplate contains:
- a frame to handle a menu invoked by a right mouse click ("$parentRightClickMenu")
- default event handlers for
			<OnLoad>
				TitanPanelButton_OnLoad(self);
			</OnLoad>
			<OnShow>
				TitanPanelButton_OnShow(self);
			</OnShow>
			<OnClick>
				TitanPanelButton_OnClick(self, button);
			</OnClick>
			<OnEnter>
				TitanPanelButton_OnEnter(self);
			</OnEnter>
			<OnLeave>
				TitanPanelButton_OnLeave(self);
			</OnLeave>
If these events are overridden then the default routine needs to be included!


NOTE: TitanPanelChildButtonTemplate - !!! Not be used anymore!!!
A much older version of TitanGold was an example.
No third party plugins we are aware of have used this over the years. This scheme may not work and will be slowly removed from the code.

--]===]

-- Globals

-- Constants
local TITAN_PANEL_LABEL_SEPARATOR = "  "
local TITAN_PANEL_BUTTON_WIDTH_CHANGE_TOLERANCE = 10;
local TITAN_PANEL_BUTTON_TYPE_TEXT = 1;
local TITAN_PANEL_BUTTON_TYPE_ICON = 2;
local TITAN_PANEL_BUTTON_TYPE_COMBO = 3;
local TITAN_PANEL_BUTTON_TYPE_CUSTOM = 4;
local pluginOnEnter = nil;
local TITAN_PANEL_MOVE_ADDON = "";
local TITAN_PANEL_DROPOFF_ADDON = "";

-- Library instances
local LibQTip = nil
local _G = getfenv(0);
local InCombatLockdown = _G.InCombatLockdown
local media = LibStub("LibSharedMedia-3.0")

--[[
print("B text"
.." "..tostring(id)..""
.." "..tostring(type(bFunction))..""
.." '"..tostring(bFunction).."'"
.." '"..tostring(buttonTextFunction).."'"
)
--]]
--==========================

--[[ local
NAME: TitanPanel_SetScale
DESC: Set the scale of each plugin and each Titan bar.
VAR:  None
OUT:  None
--]]
function TitanPanel_SetScale()
	local scale = TitanPanelGetVar("Scale");

	-- Set all the Titan bars
	for idx, v in pairs(TitanBarData) do
		_G[idx]:SetScale(scale)
	end
	-- Set all the registered plugins
	for index, value in pairs(TitanPlugins) do
		if index then
			TitanUtils_GetButton(index):SetScale(scale);
		end
	end
end

---local Helper to add a line of tooltip text to the tooltip.
---@param text string To add
---@param frame table Tooltip frame
--- Append a "\n" to the end if there is not one already there
local function TitanTooltip_AddTooltipText(text, frame)
	if (text) then
		-- Append a "\n" to the end
		if (string.sub(text, -1, -1) ~= "\n") then
			text = text .. "\n";
		end

		-- See if the string is intended for a double column
		for text1, text2 in string.gmatch(text, "([^\t\n]*)\t?([^\t\n]*)\n") do
			if (text2 ~= "") then
				-- Add as double wide
				frame:AddDoubleLine(text1, text2);
			elseif (text1 ~= "") then
				-- Add single column line
				frame:AddLine(text1);
			else
				-- Assume a blank line
				frame:AddLine("\n");
			end
		end
	else
		-- No text to display
	end
end

---local Helper to set both the parent and the position of GameTooltip for the plugin tooltip.
---@param parent table Reference to the frame to attach the tooltip to
---@param anchorPoint string Tooltip anchor location (side or corner) to use
---@param relativeToFrame string name name of the frame, usually the plugin), to attach the tooltip to
---@param relativePoint string Parent anchor location (side or corner) to use
---@param xOffset number X offset
---@param yOffset number Y offset
---@param frame table Tooltip frame
--- Set Titan_Global.debug.tool_tips to output debug
local function TitanTooltip_SetOwnerPosition(parent, anchorPoint, relativeToFrame, relativePoint, xOffset, yOffset, frame)
	-- Changes for 9.1.5 Removed the background template from the GameTooltip
	-- Making changes to it difficult and possibly changing the tooltip globally.

	frame:SetOwner(parent, "ANCHOR_NONE");
	frame:SetPoint(anchorPoint, relativeToFrame, relativePoint, xOffset, yOffset);

	-- set font size for the Game Tooltip
	if TitanPanelGetVar("DisableTooltipFont") then
		-- use UI scale
	else
		if TitanTooltipScaleSet < 1 then
			TitanTooltipOrigScale = frame:GetScale();
			TitanTooltipScaleSet = TitanTooltipScaleSet + 1;
		end
		frame:SetScale(TitanPanelGetVar("TooltipFont"));
	end

	if Titan_Global.debug.tool_tips then
		local dbg_msg = "_pos"
			.. " '" .. tostring(frame:GetName()) .. "'"
			.. " " .. tostring(frame:IsShown()) .. ""
			.. " @ '" .. tostring(relativeToFrame) .. "'"
			.. " " .. tostring(_G[relativeToFrame]:IsShown()) .. ""
		TitanDebug(dbg_msg, "normal")
		dbg_msg = ">>_pos"
			.. " " .. tostring(anchorPoint) .. ""
			.. " " .. tostring(relativePoint) .. ""
			.. " w" .. tostring(format("%0.1f", frame:GetWidth())) .. ""
			.. " h" .. tostring(format("%0.1f", frame:GetHeight())) .. ""
		TitanDebug(dbg_msg, "normal")
	end
end

---local Helper to set the screen position of the tooltip frame
---@param self table Tooltip frame
---@param id string Plugin id name
---@param frame table Tooltip frame - expected to be GameTooltip
local function TitanTooltip_SetPanelTooltip(self, id, frame)
	local button = TitanUtils_GetButton(id)

	if button then
		-- Adjust the Y offset as needed
		local rel_y = self:GetTop() - frame:GetHeight()
		local pt = ""
		local rel_pt = ""
		if rel_y > 0 then
			pt = "TOP";
			rel_pt = "BOTTOM";
		else
			-- too close to bottom of screen
			pt = "BOTTOM";
			rel_pt = "TOP";
		end
		local rel_x = self:GetLeft() + frame:GetHeight()
		if (rel_x < GetScreenWidth()) then
			-- menu will fit
			pt = pt .. "LEFT";
			rel_pt = rel_pt .. "LEFT";
		else
			pt = pt .. "RIGHT";
			rel_pt = rel_pt .. "RIGHT";
		end

		TitanTooltip_SetOwnerPosition(button, pt, button:GetName(), rel_pt, 0, 0, frame)
	end
end

---local Set the tooltip of the given Titan plugin.
---@param self table Tooltip frame
---@param id string Plugin id name
--- Set Titan_Global.debug.tool_tips to output debug of this routine
local function TitanPanelButton_SetTooltip(self, id)
	local dbg_msg = "TT:"
	local ok = false
	local frame = GameTooltip

	-- ensure that the 'self' passed is a valid frame reference
	if self:GetName() then
		dbg_msg = dbg_msg .. "'" .. self:GetName() .. "'"
		-- sanity checks
		if (TitanPanelGetVar("HideTipsInCombat") and InCombatLockdown()) then
			dbg_msg = dbg_msg .. " HideTipsInCombat"
		else
			if TitanPanelGetVar("ToolTipsShown") then
				ok = true
			else
				dbg_msg = dbg_msg .. " ToolTipsShown false"
			end
		end
	else
		dbg_msg = dbg_msg .. " No frame"
		-- Cannot even start
	end

	if ok then
		local call_success = nil
		local tmp_txt = ""
		local use_mod = TitanAllGetVar("UseTooltipModifer")
		local use_alt = TitanAllGetVar("TooltipModiferAlt")
		local use_ctrl = TitanAllGetVar("TooltipModiferCtrl")
		local use_shift = TitanAllGetVar("TooltipModiferShift")

		if use_mod then
			if (use_alt and IsAltKeyDown())
				or (use_ctrl and IsControlKeyDown())
				or (use_shift and IsShiftKeyDown())
			then
				ok = true
			end
		else
			ok = true
		end

		self.tooltipCustomFunction = nil;
		self.titan_tt_func = ""
		self.titan_tt_err = ""

		if ok and (id and TitanUtils_IsPluginRegistered(id)) then
			local plugin = TitanUtils_GetPlugin(id)
			if (plugin and plugin.tooltipCustomFunction) then
				-- Prep the tooltip frame
				TitanTooltip_SetPanelTooltip(self, id, frame);

				-- Fill the tooltip
				self.tooltipCustomFunction = plugin.tooltipCustomFunction;
				dbg_msg = dbg_msg .. " | custom"
				call_success, -- for pcall
				tmp_txt = pcall(self.tooltipCustomFunction)
				if call_success then
					-- all is good
					dbg_msg = dbg_msg .. " | ok"
				else
					dbg_msg = dbg_msg .. " | Err: " .. tmp_txt
				end

				frame:Show(); -- now show it
			elseif (plugin and plugin.tooltipTitle) then
				local tooltipTextFunc = {} ---@type function
				local tt_func = plugin.tooltipTextFunction

				if type(tt_func) == 'string' then
					-- Function MUST be in global namespace
					tooltipTextFunc = _G[tt_func]
					dbg_msg = dbg_msg .. " | string"
				elseif type(tt_func) == 'function' then
					-- Can be global or local to the plugin
					tooltipTextFunc = tt_func
					dbg_msg = dbg_msg .. " | function"
				else
					-- silently leave...
					dbg_msg = dbg_msg .. " | none found"
				end

				if (tooltipTextFunc) then
					-- Prep the tooltip frame
					TitanTooltip_SetPanelTooltip(self, id, frame);
					self.tooltipTitle = plugin.tooltipTitle;
					call_success, -- for pcall
					tmp_txt = pcall(tooltipTextFunc)

					-- Fill the tooltip
					self.tooltipText = tmp_txt
					frame:SetText(self.tooltipTitle,
						HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b);
					if (self.tooltipText) then
						TitanTooltip_AddTooltipText(self.tooltipText, frame)
						dbg_msg = dbg_msg .. " | ok"
					else
						dbg_msg = dbg_msg .. " | No text"
					end

					frame:Show() -- now show it
				end
			else
				-- no recognized method to create tooltip
				dbg_msg = "No recognized tooltip method [.tooltipCustomFunction then .tooltipTitle /.tooltipText]"
			end
		end
	else
		-- no need to waste cycles
	end

	if Titan_Global.debug.tool_tips then
		TitanDebug(dbg_msg, "normal")
	end
end

---local Is the given Titan plugin template type text?
---@param id string Plugin id name
---@return boolean IsText
local function TitanPanelButton_IsText(id)
	if (TitanPanelButton_GetType(id) == TITAN_PANEL_BUTTON_TYPE_TEXT) then
		return true
	else
		return false
	end
end

---local Is the given Titan plugin of type combo?
---@param id string Plugin id name
---@return boolean IsCombo
local function TitanPanelButton_IsCombo(id)
	if (TitanPanelButton_GetType(id) == TITAN_PANEL_BUTTON_TYPE_COMBO) then
		return true
	else
		return false
	end
end

---Titan Is the given Titan plugin of type icon?
---@param id string Plugin id name
---@return boolean is_icon
function TitanPanelButton_IsIcon(id)
	local res = false
	if (TitanPanelButton_GetType(id) == TITAN_PANEL_BUTTON_TYPE_ICON) then
		res = true
	else
		res = false
	end

	return res
end

---local Handle the OnDragStart event of the given Titan plugin.
---@param self table Plugin frame
--- Do nothing if the user has locked plugins or if in combat.
--- Set the .isMoving of the plugin (frame) so other routine can check it.
--- Set TITAN_PANEL_MOVING so any Titan routine will know a 'drag & drop' is in progress.
--- Set TITAN_PANEL_MOVE_ADDON so sanity checks can be done on the 'drop'.
local function TitanPanelButton_OnDragStart(self)
	if TitanPanelGetVar("LockButtons") or InCombatLockdown() then return end

	local frname = self;
--	if ChildButton then
--		frname = self:GetParent();
--	end

	-- Clear button positions or we'll grab the button and all buttons 'after'
	for i, j in pairs(TitanPanelSettings.Buttons) do
		local pluginid = _G[TitanUtils_ButtonName(TitanPanelSettings.Buttons[i])];
		if pluginid then
			pluginid:ClearAllPoints()
		end
	end

	-- Start the drag; close any tooltips and open control frames
	frname:StartMoving();
	frname.isMoving = true;
	TitanUtils_CloseAllControlFrames();
	TitanPanelRightClickMenu_Close();
	if AceLibrary then
		if AceLibrary:HasInstance("Dewdrop-2.0") then
			AceLibrary("Dewdrop-2.0"):Close()
		end
		if AceLibrary:HasInstance("Tablet-2.0") then
			AceLibrary("Tablet-2.0"):Close()
		end
	end
	GameTooltip:Hide();
	-- LibQTip-1.0 support code
	LibQTip = LibStub("LibQTip-1.0", true)
	if LibQTip then
		local key, tip
		for key, tip in LibQTip:IterateTooltips() do
			if tip then
				local _, relativeTo = tip:GetPoint()
				if relativeTo and relativeTo:GetName() == self:GetName() then
					tip:Hide()
					break
				end
			end
		end
	end
	-- /LibQTip-1.0 support code

	-- Hold the plugin id so we can do checks on the drop
	TITAN_PANEL_MOVE_ADDON = TitanUtils_GetButtonID(self:GetName());
--	if ChildButton then
--		TITAN_PANEL_MOVE_ADDON =
--			TitanUtils_GetButtonID(self:GetParent():GetName());
--	end
	-- Tell Titan that a drag & drop is in process
	TITAN_PANEL_MOVING = 1;
	-- Store the OnEnter handler so the tooltip does not show - or other oddities
	pluginOnEnter = self:GetScript("OnEnter")
	self:SetScript("OnEnter", nil)
end

---local Handle the OnDragStop event of the given Titan plugin.
---@param self table Plugin frame
--- Clear the .isMoving of the plugin (frame).
--- Clear TITAN_PANEL_MOVING.
--- Clear TITAN_PANEL_MOVE_ADDON.
local function TitanPanelButton_OnDragStop(self)
	if TitanPanelGetVar("LockButtons") then
		return
	end
	local ok_to_move = true
	local nonmovableFrom = false;
	local nonmovableTo = false;
	local frname = self;
--	if ChildButton then
--		frname = self:GetParent();
--	end
	if TITAN_PANEL_MOVING == 1 then
		frname:StopMovingOrSizing();
		frname.isMoving = false;
		TITAN_PANEL_MOVING = 0;

		-- See if the plugin is supposed to stay on the bar it is on
		if TitanGetVar(TITAN_PANEL_MOVE_ADDON, "ForceBar") then
			ok_to_move = false
		end

		-- eventually there could be several reasons to not allow
		-- the plugin to move
		if ok_to_move then
			local i, j;
			for i, j in pairs(TitanPanelSettings.Buttons) do
				local pluginid =
					_G[TitanUtils_ButtonName(TitanPanelSettings.Buttons[i])];
				--					_G["TitanPanel"..TitanPanelSettings.Buttons[i].."Button"];
				if (pluginid and MouseIsOver(pluginid)) and frname ~= pluginid then
					TITAN_PANEL_DROPOFF_ADDON = TitanPanelSettings.Buttons[i];
				end
			end

			-- switching sides is not allowed
			nonmovableFrom = TitanUtils_ToRight(TITAN_PANEL_MOVE_ADDON)
			nonmovableTo = TitanUtils_ToRight(TITAN_PANEL_DROPOFF_ADDON)
			if nonmovableTo ~= nonmovableFrom then
				TITAN_PANEL_DROPOFF_ADDON = "";
			end

			if TITAN_PANEL_DROPOFF_ADDON == "" then
				-- See if the plugin was dropped on a bar rather than
				-- another plugin.
				local bar
				local tbar = nil
				-- Find which bar it was dropped on
				for idx, v in pairs(TitanBarData) do
					bar = idx
					if (bar and MouseIsOver(_G[bar])) then
						tbar = bar
					end
				end

				if tbar then
					TitanPanel_RemoveButton(TITAN_PANEL_MOVE_ADDON)
					TitanUtils_AddButtonOnBar(TitanBarData[tbar].name, TITAN_PANEL_MOVE_ADDON)
				else
					-- not sure what the user did...
				end
			else
				-- plugin was dropped on another plugin - swap (for now)
				local dropoff = TitanUtils_GetCurrentIndex(TitanPanelSettings.Buttons
				, TITAN_PANEL_DROPOFF_ADDON);
				local pickup = TitanUtils_GetCurrentIndex(TitanPanelSettings.Buttons
				, TITAN_PANEL_MOVE_ADDON);
				local dropoffbar = TitanUtils_GetWhichBar(TITAN_PANEL_DROPOFF_ADDON);
				local pickupbar = TitanUtils_GetWhichBar(TITAN_PANEL_MOVE_ADDON);

				if dropoff ~= nil and dropoff ~= "" then
					-- TODO: Change to 'insert' rather than swap
					TitanPanelSettings.Buttons[dropoff] = TITAN_PANEL_MOVE_ADDON;
					TitanPanelSettings.Location[dropoff] = dropoffbar;
					TitanPanelSettings.Buttons[pickup] = TITAN_PANEL_DROPOFF_ADDON;
					TitanPanelSettings.Location[pickup] = pickupbar;
				end
			end
		end

		-- This is important! The start drag cleared the button positions so
		-- the buttons need to be put back properly.
		TitanPanel_InitPanelButtons();
		TITAN_PANEL_MOVE_ADDON = "";
		TITAN_PANEL_DROPOFF_ADDON = "";
		-- Restore the OnEnter script handler
		if pluginOnEnter then self:SetScript("OnEnter", pluginOnEnter) end
		pluginOnEnter = nil;
	end
end

---API Set the color of the tooltip text to normal (white) with the value in green.
---@param text string Label to show
---@param value any Value to show; something tostring() can handle
---@return string str Formatted text and value
--- self.tooltipText = TitanOptionSlider_TooltipText("Addons", TitanGetVar(TITAN_PERFORMANCE_ID, "NumOfAddons"));
function TitanOptionSlider_TooltipText(text, value)
	return tostring(text) .. " " .. GREEN_FONT_COLOR_CODE .. tostring(value) .. FONT_COLOR_CODE_CLOSE;
end

---API Handle the OnLoad event of the requested Titan plugin. Ensure the plugin is set to be registered.
---@param self table Plugin frame
---@param isChildButton boolean? If is child plugin !! NO LONGER USED !!
--- This is called from the Titan plugin frame in the OnLoad event - usually as the frame is created in the Titan template.
function TitanPanelButton_OnLoad(self, isChildButton)
	-- Used by plugins
--[[
--- This is called from the Titan plugin frame in the OnLoad event - usually as the frame is created in the Titan template.
--- This starts the plugin registration process. See TitanUtils for more details on plugin registration.
--- The plugin registration is a two step process because not all addons create Titan plugins in the frame create.
--- The Titan feature of converting LDB addons to Titan plugins is an example.
--- If the plugin needs an OnLoad process it should call this routine after its own.
--- TitanPanelLootTypeButton_OnLoad(self)
--- TitanPanelButton_OnLoad(self)
--]]
--	TitanUtils_PluginToRegister(self, isChildButton)
	TitanUtils_PluginToRegister(self, false)
end

---API Handle the OnShow event of the requested Titan plugin.
---@param self table Plugin frame
function TitanPanelButton_OnShow(self)
	local id = nil;
	-- ensure that the 'self' passed is a valid frame reference
	if self and self:GetName() then
		id = TitanUtils_GetButtonID(self:GetName());
	end
	-- ensure that id is a valid Titan plugin
	if (id) then
		TitanPanelButton_UpdateButton(id, 1);
	end
end

---API Handle the OnClick mouse event of the requested Titan plugin.
---@param self table Plugin frame
---@param button string Mouse button clicked
---@param isChildButton boolean? If is child plugin ! NO LONGER USED !
--- Only the left and right mouse buttons are handled by Titan.
--- Called from Titan templates unless overriden by plugin. If overridden the plugin should call this routine.
function TitanPanelButton_OnClick(self, button, isChildButton)
	local id
	-- ensure that the 'self' passed is a valid frame reference
	if self and self:GetName() then
--[[		id = TitanUtils_Ternary(isChildButton,
			TitanUtils_GetParentButtonID(self:GetName()),
			TitanUtils_GetButtonID(self:GetName()));
--]]
		id = TitanUtils_GetButtonID(self:GetName())
	end

	if id then
		local controlFrame = TitanUtils_GetControlFrame(id);

		if controlFrame and (button == "LeftButton") then
			local isControlFrameShown;
			if (not controlFrame) then
				isControlFrameShown = false;
			elseif (controlFrame:IsVisible()) then
				isControlFrameShown = false;
			else
				isControlFrameShown = true;
			end

			TitanUtils_CloseAllControlFrames();
			TitanPanelRightClickMenu_Close();

			--			local position = TitanUtils_GetWhichBar(id)
			if (isControlFrameShown) then
				-- Note: This uses anchor points to place the control frame relative to the plugin on the screen.
				local parent = self:GetName() -- plugin with the control frame
				local point = ""  -- point of the control frame
				local rel_point = "" -- The middle - top or bottom edge - of the plugin to anchor to
				--[[ Mar 2023 : removed reference to Titan bar reference
					Instead use the relative position on the screen to show the control (plugin) frame properly.
					NOTE: If Titan bars need a left click to show a control frame the offset
					will need to be changed to use the cursor position like right click menu!!
				--]]
				if (self:GetTop() - controlFrame:GetHeight()) > 0 then
					point = "TOP"
					rel_point = "BOTTOM"
				else -- below screen
					point = "BOTTOM"
					rel_point = "TOP"
				end

				local x = 0
				local y = 0

				controlFrame:ClearAllPoints();
				controlFrame:SetPoint(point .. "LEFT", parent, rel_point, 0, 0) -- default left of plugin

				-- Adjust control frame position if it's off the screen
				local offscreenX, offscreenY = TitanUtils_GetOffscreen(controlFrame);
				if (offscreenX == -1) then
					-- Off to left of screen which should not happen...
				elseif (offscreenX == 1) then
					-- off to right of screen, flip to right of plugin
					controlFrame:ClearAllPoints();
					controlFrame:SetPoint(point .. "RIGHT", parent, rel_point, 0, 0)
				end

				controlFrame:Show();
			end
		elseif (button == "RightButton") then
			TitanUtils_CloseAllControlFrames();
			-- Show RightClickMenu anyway
			TitanPanelRightClickMenu_Close();
			TitanPanelRightClickMenu_Toggle(self);
		end

		GameTooltip:Hide();
	end
end

---API Handle the OnEnter event of the requested Titan plugin.
---@param self table Plugin frame
---@param isChildButton boolean? If is child plugin ! NO LONGER USED !
--- 1. The cursor has moved over the plugin so show the plugin tooltip.
--- 2. Return if plugin "is moving" or if tooltip is already shown.
function TitanPanelButton_OnEnter(self, isChildButton)
	local id = nil;
	-- ensure that the 'self' passed is a valid frame reference
	if self and self:GetName() then
--[[		id = TitanUtils_Ternary(isChildButton,
			TitanUtils_GetParentButtonID(self:GetName()),
			TitanUtils_GetButtonID(self:GetName()));
--]]
		id = TitanUtils_GetButtonID(self:GetName())
	end

	if (id) then
		local controlFrame = TitanUtils_GetControlFrame(id);
		if (controlFrame and controlFrame:IsVisible()) then
			return;
		elseif (TitanPanelRightClickMenu_IsVisible()) then
			return;
		else
			if TITAN_PANEL_MOVING == 0 then
				TitanPanelButton_SetTooltip(self, id);
			end
			if self.isMoving then
				GameTooltip:Hide();
			end
		end
	end
end

---API Handle the OnLeave event of the requested Titan plugin.
---@param self table Plugin frame
---@param isChildButton boolean? If is child plugin ! NO LONGER USED !
--- 1. The cursor has moved over the plugin so hide the plugin tooltip.
function TitanPanelButton_OnLeave(self, isChildButton)
	local id = nil;
	-- ensure that the 'self' passed is a valid frame reference
	if self and self:GetName() then
--		id = TitanUtils_Ternary(isChildButton,
--			TitanUtils_GetParentButtonID(self:GetName()),
--			TitanUtils_GetButtonID(self:GetName()));
		id = TitanUtils_GetButtonID(self:GetName())
	end

	if (id) then
		GameTooltip:Hide();
	end

	if TitanPanelGetVar("DisableTooltipFont") then
		-- use game font & scale
	else
		-- use Titan font & scale
		GameTooltip:SetScale(TitanTooltipOrigScale);
		TitanTooltipScaleSet = 0;
	end
end

-- local routines for Update Button


local format_with_label = { [0] = "" } -- Set format string once
for idx = 1, 4 do
	format_with_label[idx] = "%s%s" .. (TITAN_PANEL_LABEL_SEPARATOR .. "%s%s"):rep(idx - 1)
end
---local Set the width of the given icon Titan plugin - icon only.
---@param id string Plugin id
--- The plugin is expected to tell Titan what routine is to be called in <self>.registry.buttonTextFunction.
--- Note: Titan handles up to 4 label-value pairs. User may customize (override) the plugin labels.
--- The text routine is called in protected mode (pcall) to ensure the Titan main routines still run.
local function TitanPanelButton_SetButtonText(id)
	--- There are several places that return in this routine. Not best practice but more readable.
	local dbg_msg = "ptxt : '" .. tostring(id) .. "'"
	local ok = false

	if (id and TitanUtils_IsPluginRegistered(id)) then
		-- seems valid, registered plugin
		ok = true
	else
		-- return silently; The plugin is not registered!
		-- output here could be a lot and really annoy the user...
		dbg_msg = dbg_msg .. " | unregistered plugin"
	end

	local pdata = TitanUtils_GetPlugin(id) -- get plugin data
	local buttonTextFunction = {} ---@type function

	if pdata then
		local bFunction = pdata.buttonTextFunction
		if type(bFunction) == 'string' then
			-- Function MUST be in global namespace
			buttonTextFunction = _G[bFunction]
			ok = true
		elseif type(bFunction) == 'function' then
			-- Can be global or local to the plugin
			buttonTextFunction = bFunction
			ok = true
		else
			dbg_msg = dbg_msg .. " | invalid function type"
			-- silently leave...
		end
	else
		-- silently leave...
		dbg_msg = dbg_msg .. " | invalid plugin data"
	end

	if ok and buttonTextFunction then
		local label1, value1, label2, value2, label3, value3, label4, value4
		local call_success = false
		local button = TitanUtils_GetButton(id) -- get plugin frame
		local buttonText = {}

		local text = false
		if button then
			buttonText = _G[button:GetName() .. TITAN_PANEL_TEXT];

			local newfont = media:Fetch("font", TitanPanelGetVar("FontName"))
			if newfont then
				buttonText:SetFont(newfont, TitanPanelGetVar("FontSize"))
			end

			-- We'll be paranoid here and call the button text in protected mode.
			-- In case the button text fails it will not take Titan with it...
			call_success, -- for pcall
			label1, value1, label2, value2, label3, value3, label4, value4 =
				pcall(buttonTextFunction, id)

			if call_success then
				-- All is good
				text = true
			else
				buttonText:SetText("<?>")
				dbg_msg = dbg_msg .. " | Err '"..tostring(label1).."'"
			end
		else
			dbg_msg = dbg_msg .. " | invalid plugin id"
		end

		--=====================================
		-- Determine the label value pairs : 1 - 4
		-- Each could be custom per user
		--
		-- NumLabelsSeen - used for the config to avoid confusing user
		-- Plugin MUST have been shown at least once.

		-- In the case of first label only (no value), set the button and return.
		if text and
			label1 and
			not (label2 or label3 or label4
				or value1 or value2 or value3 or value4) then
			buttonText:SetText(label1)

			TitanSetVar(id, "NumLabelsSeen", 1)

			dbg_msg = dbg_msg .. " | single label; no value | "
		else
			local show_label = TitanGetVar(id, "ShowLabelText")
			local values = 0
			if label1 or value1 then
				values = 1
				if show_label then
					if TitanGetVar(id, "CustomLabelTextShow") then
						-- override the label per the user
						label1 = TitanGetVar(id, "CustomLabelText")
					else
					end
				else
					label1 = ""
				end
				if label2 or value2 then
					values = 2
					if show_label then
						if TitanGetVar(id, "CustomLabel2TextShow") then
							-- override the label per the user
							label2 = TitanGetVar(id, "CustomLabel2Text")
						else
						end
					else
						label2 = ""
					end
					if label3 or value3 then
						if show_label then
							if TitanGetVar(id, "CustomLabel3TextShow") then
								-- override the label per the user
								label3 = TitanGetVar(id, "CustomLabel3Text")
							else
							end
						else
							label3 = ""
						end
						values = 3
						if label4 or value4 then
							values = 4
							if show_label then
								if TitanGetVar(id, "CustomLabel43TextShow") then
									-- override the label per the user
									label4 = TitanGetVar(id, "CustomLabel4Text")
								else
								end
							else
								label4 = ""
							end
						end
					end
				end
				dbg_msg = dbg_msg .. " | ok | " .. tostring(values)
			else
				-- no label or value
				-- Do nothing, it could be the right action for the plugin
				dbg_msg = dbg_msg .. " | no label or value : " .. tostring(values)
			end

			TitanSetVar(id, "NumLabelsSeen", values)

			-- values tells which format to use from the array
			buttonText:SetFormattedText(format_with_label[values],
				label1 or "", value1 or "",
				label2 or "", value2 or "",
				label3 or "", value3 or "",
				label4 or "", value4 or ""
			)
		end
	else
		-- no valid routine to update the plugin text
		dbg_msg = dbg_msg .. " | no valid routine found"
	end
	if Titan_Global.debug.plugin_text then
		TitanDebug(dbg_msg, "normal")
	end
end

---local Set the width of the given Titan plugin - text only.
---@param id string Plugin id
---@param setButtonWidth? integer Width in pixels
--- Titan uses a tolerance setting to prevent endless updating of the text width.
local function TitanPanelButton_SetTextButtonWidth(id, setButtonWidth)
	if (id) then
		local button = TitanUtils_GetButton(id)
		if button then
			local text = _G[button:GetName() .. TITAN_PANEL_TEXT];
			if (setButtonWidth
					or button:GetWidth() == 0
					or button:GetWidth() - text:GetWidth() > TITAN_PANEL_BUTTON_WIDTH_CHANGE_TOLERANCE
					or button:GetWidth() - text:GetWidth() < -TITAN_PANEL_BUTTON_WIDTH_CHANGE_TOLERANCE) then
				button:SetWidth(text:GetWidth());
				TitanPanelButton_Justify();
			end
		else
			-- no plugin registered, be silent
		end
	else
		-- no plugin received, be silent
	end
end

---local Set the width of the given Titan plugin - icon only.
---@param id string Plugin id
--- Wrap up by (re)drawing the plugins on the Bar.
--- Titan uses a tolerance setting to prevent endless updating of the text width.
local function TitanPanelButton_SetIconButtonWidth(id)
	if (id) then
		local button = TitanUtils_GetButton(id)
		if button and (TitanUtils_GetPlugin(id).iconButtonWidth) then
			button:SetWidth(TitanUtils_GetPlugin(id).iconButtonWidth);
		else
			-- no plugin registered, be silent
		end
	else
		-- no plugin received, be silent
	end
end

---local Set the width of the given Titan plugin - combo icon & text.
---@param id string Plugin id
---@param setButtonWidth? integer Width in pixels; default to .registry.iconWidth
--- Wrap up by (re)drawing the plugins on the Bar.
--- Titan uses a tolerance setting to prevent endless updating of the text width.
local function TitanPanelButton_SetComboButtonWidth(id, setButtonWidth)
-- TODO - ensure this routine is proper - need this param?
-- icon width default to .registry.iconWidth before getting the actual width
	if (id) then
		local button = TitanUtils_GetButton(id)
		if not button then return end -- sanity check

		local text = _G[button:GetName() .. TITAN_PANEL_TEXT];
		local icon = _G[button:GetName() .. "Icon"];
		local iconWidth, iconButtonWidth, newButtonWidth;

		-- Get icon button width
		iconButtonWidth = 0;
		if (TitanUtils_GetPlugin(id).iconButtonWidth) then
			iconButtonWidth = TitanUtils_GetPlugin(id).iconButtonWidth;
		elseif (icon:GetWidth()) then
			iconButtonWidth = icon:GetWidth();
		end

		if (TitanGetVar(id, "ShowIcon") and (iconButtonWidth ~= 0)) then
			icon:Show();
			text:ClearAllPoints();
			text:SetPoint("LEFT", icon:GetName(), "RIGHT", 2, 1);

			newButtonWidth = text:GetWidth() + iconButtonWidth;
		else
			icon:Hide();
			text:ClearAllPoints();
			text:SetPoint("LEFT", button:GetName(), "LEFT", 0, 1);

			newButtonWidth = text:GetWidth();
		end

		if (setButtonWidth
				or button:GetWidth() == 0
				or button:GetWidth() - newButtonWidth > TITAN_PANEL_BUTTON_WIDTH_CHANGE_TOLERANCE
				or button:GetWidth() - newButtonWidth < -TITAN_PANEL_BUTTON_WIDTH_CHANGE_TOLERANCE)
		then
			button:SetWidth(newButtonWidth);
			TitanPanelButton_Justify();
		end
	end
end

---API Update the display of the given Titan plugin.
---@param id string Plugin id
---@param setButtonWidth? integer Width in pixels
--- Use after any change to icon, label, or text (depending on Titan template used)
--- TitanPanelButton_UpdateButton(TITAN_CLOCK_ID)
function TitanPanelButton_UpdateButton(id, setButtonWidth)
	--	Used by plugins
	local plugin = TitanUtils_GetPlugin(id)
	-- safeguard to avoid errors
	if plugin and TitanUtils_IsPluginRegistered(id) then
		if (TitanPanelButton_IsText(id)) then
			-- Update textButton
			TitanPanelButton_SetButtonText(id);
			TitanPanelButton_SetTextButtonWidth(id, setButtonWidth);
		elseif (TitanPanelButton_IsIcon(id)) then
			-- Update iconButton
			TitanPanelButton_SetButtonIcon(id, (plugin.iconCoords or nil),
				(plugin.iconR or nil), (plugin.iconG or nil), (plugin.iconB or nil)
			);
			TitanPanelButton_SetIconButtonWidth(id);
		elseif (TitanPanelButton_IsCombo(id)) then
			-- Update comboButton
			TitanPanelButton_SetButtonText(id);
			TitanPanelButton_SetButtonIcon(id, (plugin.iconCoords or nil),
				(plugin.iconR or nil), (plugin.iconG or nil), (plugin.iconB or nil)
			);
			TitanPanelButton_SetComboButtonWidth(id, setButtonWidth);
		end
	else
		return
	end
end

---API Update the tooltip of the given Titan plugin.
---@param self table Plugin frame
function TitanPanelButton_UpdateTooltip(self)
	if not self then return end
	if (GameTooltip:IsOwned(self)) then
		local id = TitanUtils_GetButtonID(self:GetName());

		TitanPanelButton_SetTooltip(self, id);
	end
end

---API Refresh the display of the passed in Titan plugin.
---@param table table | string Either {plugin id, action} OR plugin id
---@param oldarg string? action OR nil
--- This is used by some plugins. It is not used within Titan.
--- Action :
--- 1 = refresh button
--- 2 = refresh tooltip
--- 3 = refresh button and tooltip
function TitanPanelPluginHandle_OnUpdate(table, oldarg)
	--- This is used by some plugins.
	local id, updateType = nil, nil
	-- set the id and updateType
	-- old method
	if table and type(table) == "string" and oldarg then
		id = table
		updateType = oldarg
	end
	-- new method
	if table and type(table) == "table" then
		if table[1] then id = table[1] end
		if table[2] then updateType = table[2] end
	end

	-- id is required
	if id then
		if updateType == TITAN_PANEL_UPDATE_BUTTON
			or updateType == TITAN_PANEL_UPDATE_ALL then
			TitanPanelButton_UpdateButton(id)
		end

		if (updateType == TITAN_PANEL_UPDATE_TOOLTIP
				or updateType == TITAN_PANEL_UPDATE_ALL)
			and MouseIsOver(_G[TitanUtils_ButtonName(id)]) then
			if TitanPanelRightClickMenu_IsVisible() or TITAN_PANEL_MOVING == 1 then
				return
			end

			TitanPanelButton_SetTooltip(_G[TitanUtils_ButtonName(id)], id)
		end
	end
end

---Titan Poorly named routine that sets the OnDragStart & OnDragStop scripts of a Titan plugin.
---@param id string Plugin id
---@param isChildButton boolean? If is child plugin
function TitanPanelDetectPluginMethod(id, isChildButton)
	-- Ensure the id is not nil
	if not id then return end
	local TitanPluginframe = _G[TitanUtils_ButtonName(id)];
--	if isChildButton then
--		TitanPluginframe = _G[id];
--	end
	-- Ensure the frame is valid
	if not TitanPluginframe and TitanPluginframe:GetName() then return end -- sanity check...

	-- Set the OnDragStart script
	TitanPluginframe:SetScript("OnDragStart", function(self)
		if not IsShiftKeyDown()
			and not IsControlKeyDown()
			and not IsAltKeyDown() then
--			if isChildButton then
--				TitanPanelButton_OnDragStart(self, true);
--			else
				TitanPanelButton_OnDragStart(self);
--			end
		end
	end)

	-- Set the OnDragStop script
	TitanPluginframe:SetScript("OnDragStop", function(self)
--		if isChildButton then
--			TitanPanelButton_OnDragStop(self, true)
--		else
			TitanPanelButton_OnDragStop(self);
--		end
	end)
end

---API Set the icon of the given Titan plugin.
---@param id string Plugin id
---@param iconCoords table? As {left, right, top, bottom}
---@param iconR number? Red (.0. - 1.0)
---@param iconG number? Green (.0. - 1.0)
---@param iconB number? Blue (.0. - 1.0)
--- Using TitanPanelButton_UpdateButton is preferred unless coords are needed
function TitanPanelButton_SetButtonIcon(id, iconCoords, iconR, iconG, iconB)
	if (id and TitanUtils_IsPluginRegistered(id)) then
		local button = TitanUtils_GetButton(id)
		if button then
			local icon = _G[button:GetName() .. "Icon"];
			local iconTexture = TitanUtils_GetPlugin(id).icon;
			local iconWidth = TitanUtils_GetPlugin(id).iconWidth;

			if (iconTexture) and icon then
				icon:SetTexture(iconTexture);
			end
			if (iconWidth) and icon then
				icon:SetWidth(iconWidth);
			end

			-- support for iconCoords, iconR, iconG, iconB attributes
			if iconCoords and icon then
				icon:SetTexCoord(unpack(iconCoords))
			end
			if iconR and iconG and iconB and icon then
				icon:SetVertexColor(iconR, iconG, iconB)
			end
		else
			-- plugin not registered
		end
	end
end

---Titan Get the type of the given Titan plugin.
--- This assumes that the developer is playing nice and is using the Titan templates as is...
---@param id string Plugin id name
---@return integer type (text-1, icon-2, combo-3 (default))
function TitanPanelButton_GetType(id)
	-- This assumes that the developer is playing nice and is using the Titan templates as is...
	-- id is required
	local type = 0 -- unknown
	if id then
		local button = TitanUtils_GetButton(id);
		if button then
			local text = _G[button:GetName() .. TITAN_PANEL_TEXT];
			local icon = _G[button:GetName() .. "Icon"];

			if (text and icon) then
				type = TITAN_PANEL_BUTTON_TYPE_COMBO;
			elseif (text and not icon) then
				type = TITAN_PANEL_BUTTON_TYPE_TEXT;
			elseif (not text and icon) then
				type = TITAN_PANEL_BUTTON_TYPE_ICON;
			elseif (not text and not icon) then
				type = TITAN_PANEL_BUTTON_TYPE_CUSTOM;
			end
		else
			type = TITAN_PANEL_BUTTON_TYPE_COMBO;
		end
	else
		-- no id...
	end

	return type
end

---Titan Apply saved Bar position to the Bar frame.
--- Bit of a sledge hammer; used when loading a profile over the current so the Bars are properly placed.
---@param frame_str string Bar frame name
function TitanPanelButton_ApplyBarPos(frame_str)
	--
	local frame = _G[frame_str]
	local bdata = TitanBarData[frame_str]
	if frame then
		frame:ClearAllPoints();
		if bdata.user_move then
			local x, y, w = TitanVariables_GetBarPos(frame_str)
			frame:SetPoint(bdata.show.pt, bdata.show.rel_fr, bdata.show.rel_pt, x, y)
		else
			-- full bar, ignore
		end
	end
end

---Titan Loads the Backdrop for TitanOptionsSliderTemplate with new 9.0 API
---@param self table Control frame
function TitanOptionsSliderTemplate_OnLoad(self)
	self:SetBackdrop({
		bgFile = "Interface\\Buttons\\UI-SliderBar-Background",
		edgeFile = "Interface\\Buttons\\UI-SliderBar-Border",
		tile = true,
		insets = {
			left = 6,
			right = 6,
			top = 3,
			bottom = 3,
		},
		tileSize = 8,
		edgeSize = 8,
	})
end