From a8861ce40ad2f37d8178399f7b3e261cf6824811 Mon Sep 17 00:00:00 2001 From: Liza Carvelli Date: Wed, 11 Oct 2023 02:52:55 +0200 Subject: [PATCH] Initial Commit --- .gitignore | 5 ++ DalamudReflector.cs | 113 +++++++++++++++++++++++++++++++++++++++++++ LImGui.HeaderIcon.cs | 102 ++++++++++++++++++++++++++++++++++++++ LImGui.cs | 36 ++++++++++++++ LLib.csproj | 53 ++++++++++++++++++++ LLib.sln | 16 ++++++ global.json | 7 +++ 7 files changed, 332 insertions(+) create mode 100644 .gitignore create mode 100644 DalamudReflector.cs create mode 100644 LImGui.HeaderIcon.cs create mode 100644 LImGui.cs create mode 100644 LLib.csproj create mode 100644 LLib.sln create mode 100644 global.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a398beb --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/.idea +*.user + +/bin +/obj diff --git a/DalamudReflector.cs b/DalamudReflector.cs new file mode 100644 index 0000000..22b7f4a --- /dev/null +++ b/DalamudReflector.cs @@ -0,0 +1,113 @@ +using Dalamud.Plugin; +using System; +using System.Collections.Generic; +using System.Reflection; +using Dalamud.Plugin.Services; + +namespace LLib; + +/// +/// Originally part of ECommons by NightmareXIV. +/// +/// https://github.com/NightmareXIV/ECommons/blob/master/ECommons/Reflection/DalamudReflector.cs +/// +public sealed class DalamudReflector : IDisposable +{ + private readonly DalamudPluginInterface _pluginInterface; + private readonly IFramework _framework; + private readonly IPluginLog _pluginLog; + private readonly Dictionary _pluginCache = new(); + private bool _pluginsChanged; + + public DalamudReflector(DalamudPluginInterface pluginInterface, IFramework framework, IPluginLog pluginLog) + { + _pluginInterface = pluginInterface; + _framework = framework; + _pluginLog = pluginLog; + var pm = GetPluginManager(); + pm.GetType().GetEvent("OnInstalledPluginsChanged")!.AddEventHandler(pm, OnInstalledPluginsChanged); + + _framework.Update += FrameworkUpdate; + } + + public void Dispose() + { + _framework.Update -= FrameworkUpdate; + + var pm = GetPluginManager(); + pm.GetType().GetEvent("OnInstalledPluginsChanged")!.RemoveEventHandler(pm, OnInstalledPluginsChanged); + } + + private void FrameworkUpdate(IFramework framework) + { + if (_pluginsChanged) + { + _pluginsChanged = false; + _pluginCache.Clear(); + } + } + + private object GetPluginManager() + { + return _pluginInterface.GetType().Assembly.GetType("Dalamud.Service`1", true)! + .MakeGenericType( + _pluginInterface.GetType().Assembly.GetType("Dalamud.Plugin.Internal.PluginManager", true)!) + .GetMethod("Get")!.Invoke(null, BindingFlags.Default, null, Array.Empty(), null)!; + } + + public bool TryGetDalamudPlugin(string internalName, out IDalamudPlugin? instance, bool suppressErrors = false, + bool ignoreCache = false) + { + if (!ignoreCache && _pluginCache.TryGetValue(internalName, out instance)) + { + return true; + } + + try + { + var pluginManager = GetPluginManager(); + var installedPlugins = + (System.Collections.IList)pluginManager.GetType().GetProperty("InstalledPlugins")!.GetValue( + pluginManager)!; + + foreach (var t in installedPlugins) + { + if ((string?)t.GetType().GetProperty("Name")!.GetValue(t) == internalName) + { + var type = t.GetType().Name == "LocalDevPlugin" ? t.GetType().BaseType : t.GetType(); + var plugin = (IDalamudPlugin?)type! + .GetField("instance", BindingFlags.NonPublic | BindingFlags.Instance)!.GetValue(t); + if (plugin == null) + { + _pluginLog.Warning($"[DalamudReflector] Found requested plugin {internalName} but it was null"); + } + else + { + instance = plugin; + _pluginCache[internalName] = plugin; + return true; + } + } + } + + instance = null; + return false; + } + catch (Exception e) + { + if (!suppressErrors) + { + _pluginLog.Error(e, $"Can't find {internalName} plugin: {e.Message}"); + } + + instance = null; + return false; + } + } + + private void OnInstalledPluginsChanged() + { + _pluginLog.Verbose("Installed plugins changed event fired"); + _pluginsChanged = true; + } +} diff --git a/LImGui.HeaderIcon.cs b/LImGui.HeaderIcon.cs new file mode 100644 index 0000000..e65be9a --- /dev/null +++ b/LImGui.HeaderIcon.cs @@ -0,0 +1,102 @@ +using System.Numerics; +using System.Runtime.InteropServices; +using Dalamud.Interface; +using Dalamud.Interface.Utility; +using Dalamud.Plugin; +using ImGuiNET; + +namespace LLib; + +/// +/// Originally part of ECommons by NightmareXIV. +/// +/// https://github.com/NightmareXIV/ECommons/blob/master/ECommons/ImGuiMethods/ImGuiEx.cs +/// +partial class LImGui +{ + public sealed record HeaderIconOptions + { + public Vector2 Offset { get; init; } = Vector2.Zero; + public ImGuiMouseButton MouseButton { get; init; } = ImGuiMouseButton.Left; + public string Tooltip { get; init; } = string.Empty; + public uint Color { get; init; } = 0xFFFFFFFF; + } + + private static uint _headerLastWindowId = 0; + private static ulong _headerLastFrame = 0; + private static float _headerCurrentPos = 0; + private static float _headerImGuiButtonWidth = 0; + + public static bool AddHeaderIcon(DalamudPluginInterface pluginInterface, string id, FontAwesomeIcon icon, HeaderIconOptions options = null) + { + if (ImGui.IsWindowCollapsed()) return false; + + var scale = ImGuiHelpers.GlobalScale; + var currentID = ImGui.GetID(0); + if (currentID != _headerLastWindowId || _headerLastFrame != pluginInterface.UiBuilder.FrameCount) + { + _headerLastWindowId = currentID; + _headerLastFrame = pluginInterface.UiBuilder.FrameCount; + _headerCurrentPos = 0.25f * ImGui.GetStyle().FramePadding.Length(); + if (!GetCurrentWindowFlags().HasFlag(ImGuiWindowFlags.NoTitleBar)) + _headerCurrentPos = 1; + _headerImGuiButtonWidth = 0f; + if (CurrentWindowHasCloseButton()) + _headerImGuiButtonWidth += 17 * scale; + if (!GetCurrentWindowFlags().HasFlag(ImGuiWindowFlags.NoCollapse)) + _headerImGuiButtonWidth += 17 * scale; + } + + options ??= new(); + var prevCursorPos = ImGui.GetCursorPos(); + var buttonSize = new Vector2(20 * scale); + var buttonPos = new Vector2((ImGui.GetWindowWidth() - buttonSize.X - _headerImGuiButtonWidth * scale * _headerCurrentPos) - (ImGui.GetStyle().FramePadding.X * scale), ImGui.GetScrollY() + 1); + ImGui.SetCursorPos(buttonPos); + var drawList = ImGui.GetWindowDrawList(); + drawList.PushClipRectFullScreen(); + + var pressed = false; + ImGui.InvisibleButton(id, buttonSize); + var itemMin = ImGui.GetItemRectMin(); + var itemMax = ImGui.GetItemRectMax(); + var halfSize = ImGui.GetItemRectSize() / 2; + var center = itemMin + halfSize; + if (ImGui.IsWindowHovered() && ImGui.IsMouseHoveringRect(itemMin, itemMax, false)) + { + if (!string.IsNullOrEmpty(options.Tooltip)) + ImGui.SetTooltip(options.Tooltip); + ImGui.GetWindowDrawList().AddCircleFilled(center, halfSize.X, ImGui.GetColorU32(ImGui.IsMouseDown(ImGuiMouseButton.Left) ? ImGuiCol.ButtonActive : ImGuiCol.ButtonHovered)); + if (ImGui.IsMouseReleased(options.MouseButton)) + pressed = true; + } + + ImGui.SetCursorPos(buttonPos); + ImGui.PushFont(UiBuilder.IconFont); + var iconString = icon.ToIconString(); + drawList.AddText(UiBuilder.IconFont, ImGui.GetFontSize(), itemMin + halfSize - ImGui.CalcTextSize(iconString) / 2 + options.Offset, options.Color, iconString); + ImGui.PopFont(); + + ImGui.PopClipRect(); + ImGui.SetCursorPos(prevCursorPos); + + return pressed; + } + + [LibraryImport("cimgui")] + [UnmanagedCallConv(CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] + private static partial nint igGetCurrentWindow(); + private static unsafe ImGuiWindow* GetCurrentWindow() => (ImGuiWindow*)igGetCurrentWindow(); + private static unsafe ImGuiWindowFlags GetCurrentWindowFlags() => GetCurrentWindow()->Flags; + private static unsafe bool CurrentWindowHasCloseButton() => GetCurrentWindow()->HasCloseButton != 0; + + [StructLayout(LayoutKind.Explicit)] + private struct ImGuiWindow + { + [FieldOffset(0xC)] public ImGuiWindowFlags Flags; + + [FieldOffset(0xD5)] public byte HasCloseButton; + + // 0x118 is the start of ImGuiWindowTempData + [FieldOffset(0x130)] public Vector2 CursorMaxPos; + } +} diff --git a/LImGui.cs b/LImGui.cs new file mode 100644 index 0000000..7b5aa96 --- /dev/null +++ b/LImGui.cs @@ -0,0 +1,36 @@ +using System.Diagnostics; +using System.Numerics; +using Dalamud.Interface; +using Dalamud.Plugin; +using ImGuiNET; + +namespace LLib; + +public static partial class LImGui +{ + public static void AddPatreonIcon(DalamudPluginInterface pluginInterface) + { + if (AddHeaderIcon(pluginInterface, "##Patreon", FontAwesomeIcon.Heart, new HeaderIconOptions + { + Tooltip = "Open Liza's page on Patreon", + Color = 0xFF000FF, + Offset = Vector2.Zero, + MouseButton = ImGuiMouseButton.Left + })) + { + try + { + Process.Start(new ProcessStartInfo + { + FileName = "http://patreon.com/lizac", + UseShellExecute = true, + Verb = string.Empty, + }); + } + catch + { + // not sure what to do anyway + } + } + } +} diff --git a/LLib.csproj b/LLib.csproj new file mode 100644 index 0000000..698c0b8 --- /dev/null +++ b/LLib.csproj @@ -0,0 +1,53 @@ + + + + net7.0-windows + 1.0 + 11.0 + enable + true + true + true + portable + $(SolutionDir)=X:\ + + + + $(appdata)\XIVLauncher\addon\Hooks\dev\ + + + + $(DALAMUD_HOME)/ + + + + + $(DalamudLibPath)Dalamud.dll + false + + + $(DalamudLibPath)ImGui.NET.dll + false + + + $(DalamudLibPath)Lumina.dll + false + + + $(DalamudLibPath)Lumina.Excel.dll + false + + + $(DalamudLibPath)Newtonsoft.Json.dll + false + + + $(DalamudLibPath)FFXIVClientStructs.dll + false + + + + + + + diff --git a/LLib.sln b/LLib.sln new file mode 100644 index 0000000..844822f --- /dev/null +++ b/LLib.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LLib", "LLib.csproj", "{FCC925E1-A583-4C9E-B3BE-B92210BEEAC6}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FCC925E1-A583-4C9E-B3BE-B92210BEEAC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FCC925E1-A583-4C9E-B3BE-B92210BEEAC6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FCC925E1-A583-4C9E-B3BE-B92210BEEAC6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FCC925E1-A583-4C9E-B3BE-B92210BEEAC6}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/global.json b/global.json new file mode 100644 index 0000000..aaac9e0 --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "7.0.0", + "rollForward": "latestMinor", + "allowPrerelease": false + } +} \ No newline at end of file