182 lines
6.4 KiB
C#
182 lines
6.4 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
|
using KamiToolKit.Classes;
|
|
using KamiToolKit.Enums;
|
|
|
|
namespace KamiToolKit;
|
|
|
|
internal class EventHandlerInfo {
|
|
public AtkEventListener.Delegates.ReceiveEvent? OnReceiveEventDelegate;
|
|
public Action? OnActionDelegate;
|
|
}
|
|
|
|
public abstract unsafe partial class NodeBase {
|
|
|
|
private CustomEventListener? nodeEventListener;
|
|
private readonly Dictionary<AtkEventType, EventHandlerInfo> eventHandlers = [];
|
|
|
|
/// <summary>
|
|
/// When true, mousing over this node will show the finger cursor icon.
|
|
/// </summary>
|
|
public bool ShowClickableCursor {
|
|
get => DrawFlags.HasFlag(DrawFlags.ClickableCursor);
|
|
set {
|
|
if (value) {
|
|
DrawFlags |= DrawFlags.ClickableCursor;
|
|
}
|
|
else {
|
|
DrawFlags &= ~DrawFlags.ClickableCursor;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// When true, mousing over this node will show the text input cursor icon.
|
|
/// </summary>
|
|
public bool ShowTextInputCursor {
|
|
get => DrawFlags.HasFlag(DrawFlags.TextInputCursor);
|
|
set {
|
|
if (value) {
|
|
DrawFlags |= DrawFlags.TextInputCursor;
|
|
}
|
|
else {
|
|
DrawFlags &= ~DrawFlags.TextInputCursor;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void AddEvent(AtkEventType eventType, Action callback) {
|
|
nodeEventListener ??= new CustomEventListener(HandleEvents);
|
|
|
|
SetNodeEventFlags(eventType);
|
|
|
|
if (eventHandlers.TryAdd(eventType, new EventHandlerInfo { OnActionDelegate = callback })) {
|
|
Log.Verbose($"[{eventType}] Registered for {GetType()} [{(nint)ResNode:X}]");
|
|
ResNode->AtkEventManager.RegisterEvent(eventType, 0, this, this, nodeEventListener, false);
|
|
}
|
|
else {
|
|
eventHandlers[eventType].OnActionDelegate += callback;
|
|
}
|
|
}
|
|
|
|
public void AddEvent(AtkEventType eventType, AtkEventListener.Delegates.ReceiveEvent callback) {
|
|
nodeEventListener ??= new CustomEventListener(HandleEvents);
|
|
|
|
SetNodeEventFlags(eventType);
|
|
|
|
if (eventHandlers.TryAdd(eventType, new EventHandlerInfo { OnReceiveEventDelegate = callback })) {
|
|
Log.Verbose($"[{eventType}] Registered for {GetType()} [{(nint)ResNode:X}]");
|
|
ResNode->AtkEventManager.RegisterEvent(eventType, 0, this, this, nodeEventListener, false);
|
|
}
|
|
else {
|
|
eventHandlers[eventType].OnReceiveEventDelegate += callback;
|
|
}
|
|
}
|
|
|
|
public void RemoveEvent(AtkEventType eventType) {
|
|
if (nodeEventListener is null) return;
|
|
|
|
if (eventHandlers.Remove(eventType)) {
|
|
Log.Verbose($"[{eventType}] Unregistered from {GetType()} [{(nint)ResNode:X}]");
|
|
ResNode->AtkEventManager.UnregisterEvent(eventType, 0, nodeEventListener, false);
|
|
}
|
|
|
|
// If we have removed the last event, free the event listener
|
|
if (eventHandlers.Keys.Count is 0) {
|
|
nodeEventListener.Dispose();
|
|
nodeEventListener = null;
|
|
}
|
|
}
|
|
|
|
public void RemoveEvent(AtkEventType eventType, Action callback) {
|
|
if (nodeEventListener is null) return;
|
|
|
|
if (eventHandlers.TryGetValue(eventType, out var handler)) {
|
|
handler.OnActionDelegate -= callback;
|
|
|
|
if (handler.OnReceiveEventDelegate is null && handler.OnActionDelegate is null) {
|
|
RemoveEvent(eventType);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void RemoveEvent(AtkEventType eventType, AtkEventListener.Delegates.ReceiveEvent callback) {
|
|
if (nodeEventListener is null) return;
|
|
|
|
if (eventHandlers.TryGetValue(eventType, out var handler)) {
|
|
handler.OnReceiveEventDelegate -= callback;
|
|
|
|
if (handler.OnReceiveEventDelegate is null && handler.OnActionDelegate is null) {
|
|
RemoveEvent(eventType);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void DisposeEvents() {
|
|
if (nodeEventListener is not null) {
|
|
ResNode->AtkEventManager.UnregisterEvent(AtkEventType.UnregisterAll, 0, nodeEventListener, false);
|
|
}
|
|
|
|
eventHandlers.Clear();
|
|
|
|
nodeEventListener?.Dispose();
|
|
nodeEventListener = null;
|
|
}
|
|
|
|
private void HandleEvents(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) {
|
|
try {
|
|
if (!IsVisible) return;
|
|
|
|
if (eventHandlers.TryGetValue(eventType, out var handler)) {
|
|
|
|
foreach (var noArgHandler in Delegate.EnumerateInvocationList(handler.OnActionDelegate)) {
|
|
try {
|
|
noArgHandler();
|
|
}
|
|
catch (Exception e) {
|
|
Log.Exception(e);
|
|
}
|
|
}
|
|
|
|
foreach (var argHandler in Delegate.EnumerateInvocationList(handler.OnReceiveEventDelegate)) {
|
|
try {
|
|
argHandler(thisPtr, eventType, eventParam, atkEvent, atkEventData);
|
|
}
|
|
catch (Exception e) {
|
|
Log.Exception(e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception e) {
|
|
Log.Exception(e);
|
|
}
|
|
}
|
|
|
|
private void SetNodeEventFlags(AtkEventType eventType) {
|
|
switch (eventType) {
|
|
// Hover events need to propagate down to trigger various timelines
|
|
case AtkEventType.MouseOver:
|
|
case AtkEventType.MouseOut:
|
|
case AtkEventType.MouseWheel:
|
|
AddNodeFlags(NodeFlags.EmitsEvents, NodeFlags.RespondToMouse);
|
|
break;
|
|
|
|
// Any kind of direct interaction should be a blocking event
|
|
// set HasCollision to prevent events from propagating
|
|
case AtkEventType.MouseDown:
|
|
case AtkEventType.MouseUp:
|
|
case AtkEventType.MouseMove:
|
|
case AtkEventType.MouseClick:
|
|
AddNodeFlags(NodeFlags.EmitsEvents, NodeFlags.RespondToMouse, NodeFlags.HasCollision);
|
|
break;
|
|
|
|
// ButtonClick is mostly used as an event that native calls back to, when interacting with buttons
|
|
// We do not want to re-emit, or block events in this case
|
|
case AtkEventType.ButtonClick:
|
|
break;
|
|
}
|
|
}
|
|
}
|