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.

1289 lines
41 KiB

local _, addon = ...
local L = addon.L;
local API = addon.API;
local UIFrameFade = API.UIFrameFade;
local GetDBBool = addon.GetDBBool;
local ACTION_BUTTON_SIZE = 46;
local ACTION_BUTTON_GAP = 4;
local REPOSITION_BUTTON_OFFSET = 46;
local GetItemCooldown = C_Container.GetItemCooldown;
local UnitCastingInfo = UnitCastingInfo;
local UnitChannelInfo = UnitChannelInfo;
local InCombatLockdown = InCombatLockdown;
local GetCursorPosition = GetCursorPosition;
local math = math;
local UIParent = UIParent;
local CreateFrame = CreateFrame;
local tinsert = table.insert;
local atan2 = math.atan2;
local ipairs = ipairs;
local QuickSlot = CreateFrame("Frame", nil, UIParent);
do --QuickSlot "OnLoad"
addon.QuickSlot = QuickSlot;
QuickSlot:Hide();
QuickSlot:SetSize(46, 46);
QuickSlot:SetAlpha(0);
QuickSlot:SetFrameStrata("HIGH");
QuickSlot.Buttons = {};
QuickSlot.ItemButtons = {};
QuickSlot.SpellButtons = {};
QuickSlot.numActiveButtons = 0;
QuickSlot.SpellXButton = {};
QuickSlot:SetClampedToScreen(true);
QuickSlot:SetClampRectInsets(-ACTION_BUTTON_SIZE, ACTION_BUTTON_SIZE, 8, -8);
end
local ContextMenu;
local function ContextMenu_EditMode_OnClick(self, button)
QuickSlot:EnableEditMode(true);
return true
end
local function ContextMenu_HighContrast_OnClick(self, button)
local state = not GetDBBool("QuickSlotHighContrastMode");
addon.SetDBValue("QuickSlotHighContrastMode", state);
QuickSlot:UseHighContrast(state);
return false
end
local ContextMenuData = {
{text = L["Quick Slot Reposition"], onClickFunc = ContextMenu_EditMode_OnClick},
{text = L["Quick Slot High Contrast Mode"], onClickFunc = ContextMenu_HighContrast_OnClick,},
};
local Positioner = CreateFrame("Frame", nil, UIParent);
Positioner.alpha = 0;
Positioner:Hide();
Positioner:SetFrameStrata("BACKGROUND");
Positioner.buttonSize = 46; --Constant
Positioner.buttonGap = 8; --Constant
Positioner.fromRadian = 0; --User customizable
function Positioner:GetButtonGap()
--the gap between to round buttons
return 8
end
function Positioner:GetRadius()
return math.floor( (0.5 * UIParent:GetHeight()*16/9 /3) + (self.buttonSize*0.5) + 0.5 );
end
function Positioner:GetButtonCenterGap()
local radius = self:GetRadius();
local gapArc = self.buttonGap + self.buttonSize;
local radianGap = gapArc/radius;
return radianGap
end
function Positioner:GetButtonSpan(numActiveButtons)
return (self.buttonSize + self.buttonGap) * numActiveButtons - self.buttonGap;
end
function Positioner:GetCustomPosition()
if self.db then
return self.db.quickslot_PositionX, self.db.quickslot_PositionY
end
end
function Positioner:SetCustomPosition(x, y)
self.db.quickslot_PositionX = x;
self.db.quickslot_PositionY = y;
end
function Positioner:GetFromRadian()
return self.fromRadian
end
function Positioner:SetFromRadian(radian)
if not radian then return end;
local snappedRadian = math.rad(45);
if radian > snappedRadian then
radian = snappedRadian;
else
snappedRadian = math.rad(-60) + self:GetButtonRadianByIndex(-1);
if radian < snappedRadian then
radian = snappedRadian;
end
end
for i = 0, 1 do
snappedRadian = self:GetButtonRadianByIndex(i);
if radian < snappedRadian + 0.043 and radian > snappedRadian - 0.043 then
radian = snappedRadian;
end
end
self.fromRadian = radian;
if self.db then
self.db.quickslotFromRadian = radian;
end
end
function Positioner:GetEditButtonRadian()
local radius = self:GetRadius();
return self.fromRadian + (self.buttonSize)/radius;
end
function Positioner:GetButtonRadianByIndex(index)
local radius = self:GetRadius();
local gapArc = self.buttonGap + self.buttonSize;
local radianGap = gapArc/radius;
return (1 - index)*radianGap;
end
function Positioner:GetRadianPerButton()
local radius = self:GetRadius();
local radianPerButton = self.buttonSize/radius;
return radianPerButton
end
function Positioner:GetCastBar()
return _G["PlayerCastingBarFrame"]
end
function Positioner:OnUpdate_FadeIn(elapsed)
self.alpha = self.alpha + elapsed*5;
if self.alpha >= 1 then
self.alpha = 1;
self:SetScript("OnUpdate", nil);
end
self:SetAlpha(self.alpha);
end
function Positioner:FadeInGuideLine()
self:SetScript("OnUpdate", self.OnUpdate_FadeIn);
self.t = nil;
self:Show();
end
function Positioner:OnUpdate_FadeOut(elapsed)
if self.t < 0.5 then
self.t = self.t + elapsed;
return
end
self.alpha = self.alpha - elapsed*2;
if self.alpha <= 0 then
self.alpha = 0;
self.t = nil;
self:Hide();
self:SetScript("OnUpdate", nil);
end
self:SetAlpha(self.alpha);
end
function Positioner:FadeOutGuideLine()
if not self.t then
if self.alpha >= 0.8 then
self.t = 0;
else
self.t = 1; --no delay
end
end
self:SetScript("OnUpdate", self.OnUpdate_FadeOut);
end
function Positioner:ShowGuideLineCircle(state)
self.showingGuideLine = state;
if state then
if not self.GuideLineCircle then
self.GuideLineCircle = addon.CreateArc(self);
self.GuideLineCircle:SetThickness(2);
self.GuideLineCircle:SetPoint("CENTER", UIParent, "CENTER", 0, 0);
local buttonRadian = self:GetRadianPerButton();
local toRdian = math.rad(-45.5) + buttonRadian*0.5;
local fromRadian = math.rad(30.5) - buttonRadian*0.5;
self.GuideLineCircle:SetToRadian(toRdian);
self.GuideLineCircle:SetFromRadian(fromRadian);
self.GuideLineCircle:SetAlpha(0.5);
--[[
local snappedRadian = math.rad(45);
if radian > snappedRadian then
radian = snappedRadian;
else
snappedRadian = math.rad(-60) + self:GetButtonRadianByIndex(-1);
if radian < snappedRadian then
radian = snappedRadian;
end
end
--]]
end
local radius = self:GetRadius();
self.GuideLineCircle:SetRadius(radius);
--self:Show();
self:FadeInGuideLine();
else
--self:Hide();
self:FadeOutGuideLine();
end
end
function Positioner:SetCircleMaskPosition(x, y)
if self.CircleMask2 then
self.CircleMask2:SetPoint("CENTER", UIParent, "BOTTOMLEFT", x, y);
end
end
function Positioner:HideGuideLine()
self.showingGuideLine = false;
self:Hide();
self:SetScript("OnUpdate", nil);
end
local function RealActionButton_OnLeave(self)
if not InCombatLockdown() then
self:SetScript("OnLeave", nil);
self:Release();
end
if self.owner then
self.owner:UnlockHighlight();
self.owner:SetStateNormal();
self.owner.hasActionButton = nil;
self.owner = nil;
QuickSlot:SetHeaderText();
QuickSlot:StartShowingDefaultHeaderCountdown(true);
end
GameTooltip:Hide();
end
local function RealActionButton_PostClick(self, button)
local owner = self.owner;
if owner then
if owner.onClickFunc then
owner.onClickFunc();
end
if button == "LeftButton" and owner:HasCharges() then
owner:ShowPostClickEffect();
return
end
if button == "RightButton" then
if not ContextMenu then
ContextMenu = addon.GetSharedContextMenu();
end
local menu = ContextMenu;
if menu:IsShown() then
menu:CloseMenu();
return
end
menu:SetOwner(owner);
menu:ClearAllPoints();
menu:SetPoint("LEFT", owner, "RIGHT", 12, 0);
local menuData;
if QuickSlot.buttonData.developerInfo then
menuData = API.CopyTable(ContextMenuData);
tinsert(menuData, {
type = "divider",
});
tinsert(menuData, {
--type == "info",
color = {0.5, 0.5, 0.5},
text = L["Quickslot Module Info"];
tooltip = QuickSlot.buttonData.developerInfo,
});
else
menuData = ContextMenuData;
end
menu:SetContent(menuData);
menu:Show();
end
end
end
local function RealActionButton_OnMouseDown(self, button)
if self.owner then
if self.owner:HasCharges() then
self.owner:SetStatePushed();
end
end
end
local function RealActionButton_OnMouseUp(self)
if self.owner then
self.owner:SetStateNormal();
end
end
local function ItemButton_OnEnter(self)
if self.overrideName then
QuickSlot:SetHeaderText(self.overrideName);
else
if self.actionType == "item" then
QuickSlot:SetHeaderText(API.GetColorizedItemName(self.id));
elseif self.actionType == "spell" then
QuickSlot:SetHeaderText(C_Spell.GetSpellName(self.id));
end
end
QuickSlot:StartShowingDefaultHeaderCountdown(false);
local privateKey = "QuickSlot";
local RealActionButton = addon.AcquireSecureActionButton(privateKey);
if RealActionButton then
local w, h = self:GetSize();
RealActionButton:SetFrameStrata("DIALOG");
RealActionButton:SetFixedFrameStrata(true);
RealActionButton:SetScript("OnEnter", nil);
RealActionButton:SetScript("OnLeave", RealActionButton_OnLeave);
RealActionButton:SetScript("PostClick", RealActionButton_PostClick);
RealActionButton:SetScript("OnMouseDown", RealActionButton_OnMouseDown);
RealActionButton:SetScript("OnMouseUp", RealActionButton_OnMouseUp);
RealActionButton:ClearAllPoints();
RealActionButton:SetParent(self);
RealActionButton:SetSize(w, h);
RealActionButton:SetPoint("CENTER", self, "CENTER", 0, 0);
RealActionButton:Show();
RealActionButton.owner = self;
local macroText;
if self.onClickFunc then
elseif self.macroText then
macroText = self.macroText;
else
if self.actionType == "item" then
macroText = string.format("/use item:%s", self.id);
elseif self.actionType == "spell" then
local spellName = C_Spell.GetSpellName(self.id);
if spellName then
macroText = string.format("/cast %s", spellName);
end
end
end
RealActionButton:SetAttribute("type1", "macro");
RealActionButton:SetMacroText(macroText);
RealActionButton:RegisterForClicks("LeftButtonDown", "LeftButtonUp", "RightButtonUp");
self:LockHighlight();
self.hasActionButton = true;
end
if self.tooltipLines then
local tooltip = GameTooltip;
tooltip:Hide();
tooltip:SetOwner(self, "ANCHOR_RIGHT");
for i, text in ipairs(self.tooltipLines) do
if i == 1 then
tooltip:SetText(text, 1, 1, 1, true);
else
tooltip:AddLine(text, 1, 1, 1, true);
end
end
tooltip:Show();
end
end
local function ItemButton_OnLeave(self)
if not (self:IsVisible() and self:IsMouseOver()) then
QuickSlot:SetHeaderText();
QuickSlot:StartShowingDefaultHeaderCountdown(true);
GameTooltip:Hide();
end
end
function QuickSlot:Init()
Positioner.db = PlumberDB;
Positioner:SetFromRadian(Positioner.db.quickslotFromRadian);
self.side = 1;
local Header = self:CreateFontString(nil, "OVERLAY", "GameTooltipText");
self.Header = Header;
Header:SetJustifyH("CENTER");
Header:SetJustifyV("MIDDLE");
Header:SetPoint("BOTTOM", self, "TOP", 0, 8);
Header:SetSpacing(2);
local font, height = GameTooltipText:GetFont();
Header:SetFont(font, height, ""); --OUTLINE
Header:SetShadowColor(0, 0, 0);
Header:SetShadowOffset(1, -1);
--[[
local HeaderShadow = self:CreateTexture(nil, "ARTWORK");
HeaderShadow:SetPoint("TOPLEFT", Header, "TOPLEFT", -8, 6);
HeaderShadow:SetPoint("BOTTOMRIGHT", Header, "BOTTOMRIGHT", 8, -8);
HeaderShadow:SetTexture("Interface/AddOns/Plumber/Art/Button/GenericTextDropShadow");
HeaderShadow:Hide();
HeaderShadow:SetAlpha(0);
--]]
local HeaderShadow = self:CreateTexture(nil, "ARTWORK");
HeaderShadow:SetTexture("Interface/AddOns/Plumber/Art/Frame/SubtitleShadow_NineSlice_Darker");
HeaderShadow:SetTextureSliceMargins(30, 30, 30, 30);
HeaderShadow:SetTextureSliceMode(0);
HeaderShadow:Hide();
HeaderShadow:SetAlpha(0);
HeaderShadow:SetPoint("TOPLEFT", Header, "TOPLEFT", -20, 20);
HeaderShadow:SetPoint("BOTTOMRIGHT", Header, "BOTTOMRIGHT", 20, -20);
function QuickSlot:SetHeaderText(text, transparentText)
if self:IsInEditMode() then return end;
if text then
Header:SetSize(0, 0);
Header:SetText(text);
if transparentText then
local toAlpha = self.highContrastMode and 1.0 or 0.6;
UIFrameFade(Header, 0.5, toAlpha);
UIFrameFade(HeaderShadow, 0.25, 0);
else
API.UIFrameFadeIn(Header, 0.25);
UIFrameFade(HeaderShadow, 0.25, 1);
end
local textWidth = Header:GetWrappedWidth() - 2;
if textWidth > QuickSlot.headerMaxWidth then
Header:SetSize(QuickSlot.headerMaxWidth, 64);
local numLines = Header:GetNumLines();
Header:SetHeight(numLines*18);
textWidth = Header:GetWrappedWidth();
Header:SetWidth(textWidth + 2);
end
else
UIFrameFade(Header, 0.5, 0);
UIFrameFade(HeaderShadow, 0.25, 0);
end
end
self.SpellCastOverlay = addon.CreateActionButtonSpellCastOverlay(self);
self.SpellCastOverlay:Hide();
self.Init = nil;
end
local function ContainerFrame_OnUpdate(self, elapsed)
self.t = self.t + elapsed;
if self.t > 1 then
self:SetScript("OnUpdate", nil);
self:SetHeaderText(self.defaultHeaderText, true);
end
end
function QuickSlot:SetDefaultHeaderText(text)
self.defaultHeaderText = text;
end
function QuickSlot:StartShowingDefaultHeaderCountdown(state)
if state and not self:IsInEditMode() then
self.t = 0;
self:SetScript("OnUpdate", ContainerFrame_OnUpdate);
else
self:SetScript("OnUpdate", nil);
end
end
function QuickSlot:SetButtonData(buttonData)
if buttonData == self.buttonData then
return
end
local privateKey = "QuickSlot";
addon.HideSecureActionButton(privateKey);
self.buttonData = buttonData;
self.systemName = buttonData.systemName;
self.layoutDirty = true;
self.anyItemAction = nil;
self.numActiveButtons = #buttonData.buttons;
self.spellcastType = buttonData.spellcastType;
self.SpellXButton = {};
self.ItemButtons = {};
self.SpellButtons = {};
local buttonSize = ACTION_BUTTON_SIZE;
local gap = ACTION_BUTTON_GAP;
local positionIndex = 0;
local trackIndex = 0;
local anyItemAction;
local anySpellAction;
for i, info in ipairs(buttonData.buttons) do
positionIndex = positionIndex + 1;
if info.spacer then
--Used as a spacer
elseif info.track then
--reset radian, reduce radius
positionIndex = 0;
trackIndex = trackIndex + 1;
else
local button = self.Buttons[i];
if not button then
button = addon.CreatePeudoActionButton(self);
tinsert(self.Buttons, button);
button:SetPoint("LEFT", self, "LEFT", (i - 1) * (buttonSize + gap), 0);
end
local spellID = info.spellID;
if spellID then
self.SpellXButton[spellID] = button;
end
if info.actionType == "item" then
anyItemAction = true;
button:SetItem(info.itemID, info.icon);
tinsert(self.ItemButtons, button);
elseif info.actionType == "spell" then
anySpellAction = true;
button:SetSpell(spellID, info.icon);
tinsert(self.SpellButtons, button);
end
button.spellID = spellID;
button.positionIndex = positionIndex;
button.trackIndex = trackIndex;
button.overrideName = info.name;
button.macroText = info.macroText;
button.onClickFunc = info.onClickFunc;
button.tooltipLines = info.tooltipLines;
button:SetScript("OnEnter", ItemButton_OnEnter);
button:SetScript("OnLeave", ItemButton_OnLeave);
button:Show();
if info.enabled ~= nil then
if info.enabled then
button:SetIconState(1);
else
button:SetIconState(2);
end
end
end
end
for i = self.numActiveButtons + 1, #self.Buttons do
self.Buttons[i]:Hide();
end
if self.numActiveButtons > 1 then
self.layoutIndex = 2;
else
self.layoutIndex = 1;
end
self.anyItemAction = anyItemAction;
self.anySpellAction = anySpellAction;
if anySpellAction then
self:RegisterEvent("SPELL_UPDATE_CHARGES");
self:RegisterEvent("SPELL_UPDATE_COOLDOWN");
else
self:UnregisterEvent("SPELL_UPDATE_CHARGES");
self:UnregisterEvent("SPELL_UPDATE_COOLDOWN");
end
if not self.Init then
self:UpdateFrameLayout();
end
end
function QuickSlot:SetButtonOrder(side)
if side ~= self.side then
self.side = side;
else
return
end
if not self.buttonData then
return
end
local items = self.itemData;
local spells = self.spellData;
if side > 0 then
--right side of the screen
else
--left side
--items = API.ReverseList(items);
--spells = API.ReverseList(spells);
end
--for i, button in ipairs(self.Buttons) do
-- button:SetItem(items[i]);
-- button.spellID = spells[i];
-- self.SpellXButton[ spells[i] ] = button;
--end
end
function QuickSlot:SetFrameLayout(layoutIndex)
local buttonSize = Positioner.buttonSize;
local buttonGap = Positioner.buttonGap;
local radius = math.floor( (0.5 * UIParent:GetHeight()*16/9 /3) + (buttonSize*0.5) + 0.5);
local track0Radius = radius;
local gapArc = buttonGap + buttonSize;
local fromRadian = Positioner:GetFromRadian();
local radianGap = gapArc/radius;
local radian;
local x, y;
local cx, cy = UIParent:GetCenter();
local cos = math.cos;
local sin = math.sin;
if layoutIndex == 1 then
--Normal, below the center
x, y = Positioner:GetCustomPosition();
if not (x and y) then
--x = cx + radius * cos(fromRadian);
--y = cy + radius * sin(fromRadian);
x = cx + radius;
y = cy;
end
self:ClearAllPoints();
self:SetPoint("CENTER", UIParent, "BOTTOMLEFT", x, y);
for i, button in ipairs(self.Buttons) do
button:ClearAllPoints();
button:SetPoint("CENTER", self, "CENTER", (i - 1) * (buttonSize + buttonGap), 0);
end
if self.numActiveButtons > 1 then
local buttonMiddlePoint = 0.5 * Positioner:GetButtonSpan(self.numActiveButtons or 1);
self.Header:ClearAllPoints();
self.Header:SetPoint("BOTTOM", self, "TOPLEFT", buttonMiddlePoint, 8);
self.headerMaxWidth = 0;
else
self.Header:ClearAllPoints();
self.Header:SetPoint("RIGHT", self, "LEFT", -16, 0);
self.headerMaxWidth = 240;
end
if self.RepositionButton then
self.RepositionButton:ClearAllPoints();
self.RepositionButton:SetPoint("CENTER", UIParent, "BOTTOMLEFT", x, y + REPOSITION_BUTTON_OFFSET);
end
else
--Circular, on the right side
local trackIndex = 0;
for i, button in ipairs(self.Buttons) do
button:ClearAllPoints();
if trackIndex ~= button.trackIndex then
trackIndex = button.trackIndex;
radius = track0Radius - trackIndex * gapArc;
radianGap = gapArc/radius;
end
radian = fromRadian + (1 - button.positionIndex or i)*radianGap;
x = cx + radius * cos(radian);
y = cy + radius * sin(radian);
button:SetPoint("CENTER", UIParent, "BOTTOMLEFT", x, y);
if i == 2 then
Positioner:SetCircleMaskPosition(x, y);
end
end
local headerRadiusOffset = 112; --Positive value moves towards center
local headerMaxWidth = 2*(headerRadiusOffset - buttonSize*0.5) - 8;
radian = fromRadian - (self.numActiveButtons - 1)*radianGap*0.5;
x = cx + (radius - headerRadiusOffset) * cos(radian);
y = cy + (radius - headerRadiusOffset) * sin(radian);
self.headerMaxWidth = headerMaxWidth;
self.Header:ClearAllPoints();
self.Header:SetPoint("CENTER", UIParent, "BOTTOMLEFT", x, y);
if self.RepositionButton then
--Adjust Radian:
radian = Positioner:GetEditButtonRadian();
self.RepositionButton:ClearAllPoints();
self.RepositionButton:SetPoint("CENTER", UIParent, "BOTTOMLEFT", cx + radius * cos(radian), cy + radius * sin(radian));
--self.RepositionButton:SetRotation(radian);
--Adjust Radius:
--[[
radian = fromRadian;
radius = radius + buttonSize;
self.RepositionButton:ClearAllPoints();
self.RepositionButton:SetPoint("CENTER", UIParent, "BOTTOMLEFT", cx + radius * cos(radian), cy + radius * sin(radian));
self.RepositionButton:SetRotation(radian);
--]]
end
end
end
function QuickSlot:UpdateFrameLayout()
self:SetFrameLayout(self.layoutIndex or 2);
end
function QuickSlot:SetInteractable(state, dueToCombat)
if state then
UIFrameFade(self, 0.5, 1);
else
if dueToCombat then
if self:IsShown() and not self:IsInEditMode() then
local toAlpha = 0.25;
UIFrameFade(self, 0.2, toAlpha);
end
else
end
end
for i, button in ipairs(self.Buttons) do
button:SetEnabled(state);
button:EnableMouse(state);
button:UnlockHighlight();
end
end
function QuickSlot:UpdateItemCount()
for i, button in ipairs(self.ItemButtons) do
button:UpdateCount();
end
end
function QuickSlot:UpdateSpellCharge()
for i, button in ipairs(self.SpellButtons) do
button:UpdateCount();
end
end
function QuickSlot:OnSpellCastChanged(spellID, isStartCasting)
local targetButton = self.SpellXButton[spellID];
if self.lastSpellTargetButton then
self.lastSpellTargetButton.Count:Show();
end
self.lastSpellTargetButton = targetButton;
if targetButton then
if isStartCasting then
self.isPlayerMoving = false;
self.isChanneling = true;
for i, button in ipairs(self.Buttons) do
if button.spellID == spellID then
local _, _, _, startTime, endTime = UnitChannelInfo("player");
if not _ then
_, _, _, startTime, endTime = UnitCastingInfo("player");
end
self.SpellCastOverlay:ClearAllPoints();
self.SpellCastOverlay:SetPoint("CENTER", button, "CENTER", 0, 0);
self.SpellCastOverlay:FadeIn();
self.SpellCastOverlay:SetDuration( (endTime - startTime) / 1000);
self.SpellCastOverlay:SetFrameStrata("HIGH");
button.Count:Hide();
end
end
else
self.isChanneling = false;
self.lastSpellTargetButton = nil;
self.SpellCastOverlay:FadeOut();
end
end
end
function QuickSlot:OnShow()
self.isClosing = nil;
end
function QuickSlot:OnHide()
self.isClosing = nil;
self:EnableEditMode(false);
end
QuickSlot:SetScript("OnHide", QuickSlot.OnHide);
local function GetCursorRadianToPoint(cx, cy, uiRatio)
local x, y = GetCursorPosition();
x = x * uiRatio;
y = y * uiRatio;
return atan2(y - cy, x - cx);
end
local function RepositionButton_OnUpdate_Radial(self, elapsed)
self.t = self.t + elapsed;
if self.t >= 0.016 then
self.t = 0;
local radian = GetCursorRadianToPoint(self.cx, self.cy, self.uiRatio);
if radian ~= self.radian then
self.radian = radian;
Positioner:SetFromRadian(self.frameRadian + radian - self.selfRadian);
QuickSlot:UpdateFrameLayout();
--[[
if radian > -1.57 and radian < 1.57 then
QuickSlot:SetButtonOrder(1);
else
QuickSlot:SetButtonOrder(-1);
end
--]]
end
end
end
local function RepositionButton_OnUpdate_FreeMove(self, elapsed)
self.t = self.t + elapsed;
if self.t >= 0.016 then
self.t = 0;
local x, y = GetCursorPosition();
x = self.cxOffset + x * self.uiRatio;
y = self.cyOffset + y * self.uiRatio - REPOSITION_BUTTON_OFFSET;
Positioner:SetCustomPosition(x, y);
QuickSlot:UpdateFrameLayout();
end
end
local function RepositionButton_OnMouseDown(self, button)
if button == "RightButton" then
if QuickSlot.layoutIndex == 1 then
Positioner:SetCustomPosition(nil, nil);
else
Positioner:SetFromRadian(0);
end
QuickSlot:UpdateFrameLayout();
return
end
self.t = 0;
self.cx, self.cy = UIParent:GetCenter();
self.uiRatio = 1/ UIParent:GetEffectiveScale();
self.radian = GetCursorRadianToPoint(self.cx, self.cy, self.uiRatio);
self.selfRadian = Positioner:GetEditButtonRadian();
self.frameRadian = Positioner:GetFromRadian();
local cx0, cy0 = GetCursorPosition();
cx0 = cx0 * self.uiRatio;
cy0 = cy0 * self.uiRatio;
local x0, y0 = self:GetCenter();
self.cxOffset = x0 - cx0;
self.cyOffset = y0 - cy0;
local isRadial = QuickSlot.layoutIndex ~= 1;
if isRadial then
self:SetScript("OnUpdate", RepositionButton_OnUpdate_Radial);
Positioner:ShowGuideLineCircle(true);
else
self:SetScript("OnUpdate", RepositionButton_OnUpdate_FreeMove);
Positioner:ShowGuideLineCircle(false);
end
QuickSlot:SetInteractable(false);
self:LockHighlight();
end
local function RepositionButton_OnMouseUp(self)
self.t = nil;
self.cx, self.cy = nil, nil;
self:SetScript("OnUpdate", nil);
self:UnlockHighlight();
Positioner:ShowGuideLineCircle(false);
end
local function RepositionButton_OnClick(self)
local delta = -1; --clock-wise
local oldRadian = Positioner:GetFromRadian();
local dRadian = Positioner:GetButtonCenterGap();
local newRadian = oldRadian + delta*dRadian;
Positioner:SetFromRadian(newRadian);
QuickSlot:UpdateFrameLayout();
end
local function RepositionButton_SetRotation(self, radian)
self.Icon:SetRotation(radian);
self.Highlight:SetRotation(radian);
end
function QuickSlot:IsInEditMode()
return self.isEditing == true
end
local function SetControlNodeAnimation(obj)
local ag = obj:CreateAnimationGroup();
obj.AnimIn = ag;
local s1 = ag:CreateAnimation("Scale");
s1:SetOrder(1);
s1:SetDuration(0);
s1:SetScale(0.25, 0.25);
local s2 = ag:CreateAnimation("Scale");
s2:SetOrder(2);
s2:SetDuration(0.3);
s2:SetScale(6, 6);
s2:SetSmoothing("IN_OUT");
local s3 = ag:CreateAnimation("Scale");
s3:SetOrder(3);
s3:SetDuration(0.4);
s3:SetScale(0.67, 0.67);
s3:SetSmoothing("OUT");
end
function QuickSlot:EnableEditMode(state)
if state then
if not self.RepositionButton then
local b = CreateFrame("Button", nil, self);
b:SetSize(16, 16);
self.RepositionButton = b;
b:SetFrameStrata("DIALOG");
b:SetFrameLevel(500);
b:SetFixedFrameStrata(true);
b:SetClampedToScreen(true);
local offset = 46;
b:SetClampRectInsets(-offset, offset, offset, -offset);
local tex = "Interface/AddOns/Plumber/Art/Button/RepositionButton-Circle";
b.Icon = b:CreateTexture(nil, "ARTWORK");
b.Icon:SetSize(16, 16);
b.Icon:SetPoint("CENTER", b, "CENTER", 0, 0);
b.Icon:SetTexture(tex, nil, nil, "TRILINEAR");
b.Highlight = b:CreateTexture(nil, "HIGHLIGHT");
b.Highlight:SetSize(32, 32);
b.Highlight:SetPoint("CENTER", b, "CENTER", 0, 0);
b.Highlight:SetTexture(tex.."-Highlight", nil, nil, "TRILINEAR");
b.SetRotation = RepositionButton_SetRotation;
--b:SetScript("OnClick", RepositionButton_OnClick);
b:SetScript("OnMouseDown", RepositionButton_OnMouseDown);
b:SetScript("OnMouseUp", RepositionButton_OnMouseUp);
SetControlNodeAnimation(b);
end
if not self.EditModeConfirmButton then
local b = CreateFrame("Button", nil, self);
b:SetSize(30, 30);
self.EditModeConfirmButton = b;
b:SetFrameStrata("DIALOG");
b:SetFixedFrameStrata(true);
b:SetClampedToScreen(true);
local offset = 24;
b:SetClampRectInsets(-offset, offset, offset, -offset);
local tex = "Interface/AddOns/Plumber/Art/Button/EditMode-Confirm";
b.Icon = b:CreateTexture(nil, "ARTWORK");
b.Icon:SetSize(32, 32);
b.Icon:SetPoint("CENTER", b, "CENTER", 0, 0);
b.Icon:SetTexture(tex);
API.DisableSharpening(b.Icon);
b.Highlight = b:CreateTexture(nil, "HIGHLIGHT");
b.Highlight:SetSize(32, 32);
b.Highlight:SetPoint("CENTER", b, "CENTER", 0, 0);
b.Highlight:SetTexture(tex.."-Highlight");
API.DisableSharpening(b.Highlight);
b:SetPoint("CENTER", self.Header, "CENTER", 0, 0);
b:SetScript("OnClick", function()
self:EnableEditMode(false);
end);
end
if not self.isEditing then
self.RepositionButton:Show();
self.EditModeConfirmButton:Show();
self.RepositionButton.AnimIn:Play();
UIFrameFade(self.EditModeConfirmButton, 0.25, 1, 0);
self:SetInteractable(false);
self:SetHeaderText(); --HUD_EDIT_MODE_MENU
self:StartShowingDefaultHeaderCountdown(false);
for i, button in ipairs(self.Buttons) do
button.Count:Hide();
end
self.isEditing = true;
self:UpdateFrameLayout();
self.EditModeConfirmButton:ClearAllPoints();
if self.layoutIndex == 1 then
self.EditModeConfirmButton:SetPoint("CENTER", self, "CENTER", -54, 0);
else
self.EditModeConfirmButton:SetPoint("CENTER", self.Header, "CENTER", 0, 0);
end
end
else
if self.isEditing then
self.isEditing = nil;
self.RepositionButton:Hide();
self.RepositionButton:SetScript("OnUpdate", nil);
self.EditModeConfirmButton:Hide();
Positioner:HideGuideLine();
for i, button in ipairs(self.Buttons) do
if button ~= self.lastSpellTargetButton then
button.Count:Show();
end
end
if not InCombatLockdown() then
self:SetInteractable(true);
end
if self.closeUIAfterEditing then
self.closeUIAfterEditing = nil;
self:CloseUI();
end
else
return
end
end
end
function QuickSlot:CloseUIAfterEditing()
self.closeUIAfterEditing = true;
end
function QuickSlot:ShowUI()
if self.Init then
self:Init();
end
if self.layoutDirty then
self.layoutDirty = nil;
self:UpdateFrameLayout();
end
self:UpdateItemCount();
self:RegisterEvent("BAG_UPDATE_DELAYED");
self:RegisterEvent("PLAYER_REGEN_DISABLED");
self:RegisterEvent("PLAYER_REGEN_ENABLED");
self:RegisterEvent("UI_SCALE_CHANGED");
self:RegisterEvent("LOADING_SCREEN_ENABLED");
if self.anyItemAction then
self:RegisterEvent("BAG_UPDATE_COOLDOWN");
self:UpdateItemCooldowns();
end
if self.anySpellAction then
self:RegisterEvent("SPELL_UPDATE_CHARGES");
self:RegisterEvent("SPELL_UPDATE_COOLDOWN");
self:RequestUpdateSpellCooldowns();
self:UpdateSpellCharge();
end
if self.spellcastType == 1 then
self:RegisterUnitEvent("UNIT_SPELLCAST_START", "player");
self:RegisterUnitEvent("UNIT_SPELLCAST_STOP", "player");
else
self:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_START", "player");
self:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_STOP", "player");
self:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_UPDATE", "player");
end
for _, button in ipairs(self.Buttons) do
button.Count:Show();
end
self.closeUIAfterEditing = nil;
self.isChanneling = nil;
self.lastSpellTargetButton = nil;
self:Show();
if InCombatLockdown() then
self:SetInteractable(false, true);
else
self:SetInteractable(true);
end
self:UseHighContrast(GetDBBool("QuickSlotHighContrastMode"));
return true
end
function QuickSlot:CloseUI()
if self:IsShown() then
self:EnableEditMode(false);
self.isClosing = true;
UIFrameFade(self, 0.5, 0);
self:UnregisterEvent("PLAYER_REGEN_DISABLED");
self:UnregisterEvent("PLAYER_REGEN_ENABLED");
self:UnregisterEvent("UI_SCALE_CHANGED");
self:UnregisterEvent("UNIT_SPELLCAST_CHANNEL_START");
self:UnregisterEvent("UNIT_SPELLCAST_CHANNEL_STOP");
self:UnregisterEvent("UNIT_SPELLCAST_CHANNEL_UPDATE");
self:UnregisterEvent("UNIT_SPELLCAST_START");
self:UnregisterEvent("UNIT_SPELLCAST_STOP");
self:UnregisterEvent("LOADING_SCREEN_ENABLED");
self:UnregisterEvent("BAG_UPDATE_DELAYED");
self:UnregisterEvent("BAG_UPDATE_COOLDOWN");
self:UnregisterEvent("SPELL_UPDATE_CHARGES");
self:UnregisterEvent("SPELL_UPDATE_COOLDOWN");
self:SetInteractable(false);
self.isChanneling = nil;
self.defaultHeaderText = nil;
self.SpellCastOverlay:Hide();
end
end
function QuickSlot:RequestCloseUI(systemName)
if self:IsInEditMode() then
self.closeUIAfterEditing = true;
else
if (not systemName) or (systemName and systemName == self.systemName) then
self:CloseUI();
end
end
end
function QuickSlot:UseHighContrast(state)
state = state == true;
self.highContrastMode = state;
for i, button in ipairs(self.Buttons) do
button:UseHighContrast(state);
end
if self.Header then
local font, height, flag = self.Header:GetFont();
flag = state and "OUTLINE" or "";
self.Header:SetFont(font, height, flag);
end
end
function QuickSlot:UpdateItemCooldowns()
if self.ItemButtons then
local startTime, duration, enable;
for _, button in ipairs(self.ItemButtons) do
if button.id and button.actionType == "item" then
startTime, duration, enable = GetItemCooldown(button.id);
if enable == 1 and startTime and startTime > 0 and duration and duration > 0 then
button.Cooldown:SetCooldown(startTime, duration);
button.Cooldown:Show();
button.Cooldown:SetHideCountdownNumbers(false);
else
button.Cooldown:Hide();
end
end
end
end
end
do --Spell Cooldown
local GetSpellCooldown = API.GetSpellCooldown;
local GetSpellCharges = API.GetSpellCharges;
local Throttler = CreateFrame("Frame", nil, QuickSlot);
QuickSlot.Throttler = Throttler;
Throttler:SetScript("OnHide", function(self)
self:SetScript("OnUpdate", nil);
self.pauseUpdate = nil;
end);
local function Throttler_OnUpdate(self, elapsed)
self.t = self.t + elapsed;
if self.t >= 0.5 then
self.t = 0;
self:SetScript("OnUpdate", nil);
self.pauseUpdate = nil;
end
end
function QuickSlot:RequestUpdateSpellCooldowns()
if Throttler.pauseUpdate then
else
Throttler.pauseUpdate = true;
Throttler.t = 0;
Throttler:SetScript("OnUpdate", Throttler_OnUpdate);
self:UpdateSpellCooldowns();
end
end
function QuickSlot:UpdateSpellCooldowns()
if not self.SpellButtons then return end;
local cooldownInfo, chargeInfo, startTime, duration, modRate, fromChargeCooldown;
for _, button in ipairs(self.SpellButtons) do
if button.id and button.actionType == "spell" then
startTime, duration, modRate, fromChargeCooldown = nil, nil, nil, nil;
chargeInfo = GetSpellCharges(button.id);
if chargeInfo and chargeInfo.currentCharges > 0 then
if chargeInfo.cooldownStartTime > 0 and chargeInfo.cooldownDuration > 0 then
startTime = chargeInfo.cooldownStartTime;
duration = chargeInfo.cooldownDuration;
modRate = chargeInfo.chargeModRate;
fromChargeCooldown = true;
end
end
if not (startTime and duration) then
cooldownInfo = GetSpellCooldown(button.id);
if cooldownInfo and cooldownInfo.isEnabled and cooldownInfo.startTime > 0 and cooldownInfo.duration > 0 then
startTime = cooldownInfo.startTime;
duration = cooldownInfo.duration;
modRate = cooldownInfo.modRate
end
end
if startTime and duration then
button.Cooldown:SetCooldown(startTime, duration, modRate);
button.Cooldown:Show();
if fromChargeCooldown then
button.Cooldown:SetHideCountdownNumbers(true);
else
button.Cooldown:SetHideCountdownNumbers(false);
end
else
button.Cooldown:Hide();
end
end
end
end
end
function QuickSlot:OnEvent(event, ...)
if event == "BAG_UPDATE_DELAYED" then
self:UpdateItemCount();
elseif event == "SPELL_UPDATE_COOLDOWN" then
self:RequestUpdateSpellCooldowns();
elseif event == "PLAYER_REGEN_DISABLED" then
self:SetInteractable(false, true);
elseif event == "PLAYER_REGEN_ENABLED" then
if not self.isEditing then
self:SetInteractable(true);
end
elseif event == "UI_SCALE_CHANGED" then
self:UpdateFrameLayout();
elseif event == "UNIT_SPELLCAST_CHANNEL_START" or event == "UNIT_SPELLCAST_START" then
local _, _, spellID = ...
QuickSlot:OnSpellCastChanged(spellID, true);
elseif event == "UNIT_SPELLCAST_CHANNEL_UPDATE" then
elseif event == "UNIT_SPELLCAST_CHANNEL_STOP" or event == "UNIT_SPELLCAST_STOP" then
local _, _, spellID = ...
self:OnSpellCastChanged(spellID, false);
elseif event == "LOADING_SCREEN_ENABLED" then
self:CloseUI();
elseif event == "BAG_UPDATE_COOLDOWN" then
self:UpdateItemCooldowns();
elseif event == "SPELL_UPDATE_CHARGES" then
self:UpdateSpellCharge();
end
end
QuickSlot:SetScript("OnEvent", QuickSlot.OnEvent);