ActionbarPlus

ActionbarPlus

78.7k Downloads

Shift modifer bind not properly working with

dabadoo opened this issue ยท 21 comments

commented

Describe the bug
Binds made using a shift modfier such as: "Shift-H" sometimes don't work on keypress down and only responds to keypress up.
Sometimes they do work. Sometimes they don't.

Maybe an issue related to solution for:

#26
#27

commented

Here's an update: I have this fixed for all versions WOTLK, Retail and classic era. Just running regressions on the new code now to be sure. Will check-in and release in about a day or two.

commented

@nfet
Quick test on SoD Classic.

At first it seemed to work. Pressing Shift-H worked on keydown.
I tried moving and spamming random buttons for a bit and suddenly it no longer works. I press shift-H and now it doesn't activate until key up.

Even bigger issue: The moment I press shift down the entire game freezes for a second then unfreezes again. Releasing shift doesn't cause any issue.

commented

Some more info but don't know how useful it will be:

  1. Logged on a level 1 char and disabled all addons except this one. No issues with pressing shift.
  2. Changed to my main profile. Started freezing when pressing shift.
  3. Changed back to the default profile and the issue was gone.
commented

That is so sad. I have archived that version (2024.3.7) from curseforge for now while I investigate. I will try and reproduce with a clean profile then with an existing profile.

Thank you for verifying. You are sincerely appreciated.

Also for the standard WoW Action Button behavior, I just wanted to confirm that you are getting the same standard behavior. The following details is what I experience in WoW actionbars.

Here's an example screenshot for visual aid

image

For this purpose, let's say I have priest class with Renew on Blizzard Actionbar1 slot 1 (keyboard shortcut 1).

1. "Lock Action Bars" is checked

Keypress Behavior

  • When 1 is pressed, Renew will trigger on keydown (EDIT: If all addons are disabled, this will trigger on keyup). ActionbarPlus changed this behavior.)
  • When the modifier key and 1 (i.e. SHIFT + 1) is held down, then the actionbutton triggers on keyup. This is because it also trigger the "unlock actionbar" feature so that the spell won't trigger on mouse drag.

Mouse Click Behavior

  • When clicking the WoW action button, Renew always triggers on mouseup. This is different from ActionbarPlus, where Renew will trigger on mousedown unless the modifier key is pressed, where it then triggers on keyup.

2. "Lock Action Bars" is not checked (disabled)

Keypress Behavior

  • When 1 is pressed, Renew will now trigger on keyup. This is so that the action will not trigger on mouse drag.

Mouse Click Behavior

  • Same as Keypress Behavior, triggers on keyup

ActionbarPlus action buttons behavior

ABP will esssentially behave (should behave) the same way except it will trigger on mousedown when an action is pressed. This is what I'm aiming for anyways. Also, for retail, it doesn't support the "lock and hold down" action.

commented

I think for this part, it's conditional. Please see my recent message on the button behavior.
If the "modifier key" + key is pressed, then action triggers on key-up. Holding the modifier key triggers the "unlock actionbar" so that the action can be clicked and dragged.

I disabled all the addons and the blizzard actionbars always triggers on keyup/mouseup. The side-effect of having actionbarplus enabled is that the mouse click triggers on keydown :-).

Expected Behavior:

Action button triggers "On Keydown" without delay

Also, I disabled all the addons except the debug addons and I couldn't reproduce the freeze on an existing actionbarplus profile. The "Addon Usage" addon reported only CPU usage of 1.5. This doesn't really take ABP off the hook but I will keep investigating.

image

I'm able to repro this on Retail:

Repro steps:

  1. Bind and action "Shift + key" and "Control + key" (same key)
  2. Drag and drop a spell into the same action slot
  3. Press Control + key (works as expected)
  4. Press Shift + key (I have to press 1+ consecutively) for it to trigger
    4a. On a real spell, it may instantly work afterwards
    4b. On a macro like /cast <spell>, it consistently behaves bad (2+ consecutive clicks) for it to trigger

Expected Behavior:

Action button triggers "On Keydown" without delay

commented

For this part this is the correct behavior if "shift" is the assigned modifier key.

I press shift-H and now it doesn't activate until key up.

commented

