using Dalamud.Bindings.ImGui; using Dalamud.Interface; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using LightlessSync.API.Dto.Group; using LightlessSync.LightlessConfiguration; using LightlessSync.PlayerData.Pairs; using LightlessSync.Services.Mediator; using LightlessSync.Services.ServerConfiguration; using LightlessSync.UI.Style; using LightlessSync.Utils; using System; using System.Collections.Generic; using System.Numerics; using System.Text; namespace LightlessSync.UI.Handlers; public class IdDisplayHandler { private readonly LightlessConfigService _lightlessConfigService; private readonly PlayerPerformanceConfigService _playerPerformanceConfigService; private readonly LightlessMediator _mediator; private readonly ServerConfigurationManager _serverManager; private readonly Dictionary _showIdForEntry = new(StringComparer.Ordinal); private string _editComment = string.Empty; private string _editEntry = string.Empty; private bool _editIsUid = false; private string _lastMouseOverUid = string.Empty; private bool _popupShown = false; private DateTime? _popupTime; private Vector4 _currentBg = new(0.15f, 0.15f, 0.15f, 1f); private float _highlightBoost; public IdDisplayHandler( LightlessMediator mediator, ServerConfigurationManager serverManager, LightlessConfigService lightlessConfigService, PlayerPerformanceConfigService playerPerformanceConfigService) { _mediator = mediator; _serverManager = serverManager; _lightlessConfigService = lightlessConfigService; _playerPerformanceConfigService = playerPerformanceConfigService; } public void DrawGroupText(string id, GroupFullInfoDto group, float textPosX, Func editBoxWidth) { ImGui.SameLine(textPosX); (bool textIsUid, string playerText) = GetGroupText(group); if (!string.Equals(_editEntry, group.GID, StringComparison.Ordinal)) { ImGui.AlignTextToFramePadding(); using (ImRaii.PushFont(UiBuilder.MonoFont, textIsUid)) ImGui.TextUnformatted(playerText); if (ImGui.IsItemHovered()) { ImGui.SetTooltip("Left click to switch between ID display and alias" + Environment.NewLine + "Right click to edit notes for this syncshell" + Environment.NewLine + "Middle Mouse Button to open syncshell profile in a separate window"); } if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) { var prevState = textIsUid; if (_showIdForEntry.TryGetValue(group.GID, out bool value)) { prevState = value; } _showIdForEntry[group.GID] = !prevState; } if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) { if (_editIsUid) { _serverManager.SetNoteForUid(_editEntry, _editComment, save: true); } else { _serverManager.SetNoteForGid(_editEntry, _editComment, save: true); } _editComment = _serverManager.GetNoteForGid(group.GID) ?? string.Empty; _editEntry = group.GID; _editIsUid = false; } if (ImGui.IsItemClicked(ImGuiMouseButton.Middle)) { _mediator.Publish(new GroupProfileOpenStandaloneMessage(group)); } } else { ImGui.AlignTextToFramePadding(); ImGui.SetNextItemWidth(editBoxWidth.Invoke()); if (ImGui.InputTextWithHint("", "Name/Notes", ref _editComment, 255, ImGuiInputTextFlags.EnterReturnsTrue)) { _serverManager.SetNoteForGid(group.GID, _editComment, save: true); _editEntry = string.Empty; } if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) { _editEntry = string.Empty; } UiSharedService.AttachToolTip("Hit ENTER to save\nRight click to cancel"); } } public void DrawPairText(string id, Pair pair, float textPosX, Func editBoxWidth) { ImGui.SameLine(textPosX); (bool textIsUid, string playerText) = GetPlayerText(pair); var compactPerformanceText = BuildCompactPerformanceUsageText(pair); if (!string.Equals(_editEntry, pair.UserData.UID, StringComparison.Ordinal)) { ImGui.AlignTextToFramePadding(); var rowStart = ImGui.GetCursorScreenPos(); var rowWidth = MathF.Max(editBoxWidth.Invoke(), 0f); var rowRightLimit = rowStart.X + rowWidth; var font = textIsUid ? UiBuilder.MonoFont : ImGui.GetFont(); Vector4? textColor = null; Vector4? glowColor = null; if (pair.UserData.HasVanity) { if (!string.IsNullOrWhiteSpace(pair.UserData.TextColorHex)) { textColor = UIColors.HexToRgba(pair.UserData.TextColorHex); } if (!string.IsNullOrWhiteSpace(pair.UserData.TextGlowColorHex)) { glowColor = UIColors.HexToRgba(pair.UserData.TextGlowColorHex); } } var useVanityColors = _lightlessConfigService.Current.useColoredUIDs && (textColor != null || glowColor != null); var seString = useVanityColors ? SeStringUtils.BuildFormattedPlayerName(playerText, textColor, glowColor) : SeStringUtils.BuildPlain(playerText); var drawList = ImGui.GetWindowDrawList(); bool useHighlight = false; float highlightPadX = 0f; float highlightPadY = 0f; if (useVanityColors) { float boost = Luminance.ComputeHighlight(textColor, glowColor); if (boost > 0f) { var style = ImGui.GetStyle(); useHighlight = true; highlightPadX = MathF.Max(style.FramePadding.X * 0.6f, 2f * ImGuiHelpers.GlobalScale); highlightPadY = MathF.Max(style.FramePadding.Y * 0.55f, 1.25f * ImGuiHelpers.GlobalScale); drawList.ChannelsSplit(2); drawList.ChannelsSetCurrent(1); _highlightBoost = boost; } else { _highlightBoost = 0f; } } Vector2 itemMin; Vector2 itemMax; using (ImRaii.PushFont(font, textIsUid)) { SeStringUtils.RenderSeStringWithHitbox(seString, rowStart, font, pair.UserData.UID); itemMin = ImGui.GetItemRectMin(); itemMax = ImGui.GetItemRectMax(); } if (useHighlight) { var style = ImGui.GetStyle(); var frameHeight = ImGui.GetFrameHeight(); var rowTop = rowStart.Y - style.FramePadding.Y; var rowBottom = rowTop + frameHeight; var highlightMin = new Vector2(itemMin.X - highlightPadX, rowTop - highlightPadY); var highlightMax = new Vector2(itemMax.X + highlightPadX, rowBottom + highlightPadY); var windowPos = ImGui.GetWindowPos(); var contentMin = windowPos + ImGui.GetWindowContentRegionMin(); var contentMax = windowPos + ImGui.GetWindowContentRegionMax(); highlightMin.X = MathF.Max(highlightMin.X, contentMin.X); highlightMax.X = MathF.Min(highlightMax.X, contentMax.X); highlightMin.Y = MathF.Max(highlightMin.Y, contentMin.Y); highlightMax.Y = MathF.Min(highlightMax.Y, contentMax.Y); var highlightColor = new Vector4( 0.25f + _highlightBoost, 0.25f + _highlightBoost, 0.25f + _highlightBoost, 1f ); highlightColor = Luminance.BackgroundContrast(textColor, glowColor, highlightColor, ref _currentBg); float rounding = style.FrameRounding > 0f ? style.FrameRounding : 5f * ImGuiHelpers.GlobalScale; drawList.ChannelsSetCurrent(0); drawList.AddRectFilled(highlightMin, highlightMax, ImGui.GetColorU32(highlightColor), rounding); var borderColor = style.Colors[(int)ImGuiCol.Border]; borderColor.W *= 0.25f; drawList.AddRect(highlightMin, highlightMax, ImGui.GetColorU32(borderColor), rounding); drawList.ChannelsMerge(); } var nameRectMin = ImGui.GetItemRectMin(); var nameRectMax = ImGui.GetItemRectMax(); if (ImGui.IsItemHovered()) { if (!string.Equals(_lastMouseOverUid, id, StringComparison.Ordinal)) { _popupTime = DateTime.UtcNow.AddSeconds(_lightlessConfigService.Current.ProfileDelay); } _lastMouseOverUid = id; if (_popupTime > DateTime.UtcNow || !_lightlessConfigService.Current.ProfilesShow) { ImGui.SetTooltip("Left click to switch between UID display and nick" + Environment.NewLine + "Right click to change nick for " + pair.UserData.AliasOrUID + Environment.NewLine + "Middle Mouse Button to open their profile in a separate window"); } else if (_popupTime < DateTime.UtcNow && !_popupShown) { _popupShown = true; _mediator.Publish(new ProfilePopoutToggle(pair)); } } else { if (string.Equals(_lastMouseOverUid, id, StringComparison.Ordinal)) { _mediator.Publish(new ProfilePopoutToggle(Pair: null)); _lastMouseOverUid = string.Empty; _popupShown = false; } } if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) { var prevState = textIsUid; if (_showIdForEntry.ContainsKey(pair.UserData.UID)) { prevState = _showIdForEntry[pair.UserData.UID]; } _showIdForEntry[pair.UserData.UID] = !prevState; } if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) { if (_editIsUid) { _serverManager.SetNoteForUid(_editEntry, _editComment, save: true); } else { _serverManager.SetNoteForGid(_editEntry, _editComment, save: true); } _editComment = pair.GetNote() ?? string.Empty; _editEntry = pair.UserData.UID; _editIsUid = true; } if (ImGui.IsItemClicked(ImGuiMouseButton.Middle)) { _mediator.Publish(new ProfileOpenStandaloneMessage(pair)); } if (!string.IsNullOrEmpty(compactPerformanceText)) { ImGui.SameLine(); const float compactFontScale = 0.85f; ImGui.SetWindowFontScale(compactFontScale); var compactHeight = ImGui.GetTextLineHeight(); var nameHeight = nameRectMax.Y - nameRectMin.Y; var targetPos = ImGui.GetCursorScreenPos(); var availableWidth = MathF.Max(rowRightLimit - targetPos.X, 0f); var centeredY = nameRectMin.Y + MathF.Max((nameHeight - compactHeight) * 0.5f, 0f); float verticalOffset = 1f * ImGuiHelpers.GlobalScale; centeredY += verticalOffset; ImGui.SetCursorScreenPos(new Vector2(targetPos.X, centeredY)); var performanceText = string.Empty; var wasTruncated = false; if (availableWidth > 0f) { performanceText = TruncateTextToWidth(compactPerformanceText, availableWidth, out wasTruncated); } ImGui.TextDisabled(performanceText); ImGui.SetWindowFontScale(1f); if (wasTruncated && ImGui.IsItemHovered()) { ImGui.SetTooltip(compactPerformanceText); } } } else { ImGui.AlignTextToFramePadding(); ImGui.SetNextItemWidth(MathF.Max(editBoxWidth.Invoke(), 0f)); if (ImGui.InputTextWithHint("##" + pair.UserData.UID, "Nick/Notes", ref _editComment, 255, ImGuiInputTextFlags.EnterReturnsTrue)) { _serverManager.SetNoteForUid(pair.UserData.UID, _editComment); _serverManager.SaveNotes(); _editEntry = string.Empty; } if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) { _editEntry = string.Empty; } UiSharedService.AttachToolTip("Hit ENTER to save\nRight click to cancel"); } } public (bool isGid, string text) GetGroupText(GroupFullInfoDto group) { var textIsGid = true; bool showUidInsteadOfName = ShowGidInsteadOfName(group); string? groupText = _serverManager.GetNoteForGid(group.GID); if (!showUidInsteadOfName && groupText != null) { if (string.IsNullOrEmpty(groupText)) { groupText = group.GroupAliasOrGID; } else { textIsGid = false; } } else { groupText = group.GroupAliasOrGID; } return (textIsGid, groupText!); } public (bool isUid, string text) GetPlayerText(Pair pair) { var textIsUid = true; bool showUidInsteadOfName = ShowUidInsteadOfName(pair); string? playerText = _serverManager.GetNoteForUid(pair.UserData.UID); if (!showUidInsteadOfName && playerText != null) { if (string.IsNullOrEmpty(playerText)) { playerText = pair.UserData.AliasOrUID; } else { textIsUid = false; } } else { playerText = pair.UserData.AliasOrUID; } if (_lightlessConfigService.Current.ShowCharacterNameInsteadOfNotesForVisible && pair.IsVisible && !showUidInsteadOfName) { playerText = pair.PlayerName; textIsUid = false; if (_lightlessConfigService.Current.PreferNotesOverNamesForVisible) { var note = pair.GetNote(); if (note != null) { playerText = note; } } } return (textIsUid, playerText!); } private string? BuildCompactPerformanceUsageText(Pair pair) { var config = _playerPerformanceConfigService.Current; if (!config.ShowPerformanceIndicator || !config.ShowPerformanceUsageNextToName) { return null; } var vramBytes = pair.LastAppliedApproximateEffectiveVRAMBytes >= 0 ? pair.LastAppliedApproximateEffectiveVRAMBytes : pair.LastAppliedApproximateVRAMBytes; var triangleCount = pair.LastAppliedDataTris; if (vramBytes < 0 && triangleCount < 0) { return null; } var segments = new List(2); if (vramBytes >= 0) { segments.Add(UiSharedService.ByteToString(vramBytes)); } if (triangleCount >= 0) { segments.Add(FormatTriangleCount(triangleCount)); } return segments.Count == 0 ? null : string.Join(" / ", segments); } private static string FormatTriangleCount(long triangleCount) { if (triangleCount < 0) { return string.Empty; } if (triangleCount >= 1_000_000) { return FormattableString.Invariant($"{triangleCount / 1_000_000d:0.#}m tris"); } if (triangleCount >= 1_000) { return FormattableString.Invariant($"{triangleCount / 1_000d:0.#}k tris"); } return $"{triangleCount} tris"; } internal void Clear() { _editEntry = string.Empty; _editComment = string.Empty; } internal void OpenProfile(Pair entry) { _mediator.Publish(new ProfileOpenStandaloneMessage(entry)); } private bool ShowGidInsteadOfName(GroupFullInfoDto group) { _showIdForEntry.TryGetValue(group.GID, out var showidInsteadOfName); return showidInsteadOfName; } private bool ShowUidInsteadOfName(Pair pair) { _showIdForEntry.TryGetValue(pair.UserData.UID, out var showidInsteadOfName); return showidInsteadOfName; } private static string TruncateTextToWidth(string text, float maxWidth, out bool wasTruncated) { wasTruncated = false; if (string.IsNullOrEmpty(text) || maxWidth <= 0f) { return string.Empty; } var fullWidth = ImGui.CalcTextSize(text).X; if (fullWidth <= maxWidth) { return text; } wasTruncated = true; const string Ellipsis = "..."; var ellipsisWidth = ImGui.CalcTextSize(Ellipsis).X; if (ellipsisWidth >= maxWidth) { return Ellipsis; } var builder = new StringBuilder(text.Length); var remainingWidth = maxWidth - ellipsisWidth; foreach (var rune in text.EnumerateRunes()) { var runeText = rune.ToString(); var runeWidth = ImGui.CalcTextSize(runeText).X; if (runeWidth > remainingWidth) { break; } builder.Append(runeText); remainingWidth -= runeWidth; } if (builder.Length == 0) { return Ellipsis; } builder.Append(Ellipsis); return builder.ToString(); } }