diff --git a/LightlessSync/LightlessSync.csproj b/LightlessSync/LightlessSync.csproj index fcbddb7..5ec5e78 100644 --- a/LightlessSync/LightlessSync.csproj +++ b/LightlessSync/LightlessSync.csproj @@ -31,7 +31,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -39,13 +39,13 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/LightlessSync/UI/CompactUI.cs b/LightlessSync/UI/CompactUI.cs index 2a962a6..cd758f5 100644 --- a/LightlessSync/UI/CompactUI.cs +++ b/LightlessSync/UI/CompactUI.cs @@ -629,8 +629,9 @@ public class CompactUi : WindowMediatorSubscriberBase { var seString = SeStringUtils.BuildFormattedPlayerName(uidText, vanityTextColor, vanityGlowColor); var cursorPos = ImGui.GetCursorScreenPos(); - var fontPtr = ImGui.GetFont(); - SeStringUtils.RenderSeStringWithHitbox(seString, cursorPos, fontPtr, "uid-header"); + var targetFontSize = ImGui.GetFontSize(); + var font = ImGui.GetFont(); + SeStringUtils.RenderSeStringWithHitbox(seString, cursorPos, targetFontSize ,font , "uid-header"); } else { @@ -716,8 +717,9 @@ public class CompactUi : WindowMediatorSubscriberBase { var seString = SeStringUtils.BuildFormattedPlayerName(_apiController.UID, vanityTextColor, vanityGlowColor); var cursorPos = ImGui.GetCursorScreenPos(); - var fontPtr = ImGui.GetFont(); - SeStringUtils.RenderSeStringWithHitbox(seString, cursorPos, fontPtr, "uid-footer"); + var targetFontSize = ImGui.GetFontSize(); + var font = ImGui.GetFont(); + SeStringUtils.RenderSeStringWithHitbox(seString, cursorPos, targetFontSize, font, "uid-footer"); } else { diff --git a/LightlessSync/UI/EditProfileUi.Group.cs b/LightlessSync/UI/EditProfileUi.Group.cs index ee6a329..7b47ced 100644 --- a/LightlessSync/UI/EditProfileUi.Group.cs +++ b/LightlessSync/UI/EditProfileUi.Group.cs @@ -332,7 +332,7 @@ public partial class EditProfileUi saveTooltip: "Apply the selected tags to this syncshell profile.", submitAction: payload => SubmitGroupTagChanges(payload), allowReorder: true, - sortPayloadBeforeSubmit: true, + sortPayloadBeforeSubmit: false, onPayloadPrepared: payload => { _tagEditorSelection.Clear(); @@ -586,7 +586,7 @@ public partial class EditProfileUi IsNsfw: null, IsDisabled: null)).ConfigureAwait(false); - _profileTagIds = payload.Length == 0 ? Array.Empty() : payload.ToArray(); + _profileTagIds = payload.Length == 0 ? [] : [.. payload]; Mediator.Publish(new ClearProfileGroupDataMessage(_groupInfo.Group)); } catch (Exception ex) diff --git a/LightlessSync/UI/Handlers/IdDisplayHandler.cs b/LightlessSync/UI/Handlers/IdDisplayHandler.cs index b31b145..74a6571 100644 --- a/LightlessSync/UI/Handlers/IdDisplayHandler.cs +++ b/LightlessSync/UI/Handlers/IdDisplayHandler.cs @@ -122,6 +122,7 @@ public class IdDisplayHandler if (!string.Equals(_editEntry, pair.UserData.UID, StringComparison.Ordinal)) { + var targetFontSize = ImGui.GetFontSize(); var font = textIsUid ? UiBuilder.MonoFont : ImGui.GetFont(); var rowWidth = MathF.Max(editBoxWidth.Invoke(), 0f); float rowRightLimit = 0f; @@ -183,7 +184,7 @@ public class IdDisplayHandler } } - SeStringUtils.RenderSeStringWithHitbox(seString, rowStart, font, pair.UserData.UID); + SeStringUtils.RenderSeStringWithHitbox(seString, rowStart, targetFontSize, font, pair.UserData.UID); nameRectMin = ImGui.GetItemRectMin(); nameRectMax = ImGui.GetItemRectMax(); diff --git a/LightlessSync/UI/ZoneChatUi.cs b/LightlessSync/UI/ZoneChatUi.cs index e7574e8..6944759 100644 --- a/LightlessSync/UI/ZoneChatUi.cs +++ b/LightlessSync/UI/ZoneChatUi.cs @@ -44,6 +44,8 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase private float _currentWindowOpacity = DefaultWindowOpacity; private bool _isWindowPinned; private bool _showRulesOverlay; + private bool _refocusChatInput; + private string? _refocusChatInputKey; private string? _selectedChannelKey; private bool _scrollToBottom = true; @@ -308,46 +310,60 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase _draftMessages.TryGetValue(channel.Key, out var draft); draft ??= string.Empty; + var style = ImGui.GetStyle(); + var sendButtonWidth = 100f * ImGuiHelpers.GlobalScale; + var counterWidth = ImGui.CalcTextSize($"{MaxMessageLength}/{MaxMessageLength}").X; + var reservedWidth = sendButtonWidth + counterWidth + style.ItemSpacing.X * 2f; + + ImGui.SetNextItemWidth(-reservedWidth); + var inputId = $"##chat-input-{channel.Key}"; + if (_refocusChatInput && string.Equals(_refocusChatInputKey, channel.Key, StringComparison.Ordinal)) + { + ImGui.SetKeyboardFocusHere(); + _refocusChatInput = false; + _refocusChatInputKey = null; + } + ImGui.InputText(inputId, ref draft, MaxMessageLength); + var enterPressed = ImGui.IsItemFocused() + && (ImGui.IsKeyPressed(ImGuiKey.Enter) || ImGui.IsKeyPressed(ImGuiKey.KeypadEnter)); + _draftMessages[channel.Key] = draft; + + ImGui.SameLine(); + ImGui.AlignTextToFramePadding(); + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey3); + ImGui.TextUnformatted($"{draft.Length}/{MaxMessageLength}"); + ImGui.PopStyleColor(); + + ImGui.SameLine(); + var buttonScreenPos = ImGui.GetCursorScreenPos(); + var rightEdgeScreen = ImGui.GetWindowPos().X + ImGui.GetWindowContentRegionMax().X; + var desiredButtonX = rightEdgeScreen - sendButtonWidth; + var minButtonX = buttonScreenPos.X + style.ItemSpacing.X; + var finalButtonX = MathF.Max(minButtonX, desiredButtonX); + ImGui.SetCursorScreenPos(new Vector2(finalButtonX, buttonScreenPos.Y)); + var sendColor = UIColors.Get("LightlessPurpleDefault"); + var sendHovered = UIColors.Get("LightlessPurple"); + var sendActive = UIColors.Get("LightlessPurpleActive"); + ImGui.PushStyleColor(ImGuiCol.Button, sendColor); + ImGui.PushStyleColor(ImGuiCol.ButtonHovered, sendHovered); + ImGui.PushStyleColor(ImGuiCol.ButtonActive, sendActive); + ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 6f * ImGuiHelpers.GlobalScale); + var sendClicked = false; using (ImRaii.Disabled(!canSend)) { - var style = ImGui.GetStyle(); - var sendButtonWidth = 100f * ImGuiHelpers.GlobalScale; - var counterWidth = ImGui.CalcTextSize($"{MaxMessageLength}/{MaxMessageLength}").X; - var reservedWidth = sendButtonWidth + counterWidth + style.ItemSpacing.X * 2f; - - ImGui.SetNextItemWidth(-reservedWidth); - var inputId = $"##chat-input-{channel.Key}"; - var send = ImGui.InputText(inputId, ref draft, MaxMessageLength, ImGuiInputTextFlags.EnterReturnsTrue); - _draftMessages[channel.Key] = draft; - - ImGui.SameLine(); - ImGui.AlignTextToFramePadding(); - ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey3); - ImGui.TextUnformatted($"{draft.Length}/{MaxMessageLength}"); - ImGui.PopStyleColor(); - - ImGui.SameLine(); - var buttonScreenPos = ImGui.GetCursorScreenPos(); - var rightEdgeScreen = ImGui.GetWindowPos().X + ImGui.GetWindowContentRegionMax().X; - var desiredButtonX = rightEdgeScreen - sendButtonWidth; - var minButtonX = buttonScreenPos.X + style.ItemSpacing.X; - var finalButtonX = MathF.Max(minButtonX, desiredButtonX); - ImGui.SetCursorScreenPos(new Vector2(finalButtonX, buttonScreenPos.Y)); - var sendColor = UIColors.Get("LightlessPurpleDefault"); - var sendHovered = UIColors.Get("LightlessPurple"); - var sendActive = UIColors.Get("LightlessPurpleActive"); - ImGui.PushStyleColor(ImGuiCol.Button, sendColor); - ImGui.PushStyleColor(ImGuiCol.ButtonHovered, sendHovered); - ImGui.PushStyleColor(ImGuiCol.ButtonActive, sendActive); - ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 6f * ImGuiHelpers.GlobalScale); - if (_uiSharedService.IconTextButton(FontAwesomeIcon.PaperPlane, "Send", 100f * ImGuiHelpers.GlobalScale, center: true)) + if (_uiSharedService.IconTextButton(FontAwesomeIcon.PaperPlane, $"Send##chat-send-{channel.Key}", 100f * ImGuiHelpers.GlobalScale, center: true)) { - send = true; + sendClicked = true; } - ImGui.PopStyleVar(); - ImGui.PopStyleColor(3); + } + ImGui.PopStyleVar(); + ImGui.PopStyleColor(3); - if (send && TrySendDraft(channel, draft)) + if (canSend && (enterPressed || sendClicked)) + { + _refocusChatInput = true; + _refocusChatInputKey = channel.Key; + if (TrySendDraft(channel, draft)) { _draftMessages[channel.Key] = string.Empty; _scrollToBottom = true; @@ -969,6 +985,12 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase _draftMessages.Remove(key); } } + + if (_refocusChatInputKey is not null && !existingKeys.Contains(_refocusChatInputKey)) + { + _refocusChatInputKey = null; + _refocusChatInput = false; + } } private void DrawConnectionControls() diff --git a/LightlessSync/Utils/SeStringUtils.cs b/LightlessSync/Utils/SeStringUtils.cs index 810cbe7..2188d91 100644 --- a/LightlessSync/Utils/SeStringUtils.cs +++ b/LightlessSync/Utils/SeStringUtils.cs @@ -559,17 +559,11 @@ public static class SeStringUtils ImGui.Dummy(new Vector2(0f, textSize.Y)); } + public static Vector2 RenderSeStringWithHitbox(DalamudSeString seString, Vector2 position, ImFontPtr? font = null, string? id = null) { var drawList = ImGui.GetWindowDrawList(); var usedFont = font ?? UiBuilder.MonoFont; - var drawParams = new SeStringDrawParams - { - Font = usedFont, - Color = 0xFFFFFFFF, - WrapWidth = float.MaxValue, - TargetDrawList = drawList - }; var textSize = ImGui.CalcTextSize(seString.TextValue); if (textSize.Y <= 0f) @@ -584,11 +578,17 @@ public static class SeStringUtils var verticalOffset = MathF.Max((hitboxHeight - textSize.Y) * 0.5f, 0f); var drawPos = new Vector2(position.X, position.Y + verticalOffset); - ImGui.SetCursorScreenPos(drawPos); + var drawParams = new SeStringDrawParams + { + FontSize = usedFont.FontSize, + ScreenOffset = drawPos, + Font = usedFont, + Color = 0xFFFFFFFF, + WrapWidth = float.MaxValue, + TargetDrawList = drawList + }; - drawParams.ScreenOffset = drawPos; - drawParams.Font = usedFont; - drawParams.FontSize = usedFont.FontSize; + ImGui.SetCursorScreenPos(drawPos); ImGuiHelpers.SeStringWrapped(seString.Encode(), drawParams); @@ -614,6 +614,64 @@ public static class SeStringUtils return new Vector2(textSize.X, hitboxHeight); } + public static Vector2 RenderSeStringWithHitbox(DalamudSeString seString, Vector2 position, float? targetFontSize, ImFontPtr? font = null, string? id = null) + { + var drawList = ImGui.GetWindowDrawList(); + var usedFont = font ?? ImGui.GetFont(); + + ImGui.PushFont(usedFont); + Vector2 rawSize; + float usedEffectiveSize; + try + { + usedEffectiveSize = ImGui.GetFontSize(); + rawSize = ImGui.CalcTextSize(seString.TextValue); + } + finally + { + ImGui.PopFont(); + } + + var desiredSize = targetFontSize ?? usedEffectiveSize; + var scale = usedEffectiveSize > 0 ? (desiredSize / usedEffectiveSize) : 1f; + + var textSize = rawSize * scale; + + var style = ImGui.GetStyle(); + var frameHeight = desiredSize + style.FramePadding.Y * 2f; + var hitboxHeight = MathF.Max(frameHeight, textSize.Y); + var verticalOffset = MathF.Max((hitboxHeight - textSize.Y) * 0.5f, 0f); + + var drawPos = new Vector2(position.X, position.Y + verticalOffset); + + var drawParams = new SeStringDrawParams + { + TargetDrawList = drawList, + ScreenOffset = drawPos, + Font = usedFont, + FontSize = desiredSize, + Color = 0xFFFFFFFF, + WrapWidth = float.MaxValue, + }; + + ImGui.SetCursorScreenPos(drawPos); + ImGuiHelpers.SeStringWrapped(seString.Encode(), drawParams); + + ImGui.SetCursorScreenPos(position); + ImGui.PushID(id ?? Interlocked.Increment(ref _seStringHitboxCounter).ToString()); + + try + { + ImGui.InvisibleButton("##hitbox", new Vector2(textSize.X, hitboxHeight)); + } + finally + { + ImGui.PopID(); + } + + return new Vector2(textSize.X, hitboxHeight); + } + public static Vector2 RenderIconWithHitbox(int iconId, Vector2 position, ImFontPtr? font = null, string? id = null) { var drawList = ImGui.GetWindowDrawList(); diff --git a/LightlessSync/packages.lock.json b/LightlessSync/packages.lock.json index bf2bdfe..47b77e9 100644 --- a/LightlessSync/packages.lock.json +++ b/LightlessSync/packages.lock.json @@ -52,9 +52,9 @@ }, "Meziantou.Analyzer": { "type": "Direct", - "requested": "[2.0.212, )", - "resolved": "2.0.212", - "contentHash": "U91ktjjTRTccUs3Lk+hrLD9vW+2+lhnsOf4G1GpRSJi1pLn3uK5CU6wGP9Bmz1KlJs6Oz1GGoMhxQBoqQsmAuQ==" + "requested": "[2.0.264, )", + "resolved": "2.0.264", + "contentHash": "zRG13RDG446rZNdd/YjKRd4utpbjleRDUqNQSrX0etMnH8Rz9NBlXUpS5aR2ExoOokhNfkdOW8HpLzjLj5x0hQ==" }, "Microsoft.AspNetCore.SignalR.Client": { "type": "Direct", @@ -108,35 +108,35 @@ }, "NReco.Logging.File": { "type": "Direct", - "requested": "[1.2.2, )", - "resolved": "1.2.2", - "contentHash": "UyUIkyDiHi2HAJlmEWqeKN9/FxTF0DPNdyatzMDMTXvUpgvqBFneJ2qDtZkXRJNG8eR6jU+KsbGeMmChgUdRUg==", + "requested": "[1.3.1, )", + "resolved": "1.3.1", + "contentHash": "4aFUEW1OFJsuKtg46dnqxZUyb37f9dzaWOXjUv2x/wzoHKovR9yqiMzXtCZt3+a9G78YCIAtSEz2g/GaNYbxSQ==", "dependencies": { - "Microsoft.Extensions.Logging": "8.0.1", - "Microsoft.Extensions.Logging.Configuration": "8.0.1", - "Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0" + "Microsoft.Extensions.Logging": "10.0.0", + "Microsoft.Extensions.Logging.Configuration": "10.0.0", + "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.0" } }, "SixLabors.ImageSharp": { "type": "Direct", - "requested": "[3.1.11, )", - "resolved": "3.1.11", - "contentHash": "JfPLyigLthuE50yi6tMt7Amrenr/fA31t2CvJyhy/kQmfulIBAqo5T/YFUSRHtuYPXRSaUHygFeh6Qd933EoSw==" + "requested": "[3.1.12, )", + "resolved": "3.1.12", + "contentHash": "iAg6zifihXEFS/t7fiHhZBGAdCp3FavsF4i2ZIDp0JfeYeDVzvmlbY1CNhhIKimaIzrzSi5M/NBFcWvZT2rB/A==" }, "SonarAnalyzer.CSharp": { "type": "Direct", - "requested": "[10.7.0.110445, )", - "resolved": "10.7.0.110445", - "contentHash": "U4v2LWopxADYkUv7Z5CX7ifKMdDVqHb7a1bzppIQnQi4WQR6z1Zi5rDkCHlVYGEd1U/WMz1IJCU8OmFZLJpVig==" + "requested": "[10.17.0.131074, )", + "resolved": "10.17.0.131074", + "contentHash": "N8agHzX1pK3Xv/fqMig/mHspPAmh/aKkGg7lUC1xfezAhFtPTuRqBjuyas622Tvy5jnsN5zCXJVclvNkfJJ4rQ==" }, "System.IdentityModel.Tokens.Jwt": { "type": "Direct", - "requested": "[8.7.0, )", - "resolved": "8.7.0", - "contentHash": "8dKL3A9pVqYCJIXHd4H2epQqLxSvKeNxGonR0e5g89yMchyvsM/NLuB06otx29BicUd6+LUJZgNZmvYjjPsPGg==", + "requested": "[8.15.0, )", + "resolved": "8.15.0", + "contentHash": "dpodi7ixz6hxK8YCBYAWzm0IA8JYXoKcz0hbCbNifo519//rjUI0fBD8rfNr+IGqq+2gm4oQoXwHk09LX5SqqQ==", "dependencies": { - "Microsoft.IdentityModel.JsonWebTokens": "8.7.0", - "Microsoft.IdentityModel.Tokens": "8.7.0" + "Microsoft.IdentityModel.JsonWebTokens": "8.15.0", + "Microsoft.IdentityModel.Tokens": "8.15.0" } }, "YamlDotNet": { @@ -490,32 +490,32 @@ }, "Microsoft.IdentityModel.Abstractions": { "type": "Transitive", - "resolved": "8.7.0", - "contentHash": "OQd5aVepYvh5evOmBMeAYjMIpEcTf1ZCBZaU7Nh/RlhhdXefjFDJeP1L2F2zeNT1unFr+wUu/h3Ac2Xb4BXU6w==" + "resolved": "8.15.0", + "contentHash": "e/DApa1GfxUqHSBHcpiQg8yaghKAvFVBQFcWh25jNoRobDZbduTUACY8bZ54eeGWXvimGmEDdF0zkS5Dq16XPQ==" }, "Microsoft.IdentityModel.JsonWebTokens": { "type": "Transitive", - "resolved": "8.7.0", - "contentHash": "uzsSAWhNhbrkWbQKBTE8QhzviU6sr3bJ1Bkv7gERlhswfSKOp7HsxTRLTPBpx/whQ/GRRHEwMg8leRIPbMrOgw==", + "resolved": "8.15.0", + "contentHash": "3513f5VzvOZy3ELd42wGnh1Q3e83tlGAuXFSNbENpgWYoAhLLzgFtd5PiaOPGAU0gqKhYGVzKavghLUGfX3HQg==", "dependencies": { - "Microsoft.IdentityModel.Tokens": "8.7.0" + "Microsoft.IdentityModel.Tokens": "8.15.0" } }, "Microsoft.IdentityModel.Logging": { "type": "Transitive", - "resolved": "8.7.0", - "contentHash": "Bs0TznPAu+nxa9rAVHJ+j3CYECHJkT3tG8AyBfhFYlT5ldsDhoxFT7J+PKxJHLf+ayqWfvDZHHc4639W2FQCxA==", + "resolved": "8.15.0", + "contentHash": "1gJLjhy0LV2RQMJ9NGzi5Tnb2l+c37o8D8Lrk2mrvmb6OQHZ7XJstd/XxvncXgBpad4x9CGXdipbZzJJCXKyAg==", "dependencies": { - "Microsoft.IdentityModel.Abstractions": "8.7.0" + "Microsoft.IdentityModel.Abstractions": "8.15.0" } }, "Microsoft.IdentityModel.Tokens": { "type": "Transitive", - "resolved": "8.7.0", - "contentHash": "5Z6voXjRXAnGklhmZd1mKz89UhcF5ZQQZaZc2iKrOuL4Li1UihG2vlJx8IbiFAOIxy/xdbsAm0A+WZEaH5fxng==", + "resolved": "8.15.0", + "contentHash": "zUE9ysJXBtXlHHRtcRK3Sp8NzdCI1z/BRDTXJQ2TvBoI0ENRtnufYIep0O5TSCJRJGDwwuLTUx+l/bEYZUxpCA==", "dependencies": { - "Microsoft.Extensions.Logging.Abstractions": "8.0.2", - "Microsoft.IdentityModel.Logging": "8.7.0" + "Microsoft.Extensions.Logging.Abstractions": "10.0.0", + "Microsoft.IdentityModel.Logging": "8.15.0" } }, "Microsoft.NET.StringTools": { @@ -619,7 +619,7 @@ "FlatSharp.Runtime": "[7.9.0, )", "OtterGui": "[1.0.0, )", "Penumbra.Api": "[5.13.0, )", - "Penumbra.String": "[1.0.6, )" + "Penumbra.String": "[1.0.7, )" } }, "penumbra.string": {