local mod = StarTip:NewModule("Position", "AceEvent-3.0", "AceHook-3.0") mod.name = "Positioning" local LibTimer = LibStub("LibScriptableUtilsTimer-1.0") local Evaluator = LibStub("LibScriptableUtilsEvaluator-1.0") local _G = _G local GameTooltip = _G.GameTooltip local StarTip = _G.StarTip local UIParent = _G.UIParent local self = mod local L = StarTip.L local square = {L["Left"], L["Right"], L["Top"], L["Bottom"], L["Offscreen"]} local squareDict = {[L["Left"]] = 1, [L["Right"]] = 2, [L["Top"]] = 3, [L["Bottom"]] = 4, [L["Offscreen"]] = 5} local squareNames = {"LEFT", "RIGHT", "TOP", "BOTTOM"} local defaults = { profile = { inCombat = 1, anchor = 1, unitFrames = 1, other = 1, refreshRate = 50, inCombatXOffset = 10, inCombatYOffset = 0, anchorXOffset = 10, anchorYOffset = 0, unitFramesXOffset = 10, unitFramesYOffset = 0, otherXOffset = 10, otherYOffset = 0, defaultUnitTooltipPos = 5, animationsOn = true, anchorScript = [[ -- Modify locals x and y.They're set to mouse cursor position to start. local mod = StarTip:GetModule("Position") x = x + mod.db.profile.anchorXOffset y = y + mod.db.profile.anchorYOffset ]], inCombatScript = [[ -- Modify locals x and y.They're set to mouse cursor position to start. local mod = StarTip:GetModule("Position") x = x + mod.db.profile.inCombatXOffset y = y + mod.db.profile.inCombatYOffset ]], unitFramesScript = [[ -- Modify locals x and y.They're set to mouse cursor position to start. local mod = StarTip:GetModule("Position") x = x + mod.db.profile.unitFramesXOffset y = y + mod.db.profile.unitFramesYOffset ]], otherScript = [[ -- Modify locals x and y.They're set to mouse cursor position to start. local mod = StarTip:GetModule("Position") x = x + mod.db.profile.otherXOffset y = y + mod.db.profile.otherYOffset ]], animationInit = [[ t = 0 ]], animationFrame = [[ t = t - 5 v = 0 ]], animationPoint = [[ d=(v*0.3); r=t+i*PI*0.02; x=cos(r)*d; y=sin(r)*d ]] } } mod.defaults = defaults local selections = {} for i, v in ipairs(StarTip.anchorText) do selections[i] = v end selections[#selections+1] = "Hide" local get = function(info) return self.db.profile[info[#info]] end local set = function(info,v) self.db.profile[info[#info]] = v end local inputGet = function(info) return tostring(self.db.profile[info[#info]] or 0) end local inputSet = function(info, v) self.db.profile[info[#info]] = tonumber(v) end local minX = -math.floor(GetScreenWidth()/5 + 0.5) * 5 local minY = -math.floor(GetScreenHeight()/5 + 0.5) * 5 local maxX = math.floor(GetScreenWidth()/5 + 0.5) * 5 local maxY = math.floor(GetScreenHeight()/5 + 0.5) * 5 local options = { anchor = { name = L["World Units"], desc = L["Where to anchor the tooltip when mousing over world characters"], type = "select", values = selections, get = get, set = set, order = 4 }, anchorXOffset = { name = format(L["X-axis offset: %d-%d"], minX, maxX), desc = L["The x-axis offset used to position the tooltip in relationship to the anchor point"], type = "input", pattern = "%d", get = inputGet, set = inputSet, validate = function(info, val) val = tonumber(val) return val >= minX and val <= maxX end, order = 5 }, anchorYOffset = { name = format(L["Y-axis offset: %d-%d"], minY, maxY), desc = L["The y-axis offset used to position the tooltip in relationship to the anchor point"], type = "input", pattern = "%d", get = inputGet, set = inputSet, validate = function(info, val) val = tonumber(val) return val >= minY and val <= maxY end, order = 6 }, inCombatHeader = { name = "", type = "header", order = 7 }, inCombat = { name = L["In Combat"], desc = L["Where to anchor the world unit tooltip while in combat"], type = "select", values = selections, get = get, set = set, order = 8 }, inCombatXOffset = { name = format(L["X-axis offset: %d-%d"], minX, maxX), desc = L["The x-axis offset used to position the tooltip in relationship to the anchor point"], type = "input", pattern = "%d", get = inputGet, set = inputSet, validate = function(info, val) val = tonumber(val) return val >= minX and val <= maxX end, order = 9 }, inCombatYOffset = { name = format(L["Y-axis offset: %d-%d"], minY, maxY), desc = L["The y-axis offset used to position the tooltip in relationship to the anchor point"], type = "input", pattern = "%d", get = inputGet, set = inputSet, validate = function(info, val) val = tonumber(val) return val >= minY and val <= maxX end, order = 10, }, unitFramesHeader = { name = "", type = "header", order = 11 }, unitFrames = { name = L["Unit Frames"], desc = L["Where to anchor the tooltip when mousing over a unit frame"], type = "select", values = selections, get = get, set = set, order = 12 }, unitFramesXOffset = { name = format(L["X-axis offset: %d-%d"], minX, maxX), desc = L["The x-axis offset used to position the tooltip in relationship to the anchor point"], type = "input", pattern = "%d", get = inputGet, set = inputSet, validate = function(info, val) val = tonumber(val) return val >= minX and val <= maxX end, order = 13 }, unitFramesYOffset = { name = format(L["Y-axis offset: %d-%d"], minY, maxY), desc = L["The y-axis offset used to position the tooltip in relationship to the anchor point"], type = "input", pattern = "%d", get = inputGet, set = inputSet, validate = function(info, val) val = tonumber(val) return val >= minY and val <= maxY end, order = 14 }, otherHeader = { name = "", type = "header", order = 15 }, other = { name = L["Other tooltips"], desc = L["Where to anchor most other tooltips"], type = "select", values = selections, get = get, set = set, order = 16 }, otherXOffset = { name = format(L["X-axis offset: %d-%d"], minX, maxX), desc = L["The x-axis offset used to position the tooltip in relationship to the anchor point"], type = "input", pattern = "%d", get = inputGet, set = inputSet, validate = function(info, val) val = tonumber(val) return val >= minX and val <= maxX end, order = 17 }, otherYOffset = { name = format(L["Y-axis offset: %d-%d"], minY, maxY), desc = L["The y-axis offset used to position the tooltip in relationship to the anchor point"], type = "input", pattern = "%d", get = inputGet, set = inputSet, validate = function(info, val) val = tonumber(val) return val >= minY and val <= maxY end, order = 18 }, header1 = { name = "", type="header", order = 19}, defaultUnitTooltipPos = { name = "Position UI Tooltip", type = "select", values = square, get = function() return mod.db.profile.defaultUnitTooltipPos end, set = function(info, val) mod.db.profile.defaultUnitTooltipPos = val end, order = 20 }, refreshRate = { name = "Refresh Rate", desc = "Refresh rate at which to update the tooltip's position.", type = "input", pattern = "%d", get = function() return tostring(mod.db.profile.refreshRate) end, set = function(info, val) mod.db.profile.refreshRate = tonumber(val) end, order = 21 }, animationsOn = { name = "Perform Animations?", desc = "This has very little overhead as the coordinates are just added together with the mouse pointer coordinates at each refresh of the position timer. Adjusting 'Refresh Rate' will have more impact if you're worried about performance. I hope people use animations. :)", type = "toggle", get = function() return mod.db.profile.animationsOn end, set = function(info, val) mod.db.profile.animationsOn = val end, order = 22 }, anchorScript = { name = "Gametooltip Non-Unit", type = "input", multiline = true, width = "full", get = function() return mod.db.profile.anchorScript end, set = function(info, val) mod.db.profile.anchorScript = val end, order = 23 }, inCombatScript = { name = "Gametooltip Is-Unit", type = "input", multiline = true, width = "full", get = function() return mod.db.profile.inCombatScript end, set = function(info, val) mod.db.profile.inCombatScript = val end, order = 24 }, unitFramesScript = { name = "Tooltip Main", type = "input", multiline = true, width = "full", get = function() return mod.db.profile.unitFramesScript end, set = function(info, val) mod.db.profile.unitFramesScript = val end, order = 25 }, otherScript = { name = "Other objects", type = "input", multiline = true, width = "full", get = function() return mod.db.profile.otherScript end, set = function(info, val) mod.db.profile.otherScript = val end, order = 26 }, animationInit = { name = "Animation Init Script", type = "input", multiline = true, width = "full", get = function() return mod.db.profile.animationInit end, set = function(info, val) mod.db.profile.animationInit = val end, order = 27 }, animationFrame = { name = "Animation Frame Script", type = "input", multiline = true, width = "full", get = function() return mod.db.profile.animationFrame end, set = function(info, val) mod.db.profile.animationFrame = val end, order = 28 }, animationPoint = { name = "Animation Point Script", type = "input", multiline = true, width = "full", get = function() return mod.db.profile.animationPoint end, set = function(info, val) mod.db.profile.animationPoint = val end, order = 29 } } local PositionTooltip, PositionMainTooltip function mod:OnInitialize() self.db = StarTip.db:RegisterNamespace(self:GetName(), defaults) StarTip:SetOptionsDisabled(options, true) end function mod:OnEnable() self:RegisterEvent("REGEN_DISABLED") self:RegisterEvent("REGEN_ENABLED") self:SecureHook("GameTooltip_SetDefaultAnchor") StarTip:SetOptionsDisabled(options, false) self.updateTimer = LibTimer:New("Position timer", self.db.profile.refreshRate, true, PositionTooltip) self.fakeUpdateTimer = LibTimer:New("Position fake timer", self.db.profile.refreshRate, true, PositionMainTooltip) self.environment = {} for k, v in pairs(StarTip.environment) do self.environment[k] = v end Evaluator.ExecuteCode(self.environment, "StarTip.Position.animationInit", mod.db.profile.animationInit) end function mod:OnDisable() self:UnregisterEvent("REGEN_DISABLED") self:UnregisterEvent("REGEN_ENABLED") self:Unhook("GameTooltip_SetDefaultAnchor") StarTip:SetOptionsDisabled(options, true) end function mod:GetOptions() return options end local currentOwner local currentThis local getIndex = function() local index = self.db.profile.other if GameTooltip:GetUnit() and UnitExists(StarTip.unit) then if InCombatLockdown() then index = self.db.profile.inCombat elseif currentOwner == UIParent then index = self.db.profile.anchor else index = self.db.profile.unitFrames end else index = self.db.profile.other end return index end function mod:GetPosition(x, y) local environment = self.environment environment.x = x environment.y = y if currentOwner == UIParent then if UnitExists(StarTip.unit) then if InCombatLockdown() then Evaluator.ExecuteCode(environment, "StarTip.Position.inCombat", self.db.profile.inCombatScript) else Evaluator.ExecuteCode(environment, "StarTip.Position.inCombat", self.db.profile.anchorScript) end else Evaluator.ExecuteCode(environment, "StarTip.Position.other", self.db.profile.otherScript) end else if UnitExists(StarTip.unit) then Evaluator.ExecuteCode(environment, "StarTip.Position.unitFrameScript", self.db.profile.unitFramesScript) else Evaluator.ExecuteCode(environment, "StarTip.Position.other", self.db.profile.otherScript) end end return environment.x, environment.y end local function hideGameTooltip() GameTooltip:ClearAllPoints() GameTooltip:SetClampRectInsets(10000, 0, 0, 0) GameTooltip:SetPoint("RIGHT", UIParent, "LEFT") end local isUnitTooltip local currentAnchor = "BOTTOM" PositionTooltip = function() local environment = mod.environment local effScale = GameTooltip:GetEffectiveScale() local x, y = GetCursorPosition() environment.relativeFrame = UIParent x, y = mod:GetPosition(x, y) -- execute user script local xx, yy = Evaluator.ExecuteCode(mod.environment, "Position.animation", mod.db.profile.animation) local index = getIndex(environment.anchorFrame) local anchor = environment.anchor or StarTip.opposites[StarTip.anchors[index]:sub(8)] local relative = environment.relativeRelative or "BOTTOMLEFT" environment.anchor = false environment.anchorRelative = false if not isUnitTooltip then GameTooltip:ClearAllPoints() GameTooltip:SetPoint(anchor, environment.relativeFrame, relative, x / effScale, y / effScale) end if UnitExists(StarTip.unit or "mouseover") then if mod.db.profile.defaultUnitTooltipPos == 5 then hideGameTooltip() else local pos = squareNames[self.db.profile.defaultUnitTooltipPos] GameTooltip:ClearAllPoints() GameTooltip:SetPoint(StarTip.opposites[pos], StarTip.tooltipMain, pos) end end end local minX = -math.floor(GetScreenWidth()/5 + 0.5) * 5 local minY = -math.floor(GetScreenHeight()/5 + 0.5) * 5 local maxX = math.floor(GetScreenWidth()/5 + 0.5) * 5 local maxY = math.floor(GetScreenHeight()/5 + 0.5) * 5 PositionMainTooltip = function() local tooltip = StarTip.tooltipMain local environment = mod.environment environment.effScale = tooltip:GetEffectiveScale() local x, y = GetCursorPosition() x, y = mod:GetPosition(x, y) -- execute user script -- animation if mod.db.profile.animationsOn then mod.environment.i = (mod.environment.i or 0) + 1 mod.environment.v = (mod.environment.v or 0) + random() / 100 Evaluator.ExecuteCode(mod.environment, "Position.animationPoint", mod.db.profile.animationPoint) local xx, yy = mod.environment.x, mod.environment.y x = x + floor((((xx or 0) + 1.0) * GetScreenWidth() * 0.01)) y = y + floor((((yy or 0) + 1.0) * GetScreenHeight() * 0.01)) end local effScale = environment.effScale local anchor = environment.anchor or "BOTTOMRIGHT" local relativeFrame = environment.relativeFrame or GameTooltip:GetParent() local anchorRelative = environment.anchorRelative or "BOTTOMLEFT" local index = getIndex(relativeFrame) environment.anchor = false environment.relativeFrame = false environment.anchorRelative = false if StarTip.anchors[index]:find("^CURSOR_") then anchor = StarTip.opposites[StarTip.anchors[index]:sub(8)] end tooltip:ClearAllPoints() tooltip:SetPoint(anchor, relativeFrame, anchorRelative, x / effScale, y / effScale) end local updateTimer = LibTimer:New("Position timer", 40, true, PositionTooltip) local fakeUpdateTimer = LibTimer:New("Position fake timer", 40, true, PositionMainTooltip) --[[ mod:SetOffsets = function() if currentOwner == UIParent then if UnitExists(StarTip.unit) then if InCombatLockdown() then xoffset = self.db.profile.inCombatXOffset yoffset = self.db.profile.inCombatYOffset else xoffset = self.db.profile.anchorXOffset yoffset = self.db.profile.anchorYOffset end else xoffset = self.db.profile.otherXOffset yoffset = self.db.profile.otherYOffset end else if UnitExists(StarTip.unit) then xoffset = self.db.profile.unitFramesXOffset yoffset = self.db.profile.unitFramesYOffset else xoffset = self.db.profile.otherXOffset yoffset = self.db.profile.otherYOffset end end end ]] local function delayAnchor() local this = currentThis local owner = currentOwner local index = getIndex(owner) if index == #selections then this:Hide() return elseif StarTip.anchors[index]:find("^CURSOR_") then currentAnchor = StarTip.opposites[StarTip.anchors[index]:sub(8)] isUnitTooltip = false if GameTooltip:GetUnit() then isUnitTooltip = true fakeUpdateTimer:Start() PositionMainTooltip() end updateTimer:Start() PositionTooltip() elseif GameTooltip:GetUnit() then fakeUpdateTimer:Stop() updateTimer:Stop() StarTip.envirnoment.x = 0 mod.environment.y = 0 Evaluator.ExecuteCode(mod.environment, "StarTip.Position", mod.db.profile.anchorScript) StarTip.tooltipMain:ClearAllPoints() StarTip.tooltipMain:SetPoint(StarTip.anchors[index], UIParent, StarTip.anchors[index], mod.environment.x, mod.environment.y) else fakeUpdateTimer:Stop() updateTimer:Stop() GameTooltip:ClearAllPoints() mod.environment.x = 0 mod.environment.y = 0 mod.environment.i = index Evaluator.ExecuteCode(mod.environment, "StarTip.Position", mod.db.profile.otherScript) index = StarTip.envirnment.i local anchor = StarTip.anchor or StarTip.anchors[index] local relative = StarTip.anchorRelative or anchor GameTooltip:SetPoint(anchor, UIParent, anchorRelative, mod.environment.x, mod.environment.y) StarTip.anchor = false end end local delayTimer = LibTimer:New("Position delay timer", 30, false, delayAnchor) function mod:GameTooltip_SetDefaultAnchor(this, owner) currentOwner = owner currentThis = this local index = getIndex(owner) local ownername = owner:GetName() Evaluator.ExecuteCode(mod.environment, "StarTip.Position.animationFrame", self.db.profile.animationFrame) if owner == MainMenuMicroButton then -- This one is troublesome, so single it out and anchor right away. delayAnchor() else delayTimer:Start() end end function mod:REGEN_DISABLED() if not currentOwner then return end updateFrame:SetScript("OnUpdate", nil) self:GameTooltip_SetDefaultAnchor(GameTooltip, currentOwner) end mod.REGEN_ENABLED = mod.REGEN_DISABLED function mod:OnHide() updateTimer:Stop() delayTimer:Stop() fakeUpdateTimer:Stop() end function mod:SetSpell() local index = getIndex(currentOwner) if StarTip.anchors[index]:find("^CURSOR_") then updateTimer:Stop() PositionTooltip() else GameTooltip:ClearAllPoints() GameTooltip:SetPoint(StarTip.anchors[index], UIParent, StarTip.anchors[index], xoffset, yoffset) end end function mod:SetItem() local index = getIndex(currentOwner) if StarTip.anchors[index]:find("^CURSOR_") then updateTimer:Stop() PositionTooltip() else GameTooltip:ClearAllPoints() GameTooltip:SetPoint(StarTip.anchors[index], UIParent, StarTip.anchors[index], xoffset, yoffset) end end function mod:SetUnit() local index = getIndex(currentOwner) if not StarTip.anchors[index]:find("^CURSOR_") then hideGameTooltip() end end