From 49c08f37506e3a1f3092895c57328bee2c2c125b Mon Sep 17 00:00:00 2001 From: Vollmer Date: Mon, 9 Sep 2024 15:12:04 +0200 Subject: [PATCH] [Feature] Preview & Absolute Positioning (#60) * Remove Menu Button * first pass at an editmode * add a button to toggle edit mode * move menu frame down to compensate * hide overlays when in combat * increase overlayText scale and use cell widget font * update overlay styling * update overlay label * add animation * update position Popup when dragging * update popup on UpdateLayout & UpdateUnitButton callbacks * remove anchor dropdown from units * add parent anchor options * locale * move Edit Mode button to Unit Frames tab * inform user of /cuf edit in tooltip * inform user that unit frames are clickable * cancel animations if they overlap * update edit mode button styling * use PixelUtil to set point * Bump DB version to 5 * Revise unit frame positioning to (hopefully) keep them in place with new anchoring --- Cell_UnitFrames.toc | 2 +- Core/Init.lua | 6 +- Core/OnLoad.lua | 1 + Core/SlashCommands.lua | 3 + Data/Defaults.lua | 43 +++-- Data/Revise.lua | 50 ++++- Locales/enUS.lua | 9 + Menu/Menu.lua | 17 ++ UnitFrames/EditMode.lua | 379 +++++++++++++++++++++++++++++++++++++ UnitFrames/MenuOptions.lua | 33 +--- UnitFrames/UnitButton.lua | 284 +++++++++------------------ UnitFrames/Units.lua | 91 --------- Util/Utils.lua | 24 +++ 13 files changed, 603 insertions(+), 339 deletions(-) create mode 100644 UnitFrames/EditMode.lua delete mode 100644 UnitFrames/Units.lua diff --git a/Cell_UnitFrames.toc b/Cell_UnitFrames.toc index a41e0f8..cc1b0d8 100644 --- a/Cell_UnitFrames.toc +++ b/Cell_UnitFrames.toc @@ -70,7 +70,7 @@ Widgets/Auras/Handler.lua UnitFrames/UnitButton.xml UnitFrames/UnitButton.lua UnitFrames/OnLoad.lua -UnitFrames/Units.lua UnitFrames/MenuOptions.lua +UnitFrames/EditMode.lua Core/OnLoad.lua \ No newline at end of file diff --git a/Core/Init.lua b/Core/Init.lua index b61c9ec..4f88936 100644 --- a/Core/Init.lua +++ b/Core/Init.lua @@ -2,7 +2,7 @@ local CUF = select(2, ...) _G.CUF = CUF -CUF.version = 4 +CUF.version = 5 CUF.Cell = Cell @@ -31,5 +31,9 @@ CUF.Builder = {} ---@field isMenuOpen boolean ---@field isRetail boolean ---@field selectedTab string +---@field inEditMode boolean CUF.vars = {} + +---@class CUF.unitButtons +---@field [Unit] CUFUnitButton CUF.unitButtons = {} diff --git a/Core/OnLoad.lua b/Core/OnLoad.lua index 5bee123..9696b37 100644 --- a/Core/OnLoad.lua +++ b/Core/OnLoad.lua @@ -17,6 +17,7 @@ local function OnCellInitialUpdateLayout(_layout) CUF.vars.selectedWidget = CUF.constants.WIDGET_KIND.NAME_TEXT CUF.vars.selectedLayout = Cell.vars.currentLayout CUF.vars.isRetail = WOW_PROJECT_ID == WOW_PROJECT_MAINLINE + CUF.vars.inEditMode = false -- Hide Blizzard Unit Frames for _, unit in pairs(CUF.constants.UNIT) do diff --git a/Core/SlashCommands.lua b/Core/SlashCommands.lua index d7dd16c..bb2956a 100644 --- a/Core/SlashCommands.lua +++ b/Core/SlashCommands.lua @@ -19,10 +19,13 @@ function SlashCmdList.CUF(msg, editbox) return end CUF.DB.RestoreFromBackup(rest) + elseif command == "edit" then + CUF.uFuncs:EditMode() else CUF:Print("Available commands:" .. "\n" .. "/cuf test - toggle test mode" .. "\n" .. "/cuf dev - toggle debug mode" .. "\n" .. + "/cuf edit - toggle edit mode" .. "\n" .. "/cuf restore - restore a backup" ) end diff --git a/Data/Defaults.lua b/Data/Defaults.lua index c856c36..f1f3a88 100644 --- a/Data/Defaults.lua +++ b/Data/Defaults.lua @@ -582,23 +582,25 @@ Defaults.Widgets = { ---@field [1] number ---@field [2] number ----@class TooltipPosition ----@field [1] FramePoint ----@field [2] FramePoint ----@field [3] number ----@field [4] number +---@class ParentAnchor +---@field point FramePoint +---@field relativePoint FramePoint +---@field offsetX number +---@field offsetY number ---@class UnitLayout ---@field enabled boolean ---@field powerSize number ---@field size Size ----@field point FramePoint ---@field position Position ---@field widgets WidgetTables ----@field tooltipPosition TooltipPosition ---@field sameSizeAsPlayer boolean? ---@field hideBlizzardCastBar boolean? ---@field clickCast boolean +---@field mirrorPlayer boolean? +---@field parent Unit? +---@field anchorToParent boolean? +---@field anchorPosition ParentAnchor? ---@alias UnitLayoutTable table ---@type UnitLayoutTable @@ -607,10 +609,8 @@ Defaults.Layouts = { enabled = false, powerSize = 2, size = { 200, 40 }, - point = "BOTTOMLEFT", position = { 800, 500 }, widgets = Defaults.Widgets, - tooltipPosition = { "BOTTOMLEFT", "BOTTOMLEFT", -3, 0, }, hideBlizzardCastBar = false, clickCast = false, }, @@ -618,7 +618,6 @@ Defaults.Layouts = { enabled = false, powerSize = 2, size = { 200, 40 }, - point = "BOTTOMLEFT", position = { 1400, 500 }, widgets = { nameText = Defaults.Widgets.nameText, @@ -635,15 +634,14 @@ Defaults.Layouts = { shieldBar = Defaults.Widgets.shieldBar, castBar = Defaults.Widgets.castBar, }, - tooltipPosition = { "BOTTOMLEFT", "BOTTOMLEFT", -3, 0, }, sameSizeAsPlayer = false, clickCast = false, + mirrorPlayer = false, }, focus = { enabled = false, powerSize = 2, size = { 100, 30 }, - point = "BOTTOMLEFT", position = { 800, 700 }, widgets = { nameText = Defaults.Widgets.nameText, @@ -660,7 +658,6 @@ Defaults.Layouts = { shieldBar = Defaults.Widgets.shieldBar, castBar = Defaults.Widgets.castBar, }, - tooltipPosition = { "BOTTOMLEFT", "BOTTOMLEFT", -3, 0, }, sameSizeAsPlayer = false, clickCast = false, }, @@ -668,7 +665,6 @@ Defaults.Layouts = { enabled = false, powerSize = 2, size = { 200, 40 }, - point = "BOTTOMLEFT", position = { 1620, 500 }, widgets = { nameText = Defaults.Widgets.nameText, @@ -677,15 +673,21 @@ Defaults.Layouts = { levelText = Defaults.Widgets.levelText, raidIcon = Defaults.Widgets.raidIcon, }, - tooltipPosition = { "BOTTOMLEFT", "BOTTOMLEFT", -3, 0, }, sameSizeAsPlayer = false, clickCast = false, + anchorToParent = true, + parent = CUF.constants.UNIT.TARGET, + anchorPosition = { + point = "BOTTOMLEFT", + relativePoint = "BOTTOMRIGHT", + offsetX = 2, + offsetY = 0 + }, }, pet = { enabled = false, powerSize = 2, size = { 200, 30 }, - point = "BOTTOMLEFT", position = { 800, 460 }, widgets = { nameText = Defaults.Widgets.nameText, @@ -698,8 +700,15 @@ Defaults.Layouts = { shieldBar = Defaults.Widgets.shieldBar, castBar = Defaults.Widgets.castBar, }, - tooltipPosition = { "BOTTOMLEFT", "BOTTOMLEFT", -3, 0, }, sameSizeAsPlayer = false, clickCast = false, + anchorToParent = true, + parent = CUF.constants.UNIT.PLAYER, + anchorPosition = { + point = "TOPLEFT", + relativePoint = "BOTTOMLEFT", + offsetX = 0, + offsetY = -5 + }, }, } diff --git a/Data/Revise.lua b/Data/Revise.lua index 6177ff6..e69a0e3 100644 --- a/Data/Revise.lua +++ b/Data/Revise.lua @@ -1,4 +1,4 @@ ----@diagnostic disable: inject-field +---@diagnostic disable: inject-field, undefined-field ---@class CUF local CUF = select(2, ...) @@ -34,4 +34,52 @@ function DB:Revise() end end) end + if CUF_DB.version < 5 then + -- Load late since we need proper screen size + CUF:AddEventListener("LOADING_SCREEN_DISABLED", function() + local sWidth = GetScreenWidth() / 2 + local sHeight = GetScreenHeight() / 2 + local buffer = 14 + + IterateUnitLayouts(function(layout) + if not layout.point then return end + local anchorPoint = layout.point + + local xPos, yPos = unpack(layout.position) + local bWidth, bHeight = unpack(layout.size) + local bOffsetX = bWidth / 2 + local bOffsetY = bHeight / 2 + + local newX = xPos - sWidth + local newY = yPos - sHeight + + if CellDB.general.menuPosition == "top_bottom" then + if anchorPoint == "BOTTOMLEFT" then + newY = newY + buffer + bOffsetY + elseif anchorPoint == "BOTTOMRIGHT" then + newY = newY + buffer + bOffsetY + elseif anchorPoint == "TOPLEFT" then + newY = newY - 4 - bOffsetY + elseif anchorPoint == "TOPRIGHT" then + newY = newY - 4 - bOffsetY + end + else + if anchorPoint == "BOTTOMLEFT" then + newX = newX + buffer + bOffsetX + elseif anchorPoint == "BOTTOMRIGHT" then + newX = newX - 4 - bOffsetX + elseif anchorPoint == "TOPLEFT" then + newX = newX + buffer + bOffsetX + elseif anchorPoint == "TOPRIGHT" then + newX = newX - 4 - bOffsetX + end + end + + layout.position = { newX, newY } + end) + + CUF:Fire("UpdateUnitButtons") + return true + end) + end end diff --git a/Locales/enUS.lua b/Locales/enUS.lua index 01b2afe..7958f91 100644 --- a/Locales/enUS.lua +++ b/Locales/enUS.lua @@ -152,6 +152,15 @@ L.texture = "Texture" L.VerticalFill = "Vertical Fill" L.SameSizeAsHealthBar = "Same Size As Health Bar" L.EditingLayout = "Editing Layout" +L.MirrorPlayer = "Mirror Player" +L.Positioning = "Positioning" +L.EditMode = "Edit Mode" +L.ToggleEditMode = "Toggle Edit Mode" +L.EditModeButtonTooltip = [[Edit mode allows you to position the Unit Frames. + +Clicking on a Unit Frame will bring up more options. + +"/cuf edit" will also toggle this mode.]] -- Custom Formats L.ValidTags = "Valid Tags" diff --git a/Menu/Menu.lua b/Menu/Menu.lua index c939211..02a3e85 100644 --- a/Menu/Menu.lua +++ b/Menu/Menu.lua @@ -91,10 +91,12 @@ end function menu:ShowLayoutTitle() self.layoutTitleFrame:Show() self:SetLayoutTitle() + self.editModeButton:Show() end function menu:HideLayoutTitle() self.layoutTitleFrame:Hide() + self.editModeButton:Hide() end ------------------------------------------------- @@ -257,6 +259,21 @@ function menu:CreateMenu() self:InitTabs() + local editModeButton = Cell:CreateButton(self.tabPane, L.EditMode, "accent", + { 100, 25 }) + editModeButton:SetPoint("TOPLEFT", self.tabPane, "BOTTOMLEFT", 0, 0) + CUF:SetTooltips(editModeButton, "ANCHOR_TOPLEFT", 0, 3, L.ToggleEditMode, + L.EditModeButtonTooltip) + + editModeButton:SetScript("OnClick", function() + CUF.uFuncs:EditMode() + end) + editModeButton:SetScript("OnHide", function() + CUF.uFuncs:EditMode(false) + end) + self.editModeButton = editModeButton + self.editModeButton:Hide() + hooksecurefunc(optionsFrame, "Hide", function() self:HideMenu() end) diff --git a/UnitFrames/EditMode.lua b/UnitFrames/EditMode.lua new file mode 100644 index 0000000..c388b63 --- /dev/null +++ b/UnitFrames/EditMode.lua @@ -0,0 +1,379 @@ +---@class CUF +local CUF = select(2, ...) + +local Cell = CUF.Cell +local P = Cell.pixelPerfectFuncs + +---@class CUF.uFuncs +local U = CUF.uFuncs + +local const = CUF.constants +local Util = CUF.Util +local L = CUF.L + +------------------------------------------------- +-- MARK: Positioning Popup +------------------------------------------------- + +---@type CUFPositioningPopup +local positioningPopup + +local function CreatePositioningPopup() + ---@class CUFPositioningPopup: Frame + ---@field unit Unit + positioningPopup = CUF:CreateFrame("CUFPositioningPopup", UIParent, 340, 160) + positioningPopup:SetPoint("CENTER") + positioningPopup:Hide() + + positioningPopup:SetMovable(true) + positioningPopup:EnableMouse(true) + positioningPopup:RegisterForDrag("LeftButton") + positioningPopup:SetScript("OnDragStart", function(self) + self:StartMoving() + end) + positioningPopup:SetScript("OnDragStop", function(self) + self:StopMovingOrSizing() + end) + + local closeBtn = Cell:CreateButton(positioningPopup, "×", "red", { 18, 18 }, false, false, "CELL_FONT_SPECIAL", + "CELL_FONT_SPECIAL") + closeBtn:SetPoint("TOPRIGHT", P:Scale(-5), P:Scale(-1)) + closeBtn:SetScript("OnClick", function() positioningPopup:Hide() end) + + local title = positioningPopup:CreateFontString(nil, "OVERLAY", "CELL_FONT_CLASS") + title:SetPoint("TOPLEFT", 5, -5) + positioningPopup.title = title + + -- Offsets + local xVal = GetScreenWidth() / 2 + local yVal = GetScreenHeight() / 2 + + positioningPopup.xPosSlider = Cell:CreateSlider(L["X Offset"], positioningPopup, -xVal, xVal, 150, 1) + positioningPopup.xPosSlider:SetPoint("TOPLEFT", 10, -45) + positioningPopup.xPosSlider.onValueChangedFn = function(value) + CUF.DB.CurrentLayoutTable()[positioningPopup.unit].position[1] = value + CUF:Fire("UpdateLayout", nil, "position", positioningPopup.unit) + + if positioningPopup.unit == const.UNIT.PLAYER then + CUF:Fire("UpdateLayout", nil, "position", const.UNIT.TARGET) + end + end + + positioningPopup.yPosSlider = Cell:CreateSlider(L["Y Offset"], positioningPopup, -yVal, yVal, 150, 1) + positioningPopup.yPosSlider:SetPoint("TOPLEFT", positioningPopup.xPosSlider, "TOPRIGHT", 20, 0) + + positioningPopup.yPosSlider.onValueChangedFn = function(value) + CUF.DB.CurrentLayoutTable()[positioningPopup.unit].position[2] = value + CUF:Fire("UpdateLayout", nil, "position", positioningPopup.unit) + + if positioningPopup.unit == const.UNIT.PLAYER then + CUF:Fire("UpdateLayout", nil, "position", const.UNIT.TARGET) + end + end + + -- Mirror + ---@type CheckButton + local mirrorCB = Cell:CreateCheckButton(positioningPopup, L.MirrorPlayer, function(checked) + CUF.DB.CurrentLayoutTable()[const.UNIT.TARGET].mirrorPlayer = checked + U:UpdateUnitButtonPosition("target", CUF.unitButtons.target) + positioningPopup.xPosSlider:SetEnabled(not checked) + positioningPopup.yPosSlider:SetEnabled(not checked) + end) + mirrorCB:SetPoint("TOPLEFT", positioningPopup.xPosSlider, "BOTTOMLEFT", 0, -40) + positioningPopup.mirrorCB = mirrorCB + + -- Parent Anchor + local parentAnchorFrame = CreateFrame("Frame", nil, positioningPopup) + + ---@type CellCheckButton + local anchorToParentCB = Cell:CreateCheckButton(parentAnchorFrame, "", function(checked) + CUF.DB.CurrentLayoutTable()[positioningPopup.unit].anchorToParent = checked + U:UpdateUnitButtonPosition("target", CUF.unitButtons.target) + CUF:Fire("UpdateLayout", nil, "position", positioningPopup.unit) + + positioningPopup.pointDropdown:SetEnabled(checked) + positioningPopup.relativeDropdown:SetEnabled(checked) + positioningPopup.parentOffsetXSlider:SetEnabled(checked) + positioningPopup.parentOffsetYSlider:SetEnabled(checked) + + positioningPopup.xPosSlider:SetEnabled(not checked) + positioningPopup.yPosSlider:SetEnabled(not checked) + end) + anchorToParentCB:SetPoint("TOPLEFT", positioningPopup.xPosSlider, "BOTTOMLEFT", 0, -40) + positioningPopup.anchorToParentCB = anchorToParentCB + + ---@type CellDropdown + local pointDropdown = Cell:CreateDropdown(parentAnchorFrame, 117) + pointDropdown:SetPoint("TOPLEFT", anchorToParentCB, "BOTTOMLEFT", 0, -30) + pointDropdown:SetLabel(L["Anchor Point"]) + + ---@type CellDropdown + local relativePointDropdown = Cell:CreateDropdown(parentAnchorFrame, 117) + relativePointDropdown:SetPoint("TOPLEFT", pointDropdown, "TOPRIGHT", 30, 0) + relativePointDropdown:SetLabel(L["To UnitButton's"]) + + for _, point in pairs(const.ANCHOR_POINTS) do + pointDropdown:AddItem({ + text = L[point], + value = point, + onClick = function() + CUF.DB.CurrentLayoutTable()[positioningPopup.unit].anchorPosition.point = point + CUF:Fire("UpdateLayout", nil, "position", positioningPopup.unit) + end, + }) + relativePointDropdown:AddItem({ + text = L[point], + value = point, + onClick = function() + CUF.DB.CurrentLayoutTable()[positioningPopup.unit].anchorPosition.relativePoint = point + CUF:Fire("UpdateLayout", nil, "position", positioningPopup.unit) + end, + }) + end + + local parentOffsetXSlider = Cell:CreateSlider(L["X Offset"], parentAnchorFrame, -xVal, xVal, 150, 1) + parentOffsetXSlider:SetPoint("TOPLEFT", pointDropdown, "BOTTOMLEFT", 0, -30) + parentOffsetXSlider.onValueChangedFn = function(value) + CUF.DB.CurrentLayoutTable()[positioningPopup.unit].anchorPosition.offsetX = value + CUF:Fire("UpdateLayout", nil, "position", positioningPopup.unit) + end + + local parentOffsetYSlider = Cell:CreateSlider(L["Y Offset"], parentAnchorFrame, -yVal, yVal, 150, 1) + parentOffsetYSlider:SetPoint("TOPLEFT", parentOffsetXSlider, "TOPRIGHT", 20, 0) + parentOffsetYSlider.onValueChangedFn = function(value) + CUF.DB.CurrentLayoutTable()[positioningPopup.unit].anchorPosition.offsetY = value + CUF:Fire("UpdateLayout", nil, "position", positioningPopup.unit) + end + + positioningPopup.parentAnchorFrame = parentAnchorFrame + positioningPopup.pointDropdown = pointDropdown + positioningPopup.relativeDropdown = relativePointDropdown + positioningPopup.parentOffsetXSlider = parentOffsetXSlider + positioningPopup.parentOffsetYSlider = parentOffsetYSlider +end + +local function UpdatePositioningPopup() + if not positioningPopup then return end + local unit = positioningPopup.unit + local layout = CUF.DB.CurrentLayoutTable()[unit] + + positioningPopup.xPosSlider:SetValue(layout.position[1]) + positioningPopup.yPosSlider:SetValue(layout.position[2]) + + local isMirrored = layout.mirrorPlayer or false + + if unit == const.UNIT.TARGET then + positioningPopup.mirrorCB:SetChecked(isMirrored) + positioningPopup.mirrorCB:Show() + else + positioningPopup.mirrorCB:Hide() + end + + if layout.anchorToParent ~= nil then + local checked = layout.anchorToParent + + positioningPopup.anchorToParentCB:SetChecked(checked) + positioningPopup.anchorToParentCB.label:SetText(L["Anchor To"] .. " " .. L[layout.parent]) + + positioningPopup.pointDropdown:SetSelectedValue(layout.anchorPosition.point) + positioningPopup.relativeDropdown:SetSelectedValue(layout.anchorPosition + .relativePoint) + positioningPopup.parentOffsetXSlider:SetValue(layout.anchorPosition.offsetX) + positioningPopup.parentOffsetYSlider:SetValue(layout.anchorPosition.offsetY) + + positioningPopup.pointDropdown:SetEnabled(checked) + positioningPopup.relativeDropdown:SetEnabled(checked) + positioningPopup.parentOffsetXSlider:SetEnabled(checked) + positioningPopup.parentOffsetYSlider:SetEnabled(checked) + + positioningPopup.xPosSlider:SetEnabled(not checked) + positioningPopup.yPosSlider:SetEnabled(not checked) + else + positioningPopup.xPosSlider:SetEnabled(not isMirrored) + positioningPopup.yPosSlider:SetEnabled(not isMirrored) + end +end + +---@param unit Unit +local function ShowPositioningPopup(unit) + if not positioningPopup then + CreatePositioningPopup() + end + positioningPopup:Show() + positioningPopup.title:SetText(L.Positioning .. ": " .. L[unit]) + + positioningPopup.unit = unit + + if CUF.DB.CurrentLayoutTable()[unit].anchorToParent ~= nil then + positioningPopup.parentAnchorFrame:Show() + positioningPopup:SetHeight(230) + else + positioningPopup.parentAnchorFrame:Hide() + if unit == const.UNIT.TARGET then + positioningPopup:SetHeight(120) + else + positioningPopup:SetHeight(85) + end + end + + UpdatePositioningPopup() + + CUF:RegisterCallback("UpdateUnitButtons", "UpdatePositioningPopup", UpdatePositioningPopup) + CUF:RegisterCallback("UpdateLayout", "UpdatePositioningPopup", UpdatePositioningPopup) +end + +local function HidePositioningPopup() + if positioningPopup then + positioningPopup:Hide() + end + + CUF:UnregisterCallback("UpdateUnitButtons", "UpdatePositioningPopup") + CUF:UnregisterCallback("UpdateLayout", "UpdatePositioningPopup") +end + +------------------------------------------------- +-- MARK: Overlay +------------------------------------------------- + +---@type table +local overlays = {} + +local colors = { + [const.UNIT.PLAYER] = { 1, 0, 0 }, + [const.UNIT.TARGET] = { 1, 0.5, 0 }, + [const.UNIT.TARGET_TARGET] = { 1, 1, 0 }, + [const.UNIT.FOCUS] = { 0, 1, 0 }, + [const.UNIT.PET] = { 0, 0.5, 1 }, +} + +---@param button CUFUnitButton +---@param unit Unit +local function CreateOverlayBox(button, unit) + ---@class CUFOverlayBox: CellButton + local overlay = CUF:CreateButton(UIParent, "", { 1, 1 }, + function() ShowPositioningPopup(unit) end, "accent") + overlay:SetAllPoints(button) + overlay:SetFrameStrata("HIGH") + overlay:SetFrameLevel(overlay:GetFrameLevel() + 100) + overlay:Hide() + + local label = overlay:CreateFontString(nil, "OVERLAY", const.FONTS.CELL_WIGET) + label:SetPoint("CENTER") + label:SetText(L[unit]) + + -- Register mouse and movable + overlay:RegisterForDrag("LeftButton") + overlay:SetMovable(true) + button:SetMovable(true) + + -- Animation + overlay.fadeIn = overlay:CreateAnimationGroup() + local fadeIn = overlay.fadeIn:CreateAnimation("alpha") + fadeIn:SetFromAlpha(0) + fadeIn:SetToAlpha(1) + fadeIn:SetDuration(0.5) + fadeIn:SetSmoothing("OUT") + fadeIn:SetScript("OnPlay", function() + overlay:Show() + end) + + overlay.fadeOut = overlay:CreateAnimationGroup() + local fadeOut = overlay.fadeOut:CreateAnimation("alpha") + fadeOut:SetFromAlpha(1) + fadeOut:SetToAlpha(0) + fadeOut:SetDuration(0.5) + fadeOut:SetSmoothing("IN") + fadeOut:SetScript("OnFinished", function() + overlay:Hide() + end) + + -- Scripts + overlay:SetScript("OnDragStart", function() + button:StartMoving() + end) + overlay:SetScript("OnDragStop", function() + button:StopMovingOrSizing() + local x, y = Util.GetPositionRelativeToUIParentCenter(button) + + CUF.DB.CurrentLayoutTable()[unit].position = { x, y } + U:UpdateUnitButtonPosition(unit, button) + + if unit == const.UNIT.PLAYER then + CUF:Fire("UpdateLayout", nil, "position", const.UNIT.TARGET) + end + + UpdatePositioningPopup() + end) + + -- Hooks + overlay:HookScript("OnShow", function() + button:SetMovable(true) + end) + overlay:HookScript("OnHide", function() + button:SetMovable(false) + end) + + overlays[unit] = overlay + return overlay +end + +--- Play the fade out animation and hide the overlays +--- +--- If instant is true, the overlays will be hidden instantly +---@param instant boolean? +local function HideOverlays(instant) + for _, overlay in pairs(overlays) do + if instant then + overlay:Hide() + else + overlay.fadeOut:Play() + if overlay.fadeIn:IsPlaying() then + overlay.fadeIn:Stop() + end + end + end +end + +--- Play the fade in animation and show the overlays +local function ShowOverlays() + for _, unit in pairs(CUF.constants.UNIT) do + local overlay = overlays[unit] or CreateOverlayBox(CUF.unitButtons[unit], unit) + + overlay.fadeIn:Play() + if overlay.fadeOut:IsPlaying() then + overlay.fadeOut:Stop() + end + end +end + +------------------------------------------------- +-- MARK: Edit Mode +------------------------------------------------- + +local eventFrame = CreateFrame("Frame") +eventFrame:SetScript("OnEvent", function() + CUF.vars.inEditMode = false + HideOverlays(true) + HidePositioningPopup() +end) + +--- Enable or disable edit mode +--- +--- If show is nil then the current state will be toggled +---@param show boolean? +function U:EditMode(show) + if show ~= nil then + CUF.vars.inEditMode = show + else + CUF.vars.inEditMode = not CUF.vars.inEditMode + end + + if CUF.vars.inEditMode then + ShowOverlays() + eventFrame:RegisterEvent("PLAYER_REGEN_DISABLED") + else + HideOverlays() + HidePositioningPopup() + eventFrame:UnregisterEvent("PLAYER_REGEN_DISABLED") + end +end diff --git a/UnitFrames/MenuOptions.lua b/UnitFrames/MenuOptions.lua index 37e8f14..3f81dd1 100644 --- a/UnitFrames/MenuOptions.lua +++ b/UnitFrames/MenuOptions.lua @@ -12,12 +12,6 @@ local function UpdateSize() end end -local function UpdateArrangement() - if CUF.vars.selectedLayout == CUF.DB.GetMasterLayout() then - CUF:Fire("UpdateLayout", CUF.vars.selectedLayout, CUF.vars.selectedUnit .. "-arrangement") - end -end - ---@param unitPage UnitsMenuPage local function AddLoadPageDB(unitPage) -- Load page from DB @@ -33,9 +27,6 @@ local function AddLoadPageDB(unitPage) unitPage.heightSlider:SetValue(pageDB.size[2]) unitPage.powerSizeSlider:SetValue(pageDB.powerSize) - -- unit arrangement - unitPage.anchorDropdown:SetSelectedValue(pageDB.point) - -- same as player if not isPlayerPage then unitPage.sameSizeAsPlayerCB:SetChecked(isSameSizeAsPlayer) @@ -50,12 +41,10 @@ local function AddLoadPageDB(unitPage) unitPage.widthSlider:SetEnabled(true) unitPage.heightSlider:SetEnabled(true) unitPage.powerSizeSlider:SetEnabled(true) - unitPage.anchorDropdown:SetEnabled(true) else unitPage.widthSlider:SetEnabled(not isSameSizeAsPlayer) unitPage.heightSlider:SetEnabled(not isSameSizeAsPlayer) unitPage.powerSizeSlider:SetEnabled(not isSameSizeAsPlayer) - unitPage.anchorDropdown:SetEnabled(not isSameSizeAsPlayer) end -- copy from @@ -133,8 +122,6 @@ local function AddUnitsToMenu() unitPage.widthSlider:SetEnabled(not checked) unitPage.heightSlider:SetEnabled(not checked) unitPage.powerSizeSlider:SetEnabled(not checked) - -- TODO: should be arrangment based instead - unitPage.anchorDropdown:SetEnabled(not checked) -- update size and power UpdateSize() @@ -162,27 +149,9 @@ local function AddUnitsToMenu() end) unitPage.widthSlider:SetPoint("TOPLEFT", unitPage.enabledCB, 0, -50) - ---@type CellDropdown - unitPage.anchorDropdown = Cell:CreateDropdown(unitPage.frame, 117) - unitPage.anchorDropdown:SetPoint("TOPLEFT", unitPage.widthSlider, "TOPRIGHT", 30, 0) - - local dropdownItems = {} - for _, point in ipairs(CUF.constants.UNIT_ANCHOR_POINTS) do - tinsert(dropdownItems, { - ["text"] = L[point], - ["value"] = point, - ["onClick"] = function() - CUF.DB.SelectedLayoutTable()[unit].point = point - UpdateArrangement() - end, - }) - end - unitPage.anchorDropdown:SetItems(dropdownItems) - unitPage.anchorDropdown:SetLabel(L["Anchor Point"]) - ---@type CellDropdown unitPage.copyFromDropdown = Cell:CreateDropdown(unitPage.frame, 117) - unitPage.copyFromDropdown:SetPoint("TOPLEFT", unitPage.anchorDropdown, "TOPRIGHT", 30, 0) + unitPage.copyFromDropdown:SetPoint("TOPLEFT", unitPage.widthSlider, "TOPRIGHT", 30, 0) unitPage.copyFromDropdown:SetLabel(L.CopyWidgetsFrom) CUF:SetTooltips(unitPage.copyFromDropdown, "ANCHOR_TOPLEFT", 0, 3, L.CopyWidgetsFrom, L.CopyWidgetsFromTooltip) diff --git a/UnitFrames/UnitButton.lua b/UnitFrames/UnitButton.lua index a9b086e..0fa624f 100644 --- a/UnitFrames/UnitButton.lua +++ b/UnitFrames/UnitButton.lua @@ -2,10 +2,8 @@ local CUF = select(2, ...) local Cell = CUF.Cell -local L = CUF.L local F = Cell.funcs local P = Cell.pixelPerfectFuncs -local A = Cell.animations ---@class CUF.uFuncs local U = CUF.uFuncs @@ -14,145 +12,31 @@ local W = CUF.widgets local const = CUF.constants local Util = CUF.Util -------------------------------------------------- --- MARK: Save Tooltip Position -------------------------------------------------- - ----@param unit Unit ----@param tooltipPoint FramePoint ----@param tooltipRelativePoint FramePoint ----@param tooltipX number ----@param tooltipY number -function U:SaveTooltipPosition(unit, tooltipPoint, tooltipRelativePoint, tooltipX, tooltipY) - CUF.DB.CurrentLayoutTable()[unit].tooltipPosition = { tooltipPoint, tooltipRelativePoint, tooltipX, tooltipY } -end - -------------------------------------------------- --- MARK: Create Unit Frame -------------------------------------------------- - ----@param unit Unit ----@param onEnterLogic function? ----@return CUFUnitFrame CUFUnitFrame ----@return CUFAnchorFrame CUFAnchorFrame ----@return CUFHoverFrame CUFHoverFrame ----@return CUFConfigButton CUFConfigButton -function U:CreateBaseUnitFrame(unit, onEnterLogic) - local name = const.TITLE_CASED_UNITS[unit] - - ---@class CUFUnitFrame: Frame - local frame = CreateFrame("Frame", "CUF_" .. name .. "_Frame", Cell.frames.mainFrame, "SecureFrameTemplate") - - -- Anchor - ---@class CUFAnchorFrame: Frame, CellAnimation - local anchorFrame = CreateFrame("Frame", "CUF_" .. name .. "_AnchorFrame", frame) - PixelUtil.SetPoint(anchorFrame, "TOPLEFT", UIParent, "CENTER", 1, -1) - anchorFrame:SetMovable(true) - anchorFrame:SetClampedToScreen(true) - - -- Hover - ---@class CUFHoverFrame: Frame - local hoverFrame = CreateFrame("Frame", nil, frame, "BackdropTemplate") - hoverFrame:SetPoint("TOP", anchorFrame, 0, 1) - hoverFrame:SetPoint("BOTTOM", anchorFrame, 0, -1) - hoverFrame:SetPoint("LEFT", anchorFrame, -1, 0) - hoverFrame:SetPoint("RIGHT", anchorFrame, 1, 0) - - A:ApplyFadeInOutToMenu(anchorFrame, hoverFrame) - - ---@class CUFConfigButton: Button - ---@field UpdatePixelPerfect function - local config = Cell:CreateButton(anchorFrame, nil, "accent", { 20, 10 }, false, true, nil, nil, - "SecureHandlerAttributeTemplate,SecureHandlerClickTemplate") - config:SetFrameStrata("MEDIUM") - config:SetAllPoints(anchorFrame) - config:RegisterForDrag("LeftButton") - config:SetScript("OnDragStart", function() - anchorFrame:StartMoving() - anchorFrame:SetUserPlaced(false) - end) - config:SetScript("OnDragStop", function() - anchorFrame:StopMovingOrSizing() - P:SavePosition(anchorFrame, CUF.DB.CurrentLayoutTable()[unit].position) - end) - config:HookScript("OnEnter", function() - hoverFrame:GetScript("OnEnter")(hoverFrame) - CellTooltip:SetOwner(config, "ANCHOR_NONE") - - local tooltipPoint, tooltipRelativePoint, tooltipX, tooltipY = unpack(CUF.DB.CurrentLayoutTable()[unit] - .tooltipPosition) - P:Point(CellTooltip, tooltipPoint, config, tooltipRelativePoint, tooltipX, tooltipY) - - CellTooltip:AddLine(L[unit] .. " " .. L.Frame) - - -- Execute additional logic passed to the function - if type(onEnterLogic) == "function" then - onEnterLogic(CellTooltip) - end - - CellTooltip:Show() - end) - config:HookScript("OnLeave", function() - hoverFrame:GetScript("OnLeave")(hoverFrame) - CellTooltip:Hide() - end) - - return frame, anchorFrame, hoverFrame, config -end - ------------------------------------------------- -- MARK: Button Position ------------------------------------------------- ---@param unit Unit ---@param button CUFUnitButton ----@param anchorFrame CUFAnchorFrame -function U:UpdateUnitButtonPosition(unit, button, anchorFrame) +function U:UpdateUnitButtonPosition(unit, button) local layout = CUF.DB.CurrentLayoutTable() - - local anchorPoint - if layout[unit].sameSizeAsPlayer then - anchorPoint = layout[const.UNIT.PLAYER].point - else - anchorPoint = layout[unit].point - end + local unitLayout = layout[unit] button:ClearAllPoints() - -- NOTE: detach from PreviewAnchor - P:LoadPosition(anchorFrame, layout[unit].position) - - if CellDB.general.menuPosition == "top_bottom" then - P:Size(anchorFrame, 20, 10) - - if anchorPoint == "BOTTOMLEFT" then - P:Point(button, "BOTTOMLEFT", anchorFrame, "TOPLEFT", 0, 4) - U:SaveTooltipPosition(unit, "TOPLEFT", "BOTTOMLEFT", 0, -3) - elseif anchorPoint == "BOTTOMRIGHT" then - P:Point(button, "BOTTOMRIGHT", anchorFrame, "TOPRIGHT", 0, 4) - U:SaveTooltipPosition(unit, "TOPRIGHT", "BOTTOMRIGHT", 0, -3) - elseif anchorPoint == "TOPLEFT" then - P:Point(button, "TOPLEFT", anchorFrame, "BOTTOMLEFT", 0, -4) - U:SaveTooltipPosition(unit, "BOTTOMLEFT", "TOPLEFT", 0, 3) - elseif anchorPoint == "TOPRIGHT" then - P:Point(button, "TOPRIGHT", anchorFrame, "BOTTOMRIGHT", 0, -4) - U:SaveTooltipPosition(unit, "BOTTOMRIGHT", "TOPRIGHT", 0, 3) - end - else -- left_right - P:Size(anchorFrame, 10, 20) - - if anchorPoint == "BOTTOMLEFT" then - P:Point(button, "BOTTOMLEFT", anchorFrame, "BOTTOMRIGHT", 4, 0) - U:SaveTooltipPosition(unit, "BOTTOMRIGHT", "BOTTOMLEFT", -3, 0) - elseif anchorPoint == "BOTTOMRIGHT" then - P:Point(button, "BOTTOMRIGHT", anchorFrame, "BOTTOMLEFT", -4, 0) - U:SaveTooltipPosition(unit, "BOTTOMLEFT", "BOTTOMRIGHT", 3, 0) - elseif anchorPoint == "TOPLEFT" then - P:Point(button, "TOPLEFT", anchorFrame, "TOPRIGHT", 4, 0) - U:SaveTooltipPosition(unit, "TOPRIGHT", "TOPLEFT", -3, 0) - elseif anchorPoint == "TOPRIGHT" then - P:Point(button, "TOPRIGHT", anchorFrame, "TOPLEFT", -4, 0) - U:SaveTooltipPosition(unit, "TOPLEFT", "TOPRIGHT", 3, 0) + if unitLayout.anchorToParent then + local parent = CUF.unitButtons[unitLayout.parent] + local anchor = unitLayout.anchorPosition --[[@as ParentAnchor]] + + PixelUtil.SetPoint(button, anchor.point, parent, anchor.relativePoint, anchor.offsetX, anchor.offsetY) + else + local x, y + if unit == const.UNIT.TARGET and unitLayout.mirrorPlayer then + x, y = -layout[const.UNIT.PLAYER].position[1], layout[const.UNIT.PLAYER].position[2] + else + x, y = unpack(unitLayout.position) end + + PixelUtil.SetPoint(button, "CENTER", UIParent, "CENTER", x, y) end end @@ -163,8 +47,7 @@ end ---@param unit Unit ---@param kind string? ---@param button CUFUnitButton ----@param anchorFrame CUFAnchorFrame -function U:UpdateUnitButtonLayout(unit, kind, button, anchorFrame) +function U:UpdateUnitButtonLayout(unit, kind, button) local layout = CUF.DB.CurrentLayoutTable() -- Size @@ -179,33 +62,6 @@ function U:UpdateUnitButtonLayout(unit, kind, button, anchorFrame) P:Size(button, width, height) end - -- Anchor points - if not kind or strfind(kind, "arrangement$") then - local anchorPoint - if layout[unit].sameSizeAsPlayer then - anchorPoint = layout[const.UNIT.PLAYER].point - else - anchorPoint = layout[unit].point - end - - -- anchors - local relativePoint - if anchorPoint == "BOTTOMLEFT" then - relativePoint = "TOPLEFT" - elseif anchorPoint == "BOTTOMRIGHT" then - relativePoint = "TOPRIGHT" - elseif anchorPoint == "TOPLEFT" then - relativePoint = "BOTTOMLEFT" - elseif anchorPoint == "TOPRIGHT" then - relativePoint = "BOTTOMRIGHT" - end - - button:ClearAllPoints() - button:SetPoint(anchorPoint, anchorFrame, relativePoint, 0) - - U:UpdateUnitButtonPosition(unit, button, anchorFrame) - end - -- NOTE: SetOrientation BEFORE SetPowerSize if not kind or kind == "barOrientation" then U:SetOrientation(button, Cell.vars.currentLayoutTable.barOrientation[1], @@ -220,42 +76,8 @@ function U:UpdateUnitButtonLayout(unit, kind, button, anchorFrame) end end - -- load position - if not P:LoadPosition(anchorFrame, layout[unit].position) then - P:ClearPoints(anchorFrame) - -- no position, use default - anchorFrame:SetPoint("TOPLEFT", UIParent, "CENTER") - end -end - -------------------------------------------------- --- MARK: Update Menu -------------------------------------------------- - ----@param kind ("lock" | "fadeOut" | "position")? ----@param unit Unit ----@param button CUFUnitButton ----@param anchorFrame CUFAnchorFrame ----@param config CUFConfigButton -function U:UpdateUnitButtonMenu(kind, unit, button, anchorFrame, config) - if not kind or kind == "lock" then - if CellDB.general.locked then - config:RegisterForDrag() - else - config:RegisterForDrag("LeftButton") - end - end - - if not kind or kind == "fadeOut" then - if CellDB.general.fadeOut then - anchorFrame.fadeOut:Play() - else - anchorFrame.fadeIn:Play() - end - end - - if kind == "position" then - U:UpdateUnitButtonPosition(unit, button, anchorFrame) + if not kind or kind == "position" then + U:UpdateUnitButtonPosition(unit, button) end end @@ -459,3 +281,73 @@ function U.UpdateClickCasting(noReload, onlyqueued, which) end CUF:RegisterCallback("UpdateClickCasting", "UpdateClickCasting", U.UpdateClickCasting) + +------------------------------------------------- +-- MARK: Create +------------------------------------------------- + +---@param unit Unit +---@return CUFUnitButton +---@return CUFUnitFrame CUFUnitFrame +local function CreateUnitButton(unit) + local name = CUF.constants.TITLE_CASED_UNITS[unit] + + ---@class CUFUnitFrame: Frame + local frame = CreateFrame("Frame", "CUF_" .. name .. "_Frame", Cell.frames.mainFrame, "SecureFrameTemplate") + + local button = CreateFrame("Button", + "CUF_" .. name, + frame, + "CUFUnitButtonTemplate") --[[@as CUFUnitButton]] + button:SetAttribute("unit", unit) + button:SetPoint("TOPLEFT") + + button.name = name + CUF.unitButtons[unit] = button + + return button, frame +end + +------------------------------------------------- +-- MARK: Register Callbacks +------------------------------------------------- + +-- Register callbacks: UpdateLayout, UpdateVisibility, UpdateUnitButtons +---@param unit Unit +---@param button CUFUnitButton +---@param unitFrame CUFUnitFrame +local function RegisterUnitButtonCallbacks(unit, button, unitFrame) + ---@param kind string? + ---@param which Unit? + local function UpdateLayout(_, kind, which) + if not which or which == unit then + U:UpdateUnitButtonLayout(unit, kind, button) + end + end + CUF:RegisterCallback("UpdateLayout", button.name .. "Frame_UpdateLayout", UpdateLayout) + + ---@param which string? Frame name (unit) + local function UnitFrame_UpdateVisibility(which) + U:UpdateUnitFrameVisibility(which, unit, button, unitFrame) + end + CUF:RegisterCallback("UpdateVisibility", button.name .. "Frame_UpdateVisibility", UnitFrame_UpdateVisibility) + + -- Call all callback functions and do a full update + local function UpdateUnitButtons() + UpdateLayout() + UnitFrame_UpdateVisibility() + end + CUF:RegisterCallback("UpdateUnitButtons", button.name .. "UpdateUnitButtons", UpdateUnitButtons) +end + +------------------------------------------------- +-- MARK: Init +------------------------------------------------- + +-- Initialize unit buttons +function U:InitUnitButtons() + for _, unit in pairs(CUF.constants.UNIT) do + local button, unitFrame = CreateUnitButton(unit) + RegisterUnitButtonCallbacks(unit, button, unitFrame) + end +end diff --git a/UnitFrames/Units.lua b/UnitFrames/Units.lua deleted file mode 100644 index 5fc6074..0000000 --- a/UnitFrames/Units.lua +++ /dev/null @@ -1,91 +0,0 @@ ----@class CUF -local CUF = select(2, ...) - -local Cell = CUF.Cell -local L = CUF.L -local P = Cell.pixelPerfectFuncs - ----@class CUF.uFuncs -local U = CUF.uFuncs - -------------------------------------------------- --- MARK: Create -------------------------------------------------- - ----@param unit Unit ----@param unitFrame CUFUnitFrame ----@return CUFUnitButton -local function CreateUnitButton(unit, unitFrame) - local name = CUF.constants.TITLE_CASED_UNITS[unit] - local button = CreateFrame("Button", - "CUF_" .. name, - unitFrame, - "CUFUnitButtonTemplate") --[[@as CUFUnitButton]] - button:SetAttribute("unit", unit) - button:SetPoint("TOPLEFT") - - button.name = name - CUF.unitButtons[unit] = button - return button -end - -------------------------------------------------- --- MARK: Register Callbacks -------------------------------------------------- - --- Register callbacks: UpdateMenu, UpdateLayout, UpdatePixelPerfect, UpdateVisibility, UpdateUnitButtons ----@param unit Unit ----@param button CUFUnitButton ----@param unitFrame CUFUnitFrame ----@param anchorFrame CUFAnchorFrame ----@param hoverFrame CUFHoverFrame ----@param config CUFConfigButton -local function RegisterUnitButtonCallbacks(unit, button, unitFrame, anchorFrame, hoverFrame, config) - ---@param kind ("lock" | "fadeOut" | "position")? - local function UpdateMenu(kind) - U:UpdateUnitButtonMenu(kind, unit, button, anchorFrame, config) - end - CUF:RegisterCallback("UpdateMenu", button.name .. "Frame_UpdateMenu", UpdateMenu) - - ---@param kind string? - local function UpdateLayout(_, kind) - U:UpdateUnitButtonLayout(unit, kind, button, anchorFrame) - end - CUF:RegisterCallback("UpdateLayout", button.name .. "Frame_UpdateLayout", UpdateLayout) - - local function UpdatePixelPerfect() - P:Resize(unitFrame) - P:Resize(anchorFrame) - config:UpdatePixelPerfect() - end - CUF:RegisterCallback("UpdatePixelPerfect", button.name .. "Frame_UpdatePixelPerfect", UpdatePixelPerfect) - - ---@param which string? Frame name (unit) - local function UnitFrame_UpdateVisibility(which) - U:UpdateUnitFrameVisibility(which, unit, button, unitFrame) - end - CUF:RegisterCallback("UpdateVisibility", button.name .. "Frame_UpdateVisibility", UnitFrame_UpdateVisibility) - - -- Call all callback functions and do a full update - local function UpdateUnitButtons() - UpdateMenu() - UpdateLayout() - UpdatePixelPerfect() - UnitFrame_UpdateVisibility() - end - CUF:RegisterCallback("UpdateUnitButtons", button.name .. "UpdateUnitButtons", UpdateUnitButtons) -end - -------------------------------------------------- --- MARK: Init -------------------------------------------------- - --- Initialize unit buttons -function U:InitUnitButtons() - for _, unit in pairs(CUF.constants.UNIT) do - local unitFrame, anchorFrame, hoverFrame, config = U:CreateBaseUnitFrame(unit) - local button = CreateUnitButton(unit, unitFrame) - - RegisterUnitButtonCallbacks(unit, button, unitFrame, anchorFrame, hoverFrame, config) - end -end diff --git a/Util/Utils.lua b/Util/Utils.lua index 7bea9e7..993c500 100644 --- a/Util/Utils.lua +++ b/Util/Utils.lua @@ -423,6 +423,30 @@ function Util.SetIconZoom(icon, zoomLevel) icon:SetTexCoord(offset, offset + scale, offset, offset + scale) end +------------------------------------------------- +-- MARK: Pixel Perfect +------------------------------------------------- + +--- Calculates the nearest pixel size of a number +---@param number number +---@return number +function Util.GetNearestPixelSize(number) + return PixelUtil.GetNearestPixelSize(number, PixelUtil.GetPixelToUIUnitFactor()) +end + +--- Calculates the relative position of a frame to the center of the UIParent +---@param frame Frame +---@return number, number +function Util.GetPositionRelativeToUIParentCenter(frame) + local uiParentX, uiParentY = UIParent:GetCenter() + local frameX, frameY = frame:GetCenter() + + local relativeX = Util.GetNearestPixelSize(frameX - uiParentX) + local relativeY = Util.GetNearestPixelSize(frameY - uiParentY) + + return relativeX, relativeY +end + ------------------------------------------------- -- MARK: Formatting -------------------------------------------------