Some more info but don't know how useful it will be:

  1. Logged on a level 1 char and disabled all addons except this one. No issues with pressing shift.
  2. Changed to my main profile. Started freezing when pressing shift.
  3. Changed back to the default profile and the issue was gone.

Please let me know if this version is OK to be up on CurseForge, otherwise I will have to revert to the old behavior. I like this change now because it is consistent with all versions of WoW, but I don't want to risk potential bad freezing experience to others. Thank you again.

commented

Here's the priest macro I tested with on SoD:

Keybind: ]

shift + ] or ctrl + ] is not keybinded, just ] (right square bracket)

/cast [harm] Shadow Word: Pain; [mod:ctrl] Lesser Heal; [mod:shift] Penance; Renew
commented

Just note to self here. Upon initial investigation Addon Usage did not show any CPU or memory spike. For this reason, I need more details on how to repro, so I made version 2024.3.7 active again on Curse Forge in the hope that someone will run into this issue and get another detail on this.

The work around is that the user/player can revert to the older version.

commented

Tested using version 2024.3.7

  1. "Lock Action Bars" is checked
    Keypress Behavior

When 1 is pressed, Renew will trigger on keydown (EDIT: If all addons are disabled, this will trigger on keyup). ActionbarPlus changed this behavior.)

When the modifier key and 1 (i.e. SHIFT + 1) is held down, then the actionbutton triggers on keyup. This is because it also trigger the "unlock actionbar" feature so that the spell won't trigger on mouse drag.

For me when I disable all addons, use default blizzard actionbars and have locked them with shift key enabled to drag.

  1. When 2 is pressed Lesser Heal triggers on keydown
  2. When the modifier key and 2 (i.e. SHIFT + 2) is held down my actionbutton still triggers on keydown.

I tried deleting my account#1 config-cache and got same results as my previous testing.

I tried testing the behaivior again with only this addon enabled and Lesser heal placed on default actionbars.

  1. When 2 is pressed Lesser Heal triggers on keydown
  2. When the modifier key and 2 (i.e. SHIFT + 2) is held down my actionbutton now triggers on keyup.

I did some test with addonusage enabled and commented on my findings.
https://imgur.com/a/5qsG9DZ

Would it help if I gave you my profile?
https://pastebin.com/x7i2c5gu
The name of the profile causing issues is the "MyDefault" The binded macro I've been using, placed on the addon, is Shift-H for the macro "/use Supercharged Chronoboon Displacer\n/use Chronoboon Displacer\n"

commented

Give this new version a try (Edit: 2024.3.10) when you can. I'll add more details on the fix in a bit.

Edit: 2024.3.10
https://legacy.curseforge.com/wow/addons/actionbarplus/files/

commented

Thanks for the Addon Usage details and images.  This will get me far.  Seems like ABP is not playing nice with others.  No need to get your profile for now.  I'll will try to repro.

Cheers,

commented

OK, I found a solution that makes all version (EDIT: all version of WoW) happy. Just running regressions now. Should be released by today.

commented

Testing on SoD. Macro is not working when Shift-H pressed down.

The Freeze issue is much better. No longer getting second long freezes but there still seems to be some observable stuttering every time I press shift.

https://imgur.com/a/bCMXOCH

Spamming shift and nothing else still makes the addon shoot up in memory and cpu usage.

commented

The cpu and mem are at the acceptable range. I will keep optimizing. But for the rest of the issues I am out of options at this point. I will have to get back to this sometime in the future. I apologize for the inconvenience.

commented

I'm able to repro this on Retail:

Repro steps:

  1. Bind and action "Shift + key" and "Control + key" (same key)
  2. Drag and drop a spell into the same action slot
  3. Press Control + key (works as expected)
  4. Press Shift + key (I have to press 1+ consecutively) for it to trigger
    4a. On a real spell, it may instantly work afterwards
    4b. On a macro like /cast <spell>, it consistently behaves bad (2+ consecutive clicks) for it to trigger

Expected Behavior:

Action button triggers "On Keydown" without delay

commented

Some more testing:

Pressing a bind that's NOT a shift bind will still sometimes not work while pressing shift and the bind.
Example: I press F to Nature's Swiftness. On blizzard bars, if I held shift while pressing F, it would still use my F bind since nothing else is bound to it. Keydown works without issue.
When having bound F on a ABP bar it still works whether or not I hold shift while pressing F, BUT it doesn't always trigger on keyDown as it should.

