diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonCloseArgs.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonCloseArgs.cs new file mode 100644 index 000000000..db3e442f8 --- /dev/null +++ b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonCloseArgs.cs @@ -0,0 +1,22 @@ +namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes; + +/// +/// Addon argument data for Close events. +/// +public class AddonCloseArgs : AddonArgs +{ + /// + /// Initializes a new instance of the class. + /// + internal AddonCloseArgs() + { + } + + /// + public override AddonArgsType Type => AddonArgsType.Close; + + /// + /// Gets or sets a value indicating whether the window should fire the callback method on close. + /// + public bool FireCallback { get; set; } +} diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonHideArgs.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonHideArgs.cs new file mode 100644 index 000000000..3e3521bd0 --- /dev/null +++ b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonHideArgs.cs @@ -0,0 +1,32 @@ +namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes; + +/// +/// Addon argument data for Hide events. +/// +public class AddonHideArgs : AddonArgs +{ + /// + /// Initializes a new instance of the class. + /// + internal AddonHideArgs() + { + } + + /// + public override AddonArgsType Type => AddonArgsType.Hide; + + /// + /// Gets or sets a value indicating whether to call the hide callback handler when this hides. + /// + public bool CallHideCallback { get; set; } + + /// + /// Gets or sets the flags that the window will set when it Shows/Hides. + /// + public uint SetShowHideFlags { get; set; } + + /// + /// Gets or sets a value indicating whether something for this event message. + /// + internal bool UnknownBool { get; set; } +} diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonShowArgs.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonShowArgs.cs new file mode 100644 index 000000000..3153d1208 --- /dev/null +++ b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonShowArgs.cs @@ -0,0 +1,27 @@ +namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes; + +/// +/// Addon argument data for Show events. +/// +public class AddonShowArgs : AddonArgs +{ + /// + /// Initializes a new instance of the class. + /// + internal AddonShowArgs() + { + } + + /// + public override AddonArgsType Type => AddonArgsType.Show; + + /// + /// Gets or sets a value indicating whether the window should play open sound effects. + /// + public bool SilenceOpenSoundEffect { get; set; } + + /// + /// Gets or sets the flags that the window will unset when it Shows/Hides. + /// + public uint UnsetShowHideFlags { get; set; } +} diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgsType.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgsType.cs index 9d7815cef..46ee479ac 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonArgsType.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonArgsType.cs @@ -29,4 +29,19 @@ public enum AddonArgsType /// Contains argument data for ReceiveEvent. /// ReceiveEvent, + + /// + /// Contains argument data for Show. + /// + Show, + + /// + /// Contains argument data for Hide. + /// + Hide, + + /// + /// Contains argument data for Close. + /// + Close, } diff --git a/Dalamud/Game/Addon/Lifecycle/AddonEvent.cs b/Dalamud/Game/Addon/Lifecycle/AddonEvent.cs index 5ec57b5e3..3b9c6e867 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonEvent.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonEvent.cs @@ -127,32 +127,80 @@ public enum AddonEvent PostOpen, /// - /// An even that is fired before an addon processes its close method. + /// An even that is fired before an addon processes its Close method. /// PreClose, /// - /// An event that is fired after an addon has processed its close method. + /// An event that is fired after an addon has processed its Close method. /// PostClose, /// - /// An event that is fired before an addon processes its show method. + /// An event that is fired before an addon processes its Show method. /// PreShow, /// - /// An event that is fired after an addon has processed its show method. + /// An event that is fired after an addon has processed its Show method. /// PostShow, /// - /// An event that is fired before an addon processes its hide method. + /// An event that is fired before an addon processes its Hide method. /// PreHide, /// - /// An event that is fired after an addon has processed its hide method. + /// An event that is fired after an addon has processed its Hide method. /// PostHide, + + /// + /// An event that is fired before an addon processes its OnMove method. + /// OnMove is triggered only when a move is completed. + /// + PreMove, + + /// + /// An event that is fired after an addon has processed its OnMove method. + /// OnMove is triggered only when a move is completed. + /// + PostMove, + + /// + /// An event that is fired before an addon processes its MouseOver method. + /// + PreMouseOver, + + /// + /// An event that is fired after an addon has processed its MouseOver method. + /// + PostMouseOver, + + /// + /// An event that is fired before an addon processes its MouseOut method. + /// + PreMouseOut, + + /// + /// An event that is fired after an addon has processed its MouseOut method. + /// + PostMouseOut, + + /// + /// An event that is fired before an addon processes its Focus method. + /// + /// + /// Be aware this is only called for certain popup windows, it is not triggered when clicking on windows. + /// + PreFocus, + + /// + /// An event that is fired after an addon has processed its Focus method. + /// + /// + /// Be aware this is only called for certain popup windows, it is not triggered when clicking on windows. + /// + PostFocus, } diff --git a/Dalamud/Game/Addon/Lifecycle/AddonVirtualTable.cs b/Dalamud/Game/Addon/Lifecycle/AddonVirtualTable.cs index 1ce145946..b92466b5a 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonVirtualTable.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonVirtualTable.cs @@ -20,7 +20,7 @@ internal unsafe class AddonVirtualTable : IDisposable // Copying extra entries is not problematic, and is considered safe. private const int VirtualTableEntryCount = 200; - private const bool EnableLogging = false; + private const bool EnableLogging = true; private static readonly ModuleLog Log = new("LifecycleVT"); @@ -35,9 +35,13 @@ internal unsafe class AddonVirtualTable : IDisposable private readonly AddonRequestedUpdateArgs requestedUpdateArgs = new(); private readonly AddonReceiveEventArgs receiveEventArgs = new(); private readonly AddonArgs openArgs = new(); - private readonly AddonArgs closeArgs = new(); - private readonly AddonArgs showArgs = new(); - private readonly AddonArgs hideArgs = new(); + private readonly AddonCloseArgs closeArgs = new(); + private readonly AddonShowArgs showArgs = new(); + private readonly AddonHideArgs hideArgs = new(); + private readonly AddonArgs onMoveArgs = new(); + private readonly AddonArgs onMouseOverArgs = new(); + private readonly AddonArgs onMouseOutArgs = new(); + private readonly AddonArgs focusArgs = new(); private readonly AtkUnitBase* atkUnitBase; @@ -58,6 +62,10 @@ internal unsafe class AddonVirtualTable : IDisposable private readonly AtkUnitBase.Delegates.Close closeFunction; private readonly AtkUnitBase.Delegates.Show showFunction; private readonly AtkUnitBase.Delegates.Hide hideFunction; + private readonly AtkUnitBase.Delegates.OnMove onMoveFunction; + private readonly AtkUnitBase.Delegates.OnMouseOver onMouseOverFunction; + private readonly AtkUnitBase.Delegates.OnMouseOut onMouseOutFunction; + private readonly AtkUnitBase.Delegates.Focus focusFunction; /// /// Initializes a new instance of the class. @@ -94,6 +102,10 @@ internal unsafe class AddonVirtualTable : IDisposable this.closeFunction = this.OnAddonClose; this.showFunction = this.OnAddonShow; this.hideFunction = this.OnAddonHide; + this.onMoveFunction = this.OnMove; + this.onMouseOverFunction = this.OnMouseOver; + this.onMouseOutFunction = this.OnMouseOut; + this.focusFunction = this.OnFocus; // Overwrite specific virtual table entries this.modifiedVirtualTable->Dtor = (delegate* unmanaged)Marshal.GetFunctionPointerForDelegate(this.destructorFunction); @@ -108,6 +120,10 @@ internal unsafe class AddonVirtualTable : IDisposable this.modifiedVirtualTable->Close = (delegate* unmanaged)Marshal.GetFunctionPointerForDelegate(this.closeFunction); this.modifiedVirtualTable->Show = (delegate* unmanaged)Marshal.GetFunctionPointerForDelegate(this.showFunction); this.modifiedVirtualTable->Hide = (delegate* unmanaged)Marshal.GetFunctionPointerForDelegate(this.hideFunction); + this.modifiedVirtualTable->OnMove = (delegate* unmanaged)Marshal.GetFunctionPointerForDelegate(this.onMoveFunction); + this.modifiedVirtualTable->OnMouseOver = (delegate* unmanaged)Marshal.GetFunctionPointerForDelegate(this.onMouseOverFunction); + this.modifiedVirtualTable->OnMouseOut = (delegate* unmanaged)Marshal.GetFunctionPointerForDelegate(this.onMouseOutFunction); + this.modifiedVirtualTable->Focus = (delegate* unmanaged)Marshal.GetFunctionPointerForDelegate(this.focusFunction); } /// @@ -200,6 +216,9 @@ internal unsafe class AddonVirtualTable : IDisposable this.updateArgs.Addon = addon; this.lifecycleService.InvokeListenersSafely(AddonEvent.PreUpdate, this.updateArgs); + // Note: Do not pass or allow manipulation of delta. + // It's realistically not something that should be needed. + try { this.originalVirtualTable->Update(addon, delta); @@ -321,8 +340,11 @@ internal unsafe class AddonVirtualTable : IDisposable var result = false; this.closeArgs.Addon = thisPtr; + this.closeArgs.FireCallback = fireCallback; this.lifecycleService.InvokeListenersSafely(AddonEvent.PreClose, this.closeArgs); + fireCallback = this.closeArgs.FireCallback; + try { result = this.originalVirtualTable->Close(thisPtr, fireCallback); @@ -342,8 +364,13 @@ internal unsafe class AddonVirtualTable : IDisposable this.LogEvent(EnableLogging); this.showArgs.Addon = thisPtr; + this.showArgs.SilenceOpenSoundEffect = silenceOpenSoundEffect; + this.showArgs.UnsetShowHideFlags = unsetShowHideFlags; this.lifecycleService.InvokeListenersSafely(AddonEvent.PreShow, this.showArgs); + silenceOpenSoundEffect = this.showArgs.SilenceOpenSoundEffect; + unsetShowHideFlags = this.showArgs.UnsetShowHideFlags; + try { this.originalVirtualTable->Show(thisPtr, silenceOpenSoundEffect, unsetShowHideFlags); @@ -361,8 +388,15 @@ internal unsafe class AddonVirtualTable : IDisposable this.LogEvent(EnableLogging); this.hideArgs.Addon = thisPtr; + this.hideArgs.UnknownBool = unkBool; + this.hideArgs.CallHideCallback = callHideCallback; + this.hideArgs.SetShowHideFlags = setShowHideFlags; this.lifecycleService.InvokeListenersSafely(AddonEvent.PreHide, this.hideArgs); + unkBool = this.hideArgs.UnknownBool; + callHideCallback = this.hideArgs.CallHideCallback; + setShowHideFlags = this.hideArgs.SetShowHideFlags; + try { this.originalVirtualTable->Hide(thisPtr, unkBool, callHideCallback, setShowHideFlags); @@ -375,6 +409,82 @@ internal unsafe class AddonVirtualTable : IDisposable this.lifecycleService.InvokeListenersSafely(AddonEvent.PostHide, this.hideArgs); } + private void OnMove(AtkUnitBase* thisPtr) + { + this.LogEvent(EnableLogging); + + this.onMoveArgs.Addon = thisPtr; + this.lifecycleService.InvokeListenersSafely(AddonEvent.PreMove, this.onMoveArgs); + + try + { + this.originalVirtualTable->OnMove(thisPtr); + } + catch (Exception e) + { + Log.Error(e, "Caught exception when calling original OnMove. This may be a bug in the game or another plugin hooking this method."); + } + + this.lifecycleService.InvokeListenersSafely(AddonEvent.PostMove, this.onMoveArgs); + } + + private void OnMouseOver(AtkUnitBase* thisPtr) + { + this.LogEvent(EnableLogging); + + this.onMouseOverArgs.Addon = thisPtr; + this.lifecycleService.InvokeListenersSafely(AddonEvent.PreMouseOver, this.onMouseOverArgs); + + try + { + this.originalVirtualTable->OnMouseOver(thisPtr); + } + catch (Exception e) + { + Log.Error(e, "Caught exception when calling original OnMouseOver. This may be a bug in the game or another plugin hooking this method."); + } + + this.lifecycleService.InvokeListenersSafely(AddonEvent.PostMouseOver, this.onMouseOverArgs); + } + + private void OnMouseOut(AtkUnitBase* thisPtr) + { + this.LogEvent(EnableLogging); + + this.onMouseOutArgs.Addon = thisPtr; + this.lifecycleService.InvokeListenersSafely(AddonEvent.PreMouseOut, this.onMouseOutArgs); + + try + { + this.originalVirtualTable->OnMouseOut(thisPtr); + } + catch (Exception e) + { + Log.Error(e, "Caught exception when calling original OnMouseOut. This may be a bug in the game or another plugin hooking this method."); + } + + this.lifecycleService.InvokeListenersSafely(AddonEvent.PostMouseOut, this.onMouseOutArgs); + } + + private void OnFocus(AtkUnitBase* thisPtr) + { + this.LogEvent(EnableLogging); + + this.focusArgs.Addon = thisPtr; + this.lifecycleService.InvokeListenersSafely(AddonEvent.PreFocus, this.focusArgs); + + try + { + this.originalVirtualTable->Focus(thisPtr); + } + catch (Exception e) + { + Log.Error(e, "Caught exception when calling original OnFocus. This may be a bug in the game or another plugin hooking this method."); + } + + this.lifecycleService.InvokeListenersSafely(AddonEvent.PostFocus, this.focusArgs); + } + [Conditional("DEBUG")] private void LogEvent(bool loggingEnabled, [CallerMemberName] string caller = "") {