You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

381 lines
14 KiB

--[[
This module handles the behavior of "cards" which includes pet cards, notes
and win records. Preferences and possibly team cards will become cards in
the future.
These cards are hybrid tooltips and dialogs. When the mouse is over a button
that displays a card, the card will behave like a tooltip: moving the mouse
off the button will hide the card. When the card-displaying button is
clicked, a dialog frame appears around the card and it remains locked on the
screen. Moving the mouse off the button will not hide the card, so the user
can mouseover and interact with the card's contents as if it was a dialog.
To use this card manager:
- Cards should inherit "RematchCardManagerTemplate". This adds a child frame
(hidden by default) with the "locked" border frame to surround the card.
- Cards should be movable, clampedToScreen and FULLSCREEN frameStrata
- All clickable child frames (like stats on the pet card) should be defined
prior to registering the card, when it gathers all clickableElements.
- Each card should be registered with a name via RegisterCard:
rematch:RegisterCard(cardName,frame,showFunc,title,isFloating)
- cardName is a unique string to identify this card ("PetCard", "WinRecord", etc)
- frame is the parent that inherited RematchCardManagerTemplate
- showFunc a function for each card to ShowCard(self,cardName,button,subject,forceLock)
- title is the text to display at the top of the card
- isFloating is true when a card is never pinned/anchored to an origin button
- rematch:ShowCard(cardName,etc) can be called directly too.
- Each button that displays a card should run these in their OnEnter/OnLeave/OnClick:
rematch.CardManager.ButtonOnEnter(self,cardName,subject)
rematch.CardManager.ButtonOnLeave(self,cardName)
rematch.CardManager.ButtonOnClick(self,cardName,subject,highlightFunc)
- self is the button being entered/left/clicked
- cardName is the name of the card as it was registered
- subject is a petID or teamID the showFunc will use to fill the card
- (optional) highlightFunc runs when a lock changes func(origin/self,subject)
To show a card:
- Let the OnEnter/OnLeave/OnClick do its thing. It requires no extra work.
- To directly force a card:
rematch:ShowCard(cardName,UIParent/button,subject,true)
To hide a card:
- rematch:HideCard(cardName) to hide a specific card
- rematch:HideCard() will hide all cards
- Never directly card:Hide()! It will break the card!
Each card has these in RematchSettings:
<cardname>XPos: center x coordinate of the card (when not anchored to origin)
<cardname>YPos: center y coordinate of the card (when not anchored to origin)
<cardname>IsPinned: true if the card is presently pinned (not whether pinnable)
<cardname>IsLocked: true if the card is presently locked and can't be moved
]]
local _,L = ...
local rematch = Rematch
rematch.CardManager = {}
local manager = rematch.CardManager
local settings
local cardProperties = {} -- indexed by card name, the properties of the card being managed
local cardNamesByFrame = {} -- indexed by frame, the card name registered for the frame
local delay = 0.5 -- seconds before card is shown on mouseover with default options
function rematch:RegisterCard(cardName,frame,showFunc,title,isFloating)
cardProperties[cardName] = {frame=frame,showFunc=showFunc,isFloating=isFloating,clickableElements={}}
cardNamesByFrame[frame] = cardName
-- setup lockframe
frame.LockFrame.CloseButton:SetScript("OnClick",manager.CloseButtonOnClick)
frame.LockFrame.CloseButton:SetScript("OnKeyDown",manager.CloseButtonOnKeyDown)
frame.LockFrame.TitleText:SetText(title)
frame.LockFrame.PinButton.tooltipTitle = isFloating and L["Toggle Lock"] or L["Unpin Card"]
frame.LockFrame.PinButton.tooltipBody = isFloating and L["While locked, this card cannot be moved unless Shift is held. It will also remain on screen when ESC is pressed."] or L["While pinned, this card will stay wherever you move it.\n\nClick this to unpin the card and snap it back to the button that spawned it."]
rematch:ConvertTitlebarCloseButton(frame.LockFrame.CloseButton)
-- if card doesn't have a saved position, then set a default to center of screen
if not settings[cardName.."XPos"] then
settings[cardName.."XPos"],settings[cardName.."YPos"] = UIParent:GetCenter()
end
-- discover all clickable elements of a card and save them in its clickableElements
manager:AddClickableElements(cardName,frame)
end
-- can be called from outside this module; cardName has to have been registered
-- origin is the frame/button the card is to attach to
-- subject is the petID, teamID, etc to show on the card
-- forceLock is true if the card should be locked (LockFrame shown)
function rematch:ShowCard(cardName,origin,subject,forceLock)
local card = cardProperties[cardName]
if card then
if forceLock then -- any card being forced on the screen is locked
card.locked = true
end
-- save origin/subject to the card
card.origin = origin
card.subject = subject
-- run its showFunc
card.showFunc(card.frame,subject)
-- handle LockFrame
card.frame.LockFrame:SetShown(card.locked)
-- update button in topleft corner
manager:UpdatePinButton(cardName)
-- update whether clickable elements can be clicked through (unlocked cards cannot capture mouse)
manager:UpdateClickableElements(cardName,card.locked and true)
-- position the card; for pinned cards (or static) anchor card to its saved XPos/YPos
if card.isFloating or (settings.FixedPetCard and settings[cardName.."IsPinned"]) then
card.frame:ClearAllPoints()
card.frame:SetPoint("CENTER",UIParent,"BOTTOMLEFT",settings[cardName.."XPos"],settings[cardName.."YPos"])
else -- otherwise anchor it to origin button
rematch:SmartAnchor(card.frame,origin)
end
-- and finally put it on screen
card.frame:Show()
end
end
-- this will update an existing card, likely due to some change
function rematch:UpdateCard(cardName)
local card = cardProperties[cardName]
if card.origin and card.subject then
rematch:ShowCard(cardName,card.origin,card.subject)
end
end
-- this should be called instead of card:Hide() to hide a card
-- if no cardName is given, all cards will be hidden
function rematch:HideCard(cardName)
if cardName then
rematch:StopTimer(cardName)
local card = cardProperties[cardName]
if card then
card.frame:Hide()
if card.origin then
card.origin = nil
card.subject = nil
card.locked = nil
end
end
else -- if a card isn't given, hide them all
for cardName in pairs(cardProperties) do
rematch:HideCard(cardName)
end
end
end
-- adds frame and all children of frame to a card's clickableElements if they can be clicked
function manager:AddClickableElements(cardName,frame)
if frame:IsMouseEnabled() then
tinsert(cardProperties[cardName].clickableElements,frame)
end
for _,child in pairs({frame:GetChildren()}) do
if child:IsMouseEnabled() then
manager:AddClickableElements(cardName,child)
end
end
end
-- goes through all clickableElements in a card and enables mouse if true, disables if false
-- (when an unlocked card is overlapping a button, the card can't capture the mouse or it
-- immediately triggers an OnLeave of the button)
function manager:UpdateClickableElements(cardName,enable)
local card = cardProperties[cardName]
if card then
local clickableElements = card.clickableElements
for i=1,#clickableElements do
clickableElements[i]:EnableMouse(enable)
end
end
end
--[[ LockFrame ]]
-- when a card's LockFrame is shown, make sure the parent card is a higher frameLevel
function manager:LockFrameOnShow()
local parent = self:GetParent()
local parentFrameLevel = parent and parent:GetFrameLevel()
local selfFrameLevel = self:GetFrameLevel()
if parentFrameLevel and parentFrameLevel<=selfFrameLevel then
parent:SetFrameLevel(selfFrameLevel)
self:SetFrameLevel(parentFrameLevel)
end
end
-- when LockFrame is dragged it drags its parent
function manager:LockFrameOnMouseDown()
local cardName = cardNamesByFrame[self:GetParent()]
local card = cardProperties[cardName]
if card then
-- only drag the card if it's not a static card or it's not locked
if not card.isFloating or not settings[cardName.."IsLocked"] then
self:GetParent():StartMoving()
end
end
end
-- when LockFrame is released it stops dragging its parent
function manager:LockFrameOnMouseUp()
local parent = self:GetParent()
parent:StopMovingOrSizing()
local cardName = cardNamesByFrame[parent]
local card = cardProperties[cardName]
-- if card is pinnable and "Allow Cards To Be Pinned" is enabled
if card and (card.isFloating or settings.FixedPetCard) then
settings[cardName.."IsPinned"] = true
settings[cardName.."XPos"],settings[cardName.."YPos"] = parent:GetCenter()
manager:UpdatePinButton(cardName)
end
end
-- click of the close button on the LockFrame
function manager:CloseButtonOnClick()
rematch:HideCard(cardNamesByFrame[self:GetParent():GetParent()])
end
-- while lock frame is on screen, the close button will watch for an ESC
function manager:CloseButtonOnKeyDown(key)
if key==GetBindingKey("TOGGLEGAMEMENU") then
local cardName = cardNamesByFrame[self:GetParent():GetParent()]
local card = cardProperties[cardName]
if card and (not card.isFloating or not settings[cardName.."IsLocked"]) then
rematch:HideCard(cardNamesByFrame[self:GetParent():GetParent()])
self:SetPropagateKeyboardInput(false)
return
end
end
self:SetPropagateKeyboardInput(true)
end
--[[ Origin button's OnEnter/Leave/Click ]]
-- buttons that show a card should use in their OnEnter
-- subject is the petID, teamID, etc of the card to show
function manager:ButtonOnEnter(cardName,subject)
if settings.ClickPetCard then
return
end
local card = cardProperties[cardName]
-- don't do anything if card is already locked on screen (or a dialog/menu just went away)
if card.locked or rematch:UIJustChanged() then
return
end
card.origin = self
card.subject = subject
if settings.FastPetCard then -- if "Faster Pet Card" enabled, show card immediately
rematch:ShowCard(cardName,self,subject)
else -- otherwise wait a short delay before showing card
rematch:StartTimer(cardName,delay,manager.DelayedButtonOnEnter)
end
end
-- called after delay via ButtonOnEnter, actually shows the card
function manager:DelayedButtonOnEnter()
local cardName = rematch:GetTimerStopped() -- get name of timer that triggered this function
local card = cardProperties[cardName]
rematch:ShowCard(cardName,card.origin,card.subject)
end
-- buttons that show a card should use this in their OnLeave
function manager:ButtonOnLeave(cardName)
if settings.ClickPetCard then
return
end
local card = cardProperties[cardName]
if not card.locked then
rematch:HideCard(cardName)
end
end
-- this should be called after the button checked if any other clicks (right-click,
-- shift+click, etc) needed handling and only if they weren't handled.
function manager:ButtonOnClick(cardName,subject,highlightFunc)
local card = cardProperties[cardName]
if card.frame:IsVisible() then
-- if the opened subject is being clicked, toggle its lock state
if card.origin==self and card.subject==subject then
if settings.ClickPetCard then
rematch:HideCard(cardName)
elseif card.locked then -- card locked on same origin/subject, unlock it
card.locked = nil
card.frame.LockFrame:Hide()
manager:UpdateClickableElements(cardName,false)
else -- card unlocked on same origin/subject, lock it
card.locked = true
card.frame.LockFrame:Show()
manager:UpdateClickableElements(cardName,true)
end
else -- if a different subject is clicked, open new subject and lock for this click
card.locked = true
rematch:ShowCard(cardName,self,subject)
end
else -- card is not visible (clicked before delay over or ClickToOpen)
rematch:StopTimer(cardName)
card.locked = true
rematch:ShowCard(cardName,self,subject)
end
end
-- returns the subject and origin of the shown card
function manager:GetCardInfo(cardName)
local card = cardProperties[cardName]
if card then
return card.subject,card.origin
end
end
--[[ Pinning/Locking ]]
-- updates the state of the the pin/lock button in the topleft corner of the card
function manager:UpdatePinButton(cardName)
local card = cardProperties[cardName]
if card then
local pinButton = card.frame.LockFrame.PinButton
if card.isFloating then
local isLocked = settings[cardName.."IsLocked"]
rematch:SetTitlebarButtonIcon(pinButton,isLocked and "lock" or "unlock")
pinButton:Show()
elseif settings.FixedPetCard and settings[cardName.."IsPinned"] then
rematch:SetTitlebarButtonIcon(pinButton,"pin")
pinButton:Show()
else
pinButton:Hide()
end
end
end
-- when pin/lock button is clicked on the lock frame
function manager:PinButtonOnClick()
local cardName = cardNamesByFrame[self:GetParent():GetParent()]
local card = cardProperties[cardName]
if card.isFloating then
settings[cardName.."IsLocked"] = not settings[cardName.."IsLocked"]
manager:UpdatePinButton(cardName)
else
settings[cardName.."IsPinned"] = nil
rematch:UpdateCard(cardName)
end
end
--[[ CardTest ]]
local testData = {}
local function fillTestCard(self,subject)
local petInfo = rematch.petInfo:Fetch(subject)
self.Text:SetText(petInfo.name)
self.Icon:SetNormalTexture(petInfo.icon)
end
local function fillTestCardListButton(self,subject)
local petInfo = rematch.petInfo:Fetch(subject)
self.Pet.Icon:SetTexture(petInfo.icon)
self.Text:SetText(petInfo.name)
self.petID = subject
self:UnlockHighlight()
end
rematch:InitModule(function()
settings = RematchSettings
-- rematch:RegisterCard("CardTest",CardTestCard,function(self,subject) self.Text:SetText(subject) self:Show() end,"Card Test","pin")
rematch:RegisterCard("CardTest",CardTestCard,fillTestCard,"Card Test",true)
local scrollFrame = AutoScrollFrame:Create(CardTest, "CardTestListButtonTemplate", testData, fillTestCardListButton)
scrollFrame:SetPoint("TOPLEFT",4,-24)
scrollFrame:SetPoint("BOTTOMRIGHT",-6,5)
CardTest.scrollFrame = scrollFrame
CardTest:SetScript("OnEvent",function(self,event)
wipe(testData)
for i=1,C_PetJournal.GetNumPets() do
local petID = C_PetJournal.GetPetInfoByIndex(i)
if petID then
tinsert(testData,petID)
end
end
self.scrollFrame:Update()
end)
CardTest:RegisterEvent("PET_JOURNAL_LIST_UPDATE")
end)