Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 925c3af558 |
+336
-18
@@ -8,11 +8,27 @@ using HSUI.Interface.GeneralElements;
|
|||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace HSUI.Helpers
|
namespace HSUI.Helpers
|
||||||
{
|
{
|
||||||
|
/// <summary>Colored segment for game-style tooltip formatting.</summary>
|
||||||
|
public readonly struct TooltipSegment
|
||||||
|
{
|
||||||
|
public readonly string Text;
|
||||||
|
public readonly Vector4 Color;
|
||||||
|
|
||||||
|
public TooltipSegment(string text, Vector4 color)
|
||||||
|
{
|
||||||
|
Text = text ?? "";
|
||||||
|
Color = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>When showing an ID in the tooltip title, use Action for action IDs or Status for status effect IDs.</summary>
|
/// <summary>When showing an ID in the tooltip title, use Action for action IDs or Status for status effect IDs.</summary>
|
||||||
public enum TooltipIdKind
|
public enum TooltipIdKind
|
||||||
{
|
{
|
||||||
@@ -54,7 +70,7 @@ namespace HSUI.Helpers
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private float MaxWidth => Math.Max(160, 340 * ImGuiHelpers.GlobalScale * _config.TooltipScale);
|
private float MaxWidth => Math.Max(160, _config.TooltipMaxWidth * ImGuiHelpers.GlobalScale * _config.TooltipScale);
|
||||||
private float Margin => Math.Max(4, 5 * ImGuiHelpers.GlobalScale * _config.TooltipScale);
|
private float Margin => Math.Max(4, 5 * ImGuiHelpers.GlobalScale * _config.TooltipScale);
|
||||||
|
|
||||||
private TooltipsConfig _config => ConfigurationManager.Instance.GetConfigObject<TooltipsConfig>();
|
private TooltipsConfig _config => ConfigurationManager.Instance.GetConfigObject<TooltipsConfig>();
|
||||||
@@ -65,6 +81,7 @@ namespace HSUI.Helpers
|
|||||||
private Vector2 _titleSize;
|
private Vector2 _titleSize;
|
||||||
private string? _previousRawText = null;
|
private string? _previousRawText = null;
|
||||||
private uint? _currentIconId = null;
|
private uint? _currentIconId = null;
|
||||||
|
private List<TooltipSegment>? _formattedSegments = null;
|
||||||
|
|
||||||
private Vector2 _position;
|
private Vector2 _position;
|
||||||
private Vector2 _size;
|
private Vector2 _size;
|
||||||
@@ -73,12 +90,12 @@ namespace HSUI.Helpers
|
|||||||
|
|
||||||
private const float IconSize = 24f;
|
private const float IconSize = 24f;
|
||||||
|
|
||||||
public void ShowTooltipOnCursor(string text, string? title = null, uint id = 0, string name = "", uint? iconId = null, TooltipIdKind idKind = TooltipIdKind.None)
|
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);
|
ShowTooltip(text, ImGui.GetMousePos(), title, id, name, iconId, idKind, formattedText);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ShowTooltip(string text, Vector2 position, string? title = null, uint id = 0, string name = "", uint? iconId = null, TooltipIdKind idKind = TooltipIdKind.None)
|
public void ShowTooltip(string text, Vector2 position, string? title = null, uint id = 0, string name = "", uint? iconId = null, TooltipIdKind idKind = TooltipIdKind.None, List<TooltipSegment>? formattedText = null)
|
||||||
{
|
{
|
||||||
if (text == null)
|
if (text == null)
|
||||||
{
|
{
|
||||||
@@ -95,6 +112,7 @@ namespace HSUI.Helpers
|
|||||||
}
|
}
|
||||||
|
|
||||||
_currentIconId = iconId;
|
_currentIconId = iconId;
|
||||||
|
_formattedSegments = formattedText;
|
||||||
|
|
||||||
// calcualte title size
|
// calcualte title size
|
||||||
_titleSize = Vector2.Zero;
|
_titleSize = Vector2.Zero;
|
||||||
@@ -124,20 +142,42 @@ namespace HSUI.Helpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculate text size
|
// calculate text size (build combined text from segments for sizing)
|
||||||
string fontIdForText = _config.FontID ?? FontsConfig.DefaultSmallFontKey;
|
string fontIdForText = _config.FontID ?? FontsConfig.DefaultSmallFontKey;
|
||||||
using (FontsManager.Instance.PushFont(fontIdForText))
|
using (FontsManager.Instance.PushFont(fontIdForText))
|
||||||
|
{
|
||||||
|
float lineHeight = ImGui.GetTextLineHeight();
|
||||||
|
if (_formattedSegments != null && _formattedSegments.Count > 0)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
int newlineCount = 0;
|
||||||
|
foreach (var seg in _formattedSegments)
|
||||||
|
{
|
||||||
|
sb.Append(seg.Text);
|
||||||
|
if (seg.Text == "\n") newlineCount++;
|
||||||
|
}
|
||||||
|
_textSize = ImGui.CalcTextSize(sb.ToString(), false, MaxWidth);
|
||||||
|
// Account for Dummy spacing between sections (each \n adds compactSpacing)
|
||||||
|
float compactSpacing = Math.Max(2, lineHeight * _config.TooltipLineSpacing);
|
||||||
|
_textSize.Y += newlineCount * compactSpacing;
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
_textSize = ImGui.CalcTextSize(_currentTooltipText, false, MaxWidth);
|
_textSize = ImGui.CalcTextSize(_currentTooltipText, false, MaxWidth);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
float contentWidth = Math.Max(_titleSize.X, _textSize.X);
|
float fontScale = Math.Max(0.7f, _config.TooltipFontScale);
|
||||||
float contentHeight = _titleSize.Y + _textSize.Y;
|
float contentWidth = Math.Max(_titleSize.X, _textSize.X) * fontScale;
|
||||||
float iconSizeScaled = IconSize * ImGuiHelpers.GlobalScale * _config.TooltipScale;
|
float contentHeight = (_titleSize.Y + _textSize.Y) * fontScale;
|
||||||
|
float iconSizeScaled = IconSize * ImGuiHelpers.GlobalScale * _config.TooltipScale * _config.TooltipIconScale;
|
||||||
float iconGap = _currentIconId.HasValue && _currentIconId.Value > 0 ? iconSizeScaled + Margin : 0;
|
float iconGap = _currentIconId.HasValue && _currentIconId.Value > 0 ? iconSizeScaled + Margin : 0;
|
||||||
|
// Add buffer to prevent clipping (font/scale changes, rounding, padding)
|
||||||
|
float heightBuffer = _config.TooltipHeightBuffer;
|
||||||
|
float minContentHeight = contentHeight + heightBuffer;
|
||||||
_size = new Vector2(
|
_size = new Vector2(
|
||||||
contentWidth + iconGap + Margin * 2,
|
contentWidth + iconGap + Margin * 2,
|
||||||
Math.Max(contentHeight, _currentIconId.HasValue && _currentIconId.Value > 0 ? iconSizeScaled : 0) + Margin * 2);
|
Math.Max(minContentHeight, _currentIconId.HasValue && _currentIconId.Value > 0 ? iconSizeScaled + Margin * 2 : 0) + Margin * 2);
|
||||||
|
|
||||||
// position tooltip using the given coordinates as bottom center
|
// position tooltip using the given coordinates as bottom center
|
||||||
position.X = position.X - _size.X / 2f;
|
position.X = position.X - _size.X / 2f;
|
||||||
@@ -270,6 +310,41 @@ namespace HSUI.Helpers
|
|||||||
public void RemoveTooltip()
|
public void RemoveTooltip()
|
||||||
{
|
{
|
||||||
_dataIsValid = false;
|
_dataIsValid = false;
|
||||||
|
_formattedSegments = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawTooltipBody()
|
||||||
|
{
|
||||||
|
if (_formattedSegments != null && _formattedSegments.Count > 0)
|
||||||
|
{
|
||||||
|
bool afterNewline = true;
|
||||||
|
float lineHeight = ImGui.GetTextLineHeight();
|
||||||
|
float compactSpacing = Math.Max(2, lineHeight * _config.TooltipLineSpacing);
|
||||||
|
|
||||||
|
foreach (var seg in _formattedSegments)
|
||||||
|
{
|
||||||
|
if (seg.Text == "\n")
|
||||||
|
{
|
||||||
|
ImGui.Dummy(new Vector2(0, compactSpacing));
|
||||||
|
afterNewline = true;
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrEmpty(seg.Text))
|
||||||
|
{
|
||||||
|
if (!afterNewline)
|
||||||
|
ImGui.SameLine(0, 0);
|
||||||
|
string text = afterNewline ? seg.Text.TrimStart() : seg.Text;
|
||||||
|
if (!string.IsNullOrEmpty(text))
|
||||||
|
{
|
||||||
|
ImGui.TextColored(seg.Color, text);
|
||||||
|
afterNewline = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui.TextColored(_config.TextColor.Vector, _currentTooltipText);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Draw()
|
public void Draw()
|
||||||
@@ -303,6 +378,7 @@ namespace HSUI.Helpers
|
|||||||
|
|
||||||
ImGui.PushStyleVar(ImGuiStyleVar.WindowRounding, 0);
|
ImGui.PushStyleVar(ImGuiStyleVar.WindowRounding, 0);
|
||||||
ImGui.Begin("DelvUI_tooltip", windowFlags);
|
ImGui.Begin("DelvUI_tooltip", windowFlags);
|
||||||
|
ImGui.SetWindowFontScale(_config.TooltipFontScale);
|
||||||
var drawList = ImGui.GetWindowDrawList();
|
var drawList = ImGui.GetWindowDrawList();
|
||||||
|
|
||||||
drawList.AddRectFilled(_position, _position + _size, _config.BackgroundColor.Base);
|
drawList.AddRectFilled(_position, _position + _size, _config.BackgroundColor.Base);
|
||||||
@@ -316,7 +392,7 @@ namespace HSUI.Helpers
|
|||||||
float globalScaleCorrection = -15 + 15 * ImGuiHelpers.GlobalScale;
|
float globalScaleCorrection = -15 + 15 * ImGuiHelpers.GlobalScale;
|
||||||
|
|
||||||
string fontId = _config.FontID ?? FontsConfig.DefaultSmallFontKey;
|
string fontId = _config.FontID ?? FontsConfig.DefaultSmallFontKey;
|
||||||
float iconSizeScaled = IconSize * ImGuiHelpers.GlobalScale * _config.TooltipScale;
|
float iconSizeScaled = IconSize * ImGuiHelpers.GlobalScale * _config.TooltipScale * _config.TooltipIconScale;
|
||||||
float iconGap = (_currentIconId.HasValue && _currentIconId.Value > 0) ? iconSizeScaled + Margin : 0;
|
float iconGap = (_currentIconId.HasValue && _currentIconId.Value > 0) ? iconSizeScaled + Margin : 0;
|
||||||
float wrapWidth = (_currentIconId.HasValue && _currentIconId.Value > 0)
|
float wrapWidth = (_currentIconId.HasValue && _currentIconId.Value > 0)
|
||||||
? _size.X - iconGap - Margin * 2
|
? _size.X - iconGap - Margin * 2
|
||||||
@@ -342,19 +418,19 @@ namespace HSUI.Helpers
|
|||||||
ImGui.PopTextWrapPos();
|
ImGui.PopTextWrapPos();
|
||||||
}
|
}
|
||||||
|
|
||||||
// text
|
// text (formatted or plain)
|
||||||
using (FontsManager.Instance.PushFont(fontId))
|
using (FontsManager.Instance.PushFont(fontId))
|
||||||
{
|
{
|
||||||
cursorPos = new Vector2(textBlockX, Margin + _titleSize.Y);
|
cursorPos = new Vector2(textBlockX, Margin + _titleSize.Y);
|
||||||
ImGui.SetCursorPos(cursorPos);
|
ImGui.SetCursorPos(cursorPos);
|
||||||
ImGui.PushTextWrapPos(cursorPos.X + wrapWidth + globalScaleCorrection);
|
ImGui.PushTextWrapPos(cursorPos.X + wrapWidth + globalScaleCorrection);
|
||||||
ImGui.TextColored(_config.TextColor.Vector, _currentTooltipText);
|
DrawTooltipBody();
|
||||||
ImGui.PopTextWrapPos();
|
ImGui.PopTextWrapPos();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// text
|
// text (formatted or plain)
|
||||||
using (FontsManager.Instance.PushFont(fontId))
|
using (FontsManager.Instance.PushFont(fontId))
|
||||||
{
|
{
|
||||||
var cursorPos = new Vector2(textBlockX, Margin);
|
var cursorPos = new Vector2(textBlockX, Margin);
|
||||||
@@ -362,11 +438,12 @@ namespace HSUI.Helpers
|
|||||||
|
|
||||||
ImGui.SetCursorPos(cursorPos);
|
ImGui.SetCursorPos(cursorPos);
|
||||||
ImGui.PushTextWrapPos(cursorPos.X + textWidth + globalScaleCorrection);
|
ImGui.PushTextWrapPos(cursorPos.X + textWidth + globalScaleCorrection);
|
||||||
ImGui.TextColored(_config.TextColor.Vector, _currentTooltipText);
|
DrawTooltipBody();
|
||||||
ImGui.PopTextWrapPos();
|
ImGui.PopTextWrapPos();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui.SetWindowFontScale(1f);
|
||||||
ImGui.End();
|
ImGui.End();
|
||||||
ImGui.PopStyleVar();
|
ImGui.PopStyleVar();
|
||||||
|
|
||||||
@@ -375,6 +452,211 @@ namespace HSUI.Helpers
|
|||||||
RemoveTooltip();
|
RemoveTooltip();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses action/ability description text into colored segments for game-style tooltips.
|
||||||
|
/// Adds newlines before section headers and colors: section labels (Additional Effect, Duration) green,
|
||||||
|
/// status names (e.g. Blood of the Dragon) yellow, secondary labels grey.
|
||||||
|
/// </summary>
|
||||||
|
public static List<TooltipSegment> FormatActionTooltip(string description, Vector4 textColor, Vector4 sectionLabelColor, Vector4 statusNameColor)
|
||||||
|
{
|
||||||
|
var result = new List<TooltipSegment>();
|
||||||
|
if (string.IsNullOrEmpty(description))
|
||||||
|
return result;
|
||||||
|
|
||||||
|
// Pattern: section labels to color (Additional Effect, Duration, X Effect, Maximum Charges, Blood Gauge Cost, Combo, roles)
|
||||||
|
var sectionRegex = new Regex(@"\b(Additional Effect:|Duration:|Maximum Charges:|Blood Gauge Cost:|Combo Action:|Combo Potency:|Combo Bonus:|Tank:|Melee:|Ranged:|Caster:|Healer:|[A-Za-z][A-Za-z0-9\s]+ Effect:)", RegexOptions.None);
|
||||||
|
// Pattern: "Grants X X Effect:" - captures status name X
|
||||||
|
var grantsRegex = new Regex(@"Grants\s+(.+?)\s+\1\s+Effect:", RegexOptions.Singleline);
|
||||||
|
|
||||||
|
int lastEnd = 0;
|
||||||
|
foreach (Match m in sectionRegex.Matches(description))
|
||||||
|
{
|
||||||
|
string chunk = description.Substring(lastEnd, m.Index - lastEnd).TrimEnd();
|
||||||
|
if (!string.IsNullOrEmpty(chunk))
|
||||||
|
{
|
||||||
|
// Check for "Grants StatusName" in this chunk to color status name yellow
|
||||||
|
var grantsMatch = grantsRegex.Match(chunk);
|
||||||
|
if (grantsMatch.Success)
|
||||||
|
{
|
||||||
|
string statusName = grantsMatch.Groups[1].Value.Trim();
|
||||||
|
string grantsPrefix = "Grants " + statusName;
|
||||||
|
int idx = chunk.IndexOf(grantsPrefix, StringComparison.Ordinal);
|
||||||
|
if (idx >= 0)
|
||||||
|
{
|
||||||
|
if (idx > 0)
|
||||||
|
result.Add(new TooltipSegment(chunk.Substring(0, idx), textColor));
|
||||||
|
result.Add(new TooltipSegment("Grants ", textColor));
|
||||||
|
result.Add(new TooltipSegment(statusName + " ", statusNameColor));
|
||||||
|
int after = idx + grantsPrefix.Length;
|
||||||
|
if (after < chunk.Length)
|
||||||
|
result.Add(new TooltipSegment(chunk.Substring(after), textColor));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
result.Add(new TooltipSegment(chunk, textColor));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
result.Add(new TooltipSegment(chunk, textColor));
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Add(new TooltipSegment("\n", textColor));
|
||||||
|
result.Add(new TooltipSegment(m.Value + " ", sectionLabelColor));
|
||||||
|
// Add newline so description wraps below label like stock game (not floating beside it)
|
||||||
|
if (m.Value.IndexOf("Effect:", StringComparison.OrdinalIgnoreCase) >= 0)
|
||||||
|
result.Add(new TooltipSegment("\n", textColor));
|
||||||
|
lastEnd = m.Index + m.Length;
|
||||||
|
}
|
||||||
|
if (lastEnd < description.Length)
|
||||||
|
{
|
||||||
|
string tail = description.Substring(lastEnd).TrimStart();
|
||||||
|
if (!string.IsNullOrEmpty(tail))
|
||||||
|
{
|
||||||
|
string valuePart = "";
|
||||||
|
string notePart = "";
|
||||||
|
bool hasValue = false;
|
||||||
|
|
||||||
|
int newlineIdx = tail.IndexOf('\n');
|
||||||
|
if (newlineIdx >= 0)
|
||||||
|
{
|
||||||
|
// "2\nCannot be executed..." - value on first line, note on next
|
||||||
|
valuePart = tail.Substring(0, newlineIdx).TrimEnd();
|
||||||
|
notePart = tail.Substring(newlineIdx + 1).TrimStart();
|
||||||
|
hasValue = Regex.IsMatch(valuePart, @"^\d+[ms]?$");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// "2 Cannot be executed while bound." - value + space + note (no newline)
|
||||||
|
var valueNoteMatch = Regex.Match(tail, @"^(\d+[ms]?)\s+([A-Z].+)$", RegexOptions.Singleline);
|
||||||
|
if (valueNoteMatch.Success)
|
||||||
|
{
|
||||||
|
valuePart = valueNoteMatch.Groups[1].Value;
|
||||||
|
notePart = valueNoteMatch.Groups[2].Value;
|
||||||
|
hasValue = true;
|
||||||
|
}
|
||||||
|
else if (Regex.IsMatch(tail, @"^\d+[ms]?$"))
|
||||||
|
{
|
||||||
|
// Plain value only: "50" (Blood Gauge Cost), "2" (charges) - stay on same line as label
|
||||||
|
valuePart = tail;
|
||||||
|
hasValue = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add valuePart when present - section content (e.g. "Restore party HP..." for Healer, "2" for charges)
|
||||||
|
if (!string.IsNullOrEmpty(valuePart))
|
||||||
|
{
|
||||||
|
result.Add(new TooltipSegment(valuePart, textColor));
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrEmpty(notePart))
|
||||||
|
{
|
||||||
|
result.Add(new TooltipSegment("\n", textColor));
|
||||||
|
result.Add(new TooltipSegment(notePart, textColor));
|
||||||
|
}
|
||||||
|
else if (!hasValue)
|
||||||
|
{
|
||||||
|
// Standalone note: "(Duration can be extended...)", "Cannot be executed...", footnotes
|
||||||
|
result.Add(new TooltipSegment("\n", textColor));
|
||||||
|
result.Add(new TooltipSegment(tail, textColor));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Builds formatted segments from a full action tooltip body (stats + description).
|
||||||
|
/// Used when UseGameStyleFormatting is enabled.
|
||||||
|
/// Stats are placed first, then exactly one newline, then description below.
|
||||||
|
/// </summary>
|
||||||
|
public static List<TooltipSegment>? BuildFormattedActionTooltipBody(string body, TooltipsConfig config)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(body) || !config.UseGameStyleFormatting)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var result = new List<TooltipSegment>();
|
||||||
|
var textColor = config.TextColor.Vector;
|
||||||
|
var sectionColor = config.SectionLabelColor.Vector;
|
||||||
|
var statusColor = config.StatusNameColor.Vector;
|
||||||
|
var secondaryColor = config.SecondaryLabelColor.Vector;
|
||||||
|
|
||||||
|
var blocks = body.Split(new[] { "\n\n" }, StringSplitOptions.None);
|
||||||
|
int descStart = 0;
|
||||||
|
bool hasStats = false;
|
||||||
|
|
||||||
|
// Stats block: may contain "Potency: X" and "Cast: ... | Recast: ..." on separate lines
|
||||||
|
for (int i = 0; i < blocks.Length; i++)
|
||||||
|
{
|
||||||
|
string block = blocks[i].Trim();
|
||||||
|
var lines = block.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
bool blockIsStats = false;
|
||||||
|
foreach (string line in lines)
|
||||||
|
{
|
||||||
|
string part = line.Trim();
|
||||||
|
if (part.StartsWith("Potency:", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
if (hasStats) result.Add(new TooltipSegment("\n", textColor));
|
||||||
|
result.Add(new TooltipSegment("Potency:", secondaryColor));
|
||||||
|
result.Add(new TooltipSegment(" " + part.Substring(8).TrimStart(), textColor));
|
||||||
|
hasStats = true;
|
||||||
|
blockIsStats = true;
|
||||||
|
}
|
||||||
|
else if (part.Contains("Cast:") || part.Contains("Recast:"))
|
||||||
|
{
|
||||||
|
if (hasStats) result.Add(new TooltipSegment("\n", textColor));
|
||||||
|
var statsSegs = FormatActionStats(part, textColor, secondaryColor);
|
||||||
|
result.AddRange(statsSegs);
|
||||||
|
hasStats = true;
|
||||||
|
blockIsStats = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!blockIsStats)
|
||||||
|
{
|
||||||
|
descStart = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
descStart = i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Description: exactly one newline after stats, then description with compact section spacing
|
||||||
|
string desc = string.Join("\n", blocks.Skip(descStart)).Trim();
|
||||||
|
if (!string.IsNullOrEmpty(desc))
|
||||||
|
{
|
||||||
|
if (hasStats) result.Add(new TooltipSegment("\n", textColor));
|
||||||
|
var descSegs = FormatActionTooltip(desc, textColor, sectionColor, statusColor);
|
||||||
|
result.AddRange(descSegs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.Count > 0 ? result : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Formats action stats (Cast, Recast, Range, Radius) as colored segments.
|
||||||
|
/// </summary>
|
||||||
|
public static List<TooltipSegment> FormatActionStats(string statsLine, Vector4 textColor, Vector4 secondaryLabelColor)
|
||||||
|
{
|
||||||
|
var result = new List<TooltipSegment>();
|
||||||
|
if (string.IsNullOrEmpty(statsLine))
|
||||||
|
return result;
|
||||||
|
|
||||||
|
// statsLine format: "Cast: Instant | Recast: 60.0s | Range: 15y | Radius: 15y"
|
||||||
|
var parts = statsLine.Split(new[] { " | " }, StringSplitOptions.None);
|
||||||
|
for (int i = 0; i < parts.Length; i++)
|
||||||
|
{
|
||||||
|
if (i > 0)
|
||||||
|
result.Add(new TooltipSegment(" | ", textColor));
|
||||||
|
int colonIdx = parts[i].IndexOf(':');
|
||||||
|
if (colonIdx >= 0)
|
||||||
|
{
|
||||||
|
result.Add(new TooltipSegment(parts[i].Substring(0, colonIdx + 1), secondaryLabelColor));
|
||||||
|
result.Add(new TooltipSegment(parts[i].Substring(colonIdx + 1), textColor));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result.Add(new TooltipSegment(parts[i], textColor));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private Vector2 ConstrainPosition(Vector2 position, Vector2 size)
|
private Vector2 ConstrainPosition(Vector2 position, Vector2 size)
|
||||||
{
|
{
|
||||||
var screenSize = ImGui.GetWindowViewport().Size;
|
var screenSize = ImGui.GetWindowViewport().Size;
|
||||||
@@ -412,21 +694,57 @@ namespace HSUI.Helpers
|
|||||||
public string? FontID = null;
|
public string? FontID = null;
|
||||||
|
|
||||||
[DragFloat("Tooltip Scale", min = 0.5f, max = 2f, help = "Scale the tooltip window size. Useful for large resolutions.")]
|
[DragFloat("Tooltip Scale", min = 0.5f, max = 2f, help = "Scale the tooltip window size. Useful for large resolutions.")]
|
||||||
[Order(6)]
|
[Order(5)]
|
||||||
public float TooltipScale = 1f;
|
public float TooltipScale = 1f;
|
||||||
|
|
||||||
[Checkbox("Show Status Effects IDs")]
|
[DragFloat("Tooltip Max Width", min = 160f, max = 600f, help = "Maximum width of the tooltip box in pixels.")]
|
||||||
|
[Order(6)]
|
||||||
|
public float TooltipMaxWidth = 340f;
|
||||||
|
|
||||||
|
[DragFloat("Line Spacing", min = 0.2f, max = 1.5f, help = "Spacing between lines and sections (1 = normal, lower = compact).")]
|
||||||
[Order(7)]
|
[Order(7)]
|
||||||
|
public float TooltipLineSpacing = 0.6f;
|
||||||
|
|
||||||
|
[DragFloat("Font Scale", min = 0.7f, max = 1.5f, help = "Scale factor for tooltip font size.")]
|
||||||
|
[Order(8)]
|
||||||
|
public float TooltipFontScale = 1f;
|
||||||
|
|
||||||
|
[DragFloat("Icon Scale", min = 0.5f, max = 2f, help = "Scale factor for the ability/status icon in tooltips.")]
|
||||||
|
[Order(9)]
|
||||||
|
public float TooltipIconScale = 1f;
|
||||||
|
|
||||||
|
[DragFloat("Height Buffer", min = 0f, max = 80f, help = "Extra space at bottom of tooltip to prevent clipping. Adjust if text is cut off.")]
|
||||||
|
[Order(10)]
|
||||||
|
public float TooltipHeightBuffer = 28f;
|
||||||
|
|
||||||
|
[Checkbox("Show Status Effects IDs")]
|
||||||
|
[Order(11)]
|
||||||
public bool ShowStatusIDs = false;
|
public bool ShowStatusIDs = false;
|
||||||
|
|
||||||
[Checkbox("Show Action ID", help = "Show action ID in hotbar and action tooltips.")]
|
[Checkbox("Show Action ID", help = "Show action ID in hotbar and action tooltips.")]
|
||||||
[Order(8)]
|
[Order(12)]
|
||||||
public bool ShowActionIDs = false;
|
public bool ShowActionIDs = false;
|
||||||
|
|
||||||
[Checkbox("Show Source Name")]
|
[Checkbox("Show Source Name")]
|
||||||
[Order(10)]
|
[Order(13)]
|
||||||
public bool ShowSourceName = false;
|
public bool ShowSourceName = false;
|
||||||
|
|
||||||
|
[Checkbox("Game-style formatting", help = "Color section labels (Additional Effect, Duration) green, status names yellow, and add new lines between sections like the base game tooltips.")]
|
||||||
|
[Order(14)]
|
||||||
|
public bool UseGameStyleFormatting = true;
|
||||||
|
|
||||||
|
[ColorEdit4("Section Label Color", help = "Color for labels like 'Additional Effect:', 'Duration:'.")]
|
||||||
|
[Order(15)]
|
||||||
|
public PluginConfigColor SectionLabelColor = new PluginConfigColor(new(0f, 255f / 255f, 0f, 1f));
|
||||||
|
|
||||||
|
[ColorEdit4("Status Name Color", help = "Color for buff/status effect names like 'Blood of the Dragon'.")]
|
||||||
|
[Order(16)]
|
||||||
|
public PluginConfigColor StatusNameColor = new PluginConfigColor(new(255f / 255f, 255f / 255f, 0f, 1f));
|
||||||
|
|
||||||
|
[ColorEdit4("Secondary Label Color", help = "Color for Cast, Recast, Range, Radius labels.")]
|
||||||
|
[Order(17)]
|
||||||
|
public PluginConfigColor SecondaryLabelColor = new PluginConfigColor(new(204f / 255f, 204f / 255f, 204f / 255f, 1f));
|
||||||
|
|
||||||
[ColorEdit4("Background Color")]
|
[ColorEdit4("Background Color")]
|
||||||
[Order(15)]
|
[Order(15)]
|
||||||
public PluginConfigColor BackgroundColor = new PluginConfigColor(new(19f / 255f, 19f / 255f, 19f / 255f, 190f / 250f));
|
public PluginConfigColor BackgroundColor = new PluginConfigColor(new(19f / 255f, 19f / 255f, 19f / 255f, 190f / 250f));
|
||||||
|
|||||||
@@ -370,7 +370,9 @@ namespace HSUI.Interface.GeneralElements
|
|||||||
if (!string.IsNullOrEmpty(title) || !string.IsNullOrEmpty(text))
|
if (!string.IsNullOrEmpty(title) || !string.IsNullOrEmpty(text))
|
||||||
{
|
{
|
||||||
string body = string.IsNullOrEmpty(text) ? title : text;
|
string body = string.IsNullOrEmpty(text) ? title : text;
|
||||||
TooltipsHelper.Instance.ShowTooltipOnCursor(body, title, slot.ActionId, "", slot.IconId > 0 ? slot.IconId : null, Helpers.TooltipIdKind.Action);
|
var tooltipConfig = ConfigurationManager.Instance?.GetConfigObject<HSUI.Helpers.TooltipsConfig>();
|
||||||
|
var formatted = tooltipConfig != null ? TooltipsHelper.BuildFormattedActionTooltipBody(body, tooltipConfig) : null;
|
||||||
|
TooltipsHelper.Instance.ShowTooltipOnCursor(body, title, slot.ActionId, "", slot.IconId > 0 ? slot.IconId : null, Helpers.TooltipIdKind.Action, formatted);
|
||||||
if (IsTooltipDebugEnabled())
|
if (IsTooltipDebugEnabled())
|
||||||
Plugin.Logger.Information($"[HSUI Tooltip DBG] ActionBar tooltip (main overlay): slot={i} title='{title}'");
|
Plugin.Logger.Information($"[HSUI Tooltip DBG] ActionBar tooltip (main overlay): slot={i} title='{title}'");
|
||||||
}
|
}
|
||||||
@@ -429,7 +431,9 @@ namespace HSUI.Interface.GeneralElements
|
|||||||
if (!string.IsNullOrEmpty(title) || !string.IsNullOrEmpty(text))
|
if (!string.IsNullOrEmpty(title) || !string.IsNullOrEmpty(text))
|
||||||
{
|
{
|
||||||
string body = string.IsNullOrEmpty(text) ? title : text;
|
string body = string.IsNullOrEmpty(text) ? title : text;
|
||||||
TooltipsHelper.Instance.ShowTooltipOnCursor(body, title, slot.ActionId, "", slot.IconId > 0 ? slot.IconId : null, Helpers.TooltipIdKind.Action);
|
var tooltipConfig = ConfigurationManager.Instance?.GetConfigObject<HSUI.Helpers.TooltipsConfig>();
|
||||||
|
var formatted = tooltipConfig != null ? TooltipsHelper.BuildFormattedActionTooltipBody(body, tooltipConfig) : null;
|
||||||
|
TooltipsHelper.Instance.ShowTooltipOnCursor(body, title, slot.ActionId, "", slot.IconId > 0 ? slot.IconId : null, Helpers.TooltipIdKind.Action, formatted);
|
||||||
if (IsTooltipDebugEnabled())
|
if (IsTooltipDebugEnabled())
|
||||||
Plugin.Logger.Information($"[HSUI Tooltip DBG] ActionBar tooltip (overlay): slot={i} title='{title}'");
|
Plugin.Logger.Information($"[HSUI Tooltip DBG] ActionBar tooltip (overlay): slot={i} title='{title}'");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user