Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mouse input Events & deselect on void click #62

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
3 changes: 3 additions & 0 deletions Ktisis/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ public class Configuration : IPluginConfiguration {

public bool DisableChangeTargetOnLeftClick { get; set; } = false;
public bool DisableChangeTargetOnRightClick { get; set; } = false;
public bool DeselectBoneClickVoid { get; set; } = true;
public bool DeselectBoneClickVoidActorPassTrough { get; set; } = true;
public int ClickDuration { get; set; } = 100;

// Overlay

Expand Down
9 changes: 9 additions & 0 deletions Ktisis/Events/EventManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ public static class EventManager {
internal delegate void KeyReleaseEventDelegate(VirtualKey key);
internal static KeyReleaseEventDelegate? OnKeyReleased;

internal delegate bool MousePressedEventDelegate(MouseButton button);
internal static MousePressedEventDelegate? OnMousePressed;

internal delegate bool MouseReleaseEventDelegate(MouseButton button);
internal static MouseReleaseEventDelegate? OnMouseReleased;

internal delegate bool MouseClickEventDelegate(MouseButton button);
internal static MouseClickEventDelegate? OnMouseClicked;

public static void FireOnGposeChangeEvent(ActorGposeState state) {
Logger.Debug($"FireOnGposeChangeEvent {state}");
OnGPoseChange?.Invoke(state);
Expand Down
66 changes: 65 additions & 1 deletion Ktisis/Interface/Input.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,23 @@ internal static bool OnKeyPressed(QueueItem input) {
return false;
}

// Mouse input handling
internal static bool OnMousePressed(MouseButton button) {
return false;
}
internal static bool OnMouseReleased(MouseButton button) {
return false;
}
internal static bool OnMouseClicked(MouseButton button) {

if (Ktisis.Configuration.DeselectBoneClickVoid && button == MouseButton.Left && OverlayWindow.IsGizmoVisible && (Ktisis.Configuration.DeselectBoneClickVoidActorPassTrough || !IsMouseOverTarget()) && !IsMouseOverWindow()) {
OverlayWindow.DeselectGizmo();
return true;
}
return false;
}


internal static void OnKeyReleased(VirtualKey key) {
if (!Ktisis.Configuration.EnableKeybinds || IsChatInputActive())
return;
Expand Down Expand Up @@ -141,6 +158,8 @@ internal static bool IsPurposeUsed(Purpose purpose, VirtualKey input) {
return match;
}



[Serializable]
public enum Purpose {
GlobalModifierKey,
Expand Down Expand Up @@ -179,10 +198,16 @@ public enum Purpose {
public static void Init() {
EventManager.OnKeyPressed += OnKeyPressed;
EventManager.OnKeyReleased += OnKeyReleased;
EventManager.OnMousePressed += OnMousePressed;
EventManager.OnMouseReleased += OnMouseReleased;
EventManager.OnMouseClicked += OnMouseClicked;
}
public static void Dispose() {
EventManager.OnKeyPressed -= OnKeyPressed;
EventManager.OnKeyReleased -= OnKeyReleased;
EventManager.OnMousePressed -= OnMousePressed;
EventManager.OnMouseReleased -= OnMouseReleased;
EventManager.OnMouseClicked -= OnMouseClicked;
}

// Below are the methods and variables needed for Monitor to handle inputs
Expand Down Expand Up @@ -258,5 +283,44 @@ private static void ReadPurposesStates() {
}).ToDictionary(kp => kp.purpose, kp => kp.state);
}
private unsafe static bool IsChatInputActive() => ((UIModule*)Services.GameGui.GetUIModule())->GetRaptureAtkModule()->AtkModule.IsTextInputActive() == 1;

private static readonly List<string> MouseObstructingAddonNames = new() {
{ "CameraSetting"},
{ "ChatLog"},
{ "ChatLogPanel_0" },
{ "ChatLogPanel_1" },
{ "ChatLogPanel_2" },
{ "ChatLogPanel_3" },
{ "GroupPoseGuide" },
{ "GroupPoseStamp" },
// { "GroupPoseStampImage" }, // this seem to take almost all the screen
};
private unsafe static bool IsMouseOverWindow() {

foreach(var addonName in MouseObstructingAddonNames) {
var address = Services.GameGui.GetAddonByName(addonName, 1);
if (address == IntPtr.Zero) continue;

var addon = (FFXIVClientStructs.FFXIV.Component.GUI.AtkUnitBase*)address;
if (addon == null || !addon->IsVisible) continue;

var rootNode = addon->RootNode;
if (rootNode == null || !rootNode->IsVisible) continue;

var right = addon->X;
var left = addon->X + (addon->RootNode->Width * addon->Scale);
var top = addon->Y;
var bottom = addon->Y + (addon->RootNode->Height * addon->Scale);

if (ControlHooks.MouseState.PosX > right
&& ControlHooks.MouseState.PosX < left
&& ControlHooks.MouseState.PosY > top
&& ControlHooks.MouseState.PosY < bottom)
return true;
}
return false;
}
private unsafe static bool IsMouseOverTarget() =>
Services.Targets->MouseOverTarget != null;
}
}
}
12 changes: 12 additions & 0 deletions Ktisis/Interface/Windows/ConfigGui.cs
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,18 @@ public static void DrawInputTab(Configuration cfg) {
if (ImGui.Checkbox(Locale.GetString("Disable_Change_Target_On_Right_Click"), ref disableChangeTargetOnRightClick))
cfg.DisableChangeTargetOnRightClick = disableChangeTargetOnRightClick;

var clickDuration = cfg.ClickDuration;
ImGui.SetNextItemWidth(ImGui.GetFontSize() * 4);
if (ImGui.DragInt(Locale.GetString("Click_duration"), ref clickDuration,0.1f,10,1500,"%.d ms"))
cfg.ClickDuration = clickDuration;

var deselectBoneClickVoid = cfg.DeselectBoneClickVoid;
if (ImGui.Checkbox(Locale.GetString("Deselect_Bone_Click_Void"), ref deselectBoneClickVoid))
cfg.DeselectBoneClickVoid = deselectBoneClickVoid;

var deselectBoneClickVoidActorPassThrough = cfg.DeselectBoneClickVoidActorPassTrough;
if (ImGui.Checkbox(Locale.GetString("Deselect_Bone_Click_Void_Actor_pass_through"), ref deselectBoneClickVoidActorPassThrough))
cfg.DeselectBoneClickVoidActorPassTrough = deselectBoneClickVoidActorPassThrough;

ImGui.Spacing();
ImGui.Text(Locale.GetString("Keyboard_shortcuts"));
Expand Down
65 changes: 64 additions & 1 deletion Ktisis/Interop/Hooks/ControlHooks.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;

using Dalamud.Hooking;
using Dalamud.Game.ClientState.Keys;
Expand All @@ -8,7 +10,11 @@

namespace Ktisis.Interop.Hooks {
internal static class ControlHooks {

public static KeyboardState KeyboardState = new();
public static MouseState MouseState = new();
private static MouseState LastMouseState = new();
private static readonly Dictionary<MouseButton, Stopwatch?> PressingFor = new();

internal unsafe delegate void InputDelegate(InputEvent* input, IntPtr a2, ControllerState* controllerState, MouseState* mouseState, KeyboardState* keyState);
internal static Hook<InputDelegate> InputHook = null!;
Expand All @@ -20,7 +26,64 @@ internal unsafe static void InputDetour(InputEvent* input, IntPtr a2, Controller

try {
if (mouseState != null) {
// TODO
MouseState = *mouseState;
foreach (var button in Enum.GetValues<MouseButton>()) {

if (button == MouseButton.None) continue;

if (!PressingFor.ContainsKey(button))
PressingFor.Add(button, null);

var buttonPressed = (mouseState->Clicked & button) != 0;
if (buttonPressed)
PressingFor[button] = Stopwatch.StartNew();

var isReleased = (LastMouseState.Pressed & button) != 0 && (mouseState->Pressed & button) == 0;
long timePressedMs = -1;
if (isReleased && PressingFor[button] != null) {
timePressedMs = PressingFor[button]!.ElapsedMilliseconds;
PressingFor[button] = null;
}


if (EventManager.OnMouseClicked != null && isReleased && timePressedMs > 0 && timePressedMs < Ktisis.Configuration.ClickDuration) {
var invokeClickedList = EventManager.OnMouseClicked.GetInvocationList();
foreach (var invoke in invokeClickedList) {
if ((bool)invoke.Method.Invoke(invoke.Target, new object[] { button })!) {
mouseState->Clicked &= ~button;
MouseState.Clicked &= ~button;
LastMouseState.Clicked &= ~button;
mouseState->Pressed &= ~button;
MouseState.Pressed &= ~button;
LastMouseState.Pressed &= ~button;
}
}
}

isReleased = (LastMouseState.Pressed & button) != 0 && (mouseState->Pressed & button) == 0; // it may have been altered by OnMouseClicked
if (EventManager.OnMousePressed != null && isReleased) {
var invokePressedList = EventManager.OnMousePressed.GetInvocationList();
foreach (var invoke in invokePressedList) {
if ((bool)invoke.Method.Invoke(invoke.Target, new object[] { button })!) {
// TODO: check if this is enough as Pressed is a constant value
mouseState->Pressed &= ~button;
MouseState.Pressed &= ~button;
}
}
}

buttonPressed = (mouseState->Clicked & button) != 0; // it may have been altered by OnMouseClicked
if (EventManager.OnMousePressed != null && buttonPressed) {
var invokeClickedList = EventManager.OnMousePressed.GetInvocationList();
foreach (var invoke in invokeClickedList) {
if ((bool)invoke.Method.Invoke(invoke.Target, new object[] { button })!) {
mouseState->Clicked &= ~button;
MouseState.Clicked &= ~button;
}
}
}
}
LastMouseState = *mouseState;
}

// Process queue
Expand Down