Initial commit: AetherBags + KamiToolKit for FC Gitea
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user