v1.0.8.23: Main Menu bar; Duty List transparent background
Made-with: Cursor
This commit is contained in:
@@ -334,7 +334,7 @@ namespace HSUI.Helpers
|
||||
DrawGradientFilledRect(cursorPos, new Vector2(Math.Max(1, barSize.X * shield), h), color, drawList);
|
||||
}
|
||||
|
||||
public static void DrawInWindow(string name, Vector2 pos, Vector2 size, bool needsInput, Action<ImDrawListPtr> drawAction)
|
||||
public static void DrawInWindow(string name, Vector2 pos, Vector2 size, bool needsInput, Action<ImDrawListPtr> drawAction, bool allowInputInClipRect = false)
|
||||
{
|
||||
const ImGuiWindowFlags windowFlags = ImGuiWindowFlags.NoTitleBar |
|
||||
ImGuiWindowFlags.NoScrollbar |
|
||||
@@ -344,7 +344,7 @@ namespace HSUI.Helpers
|
||||
|
||||
bool inputs = InputsHelper.Instance?.IsProxyEnabled == true ? false : needsInput;
|
||||
|
||||
DrawInWindow(name, pos, size, inputs, false, windowFlags, drawAction);
|
||||
DrawInWindow(name, pos, size, inputs, false, windowFlags, drawAction, allowInputInClipRect);
|
||||
}
|
||||
|
||||
public static void DrawInWindow(
|
||||
@@ -354,7 +354,8 @@ namespace HSUI.Helpers
|
||||
bool needsInput,
|
||||
bool needsWindow,
|
||||
ImGuiWindowFlags windowFlags,
|
||||
Action<ImDrawListPtr> drawAction)
|
||||
Action<ImDrawListPtr> drawAction,
|
||||
bool allowInputInClipRect = false)
|
||||
{
|
||||
|
||||
if (!ClipRectsHelper.Instance.Enabled || ClipRectsHelper.Instance.Mode == WindowClippingMode.Performance)
|
||||
@@ -408,7 +409,7 @@ namespace HSUI.Helpers
|
||||
if (ClipRectsHelper.Instance.Mode == WindowClippingMode.Hide) { return; }
|
||||
|
||||
ImGuiWindowFlags flags = windowFlags;
|
||||
if (needsInput && clipRect.Value.Contains(ImGui.GetMousePos()))
|
||||
if (needsInput && !allowInputInClipRect && clipRect.Value.Contains(ImGui.GetMousePos()))
|
||||
{
|
||||
flags |= ImGuiWindowFlags.NoInputs;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,434 @@
|
||||
using AtkValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.Sheets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace HSUI.Helpers
|
||||
{
|
||||
public readonly struct MainMenuEntry
|
||||
{
|
||||
public uint CommandId { get; }
|
||||
public uint IconId { get; }
|
||||
public string Name { get; }
|
||||
|
||||
public MainMenuEntry(uint commandId, uint iconId, string name)
|
||||
{
|
||||
CommandId = commandId;
|
||||
IconId = iconId;
|
||||
Name = name ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
public static class MainMenuHelper
|
||||
{
|
||||
/// <summary>Names of the 7 top-level Main Menu categories. Clicking these opens the context menu, not a direct action.</summary>
|
||||
private static readonly string[] MainMenuCategoryNames =
|
||||
{
|
||||
"Character", "Duty", "Logs", "Travel", "Party", "Social", "System"
|
||||
};
|
||||
|
||||
/// <summary>Fixed icon IDs for categories (from sheet). Used for display; command row is still resolved by name.</summary>
|
||||
private static readonly IReadOnlyDictionary<string, uint> FixedCategoryIconIds = new Dictionary<string, uint>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "Duty", 5 },
|
||||
{ "Logs", 21 },
|
||||
{ "Travel", 7 },
|
||||
{ "Social", 20 }
|
||||
};
|
||||
|
||||
/// <summary>Categories that must use the category header row (smallest RowId in MainCommandCategory) so the context menu matches the game bar.</summary>
|
||||
private static readonly HashSet<string> UseCategoryHeaderForMenu = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"Logs",
|
||||
"Travel"
|
||||
};
|
||||
|
||||
/// <summary>Submenu item names that uniquely identify the Logs category (from default game context menu).</summary>
|
||||
private static readonly string[] LogsCategoryAnchorNames = { "Hunting Log", "Sightseeing Log", "Crafting Log", "Gathering Log", "Fishing Log", "Fish Guide", "Orchestrion List", "Challenge Log" };
|
||||
|
||||
/// <summary>Submenu item names that uniquely identify the Travel category (from default game context menu).</summary>
|
||||
private static readonly string[] TravelCategoryAnchorNames = { "Aether Currents", "Mount Speed", "Shared FATE", "Map", "Teleport", "Return" };
|
||||
|
||||
/// <summary>Get the 7 top-level Main Menu categories (Character, Duty, Logs, Travel, Party, Social, System) that are enabled. Clicking these opens the context menu.</summary>
|
||||
public static List<MainMenuEntry> GetEnabledMainCommands()
|
||||
{
|
||||
var list = new List<MainMenuEntry>();
|
||||
try
|
||||
{
|
||||
var sheet = Plugin.DataManager.GetExcelSheet<MainCommand>();
|
||||
if (sheet == null) return list;
|
||||
|
||||
return GetEnabledMainCommandsUnsafe(list, sheet);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Plugin.Logger.Warning($"[HSUI] MainMenuHelper.GetEnabledMainCommands: {ex.Message}");
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>True if the sheet row name matches the main menu category (exact or flexible).</summary>
|
||||
private static bool MatchesCategoryName(string rowName, string categoryName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(rowName)) return false;
|
||||
string r = rowName.Trim();
|
||||
if (string.Equals(r, categoryName, StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
if (string.Equals(categoryName, "Duty", StringComparison.OrdinalIgnoreCase))
|
||||
return r.IndexOf("Duty", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
if (string.Equals(categoryName, "Travel", StringComparison.OrdinalIgnoreCase))
|
||||
return r.IndexOf("Travel", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| r.IndexOf("Teleport", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| r.IndexOf("Return", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| r.IndexOf("Aetheryte", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| r.IndexOf("Port", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
if (string.Equals(categoryName, "Party", StringComparison.OrdinalIgnoreCase))
|
||||
return r.IndexOf("Party", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
if (string.Equals(categoryName, "Logs", StringComparison.OrdinalIgnoreCase))
|
||||
return r.IndexOf("Log", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| string.Equals(r, "Journal", StringComparison.OrdinalIgnoreCase)
|
||||
|| r.IndexOf("Quest", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| r.IndexOf("Completion", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
if (string.Equals(categoryName, "Social", StringComparison.OrdinalIgnoreCase))
|
||||
return r.IndexOf("Social", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| r.IndexOf("Friend", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| r.IndexOf("Contact", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| r.IndexOf("Linkshell", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| r.IndexOf("Free Company", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| r.IndexOf("Fellowship", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| r.IndexOf("Group", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
if (string.Equals(categoryName, "System", StringComparison.OrdinalIgnoreCase))
|
||||
return r.IndexOf("System", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| r.IndexOf("Config", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Get the category header row ID (smallest RowId in the same MainCommandCategory). For Logs/Travel we identify the category by submenu item names from the default game menu.</summary>
|
||||
private static unsafe uint GetCategoryHeaderCommandId(string categoryName, Lumina.Excel.ExcelSheet<MainCommand> sheet, AgentHUD* agentHud)
|
||||
{
|
||||
uint categoryGroupId = 0;
|
||||
string[]? anchorNames = null;
|
||||
if (string.Equals(categoryName, "Logs", StringComparison.OrdinalIgnoreCase))
|
||||
anchorNames = LogsCategoryAnchorNames;
|
||||
else if (string.Equals(categoryName, "Travel", StringComparison.OrdinalIgnoreCase))
|
||||
anchorNames = TravelCategoryAnchorNames;
|
||||
|
||||
if (anchorNames != null)
|
||||
{
|
||||
// Find category by a row that matches one of the known submenu names (e.g. "Hunting Log", "Aether Currents").
|
||||
foreach (var row in sheet)
|
||||
{
|
||||
uint id = row.RowId;
|
||||
if (id == 0) continue;
|
||||
if (!agentHud->IsMainCommandEnabled(id)) continue;
|
||||
string name = "";
|
||||
try { name = row.Name.ToString(); }
|
||||
catch { }
|
||||
name = name?.Trim() ?? "";
|
||||
foreach (string anchor in anchorNames)
|
||||
{
|
||||
if (name.IndexOf(anchor, StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
{
|
||||
try { categoryGroupId = row.MainCommandCategory.RowId; break; }
|
||||
catch { }
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (categoryGroupId != 0) break;
|
||||
}
|
||||
}
|
||||
|
||||
if (categoryGroupId == 0)
|
||||
{
|
||||
// Fallback: exact "Logs"/"Travel" or flexible category name match
|
||||
bool exactOnly = string.Equals(categoryName, "Logs", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(categoryName, "Travel", StringComparison.OrdinalIgnoreCase);
|
||||
foreach (var row in sheet)
|
||||
{
|
||||
uint id = row.RowId;
|
||||
if (id == 0) continue;
|
||||
string name = "";
|
||||
try { name = row.Name.ToString(); }
|
||||
catch { }
|
||||
if (exactOnly && !string.Equals(name.Trim(), categoryName, StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
if (!exactOnly && !MatchesCategoryName(name, categoryName))
|
||||
continue;
|
||||
if (!agentHud->IsMainCommandEnabled(id))
|
||||
continue;
|
||||
try { categoryGroupId = row.MainCommandCategory.RowId; break; }
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
if (categoryGroupId == 0)
|
||||
{
|
||||
foreach (var row in sheet)
|
||||
{
|
||||
uint id = row.RowId;
|
||||
if (id == 0) continue;
|
||||
string name = "";
|
||||
try { name = row.Name.ToString(); }
|
||||
catch { }
|
||||
if (!MatchesCategoryName(name, categoryName) || !agentHud->IsMainCommandEnabled(id))
|
||||
continue;
|
||||
try { categoryGroupId = row.MainCommandCategory.RowId; break; }
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
if (categoryGroupId == 0) return 0;
|
||||
uint headerId = 0;
|
||||
foreach (var row in sheet)
|
||||
{
|
||||
if (row.RowId == 0) continue;
|
||||
try { if (row.MainCommandCategory.RowId != categoryGroupId) continue; }
|
||||
catch { continue; }
|
||||
if (!agentHud->IsMainCommandEnabled(row.RowId)) continue;
|
||||
if (headerId == 0 || row.RowId < headerId)
|
||||
headerId = row.RowId;
|
||||
}
|
||||
return headerId;
|
||||
}
|
||||
|
||||
private static unsafe List<MainMenuEntry> GetEnabledMainCommandsUnsafe(List<MainMenuEntry> list, Lumina.Excel.ExcelSheet<MainCommand> sheet)
|
||||
{
|
||||
var agentHud = AgentHUD.Instance();
|
||||
if (agentHud == null) return list;
|
||||
|
||||
foreach (string categoryName in MainMenuCategoryNames)
|
||||
{
|
||||
// Logs and Travel: use category header row so context menu matches game bar.
|
||||
if (UseCategoryHeaderForMenu.Contains(categoryName))
|
||||
{
|
||||
uint headerId = GetCategoryHeaderCommandId(categoryName, sheet, agentHud);
|
||||
if (headerId != 0)
|
||||
{
|
||||
uint iconId = 0;
|
||||
if (FixedCategoryIconIds.TryGetValue(categoryName, out uint fixedIconId))
|
||||
iconId = fixedIconId;
|
||||
if (iconId == 0)
|
||||
iconId = GetFallbackIconIdForCategory(categoryName);
|
||||
list.Add(new MainMenuEntry(headerId, iconId, categoryName));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var row in sheet)
|
||||
{
|
||||
uint id = row.RowId;
|
||||
if (id == 0) continue;
|
||||
|
||||
string name = "";
|
||||
try { name = row.Name.ToString(); }
|
||||
catch { }
|
||||
|
||||
if (!MatchesCategoryName(name, categoryName))
|
||||
continue;
|
||||
if (!agentHud->IsMainCommandEnabled(id))
|
||||
continue;
|
||||
|
||||
// Icon: use fixed icon ID for Duty/Logs/Travel/Social (from sheet); others use sheet → game → fallback.
|
||||
uint iconId = 0;
|
||||
if (FixedCategoryIconIds.TryGetValue(categoryName, out uint fixedIconId))
|
||||
iconId = fixedIconId;
|
||||
if (iconId == 0)
|
||||
{
|
||||
try { iconId = (uint)row.Icon; }
|
||||
catch { }
|
||||
}
|
||||
if (iconId == 0)
|
||||
iconId = GetIconIdForMainCommand(id);
|
||||
if (iconId == 0)
|
||||
iconId = GetFallbackIconIdForCategory(categoryName);
|
||||
|
||||
list.Add(new MainMenuEntry(id, iconId, categoryName));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>Resolve icon for a Main Command. Uses RaptureHotbarModule when sheet row.Icon is 0.</summary>
|
||||
public static unsafe uint GetIconIdForMainCommand(uint commandId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var module = RaptureHotbarModule.Instance();
|
||||
if (module != null && module->ModuleReady)
|
||||
{
|
||||
var bar = module->StandardHotbars[0];
|
||||
var slot = bar.GetHotbarSlot(0);
|
||||
if (slot != null)
|
||||
{
|
||||
int gameIcon = slot->GetIconIdForSlot(RaptureHotbarModule.HotbarSlotType.MainCommand, commandId);
|
||||
if (gameIcon > 0) return (uint)gameIcon;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Plugin.Logger.Warning($"[HSUI] MainMenuHelper.GetIconIdForMainCommand({commandId}): {ex.Message}");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>Fallback icon when game and sheet both return 0. Duty = 61002 (exclamation) to match default bar.</summary>
|
||||
private static uint GetFallbackIconIdForCategory(string categoryName)
|
||||
{
|
||||
return (categoryName?.ToLowerInvariant()) switch
|
||||
{
|
||||
"character" => 61001,
|
||||
"duty" => 61002,
|
||||
"logs" => 61003,
|
||||
"travel" => 61004,
|
||||
"party" => 61005,
|
||||
"social" => 61006,
|
||||
"system" => 61007,
|
||||
_ => 0
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>Execute a main command by ID (e.g. Character, Social, Collection).</summary>
|
||||
public static unsafe void ExecuteMainCommand(uint commandId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var uiModule = Framework.Instance()->GetUIModule();
|
||||
if (uiModule == null) return;
|
||||
|
||||
uiModule->ExecuteMainCommand(commandId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Plugin.Logger.Warning($"[HSUI] MainMenuHelper.ExecuteMainCommand({commandId}): {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Open the context submenu for a category, or execute directly if no submenu. When sub-commands exist, the HUD shows a custom context menu; otherwise executes the command.</summary>
|
||||
public static unsafe void OpenSubmenuOrExecute(uint categoryCommandId)
|
||||
{
|
||||
var subIds = GetSubCommandIds(categoryCommandId);
|
||||
if (subIds != null && subIds.Count > 0)
|
||||
return; // Caller should use GetSubCommandEntries and show custom menu; we don't call OpenSystemMenu (crashes)
|
||||
ExecuteMainCommand(categoryCommandId);
|
||||
}
|
||||
|
||||
/// <summary>Get sub-command entries (id + display name) for the category. Same options as the default main menu bar context menu. Returns empty if category has no sub-commands.</summary>
|
||||
public static List<(uint CommandId, string Name)> GetSubCommandEntries(uint categoryCommandId)
|
||||
{
|
||||
var list = new List<(uint, string)>();
|
||||
try
|
||||
{
|
||||
var subIds = GetSubCommandIds(categoryCommandId);
|
||||
if (subIds == null || subIds.Count == 0) return list;
|
||||
|
||||
var sheet = Plugin.DataManager.GetExcelSheet<MainCommand>();
|
||||
if (sheet == null) return list;
|
||||
|
||||
foreach (uint id in subIds)
|
||||
{
|
||||
string name = "";
|
||||
if (sheet.TryGetRow(id, out var row))
|
||||
{
|
||||
try { name = row.Name.ToString(); }
|
||||
catch { }
|
||||
}
|
||||
if (string.IsNullOrEmpty(name)) name = $"#{id}";
|
||||
list.Add((id, name));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Plugin.Logger.Warning($"[HSUI] MainMenuHelper.GetSubCommandEntries({categoryCommandId}): {ex.Message}");
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>Get MainCommand IDs that belong to the same category as the given command (for submenu).</summary>
|
||||
private static unsafe List<uint> GetSubCommandIds(uint categoryCommandId)
|
||||
{
|
||||
var list = new List<uint>();
|
||||
try
|
||||
{
|
||||
var sheet = Plugin.DataManager.GetExcelSheet<MainCommand>();
|
||||
if (sheet == null) return list;
|
||||
|
||||
var agentHud = AgentHUD.Instance();
|
||||
if (agentHud == null) return list;
|
||||
|
||||
if (!sheet.TryGetRow(categoryCommandId, out var categoryRow)) return list;
|
||||
|
||||
uint categoryGroupId;
|
||||
try
|
||||
{
|
||||
categoryGroupId = categoryRow.MainCommandCategory.RowId;
|
||||
}
|
||||
catch { return list; }
|
||||
|
||||
foreach (var row in sheet)
|
||||
{
|
||||
if (row.RowId == 0) continue;
|
||||
try
|
||||
{
|
||||
if (row.MainCommandCategory.RowId != categoryGroupId) continue;
|
||||
}
|
||||
catch { continue; }
|
||||
if (!agentHud->IsMainCommandEnabled(row.RowId)) continue;
|
||||
list.Add(row.RowId);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Plugin.Logger.Warning($"[HSUI] MainMenuHelper.GetSubCommandIds({categoryCommandId}): {ex.Message}");
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>Open the game's system menu with the given MainCommand IDs (context submenu).</summary>
|
||||
private static unsafe void OpenSystemMenuWithCommands(List<uint> commandIds)
|
||||
{
|
||||
if (commandIds == null || commandIds.Count == 0) return;
|
||||
if (commandIds.Count > 17) return; // game limit
|
||||
|
||||
var agentHud = AgentHUD.Instance();
|
||||
if (agentHud == null) return;
|
||||
|
||||
int count = commandIds.Count;
|
||||
int allocSize = 5 + 17 + 18; // atkValueArgs layout: [4]=size, [5..21]=commands, [23..]=optional name strings
|
||||
var ptr = (AtkValue*)Marshal.AllocHGlobal(allocSize * sizeof(AtkValue));
|
||||
try
|
||||
{
|
||||
// Zero entire buffer so the game never reads garbage as string pointers (prevents crash in Utf8String.SetString)
|
||||
for (int i = 0; i < allocSize; i++)
|
||||
{
|
||||
ptr[i].Type = AtkValueType.Null;
|
||||
ptr[i].Int = 0;
|
||||
}
|
||||
|
||||
ptr[4].ChangeType(AtkValueType.UInt);
|
||||
ptr[4].UInt = (uint)count;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
ptr[5 + i].ChangeType(AtkValueType.Int);
|
||||
ptr[5 + i].Int = (int)commandIds[i];
|
||||
}
|
||||
|
||||
agentHud->OpenSystemMenu(ptr, (uint)count);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(new IntPtr(ptr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,8 +88,17 @@ namespace HSUI.Helpers
|
||||
|
||||
private bool _dataIsValid = false;
|
||||
|
||||
/// <summary>Set when an HUD element (e.g. Main Menu) has the mouse over it this frame. World object tooltip will not show.</summary>
|
||||
private bool _mouseOverHudElement = false;
|
||||
|
||||
private const float IconSize = 24f;
|
||||
|
||||
/// <summary>Call when the mouse is over an interactive HUD element so world object tooltip is not shown.</summary>
|
||||
public void NotifyMouseOverHudElement()
|
||||
{
|
||||
_mouseOverHudElement = true;
|
||||
}
|
||||
|
||||
public void ShowTooltipOnCursor(string text, string? title = null, uint id = 0, string name = "", uint? iconId = null, TooltipIdKind idKind = TooltipIdKind.None, List<TooltipSegment>? formattedText = null)
|
||||
{
|
||||
ShowTooltip(text, ImGui.GetMousePos(), title, id, name, iconId, idKind, formattedText);
|
||||
@@ -114,7 +123,13 @@ namespace HSUI.Helpers
|
||||
_currentIconId = iconId;
|
||||
_formattedSegments = formattedText;
|
||||
|
||||
// calcualte title size
|
||||
// When showing a simple tooltip with no title (e.g. HUD), clear previous title/body so we don't mix with last frame's world tooltip
|
||||
if (title == null)
|
||||
{
|
||||
_currentTooltipTitle = null;
|
||||
}
|
||||
|
||||
// calculate title size
|
||||
_titleSize = Vector2.Zero;
|
||||
if (title != null)
|
||||
{
|
||||
@@ -199,19 +214,17 @@ namespace HSUI.Helpers
|
||||
/// </summary>
|
||||
public void ShowWorldObjectTooltip()
|
||||
{
|
||||
// Don't overwrite if an HUD element already set a tooltip this frame (e.g. Main Menu icon)
|
||||
if (_dataIsValid)
|
||||
return;
|
||||
// Don't show world tooltip when mouse is over an interactive HUD element (e.g. Main Menu bar)
|
||||
if (_mouseOverHudElement)
|
||||
return;
|
||||
try
|
||||
{
|
||||
_worldTooltipConfig ??= ConfigurationManager.Instance.GetConfigObject<WorldObjectTooltipConfig>();
|
||||
if (_worldTooltipConfig == null || !_worldTooltipConfig.Enabled)
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Config not ready yet or not found - silently skip
|
||||
if (_config.DebugTooltips)
|
||||
Plugin.Logger.Warning($"[HSUI Tooltip] WorldObjectTooltipConfig not available: {ex.Message}");
|
||||
return;
|
||||
}
|
||||
|
||||
IGameObject? mouseOverTarget = Plugin.TargetManager.MouseOverTarget;
|
||||
if (mouseOverTarget == null)
|
||||
@@ -294,6 +307,13 @@ namespace HSUI.Helpers
|
||||
{
|
||||
ShowTooltipOnCursor(body, name, 0, "", iconId);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Config not ready yet or not found - silently skip
|
||||
if (_config.DebugTooltips)
|
||||
Plugin.Logger.Warning($"[HSUI Tooltip] WorldObjectTooltipConfig not available: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe uint? GetWorldObjectIconId(IGameObject gameObject)
|
||||
@@ -310,6 +330,7 @@ namespace HSUI.Helpers
|
||||
public void RemoveTooltip()
|
||||
{
|
||||
_dataIsValid = false;
|
||||
_mouseOverHudElement = false;
|
||||
_formattedSegments = null;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user