diff --git a/Yipper.toc b/Yipper.toc index e52ce42..bef0d6b 100644 --- a/Yipper.toc +++ b/Yipper.toc @@ -1,5 +1,5 @@ ## Interface: 120007, 120005 -## Version: 2.1.0 +## Version: 3.0.0 ## Title: Yipper ## Notes: AddOn to track chat messages from players ## Author: Keento @@ -17,12 +17,15 @@ ## Category-zhCN: 角色扮演 ## Category-zhTW: 角色扮演 +source\Yipper.Api.lua +source\EventListener.lua +source\PlayerTracker.lua + src/Constants.lua src/Utils.lua src/Comms.lua src/UI.lua -src/UI.Settings.lua +ui/YipperSettingsFrameTemplate.xml src/Minimap.lua -src/Events.lua src/EventQueue.lua src/Core.lua diff --git a/source/EventListener.lua b/source/EventListener.lua new file mode 100644 index 0000000..c3d0179 --- /dev/null +++ b/source/EventListener.lua @@ -0,0 +1,317 @@ +--- EventListener +-- +-- The EventListener is an object that runs when the AddOn starts and handles all the incoming events that +-- Yipper is listening to. By isolating the logic for the event handling in a stand-alone class, we can focus +-- objects to specific purposes and maintain the code better. +-- +-- Depends on Database, Comms, API +local addonName, Yipper = ... +local events = { + -- Global AddOn events + "ADDON_LOADED", + + -- Chat events + "CHAT_MSG_EMOTE", + "CHAT_MSG_TEXT_EMOTE", + "CHAT_MSG_GUILD", + "CHAT_MSG_OFFICER", + "CHAT_MSG_PARTY", + "CHAT_MSG_PARTY_LEADER", + "CHAT_MSG_RAID", + "CHAT_MSG_RAID_LEADER", + "CHAT_MSG_RAID_WARNING", + "CHAT_MSG_YELL", + "CHAT_MSG_SAY", + "CHAT_MSG_WHISPER", + "CHAT_MSG_SYSTEM", + "CHAT_MSG_ADDON_LOGGED", + + -- Loading screen events + "LOADING_SCREEN_ENABLED", + "LOADING_SCREEN_DISABLED", + + -- Player related events + "PLAYER_LOGOUT", + "PLAYER_REGEN_DISABLED", + "PLAYER_REGEN_ENABLED", + + -- Yipper Custom Events + "YIPPER_TRACKED_PLAYER_CHANGED" +} + +-- Initialize the module and set the function look up. +local EventListener = {} +EventListener.__index = EventListener + +--- Creates a new instance of the EventListener. +--- @return table An instance of the EventListener class. +function EventListener.new() + local newObject = setmetatable({}, self) + + -- Object initialization + newObject:Initialize() + + return newObject +end + +--- Initializes the instance, ensuring the frame and event handling is in place. +---@return nil +---@private +function EventListener:Initialize() + self._frame = CreateFrame("Frame", "Yipper_EventFrame", UIParent) + + -- Register Events + for _, event in pairs(events) do + self._frame:RegisterEvent(event) + end + + -- Capture the EventListener instance so the handler can reach it. + -- WoW calls OnEvent with the *frame* as the first argument, so we + -- must not rely on `self` inside the callback -- it would be the frame. + local instance = self + + self._frame:SetScript("OnEvent", function(_, event, ...) + instance:OnEvent(event, ...) + end) +end + +--- Handles the incoming events from the client. +---@return nil +---@private +function EventListener:OnEvent(event, ...) + if (event == "ADDON_LOADED" and addonName == name) then + -- If the YipperDB is nil, it means this is the first time loading + -- the Addon. In that case, assign the default values for the AddOn + -- to the DB. + if YipperDB == nil then + Yipper.DB = { + ["Messages"] = {}, + ["MaxMessages"] = 50, + ["BackgroundColor"] = Yipper.Constants.BlackColor, + ["BorderColor"] = Yipper.Constants.BlackColor, + ["Alpha"] = Yipper.Constants.Alpha, + ["FontSize"] = Yipper.Constants.FontSize, + ["SelectedFont"] = Yipper.Constants.Fonts.FrizQuadrata, + ["NotificationSound"] = nil, + ["NotificationColor"] = Yipper.Constants.NotificationColor, + ["ShowHeader"] = true, + ["PingTrackedPlayer"] = false, + ["EnableMinimapButton"] = true + } + -- If the YipperDB variable has been loaded for the character, + -- assign it to the internal DB so the AddOn has its settings + -- loaded and available. + elseif YipperDB then + Yipper.DB = YipperDB + + -- Wipe the past messages if Yipper has been updated. + -- This avoids weird behavior in updates when we change stuff. + if (not Yipper.DB.Version) or Yipper.Utils:IsUpdated(Yipper.DB.Version) then + Yipper.DB.Version = Yipper.Constants.VERSION + Yipper.DB.Messages = { } + end + + -- Fix the keywords, they should be an array + -- Apply this for any version after 1.5.6 to fix the data. + if Yipper.Utils:IsUpdated("1.5.6") then + if Yipper.DB.Keywords and type(Yipper.DB.Keywords) == "string" then + local keywords = Yipper.DB.Keywords or "" + Yipper.DB.Keywords = Yipper.Utils:SplitString(keywords, ",") + end + end + + -- Migrate the NotificationColor from the 0-1 range to the 0-255 range. + -- Older versions stored the color components as 0-1, but the constants now + -- use the 0-255 range to stay consistent with the other color constants. + local c = Yipper.DB.NotificationColor + if c ~= nil and c.r ~= nil and c.g ~= nil and c.b ~= nil + and c.r <= 1 and c.g <= 1 and c.b <= 1 then + Yipper.DB.NotificationColor = { + ["r"] = c.r * 255, + ["g"] = c.g * 255, + ["b"] = c.b * 255 + } + end + end + elseif event == "PLAYER_LOGOUT" then + YipperDB = Yipper.DB + -- Loading screen has started, put events in the queue and process later. + elseif event == "LOADING_SCREEN_ENABLED" then + Yipper.EventQueue.IsLoadingScreenOrCombat = true + -- Loading screen has ended, process events like normal and process the queue. + elseif event == "LOADING_SCREEN_DISABLED" then + Yipper.EventQueue.IsLoadingScreenOrCombat = false + Yipper.EventQueue:ProcessQueue() + -- Combat has started, put events in the event queue and process later. + elseif event == "PLAYER_REGEN_DISABLED" then + Yipper.EventQueue.IsLoadingScreenOrCombat = true + -- Combat has ended, process events light normal, and process the queue. + elseif event == "PLAYER_REGEN_ENABLED" then + Yipper.EventQueue.IsLoadingScreenOrCombat = false + Yipper.EventQueue:ProcessQueue() + elseif event == "CHAT_MSG_SAY" then + local message, _, _, _, _, _, _, _, _, _, lineId, guid = ... + self:StoreMessage(message, guid, lineId, event) + elseif event == "CHAT_MSG_EMOTE" then + local message, _, _, _, _, _, _, _, _, _, lineId, guid = ... + + -- NPC Emotes in instances do not have a GUID. + -- Ignore these events + if guid == nil then + return + end + + self:StoreMessage(message, guid, lineId, event) + elseif event == "CHAT_MSG_GUILD" then + local message, _, _, _, _, _, _, _, _, _, lineId, guid = ... + self:StoreMessage(message, guid, lineId, event) + elseif event == "CHAT_MSG_OFFICER" then + local message, _, _, _, _, _, _, _, _, _, lineId, guid = ... + self:StoreMessage(message, guid, lineId, event) + elseif event == "CHAT_MSG_PARTY" then + local message, _, _, _, _, _, _, _, _, _, lineId, guid = ... + self:StoreMessage(message, guid, lineId, event) + elseif event == "CHAT_MSG_PARTY_LEADER" then + local message, _, _, _, _, _, _, _, _, _, lineId, guid = ... + self:StoreMessage(message, guid, lineId, event) + elseif event == "CHAT_MSG_RAID" then + local message, _, _, _, _, _, _, _, _, _, lineId, guid = ... + self:StoreMessage(message, guid, lineId, event) + elseif event == "CHAT_MSG_RAID_LEADER" then + local message, _, _, _, _, _, _, _, _, _, lineId, guid = ... + self:StoreMessage(message, guid, lineId, event) + elseif event == "CHAT_MSG_YELL" then + local message, _, _, _, _, _, _, _, _, _, lineId, guid = ... + self:StoreMessage(message, guid, lineId, event) + elseif event == "CHAT_MSG_WHISPER" then + local message, _, _, _, _, _, _, _, _, _, lineId, guid = ... + self:StoreMessage(message, guid, lineId, event) + elseif event == "CHAT_MSG_RAID_WARNING" then + local message, _, _, _, _, _, _, _, _, _, lineId, guid = ... + self:StoreMessage(message, guid, lineId, event) + elseif event == "CHAT_MSG_SYSTEM" then + local message, _, _, _, _, _, _, _, _, _, lineId, _ = ... + + -- Do not attempt to process messages when they are secret. + -- If we're dealing with a secret system message, just ignore it. + -- Means the player is in combat, and these messages are useless for us. + if Yipper.API:IsSecret(message) then + return + end + + -- If the event doesn't include "rolls", it's not a roll event + -- and we can discard it + if not string.find(message, "rolls") then + return + end + + local author, rollResult, rollMin, rollMax = string.match(message, "(.+) rolls (%d+) %((%d+)-(%d+)%)"); + + -- Only broadcast our own messages, otherwise every single roll event will be broadcast as "us". + -- We don't want that, we want the AddOn to receive rolls from other people and process them + -- accordingly. + if author == UnitName("player") then + -- Because Blizzard in all their wisdom decided not to include the realm or anything + -- tangible for these events, we'll do it ourselves with a custom event. + -- If we rolled, just broadcast the roll over the Comms and let the listening + -- addons handle it to parse the roll message properly with the needed data. + Yipper.Comms:BroadcastMessage(message.."||"..UnitGUID("player").."||"..lineId) + end + elseif event == "CHAT_MSG_ADDON_LOGGED" then + local prefix, message, channel, sender, target, zoneChannelId, localID, name, instanceID = ... + + -- We only care about messages for Yipper, ignore everything else. + if prefix == addonName then + -- Since this will just be a roll broadcast by someone, + -- Add it to the message list as a system message. + local actualMessage, guid, lineId = message:match("^(.+)||(.+)||(.+)$") + self:StoreMessage(actualMessage, guid, lineId, "CHAT_MSG_SYSTEM") + end + elseif event == "CHAT_MSG_TEXT_EMOTE" then + local message, _, _, _, _, _, _, _, _, _, lineId, guid = ... + + -- NPC Emotes in instances do not have a GUID. + -- Ignore these events + if guid == nil then + return + end + + -- Because actual emotes work different, + -- we need to fix the sender in both cases. + -- If we're the sender, just construct it using the realm. + if sender == UnitName("player") then + guid = UnitGUID("player") + end + + self:StoreMessage(message, guid, lineId, event) + end +end + +--- Stores a message with the required arguments for the specific player. +--- This builds the table with messages and automatically trims it as well +--- when the maximum amount is exceeded. +---@param message string The message received through the chat events +---@param guid string The GUID of the sender +---@param lineId number The line identifier of the message in the main chat +---@param event string The event that triggered the message +---@return nil +---@private +function EventListener:StoreMessage(message, guid, lineId, event) + -- Since our entire logic hinges on the guid not being a secret, + -- we will drop the entire message in case the guid is flagged as secret. + -- When you're in combat, you really don't care about RP anyways. + if Yipper.API:IsSecret(guid) then + return + end + + -- Sanity check, to ensure nothing bad happens in case the table is not set... + if not Yipper.DB.Messages then + Yipper.DB.Messages = {} + end + + -- Check if the sender has a record table, might be the first time they're sending a message. + if not Yipper.DB.Messages[guid] then + Yipper.DB.Messages[guid] = { } + end + + -- Inject the record in the table. + table.insert(Yipper.DB.Messages[guid], { + ["message"] = message, + ["lineId"] = lineId, + ["event"] = event, + ["timestamp"] = date("%H:%M"), + ["epoch"] = time() + }) + + -- If we have exceeded the maximum amount of messages, + -- remove the oldest ones. + if(table.getn(Yipper.DB.Messages) > Yipper.DB.MaxMessages) then + table.remove(Yipper.DB.Messages, 1) + end + + -- Play the notification sound, if applicable. + Yipper.Utils:PlayNotification(message, guid) + + -- Push the message into the messageFrame if the sender is the user currently + -- being tracked. + if guid == Yipper.TrackedPlayerGuid then + -- Check if we're at the bottom + local wasAtBottom = Yipper.messageFrame:AtBottom() + + -- Push the message into the frame. + Yipper.UI:AddMessageToFrame({ + ["message"] = message, + ["lineId"] = lineId, + ["event"] = event, + ["timestamp"] = date("%H:%M"), + ["epoch"] = time() + }) + + if not wasAtBottom then + Yipper.messageFrame:ScrollUp() + end + end +end + +-- Create the instance and store it in Yipper to make it available. +Yipper.EventListener = EventListener.new() diff --git a/source/PlayerTracker.lua b/source/PlayerTracker.lua new file mode 100644 index 0000000..77fd6eb --- /dev/null +++ b/source/PlayerTracker.lua @@ -0,0 +1,65 @@ +--- PlayerTracker +-- +-- The PlayerTracker is responsible for keeping track of which player is currently tracked. +-- The user can have multiple windows open to track additional players, but one window will always +-- be used for the tracked player, and that is handled by this class to access the data. +local addonName, Yipper = ... + +-- Initialize the module and set the function look up. +local PlayerTracker = {} +PlayerTracker.__index = PlayerTracker + +--- Creates a new instance of the PlayerTracker. +--- @return table An instance of the PlayerTracker class. +function PlayerTracker.new() + local newObject = setmetatable({}, self) + + -- Object initialization + newObject:Initialize() + + return newObject +end + +--- Initializes the instance, ensuring the frame and event handling is in place. +---@return nil +---@private +function PlayerTracker:Initialize() + -- Create the ticket that tracks the player every 0.1 seconds. + self._ticker = C_Timer.NewTicker(0.1, function() self:UpdateTrackedPlayer() end) + self._trackedPlayer = nil +end + +--- Updates the tracked player by checking if we're hovering over someone, or +--- have someone selected as target. +---@return nil +---@private +function PlayerTracker:UpdateTrackedPlayer() + -- Variable for tracking the new potential target + local newTrackedPlayerGuid + + -- Hovering takes precedence, if we're hovering over a player, + -- that will be our new target. + -- Otherwise check if the target we have currently have is a player. + if UnitGUID("mouseover") ~= nil and UnitIsPlayer("mouseover") then + newTrackedPlayerGuid = UnitGUID("mouseover") + elseif UnitGUID("target") ~= nil and UnitIsPlayer("target") then + newTrackedPlayerGuid = UnitGUID("target") + end + + -- Do not proceed if the Guid is a secret. + -- In PropHunt we're able to track players, but their values are secret. + -- Attempting anything will just break the AddOn. + if Yipper.API:IsSecret(newTrackedPlayerGuid) then + return + end + + -- Only update if the newTrackedPlayer is different from the one we're + -- currently tracking. This accounts for hover, deselect or selecting + -- a target. + if newTrackedPlayerGuid ~= self._trackedPlayer then + self._trackedPlayer = newTrackedPlayerGuid + + -- Raise a custom event that the player changed. + C_Event.TriggerEvent("YIPPER_TRACKED_PLAYER_CHANGED", self._trackedPlayer) + end +end diff --git a/source/Yipper.Api.lua b/source/Yipper.Api.lua new file mode 100644 index 0000000..e53e5aa --- /dev/null +++ b/source/Yipper.Api.lua @@ -0,0 +1,37 @@ +--- Yipper API +-- +-- This file represents the API of Yipper and the methods exposed towards other AddOns running in the client. +-- For more documentation, please check out our wiki on the usage of the methods. +-- +local addonName, Yipper = ... + +-- Define the table for the API so that other AddOns can simply call Yipper.API when the AddOn has been +-- loaded by the client. +Yipper.API = {} + +------------------------------------------------------------------------------------------------------------------------ +-- API Definition +------------------------------------------------------------------------------------------------------------------------ + +--- Returns all messages for the specified player +---@param guid string The GUID identifying the player. +---@return table An array of messages for the player +---@return nil If the GUID is secret or the table is empty +function Yipper.API:MessagesForPlayer(guid) + if self:IsSecret(guid) then + return nil + end + + if not Yipper.DB.Messages[guid] ~= nil then + return nil + end + + return Yipper.DB.Messages[guid] +end + +--- Determines whether the provided value is considered a secret or not. +--- @param value any The value to check for being a secret. +--- @return bool True if the value is a secret; otherwise false +function Yipper.API:IsSecret(value) + return issecretvalue(value) and not canaccessvalue(value) +end diff --git a/src/Core.lua b/src/Core.lua index 0a8b4a0..1677b50 100644 --- a/src/Core.lua +++ b/src/Core.lua @@ -92,8 +92,8 @@ function Yipper:OnEvent(event, ...) end -- If the Yipper UI Settings is available, initialize it - if Yipper.UI.Settings then - Yipper.UI.Settings:Init() + if Yipper.SettingsFrame then + Yipper.SettingsFrame:Init() end -- If the Yipper minimap is available, initialize it diff --git a/src/Events.lua b/src/Events.lua deleted file mode 100644 index ee3136e..0000000 --- a/src/Events.lua +++ /dev/null @@ -1,251 +0,0 @@ -local addonName, Yipper = ... - -Yipper.Events = {} - ---- Initialises the Chat module and registers the required events --- Registers all the events that Yipper listens to, and sets up two dedicated timers: --- --- _trackedPlayerTicker: Fires every 100ms to check which player is tracked. --- _messageTicker: Fires every minute to refresh the messages and their fading color. --- --- @return nil -function Yipper.Events:Init() - for _, event in pairs(Yipper.Constants.ChatEvents) do - Yipper.mainFrame:RegisterEvent(event) - end - - -- Set up a timer that will trigger our tracking update - -- This helps us in checking whether the mouse has actually - -- left the unit as well. - if not self._trackedPlayerTicker then - self._trackedPlayerTicker = C_Timer.NewTicker(0.1, function() - self:UpdateTrackedPlayer() - end) - end - - -- Set up a timer that will refresh the messages in the chat - -- frame every minute, to allow us to properly apply the coloring. - -- This can be achieved by simply wiping the messages and then - -- adding them all on the fly. - if not self._messageTicker then - self._messageTicker = C_Timer.NewTicker(60, function() - Yipper.UI:UpdateDisplayedText() - end) - end -end - ---- Handles the incoming chat events --- @param event The name of the event being triggered --- @param ... The arguments passed down with the event (message, sender, etc.) --- --- The code is identical for most events, but we keep the splitting separate, --- just in case we need to account for special situation, so we can isolate the --- problem message's logic in the future. -function Yipper.Events:OnEvent(event, ...) - if event == "CHAT_MSG_SAY" then - local message, _, _, _, _, _, _, _, _, _, lineId, guid = ... - self:StoreMessage(message, guid, lineId, event) - elseif event == "CHAT_MSG_EMOTE" then - local message, _, _, _, _, _, _, _, _, _, lineId, guid = ... - - -- NPC Emotes in instances do not have a GUID. - -- Ignore these events - if guid == nil then - return - end - - self:StoreMessage(message, guid, lineId, event) - elseif event == "CHAT_MSG_GUILD" then - local message, _, _, _, _, _, _, _, _, _, lineId, guid = ... - self:StoreMessage(message, guid, lineId, event) - elseif event == "CHAT_MSG_OFFICER" then - local message, _, _, _, _, _, _, _, _, _, lineId, guid = ... - self:StoreMessage(message, guid, lineId, event) - elseif event == "CHAT_MSG_PARTY" then - local message, _, _, _, _, _, _, _, _, _, lineId, guid = ... - self:StoreMessage(message, guid, lineId, event) - elseif event == "CHAT_MSG_PARTY_LEADER" then - local message, _, _, _, _, _, _, _, _, _, lineId, guid = ... - self:StoreMessage(message, guid, lineId, event) - elseif event == "CHAT_MSG_RAID" then - local message, _, _, _, _, _, _, _, _, _, lineId, guid = ... - self:StoreMessage(message, guid, lineId, event) - elseif event == "CHAT_MSG_RAID_LEADER" then - local message, _, _, _, _, _, _, _, _, _, lineId, guid = ... - self:StoreMessage(message, guid, lineId, event) - elseif event == "CHAT_MSG_YELL" then - local message, _, _, _, _, _, _, _, _, _, lineId, guid = ... - self:StoreMessage(message, guid, lineId, event) - elseif event == "CHAT_MSG_WHISPER" then - local message, _, _, _, _, _, _, _, _, _, lineId, guid = ... - self:StoreMessage(message, guid, lineId, event) - elseif event == "CHAT_MSG_RAID_WARNING" then - local message, _, _, _, _, _, _, _, _, _, lineId, guid = ... - self:StoreMessage(message, guid, lineId, event) - elseif event == "CHAT_MSG_SYSTEM" then - local message, _, _, _, _, _, _, _, _, _, lineId, guid = ... - - -- Do not attempt to process messages when they are secret. - -- If we're dealing with a secret system message, just ignore it. - -- Means the player is in combat, and these messages are useless for us. - if Yipper.Utils:IsSecret(message) then - return - end - - -- If the event doesn't include "rolls", it's not a roll event - -- and we can discard it - if not string.find(message, "rolls") then - return - end - - local author, rollResult, rollMin, rollMax = string.match(message, "(.+) rolls (%d+) %((%d+)-(%d+)%)"); - - -- Only broadcast our own messages, otherwise every single roll event will be broadcast as "us". - -- We don't want that, we want the AddOn to receive rolls from other people and process them - -- accordingly. - if author == UnitName("player") then - -- Because Blizzard in all their wisdom decided not to include the realm or anything - -- tangible for these events, we'll do it ourselves with a custom event. - -- If we rolled, just broadcast the roll over the Comms and let the listening - -- addons handle it to parse the roll message properly with the needed data. - Yipper.Comms:BroadcastMessage(message.."||"..UnitGUID("player")) - end - elseif event == "CHAT_MSG_ADDON_LOGGED" then - local prefix, message, channel, sender, target, zoneChannelId, localID, name, instanceID = ... - - -- We only care about messages for Yipper, ignore everything else. - if prefix == addonName then - -- Since this will just be a roll broadcast by someone, - -- Add it to the message list as a system message. - local actualMessage, guid = message:match("^(.+)||(.+)$") - self:StoreMessage(actualMessage, guid, GetChatTypeIndex("SYSTEM"), "CHAT_MSG_SYSTEM") - end - elseif event == "CHAT_MSG_TEXT_EMOTE" then - local message, _, _, _, _, _, _, _, _, _, lineId, guid = ... - - -- NPC Emotes in instances do not have a GUID. - -- Ignore these events - if guid == nil then - return - end - - -- Because actual emotes work different, - -- we need to fix the sender in both cases. - -- If we're the sender, just construct it using the realm. - if sender == UnitName("player") then - guid = UnitGUID("player") - end - - self:StoreMessage(message, guid, lineId, event) - end -end - ---- Stores a message with the required arguments for the specific player. ---- This builds the table with messages and automatically trims it as well ---- when the maximum amount is exceeded. -function Yipper.Events:StoreMessage(message, guid, lineId, event) - -- Since our entire logic hinges on the guid not being a secret, - -- we will drop the entire message in case the guid is flagged as secret. - -- When you're in combat, you really don't care about RP anyways. - if Yipper.Utils:IsSecret(guid) then - return - end - - -- Sanity check, to ensure nothing bad happens in case - -- the table is not set... - if not Yipper.DB.Messages then - Yipper.DB.Messages = {} - end - - -- Check if the sender has a record table, might be the first time they're sending - -- a message. - if not Yipper.DB.Messages[guid] then - Yipper.DB.Messages[guid] = { } - end - - -- Inject the record in the table. - table.insert(Yipper.DB.Messages[guid], { - ["message"] = message, - ["lineId"] = lineId, - ["event"] = event, - ["timestamp"] = date("%H:%M"), - ["epoch"] = time() - }) - - -- If we have exceeded the maximum amount of messages, - -- remove the oldest ones. - if(table.getn(Yipper.DB.Messages) > Yipper.DB.MaxMessages) then - table.remove(Yipper.DB.Messages, 1) - end - - -- Play the notification sound, if applicable. - Yipper.Utils:PlayNotification(message, guid) - - -- Push the message into the messageFrame if the sender is the user currently - -- being tracked. - if guid == Yipper.TrackedPlayerGuid then - -- Check if we're at the bottom - local wasAtBottom = Yipper.messageFrame:AtBottom() - - -- Push the message into the frame. - Yipper.UI:AddMessageToFrame({ - ["message"] = message, - ["lineId"] = lineId, - ["event"] = event, - ["timestamp"] = date("%H:%M"), - ["epoch"] = time() - }) - - if not wasAtBottom then - Yipper.messageFrame:ScrollUp() - end - end -end - --- Yipper.Events - UpdateTrackedPlayer --- --- Updates the tracked player based on the following order: ---- 1. Are we hovering over someone with the the mouse? ---- 2. Do we have someone selected? ---- 3. Set to nil in all other cases. --- --- Important: This method is called by a Ticker every 0.1 seconds! --- --- The value being tracked here can become secret in the Proving Grounds --- Because blizzard decided in their wisdom that the GUID is secret there, --- as well as using fake player values. -function Yipper.Events:UpdateTrackedPlayer() - -- Variable for tracking the new potential target - local newTrackedPlayerGuid - - -- Hovering takes precedence, if we're hovering over a player, - -- that will be our new target. - -- Otherwise check if the target we have currently have is a player. - if UnitGUID("mouseover") ~= nil and UnitIsPlayer("mouseover") then - newTrackedPlayerGuid = UnitGUID("mouseover") - elseif UnitGUID("target") ~= nil and UnitIsPlayer("target") then - newTrackedPlayerGuid = UnitGUID("target") - end - - -- Do not proceed if the Guid is a secret. - -- In PropHunt we're able to track players, but their values are secret. - -- Attempting anything will just break the AddOn. - if Yipper.Utils:IsSecret(newTrackedPlayerGuid) then - return - end - - -- Only update if the newTrackedPlayer is different from the one we're - -- currently tracking. This accounts for hover, deselect or selecting - -- a target. - if newTrackedPlayerGuid ~= Yipper.TrackedPlayerGuid then - Yipper.TrackedPlayerGuid = newTrackedPlayerGuid - - -- If we have a target or hover, show their messages. - -- If not, clear the messages. - if Yipper.TrackedPlayerGuid then - Yipper.UI.UpdateDisplayedText() - else - Yipper.messageFrame:Clear() - end - end -end diff --git a/src/UI.lua b/src/UI.lua index 0308961..7a170b3 100644 --- a/src/UI.lua +++ b/src/UI.lua @@ -268,7 +268,7 @@ function Yipper.UI:UpdateDisplayedText() if Yipper.TrackedPlayerGuid == nil or -- No player tracked Yipper.DB.Messages == nil or -- new character, no messages yet initializes Yipper.DB.Messages[Yipper.TrackedPlayerGuid] == nil or -- Player has not produced messages - Yipper.Utils:IsSecret(Yipper.TrackedPlayerGuid) then -- Value is a secret, don't bother... + Yipper.API:IsSecret(Yipper.TrackedPlayerGuid) then -- Value is a secret, don't bother... return end diff --git a/src/Utils.lua b/src/Utils.lua index 4ad6fa4..f742617 100644 --- a/src/Utils.lua +++ b/src/Utils.lua @@ -7,14 +7,6 @@ local _, Yipper = ... -- Initialize the module Yipper.Utils = {} --- Yipper.Utils - IsSecret --- --- Function that returns true when the passed in data is a secret --- and not accessible for safe usage. -function Yipper.Utils:IsSecret(value) - return issecretvalue(value) and not canaccessvalue(value) -end - -- Yipper.Utils - IsUpdated -- -- Returns a boolean if Yipper has been updated. @@ -90,7 +82,7 @@ end -- TODO: Expand to include the TRP3 Profile at some point. function Yipper.Utils:ColorizeMessage(message) -- Don't bother editing the message if we can't work with it. - if self:IsSecret(message) then + if Yipper.API:IsSecret(message) then return message end diff --git a/src/UI.Settings.lua b/ui/YipperSettingsFrameTemplate.lua similarity index 51% rename from src/UI.Settings.lua rename to ui/YipperSettingsFrameTemplate.lua index e622142..4c1880e 100644 --- a/src/UI.Settings.lua +++ b/ui/YipperSettingsFrameTemplate.lua @@ -1,97 +1,23 @@ --- Yipper - UI.Settings +-- Yipper - YipperSettingsFrameTemplate (code-behind) -- --- This file controls the settings Window of Yipper. --- The Settings UI will be registered under the main AddOn --- but is only invoked through commands from the command line --- or minimap. +-- Companion script for ui/YipperSettingsFrameTemplate.xml. The XML +-- declares the static structure of the settings window; this file +-- exposes the Yipper.SettingsFrame module and the per-widget wiring +-- functions that the template's OnLoad scripts call into. local addonName, Yipper = ... -local backdropConfiguration = { - bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], - edgeFile = [[Interface\Tooltips\UI-Tooltip-Border]], - edgeSize = 16, - insets = { left = 4, right = 3, top = 4, bottom = 3 } -} -- Initialize the module -Yipper.UI.Settings = { } - --- Initializes the UI.Settings module, constructing the main frame for --- the module, and registering the desired behavior for the buttons and --- events. -function Yipper.UI.Settings:Init() - -- Create the main frame for us to draw all components in. - local frame = CreateFrame("Frame", nil, UIParent) - local backgroundColor = Yipper.DB.BackgroundColor or Yipper.Constants.BlackColor - local borderColor = Yipper.DB.BorderColor or Yipper.Constants.BlackColor - local alpha = Yipper.DB.WindowAlpha or Yipper.Constants.Alpha - - -- Apply the required Mixin to our frame so we have access to the - -- needed functions. - Mixin(frame, BackdropTemplateMixin) - - -- Configure the frame - frame:SetSize(400, 600) - frame:SetBackdrop(backdropConfiguration) - frame:SetBackdropColor(backgroundColor.r, backgroundColor.g, backgroundColor.b, alpha / 100) - frame:SetBackdropBorderColor(borderColor.r, borderColor.g, borderColor.b, alpha / 100) - - -- Add behavior to our frame - -- Make it movable - frame:SetMovable(true) - frame:EnableMouse(true) - frame:RegisterForDrag("LeftButton") - frame:SetScript("OnDragStart", function(self) - self:StartMoving() - end) - frame:SetScript("OnDragStop", function(self) - self:StopMovingOrSizing() - Yipper.UI.Settings:SavePosition() -- Save after moving - end) +Yipper.SettingsFrame = { } - -- Create close button (top-right corner) - local closeButton = CreateFrame("Button", nil, frame, "UIPanelCloseButton") +-- Initializes the SettingsFrame module by instantiating the XML +-- template, storing the reference, and applying runtime state +-- that the template can't express declaratively. +function Yipper.SettingsFrame:Init() + local frame = CreateFrame("Frame", "Yipper_SettingsFrame_Mainframe", UIParent, "Yipper_SettingsFrame_Template") - closeButton:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -2, -2) - closeButton:SetSize(20, 20) - closeButton:SetScript("OnClick", function() - frame:Hide() - end) - - -- Header - local headerHeight = 30 - local headerRightInset = 45 -- keeps text clear of close button and the scrollbar gutter - - local headerFrame = CreateFrame("Frame", nil, frame) - headerFrame:SetHeight(headerHeight) - headerFrame:SetPoint("TOPLEFT", frame, "TOPLEFT", 6, -4) - headerFrame:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -headerRightInset, -4) - - local headerText = headerFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge") - headerText:SetPoint("TOPLEFT", headerFrame, "TOPLEFT", 0, 0) - headerText:SetPoint("BOTTOMRIGHT", headerFrame, "BOTTOMRIGHT", 0, 0) - headerText:SetText("Yipper Settings") - headerText:SetJustifyH("LEFT") - headerText:SetJustifyV("MIDDLE") - - -- Store the reference Yipper.settingsFrame = frame - -- Offload the creation of settings to specific functions - -- Keeps our code here manageable - self:AddMinimapSettings() - self:AddHeaderSettings() - self:AddTrackerSettings() - self:AddAlphaSettings() - self:AddBackgroundColorPicker() - self:AddBorderColorPicker() - self:AddFontSizeSettings() - self:AddFontSelectionSettings() - self:AddNotificationColorPicker() - self:NotificationSelectionSettings() - self:KeywordSettings() - - -- Define a custom toggle function - Yipper.settingsFrame.Toggle = function(self) + frame.Toggle = function(self) if self:IsShown() then self:Hide() else @@ -99,18 +25,27 @@ function Yipper.UI.Settings:Init() end end - -- Restore saved position/size or use defaults self:RestorePosition() + frame:Hide() +end + +-- Applies the backdrop colors from the DB. Runs as the template's +-- OnLoad, before Yipper.settingsFrame has been assigned, so we +-- operate on the frame argument directly. +function Yipper.SettingsFrame:OnTemplateLoaded(frame) + local backgroundColor = Yipper.DB.BackgroundColor or Yipper.Constants.BlackColor + local borderColor = Yipper.DB.BorderColor or Yipper.Constants.BlackColor + local alpha = Yipper.DB.WindowAlpha or Yipper.Constants.Alpha - -- Do not show the settings frame by default - Yipper.settingsFrame:Hide() + frame:SetBackdropColor(backgroundColor.r, backgroundColor.g, backgroundColor.b, alpha / 100) + frame:SetBackdropBorderColor(borderColor.r, borderColor.g, borderColor.b, alpha / 100) end -- Yipper.UI - SavePosition -- -- Stores the reference point of the mainFrame, as well as its dimensions -- inside the Yipper.DB so the game can save these values on exit. -function Yipper.UI.Settings:SavePosition() +function Yipper.SettingsFrame:SavePosition() local point, _, relativePoint, xOfs, yOfs = Yipper.settingsFrame:GetPoint() local width, height = Yipper.settingsFrame:GetSize() @@ -128,30 +63,25 @@ end -- -- Loads the relative point of the mainFrame, as well as its dimensions -- from the Yipper.DB and applies them to the mainFrame when loaded. -function Yipper.UI.Settings:RestorePosition() +function Yipper.SettingsFrame:RestorePosition() local pos = Yipper.DB.SettingsFramePosition if pos then - -- Restore saved position and size Yipper.settingsFrame:ClearAllPoints() Yipper.settingsFrame:SetPoint(pos.point, UIParent, pos.relativePoint, pos.xOfs, pos.yOfs) Yipper.settingsFrame:SetSize(pos.width, pos.height) else - -- Use default position and size Yipper.settingsFrame:SetSize(400, 600) Yipper.settingsFrame:SetPoint("CENTER", UIParent, "CENTER", 0, 0) end end --- Yipper.UI.Settings - AddMinimapSettings +-- Yipper.SettingsFrame - AddMinimapSettings -- --- Responsible for hooking up the UI components to be able to toggle --- the minimap button of the AddOn. Some users like buttons, some don't. -function Yipper.UI.Settings:AddMinimapSettings() - local checkbox = CreateFrame("CheckButton", nil, Yipper.settingsFrame, "ChatConfigCheckButtonTemplate") - +-- Wires the minimap-visibility checkbox to the DB and the +-- minimap frame. +function Yipper.SettingsFrame:AddMinimapSettings(checkbox) checkbox:SetChecked(Yipper.DB.EnableMinimapButton) - checkbox:SetPoint("TOPLEFT", 30, -35) checkbox.Text:SetText("Toggle Minimap Visibility") checkbox.tooltip = "Toggle the visibility of the Yipper Minimap Button" checkbox:HookScript("OnClick", function(self) @@ -165,15 +95,12 @@ function Yipper.UI.Settings:AddMinimapSettings() end) end --- Yipper.UI.Settings - AddHeaderSettings +-- Yipper.SettingsFrame - AddHeaderSettings -- --- Responsible for hooking up the UI components to be able to toggle --- the header of the mainFrame. Some users like to hide this. -function Yipper.UI.Settings:AddHeaderSettings() - local checkbox = CreateFrame("CheckButton", nil, Yipper.settingsFrame, "ChatCOnfigCheckButtonTemplate") - +-- Wires the header-visibility checkbox to the DB and the +-- header frame. +function Yipper.SettingsFrame:AddHeaderSettings(checkbox) checkbox:SetChecked(Yipper.DB.ShowHeader) - checkbox:SetPoint("TOPLEFT", 30, -65) checkbox.Text:SetText("Toggle Header Visibility") checkbox.tooltip = "Toggle the visibility of the Yipper Header" checkbox:HookScript("OnClick", function(self) @@ -189,15 +116,11 @@ function Yipper.UI.Settings:AddHeaderSettings() end) end --- Yipper.UI.Settings - AddTrackerSettings +-- Yipper.SettingsFrame - AddTrackerSettings -- --- Responsible for hooking up the UI components to be able to toggle --- the the tracking of notifications for the talking target. -function Yipper.UI.Settings:AddTrackerSettings() - local checkbox = CreateFrame("CheckButton", nil, Yipper.settingsFrame, "ChatCOnfigCheckButtonTemplate") - +-- Wires the tracker-notification checkbox to the DB. +function Yipper.SettingsFrame:AddTrackerSettings(checkbox) checkbox:SetChecked(Yipper.DB.PingTrackedPlayer) - checkbox:SetPoint("TOPLEFT", 30, -95) checkbox.Text:SetText("Notify on message") checkbox.tooltip = "Sends notification sounds when the tracked player sends a message." checkbox:HookScript("OnClick", function(self) @@ -205,11 +128,11 @@ function Yipper.UI.Settings:AddTrackerSettings() end) end --- Yipper.UI.Settings - AddAlphaSettings +-- Yipper.SettingsFrame - AddAlphaSettings -- --- Responsible for hooking up the UI components to be able to control the --- alpha levels of the windows to set their transparency. -function Yipper.UI.Settings:AddAlphaSettings() +-- Configures the window-alpha slider with formatters and binds +-- value changes back to the DB and main frame. +function Yipper.SettingsFrame:AddAlphaSettings(slider) local minvalue = 0 local maxValue = 100 local stepValue = 1 @@ -220,13 +143,7 @@ function Yipper.UI.Settings:AddAlphaSettings() options:SetLabelFormatter(MinimalSliderWithSteppersMixin.Label.Min, function(_) return minvalue end); options:SetLabelFormatter(MinimalSliderWithSteppersMixin.Label.Top, function(_) return "Window Alpha" end); - local slider = CreateFrame("Frame", nil, Yipper.settingsFrame, "MinimalSliderWithSteppersTemplate") - - slider:SetWidth(330, 20) - slider:SetPoint("TOPLEFT", 30, -130) slider:Init(Yipper.DB.WindowAlpha or 100, options.minValue, options.maxValue, options.steps, options.formatters) - - -- When the value changes, apply the alpha, but keep the color! slider:RegisterCallback("OnValueChanged", function(_, value) Yipper.DB.WindowAlpha = value @@ -237,11 +154,11 @@ function Yipper.UI.Settings:AddAlphaSettings() end, slider) end --- Yipper.UI.Settings - AddFontSizeSettings +-- Yipper.SettingsFrame - AddFontSizeSettings -- --- Responsible for hooking up the UI components to be able to control the --- font size of the windows to make text more readable. -function Yipper.UI.Settings:AddFontSizeSettings() +-- Configures the font-size slider with formatters and binds +-- value changes back to the DB and message frame. +function Yipper.SettingsFrame:AddFontSizeSettings(slider) local minvalue = 8 local maxValue = 64 local stepValue = 2 @@ -252,10 +169,6 @@ function Yipper.UI.Settings:AddFontSizeSettings() options:SetLabelFormatter(MinimalSliderWithSteppersMixin.Label.Min, function(_) return minvalue end); options:SetLabelFormatter(MinimalSliderWithSteppersMixin.Label.Top, function(_) return "Font Size" end); - local slider = CreateFrame("Frame", nil, Yipper.settingsFrame, "MinimalSliderWithSteppersTemplate") - - slider:SetWidth(330) - slider:SetPoint("TOPLEFT", 30, -170) slider:Init( Yipper.DB.FontSize or Yipper.Constants.FontSize, options.minValue, @@ -269,31 +182,22 @@ function Yipper.UI.Settings:AddFontSizeSettings() end, slider) end --- Yipper.UI.Settings - AddBackgroundColorPicker +-- Yipper.SettingsFrame - AddBackgroundColorPicker -- --- Responsible for hooking up the UI components to be able to select the background --- color of the windows. -function Yipper.UI.Settings:AddBackgroundColorPicker() +-- Wires the background color button to ColorPickerFrame and to +-- the DB. The tinted texture lives in the XML. +function Yipper.SettingsFrame:AddBackgroundColorPicker(colorButton) local color = Yipper.DB.BackgroundColor or Yipper.Constants.BlackColor local alpha = Yipper.DB.WindowAlpha or Yipper.Constants.Alpha - local colorButton = CreateFrame("Button", nil, Yipper.settingsFrame, "UIPanelButtonTemplate") - - colorButton:SetSize(340, 20) - colorButton:SetPoint("TOPLEFT", 30, -225) - colorButton:SetText("Set Background Color") - colorButton.Texture = colorButton:CreateTexture() - colorButton.Texture:SetAllPoints() - colorButton.Texture:SetTexture("Interface\\BUTTONS\\WHITE8X8")-- just a white square but could be anything (presumably white) + colorButton.Texture:SetVertexColor(color.r, color.g, color.b) colorButton:SetScript("OnClick", function(_) local function OnColorChanged() local red, green, blue = ColorPickerFrame:GetColorRGB() - -- Store the selected color Yipper.DB.BackgroundColor = { ["r"] = red, ["g"] = green, ["b"] = blue } - -- Update UI components colorButton.Texture:SetVertexColor(red, green, blue) Yipper.settingsFrame:SetBackdropColor(red, green, blue, alpha / 100) Yipper.mainFrame:SetBackdropColor(red, green, blue, alpha / 100) @@ -302,10 +206,8 @@ function Yipper.UI.Settings:AddBackgroundColorPicker() local function OnCancel() local red, green, blue = ColorPickerFrame:GetPreviousValues() - -- Store the selected color Yipper.DB.BackgroundColor = { ["r"] = red, ["g"] = green, ["b"] = blue } - -- Update UI components colorButton.Texture:SetVertexColor(red, green, blue) Yipper.settingsFrame:SetBackdropColor(red, green, blue, alpha / 100) Yipper.mainFrame:SetBackdropColor(red, green, blue, alpha / 100) @@ -326,31 +228,21 @@ function Yipper.UI.Settings:AddBackgroundColorPicker() end) end --- Yipper.UI.Settings - AddBorderColorPicker +-- Yipper.SettingsFrame - AddBorderColorPicker -- --- Responsible for hooking up the UI components to be able to select the background --- color of the windows. -function Yipper.UI.Settings:AddBorderColorPicker() +-- Wires the border color button to ColorPickerFrame and to the DB. +function Yipper.SettingsFrame:AddBorderColorPicker(colorButton) local color = Yipper.DB.BorderColor or Yipper.Constants.BlackColor local alpha = Yipper.DB.WindowAlpha or Yipper.Constants.Alpha - local colorButton = CreateFrame("Button", nil, Yipper.settingsFrame, "UIPanelButtonTemplate") - - colorButton:SetSize(340, 20) - colorButton:SetPoint("TOPLEFT", 30, -255) - colorButton:SetText("Set Border Color") - colorButton.Texture = colorButton:CreateTexture() - colorButton.Texture:SetAllPoints() - colorButton.Texture:SetTexture("Interface\\BUTTONS\\WHITE8X8")-- just a white square but could be anything (presumably white) + colorButton.Texture:SetVertexColor(color.r, color.g, color.b) colorButton:SetScript("OnClick", function(_) local function OnColorChanged() local red, green, blue = ColorPickerFrame:GetColorRGB() - -- Store the selected color Yipper.DB.BorderColor = { ["r"] = red, ["g"] = green, ["b"] = blue } - -- Update UI components colorButton.Texture:SetVertexColor(red, green, blue) Yipper.settingsFrame:SetBackdropBorderColor(red, green, blue, alpha / 100) Yipper.mainFrame:SetBackdropBorderColor(red, green, blue, alpha / 100) @@ -359,10 +251,8 @@ function Yipper.UI.Settings:AddBorderColorPicker() local function OnCancel() local red, green, blue = ColorPickerFrame:GetPreviousValues() - -- Store the selected color Yipper.DB.BorderColor = { ["r"] = red, ["g"] = green, ["b"] = blue } - -- Update UI components colorButton.Texture:SetVertexColor(red, green, blue) Yipper.settingsFrame:SetBackdropBorderColor(red, green, blue, alpha / 100) Yipper.mainFrame:SetBackdropBorderColor(red, green, blue, alpha / 100) @@ -383,42 +273,29 @@ function Yipper.UI.Settings:AddBorderColorPicker() end) end --- Yipper.UI.Settings - AddNotificationColorPicker +-- Yipper.SettingsFrame - AddNotificationColorPicker -- --- Responsible for hooking up the UI components to be able to select the background --- color of the windows. -function Yipper.UI.Settings:AddNotificationColorPicker() +-- Wires the notification color button to ColorPickerFrame and to +-- the DB. Colors are stored in 0-255 range to match other constants. +function Yipper.SettingsFrame:AddNotificationColorPicker(colorButton) local color = Yipper.DB.NotificationColor or Yipper.Constants.NotificationColor - local colorButton = CreateFrame("Button", nil, Yipper.settingsFrame, "UIPanelButtonTemplate") - - colorButton:SetSize(340, 20) - colorButton:SetPoint("TOPLEFT", 30, -285) - colorButton:SetText("Set Notification Color") - colorButton.Texture = colorButton:CreateTexture() - colorButton.Texture:SetAllPoints() - colorButton.Texture:SetTexture("Interface\\BUTTONS\\WHITE8X8")-- just a white square but could be anything (presumably white) + colorButton.Texture:SetVertexColor(color.r / 255, color.g / 255, color.b / 255) colorButton:SetScript("OnClick", function(_) local function OnColorChanged() local red, green, blue = ColorPickerFrame:GetColorRGB() - -- Store the selected color in the 0-255 range to stay consistent - -- with the other color constants. Yipper.DB.NotificationColor = { ["r"] = red * 255, ["g"] = green * 255, ["b"] = blue * 255 } - -- Update UI components colorButton.Texture:SetVertexColor(red, green, blue) end local function OnCancel() local red, green, blue = ColorPickerFrame:GetPreviousValues() - -- Store the selected color in the 0-255 range to stay consistent - -- with the other color constants. Yipper.DB.NotificationColor = { ["r"] = red * 255, ["g"] = green * 255, ["b"] = blue * 255 } - -- Update UI components colorButton.Texture:SetVertexColor(red, green, blue) end @@ -438,14 +315,11 @@ function Yipper.UI.Settings:AddNotificationColorPicker() end) end --- Yipper.UI.Settings - NotificationSelectionSettings +-- Yipper.SettingsFrame - NotificationSelectionSettings -- --- Responsible for hooking up the UI components to be able to control the --- notification sound. -function Yipper.UI.Settings:NotificationSelectionSettings() - -- Local function to determine whether a button is selected based upon their index. - -- We use the index to determine the sound ID, if it matches what's currently selected - -- then flag the radio button as selected. +-- Wires the notification-sound dropdown's generator to its +-- backing DB value. +function Yipper.SettingsFrame:NotificationSelectionSettings(dropdown) local function IsSelected(index) if index == "None" then return Yipper.DB.NotificationSound == nil @@ -454,9 +328,6 @@ function Yipper.UI.Settings:NotificationSelectionSettings() end end - -- Local function to determine what to do when a button is selected. - -- The index is the name of the key in our sound table. - -- Set the ID and play sound on selection as preview. local function SetSelected(index) if index == "None" then Yipper.DB.NotificationSound = nil @@ -465,49 +336,32 @@ function Yipper.UI.Settings:NotificationSelectionSettings() Yipper.DB.NotificationSound = soundId - -- If a soundId was selected, play the sound. if soundId then PlaySound(soundId) end end end - -- Generator function to create all the buttons. local function GeneratorFunction(owner, rootDescription) - -- Set the Title rootDescription:CreateTitle("Notification Sound Selection") - -- Generate the radio buttons for key, sound in pairs(Yipper.Constants.Sounds) do rootDescription:CreateRadio(sound.name, IsSelected, SetSelected, key) end - -- Add a Divider rootDescription:CreateDivider() - - -- Add the "None" button rootDescription:CreateRadio("None", IsSelected, SetSelected, "None") end - -- Create and configure the DropDown. - local dropdown = CreateFrame("DropdownButton", nil, Yipper.settingsFrame, "UIPanelButtonTemplate") - - dropdown:SetText("Select Notification Sound") - dropdown:SetSize(340, 20) - dropdown:SetPoint("TOPLEFT", 30, -310) dropdown:SetupMenu(GeneratorFunction) - dropdown.Texture = dropdown:CreateTexture() - dropdown.Texture:SetAllPoints() - dropdown.Texture:SetTexture("Interface\\BUTTONS\\WHITE8X8")-- just a white square but could be anything (presumably white) dropdown.Texture:SetVertexColor(0, 0, 0) end --- Yipper.UI.Settings - AddFontSelectionSettings +-- Yipper.SettingsFrame - AddFontSelectionSettings -- --- Responsible for hooking up the UI components to be able to control the --- font of the windows to make text more readable. -function Yipper.UI.Settings:AddFontSelectionSettings() - -- Generator function to construct the actual button +-- Wires the font-selection dropdown's generator to the available +-- fonts and the message frame. +function Yipper.SettingsFrame:AddFontSelectionSettings(dropdown) local createButton = function(root, display, font) root:CreateButton(display, function(data) local fontSize = Yipper.DB.FontSize @@ -517,7 +371,6 @@ function Yipper.UI.Settings:AddFontSelectionSettings() end) end - -- Generator function to create all the buttons. local function GeneratorFunction(owner, rootDescription) rootDescription:CreateTitle("Font Selection") for name, path in pairs(Yipper.Constants.Fonts) do @@ -525,40 +378,17 @@ function Yipper.UI.Settings:AddFontSelectionSettings() end end - -- Create and configure the DropDown. - local dropdown = CreateFrame("DropdownButton", nil, Yipper.settingsFrame, "UIPanelButtonTemplate") - - dropdown:SetText("Select Font") - dropdown:SetSize(340, 20) - dropdown:SetPoint("TOPLEFT", 30, -340) dropdown:SetupMenu(GeneratorFunction) - dropdown.Texture = dropdown:CreateTexture() - dropdown.Texture:SetAllPoints() - dropdown.Texture:SetTexture("Interface\\BUTTONS\\WHITE8X8")-- just a white square but could be anything (presumably white) dropdown.Texture:SetVertexColor(0, 0, 0) end --- Yipper.UI.Settings - KeywordSettings +-- Yipper.SettingsFrame - KeywordSettings -- --- This function is responsible for generating the UI components to set the --- additional keywords on which additional notifications should happen. --- These keywords are separated by commas, and treated as is. -function Yipper.UI.Settings:KeywordSettings() - local editBox = CreateFrame("EditBox", nil, Yipper.settingsFrame) - - Mixin(editBox, BackdropTemplateMixin) - +-- Populates the keyword editbox from the DB and persists edits +-- when focus is lost. +function Yipper.SettingsFrame:KeywordSettings(editBox) editBox:SetFontObject(ChatFontNormal) - editBox:SetBackdrop(backdropConfiguration) - editBox:SetBackdropColor(0, 0, 0, 1) - editBox:SetBackdropBorderColor(0.4, 0.4, 0.4, 1) - editBox:SetTextInsets(4, 4, 3, 3) - editBox:EnableMouse(true) - editBox:SetMultiLine(true) - editBox:SetSize(340, 1) - editBox:SetPoint("TOPLEFT", 30, -390) - - -- Construct the text to display; account for not keywords being stored. + local keywords = "" if Yipper.DB.Keywords then @@ -566,9 +396,7 @@ function Yipper.UI.Settings:KeywordSettings() end editBox:SetText(keywords or "", ",") - -- Set the script to save the data automatically when focus is lost editBox:SetScript("OnEditFocusLost", function(self) - -- Account for no data and strip trailing commas local cleanKeywords = (self:GetText() or ""):gsub(",$", "") if cleanKeywords == nil or cleanKeywords == "" then @@ -578,10 +406,4 @@ function Yipper.UI.Settings:KeywordSettings() Yipper.DB.Keywords = Yipper.Utils:SplitString(cleanKeywords, ",") end end) - - -- Add a nice label to the box to explain the purpose. - editBox.Label = editBox:CreateFontString(nil, "BORDER", "GameFontNormal") - editBox.Label:SetJustifyH("RIGHT") - editBox.Label:SetPoint("CENTER", editBox, "TOP", 0, 10) - editBox.Label:SetText("Additional Keywords - comma separated") -end +end \ No newline at end of file diff --git a/ui/YipperSettingsFrameTemplate.xml b/ui/YipperSettingsFrameTemplate.xml new file mode 100644 index 0000000..19b9385 --- /dev/null +++ b/ui/YipperSettingsFrameTemplate.xml @@ -0,0 +1,258 @@ + +