diff --git a/LightlessSync/UI/LightFinderUI.cs b/LightlessSync/UI/LightFinderUI.cs index fa88475..ca74bc9 100644 --- a/LightlessSync/UI/LightFinderUI.cs +++ b/LightlessSync/UI/LightFinderUI.cs @@ -2,6 +2,7 @@ using Dalamud.Bindings.ImGui; using Dalamud.Interface; using Dalamud.Interface.Colors; using Dalamud.Interface.Utility; +using Dalamud.Utility; using LightlessSync.API.Data.Extensions; using LightlessSync.API.Dto.Group; using LightlessSync.LightlessConfiguration; diff --git a/LightlessSync/UI/SyncshellFinderUI.cs b/LightlessSync/UI/SyncshellFinderUI.cs index 7374203..00a008f 100644 --- a/LightlessSync/UI/SyncshellFinderUI.cs +++ b/LightlessSync/UI/SyncshellFinderUI.cs @@ -1,6 +1,7 @@ using Dalamud.Bindings.ImGui; using Dalamud.Interface; using Dalamud.Interface.Colors; +using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using LightlessSync.API.Data; @@ -8,14 +9,16 @@ using LightlessSync.API.Data.Enum; using LightlessSync.API.Data.Extensions; using LightlessSync.API.Dto; using LightlessSync.API.Dto.Group; +using LightlessSync.API.Dto.User; using LightlessSync.Services; +using LightlessSync.Services.LightFinder; using LightlessSync.Services.Mediator; +using LightlessSync.UI.Services; +using LightlessSync.UI.Tags; using LightlessSync.Utils; using LightlessSync.WebAPI; -using LightlessSync.UI.Services; using Microsoft.Extensions.Logging; using System.Numerics; -using LightlessSync.Services.LightFinder; namespace LightlessSync.UI; @@ -28,6 +31,10 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase private readonly PairUiService _pairUiService; private readonly DalamudUtilService _dalamudUtilService; + private Vector4 _tagBackgroundColor = new(0.18f, 0.18f, 0.18f, 0.95f); + private Vector4 _tagBorderColor = new(0.35f, 0.35f, 0.35f, 0.4f); + + private readonly List _seResolvedSegments = new(); private readonly List _nearbySyncshells = []; private List _currentSyncshells = []; private int _selectedNearbyIndex = -1; @@ -40,6 +47,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase private bool _useTestSyncshells = false; private bool _compactView = false; + private readonly LightlessProfileManager _lightlessProfileManager; public SyncshellFinderUI( ILogger logger, @@ -50,7 +58,8 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase ApiController apiController, LightFinderScannerService broadcastScannerService, PairUiService pairUiService, - DalamudUtilService dalamudUtilService) : base(logger, mediator, "Shellfinder###LightlessSyncshellFinderUI", performanceCollectorService) + DalamudUtilService dalamudUtilService, + LightlessProfileManager lightlessProfileManager) : base(logger, mediator, "Shellfinder###LightlessSyncshellFinderUI", performanceCollectorService) { _broadcastService = broadcastService; _uiSharedService = uiShared; @@ -58,16 +67,18 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase _broadcastScannerService = broadcastScannerService; _pairUiService = pairUiService; _dalamudUtilService = dalamudUtilService; + _lightlessProfileManager = lightlessProfileManager; IsOpen = false; WindowBuilder.For(this) - .SetSizeConstraints(new Vector2(600, 400), new Vector2(600, 550)) - .Apply(); + .SetSizeConstraints(new Vector2(600, 400), new Vector2(600, 550)) + .Apply(); Mediator.Subscribe(this, async _ => await RefreshSyncshellsAsync().ConfigureAwait(false)); Mediator.Subscribe(this, async _ => await RefreshSyncshellsAsync().ConfigureAwait(false)); Mediator.Subscribe(this, async _ => await RefreshSyncshellsAsync(_.gid).ConfigureAwait(false)); Mediator.Subscribe(this, async _ => await RefreshSyncshellsAsync(_.gid).ConfigureAwait(false)); + } public override async void OnOpen() @@ -80,7 +91,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase { ImGui.BeginGroup(); _uiSharedService.MediumText("Nearby Syncshells", UIColors.Get("LightlessPurple")); - + #if DEBUG if (ImGui.SmallButton("Show test syncshells")) { @@ -92,7 +103,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase string checkboxLabel = "Compact view"; float availWidth = ImGui.GetContentRegionAvail().X; - float checkboxWidth = ImGui.CalcTextSize(checkboxLabel).X + ImGui.GetFrameHeight(); + float checkboxWidth = ImGui.CalcTextSize(checkboxLabel).X + ImGui.GetFrameHeight(); float rightX = ImGui.GetCursorPosX() + availWidth - checkboxWidth - 4.0f; ImGui.SetCursorPosX(rightX); @@ -130,13 +141,17 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase return; } + var broadcasts = _broadcastScannerService.GetActiveSyncshellBroadcasts() ?? []; + var cardData = new List<(GroupJoinDto Shell, string BroadcasterName)>(); - var broadcasts = _broadcastScannerService.GetActiveSyncshellBroadcasts(); foreach (var shell in _nearbySyncshells) { string broadcasterName; + if (shell?.Group == null || string.IsNullOrEmpty(shell.Group.GID)) + continue; + if (_useTestSyncshells) { var displayName = !string.IsNullOrEmpty(shell.Group.Alias) @@ -206,7 +221,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase var (shell, broadcasterName) = listData[index]; ImGui.PushID(shell.Group.GID); - float rowHeight = 90f * ImGuiHelpers.GlobalScale; + float rowHeight = 74f * ImGuiHelpers.GlobalScale; ImGui.BeginChild($"ShellRow##{shell.Group.GID}", new Vector2(-1, rowHeight), border: true); @@ -234,10 +249,48 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurpleDefault")); - ImGui.Dummy(new Vector2(0, 6 * ImGuiHelpers.GlobalScale)); + var groupProfile = _lightlessProfileManager.GetLightlessGroupProfile(shell.Group); + IReadOnlyList groupTags = + groupProfile != null && groupProfile.Tags.Count > 0 + ? ProfileTagService.ResolveTags(groupProfile.Tags) + : []; + + var limitedTags = groupTags.Count > 3 + ? [.. groupTags.Take(3)] + : groupTags; + + float tagScale = ImGuiHelpers.GlobalScale * 0.9f; + + Vector2 rowStartLocal = ImGui.GetCursorPos(); + + float tagsWidth = 0f; + float tagsHeight = 0f; + + if (limitedTags.Count > 0) + { + (tagsWidth, tagsHeight) = RenderProfileTagsSingleRow(limitedTags, tagScale); + } + else + { + ImGui.SetCursorPosX(startX); + ImGui.TextDisabled("-- No tags set --"); + ImGui.Dummy(new Vector2(0, 4 * ImGuiHelpers.GlobalScale)); + } + + float btnBaselineY = rowStartLocal.Y; + float joinX = rowStartLocal.X + (tagsWidth > 0 ? tagsWidth + style.ItemSpacing.X : 0f); + + ImGui.SetCursorPos(new Vector2(joinX, btnBaselineY)); DrawJoinButton(shell); + float btnHeight = ImGui.GetFrameHeightWithSpacing(); + float rowHeightUsed = MathF.Max(tagsHeight, btnHeight); + + ImGui.SetCursorPos(new Vector2( + rowStartLocal.X, + rowStartLocal.Y + rowHeightUsed)); + ImGui.EndChild(); ImGui.PopID(); @@ -311,10 +364,39 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase ImGui.SetTooltip("Broadcaster of the syncshell."); ImGui.EndGroup(); + UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurpleDefault")); ImGui.Dummy(new Vector2(0, 6 * ImGuiHelpers.GlobalScale)); + var groupProfile = _lightlessProfileManager.GetLightlessGroupProfile(shell.Group); + + IReadOnlyList groupTags = + groupProfile != null && groupProfile.Tags.Count > 0 + ? ProfileTagService.ResolveTags(groupProfile.Tags) + : []; + + float tagScale = ImGuiHelpers.GlobalScale * 0.9f; + + if (groupTags.Count > 0) + { + var limitedTags = groupTags.Count > 2 + ? [.. groupTags.Take(2)] + : groupTags; + + ImGui.SetCursorPosX(startX); + + var (_, tagsHeight) = RenderProfileTagsSingleRow(limitedTags, tagScale); + + ImGui.Dummy(new Vector2(0, 4 * ImGuiHelpers.GlobalScale)); + } + else + { + ImGui.SetCursorPosX(startX); + ImGui.TextDisabled("-- No tags set --"); + ImGui.Dummy(new Vector2(0, 4 * ImGuiHelpers.GlobalScale)); + } + var buttonHeight = ImGui.GetFrameHeightWithSpacing(); var remainingY = ImGui.GetContentRegionAvail().Y - buttonHeight; if (remainingY > 0) @@ -338,7 +420,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase { if (totalPages > 1) { - UiSharedService.ColoredSeparator(UIColors.Get("PairBlue")); + UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurpleDefault")); var style = ImGui.GetStyle(); string pageLabel = $"Page {_syncshellPageIndex + 1}/{totalPages}"; @@ -371,10 +453,6 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase const string visibleLabel = "Join"; var label = $"{visibleLabel}##{shell.Group.GID}"; - ImGui.PushStyleColor(ImGuiCol.Button, UIColors.Get("LightlessGreen")); - ImGui.PushStyleColor(ImGuiCol.ButtonHovered, UIColors.Get("LightlessGreen").WithAlpha(0.85f)); - ImGui.PushStyleColor(ImGuiCol.ButtonActive, UIColors.Get("LightlessGreen").WithAlpha(0.75f)); - var isAlreadyMember = _currentSyncshells.Exists(g => string.Equals(g.GID, shell.GID, StringComparison.Ordinal)); var isRecentlyJoined = _recentlyJoined.Contains(shell.GID); @@ -386,7 +464,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase var textSize = ImGui.CalcTextSize(visibleLabel); var width = textSize.X + style.FramePadding.X * 20f; - buttonSize = new Vector2(width, 0); + buttonSize = new Vector2(width, 30f); float availX = ImGui.GetContentRegionAvail().X; float curX = ImGui.GetCursorPosX(); @@ -400,6 +478,9 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase if (!isAlreadyMember && !isRecentlyJoined) { + ImGui.PushStyleColor(ImGuiCol.Button, UIColors.Get("LightlessGreen")); + ImGui.PushStyleColor(ImGuiCol.ButtonHovered, UIColors.Get("LightlessGreen").WithAlpha(0.85f)); + ImGui.PushStyleColor(ImGuiCol.ButtonActive, UIColors.Get("LightlessGreen").WithAlpha(0.75f)); if (ImGui.Button(label, buttonSize)) { _logger.LogInformation($"Join requested for Syncshell {shell.Group.GID} ({shell.Group.Alias})"); @@ -436,6 +517,10 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase } else { + ImGui.PushStyleColor(ImGuiCol.Button, UIColors.Get("DimRed")); + ImGui.PushStyleColor(ImGuiCol.ButtonHovered, UIColors.Get("DimRed").WithAlpha(0.85f)); + ImGui.PushStyleColor(ImGuiCol.ButtonActive, UIColors.Get("DimRed").WithAlpha(0.75f)); + using (ImRaii.Disabled()) { ImGui.Button(label, buttonSize); @@ -446,6 +531,72 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase ImGui.PopStyleColor(3); } + + private (float widthUsed, float rowHeight) RenderProfileTagsSingleRow(IReadOnlyList tags, float scale) + { + if (tags == null || tags.Count == 0) + return (0f, 0f); + + var drawList = ImGui.GetWindowDrawList(); + var style = ImGui.GetStyle(); + var defaultTextColorU32 = ImGui.GetColorU32(ImGuiCol.Text); + + var baseLocal = ImGui.GetCursorPos(); + var baseScreen = ImGui.GetCursorScreenPos(); + float availableWidth = ImGui.GetContentRegionAvail().X; + if (availableWidth <= 0f) + availableWidth = 1f; + + float cursorLocalX = baseLocal.X; + float cursorScreenX = baseScreen.X; + float rowHeight = 0f; + + for (int i = 0; i < tags.Count; i++) + { + var tag = tags[i]; + if (!tag.HasContent) + continue; + + var tagSize = ProfileTagRenderer.MeasureTag(tag, scale, style, _tagBackgroundColor, _tagBorderColor, defaultTextColorU32, _seResolvedSegments, GetIconWrap, _logger); + + float tagWidth = tagSize.X; + float tagHeight = tagSize.Y; + + if (cursorLocalX > baseLocal.X && cursorLocalX + tagWidth > baseLocal.X + availableWidth) + break; + + var tagScreenPos = new Vector2(cursorScreenX, baseScreen.Y); + ImGui.SetCursorScreenPos(tagScreenPos); + ImGui.InvisibleButton($"##profileTagInline_{i}", tagSize); + + ProfileTagRenderer.RenderTag(tag, tagScreenPos, scale, drawList, style, _tagBackgroundColor, _tagBorderColor, defaultTextColorU32, _seResolvedSegments, GetIconWrap, _logger); + + cursorLocalX += tagWidth + style.ItemSpacing.X; + cursorScreenX += tagWidth + style.ItemSpacing.X; + rowHeight = MathF.Max(rowHeight, tagHeight); + } + + ImGui.SetCursorPos(new Vector2(baseLocal.X, baseLocal.Y + rowHeight)); + + float widthUsed = cursorLocalX - baseLocal.X; + return (widthUsed, rowHeight); + } + + private IDalamudTextureWrap? GetIconWrap(uint iconId) + { + try + { + if (_uiSharedService.TryGetIcon(iconId, out var wrap) && wrap != null) + return wrap; + } + catch (Exception ex) + { + _logger.LogDebug(ex, "Failed to resolve icon {IconId} for profile tags", iconId); + } + + return null; + } + private void DrawConfirmation() { if (_joinDto != null && _joinInfo != null) @@ -470,9 +621,9 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase finalPermissions.SetDisableVFX(_ownPermissions.DisableGroupVFX); _ = _apiController.GroupJoinFinalize(new GroupJoinDto(_joinDto.Group, _joinDto.Password, finalPermissions)); - + _recentlyJoined.Add(_joinDto.Group.GID); - + _joinDto = null; _joinInfo = null; }