f37369cdda
Co-authored-by: Cursor <cursoragent@cursor.com>
441 lines
15 KiB
C#
441 lines
15 KiB
C#
using HSUI.Config;
|
|
using HSUI.Interface.GeneralElements;
|
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
|
using Dalamud.Bindings.ImGui;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.Numerics;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
namespace HSUI.Helpers
|
|
{
|
|
public class ClipRectsHelper
|
|
{
|
|
#region Singleton
|
|
private ClipRectsHelper()
|
|
{
|
|
ConfigurationManager.Instance.ResetEvent += OnConfigReset;
|
|
OnConfigReset(ConfigurationManager.Instance);
|
|
|
|
// other plugins can add clip rects for HSUI
|
|
// rect start point = vector.X, vector.Y
|
|
// rect end point = vector.Z, vector.W
|
|
_thirdPartyClipRects = Plugin.PluginInterface.GetOrCreateData<Dictionary<string, Vector4>>(_sharedDataId, () => new());
|
|
}
|
|
|
|
public static void Initialize() { Instance = new ClipRectsHelper(); }
|
|
|
|
public static ClipRectsHelper Instance { get; private set; } = null!;
|
|
|
|
~ClipRectsHelper()
|
|
{
|
|
Dispose(false);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
protected void Dispose(bool disposing)
|
|
{
|
|
if (!disposing)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ConfigurationManager.Instance.ResetEvent -= OnConfigReset;
|
|
|
|
Plugin.PluginInterface.RelinquishData(_sharedDataId);
|
|
|
|
Instance = null!;
|
|
}
|
|
#endregion
|
|
|
|
private WindowClippingConfig _config = null!;
|
|
|
|
private void OnConfigReset(ConfigurationManager sender)
|
|
{
|
|
_config = sender.GetConfigObject<WindowClippingConfig>();
|
|
}
|
|
|
|
public bool Enabled => _config.Enabled;
|
|
public WindowClippingMode? Mode => _config.Enabled ? _config.Mode : null;
|
|
|
|
private List<ClipRect> _clipRects = new List<ClipRect>();
|
|
private List<ClipRect> _extraClipRects = new List<ClipRect>();
|
|
|
|
private static Dictionary<string, Vector4> _thirdPartyClipRects = new();
|
|
private static string _sharedDataId = "HSUI.ClipRects";
|
|
|
|
private static List<string> _ignoredAddonNames = new List<string>()
|
|
{
|
|
"_FocusTargetInfo",
|
|
};
|
|
|
|
private readonly string[] _hotbarAddonNames = { "_ActionBar", "_ActionBar01", "_ActionBar02", "_ActionBar03", "_ActionBar04", "_ActionBar05", "_ActionBar06", "_ActionBar07", "_ActionBar08", "_ActionBar09" };
|
|
|
|
public unsafe void Update()
|
|
{
|
|
if (!_config.Enabled) { return; }
|
|
|
|
_clipRects.Clear();
|
|
_extraClipRects.Clear();
|
|
|
|
// find clip rects for game windows
|
|
AtkStage* stage = AtkStage.Instance();
|
|
if (stage == null) { return; }
|
|
|
|
RaptureAtkUnitManager* manager = stage->RaptureAtkUnitManager;
|
|
if (manager == null) { return; }
|
|
|
|
AtkUnitList* loadedUnitsList = &manager->AtkUnitManager.AllLoadedUnitsList;
|
|
if (loadedUnitsList == null) { return; }
|
|
|
|
for (int i = 0; i < loadedUnitsList->Count; i++)
|
|
{
|
|
try
|
|
{
|
|
AtkUnitBase* addon = *(AtkUnitBase**)Unsafe.AsPointer(ref loadedUnitsList->Entries[i]);
|
|
if (addon == null || addon->RootNode == null || !addon->IsVisible || addon->WindowNode == null || addon->Scale == 0 || !addon->WindowNode->IsVisible())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
string name = addon->NameString;
|
|
if (_ignoredAddonNames.Contains(name))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
float margin = 5 * addon->Scale;
|
|
float bottomMargin = 13 * addon->Scale;
|
|
|
|
Vector2 pos = new Vector2(addon->RootNode->X + margin, addon->RootNode->Y + margin);
|
|
Vector2 size = new Vector2(
|
|
addon->RootNode->Width * addon->Scale - margin,
|
|
addon->RootNode->Height * addon->Scale - bottomMargin
|
|
);
|
|
|
|
// just in case this causes weird issues / crashes (doubt it though...)
|
|
ClipRect clipRect = new ClipRect(pos, pos + size);
|
|
if (clipRect.Max.X < clipRect.Min.X || clipRect.Max.Y < clipRect.Min.Y)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
_clipRects.Add(clipRect);
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
if (_config.ThirdPartyClipRectsEnabled)
|
|
{
|
|
// find clip rects from other plugins
|
|
Dictionary<string, Vector4> dict = _thirdPartyClipRects;
|
|
foreach (Vector4 vector in dict.Values)
|
|
{
|
|
ClipRect clipRect = new ClipRect(new(vector.X, vector.Y), new(vector.Z, vector.W));
|
|
_clipRects.Add(clipRect);
|
|
}
|
|
}
|
|
}
|
|
|
|
private List<ClipRect> ActiveClipRects()
|
|
{
|
|
return [.. _clipRects, .. _extraClipRects];
|
|
}
|
|
|
|
public void AddNameplatesClipRects()
|
|
{
|
|
if (!_config.NameplatesClipRectsEnabled) { return; }
|
|
|
|
// target cast bar
|
|
ClipRect? targetCastbarClipRect = GetTargetCastbarClipRect();
|
|
if (targetCastbarClipRect.HasValue)
|
|
{
|
|
_extraClipRects.Add(targetCastbarClipRect.Value);
|
|
}
|
|
|
|
// hotbars
|
|
_extraClipRects.AddRange(GetHotbarsClipRects());
|
|
|
|
// chat bubbles
|
|
_extraClipRects.AddRange(GetNPCChatBubbleClipRect());
|
|
_extraClipRects.AddRange(GetPlayerChatBubbleClipRect());
|
|
}
|
|
|
|
public void RemoveNameplatesClipRects()
|
|
{
|
|
_extraClipRects.Clear();
|
|
}
|
|
|
|
private unsafe ClipRect? GetTargetCastbarClipRect()
|
|
{
|
|
if (!_config.TargetCastbarClipRectEnabled) { return null; }
|
|
|
|
AtkUnitBase* addon = (AtkUnitBase*)Plugin.GameGui.GetAddonByName("_TargetInfoCastBar", 1).Address;
|
|
if (addon == null || !addon->IsVisible) { return null; }
|
|
|
|
AtkResNode* baseNode = addon->GetNodeById(2);
|
|
AtkImageNode* imageNode = addon->GetImageNodeById(7);
|
|
|
|
if (baseNode == null || !baseNode->IsVisible()) { return null; }
|
|
if (imageNode == null || !imageNode->IsVisible()) { return null; }
|
|
|
|
Vector2 pos = new Vector2(
|
|
addon->X + (baseNode->X * addon->Scale),
|
|
addon->Y + (baseNode->Y * addon->Scale)
|
|
);
|
|
Vector2 size = new Vector2(
|
|
imageNode->Width * addon->Scale,
|
|
imageNode->Height * addon->Scale
|
|
);
|
|
|
|
return new ClipRect(pos, pos + size);
|
|
}
|
|
|
|
private unsafe List<ClipRect> GetHotbarsClipRects()
|
|
{
|
|
List<ClipRect> rects = new List<ClipRect>();
|
|
if (!_config.HotbarsClipRectsEnabled) { return rects; }
|
|
|
|
foreach (string addonName in _hotbarAddonNames)
|
|
{
|
|
AtkUnitBase* addon = (AtkUnitBase*)Plugin.GameGui.GetAddonByName(addonName, 1).Address;
|
|
if (addon == null || !addon->IsVisible) { continue; }
|
|
|
|
AtkComponentNode* firstNode = addon->GetComponentNodeById(8);
|
|
AtkComponentNode* lastNode = addon->GetComponentNodeById(19);
|
|
|
|
if (firstNode == null || lastNode == null) { continue; }
|
|
|
|
|
|
float margin = 10f * addon->Scale;
|
|
|
|
Vector2 min = new Vector2(
|
|
addon->X + (firstNode->AtkResNode.X * addon->Scale) + margin,
|
|
addon->Y + (firstNode->AtkResNode.Y * addon->Scale) + margin
|
|
);
|
|
Vector2 max = new Vector2(
|
|
addon->X + (lastNode->AtkResNode.X * addon->Scale) + (lastNode->AtkResNode.Width * addon->Scale) - margin,
|
|
addon->Y + (lastNode->AtkResNode.Y * addon->Scale) + (lastNode->AtkResNode.Height * addon->Scale) - margin
|
|
);
|
|
|
|
rects.Add(new ClipRect(min, max));
|
|
}
|
|
|
|
return rects;
|
|
}
|
|
|
|
private unsafe List<ClipRect> GetNPCChatBubbleClipRect()
|
|
{
|
|
List<ClipRect> rects = new List<ClipRect>();
|
|
if (!_config.ChatBubblesNPCClipRectsEnabled) { return rects; }
|
|
|
|
var addon = (AddonMiniTalk*) Plugin.GameGui.GetAddonByName("_MiniTalk").Address;
|
|
if (addon is null)
|
|
{
|
|
return rects;
|
|
}
|
|
|
|
foreach (var talkBubble in addon->TalkBubbles) {
|
|
if (!talkBubble.ComponentNode->IsVisible())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
AtkNineGridNode* bubbleNineGridNode = talkBubble.BubbleNineGridNode;
|
|
|
|
Vector2 position = new Vector2(
|
|
bubbleNineGridNode->ScreenX,
|
|
bubbleNineGridNode->ScreenY
|
|
);
|
|
Vector2 scale = GetNodeScale((AtkResNode*) bubbleNineGridNode, new Vector2(bubbleNineGridNode->ScaleX, bubbleNineGridNode->ScaleY));
|
|
Vector2 size = new Vector2(
|
|
bubbleNineGridNode->Width,
|
|
bubbleNineGridNode->Height
|
|
) * scale;
|
|
|
|
rects.Add(new ClipRect(position, position + size));
|
|
}
|
|
|
|
return rects;
|
|
}
|
|
|
|
public unsafe List<ClipRect> GetPlayerChatBubbleClipRect()
|
|
{
|
|
List<ClipRect> rects = new List<ClipRect>();
|
|
if (!_config.ChatBubblesPlayersClipRectsEnabled) { return rects; }
|
|
|
|
AtkUnitBase* addon = (AtkUnitBase*) Plugin.GameGui.GetAddonByName("MiniTalkPlayer").Address;
|
|
if (addon is null)
|
|
{
|
|
return rects;
|
|
}
|
|
|
|
foreach (var node in addon->UldManager.Nodes) {
|
|
if (node.Value is null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (node.Value->GetNodeType() is not NodeType.Component || !node.Value->IsVisible())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
AtkComponentNode* componentNode = (AtkComponentNode*)node.Value;
|
|
AtkComponentBase* component = componentNode->GetComponent();
|
|
if (component is null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
AtkResNode* bubbleNode = component->UldManager.SearchNodeById(4);
|
|
if (bubbleNode is null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Vector2 position = new Vector2(
|
|
componentNode->ScreenX,
|
|
componentNode->ScreenY
|
|
);
|
|
Vector2 scale = GetNodeScale(bubbleNode, new Vector2(bubbleNode->ScaleX, bubbleNode->ScaleY));
|
|
Vector2 size = new Vector2(
|
|
bubbleNode->Width,
|
|
bubbleNode->Height
|
|
) * scale;
|
|
|
|
rects.Add(new ClipRect(position, position + size));
|
|
}
|
|
|
|
return rects;
|
|
}
|
|
|
|
public ClipRect? GetClipRectForArea(Vector2 pos, Vector2 size)
|
|
{
|
|
if (!_config.Enabled) { return null; }
|
|
|
|
List<ClipRect> rects = ActiveClipRects();
|
|
|
|
foreach (ClipRect clipRect in rects)
|
|
{
|
|
ClipRect area = new ClipRect(pos, pos + size);
|
|
if (clipRect.IntersectsWith(area))
|
|
{
|
|
return clipRect;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public static ClipRect[] GetInvertedClipRects(ClipRect clipRect)
|
|
{
|
|
float maxX = ImGui.GetMainViewport().Size.X;
|
|
float maxY = ImGui.GetMainViewport().Size.Y;
|
|
|
|
Vector2 aboveMin = new Vector2(0, 0);
|
|
Vector2 aboveMax = new Vector2(maxX, clipRect.Min.Y);
|
|
Vector2 leftMin = new Vector2(0, clipRect.Min.Y);
|
|
Vector2 leftMax = new Vector2(clipRect.Min.X, maxY);
|
|
|
|
Vector2 rightMin = new Vector2(clipRect.Max.X, clipRect.Min.Y);
|
|
Vector2 rightMax = new Vector2(maxX, clipRect.Max.Y);
|
|
Vector2 belowMin = new Vector2(clipRect.Min.X, clipRect.Max.Y);
|
|
Vector2 belowMax = new Vector2(maxX, maxY);
|
|
|
|
ClipRect[] invertedClipRects = new ClipRect[4];
|
|
invertedClipRects[0] = new ClipRect(aboveMin, aboveMax);
|
|
invertedClipRects[1] = new ClipRect(leftMin, leftMax);
|
|
invertedClipRects[2] = new ClipRect(rightMin, rightMax);
|
|
invertedClipRects[3] = new ClipRect(belowMin, belowMax);
|
|
|
|
return invertedClipRects;
|
|
}
|
|
|
|
public bool IsPointClipped(Vector2 point)
|
|
{
|
|
if (!_config.Enabled) { return false; }
|
|
|
|
List<ClipRect> rects = ActiveClipRects();
|
|
|
|
foreach (ClipRect clipRect in rects)
|
|
{
|
|
if (clipRect.Contains(point))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public static unsafe Vector2 GetNodeScale(AtkResNode* node, Vector2 currentScale) {
|
|
if (node is null)
|
|
{
|
|
return currentScale;
|
|
}
|
|
|
|
if (node->ParentNode is not null) {
|
|
currentScale.X *= node->ParentNode->GetScaleX();
|
|
currentScale.Y *= node->ParentNode->GetScaleY();
|
|
|
|
return GetNodeScale(node->ParentNode, currentScale);
|
|
}
|
|
|
|
return currentScale;
|
|
}
|
|
}
|
|
|
|
public struct ClipRect
|
|
{
|
|
public readonly Vector2 Min;
|
|
public readonly Vector2 Max;
|
|
|
|
private readonly Rectangle Rectangle;
|
|
|
|
public ClipRect(Vector2 min, Vector2 max)
|
|
{
|
|
Vector2 screenSize = ImGui.GetMainViewport().Size;
|
|
|
|
Min = Clamp(min, Vector2.Zero, screenSize);
|
|
Max = Clamp(max, Vector2.Zero, screenSize);
|
|
|
|
Vector2 size = Max - Min;
|
|
|
|
Rectangle = new Rectangle((int)Min.X, (int)Min.Y, (int)size.X, (int)size.Y);
|
|
}
|
|
|
|
public bool Contains(Vector2 point)
|
|
{
|
|
return Rectangle.Contains((int)point.X, (int)point.Y);
|
|
}
|
|
|
|
public bool IntersectsWith(ClipRect other)
|
|
{
|
|
return Rectangle.IntersectsWith(other.Rectangle);
|
|
}
|
|
|
|
public ClipRect? Intersect(ClipRect other)
|
|
{
|
|
float minX = Math.Max(Min.X, other.Min.X);
|
|
float minY = Math.Max(Min.Y, other.Min.Y);
|
|
float maxX = Math.Min(Max.X, other.Max.X);
|
|
float maxY = Math.Min(Max.Y, other.Max.Y);
|
|
if (minX >= maxX || minY >= maxY) return null;
|
|
return new ClipRect(new Vector2(minX, minY), new Vector2(maxX, maxY));
|
|
}
|
|
|
|
private static Vector2 Clamp(Vector2 vector, Vector2 min, Vector2 max)
|
|
{
|
|
return new Vector2(Math.Max(min.X, Math.Min(max.X, vector.X)), Math.Max(min.Y, Math.Min(max.Y, vector.Y)));
|
|
}
|
|
}
|
|
}
|