Added tags in the shell finder, button is red when not joinable. Fixed some null errors.

This commit is contained in:
cake
2025-12-15 23:52:06 +01:00
parent 4444a88746
commit d5c11cd22f
2 changed files with 170 additions and 18 deletions

View File

@@ -2,6 +2,7 @@ using Dalamud.Bindings.ImGui;
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Colors; using Dalamud.Interface.Colors;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Dalamud.Utility;
using LightlessSync.API.Data.Extensions; using LightlessSync.API.Data.Extensions;
using LightlessSync.API.Dto.Group; using LightlessSync.API.Dto.Group;
using LightlessSync.LightlessConfiguration; using LightlessSync.LightlessConfiguration;

View File

@@ -1,6 +1,7 @@
using Dalamud.Bindings.ImGui; using Dalamud.Bindings.ImGui;
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Colors; using Dalamud.Interface.Colors;
using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Utility.Raii;
using LightlessSync.API.Data; using LightlessSync.API.Data;
@@ -8,14 +9,16 @@ using LightlessSync.API.Data.Enum;
using LightlessSync.API.Data.Extensions; using LightlessSync.API.Data.Extensions;
using LightlessSync.API.Dto; using LightlessSync.API.Dto;
using LightlessSync.API.Dto.Group; using LightlessSync.API.Dto.Group;
using LightlessSync.API.Dto.User;
using LightlessSync.Services; using LightlessSync.Services;
using LightlessSync.Services.LightFinder;
using LightlessSync.Services.Mediator; using LightlessSync.Services.Mediator;
using LightlessSync.UI.Services;
using LightlessSync.UI.Tags;
using LightlessSync.Utils; using LightlessSync.Utils;
using LightlessSync.WebAPI; using LightlessSync.WebAPI;
using LightlessSync.UI.Services;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System.Numerics; using System.Numerics;
using LightlessSync.Services.LightFinder;
namespace LightlessSync.UI; namespace LightlessSync.UI;
@@ -28,6 +31,10 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
private readonly PairUiService _pairUiService; private readonly PairUiService _pairUiService;
private readonly DalamudUtilService _dalamudUtilService; 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<SeStringUtils.SeStringSegment> _seResolvedSegments = new();
private readonly List<GroupJoinDto> _nearbySyncshells = []; private readonly List<GroupJoinDto> _nearbySyncshells = [];
private List<GroupFullInfoDto> _currentSyncshells = []; private List<GroupFullInfoDto> _currentSyncshells = [];
private int _selectedNearbyIndex = -1; private int _selectedNearbyIndex = -1;
@@ -40,6 +47,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
private bool _useTestSyncshells = false; private bool _useTestSyncshells = false;
private bool _compactView = false; private bool _compactView = false;
private readonly LightlessProfileManager _lightlessProfileManager;
public SyncshellFinderUI( public SyncshellFinderUI(
ILogger<SyncshellFinderUI> logger, ILogger<SyncshellFinderUI> logger,
@@ -50,7 +58,8 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
ApiController apiController, ApiController apiController,
LightFinderScannerService broadcastScannerService, LightFinderScannerService broadcastScannerService,
PairUiService pairUiService, PairUiService pairUiService,
DalamudUtilService dalamudUtilService) : base(logger, mediator, "Shellfinder###LightlessSyncshellFinderUI", performanceCollectorService) DalamudUtilService dalamudUtilService,
LightlessProfileManager lightlessProfileManager) : base(logger, mediator, "Shellfinder###LightlessSyncshellFinderUI", performanceCollectorService)
{ {
_broadcastService = broadcastService; _broadcastService = broadcastService;
_uiSharedService = uiShared; _uiSharedService = uiShared;
@@ -58,16 +67,18 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
_broadcastScannerService = broadcastScannerService; _broadcastScannerService = broadcastScannerService;
_pairUiService = pairUiService; _pairUiService = pairUiService;
_dalamudUtilService = dalamudUtilService; _dalamudUtilService = dalamudUtilService;
_lightlessProfileManager = lightlessProfileManager;
IsOpen = false; IsOpen = false;
WindowBuilder.For(this) WindowBuilder.For(this)
.SetSizeConstraints(new Vector2(600, 400), new Vector2(600, 550)) .SetSizeConstraints(new Vector2(600, 400), new Vector2(600, 550))
.Apply(); .Apply();
Mediator.Subscribe<SyncshellBroadcastsUpdatedMessage>(this, async _ => await RefreshSyncshellsAsync().ConfigureAwait(false)); Mediator.Subscribe<SyncshellBroadcastsUpdatedMessage>(this, async _ => await RefreshSyncshellsAsync().ConfigureAwait(false));
Mediator.Subscribe<BroadcastStatusChangedMessage>(this, async _ => await RefreshSyncshellsAsync().ConfigureAwait(false)); Mediator.Subscribe<BroadcastStatusChangedMessage>(this, async _ => await RefreshSyncshellsAsync().ConfigureAwait(false));
Mediator.Subscribe<UserLeftSyncshell>(this, async _ => await RefreshSyncshellsAsync(_.gid).ConfigureAwait(false)); Mediator.Subscribe<UserLeftSyncshell>(this, async _ => await RefreshSyncshellsAsync(_.gid).ConfigureAwait(false));
Mediator.Subscribe<UserJoinedSyncshell>(this, async _ => await RefreshSyncshellsAsync(_.gid).ConfigureAwait(false)); Mediator.Subscribe<UserJoinedSyncshell>(this, async _ => await RefreshSyncshellsAsync(_.gid).ConfigureAwait(false));
} }
public override async void OnOpen() public override async void OnOpen()
@@ -80,7 +91,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
{ {
ImGui.BeginGroup(); ImGui.BeginGroup();
_uiSharedService.MediumText("Nearby Syncshells", UIColors.Get("LightlessPurple")); _uiSharedService.MediumText("Nearby Syncshells", UIColors.Get("LightlessPurple"));
#if DEBUG #if DEBUG
if (ImGui.SmallButton("Show test syncshells")) if (ImGui.SmallButton("Show test syncshells"))
{ {
@@ -92,7 +103,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
string checkboxLabel = "Compact view"; string checkboxLabel = "Compact view";
float availWidth = ImGui.GetContentRegionAvail().X; 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; float rightX = ImGui.GetCursorPosX() + availWidth - checkboxWidth - 4.0f;
ImGui.SetCursorPosX(rightX); ImGui.SetCursorPosX(rightX);
@@ -130,13 +141,17 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
return; return;
} }
var broadcasts = _broadcastScannerService.GetActiveSyncshellBroadcasts() ?? [];
var cardData = new List<(GroupJoinDto Shell, string BroadcasterName)>(); var cardData = new List<(GroupJoinDto Shell, string BroadcasterName)>();
var broadcasts = _broadcastScannerService.GetActiveSyncshellBroadcasts();
foreach (var shell in _nearbySyncshells) foreach (var shell in _nearbySyncshells)
{ {
string broadcasterName; string broadcasterName;
if (shell?.Group == null || string.IsNullOrEmpty(shell.Group.GID))
continue;
if (_useTestSyncshells) if (_useTestSyncshells)
{ {
var displayName = !string.IsNullOrEmpty(shell.Group.Alias) var displayName = !string.IsNullOrEmpty(shell.Group.Alias)
@@ -206,7 +221,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
var (shell, broadcasterName) = listData[index]; var (shell, broadcasterName) = listData[index];
ImGui.PushID(shell.Group.GID); 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); 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")); UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"));
ImGui.Dummy(new Vector2(0, 6 * ImGuiHelpers.GlobalScale)); var groupProfile = _lightlessProfileManager.GetLightlessGroupProfile(shell.Group);
IReadOnlyList<ProfileTagDefinition> 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); DrawJoinButton(shell);
float btnHeight = ImGui.GetFrameHeightWithSpacing();
float rowHeightUsed = MathF.Max(tagsHeight, btnHeight);
ImGui.SetCursorPos(new Vector2(
rowStartLocal.X,
rowStartLocal.Y + rowHeightUsed));
ImGui.EndChild(); ImGui.EndChild();
ImGui.PopID(); ImGui.PopID();
@@ -311,10 +364,39 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
ImGui.SetTooltip("Broadcaster of the syncshell."); ImGui.SetTooltip("Broadcaster of the syncshell.");
ImGui.EndGroup(); ImGui.EndGroup();
UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurpleDefault")); UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"));
ImGui.Dummy(new Vector2(0, 6 * ImGuiHelpers.GlobalScale)); ImGui.Dummy(new Vector2(0, 6 * ImGuiHelpers.GlobalScale));
var groupProfile = _lightlessProfileManager.GetLightlessGroupProfile(shell.Group);
IReadOnlyList<ProfileTagDefinition> 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 buttonHeight = ImGui.GetFrameHeightWithSpacing();
var remainingY = ImGui.GetContentRegionAvail().Y - buttonHeight; var remainingY = ImGui.GetContentRegionAvail().Y - buttonHeight;
if (remainingY > 0) if (remainingY > 0)
@@ -338,7 +420,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
{ {
if (totalPages > 1) if (totalPages > 1)
{ {
UiSharedService.ColoredSeparator(UIColors.Get("PairBlue")); UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"));
var style = ImGui.GetStyle(); var style = ImGui.GetStyle();
string pageLabel = $"Page {_syncshellPageIndex + 1}/{totalPages}"; string pageLabel = $"Page {_syncshellPageIndex + 1}/{totalPages}";
@@ -371,10 +453,6 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
const string visibleLabel = "Join"; const string visibleLabel = "Join";
var label = $"{visibleLabel}##{shell.Group.GID}"; 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 isAlreadyMember = _currentSyncshells.Exists(g => string.Equals(g.GID, shell.GID, StringComparison.Ordinal));
var isRecentlyJoined = _recentlyJoined.Contains(shell.GID); var isRecentlyJoined = _recentlyJoined.Contains(shell.GID);
@@ -386,7 +464,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
var textSize = ImGui.CalcTextSize(visibleLabel); var textSize = ImGui.CalcTextSize(visibleLabel);
var width = textSize.X + style.FramePadding.X * 20f; var width = textSize.X + style.FramePadding.X * 20f;
buttonSize = new Vector2(width, 0); buttonSize = new Vector2(width, 30f);
float availX = ImGui.GetContentRegionAvail().X; float availX = ImGui.GetContentRegionAvail().X;
float curX = ImGui.GetCursorPosX(); float curX = ImGui.GetCursorPosX();
@@ -400,6 +478,9 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
if (!isAlreadyMember && !isRecentlyJoined) 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)) if (ImGui.Button(label, buttonSize))
{ {
_logger.LogInformation($"Join requested for Syncshell {shell.Group.GID} ({shell.Group.Alias})"); _logger.LogInformation($"Join requested for Syncshell {shell.Group.GID} ({shell.Group.Alias})");
@@ -436,6 +517,10 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
} }
else 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()) using (ImRaii.Disabled())
{ {
ImGui.Button(label, buttonSize); ImGui.Button(label, buttonSize);
@@ -446,6 +531,72 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
ImGui.PopStyleColor(3); ImGui.PopStyleColor(3);
} }
private (float widthUsed, float rowHeight) RenderProfileTagsSingleRow(IReadOnlyList<ProfileTagDefinition> 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() private void DrawConfirmation()
{ {
if (_joinDto != null && _joinInfo != null) if (_joinDto != null && _joinInfo != null)
@@ -470,9 +621,9 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
finalPermissions.SetDisableVFX(_ownPermissions.DisableGroupVFX); finalPermissions.SetDisableVFX(_ownPermissions.DisableGroupVFX);
_ = _apiController.GroupJoinFinalize(new GroupJoinDto(_joinDto.Group, _joinDto.Password, finalPermissions)); _ = _apiController.GroupJoinFinalize(new GroupJoinDto(_joinDto.Group, _joinDto.Password, finalPermissions));
_recentlyJoined.Add(_joinDto.Group.GID); _recentlyJoined.Add(_joinDto.Group.GID);
_joinDto = null; _joinDto = null;
_joinInfo = null; _joinInfo = null;
} }