commented

I can make changes that fixes my keybinds to work with shift on keydown but then shift-clicking to move breaks and starts using items on Keydown when trying to drag with mouse.

commented

So far it seems like I've managed to find a solution that makes it work very similair to the blizzard actionbars.
Keybinds trigger on down portion, as they should, no matter the modifer. Mouse clicking icons trigger on the up portion.
0 issues with accidentally using CDs/Consumables when moving action buttons around so far.
Hovering over an icon and pressing the relevant keybind on your keyboard unfortunately still triggers on the up portion instead of the down portion.
I can't really see how this would be a big issue for most players. Why hover over the bind and then choose to press the keybind?

This is the code for ButtonUI.lua. I made it together with chatgpt since I don't really understand much. So if there is something silly in here... I won't be able to explain why ๐Ÿ˜„

`
--[[-----------------------------------------------------------------------------
WoW Vars
-------------------------------------------------------------------------------]]
local GetCursorInfo, ClearCursor, CreateFrame, UIParent = GetCursorInfo, ClearCursor, CreateFrame, UIParent
local InCombatLockdown, GameFontHighlightSmallOutline = InCombatLockdown, GameFontHighlightSmallOutline
local C_Timer, C_PetJournal = C_Timer, C_PetJournal

--[[-----------------------------------------------------------------------------
LUA Vars
-------------------------------------------------------------------------------]]
local tostring, format, strlower, tinsert = tostring, string.format, string.lower, table.insert

--[[-----------------------------------------------------------------------------
Local Vars
-------------------------------------------------------------------------------]]
--- @type Namespace
local _, ns = ...
local pformat = ns.pformat
local O, GC, M, LibStub = ns.O, ns.O.GlobalConstants, ns.M, ns.O.LibStub

local AO = O.AceLibFactory:A()
local AceEvent, AceGUI, AceHook = AO.AceEvent, AO.AceGUI, AO.AceHook

local String = O.String
local A, P, PH = O.Assert, O.Profile, O.PickupHandler

local WMX, ButtonMX = O.WidgetMixin, O.ButtonMixin
local E, WAttr = GC.E, GC.WidgetAttributes

local IsBlank = String.IsBlank
local AssertThatMethodArgIsNotNil = A.AssertThatMethodArgIsNotNil

--[[-----------------------------------------------------------------------------
New Instance
-------------------------------------------------------------------------------]]
--- @Class ButtonUIWidgetBuilder : WidgetMixin
local _B = LibStub:NewLibrary(M.ButtonUIWidgetBuilder)

--- @Class ButtonUILib
local _L = LibStub:NewLibrary(M.ButtonUI, 1)
local p = O.LogFactory:NewLogger(M.ButtonUI)

--- @return ButtonUIWidgetBuilder
function _L:WidgetBuilder() return _B end

--[[-----------------------------------------------------------------------------
Scripts
-------------------------------------------------------------------------------]]
--- @param cursorInfo CursorInfo
local function IsValidDragSource(cursorInfo)
--p:log("IsValidDragSource| CursorInfo=%s", cursorInfo)
if not cursorInfo or IsBlank(cursorInfo.type) then
-- This can happen if a chat tab or others is dragged into
-- the action bar.
--p:log(20, 'Received drag event with invalid cursor info. Skipping...')w
return false
end
return O.ReceiveDragEventHandler:IsSupportedCursorType(cursorInfo)
end

---TODO: See the following implementation to mimic keydown
--- - https://wowpedia.fandom.com/wiki/CVar_ActionButtonUseKeyDown
--- - https://www.wowinterface.com/forums/showthread.php?t=58768
--- @param widget ButtonUIWidget
--- @param down boolean true if the press is KeyDown
local function RegisterForClicks(widget, event, down)
local useKeyDown = GetCVarBool("ActionButtonUseKeyDown")
local btn = widget.button()

if E.ON_LEAVE == event then
    if useKeyDown then
        btn:RegisterForClicks('AnyDown')
    else
        btn:RegisterForClicks('AnyUp')
    end
elseif E.ON_ENTER == event then
    if useKeyDown then
        --- Note: Macro will not trigger on first click if Drag Key is used in 'mod:<key>' in macros
        --- Macros should not use mod:<key> on the same drag key
        btn:RegisterForClicks('AnyUp')
    else
        btn:RegisterForClicks('AnyUp')
    end
elseif E.MODIFIER_STATE_CHANGED == event or 'PreClick' == event or 'PostClick' == event then
    if useKeyDown then
        if down then
            btn:RegisterForClicks('AnyDown') -- Register for down press for all keybound action buttons
        else
            btn:RegisterForClicks('AnyUp')   -- Register for up press for all keybound action buttons
        end
    else
        btn:RegisterForClicks('AnyUp')
    end
end

end

--- @param btn ButtonUI
--- @param key string The key clicked
--- @param down boolean true if the press is KeyDown
local function OnPreClick(btn, key, down)
local w = btn.widget
w:SendMessage(GC.M.OnButtonPreClick, w)
if w:IsBattlePet() and C_PetJournal then
w:SendMessage(GC.M.OnButtonClickBattlePet, w)
return
elseif w:IsEquipmentSet() then
w:SendMessage(GC.M.OnButtonClickEquipmentSet, w)
return
else
w:UpdateRangeIndicator()
end
-- This prevents the button from being clicked
-- on sequential drag-and-drops (one after another)
if PH:IsPickingUpSomething(btn) then btn:SetAttribute("type", "empty") end
RegisterForClicks(w, 'PreClick', down)
end

--- @param btn ButtonUI
--- @param key string The key clicked
--- @param down boolean true if the press is KeyDown
local function OnPostClick(btn, key, down)
local w = btn.widget
w:SendMessage(GC.M.OnButtonPostClick, w)

---@param handlerFn ButtonHandlerFunction
local function CallbackFn(handlerFn) O.ActionbarPlusAPI:UpdateM6Macros(handlerFn) end
w:SendMessage(GC.M.OnButtonPostClickExt, ns.M.ButtonUI, CallbackFn)

-- This prevents the button from being clicked
-- on sequential drag-and-drops (one after another)
RegisterForClicks(w, 'PreClick', down)

end

--- @param btnUI ButtonUI
local function OnDragStart(btnUI)
if InCombatLockdown() then return end
--- @type ButtonUIWidget
local w = btnUI.widget
if w:IsEmpty() then return end

if InCombatLockdown() or not WMX:IsDragKeyDown() then return end
w:Reset()
p:log(20, 'DragStarted| Actionbar-Info: %s', pformat(btnUI.widget:GetActionbarInfo()))

PH:Pickup(btnUI.widget)

w:SetButtonAsEmpty()
w:ShowEmptyGrid()
w:ShowKeybindText(true)
w:Fire('OnDragStart')

end

--- Used with button:RegisterForDrag('LeftButton')
--- @param btnUI ButtonUI
local function OnReceiveDrag(btnUI)
if InCombatLockdown() then return end
AssertThatMethodArgIsNotNil(btnUI, 'btnUI', 'OnReceiveDrag(btnUI)')
local cursorUtil = ns:CreateCursorUtil()
if not cursorUtil:IsValid() then
p:log(20, 'OnReceiveDrag| CursorInfo: %s isValid: false', pformat:B()(cursorUtil:GetCursor()))
return false
else
p:log(20, 'OnReceiveDrag| CursorInfo: %s', pformat:B()(cursorUtil:GetCursor()))
end
ClearCursor()

--- @type ReceiveDragEventHandler
O.ReceiveDragEventHandler:Handle(btnUI, cursorUtil)

btnUI.widget:Fire('OnReceiveDrag')

end

---Triggered by SetCallback('event', fn)
--- @param widget ButtonUIWidget
local function OnReceiveDragCallback(widget) widget:UpdateStateDelayed(0.01) end

--- @param button Frame The action button frame
--- @return boolean true if the mouse is over the action button, false otherwise
local function IsMouseOverActionButton(button)
local mouseX, mouseY = GetCursorPosition()
local scale = UIParent:GetEffectiveScale()
local buttonX, buttonY = button:GetCenter()

if not buttonX or not buttonY then
    return false
end

local buttonWidth = button:GetWidth()
local buttonHeight = button:GetHeight()

buttonX = buttonX * scale
buttonY = buttonY * scale

return mouseX >= (buttonX - buttonWidth / 2) and mouseX <= (buttonX + buttonWidth / 2)
       and mouseY >= (buttonY - buttonHeight / 2) and mouseY <= (buttonY + buttonHeight / 2)

end

--- @param widget ButtonUIWidget
--- @param event string
--- @param mouseButtonPressed string LMOUSECLICK, etc...
--- @param down boolean 1 or true if the press is KeyDown
local function OnModifierStateChanged(widget, event, mouseButtonPressed, down)
if IsMouseOverActionButton(widget.button()) then
-- If the mouse is over the action button, ignore modifier key presses
RegisterForClicks(widget, E.MODIFIER_STATE_CHANGED)
else
-- If the mouse is not over the action button, handle modifier key presses
RegisterForClicks(widget, E.MODIFIER_STATE_CHANGED, down)
if widget:IsMacro() then
if mouseButtonPressed == "LeftButton" and IsModifierKeyDown() then
widget:RegisterForClicks('AnyDown') -- Register for down press for left mouse button when modifiers are active
else
widget:UpdateMacroState()
end
end
end
end

--- @param widget ButtonUIWidget
local function OnBeforeEnter(widget)
RegisterForClicks(widget, E.ON_ENTER)
widget:RegisterEvent(E.MODIFIER_STATE_CHANGED, OnModifierStateChanged, widget)

-- handle stuff before event
--- @param down boolean true if the press is KeyDown
--widget:RegisterEvent(E.MODIFIER_STATE_CHANGED, function(w, event, key, down)
--    RegisterForClicks(w, E.MODIFIER_STATE_CHANGED, down)
--end, widget)

end
--- @param widget ButtonUIWidget
local function OnBeforeLeave(widget)
--RegisterMacroEvent(widget)
if not widget:IsMacro() then
--widget:RegisterEvent(E.MODIFIER_STATE_CHANGED, OnModifierStateChanged, widget)
widget:UnregisterEvent(E.MODIFIER_STATE_CHANGED)
end
RegisterForClicks(widget, E.ON_LEAVE)
end
--- @param btn ButtonUI
local function OnEnter(btn)
OnBeforeEnter(btn.widget)
---Receiver will get a func(widget, event) {}
btn.widget:Fire(E.ON_ENTER)
end
--- @param btn ButtonUI
local function OnLeave(btn)
OnBeforeLeave(btn.widget)
---Receiver will get a func(widget, event) {}
btn.widget:Fire(E.ON_LEAVE)
end

local function OnClick_SecureHookScript(btn, mouseButton, down)
--p:log(20, 'SecureHookScript| Actionbar: %s', pformat(btn.widget:GetActionbarInfo()))
btn:RegisterForClicks(WMX:IsDragKeyDown() and 'AnyUp' or 'AnyDown')
if not PH:IsPickingUpSomething() then return end
OnReceiveDrag(btn)
end

--- @param widget ButtonUIWidget
--- @param event string Event string
local function OnUpdateButtonCooldown(widget, event)
if widget:IsNotUpdatable() then return end

widget:UpdateCooldown()
local cd = widget:GetCooldownInfo();
if (cd == nil or cd.icon == nil) then return end
widget:SetCooldownTextures(cd.icon)

end

--- @param widget ButtonUIWidget
--- @param event string Event string
local function OnSpellUpdateUsable(widget, event)
if widget:IsNotUpdatable() then return end
widget:UpdateRangeIndicator()
widget:UpdateUsable()
widget:UpdateGlow()
end

--- @param widget ButtonUIWidget
--- @param event string
local function OnPlayerControlLost(widget, event, ...)
if not widget:IsHideWhenTaxi() then return end
C_Timer.After(1, function()
local playerOnTaxi = UnitOnTaxi(GC.UnitId.player)
p:log(10, 'Player on Taxi: %s [%s]', playerOnTaxi, GetTime())
if playerOnTaxi ~= true then return end
WMX:ShowActionbarsDelayed(false, 1)
end)
end

--- @param widget ButtonUIWidget
--- @param event string
local function OnPlayerControlGained(widget, event, ...)
if not widget:IsHideWhenTaxi() then return end
WMX:ShowActionbarsDelayed(true, 2)
end

--- @see "UnitDocumentation.lua"
--- @param widget ButtonUIWidget
--- @param event string
local function OnPlayerTargetChanged(widget, event)
if widget:IsNotUpdatable() then return end
widget:UpdateRangeIndicator()
end

--- @see "UnitDocumentation.lua"
--- @param widget ButtonUIWidget
--- @param event string
local function OnPlayerTargetChangedDelayed(widget, event)
C_Timer.After(0.1, function() OnPlayerTargetChanged(widget, event) end)
end

---@param widget ButtonUIWidget
local function OnPlayerStoppedMoving(widget, event)
--if widget:IsNotUpdatable() then return end
--p:log('moving-stopped[%s]: %s', widget:GN(), GetTime())
OnPlayerTargetChangedDelayed(widget, event)
end
--[[-----------------------------------------------------------------------------
Support Functions
-------------------------------------------------------------------------------]]
--- @param widget ButtonUIWidget
--- @param name string The widget name.
local function RegisterWidget(widget, name)
assert(widget ~= nil)
assert(name ~= nil)

local WidgetBase = AceGUI.WidgetBase
widget.userdata = {}
widget.events = {}
local mt = {
    __tostring = function() return name  end,
    __index = WidgetBase
}
setmetatable(widget, mt)

end

--- @param button ButtonUI
local function RegisterScripts(button)
AceHook:SecureHookScript(button, 'OnClick', OnClick_SecureHookScript)

button:SetScript("PreClick", OnPreClick)
button:SetScript("PostClick", OnPostClick)

button:SetScript('OnDragStart', OnDragStart)
button:SetScript('OnReceiveDrag', OnReceiveDrag)
button:SetScript(E.ON_ENTER, OnEnter)
button:SetScript(E.ON_LEAVE, OnLeave)

end

--- @param widget ButtonUIWidget
local function RegisterSpellUpdateUsable(widget)
if not ns:IsVanilla() then
widget:RegisterEvent(E.SPELL_UPDATE_USABLE, OnSpellUpdateUsable, widget)
else
-- In Vanilla, SPELL_UPDATE_USABLE does not fire very often
widget:RegisterBucketEvent({ E.SPELL_UPDATE_USABLE, E.ACTIONBAR_UPDATE_USABLE }, 0.1, function(units)
OnSpellUpdateUsable(widget)
end);
end
end

--- see: Interface_[Vanilla|TBC|etc.]/FrameXML/Constants.lua
--- ClassicExpansionAtLeast(LE_EXPANSION_CLASSIC)
--- ClassicExpansionAtLeast(LE_EXPANSION_BURNING_CRUSADE)
--- @param widget ButtonUIWidget
local function RegisterUpdateRangeIndicatorOnSpellCast(widget)
if not GC.F.ENABLE_RANGE_INDICATOR_UPDATE_ON_SPELLCAST then return end
local bucketEvents = { E.UNIT_SPELLCAST_SENT, E.UNIT_SPELLCAST_FAILED }
widget:RegisterBucketEvent(bucketEvents, 0.5, function(units)
if not units.player then return end
if widget:IsHidden() or O.API:HasTarget() ~= true then return end
local spell, ranged = widget:GetEffectiveRangedSpellName()
if ranged == false then return end
widget:UpdateRangeIndicatorBySpell(spell)
end, widget)
end

--- @param widget ButtonUIWidget
local function RegisterCallbacks(widget)

-- TODO Next: Tracks changing spells such as Covenant abilities in Shadowlands.
widget:RegisterEvent(E.SPELL_UPDATE_COOLDOWN, OnUpdateButtonCooldown, widget)
widget:RegisterEvent(E.PLAYER_CONTROL_LOST, OnPlayerControlLost, widget)
widget:RegisterEvent(E.PLAYER_CONTROL_GAINED, OnPlayerControlGained, widget)
widget:RegisterEvent(E.MODIFIER_STATE_CHANGED, OnModifierStateChanged, widget)
widget:RegisterEvent(E.PLAYER_STOPPED_MOVING, OnPlayerStoppedMoving, widget)
RegisterSpellUpdateUsable(widget)
RegisterUpdateRangeIndicatorOnSpellCast(widget)

-- Callbacks (fired via Ace Events)
widget:SetCallback(E.ON_RECEIVE_DRAG, OnReceiveDragCallback)

--- @param w ButtonUIWidget
widget:SetCallback("OnEnter", function(w)
    if InCombatLockdown() then return end
    if not GetCursorInfo() then return end
    w:SetHighlightEmptyButtonEnabled(true)
end)
widget:SetCallback("OnLeave", function(w)
    if InCombatLockdown() then return end
    if not GetCursorInfo() then return end
    w:SetHighlightEmptyButtonEnabled(false)
end)

end

--[[-----------------------------------------------------------------------------
Builder Methods
-------------------------------------------------------------------------------]]

---Creates a new ButtonUI
--- @param dragFrameWidget FrameWidget The drag frame this button is attached to
--- @param rowNum number The row number
--- @param colNum number The column number
--- @param btnIndex number The button index number
--- @return ButtonUIWidget
function _B:Create(dragFrameWidget, rowNum, colNum, btnIndex)

local btnName = GC:ButtonName(dragFrameWidget.index, btnIndex)

--- @class __ButtonUI
local button = CreateFrame("Button", btnName, UIParent, GC.C.SECURE_ACTION_BUTTON_TEMPLATE)
--- @alias ButtonUI __ButtonUI|_Button

--local button = CreateFrame("Button", btnName, UIParent, "SecureActionButtonTemplate,SecureHandlerBaseTemplate")
button.text = WMX:CreateFontString(button)
button.indexText = WMX:CreateIndexTextFontString(button)
button.keybindText = WMX:CreateKeybindTextFontString(button)
button.nameText = WMX:CreateNameTextFontString(button)
RegisterScripts(button)

-- todo next: add ActionButtonUseKeyDown to options UI; add to abp_info
--            iterate through all buttons and call #RegisterForClicks()
-- /run SetCVar("ActionButtonUseKeyDown", 1)
-- /run SetCVar("ActionButtonUseKeyDown", 0)
-- /dump GetCVarBool("ActionButtonUseKeyDown")

button:RegisterForDrag("LeftButton", "RightButton");
button:RegisterForClicks("AnyDown", "AnyUp");

--- see: Interface/AddOns/Blizzard_APIDocumentationGenerated/CooldownFrameAPIDocumentation.lua
--- @class CooldownFrame : _CooldownFrame
local cooldown = CreateFrame("Cooldown", btnName .. 'Cooldown', button,  "CooldownFrameTemplate")
cooldown:SetAllPoints(button)
cooldown:SetSwipeColor(1, 1, 1)
cooldown:SetCountdownFont(GameFontHighlightSmallOutline:GetFont())
cooldown:SetDrawEdge(true)
cooldown:SetEdgeScale(0.0)
cooldown:SetHideCountdownNumbers(false)
cooldown:SetUseCircularEdge(false)
cooldown:SetPoint('CENTER')

--- @alias ButtonUIWidget __ButtonUIWidget | BaseLibraryObject_WithAceEvent
--- @class __ButtonUIWidget : ButtonMixin
local __widget = {
    --- @type fun() : ActionbarPlus
    addon = function() return ABP end,
    --- @type number
    index = btnIndex,
    --- @type number
    frameIndex = dragFrameWidget:GetIndex(),
    --- @type string
    buttonName = btnName,
    --- @type fun() : FrameWidget
    dragFrame = function() return dragFrameWidget end,
    --- @type fun() : ButtonUI
    button = function() return button  end,
    --- @type fun() : CooldownFrame
    cooldown = function() return cooldown end,
    --- @type table
    cooldownInfo = nil,
    ---Don't make this 'LOW'. ElvUI AFK Disables it after coming back from AFK
    --- @type string
    frameStrata = dragFrameWidget.frameStrata or 'MEDIUM',
    frameLevel = (dragFrameWidget.frameLevel + 100) or 100,
    --- @type number
    buttonPadding = 1,
    placement = { rowNum = rowNum, colNum = colNum },
}
--- @type ButtonUIWidget
local widget = __widget

button.widget, cooldown.widget = widget, widget

AceEvent:Embed(widget)
ns:AceBucketEmbed(widget)

ButtonMX:Mixin(widget)

RegisterWidget(widget, btnName .. '::Widget')
RegisterCallbacks(widget)

widget:InitWidget()

return widget

end

`

commented

Hi There,
Please give this another try and see if it is fixed on #344.

commented

Tried latest version but the issue still persist. @kapresoft