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 = "")
{