From 38a360bfeefea46d657d66ead48eee06e47382bf Mon Sep 17 00:00:00 2001 From: CakeAndBanana Date: Tue, 16 Sep 2025 21:06:15 +0200 Subject: [PATCH 01/29] Fixed some issues with folders not showing, adding empty folders to the list. --- LightlessSync/UI/CompactUI.cs | 8 ++------ .../UI/Components/DrawGroupedGroupFolder.cs | 2 -- LightlessSync/UI/SettingsUi.cs | 12 ++++++------ 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/LightlessSync/UI/CompactUI.cs b/LightlessSync/UI/CompactUI.cs index 679c5f2..25632a2 100644 --- a/LightlessSync/UI/CompactUI.cs +++ b/LightlessSync/UI/CompactUI.cs @@ -549,7 +549,7 @@ public class CompactUi : WindowMediatorSubscriberBase bool FilterNotTaggedUsers(KeyValuePair> u) => u.Key.IsDirectlyPaired && !u.Key.IsOneSidedPair && !_tagHandler.HasAnyPairTag(u.Key.UserData.UID); bool FilterNotTaggedSyncshells(GroupFullInfoDto group) - => (!_tagHandler.HasAnySyncshellTag(group.GID) && !_configService.Current.ShowGroupedSyncshellsInAll) || true; + => !_tagHandler.HasAnySyncshellTag(group.GID) || _configService.Current.ShowGroupedSyncshellsInAll; bool FilterOfflineUsers(KeyValuePair> u) => ((u.Key.IsDirectlyPaired && _configService.Current.ShowSyncshellOfflineUsersSeparately) || !_configService.Current.ShowSyncshellOfflineUsersSeparately) @@ -607,11 +607,7 @@ public class CompactUi : WindowMediatorSubscriberBase syncshellFolderTags.Add(_drawEntityFactory.CreateDrawGroupFolder($"tag_{group.GID}", group, filteredGroupPairs, allGroupPairs)); } } - - if (syncshellFolderTags.Count > 0) - { - drawFolders.Add(new DrawGroupedGroupFolder(syncshellFolderTags, _tagHandler, _uiSharedService, _selectSyncshellForTagUi, _renameSyncshellTagUi, syncshelltag)); - } + drawFolders.Add(new DrawGroupedGroupFolder(syncshellFolderTags, _tagHandler, _uiSharedService, _selectSyncshellForTagUi, _renameSyncshellTagUi, syncshelltag)); } var allOnlineNotTaggedPairs = ImmutablePairList(allPairs diff --git a/LightlessSync/UI/Components/DrawGroupedGroupFolder.cs b/LightlessSync/UI/Components/DrawGroupedGroupFolder.cs index d1876ac..2aa3d5c 100644 --- a/LightlessSync/UI/Components/DrawGroupedGroupFolder.cs +++ b/LightlessSync/UI/Components/DrawGroupedGroupFolder.cs @@ -34,8 +34,6 @@ public class DrawGroupedGroupFolder : IDrawFolder public void Draw() { - if (!_groups.Any()) return; - string _id = "__folder_syncshells"; if (_tag != "") { diff --git a/LightlessSync/UI/SettingsUi.cs b/LightlessSync/UI/SettingsUi.cs index 3c392ba..2b9de1b 100644 --- a/LightlessSync/UI/SettingsUi.cs +++ b/LightlessSync/UI/SettingsUi.cs @@ -1145,12 +1145,12 @@ public class SettingsUi : WindowMediatorSubscriberBase } _uiShared.DrawHelpText("This will group up all Syncshells in a special 'All Syncshells' folder in the main UI."); - //if (ImGui.Checkbox("Show grouped syncshells in main screen/all syncshells", ref groupedSyncshells)) - //{ - // _configService.Current.ShowGroupedSyncshellsInAll = groupedSyncshells; - // _configService.Save(); - // Mediator.Publish(new RefreshUiMessage()); - //} + if (ImGui.Checkbox("Show grouped syncshells in main screen/all syncshells", ref groupedSyncshells)) + { + _configService.Current.ShowGroupedSyncshellsInAll = groupedSyncshells; + _configService.Save(); + Mediator.Publish(new RefreshUiMessage()); + } _uiShared.DrawHelpText("This will show grouped syncshells in main screen or group 'All Syncshells'."); if (ImGui.Checkbox("Show player name for visible players", ref showNameInsteadOfNotes)) From fe898cdc2b7dee6d33a9262c3b13accf29ce8831 Mon Sep 17 00:00:00 2001 From: defnotken Date: Wed, 17 Sep 2025 19:58:25 -0500 Subject: [PATCH 02/29] Start 1.11.13 --- LightlessSync/FileCache/FileCacheManager.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/LightlessSync/FileCache/FileCacheManager.cs b/LightlessSync/FileCache/FileCacheManager.cs index 34e5a5b..51487f6 100644 --- a/LightlessSync/FileCache/FileCacheManager.cs +++ b/LightlessSync/FileCache/FileCacheManager.cs @@ -189,7 +189,14 @@ public sealed class FileCacheManager : IHostedService Parallel.ForEach(allEntities, entity => { - cacheDict[entity.PrefixedFilePath] = entity; + if (entity != null && entity.PrefixedFilePath != null) + { + cacheDict[entity.PrefixedFilePath] = entity; + } + else + { + _logger.LogWarning("Null FileCacheEntity or PrefixedFilePath encountered in cache population: {entity}", entity); + } }); var cleanedPaths = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); From bd2c3a4a8010cde7c5e8a4a61d1d588476a5d684 Mon Sep 17 00:00:00 2001 From: defnotken Date: Thu, 18 Sep 2025 22:03:40 -0500 Subject: [PATCH 03/29] add cache logging to 1.11.13 --- LightlessSync/FileCache/FileCacheManager.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/LightlessSync/FileCache/FileCacheManager.cs b/LightlessSync/FileCache/FileCacheManager.cs index 51487f6..17f22de 100644 --- a/LightlessSync/FileCache/FileCacheManager.cs +++ b/LightlessSync/FileCache/FileCacheManager.cs @@ -260,6 +260,7 @@ public sealed class FileCacheManager : IHostedService { if (_fileCaches.TryGetValue(hash, out var caches)) { + _logger.LogTrace("Removing from DB: {hash} => {path}", hash, prefixedFilePath); var removedCount = caches?.RemoveAll(c => string.Equals(c.PrefixedFilePath, prefixedFilePath, StringComparison.Ordinal)); _logger.LogTrace("Removed from DB: {count} file(s) with hash {hash} and file cache {path}", removedCount, hash, prefixedFilePath); @@ -404,6 +405,12 @@ public sealed class FileCacheManager : IHostedService private FileCacheEntity? Validate(FileCacheEntity fileCache) { + if (string.IsNullOrWhiteSpace(fileCache.ResolvedFilepath)) + { + _logger.LogWarning("FileCacheEntity has empty ResolvedFilepath for hash {hash}, prefixed path {prefixed}", fileCache.Hash, fileCache.PrefixedFilePath); + RemoveHashedFile(fileCache.Hash, fileCache.PrefixedFilePath); + return null; + } var file = new FileInfo(fileCache.ResolvedFilepath); if (!file.Exists) { From 4060ba96f1618cd825a87a98ba627568595fc9a7 Mon Sep 17 00:00:00 2001 From: choco Date: Thu, 25 Sep 2025 00:15:42 +0200 Subject: [PATCH 04/29] moved settings button from compact UI to top tab menu --- LightlessSync/UI/CompactUI.cs | 17 +---------------- LightlessSync/UI/TopTabMenu.cs | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/LightlessSync/UI/CompactUI.cs b/LightlessSync/UI/CompactUI.cs index 5a7439a..a14be42 100644 --- a/LightlessSync/UI/CompactUI.cs +++ b/LightlessSync/UI/CompactUI.cs @@ -1,4 +1,4 @@ -using Dalamud.Bindings.ImGui; +using Dalamud.Bindings.ImGui; using Dalamud.Interface; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; @@ -109,21 +109,6 @@ public class CompactUi : WindowMediatorSubscriberBase AllowClickthrough = false; TitleBarButtons = new() { - new TitleBarButton() - { - Icon = FontAwesomeIcon.Cog, - Click = (msg) => - { - Mediator.Publish(new UiToggleMessage(typeof(SettingsUi))); - }, - IconOffset = new(2,1), - ShowTooltip = () => - { - ImGui.BeginTooltip(); - ImGui.Text("Open Lightless Settings"); - ImGui.EndTooltip(); - } - }, new TitleBarButton() { Icon = FontAwesomeIcon.Book, diff --git a/LightlessSync/UI/TopTabMenu.cs b/LightlessSync/UI/TopTabMenu.cs index c7f5035..9aa112c 100644 --- a/LightlessSync/UI/TopTabMenu.cs +++ b/LightlessSync/UI/TopTabMenu.cs @@ -1,4 +1,4 @@ -using Dalamud.Bindings.ImGui; +using Dalamud.Bindings.ImGui; using Dalamud.Interface; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; @@ -40,7 +40,8 @@ public class TopTabMenu Individual, Syncshell, Lightfinder, - UserConfig + UserConfig, + Settings } public string Filter @@ -67,7 +68,7 @@ public class TopTabMenu { var availableWidth = ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X; var spacing = ImGui.GetStyle().ItemSpacing; - var buttonX = (availableWidth - (spacing.X * 3)) / 4f; + var buttonX = (availableWidth - (spacing.X * 4)) / 5f; var buttonY = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Pause).Y; var buttonSize = new Vector2(buttonX, buttonY); var drawList = ImGui.GetWindowDrawList(); @@ -144,6 +145,17 @@ public class TopTabMenu } UiSharedService.AttachToolTip("Your User Menu"); + ImGui.SameLine(); + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + if (ImGui.Button(FontAwesomeIcon.Cog.ToIconString(), buttonSize)) + { + _lightlessMediator.Publish(new UiToggleMessage(typeof(SettingsUi))); + } + + } + UiSharedService.AttachToolTip("Lightless Sync Settings"); + ImGui.NewLine(); btncolor.Dispose(); From 6bb379ebad4bd6b4ef9de82a69ca5421c07f8756 Mon Sep 17 00:00:00 2001 From: CakeAndBanana Date: Thu, 25 Sep 2025 17:56:30 +0200 Subject: [PATCH 05/29] Returning finder on joining syncshell, added functions for syncshell profiles that would be needed later --- LightlessAPI | 2 +- LightlessSync/UI/SyncshellFinderUI.cs | 2 +- .../WebAPI/SignalR/ApiController.Functions.Callbacks.cs | 6 ++++++ LightlessSync/WebAPI/SignalR/ApiController.cs | 1 + 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/LightlessAPI b/LightlessAPI index aec2a50..d62adbb 160000 --- a/LightlessAPI +++ b/LightlessAPI @@ -1 +1 @@ -Subproject commit aec2a5023e8a513b63dc03d59a70aa51ab61941c +Subproject commit d62adbb5b61ee12fc62e43cd70fb679eb2bcd1e4 diff --git a/LightlessSync/UI/SyncshellFinderUI.cs b/LightlessSync/UI/SyncshellFinderUI.cs index 6f4050e..291cce6 100644 --- a/LightlessSync/UI/SyncshellFinderUI.cs +++ b/LightlessSync/UI/SyncshellFinderUI.cs @@ -189,7 +189,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase finalPermissions.SetDisableAnimations(_ownPermissions.DisableGroupAnimations); finalPermissions.SetDisableVFX(_ownPermissions.DisableGroupVFX); - _ = _apiController.GroupJoinFinalize(new GroupJoinDto(_joinDto.Group, _joinDto.Password, finalPermissions)); + _ = _apiController.GroupJoinFinalize(new GroupJoinDto(_joinDto.Group, _joinDto.Password, finalPermissions, true)); _joinDto = null; _joinInfo = null; } diff --git a/LightlessSync/WebAPI/SignalR/ApiController.Functions.Callbacks.cs b/LightlessSync/WebAPI/SignalR/ApiController.Functions.Callbacks.cs index 154dbe6..457361a 100644 --- a/LightlessSync/WebAPI/SignalR/ApiController.Functions.Callbacks.cs +++ b/LightlessSync/WebAPI/SignalR/ApiController.Functions.Callbacks.cs @@ -277,6 +277,12 @@ public partial class ApiController _lightlessHub!.On(nameof(Client_GroupSendInfo), act); } + public void OnGroupUpdateProfile(Action act) + { + if (_initialized) return; + _lightlessHub!.On(nameof(Client_GroupSendProfile), act); + } + public void OnReceiveServerMessage(Action act) { if (_initialized) return; diff --git a/LightlessSync/WebAPI/SignalR/ApiController.cs b/LightlessSync/WebAPI/SignalR/ApiController.cs index f341d5d..4a4071b 100644 --- a/LightlessSync/WebAPI/SignalR/ApiController.cs +++ b/LightlessSync/WebAPI/SignalR/ApiController.cs @@ -445,6 +445,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL OnGroupPairLeft((dto) => _ = Client_GroupPairLeft(dto)); OnGroupSendFullInfo((dto) => _ = Client_GroupSendFullInfo(dto)); OnGroupSendInfo((dto) => _ = Client_GroupSendInfo(dto)); + OnGroupUpdateProfile((dto) => _ = Client_GroupSendProfile(dto)); OnGroupChangeUserPairPermissions((dto) => _ = Client_GroupChangeUserPairPermissions(dto)); OnGposeLobbyJoin((dto) => _ = Client_GposeLobbyJoin(dto)); From ab3ca78c7f09dcfd8a07f1e611e4db1594f8cb8c Mon Sep 17 00:00:00 2001 From: CakeAndBanana Date: Thu, 25 Sep 2025 18:53:20 +0200 Subject: [PATCH 06/29] Fixed typo in setup --- LightlessSync/UI/IntroUI.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LightlessSync/UI/IntroUI.cs b/LightlessSync/UI/IntroUI.cs index bc32128..470cadb 100644 --- a/LightlessSync/UI/IntroUI.cs +++ b/LightlessSync/UI/IntroUI.cs @@ -167,7 +167,7 @@ public partial class IntroUi : WindowMediatorSubscriberBase } else { - UiSharedService.TextWrapped("To not unnecessary download files already present on your computer, Lightless Sync will have to scan your Penumbra mod directory. " + + UiSharedService.TextWrapped("To not unnecessarily download files already present on your computer, Lightless Sync will have to scan your Penumbra mod directory. " + "Additionally, a local storage folder must be set where Lightless Sync will download other character files to. " + "Once the storage folder is set and the scan complete, this page will automatically forward to registration at a service."); UiSharedService.TextWrapped("Note: The initial scan, depending on the amount of mods you have, might take a while. Please wait until it is completed."); From 864a2e66771c1a7d5ad452abe39cf02ae8bf540c Mon Sep 17 00:00:00 2001 From: azyges Date: Fri, 26 Sep 2025 03:29:28 +0900 Subject: [PATCH 07/29] slight clean up and minor spelling mistake --- LightlessSync/Services/BroadcastService.cs | 41 +++++++++++----------- LightlessSync/UI/IntroUI.cs | 2 +- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/LightlessSync/Services/BroadcastService.cs b/LightlessSync/Services/BroadcastService.cs index 09a9674..6d6409e 100644 --- a/LightlessSync/Services/BroadcastService.cs +++ b/LightlessSync/Services/BroadcastService.cs @@ -4,9 +4,7 @@ using LightlessSync.LightlessConfiguration; using LightlessSync.Services.Mediator; using LightlessSync.Utils; using LightlessSync.WebAPI; -using LightlessSync.WebAPI.SignalR; using Microsoft.AspNetCore.SignalR; -using Microsoft.AspNetCore.SignalR.Client; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -16,7 +14,6 @@ public class BroadcastService : IHostedService, IMediatorSubscriber private readonly ILogger _logger; private readonly ApiController _apiController; private readonly LightlessMediator _mediator; - private readonly HubFactory _hubFactory; private readonly LightlessConfigService _config; private readonly DalamudUtilService _dalamudUtil; public LightlessMediator Mediator => _mediator; @@ -29,35 +26,37 @@ public class BroadcastService : IHostedService, IMediatorSubscriber private TimeSpan? _remainingTtl = null; private DateTime _lastTtlCheck = DateTime.MinValue; private DateTime _lastForcedDisableTime = DateTime.MinValue; - private static readonly TimeSpan DisableCooldown = TimeSpan.FromSeconds(5); + private static readonly TimeSpan _disableCooldown = TimeSpan.FromSeconds(5); public TimeSpan? RemainingTtl => _remainingTtl; public TimeSpan? RemainingCooldown { get { var elapsed = DateTime.UtcNow - _lastForcedDisableTime; - if (elapsed >= DisableCooldown) return null; - return DisableCooldown - elapsed; + if (elapsed >= _disableCooldown) return null; + return _disableCooldown - elapsed; } } - public BroadcastService(ILogger logger, LightlessMediator mediator, HubFactory hubFactory, LightlessConfigService config, DalamudUtilService dalamudUtil, ApiController apiController) + + public BroadcastService(ILogger logger, LightlessMediator mediator, LightlessConfigService config, DalamudUtilService dalamudUtil, ApiController apiController) { _logger = logger; _mediator = mediator; - _hubFactory = hubFactory; _config = config; _dalamudUtil = dalamudUtil; _apiController = apiController; } + private async Task RequireConnectionAsync(string context, Func action) { if (!_apiController.IsConnected) { - _logger.LogDebug($"{context} skipped, not connected"); + _logger.LogDebug(context + " skipped, not connected"); return; } await action().ConfigureAwait(false); } + public async Task StartAsync(CancellationToken cancellationToken) { _mediator.Subscribe(this, OnEnableBroadcast); @@ -65,7 +64,7 @@ public class BroadcastService : IHostedService, IMediatorSubscriber _mediator.Subscribe(this, OnTick); _apiController.OnConnected += () => _ = CheckLightfinderSupportAsync(cancellationToken); - _ = CheckLightfinderSupportAsync(cancellationToken); + //_ = CheckLightfinderSupportAsync(cancellationToken); } public Task StopAsync(CancellationToken cancellationToken) @@ -86,13 +85,12 @@ public class BroadcastService : IHostedService, IMediatorSubscriber if (cancellationToken.IsCancellationRequested) return; - var hub = _hubFactory.GetOrCreate(CancellationToken.None); var dummy = "0".PadLeft(64, '0'); - await hub.InvokeAsync("IsUserBroadcasting", dummy, cancellationToken); - await hub.InvokeAsync("SetBroadcastStatus", dummy, true, null, cancellationToken); - await hub.InvokeAsync("GetBroadcastTtl", dummy, cancellationToken); - await hub.InvokeAsync>("AreUsersBroadcasting", new[] { dummy }, cancellationToken); + await _apiController.IsUserBroadcasting(dummy).ConfigureAwait(false); + await _apiController.SetBroadcastStatus(dummy, true, null).ConfigureAwait(false); + await _apiController.GetBroadcastTtl(dummy).ConfigureAwait(false); + await _apiController.AreUsersBroadcasting([dummy]).ConfigureAwait(false); IsLightFinderAvailable = true; _logger.LogInformation("Lightfinder is available."); @@ -119,6 +117,7 @@ public class BroadcastService : IHostedService, IMediatorSubscriber _config.Current.BroadcastEnabled = false; _config.Current.BroadcastTtl = DateTime.MinValue; _config.Save(); + _mediator.Publish(new BroadcastStatusChangedMessage(false, null)); } } @@ -151,13 +150,13 @@ public class BroadcastService : IHostedService, IMediatorSubscriber _config.Save(); _mediator.Publish(new BroadcastStatusChangedMessage(false, null)); - Mediator.Publish(new EventMessage(new Services.Events.Event(nameof(BroadcastService), Services.Events.EventSeverity.Informational,$"Disabled Lightfinder for Player: {msg.HashedCid}"))); + Mediator.Publish(new EventMessage(new Services.Events.Event(nameof(BroadcastService), Services.Events.EventSeverity.Informational, $"Disabled Lightfinder for Player: {msg.HashedCid}"))); return; } _waitingForTtlFetch = true; - var ttl = await GetBroadcastTtlAsync(msg.HashedCid).ConfigureAwait(false); + TimeSpan? ttl = await GetBroadcastTtlAsync(msg.HashedCid).ConfigureAwait(false); if (ttl is { } remaining && remaining > TimeSpan.Zero) { @@ -325,8 +324,8 @@ public class BroadcastService : IHostedService, IMediatorSubscriber _syncedOnStartup = true; try { - var hashedCid = (await _dalamudUtil.GetCIDAsync().ConfigureAwait(false)).ToString().GetHash256(); - var ttl = await GetBroadcastTtlAsync(hashedCid).ConfigureAwait(false); + string hashedCid = (await _dalamudUtil.GetCIDAsync().ConfigureAwait(false)).ToString().GetHash256(); + TimeSpan? ttl = await GetBroadcastTtlAsync(hashedCid).ConfigureAwait(false); if (ttl is { } remaining && remaining > TimeSpan.Zero) { @@ -357,8 +356,8 @@ public class BroadcastService : IHostedService, IMediatorSubscriber return; } - var expiry = _config.Current.BroadcastTtl; - var remaining = expiry - DateTime.UtcNow; + DateTime expiry = _config.Current.BroadcastTtl; + TimeSpan remaining = expiry - DateTime.UtcNow; _remainingTtl = remaining > TimeSpan.Zero ? remaining : null; if (_remainingTtl == null) { diff --git a/LightlessSync/UI/IntroUI.cs b/LightlessSync/UI/IntroUI.cs index bc32128..470cadb 100644 --- a/LightlessSync/UI/IntroUI.cs +++ b/LightlessSync/UI/IntroUI.cs @@ -167,7 +167,7 @@ public partial class IntroUi : WindowMediatorSubscriberBase } else { - UiSharedService.TextWrapped("To not unnecessary download files already present on your computer, Lightless Sync will have to scan your Penumbra mod directory. " + + UiSharedService.TextWrapped("To not unnecessarily download files already present on your computer, Lightless Sync will have to scan your Penumbra mod directory. " + "Additionally, a local storage folder must be set where Lightless Sync will download other character files to. " + "Once the storage folder is set and the scan complete, this page will automatically forward to registration at a service."); UiSharedService.TextWrapped("Note: The initial scan, depending on the amount of mods you have, might take a while. Please wait until it is completed."); From 9bc2ad24cd752d3b3e060c2017450d2dbfa4fdf5 Mon Sep 17 00:00:00 2001 From: azyges Date: Fri, 26 Sep 2025 04:51:05 +0900 Subject: [PATCH 08/29] clearing syncshell from lightfinder users --- LightlessAPI | 2 +- LightlessSync/UI/SyncshellAdminUI.cs | 20 +++++++++++++++---- LightlessSync/UI/SyncshellFinderUI.cs | 2 +- .../SignalR/ApiController.Functions.Groups.cs | 5 +++++ 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/LightlessAPI b/LightlessAPI index d62adbb..3c10380 160000 --- a/LightlessAPI +++ b/LightlessAPI @@ -1 +1 @@ -Subproject commit d62adbb5b61ee12fc62e43cd70fb679eb2bcd1e4 +Subproject commit 3c10380162b162c47c99f63ecfc627a49887fe84 diff --git a/LightlessSync/UI/SyncshellAdminUI.cs b/LightlessSync/UI/SyncshellAdminUI.cs index 3dd90de..fb30067 100644 --- a/LightlessSync/UI/SyncshellAdminUI.cs +++ b/LightlessSync/UI/SyncshellAdminUI.cs @@ -336,7 +336,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase } ImGui.Separator(); - if (_uiSharedService.MediumTreeNode("Mass Cleanup", UIColors.Get("LightlessPurple"))) + if (_uiSharedService.MediumTreeNode("Mass Cleanup", UIColors.Get("DimRed"))) { using (ImRaii.Disabled(!UiSharedService.CtrlPressed())) { @@ -348,6 +348,18 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase UiSharedService.AttachToolTip("This will remove all non-pinned, non-moderator users from the Syncshell." + UiSharedService.TooltipSeparator + "Hold CTRL to enable this button"); + ImGui.SameLine(); + + using (ImRaii.Disabled(!UiSharedService.CtrlPressed())) + { + if (_uiSharedService.IconTextButton(FontAwesomeIcon.Brush, "Clear Lightfinder Users")) + { + _ = _apiController.GroupClearFinder(new(GroupFullInfo.Group)); + } + } + UiSharedService.AttachToolTip("This will remove all users that joined through Lightfinder from the Syncshell." + + UiSharedService.TooltipSeparator + "Hold CTRL to enable this button"); + ImGuiHelpers.ScaledDummy(2f); ImGui.Separator(); ImGuiHelpers.ScaledDummy(2f); @@ -410,12 +422,12 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase UiSharedService.TextWrapped($"Syncshell was pruned and {_pruneTask.Result} inactive user(s) have been removed."); } } - _uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f); + _uiSharedService.ColoredSeparator(UIColors.Get("DimRed"), 1.5f); ImGui.TreePop(); } ImGui.Separator(); - if (_uiSharedService.MediumTreeNode("User Bans", UIColors.Get("LightlessPurple"))) + if (_uiSharedService.MediumTreeNode("User Bans", UIColors.Get("LightlessYellow"))) { if (_uiSharedService.IconTextButton(FontAwesomeIcon.Retweet, "Refresh Banlist from Server")) { @@ -456,7 +468,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase } ImGui.EndTable(); } - _uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f); + _uiSharedService.ColoredSeparator(UIColors.Get("LightlessYellow"), 1.5f); ImGui.TreePop(); } ImGui.Separator(); diff --git a/LightlessSync/UI/SyncshellFinderUI.cs b/LightlessSync/UI/SyncshellFinderUI.cs index 291cce6..6f4050e 100644 --- a/LightlessSync/UI/SyncshellFinderUI.cs +++ b/LightlessSync/UI/SyncshellFinderUI.cs @@ -189,7 +189,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase finalPermissions.SetDisableAnimations(_ownPermissions.DisableGroupAnimations); finalPermissions.SetDisableVFX(_ownPermissions.DisableGroupVFX); - _ = _apiController.GroupJoinFinalize(new GroupJoinDto(_joinDto.Group, _joinDto.Password, finalPermissions, true)); + _ = _apiController.GroupJoinFinalize(new GroupJoinDto(_joinDto.Group, _joinDto.Password, finalPermissions)); _joinDto = null; _joinInfo = null; } diff --git a/LightlessSync/WebAPI/SignalR/ApiController.Functions.Groups.cs b/LightlessSync/WebAPI/SignalR/ApiController.Functions.Groups.cs index cd17ea8..c7581d1 100644 --- a/LightlessSync/WebAPI/SignalR/ApiController.Functions.Groups.cs +++ b/LightlessSync/WebAPI/SignalR/ApiController.Functions.Groups.cs @@ -45,6 +45,11 @@ public partial class ApiController CheckConnection(); await _lightlessHub!.SendAsync(nameof(GroupClear), group).ConfigureAwait(false); } + public async Task GroupClearFinder(GroupDto group) + { + CheckConnection(); + await _lightlessHub!.SendAsync(nameof(GroupClearFinder), group).ConfigureAwait(false); + } public async Task GroupCreate() { From e3a3f16d14706f330ae807708facda1189fb45ad Mon Sep 17 00:00:00 2001 From: CakeAndBanana Date: Fri, 26 Sep 2025 04:40:15 +0200 Subject: [PATCH 09/29] Hiding of syncshells that already been joined in shell finder --- LightlessSync/Plugin.cs | 2 +- LightlessSync/UI/SyncshellFinderUI.cs | 91 ++++++++++++++------------- 2 files changed, 50 insertions(+), 43 deletions(-) diff --git a/LightlessSync/Plugin.cs b/LightlessSync/Plugin.cs index b9a70fe..09f218e 100644 --- a/LightlessSync/Plugin.cs +++ b/LightlessSync/Plugin.cs @@ -230,7 +230,7 @@ public sealed class Plugin : IDalamudPlugin s.GetRequiredService(), s.GetRequiredService())); collection.AddScoped(); collection.AddScoped((s) => new BroadcastUI(s.GetRequiredService>(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService())); - collection.AddScoped((s) => new SyncshellFinderUI(s.GetRequiredService>(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService())); + collection.AddScoped((s) => new SyncshellFinderUI(s.GetRequiredService>(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService())); collection.AddScoped(); collection.AddScoped(); collection.AddScoped(); diff --git a/LightlessSync/UI/SyncshellFinderUI.cs b/LightlessSync/UI/SyncshellFinderUI.cs index 6f4050e..e009bb5 100644 --- a/LightlessSync/UI/SyncshellFinderUI.cs +++ b/LightlessSync/UI/SyncshellFinderUI.cs @@ -4,15 +4,15 @@ using Dalamud.Interface.Colors; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using LightlessSync.API.Data.Enum; +using LightlessSync.API.Data.Extensions; using LightlessSync.API.Dto; using LightlessSync.API.Dto.Group; -using LightlessSync.LightlessConfiguration; +using LightlessSync.PlayerData.Pairs; using LightlessSync.Services; using LightlessSync.Services.Mediator; using LightlessSync.Utils; using LightlessSync.WebAPI; using Microsoft.Extensions.Logging; -using LightlessSync.API.Data.Extensions; using System.Numerics; namespace LightlessSync.UI; @@ -20,12 +20,12 @@ namespace LightlessSync.UI; public class SyncshellFinderUI : WindowMediatorSubscriberBase { private readonly ApiController _apiController; - private readonly LightlessConfigService _configService; private readonly BroadcastService _broadcastService; private readonly UiSharedService _uiSharedService; private readonly BroadcastScannerService _broadcastScannerService; + private readonly PairManager _pairManager; - private readonly List _nearbySyncshells = new(); + private readonly List _nearbySyncshells = []; private int _selectedNearbyIndex = -1; private GroupJoinDto? _joinDto; @@ -37,17 +37,16 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase LightlessMediator mediator, PerformanceCollectorService performanceCollectorService, BroadcastService broadcastService, - LightlessConfigService configService, UiSharedService uiShared, ApiController apiController, - BroadcastScannerService broadcastScannerService - ) : base(logger, mediator, "Shellfinder###LightlessSyncshellFinderUI", performanceCollectorService) + BroadcastScannerService broadcastScannerService, + PairManager pairManager) : base(logger, mediator, "Shellfinder###LightlessSyncshellFinderUI", performanceCollectorService) { _broadcastService = broadcastService; _uiSharedService = uiShared; - _configService = configService; _apiController = apiController; _broadcastScannerService = broadcastScannerService; + _pairManager = pairManager; IsOpen = false; SizeConstraints = new() @@ -56,14 +55,14 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase MaximumSize = new(600, 550) }; - Mediator.Subscribe(this, async _ => await RefreshSyncshellsAsync()); - Mediator.Subscribe(this, async _ => await RefreshSyncshellsAsync()); + Mediator.Subscribe(this, async _ => await RefreshSyncshellsAsync().ConfigureAwait(false)); + Mediator.Subscribe(this, async _ => await RefreshSyncshellsAsync().ConfigureAwait(false)); } public override async void OnOpen() { _ownPermissions = _apiController.DefaultPermissions.DeepClone()!; - await RefreshSyncshellsAsync(); + await RefreshSyncshellsAsync().ConfigureAwait(false); } protected override void DrawInternal() @@ -170,28 +169,31 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase private void DrawConfirmation() { - ImGui.Separator(); - ImGui.TextUnformatted($"Join Syncshell: {_joinDto.Group.AliasOrGID} by {_joinInfo.OwnerAliasOrUID}"); - ImGuiHelpers.ScaledDummy(2f); - ImGui.TextUnformatted("Suggested Syncshell Permissions:"); - - DrawPermissionRow("Sounds", _joinInfo.GroupPermissions.IsPreferDisableSounds(), _ownPermissions.DisableGroupSounds, v => _ownPermissions.DisableGroupSounds = v); - DrawPermissionRow("Animations", _joinInfo.GroupPermissions.IsPreferDisableAnimations(), _ownPermissions.DisableGroupAnimations, v => _ownPermissions.DisableGroupAnimations = v); - DrawPermissionRow("VFX", _joinInfo.GroupPermissions.IsPreferDisableVFX(), _ownPermissions.DisableGroupVFX, v => _ownPermissions.DisableGroupVFX = v); - - ImGui.NewLine(); - ImGui.NewLine(); - - if (_uiSharedService.IconTextButton(Dalamud.Interface.FontAwesomeIcon.Plus, $"Finalize and join {_joinDto.Group.AliasOrGID}")) + if (_joinDto != null && _joinInfo != null) { - var finalPermissions = GroupUserPreferredPermissions.NoneSet; - finalPermissions.SetDisableSounds(_ownPermissions.DisableGroupSounds); - finalPermissions.SetDisableAnimations(_ownPermissions.DisableGroupAnimations); - finalPermissions.SetDisableVFX(_ownPermissions.DisableGroupVFX); + ImGui.Separator(); + ImGui.TextUnformatted($"Join Syncshell: {_joinDto.Group.AliasOrGID} by {_joinInfo.OwnerAliasOrUID}"); + ImGuiHelpers.ScaledDummy(2f); + ImGui.TextUnformatted("Suggested Syncshell Permissions:"); - _ = _apiController.GroupJoinFinalize(new GroupJoinDto(_joinDto.Group, _joinDto.Password, finalPermissions)); - _joinDto = null; - _joinInfo = null; + DrawPermissionRow("Sounds", _joinInfo.GroupPermissions.IsPreferDisableSounds(), _ownPermissions.DisableGroupSounds, v => _ownPermissions.DisableGroupSounds = v); + DrawPermissionRow("Animations", _joinInfo.GroupPermissions.IsPreferDisableAnimations(), _ownPermissions.DisableGroupAnimations, v => _ownPermissions.DisableGroupAnimations = v); + DrawPermissionRow("VFX", _joinInfo.GroupPermissions.IsPreferDisableVFX(), _ownPermissions.DisableGroupVFX, v => _ownPermissions.DisableGroupVFX = v); + + ImGui.NewLine(); + ImGui.NewLine(); + + if (_uiSharedService.IconTextButton(Dalamud.Interface.FontAwesomeIcon.Plus, $"Finalize and join {_joinDto.Group.AliasOrGID}")) + { + var finalPermissions = GroupUserPreferredPermissions.NoneSet; + finalPermissions.SetDisableSounds(_ownPermissions.DisableGroupSounds); + finalPermissions.SetDisableAnimations(_ownPermissions.DisableGroupAnimations); + finalPermissions.SetDisableVFX(_ownPermissions.DisableGroupVFX); + + _ = _apiController.GroupJoinFinalize(new GroupJoinDto(_joinDto.Group, _joinDto.Password, finalPermissions)); + _joinDto = null; + _joinInfo = null; + } } } @@ -224,6 +226,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase private async Task RefreshSyncshellsAsync() { var syncshellBroadcasts = _broadcastScannerService.GetActiveSyncshellBroadcasts(); + var currentSyncshells = _pairManager.GroupPairs.Select(g => g.Key).ToList(); if (syncshellBroadcasts.Count == 0) { @@ -231,11 +234,20 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase return; } - List updatedList; + List updatedList = []; try { - var groups = await _apiController.GetBroadcastedGroups(syncshellBroadcasts); - updatedList = groups?.ToList() ?? new(); + var groups = await _apiController.GetBroadcastedGroups(syncshellBroadcasts).ConfigureAwait(false); + if (groups != null && currentSyncshells != null) + { + foreach (var group in groups) + { + if (!currentSyncshells.Exists(g => string.Equals(g.GID, group.GID, StringComparison.Ordinal))) + { + updatedList = groups?.ToList(); + } + } + } } catch (Exception ex) { @@ -243,8 +255,8 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase return; } - var currentGids = _nearbySyncshells.Select(s => s.Group.GID).ToHashSet(); - var newGids = updatedList.Select(s => s.Group.GID).ToHashSet(); + var currentGids = _nearbySyncshells.Select(s => s.Group.GID).ToHashSet(StringComparer.Ordinal); + var newGids = updatedList.Select(s => s.Group.GID).ToHashSet(StringComparer.Ordinal); if (currentGids.SetEquals(newGids)) return; @@ -256,7 +268,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase if (previousGid != null) { - var newIndex = _nearbySyncshells.FindIndex(s => s.Group.GID == previousGid); + var newIndex = _nearbySyncshells.FindIndex(s => string.Equals(s.Group.GID, previousGid, StringComparison.Ordinal)); if (newIndex >= 0) { _selectedNearbyIndex = newIndex; @@ -290,9 +302,4 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase return _nearbySyncshells[_selectedNearbyIndex].Group.GID; } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - } } From c182d10a0aa2725b77a18da202453057daab5fb3 Mon Sep 17 00:00:00 2001 From: CakeAndBanana Date: Fri, 26 Sep 2025 04:54:12 +0200 Subject: [PATCH 10/29] Moved the warning triangle downwards --- LightlessSync/UI/CompactUI.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/LightlessSync/UI/CompactUI.cs b/LightlessSync/UI/CompactUI.cs index b93b393..6af9498 100644 --- a/LightlessSync/UI/CompactUI.cs +++ b/LightlessSync/UI/CompactUI.cs @@ -561,6 +561,7 @@ public class CompactUi : WindowMediatorSubscriberBase if ((isOverTriHold || isOverVRAMUsage) && _playerPerformanceConfig.Current.WarnOnExceedingThresholds) { ImGui.SameLine(); + ImGui.SetCursorPosY(cursorY + 15f); _uiSharedService.IconText(FontAwesomeIcon.ExclamationTriangle, UIColors.Get("LightlessYellow")); string warningMessage = ""; From f6784fdf2864fe2aa9f0d9470065630ae1023134 Mon Sep 17 00:00:00 2001 From: CakeAndBanana Date: Fri, 26 Sep 2025 05:14:39 +0200 Subject: [PATCH 11/29] Refactored drawfolders function of CompactUI --- LightlessSync/UI/CompactUI.cs | 273 +++++++++++++++++----------------- 1 file changed, 137 insertions(+), 136 deletions(-) diff --git a/LightlessSync/UI/CompactUI.cs b/LightlessSync/UI/CompactUI.cs index 6af9498..77305c1 100644 --- a/LightlessSync/UI/CompactUI.cs +++ b/LightlessSync/UI/CompactUI.cs @@ -141,7 +141,7 @@ public class CompactUi : WindowMediatorSubscriberBase }, }; - _drawFolders = [.. GetDrawFolders()]; + _drawFolders = [.. DrawFolders]; #if DEBUG string dev = "Dev Build"; @@ -158,7 +158,7 @@ public class CompactUi : WindowMediatorSubscriberBase Mediator.Subscribe(this, (_) => UiSharedService_GposeEnd()); Mediator.Subscribe(this, (msg) => _currentDownloads[msg.DownloadId] = msg.DownloadStatus); Mediator.Subscribe(this, (msg) => _currentDownloads.TryRemove(msg.DownloadId, out _)); - Mediator.Subscribe(this, (msg) => _drawFolders = GetDrawFolders().ToList()); + Mediator.Subscribe(this, (msg) => _drawFolders = DrawFolders.ToList()); Flags |= ImGuiWindowFlags.NoDocking; @@ -612,164 +612,165 @@ public class CompactUi : WindowMediatorSubscriberBase } } - private IEnumerable GetDrawFolders() + private IEnumerable DrawFolders { - List drawFolders = []; - - var allPairs = _pairManager.PairsWithGroups - .ToDictionary(k => k.Key, k => k.Value); - var filteredPairs = allPairs - .Where(p => - { - if (_tabMenu.Filter.IsNullOrEmpty()) return true; - return p.Key.UserData.AliasOrUID.Contains(_tabMenu.Filter, StringComparison.OrdinalIgnoreCase) || - (p.Key.GetNote()?.Contains(_tabMenu.Filter, StringComparison.OrdinalIgnoreCase) ?? false) || - (p.Key.PlayerName?.Contains(_tabMenu.Filter, StringComparison.OrdinalIgnoreCase) ?? false); - }) - .ToDictionary(k => k.Key, k => k.Value); - - string? AlphabeticalSort(KeyValuePair> u) - => (_configService.Current.ShowCharacterNameInsteadOfNotesForVisible && !string.IsNullOrEmpty(u.Key.PlayerName) - ? (_configService.Current.PreferNotesOverNamesForVisible ? u.Key.GetNote() : u.Key.PlayerName) - : (u.Key.GetNote() ?? u.Key.UserData.AliasOrUID)); - bool FilterOnlineOrPausedSelf(KeyValuePair> u) - => (u.Key.IsOnline || (!u.Key.IsOnline && !_configService.Current.ShowOfflineUsersSeparately) - || u.Key.UserPair.OwnPermissions.IsPaused()); - Dictionary> BasicSortedDictionary(IEnumerable>> u) - => u.OrderByDescending(u => u.Key.IsVisible) - .ThenByDescending(u => u.Key.IsOnline) - .ThenBy(AlphabeticalSort, StringComparer.OrdinalIgnoreCase) - .ToDictionary(u => u.Key, u => u.Value); - ImmutableList ImmutablePairList(IEnumerable>> u) - => u.Select(k => k.Key).ToImmutableList(); - bool FilterVisibleUsers(KeyValuePair> u) - => u.Key.IsVisible - && (_configService.Current.ShowSyncshellUsersInVisible || !(!_configService.Current.ShowSyncshellUsersInVisible && !u.Key.IsDirectlyPaired)); - bool FilterTagUsers(KeyValuePair> u, string tag) - => u.Key.IsDirectlyPaired && !u.Key.IsOneSidedPair && _tagHandler.HasPairTag(u.Key.UserData.UID, tag); - bool FilterGroupUsers(KeyValuePair> u, GroupFullInfoDto group) - => u.Value.Exists(g => string.Equals(g.GID, group.GID, StringComparison.Ordinal)); - bool FilterNotTaggedUsers(KeyValuePair> u) - => u.Key.IsDirectlyPaired && !u.Key.IsOneSidedPair && !_tagHandler.HasAnyPairTag(u.Key.UserData.UID); - bool FilterNotTaggedSyncshells(GroupFullInfoDto group) - => !_tagHandler.HasAnySyncshellTag(group.GID) || _configService.Current.ShowGroupedSyncshellsInAll; - bool FilterOfflineUsers(KeyValuePair> u) - => ((u.Key.IsDirectlyPaired && _configService.Current.ShowSyncshellOfflineUsersSeparately) - || !_configService.Current.ShowSyncshellOfflineUsersSeparately) - && (!u.Key.IsOneSidedPair || u.Value.Any()) && !u.Key.IsOnline && !u.Key.UserPair.OwnPermissions.IsPaused(); - bool FilterOfflineSyncshellUsers(KeyValuePair> u) - => (!u.Key.IsDirectlyPaired && !u.Key.IsOnline && !u.Key.UserPair.OwnPermissions.IsPaused()); - - - if (_configService.Current.ShowVisibleUsersSeparately) + get { - var allVisiblePairs = ImmutablePairList(allPairs - .Where(FilterVisibleUsers)); - var filteredVisiblePairs = BasicSortedDictionary(filteredPairs - .Where(FilterVisibleUsers)); + var drawFolders = new List(); + var filter = _tabMenu.Filter; - drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(TagHandler.CustomVisibleTag, filteredVisiblePairs, allVisiblePairs)); - } + var allPairs = _pairManager.PairsWithGroups.ToDictionary(k => k.Key, k => k.Value); + var filteredPairs = allPairs.Where(p => PassesFilter(p.Key, filter)).ToDictionary(k => k.Key, k => k.Value); - List groupFolders = new(); - - foreach (var group in _pairManager.GroupPairs.Select(g => g.Key).OrderBy(g => g.GroupAliasOrGID, StringComparer.OrdinalIgnoreCase)) - { - GetGroups(allPairs, filteredPairs, group, out ImmutableList allGroupPairs, out Dictionary> filteredGroupPairs); - if (FilterNotTaggedSyncshells(group)) + //Filter of online/visible pairs + if (_configService.Current.ShowVisibleUsersSeparately) { - groupFolders.Add(_drawEntityFactory.CreateDrawGroupFolder(group, filteredGroupPairs, allGroupPairs)); + var allVisiblePairs = ImmutablePairList(allPairs.Where(p => FilterVisibleUsers(p.Key))); + var filteredVisiblePairs = BasicSortedDictionary(filteredPairs.Where(p => FilterVisibleUsers(p.Key))); + + drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(TagHandler.CustomVisibleTag, filteredVisiblePairs, allVisiblePairs)); } - } - if (_configService.Current.GroupUpSyncshells) - drawFolders.Add(new DrawGroupedGroupFolder(groupFolders, _tagHandler, _uiSharedService, _selectSyncshellForTagUi, _renameSyncshellTagUi, "")); - else - drawFolders.AddRange(groupFolders); - - var tags = _tagHandler.GetAllPairTagsSorted(); - foreach (var tag in tags) - { - var allTagPairs = ImmutablePairList(allPairs - .Where(u => FilterTagUsers(u, tag))); - var filteredTagPairs = BasicSortedDictionary(filteredPairs - .Where(u => FilterTagUsers(u, tag) && FilterOnlineOrPausedSelf(u))); - - drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(tag, filteredTagPairs, allTagPairs)); - } - - var syncshellTags = _tagHandler.GetAllSyncshellTagsSorted(); - foreach (var syncshelltag in syncshellTags) - { - List syncshellFolderTags = []; + //Filter of not foldered syncshells + var groupFolders = new List(); foreach (var group in _pairManager.GroupPairs.Select(g => g.Key).OrderBy(g => g.GroupAliasOrGID, StringComparer.OrdinalIgnoreCase)) { - if (_tagHandler.HasSyncshellTag(group.GID, syncshelltag)) + GetGroups(allPairs, filteredPairs, group, out ImmutableList allGroupPairs, out Dictionary> filteredGroupPairs); + + if (FilterNotTaggedSyncshells(group)) { - GetGroups(allPairs, filteredPairs, group, out ImmutableList allGroupPairs, out Dictionary> filteredGroupPairs); - syncshellFolderTags.Add(_drawEntityFactory.CreateDrawGroupFolder($"tag_{group.GID}", group, filteredGroupPairs, allGroupPairs)); + groupFolders.Add(_drawEntityFactory.CreateDrawGroupFolder(group, filteredGroupPairs, allGroupPairs)); } } - drawFolders.Add(new DrawGroupedGroupFolder(syncshellFolderTags, _tagHandler, _uiSharedService, _selectSyncshellForTagUi, _renameSyncshellTagUi, syncshelltag)); - } - var allOnlineNotTaggedPairs = ImmutablePairList(allPairs - .Where(FilterNotTaggedUsers)); - var onlineNotTaggedPairs = BasicSortedDictionary(filteredPairs - .Where(u => FilterNotTaggedUsers(u) && FilterOnlineOrPausedSelf(u))); + //Filter of grouped up syncshells (All Syncshells Folder) + if (_configService.Current.GroupUpSyncshells) + drawFolders.Add(new DrawGroupedGroupFolder(groupFolders, _tagHandler, _uiSharedService, + _selectSyncshellForTagUi, _renameSyncshellTagUi, "")); + else + drawFolders.AddRange(groupFolders); - drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder((_configService.Current.ShowOfflineUsersSeparately ? TagHandler.CustomOnlineTag : TagHandler.CustomAllTag), - onlineNotTaggedPairs, allOnlineNotTaggedPairs)); - - if (_configService.Current.ShowOfflineUsersSeparately) - { - var allOfflinePairs = ImmutablePairList(allPairs - .Where(FilterOfflineUsers)); - var filteredOfflinePairs = BasicSortedDictionary(filteredPairs - .Where(FilterOfflineUsers)); - - drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(TagHandler.CustomOfflineTag, filteredOfflinePairs, allOfflinePairs)); - if (_configService.Current.ShowSyncshellOfflineUsersSeparately) + //Filter of grouped/foldered pairs + foreach (var tag in _tagHandler.GetAllPairTagsSorted()) { - var allOfflineSyncshellUsers = ImmutablePairList(allPairs - .Where(FilterOfflineSyncshellUsers)); - var filteredOfflineSyncshellUsers = BasicSortedDictionary(filteredPairs - .Where(FilterOfflineSyncshellUsers)); + var allTagPairs = ImmutablePairList(allPairs.Where(p => FilterTagUsers(p.Key, tag))); + var filteredTagPairs = BasicSortedDictionary(filteredPairs.Where(p => FilterTagUsers(p.Key, tag) && FilterOnlineOrPausedSelf(p.Key))); - drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(TagHandler.CustomOfflineSyncshellTag, - filteredOfflineSyncshellUsers, - allOfflineSyncshellUsers)); + drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(tag, filteredTagPairs, allTagPairs)); } - } - drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(TagHandler.CustomUnpairedTag, - BasicSortedDictionary(filteredPairs.Where(u => u.Key.IsOneSidedPair)), - ImmutablePairList(allPairs.Where(u => u.Key.IsOneSidedPair)))); - - return drawFolders; - - void GetGroups(Dictionary> allPairs, Dictionary> filteredPairs, GroupFullInfoDto group, out ImmutableList allGroupPairs, out Dictionary> filteredGroupPairs) - { - allGroupPairs = ImmutablePairList(allPairs - .Where(u => FilterGroupUsers(u, group))); - filteredGroupPairs = filteredPairs - .Where(u => FilterGroupUsers(u, group) && FilterOnlineOrPausedSelf(u)) - .OrderByDescending(u => u.Key.IsOnline) - .ThenBy(u => + //Filter of grouped/foldered syncshells + foreach (var syncshellTag in _tagHandler.GetAllSyncshellTagsSorted()) + { + var syncshellFolderTags = new List(); + foreach (var group in _pairManager.GroupPairs.Select(g => g.Key).OrderBy(g => g.GroupAliasOrGID, StringComparer.OrdinalIgnoreCase)) { - if (string.Equals(u.Key.UserData.UID, group.OwnerUID, StringComparison.Ordinal)) return 0; - if (group.GroupPairUserInfos.TryGetValue(u.Key.UserData.UID, out var info)) + if (_tagHandler.HasSyncshellTag(group.GID, syncshellTag)) { - if (info.IsModerator()) return 1; - if (info.IsPinned()) return 2; + GetGroups(allPairs, filteredPairs, group, + out ImmutableList allGroupPairs, + out Dictionary> filteredGroupPairs); + + syncshellFolderTags.Add(_drawEntityFactory.CreateDrawGroupFolder($"tag_{group.GID}", group, filteredGroupPairs, allGroupPairs)); } - return u.Key.IsVisible ? 3 : 4; - }) - .ThenBy(AlphabeticalSort, StringComparer.OrdinalIgnoreCase) - .ToDictionary(k => k.Key, k => k.Value); + } + + drawFolders.Add(new DrawGroupedGroupFolder(syncshellFolderTags, _tagHandler, _uiSharedService, _selectSyncshellForTagUi, _renameSyncshellTagUi, syncshellTag)); + } + + //Filter of offline pairs + var allOnlineNotTaggedPairs = ImmutablePairList(allPairs.Where(p => FilterNotTaggedUsers(p.Key))); + var onlineNotTaggedPairs = BasicSortedDictionary(filteredPairs.Where(p => FilterNotTaggedUsers(p.Key) && FilterOnlineOrPausedSelf(p.Key))); + + drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder((_configService.Current.ShowOfflineUsersSeparately ? TagHandler.CustomOnlineTag : TagHandler.CustomAllTag), onlineNotTaggedPairs, allOnlineNotTaggedPairs)); + + if (_configService.Current.ShowOfflineUsersSeparately) + { + var allOfflinePairs = ImmutablePairList(allPairs.Where(p => FilterOfflineUsers(p.Key, p.Value))); + var filteredOfflinePairs = BasicSortedDictionary(filteredPairs.Where(p => FilterOfflineUsers(p.Key, p.Value))); + + drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(TagHandler.CustomOfflineTag, filteredOfflinePairs, allOfflinePairs)); + + if (_configService.Current.ShowSyncshellOfflineUsersSeparately) + { + var allOfflineSyncshellUsers = ImmutablePairList(allPairs.Where(p => FilterOfflineSyncshellUsers(p.Key))); + var filteredOfflineSyncshellUsers = BasicSortedDictionary(filteredPairs.Where(p => FilterOfflineSyncshellUsers(p.Key))); + + drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(TagHandler.CustomOfflineSyncshellTag, filteredOfflineSyncshellUsers, allOfflineSyncshellUsers)); + } + } + + drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(TagHandler.CustomUnpairedTag, + BasicSortedDictionary(filteredPairs.Where(p => p.Key.IsOneSidedPair)), + ImmutablePairList(allPairs.Where(p => p.Key.IsOneSidedPair)))); + + return drawFolders; } } + private static bool PassesFilter(Pair pair, string filter) + { + if (string.IsNullOrEmpty(filter)) return true; + + return pair.UserData.AliasOrUID.Contains(filter, StringComparison.OrdinalIgnoreCase) || (pair.GetNote()?.Contains(filter, StringComparison.OrdinalIgnoreCase) ?? false) || (pair.PlayerName?.Contains(filter, StringComparison.OrdinalIgnoreCase) ?? false); + } + + private string AlphabeticalSortKey(Pair pair) + { + if (_configService.Current.ShowCharacterNameInsteadOfNotesForVisible && !string.IsNullOrEmpty(pair.PlayerName)) + { + return _configService.Current.PreferNotesOverNamesForVisible ? (pair.GetNote() ?? string.Empty) : pair.PlayerName; + } + + return pair.GetNote() ?? pair.UserData.AliasOrUID; + } + + private bool FilterOnlineOrPausedSelf(Pair pair) => pair.IsOnline || (!pair.IsOnline && !_configService.Current.ShowOfflineUsersSeparately) || pair.UserPair.OwnPermissions.IsPaused(); + + private bool FilterVisibleUsers(Pair pair) => pair.IsVisible && (_configService.Current.ShowSyncshellUsersInVisible || pair.IsDirectlyPaired); + + private bool FilterTagUsers(Pair pair, string tag) => pair.IsDirectlyPaired && !pair.IsOneSidedPair && _tagHandler.HasPairTag(pair.UserData.UID, tag); + + private static bool FilterGroupUsers(List groups, GroupFullInfoDto group) => groups.Exists(g => string.Equals(g.GID, group.GID, StringComparison.Ordinal)); + + private bool FilterNotTaggedUsers(Pair pair) => pair.IsDirectlyPaired && !pair.IsOneSidedPair && !_tagHandler.HasAnyPairTag(pair.UserData.UID); + + private bool FilterNotTaggedSyncshells(GroupFullInfoDto group) => !_tagHandler.HasAnySyncshellTag(group.GID) || _configService.Current.ShowGroupedSyncshellsInAll; + + private bool FilterOfflineUsers(Pair pair, List groups) => ((pair.IsDirectlyPaired && _configService.Current.ShowSyncshellOfflineUsersSeparately) || !_configService.Current.ShowSyncshellOfflineUsersSeparately) && (!pair.IsOneSidedPair || groups.Count != 0) && !pair.IsOnline && !pair.UserPair.OwnPermissions.IsPaused(); + + private static bool FilterOfflineSyncshellUsers(Pair pair) => !pair.IsDirectlyPaired && !pair.IsOnline && !pair.UserPair.OwnPermissions.IsPaused(); + + private Dictionary> BasicSortedDictionary(IEnumerable>> pairs) => pairs.OrderByDescending(u => u.Key.IsVisible).ThenByDescending(u => u.Key.IsOnline).ThenBy(u => AlphabeticalSortKey(u.Key), StringComparer.OrdinalIgnoreCase).ToDictionary(u => u.Key, u => u.Value); + + private static ImmutableList ImmutablePairList(IEnumerable>> pairs) => [.. pairs.Select(k => k.Key)]; + + private void GetGroups(Dictionary> allPairs, + Dictionary> filteredPairs, + GroupFullInfoDto group, + out ImmutableList allGroupPairs, + out Dictionary> filteredGroupPairs) + { + allGroupPairs = ImmutablePairList(allPairs + .Where(u => FilterGroupUsers(u.Value, group))); + + filteredGroupPairs = filteredPairs + .Where(u => FilterGroupUsers( u.Value, group) && FilterOnlineOrPausedSelf(u.Key)) + .OrderByDescending(u => u.Key.IsOnline) + .ThenBy(u => + { + if (string.Equals(u.Key.UserData.UID, group.OwnerUID, StringComparison.Ordinal)) return 0; + if (group.GroupPairUserInfos.TryGetValue(u.Key.UserData.UID, out var info)) + { + if (info.IsModerator()) return 1; + if (info.IsPinned()) return 2; + } + return u.Key.IsVisible ? 3 : 4; + }) + .ThenBy(u => AlphabeticalSortKey(u.Key), StringComparer.OrdinalIgnoreCase) + .ToDictionary(k => k.Key, k => k.Value); + } + private string GetServerError() { return _apiController.ServerState switch From 3e15dd643e86106823e460a0b860aaebad824a84 Mon Sep 17 00:00:00 2001 From: CakeAndBanana Date: Fri, 26 Sep 2025 05:17:16 +0200 Subject: [PATCH 12/29] Added more comments --- LightlessSync/UI/CompactUI.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/LightlessSync/UI/CompactUI.cs b/LightlessSync/UI/CompactUI.cs index 77305c1..fea81d9 100644 --- a/LightlessSync/UI/CompactUI.cs +++ b/LightlessSync/UI/CompactUI.cs @@ -678,7 +678,7 @@ public class CompactUi : WindowMediatorSubscriberBase drawFolders.Add(new DrawGroupedGroupFolder(syncshellFolderTags, _tagHandler, _uiSharedService, _selectSyncshellForTagUi, _renameSyncshellTagUi, syncshellTag)); } - //Filter of offline pairs + //Filter of not grouped/foldered and offline pairs var allOnlineNotTaggedPairs = ImmutablePairList(allPairs.Where(p => FilterNotTaggedUsers(p.Key))); var onlineNotTaggedPairs = BasicSortedDictionary(filteredPairs.Where(p => FilterNotTaggedUsers(p.Key) && FilterOnlineOrPausedSelf(p.Key))); @@ -700,6 +700,7 @@ public class CompactUi : WindowMediatorSubscriberBase } } + //Unpaired drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(TagHandler.CustomUnpairedTag, BasicSortedDictionary(filteredPairs.Where(p => p.Key.IsOneSidedPair)), ImmutablePairList(allPairs.Where(p => p.Key.IsOneSidedPair)))); From fd9bd3975b04f7e2fa56ebabd577ea11e567971b Mon Sep 17 00:00:00 2001 From: azyges Date: Sat, 27 Sep 2025 01:38:05 +0900 Subject: [PATCH 13/29] colored uid client support --- LightlessAPI | 2 +- LightlessSync/UI/BroadcastUI.cs | 2 +- LightlessSync/UI/EditProfileUi.cs | 452 ++++++++++++------ LightlessSync/UI/Handlers/IdDisplayHandler.cs | 25 +- LightlessSync/UI/JoinSyncshellUI.cs | 2 +- .../SignalR/ApIController.Functions.Users.cs | 6 + LightlessSync/WebAPI/SignalR/ApiController.cs | 4 + 7 files changed, 329 insertions(+), 164 deletions(-) diff --git a/LightlessAPI b/LightlessAPI index 3c10380..5bfd21a 160000 --- a/LightlessAPI +++ b/LightlessAPI @@ -1 +1 @@ -Subproject commit 3c10380162b162c47c99f63ecfc627a49887fe84 +Subproject commit 5bfd21aaa90817f14c9e2931e77b20f4276f16ed diff --git a/LightlessSync/UI/BroadcastUI.cs b/LightlessSync/UI/BroadcastUI.cs index 90b19ad..4977fb6 100644 --- a/LightlessSync/UI/BroadcastUI.cs +++ b/LightlessSync/UI/BroadcastUI.cs @@ -131,7 +131,7 @@ namespace LightlessSync.UI ImGuiHelpers.ScaledDummy(0.25f); } - if (ImGui.BeginTabBar("##MyTabBar")) + if (ImGui.BeginTabBar("##BroadcastTabs")) { if (ImGui.BeginTabItem("Lightfinder")) { diff --git a/LightlessSync/UI/EditProfileUi.cs b/LightlessSync/UI/EditProfileUi.cs index 753c790..6c787b3 100644 --- a/LightlessSync/UI/EditProfileUi.cs +++ b/LightlessSync/UI/EditProfileUi.cs @@ -4,14 +4,17 @@ using Dalamud.Interface.Colors; using Dalamud.Interface.ImGuiFileDialog; using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; using LightlessSync.API.Data; using LightlessSync.API.Dto.User; using LightlessSync.Services; using LightlessSync.Services.Mediator; +using LightlessSync.Utils; using LightlessSync.WebAPI; using Microsoft.Extensions.Logging; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; +using System.Numerics; namespace LightlessSync.UI; @@ -30,6 +33,15 @@ public class EditProfileUi : WindowMediatorSubscriberBase private bool _showFileDialogError = false; private bool _wasOpen; + private bool vanityInitialized; // useless for now + private bool textEnabled; + private bool glowEnabled; + private Vector4 textColor; + private Vector4 glowColor; + + private record VanityState(bool TextEnabled, bool GlowEnabled, Vector4 TextColor, Vector4 GlowColor); + private VanityState _savedVanity; + public EditProfileUi(ILogger logger, LightlessMediator mediator, ApiController apiController, UiSharedService uiSharedService, FileDialogManager fileDialogManager, LightlessProfileManager lightlessProfileManager, PerformanceCollectorService performanceCollectorService) @@ -38,8 +50,8 @@ public class EditProfileUi : WindowMediatorSubscriberBase IsOpen = false; this.SizeConstraints = new() { - MinimumSize = new(768, 512), - MaximumSize = new(768, 2000) + MinimumSize = new(850, 640), + MaximumSize = new(850, 700) }; _apiController = apiController; _uiSharedService = uiSharedService; @@ -57,172 +69,314 @@ public class EditProfileUi : WindowMediatorSubscriberBase _pfpTextureWrap = null; } }); + Mediator.Subscribe(this, msg => + { + LoadVanity(); + }); + } + + void DrawNoteLine(string icon, Vector4 color, string text) + { + _uiSharedService.MediumText(icon, color); + ImGui.SameLine(); + + ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 3); + ImGui.TextWrapped(text); + } + + private void LoadVanity() + { + textEnabled = !string.IsNullOrEmpty(_apiController.TextColorHex); + glowEnabled = !string.IsNullOrEmpty(_apiController.TextGlowColorHex); + + textColor = textEnabled ? UIColors.HexToRgba(_apiController.TextColorHex!) : Vector4.One; + glowColor = glowEnabled ? UIColors.HexToRgba(_apiController.TextGlowColorHex!) : Vector4.Zero; + + _savedVanity = new VanityState(textEnabled, glowEnabled, textColor, glowColor); + vanityInitialized = true; } protected override void DrawInternal() { - _uiSharedService.BigText("Current Profile (as saved on server)"); + _uiSharedService.UnderlinedBigText("Notes and Rules for Profiles", UIColors.Get("LightlessYellow")); + ImGui.Dummy(new Vector2(5)); + + ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(2, 2)); + + DrawNoteLine("# ", UIColors.Get("LightlessBlue"), "All users that are paired and unpaused with you will be able to see your profile picture and description."); + DrawNoteLine("! ", UIColors.Get("LightlessYellow"), "Other users have the possibility to report your profile for breaking the rules."); + DrawNoteLine("!!! ", UIColors.Get("DimRed"), "AVOID: Anything as profile image that can be considered highly illegal or obscene (bestiality, anything that could be considered a sexual act with a minor (that includes Lalafells), etc.)"); + DrawNoteLine("!!! ", UIColors.Get("DimRed"), "AVOID: Slurs of any kind in the description that can be considered highly offensive"); + DrawNoteLine("! ", UIColors.Get("LightlessYellow"), "In case of valid reports from other users this can lead to disabling your profile forever or terminating your Lightless account indefinitely."); + DrawNoteLine("! ", UIColors.Get("LightlessYellow"), "Judgement of your profile validity from reports through staff is not up to debate and the decisions to disable your profile/account permanent."); + DrawNoteLine("! ", UIColors.Get("LightlessBlue"), "If your profile picture or profile description could be considered NSFW, enable the toggle in profile settings."); + + ImGui.PopStyleVar(); + + ImGui.Dummy(new Vector2(3)); var profile = _lightlessProfileManager.GetLightlessProfile(new UserData(_apiController.UID)); - if (profile.IsFlagged) - { - UiSharedService.ColorTextWrapped(profile.Description, ImGuiColors.DalamudRed); - return; - } - - if (!_profileImage.SequenceEqual(profile.ImageData.Value)) - { - _profileImage = profile.ImageData.Value; - _pfpTextureWrap?.Dispose(); - _pfpTextureWrap = _uiSharedService.LoadImage(_profileImage); - } - - if (!string.Equals(_profileDescription, profile.Description, StringComparison.OrdinalIgnoreCase)) - { - _profileDescription = profile.Description; - _descriptionText = _profileDescription; - } - - if (_pfpTextureWrap != null) - { - ImGui.Image(_pfpTextureWrap.Handle, ImGuiHelpers.ScaledVector2(_pfpTextureWrap.Width, _pfpTextureWrap.Height)); - } - - var spacing = ImGui.GetStyle().ItemSpacing.X; - ImGuiHelpers.ScaledRelativeSameLine(256, spacing); - using (_uiSharedService.GameFont.Push()) - { - var descriptionTextSize = ImGui.CalcTextSize(profile.Description, wrapWidth: 256f); - var childFrame = ImGuiHelpers.ScaledVector2(256 + ImGui.GetStyle().WindowPadding.X + ImGui.GetStyle().WindowBorderSize, 256); - if (descriptionTextSize.Y > childFrame.Y) + if (ImGui.BeginTabBar("##EditProfileTabs")) + { + if (ImGui.BeginTabItem("Current Profile")) { - _adjustedForScollBarsOnlineProfile = true; - } - else - { - _adjustedForScollBarsOnlineProfile = false; - } - childFrame = childFrame with - { - X = childFrame.X + (_adjustedForScollBarsOnlineProfile ? ImGui.GetStyle().ScrollbarSize : 0), - }; - if (ImGui.BeginChildFrame(101, childFrame)) - { - UiSharedService.TextWrapped(profile.Description); - } - ImGui.EndChildFrame(); - } + _uiSharedService.MediumText("Current Profile (as saved on server)", UIColors.Get("LightlessPurple")); + ImGui.Dummy(new Vector2(5)); - var nsfw = profile.IsNSFW; - ImGui.BeginDisabled(); - ImGui.Checkbox("Is NSFW", ref nsfw); - ImGui.EndDisabled(); - - ImGui.Separator(); - _uiSharedService.BigText("Notes and Rules for Profiles"); - - ImGui.TextWrapped($"- All users that are paired and unpaused with you will be able to see your profile picture and description.{Environment.NewLine}" + - $"- Other users have the possibility to report your profile for breaking the rules.{Environment.NewLine}" + - $"- !!! AVOID: anything as profile image that can be considered highly illegal or obscene (bestiality, anything that could be considered a sexual act with a minor (that includes Lalafells), etc.){Environment.NewLine}" + - $"- !!! AVOID: slurs of any kind in the description that can be considered highly offensive{Environment.NewLine}" + - $"- In case of valid reports from other users this can lead to disabling your profile forever or terminating your Lightless account indefinitely.{Environment.NewLine}" + - $"- Judgement of your profile validity from reports through staff is not up to debate and the decisions to disable your profile/account permanent.{Environment.NewLine}" + - $"- If your profile picture or profile description could be considered NSFW, enable the toggle below."); - ImGui.Separator(); - _uiSharedService.BigText("Profile Settings"); - - if (_uiSharedService.IconTextButton(FontAwesomeIcon.FileUpload, "Upload new profile picture")) - { - _fileDialogManager.OpenFileDialog("Select new Profile picture", ".png", (success, file) => - { - if (!success) return; - _ = Task.Run(async () => + if (profile.IsFlagged) { - var fileContent = File.ReadAllBytes(file); - using MemoryStream ms = new(fileContent); - var format = await Image.DetectFormatAsync(ms).ConfigureAwait(false); - if (!format.FileExtensions.Contains("png", StringComparer.OrdinalIgnoreCase)) + UiSharedService.ColorTextWrapped(profile.Description, ImGuiColors.DalamudRed); + return; + } + + if (!_profileImage.SequenceEqual(profile.ImageData.Value)) + { + _profileImage = profile.ImageData.Value; + _pfpTextureWrap?.Dispose(); + _pfpTextureWrap = _uiSharedService.LoadImage(_profileImage); + } + + if (!string.Equals(_profileDescription, profile.Description, StringComparison.OrdinalIgnoreCase)) + { + _profileDescription = profile.Description; + _descriptionText = _profileDescription; + } + + if (_pfpTextureWrap != null) + { + ImGui.Image(_pfpTextureWrap.Handle, ImGuiHelpers.ScaledVector2(_pfpTextureWrap.Width, _pfpTextureWrap.Height)); + } + + var spacing = ImGui.GetStyle().ItemSpacing.X; + ImGuiHelpers.ScaledRelativeSameLine(256, spacing); + using (_uiSharedService.GameFont.Push()) + { + var descriptionTextSize = ImGui.CalcTextSize(profile.Description, wrapWidth: 256f); + var childFrame = ImGuiHelpers.ScaledVector2(256 + ImGui.GetStyle().WindowPadding.X + ImGui.GetStyle().WindowBorderSize, 256); + if (descriptionTextSize.Y > childFrame.Y) { - _showFileDialogError = true; - return; + _adjustedForScollBarsOnlineProfile = true; } - using var image = Image.Load(fileContent); - - if (image.Width > 256 || image.Height > 256 || (fileContent.Length > 250 * 1024)) + else { - _showFileDialogError = true; - return; + _adjustedForScollBarsOnlineProfile = false; } + childFrame = childFrame with + { + X = childFrame.X + (_adjustedForScollBarsOnlineProfile ? ImGui.GetStyle().ScrollbarSize : 0), + }; + if (ImGui.BeginChildFrame(101, childFrame)) + { + UiSharedService.TextWrapped(profile.Description); + } + ImGui.EndChildFrame(); + } - _showFileDialogError = false; - await _apiController.UserSetProfile(new UserProfileDto(new UserData(_apiController.UID), Disabled: false, IsNSFW: null, Convert.ToBase64String(fileContent), Description: null)) - .ConfigureAwait(false); - }); - }); - } - UiSharedService.AttachToolTip("Select and upload a new profile picture"); - ImGui.SameLine(); - if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, "Clear uploaded profile picture")) - { - _ = _apiController.UserSetProfile(new UserProfileDto(new UserData(_apiController.UID), Disabled: false, IsNSFW: null, "", Description: null)); - } - UiSharedService.AttachToolTip("Clear your currently uploaded profile picture"); - if (_showFileDialogError) - { - UiSharedService.ColorTextWrapped("The profile picture must be a PNG file with a maximum height and width of 256px and 250KiB size", ImGuiColors.DalamudRed); - } - var isNsfw = profile.IsNSFW; - if (ImGui.Checkbox("Profile is NSFW", ref isNsfw)) - { - _ = _apiController.UserSetProfile(new UserProfileDto(new UserData(_apiController.UID), Disabled: false, isNsfw, ProfilePictureBase64: null, Description: null)); - } - _uiSharedService.DrawHelpText("If your profile description or image can be considered NSFW, toggle this to ON"); - var widthTextBox = 400; - var posX = ImGui.GetCursorPosX(); - ImGui.TextUnformatted($"Description {_descriptionText.Length}/1500"); - ImGui.SetCursorPosX(posX); - ImGuiHelpers.ScaledRelativeSameLine(widthTextBox, ImGui.GetStyle().ItemSpacing.X); - ImGui.TextUnformatted("Preview (approximate)"); - using (_uiSharedService.GameFont.Push()) - ImGui.InputTextMultiline("##description", ref _descriptionText, 1500, ImGuiHelpers.ScaledVector2(widthTextBox, 200)); + var nsfw = profile.IsNSFW; + ImGui.BeginDisabled(); + ImGui.Checkbox("Is NSFW", ref nsfw); + ImGui.EndDisabled(); - ImGui.SameLine(); - - using (_uiSharedService.GameFont.Push()) - { - var descriptionTextSizeLocal = ImGui.CalcTextSize(_descriptionText, wrapWidth: 256f); - var childFrameLocal = ImGuiHelpers.ScaledVector2(256 + ImGui.GetStyle().WindowPadding.X + ImGui.GetStyle().WindowBorderSize, 200); - if (descriptionTextSizeLocal.Y > childFrameLocal.Y) - { - _adjustedForScollBarsLocalProfile = true; + _uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f); + ImGui.EndTabItem(); } - else - { - _adjustedForScollBarsLocalProfile = false; - } - childFrameLocal = childFrameLocal with - { - X = childFrameLocal.X + (_adjustedForScollBarsLocalProfile ? ImGui.GetStyle().ScrollbarSize : 0), - }; - if (ImGui.BeginChildFrame(102, childFrameLocal)) - { - UiSharedService.TextWrapped(_descriptionText); - } - ImGui.EndChildFrame(); - } - if (_uiSharedService.IconTextButton(FontAwesomeIcon.Save, "Save Description")) - { - _ = _apiController.UserSetProfile(new UserProfileDto(new UserData(_apiController.UID), Disabled: false, IsNSFW: null, ProfilePictureBase64: null, _descriptionText)); + if (ImGui.BeginTabItem("Profile Settings")) + { + _uiSharedService.MediumText("Profile Settings", UIColors.Get("LightlessPurple")); + ImGui.Dummy(new Vector2(5)); + + if (_uiSharedService.IconTextButton(FontAwesomeIcon.FileUpload, "Upload new profile picture")) + { + _fileDialogManager.OpenFileDialog("Select new Profile picture", ".png", (success, file) => + { + if (!success) return; + _ = Task.Run(async () => + { + var fileContent = File.ReadAllBytes(file); + using MemoryStream ms = new(fileContent); + var format = await Image.DetectFormatAsync(ms).ConfigureAwait(false); + if (!format.FileExtensions.Contains("png", StringComparer.OrdinalIgnoreCase)) + { + _showFileDialogError = true; + return; + } + using var image = Image.Load(fileContent); + + if (image.Width > 256 || image.Height > 256 || (fileContent.Length > 250 * 1024)) + { + _showFileDialogError = true; + return; + } + + _showFileDialogError = false; + await _apiController.UserSetProfile(new UserProfileDto(new UserData(_apiController.UID), Disabled: false, IsNSFW: null, Convert.ToBase64String(fileContent), Description: null)) + .ConfigureAwait(false); + }); + }); + } + UiSharedService.AttachToolTip("Select and upload a new profile picture"); + ImGui.SameLine(); + if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, "Clear uploaded profile picture")) + { + _ = _apiController.UserSetProfile(new UserProfileDto(new UserData(_apiController.UID), Disabled: false, IsNSFW: null, "", Description: null)); + } + UiSharedService.AttachToolTip("Clear your currently uploaded profile picture"); + if (_showFileDialogError) + { + UiSharedService.ColorTextWrapped("The profile picture must be a PNG file with a maximum height and width of 256px and 250KiB size", ImGuiColors.DalamudRed); + } + var isNsfw = profile.IsNSFW; + if (ImGui.Checkbox("Profile is NSFW", ref isNsfw)) + { + _ = _apiController.UserSetProfile(new UserProfileDto(new UserData(_apiController.UID), Disabled: false, isNsfw, ProfilePictureBase64: null, Description: null)); + } + _uiSharedService.DrawHelpText("If your profile description or image can be considered NSFW, toggle this to ON"); + var widthTextBox = 400; + var posX = ImGui.GetCursorPosX(); + ImGui.TextUnformatted($"Description {_descriptionText.Length}/1500"); + ImGui.SetCursorPosX(posX); + ImGuiHelpers.ScaledRelativeSameLine(widthTextBox, ImGui.GetStyle().ItemSpacing.X); + ImGui.TextUnformatted("Preview (approximate)"); + using (_uiSharedService.GameFont.Push()) + ImGui.InputTextMultiline("##description", ref _descriptionText, 1500, ImGuiHelpers.ScaledVector2(widthTextBox, 200)); + + ImGui.SameLine(); + + using (_uiSharedService.GameFont.Push()) + { + var descriptionTextSizeLocal = ImGui.CalcTextSize(_descriptionText, wrapWidth: 256f); + var childFrameLocal = ImGuiHelpers.ScaledVector2(256 + ImGui.GetStyle().WindowPadding.X + ImGui.GetStyle().WindowBorderSize, 200); + if (descriptionTextSizeLocal.Y > childFrameLocal.Y) + { + _adjustedForScollBarsLocalProfile = true; + } + else + { + _adjustedForScollBarsLocalProfile = false; + } + childFrameLocal = childFrameLocal with + { + X = childFrameLocal.X + (_adjustedForScollBarsLocalProfile ? ImGui.GetStyle().ScrollbarSize : 0), + }; + if (ImGui.BeginChildFrame(102, childFrameLocal)) + { + UiSharedService.TextWrapped(_descriptionText); + } + ImGui.EndChildFrame(); + } + + if (_uiSharedService.IconTextButton(FontAwesomeIcon.Save, "Save Description")) + { + _ = _apiController.UserSetProfile(new UserProfileDto(new UserData(_apiController.UID), Disabled: false, IsNSFW: null, ProfilePictureBase64: null, _descriptionText)); + } + UiSharedService.AttachToolTip("Sets your profile description text"); + ImGui.SameLine(); + if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, "Clear Description")) + { + _ = _apiController.UserSetProfile(new UserProfileDto(new UserData(_apiController.UID), Disabled: false, IsNSFW: null, ProfilePictureBase64: null, "")); + } + UiSharedService.AttachToolTip("Clears your profile description text"); + + ImGui.EndTabItem(); + } + + if (ImGui.BeginTabItem("Vanity Settings")) + { + _uiSharedService.MediumText("Supporter Vanity Settings", UIColors.Get("LightlessPurple")); + ImGui.Dummy(new Vector2(4)); + DrawNoteLine("# ", UIColors.Get("LightlessPurple"), "Must be a supporter through Patreon/Ko-fi to access these settings."); + + var hasVanity = _apiController.HasVanity; + + if (!hasVanity) + { + UiSharedService.ColorTextWrapped("You do not currently have vanity access. Become a supporter to unlock these features.", UIColors.Get("DimRed")); + ImGui.Dummy(new Vector2(8)); + ImGui.BeginDisabled(); + } + + _uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"), 1.5f); + _uiSharedService.MediumText("Colored UID", UIColors.Get("LightlessPurple")); + ImGui.Dummy(new Vector2(5)); + + var font = UiBuilder.MonoFont; + var playerUID = _apiController.UID; + var playerDisplay = _apiController.DisplayName; + + var previewTextColor = textEnabled ? textColor : Vector4.One; + var previewGlowColor = glowEnabled ? glowColor : Vector4.Zero; + + var seString = SeStringUtils.BuildFormattedPlayerName(playerDisplay, previewTextColor, previewGlowColor); + + using (ImRaii.PushFont(font)) + { + var offsetX = 10f; + var pos = ImGui.GetCursorScreenPos() + new Vector2(offsetX, 0); + var size = ImGui.CalcTextSize(seString.TextValue); + + var padding = new Vector2(6, 3); + var rectMin = pos - padding; + var rectMax = pos + size + padding; + + var bgColor = new Vector4(0.15f, 0.15f, 0.15f, 1f); + var borderColor = UIColors.Get("LightlessPurple"); + + var drawList = ImGui.GetWindowDrawList(); + drawList.AddRectFilled(rectMin, rectMax, ImGui.GetColorU32(bgColor), 6.0f); + drawList.AddRect(rectMin, rectMax, ImGui.GetColorU32(borderColor), 6.0f, ImDrawFlags.None, 1.5f); + + SeStringUtils.RenderSeStringWithHitbox(seString, pos, font); + } + + const float colorPickAlign = 90f; + + DrawNoteLine("- ", UIColors.Get("LightlessPurple"), "Text Color"); + ImGui.SameLine(colorPickAlign); + ImGui.Checkbox("##toggleTextColor", ref textEnabled); + ImGui.SameLine(); + ImGui.BeginDisabled(!textEnabled); + ImGui.ColorEdit4($"##color_text", ref textColor, ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.AlphaPreviewHalf); + ImGui.EndDisabled(); + + DrawNoteLine("- ", UIColors.Get("LightlessPurple"), "Glow Color"); + ImGui.SameLine(colorPickAlign); + ImGui.Checkbox("##toggleGlowColor", ref glowEnabled); + ImGui.SameLine(); + ImGui.BeginDisabled(!glowEnabled); + ImGui.ColorEdit4($"##color_glow", ref glowColor, ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.AlphaPreviewHalf); + ImGui.EndDisabled(); + + bool changed = !Equals(_savedVanity, new VanityState(textEnabled, glowEnabled, textColor, glowColor)); + + if (!changed) + ImGui.BeginDisabled(); + + if (_uiSharedService.IconTextButton(FontAwesomeIcon.Save, "Save Changes")) + { + string? newText = textEnabled ? UIColors.RgbaToHex(textColor) : string.Empty; + string? newGlow = glowEnabled ? UIColors.RgbaToHex(glowColor) : string.Empty; + + _ = _apiController.UserUpdateVanityColors(new UserVanityColorsDto(newText, newGlow)); + + _savedVanity = new VanityState(textEnabled, glowEnabled, textColor, glowColor); + } + + if (!changed) + ImGui.EndDisabled(); + + ImGui.Dummy(new Vector2(5)); + _uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f); + + if (!hasVanity) + ImGui.EndDisabled(); + + ImGui.EndTabItem(); + } + + ImGui.EndTabBar(); } - UiSharedService.AttachToolTip("Sets your profile description text"); - ImGui.SameLine(); - if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, "Clear Description")) - { - _ = _apiController.UserSetProfile(new UserProfileDto(new UserData(_apiController.UID), Disabled: false, IsNSFW: null, ProfilePictureBase64: null, "")); - } - UiSharedService.AttachToolTip("Clears your profile description text"); } protected override void Dispose(bool disposing) diff --git a/LightlessSync/UI/Handlers/IdDisplayHandler.cs b/LightlessSync/UI/Handlers/IdDisplayHandler.cs index 81ed337..048efe9 100644 --- a/LightlessSync/UI/Handlers/IdDisplayHandler.cs +++ b/LightlessSync/UI/Handlers/IdDisplayHandler.cs @@ -98,20 +98,21 @@ public class IdDisplayHandler var font = UiBuilder.MonoFont; - var isAdmin = pair.UserData.IsAdmin; - var isModerator = pair.UserData.IsModerator; + Vector4? textColor = null; + Vector4? glowColor = null; - Vector4? textColor = isAdmin - ? UIColors.Get("LightlessAdminText") - : isModerator - ? UIColors.Get("LightlessModeratorText") - : null; + if (pair.UserData.HasVanity) + { + if (!string.IsNullOrWhiteSpace(pair.UserData.TextColorHex)) + { + textColor = UIColors.HexToRgba(pair.UserData.TextColorHex); + } - Vector4? glowColor = isAdmin - ? UIColors.Get("LightlessAdminGlow") - : isModerator - ? UIColors.Get("LightlessModeratorGlow") - : null; + if (!string.IsNullOrWhiteSpace(pair.UserData.TextGlowColorHex)) + { + glowColor = UIColors.HexToRgba(pair.UserData.TextGlowColorHex); + } + } var seString = (textColor != null || glowColor != null) ? SeStringUtils.BuildFormattedPlayerName(playerText, textColor, glowColor) diff --git a/LightlessSync/UI/JoinSyncshellUI.cs b/LightlessSync/UI/JoinSyncshellUI.cs index 3de9026..b02a84e 100644 --- a/LightlessSync/UI/JoinSyncshellUI.cs +++ b/LightlessSync/UI/JoinSyncshellUI.cs @@ -63,7 +63,7 @@ internal class JoinSyncshellUI : WindowMediatorSubscriberBase "Joining a Syncshell will pair you implicitly with all existing users in the Syncshell." + Environment.NewLine + "All permissions to all users in the Syncshell will be set to the preferred Syncshell permissions on joining, excluding prior set preferred permissions."); ImGui.Separator(); - ImGui.TextUnformatted("Note: Syncshell ID and Password are case sensitive. MSS- is part of Syncshell IDs, unless using Vanity IDs."); + ImGui.TextUnformatted("Note: Syncshell ID and Password are case sensitive. LLS- is part of Syncshell IDs, unless using Vanity IDs."); ImGui.AlignTextToFramePadding(); ImGui.TextUnformatted("Syncshell ID"); diff --git a/LightlessSync/WebAPI/SignalR/ApIController.Functions.Users.cs b/LightlessSync/WebAPI/SignalR/ApIController.Functions.Users.cs index 95f1de8..d268dd8 100644 --- a/LightlessSync/WebAPI/SignalR/ApIController.Functions.Users.cs +++ b/LightlessSync/WebAPI/SignalR/ApIController.Functions.Users.cs @@ -134,6 +134,12 @@ public partial class ApiController await _lightlessHub!.InvokeAsync(nameof(UserSetProfile), userDescription).ConfigureAwait(false); } + public async Task UserUpdateVanityColors(UserVanityColorsDto dto) + { + if (!IsConnected) return; + await _lightlessHub!.InvokeAsync(nameof(UserUpdateVanityColors), dto).ConfigureAwait(false); + } + public async Task UserUpdateDefaultPermissions(DefaultPermissionsDto defaultPermissionsDto) { CheckConnection(); diff --git a/LightlessSync/WebAPI/SignalR/ApiController.cs b/LightlessSync/WebAPI/SignalR/ApiController.cs index 4a4071b..efa6e6f 100644 --- a/LightlessSync/WebAPI/SignalR/ApiController.cs +++ b/LightlessSync/WebAPI/SignalR/ApiController.cs @@ -77,6 +77,10 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL public DefaultPermissionsDto? DefaultPermissions => _connectionDto?.DefaultPreferredPermissions ?? null; public string DisplayName => _connectionDto?.User.AliasOrUID ?? string.Empty; + public bool HasVanity => _connectionDto?.HasVanity ?? false; + public string TextColorHex => _connectionDto?.TextColorHex ?? string.Empty; + public string TextGlowColorHex => _connectionDto?.TextGlowColorHex ?? string.Empty; + public bool IsConnected => ServerState == ServerState.Connected; public bool IsCurrentVersion => (Assembly.GetExecutingAssembly().GetName().Version ?? new Version(0, 0, 0, 0)) >= (_connectionDto?.CurrentClientVersion ?? new Version(0, 0, 0, 0)); From c6f8d6843e77fd6e39c14cc1302e1c948f960a9f Mon Sep 17 00:00:00 2001 From: CakeAndBanana Date: Fri, 26 Sep 2025 18:39:38 +0200 Subject: [PATCH 14/29] Disabled button and added tooltip for already joined/owned syncshells in finder --- LightlessSync/UI/SyncshellFinderUI.cs | 86 ++++++++++++++------------- 1 file changed, 44 insertions(+), 42 deletions(-) diff --git a/LightlessSync/UI/SyncshellFinderUI.cs b/LightlessSync/UI/SyncshellFinderUI.cs index e009bb5..81f0b0e 100644 --- a/LightlessSync/UI/SyncshellFinderUI.cs +++ b/LightlessSync/UI/SyncshellFinderUI.cs @@ -26,6 +26,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase private readonly PairManager _pairManager; private readonly List _nearbySyncshells = []; + private List _currentSyncshells = []; private int _selectedNearbyIndex = -1; private GroupJoinDto? _joinDto; @@ -106,10 +107,8 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase ImGui.TableSetupColumn("Join", ImGuiTableColumnFlags.WidthFixed, 80f * ImGuiHelpers.GlobalScale); ImGui.TableHeadersRow(); - for (int i = 0; i < _nearbySyncshells.Count; i++) + foreach (var shell in _nearbySyncshells) { - var shell = _nearbySyncshells[i]; - ImGui.TableNextRow(); ImGui.TableNextColumn(); ImGui.TextUnformatted(shell.Group.Alias ?? "(No Alias)"); @@ -122,41 +121,53 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase ImGui.PushStyleColor(ImGuiCol.ButtonHovered, UIColors.Get("LightlessGreen").WithAlpha(0.85f)); ImGui.PushStyleColor(ImGuiCol.ButtonActive, UIColors.Get("LightlessGreen").WithAlpha(0.75f)); - if (ImGui.Button(label)) + if (!_currentSyncshells.Exists(g => string.Equals(g.GID, shell.GID, StringComparison.Ordinal))) { - _logger.LogInformation($"Join requested for Syncshell {shell.Group.GID} ({shell.Group.Alias})"); - _ = Task.Run(async () => + + if (ImGui.Button(label)) { - try - { - var info = await _apiController.GroupJoinHashed(new GroupJoinHashedDto( - shell.Group, - shell.Password, - shell.GroupUserPreferredPermissions - )).ConfigureAwait(false); + _logger.LogInformation($"Join requested for Syncshell {shell.Group.GID} ({shell.Group.Alias})"); - if (info != null && info.Success) - { - _joinDto = new GroupJoinDto(shell.Group, shell.Password, shell.GroupUserPreferredPermissions); - _joinInfo = info; - _ownPermissions = _apiController.DefaultPermissions.DeepClone()!; - - _logger.LogInformation($"Fetched join info for {shell.Group.GID}"); - } - else - { - _logger.LogWarning($"Failed to join {shell.Group.GID}: info was null or unsuccessful"); - } - } - catch (Exception ex) + _ = Task.Run(async () => { - _logger.LogError(ex, $"Join failed for {shell.Group.GID}"); - } - }); + try + { + var info = await _apiController.GroupJoinHashed(new GroupJoinHashedDto( + shell.Group, + shell.Password, + shell.GroupUserPreferredPermissions + )).ConfigureAwait(false); + + if (info != null && info.Success) + { + _joinDto = new GroupJoinDto(shell.Group, shell.Password, shell.GroupUserPreferredPermissions); + _joinInfo = info; + _ownPermissions = _apiController.DefaultPermissions.DeepClone()!; + + _logger.LogInformation($"Fetched join info for {shell.Group.GID}"); + } + else + { + _logger.LogWarning($"Failed to join {shell.Group.GID}: info was null or unsuccessful"); + } + } + catch (Exception ex) + { + _logger.LogError(ex, $"Join failed for {shell.Group.GID}"); + } + }); + } } + else + { - + using (ImRaii.Disabled()) + { + ImGui.Button(label); + } + UiSharedService.AttachToolTip("Already a member or owner of this Syncshell."); + } ImGui.PopStyleColor(3); } @@ -226,7 +237,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase private async Task RefreshSyncshellsAsync() { var syncshellBroadcasts = _broadcastScannerService.GetActiveSyncshellBroadcasts(); - var currentSyncshells = _pairManager.GroupPairs.Select(g => g.Key).ToList(); + _currentSyncshells = _pairManager.GroupPairs.Select(g => g.Key).ToList(); if (syncshellBroadcasts.Count == 0) { @@ -238,16 +249,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase try { var groups = await _apiController.GetBroadcastedGroups(syncshellBroadcasts).ConfigureAwait(false); - if (groups != null && currentSyncshells != null) - { - foreach (var group in groups) - { - if (!currentSyncshells.Exists(g => string.Equals(g.GID, group.GID, StringComparison.Ordinal))) - { - updatedList = groups?.ToList(); - } - } - } + updatedList = groups?.ToList(); } catch (Exception ex) { From 3831dd24f1be5bda865c65ea669ea14e7db0f55c Mon Sep 17 00:00:00 2001 From: choco Date: Sun, 28 Sep 2025 15:16:45 +0200 Subject: [PATCH 15/29] settings cleanup readded title settings --- LightlessSync/UI/CompactUI.cs | 15 +++++++++++++++ LightlessSync/UI/TopTabMenu.cs | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/LightlessSync/UI/CompactUI.cs b/LightlessSync/UI/CompactUI.cs index a14be42..247e440 100644 --- a/LightlessSync/UI/CompactUI.cs +++ b/LightlessSync/UI/CompactUI.cs @@ -109,6 +109,21 @@ public class CompactUi : WindowMediatorSubscriberBase AllowClickthrough = false; TitleBarButtons = new() { + new TitleBarButton() + { + Icon = FontAwesomeIcon.Cog, + Click = (msg) => + { + Mediator.Publish(new UiToggleMessage(typeof(SettingsUi))); + }, + IconOffset = new(2,1), + ShowTooltip = () => + { + ImGui.BeginTooltip(); + ImGui.Text("Open Lightless Settings"); + ImGui.EndTooltip(); + } + }, new TitleBarButton() { Icon = FontAwesomeIcon.Book, diff --git a/LightlessSync/UI/TopTabMenu.cs b/LightlessSync/UI/TopTabMenu.cs index 9aa112c..339b22c 100644 --- a/LightlessSync/UI/TopTabMenu.cs +++ b/LightlessSync/UI/TopTabMenu.cs @@ -154,7 +154,7 @@ public class TopTabMenu } } - UiSharedService.AttachToolTip("Lightless Sync Settings"); + UiSharedService.AttachToolTip("Open Lightless Settings"); ImGui.NewLine(); btncolor.Dispose(); From 08e3c8678f6b252480f22b4f337b72818f4dd0a7 Mon Sep 17 00:00:00 2001 From: choco Date: Sun, 28 Sep 2025 15:49:15 +0200 Subject: [PATCH 16/29] added broadcaster name display in Syncshell finder table --- LightlessSync/Plugin.cs | 4 ++-- LightlessSync/UI/SyncshellFinderUI.cs | 29 +++++++++++++++++++-------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/LightlessSync/Plugin.cs b/LightlessSync/Plugin.cs index 09f218e..3529735 100644 --- a/LightlessSync/Plugin.cs +++ b/LightlessSync/Plugin.cs @@ -1,4 +1,4 @@ -using Dalamud.Game; +using Dalamud.Game; using Dalamud.Game.ClientState.Objects; using Dalamud.Interface.ImGuiFileDialog; using Dalamud.Interface.Windowing; @@ -230,7 +230,7 @@ public sealed class Plugin : IDalamudPlugin s.GetRequiredService(), s.GetRequiredService())); collection.AddScoped(); collection.AddScoped((s) => new BroadcastUI(s.GetRequiredService>(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService())); - collection.AddScoped((s) => new SyncshellFinderUI(s.GetRequiredService>(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService())); + collection.AddScoped((s) => new SyncshellFinderUI(s.GetRequiredService>(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService())); collection.AddScoped(); collection.AddScoped(); collection.AddScoped(); diff --git a/LightlessSync/UI/SyncshellFinderUI.cs b/LightlessSync/UI/SyncshellFinderUI.cs index 81f0b0e..32be263 100644 --- a/LightlessSync/UI/SyncshellFinderUI.cs +++ b/LightlessSync/UI/SyncshellFinderUI.cs @@ -1,4 +1,4 @@ -using Dalamud.Bindings.ImGui; +using Dalamud.Bindings.ImGui; using Dalamud.Interface; using Dalamud.Interface.Colors; using Dalamud.Interface.Utility; @@ -24,6 +24,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase private readonly UiSharedService _uiSharedService; private readonly BroadcastScannerService _broadcastScannerService; private readonly PairManager _pairManager; + private readonly DalamudUtilService _dalamudUtilService; private readonly List _nearbySyncshells = []; private List _currentSyncshells = []; @@ -41,13 +42,15 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase UiSharedService uiShared, ApiController apiController, BroadcastScannerService broadcastScannerService, - PairManager pairManager) : base(logger, mediator, "Shellfinder###LightlessSyncshellFinderUI", performanceCollectorService) + PairManager pairManager, + DalamudUtilService dalamudUtilService) : base(logger, mediator, "Shellfinder###LightlessSyncshellFinderUI", performanceCollectorService) { _broadcastService = broadcastService; _uiSharedService = uiShared; _apiController = apiController; _broadcastScannerService = broadcastScannerService; _pairManager = pairManager; + _dalamudUtilService = dalamudUtilService; IsOpen = false; SizeConstraints = new() @@ -102,8 +105,8 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase if (ImGui.BeginTable("##NearbySyncshellsTable", 3, ImGuiTableFlags.Borders | ImGuiTableFlags.RowBg)) { - ImGui.TableSetupColumn("Alias", ImGuiTableColumnFlags.WidthStretch); - ImGui.TableSetupColumn("GID", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableSetupColumn("Syncshell", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableSetupColumn("Broadcaster", ImGuiTableColumnFlags.WidthStretch); ImGui.TableSetupColumn("Join", ImGuiTableColumnFlags.WidthFixed, 80f * ImGuiHelpers.GlobalScale); ImGui.TableHeadersRow(); @@ -111,9 +114,21 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase { ImGui.TableNextRow(); ImGui.TableNextColumn(); - ImGui.TextUnformatted(shell.Group.Alias ?? "(No Alias)"); + + var displayName = !string.IsNullOrEmpty(shell.Group.Alias) ? shell.Group.Alias : shell.Group.GID; + ImGui.TextUnformatted(displayName); + ImGui.TableNextColumn(); - ImGui.TextUnformatted(shell.Group.GID); + var broadcasts = _broadcastScannerService.GetActiveSyncshellBroadcasts(); + var broadcast = broadcasts.FirstOrDefault(b => string.Equals(b.GID, shell.Group.GID, StringComparison.Ordinal)); + var broadcasterName = "Unknown"; + if (broadcast != null) + { + var playerInfo = _dalamudUtilService.FindPlayerByNameHash(broadcast.HashedCID); + broadcasterName = !string.IsNullOrEmpty(playerInfo.Name) ? playerInfo.Name : "Unknown Player"; + } + ImGui.TextUnformatted(broadcasterName); + ImGui.TableNextColumn(); var label = $"Join##{shell.Group.GID}"; @@ -123,8 +138,6 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase if (!_currentSyncshells.Exists(g => string.Equals(g.GID, shell.GID, StringComparison.Ordinal))) { - - if (ImGui.Button(label)) { _logger.LogInformation($"Join requested for Syncshell {shell.Group.GID} ({shell.Group.Alias})"); From 0cc7181e98552f5e577f1d1288c8bd24a1bc2f43 Mon Sep 17 00:00:00 2001 From: choco Date: Sun, 28 Sep 2025 19:41:19 +0200 Subject: [PATCH 17/29] added world name to syncshell broadcaster info --- LightlessSync/Services/DalamudUtilService.cs | 15 +++++++++++++-- LightlessSync/UI/SyncshellFinderUI.cs | 12 +++++++++--- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/LightlessSync/Services/DalamudUtilService.cs b/LightlessSync/Services/DalamudUtilService.cs index ed66506..e5fd735 100644 --- a/LightlessSync/Services/DalamudUtilService.cs +++ b/LightlessSync/Services/DalamudUtilService.cs @@ -1,4 +1,4 @@ -using Dalamud.Game.ClientState.Conditions; +using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.ClientState.Objects.Types; @@ -541,7 +541,6 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber curWaitTime += tick; Thread.Sleep(tick); } - Thread.Sleep(tick * 2); } @@ -557,6 +556,18 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber return result; } + public string? GetWorldNameFromPlayerAddress(nint address) + { + if (address == nint.Zero) return null; + + EnsureIsOnFramework(); + var playerCharacter = _objectTable.OfType().FirstOrDefault(p => p.Address == address); + if (playerCharacter == null) return null; + + var worldId = (ushort)playerCharacter.HomeWorld.RowId; + return WorldData.Value.TryGetValue(worldId, out var worldName) ? worldName : null; + } + private unsafe void CheckCharacterForDrawing(nint address, string characterName) { var gameObj = (GameObject*)address; diff --git a/LightlessSync/UI/SyncshellFinderUI.cs b/LightlessSync/UI/SyncshellFinderUI.cs index 32be263..4af72d5 100644 --- a/LightlessSync/UI/SyncshellFinderUI.cs +++ b/LightlessSync/UI/SyncshellFinderUI.cs @@ -119,13 +119,18 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase ImGui.TextUnformatted(displayName); ImGui.TableNextColumn(); - var broadcasts = _broadcastScannerService.GetActiveSyncshellBroadcasts(); - var broadcast = broadcasts.FirstOrDefault(b => string.Equals(b.GID, shell.Group.GID, StringComparison.Ordinal)); var broadcasterName = "Unknown"; + var broadcast = _broadcastScannerService.GetActiveSyncshellBroadcasts() + .FirstOrDefault(b => string.Equals(b.GID, shell.Group.GID, StringComparison.Ordinal)); + if (broadcast != null) { var playerInfo = _dalamudUtilService.FindPlayerByNameHash(broadcast.HashedCID); - broadcasterName = !string.IsNullOrEmpty(playerInfo.Name) ? playerInfo.Name : "Unknown Player"; + if (!string.IsNullOrEmpty(playerInfo.Name)) + { + var worldName = _dalamudUtilService.GetWorldNameFromPlayerAddress(playerInfo.Address); + broadcasterName = !string.IsNullOrEmpty(worldName) ? $"{playerInfo.Name} ({worldName})" : playerInfo.Name; + } } ImGui.TextUnformatted(broadcasterName); @@ -317,4 +322,5 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase return _nearbySyncshells[_selectedNearbyIndex].Group.GID; } + } From 7b415b4e478b56d38b3a7685969980b74e1dcbe6 Mon Sep 17 00:00:00 2001 From: CakeAndBanana Date: Mon, 29 Sep 2025 18:37:02 +0200 Subject: [PATCH 18/29] Changed it to so debug shows while in debug mode. --- LightlessSync/UI/BroadcastUI.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LightlessSync/UI/BroadcastUI.cs b/LightlessSync/UI/BroadcastUI.cs index 4977fb6..2076f6c 100644 --- a/LightlessSync/UI/BroadcastUI.cs +++ b/LightlessSync/UI/BroadcastUI.cs @@ -309,7 +309,7 @@ namespace LightlessSync.UI ImGui.EndTabItem(); } - +#if DEBUG if (ImGui.BeginTabItem("Debug")) { ImGui.Text("Broadcast Cache"); @@ -365,7 +365,7 @@ namespace LightlessSync.UI ImGui.EndTable(); } - +#endif ImGui.EndTabItem(); } From f74cd01a3290ff5de3821d6af4345276ad527fc5 Mon Sep 17 00:00:00 2001 From: CakeAndBanana Date: Mon, 29 Sep 2025 19:44:36 +0200 Subject: [PATCH 19/29] Pair request button only shows when lightfinder is on --- LightlessSync/Plugin.cs | 2 +- LightlessSync/UI/ContextMenu.cs | 23 +++++++++++++++-------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/LightlessSync/Plugin.cs b/LightlessSync/Plugin.cs index 3529735..24f1745 100644 --- a/LightlessSync/Plugin.cs +++ b/LightlessSync/Plugin.cs @@ -147,7 +147,7 @@ public sealed class Plugin : IDalamudPlugin collection.AddSingleton(); collection.AddSingleton(); collection.AddSingleton(addonLifecycle); - collection.AddSingleton(p => new ContextMenu(contextMenu, pluginInterface, gameData, p.GetRequiredService>(), p.GetRequiredService(), p.GetRequiredService(), objectTable)); + collection.AddSingleton(p => new ContextMenu(contextMenu, pluginInterface, gameData, p.GetRequiredService>(), p.GetRequiredService(), p.GetRequiredService(), objectTable, p.GetRequiredService())); collection.AddSingleton((s) => new IpcCallerPenumbra(s.GetRequiredService>(), pluginInterface, s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService())); collection.AddSingleton((s) => new IpcCallerGlamourer(s.GetRequiredService>(), pluginInterface, diff --git a/LightlessSync/UI/ContextMenu.cs b/LightlessSync/UI/ContextMenu.cs index 1e1cfc3..4a4ed62 100644 --- a/LightlessSync/UI/ContextMenu.cs +++ b/LightlessSync/UI/ContextMenu.cs @@ -3,6 +3,7 @@ using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.Gui.ContextMenu; using Dalamud.Plugin; using Dalamud.Plugin.Services; +using LightlessSync.LightlessConfiguration; using LightlessSync.Services; using LightlessSync.Utils; using LightlessSync.WebAPI; @@ -19,6 +20,7 @@ internal class ContextMenu : IHostedService private readonly IDataManager _gameData; private readonly ILogger _logger; private readonly DalamudUtilService _dalamudUtil; + private readonly LightlessConfigService _configService; private readonly ApiController _apiController; private readonly IObjectTable _objectTable; @@ -37,7 +39,8 @@ internal class ContextMenu : IHostedService ILogger logger, DalamudUtilService dalamudUtil, ApiController apiController, - IObjectTable objectTable) + IObjectTable objectTable, + LightlessConfigService configService) { _contextMenu = contextMenu; _pluginInterface = pluginInterface; @@ -46,6 +49,7 @@ internal class ContextMenu : IHostedService _dalamudUtil = dalamudUtil; _apiController = apiController; _objectTable = objectTable; + _configService = configService; } public Task StartAsync(CancellationToken cancellationToken) @@ -90,14 +94,17 @@ internal class ContextMenu : IHostedService if (!IsWorldValid(world)) return; - args.AddMenuItem(new MenuItem + if (_configService.Current.BroadcastEnabled) { - Name = "Send Pair Request", - PrefixChar = 'L', - UseDefaultPrefix = false, - PrefixColor = 708, - OnClicked = async _ => await HandleSelection(args) - }); + args.AddMenuItem(new MenuItem + { + Name = "Send Pair Request", + PrefixChar = 'L', + UseDefaultPrefix = false, + PrefixColor = 708, + OnClicked = async _ => await HandleSelection(args) + }); + } } private async Task HandleSelection(IMenuArgs args) From dc1fdd4a3e0816818f98fd894a18a633d80b618e Mon Sep 17 00:00:00 2001 From: CakeAndBanana Date: Mon, 29 Sep 2025 19:52:19 +0200 Subject: [PATCH 20/29] Removed the lightfinder needs to be turned on. --- LightlessSync/UI/ContextMenu.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/LightlessSync/UI/ContextMenu.cs b/LightlessSync/UI/ContextMenu.cs index 4a4ed62..e34a694 100644 --- a/LightlessSync/UI/ContextMenu.cs +++ b/LightlessSync/UI/ContextMenu.cs @@ -94,17 +94,14 @@ internal class ContextMenu : IHostedService if (!IsWorldValid(world)) return; - if (_configService.Current.BroadcastEnabled) + args.AddMenuItem(new MenuItem { - args.AddMenuItem(new MenuItem - { - Name = "Send Pair Request", - PrefixChar = 'L', - UseDefaultPrefix = false, - PrefixColor = 708, - OnClicked = async _ => await HandleSelection(args) - }); - } + Name = "Send Pair Request", + PrefixChar = 'L', + UseDefaultPrefix = false, + PrefixColor = 708, + OnClicked = async _ => await HandleSelection(args) + }); } private async Task HandleSelection(IMenuArgs args) From 7d6d500e6a7436837980918dc3d553557ac8e54d Mon Sep 17 00:00:00 2001 From: CakeAndBanana Date: Mon, 29 Sep 2025 22:38:38 +0200 Subject: [PATCH 21/29] Changes from Bites to Bytes. --- LightlessSync/UI/ContextMenu.cs | 5 ++--- LightlessSync/UI/UISharedService.cs | 10 ++++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/LightlessSync/UI/ContextMenu.cs b/LightlessSync/UI/ContextMenu.cs index e34a694..5a8b291 100644 --- a/LightlessSync/UI/ContextMenu.cs +++ b/LightlessSync/UI/ContextMenu.cs @@ -1,5 +1,4 @@ using Dalamud.Game.ClientState.Objects.SubKinds; -using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.Gui.ContextMenu; using Dalamud.Plugin; using Dalamud.Plugin.Services; @@ -81,7 +80,7 @@ internal class ContextMenu : IHostedService if (!_pluginInterface.UiBuilder.ShouldModifyUi) return; - if (!ValidAddons.Contains(args.AddonName)) + if (!ValidAddons.Contains(args.AddonName, StringComparer.Ordinal)) return; if (args.Target is not MenuTargetDefault target) @@ -127,7 +126,7 @@ internal class ContextMenu : IHostedService return; } - var senderCid = (await _dalamudUtil.GetCIDAsync()).ToString().GetHash256(); + var senderCid = (await _dalamudUtil.GetCIDAsync().ConfigureAwait(false)).ToString().GetHash256(); var receiverCid = DalamudUtilService.GetHashedCIDFromPlayerPointer(targetData.Address); _logger.LogInformation("Sending pair request: sender {SenderCid}, receiver {ReceiverCid}", senderCid, receiverCid); diff --git a/LightlessSync/UI/UISharedService.cs b/LightlessSync/UI/UISharedService.cs index e4753dc..7fdf97f 100644 --- a/LightlessSync/UI/UISharedService.cs +++ b/LightlessSync/UI/UISharedService.cs @@ -173,12 +173,14 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase public static string ByteToString(long bytes, bool addSuffix = true) { - string[] suffix = ["B", "KiB", "MiB", "GiB", "TiB"]; - int i; + string[] suffix = { "B", "KB", "MB", "GB", "TB" }; + int i = 0; double dblSByte = bytes; - for (i = 0; i < suffix.Length && bytes >= 1024; i++, bytes /= 1024) + + while (dblSByte >= 1000 && i < suffix.Length - 1) { - dblSByte = bytes / 1024.0; + dblSByte /= 1000.0; + i++; } return addSuffix ? $"{dblSByte:0.00} {suffix[i]}" : $"{dblSByte:0.00}"; From 8a9d4b8daa81f6c7a432662273ace589e0550978 Mon Sep 17 00:00:00 2001 From: CakeAndBanana Date: Mon, 29 Sep 2025 23:19:02 +0200 Subject: [PATCH 22/29] Added more byte options --- LightlessSync/UI/UISharedService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LightlessSync/UI/UISharedService.cs b/LightlessSync/UI/UISharedService.cs index 7fdf97f..9a935ba 100644 --- a/LightlessSync/UI/UISharedService.cs +++ b/LightlessSync/UI/UISharedService.cs @@ -173,7 +173,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase public static string ByteToString(long bytes, bool addSuffix = true) { - string[] suffix = { "B", "KB", "MB", "GB", "TB" }; + string[] suffix = { "B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}; int i = 0; double dblSByte = bytes; From 0b7b543dd7e07439a7935b7c2c90a28e59930693 Mon Sep 17 00:00:00 2001 From: CakeAndBanana Date: Mon, 29 Sep 2025 23:56:11 +0200 Subject: [PATCH 23/29] Add Group check, reworked world check for instances. --- LightlessSync/UI/BroadcastUI.cs | 21 ++++++++------------- LightlessSync/UI/ContextMenu.cs | 24 +++++++++++++++++++++++- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/LightlessSync/UI/BroadcastUI.cs b/LightlessSync/UI/BroadcastUI.cs index 2076f6c..6d50184 100644 --- a/LightlessSync/UI/BroadcastUI.cs +++ b/LightlessSync/UI/BroadcastUI.cs @@ -22,7 +22,7 @@ namespace LightlessSync.UI private IReadOnlyList _allSyncshells; private string _userUid = string.Empty; - private List<(string Label, string? GID, bool IsAvailable)> _syncshellOptions = new(); + private readonly List<(string Label, string? GID, bool IsAvailable)> _syncshellOptions = new(); public BroadcastUI( ILogger logger, @@ -48,7 +48,7 @@ namespace LightlessSync.UI MaximumSize = new(750, 400) }; - mediator.Subscribe(this, async _ => await RefreshSyncshells()); + mediator.Subscribe(this, async _ => await RefreshSyncshells().ConfigureAwait(false)); } private void RebuildSyncshellDropdownOptions() @@ -62,7 +62,7 @@ namespace LightlessSync.UI _syncshellOptions.Clear(); _syncshellOptions.Add(("None", null, true)); - var addedGids = new HashSet(); + var addedGids = new HashSet(StringComparer.Ordinal); foreach (var shell in ownedSyncshells) { @@ -73,7 +73,7 @@ namespace LightlessSync.UI if (!string.IsNullOrEmpty(selectedGid) && !addedGids.Contains(selectedGid)) { - var matching = allSyncshells.FirstOrDefault(g => g.GID == selectedGid); + var matching = allSyncshells.FirstOrDefault(g => string.Equals(g.GID, selectedGid, StringComparison.Ordinal)); if (matching != null) { var label = matching.GroupAliasOrGID ?? matching.GID; @@ -97,7 +97,7 @@ namespace LightlessSync.UI { if (!_apiController.IsConnected) { - _allSyncshells = Array.Empty(); + _allSyncshells = []; RebuildSyncshellDropdownOptions(); return; } @@ -109,7 +109,7 @@ namespace LightlessSync.UI catch (Exception ex) { _logger.LogError(ex, "Failed to fetch Syncshells."); - _allSyncshells = Array.Empty(); + _allSyncshells = []; } RebuildSyncshellDropdownOptions(); @@ -260,14 +260,14 @@ namespace LightlessSync.UI } var selectedGid = _configService.Current.SelectedFinderSyncshell; - var currentOption = _syncshellOptions.FirstOrDefault(o => o.GID == selectedGid); + var currentOption = _syncshellOptions.FirstOrDefault(o => string.Equals(o.GID, selectedGid, StringComparison.Ordinal)); var preview = currentOption.Label ?? "Select a Syncshell..."; if (ImGui.BeginCombo("##SyncshellDropdown", preview)) { foreach (var (label, gid, available) in _syncshellOptions) { - bool isSelected = gid == selectedGid; + bool isSelected = string.Equals(gid, selectedGid, StringComparison.Ordinal); if (!available) ImGui.PushStyleColor(ImGuiCol.Text, UIColors.Get("DimRed")); @@ -373,10 +373,5 @@ namespace LightlessSync.UI ImGui.EndTabBar(); } } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - } } } diff --git a/LightlessSync/UI/ContextMenu.cs b/LightlessSync/UI/ContextMenu.cs index 5a8b291..80bbb0c 100644 --- a/LightlessSync/UI/ContextMenu.cs +++ b/LightlessSync/UI/ContextMenu.cs @@ -9,6 +9,7 @@ using LightlessSync.WebAPI; using Lumina.Excel.Sheets; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using System.Linq; namespace LightlessSync.UI; @@ -141,8 +142,29 @@ internal class ContextMenu : IHostedService private World GetWorld(uint worldId) { var sheet = _gameData.GetExcelSheet()!; - return sheet.TryGetRow(worldId, out var world) ? world : sheet.First(); + var luminaWorlds = sheet.Where(x => + { + var dc = x.DataCenter.ValueNullable; + var name = x.Name.ExtractText(); + var internalName = x.InternalName.ExtractText(); + + if (dc == null || dc.Value.Region == 0 || string.IsNullOrWhiteSpace(dc.Value.Name.ExtractText())) + return false; + + if (string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(internalName)) + return false; + + if (name.Contains('-', StringComparison.Ordinal) || name.Contains('_', StringComparison.Ordinal)) + return false; + + return x.RowId > 3001 && IsChineseJapaneseKoreanString(name); + }); + + return luminaWorlds.FirstOrDefault(x => x.RowId == worldId); } + private static bool IsChineseJapaneseKoreanString(string text) => text.All(IsChineseJapaneseKoreanCharacter); + + private static bool IsChineseJapaneseKoreanCharacter(char c) => (c >= 0x4E00 && c <= 0x9FFF); public bool IsWorldValid(uint worldId) => IsWorldValid(GetWorld(worldId)); From 9696ac60f1fe8ac65863263a48ab1cf90d3be25b Mon Sep 17 00:00:00 2001 From: CakeAndBanana Date: Tue, 30 Sep 2025 00:00:11 +0200 Subject: [PATCH 24/29] Added region bound. --- LightlessSync/UI/ContextMenu.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/LightlessSync/UI/ContextMenu.cs b/LightlessSync/UI/ContextMenu.cs index 80bbb0c..19b5955 100644 --- a/LightlessSync/UI/ContextMenu.cs +++ b/LightlessSync/UI/ContextMenu.cs @@ -157,9 +157,15 @@ internal class ContextMenu : IHostedService if (name.Contains('-', StringComparison.Ordinal) || name.Contains('_', StringComparison.Ordinal)) return false; - return x.RowId > 3001 && IsChineseJapaneseKoreanString(name); + return x.DataCenter.Value.Region != 5 || x.RowId > 3001 && x.RowId != 1200 && IsChineseJapaneseKoreanString(name); }); + foreach (var world in luminaWorlds) + { + _logger.LogInformation(message: $"ID: {world.RowId} - World: {world.Name.ExtractText()}"); + } + _logger.LogInformation(message: $"Character is in World: {worldId}"); + return luminaWorlds.FirstOrDefault(x => x.RowId == worldId); } private static bool IsChineseJapaneseKoreanString(string text) => text.All(IsChineseJapaneseKoreanCharacter); From e85b9007546dbbf4c715daa28b97cfcd71b0f875 Mon Sep 17 00:00:00 2001 From: azyges Date: Tue, 30 Sep 2025 11:17:55 +0900 Subject: [PATCH 25/29] some ui updates --- LightlessSync/UI/BroadcastUI.cs | 5 +- LightlessSync/UI/DataAnalysisUi.cs | 207 +++++++++++++++++----------- LightlessSync/UI/SettingsUi.cs | 17 +++ LightlessSync/UI/UISharedService.cs | 15 ++ 4 files changed, 158 insertions(+), 86 deletions(-) diff --git a/LightlessSync/UI/BroadcastUI.cs b/LightlessSync/UI/BroadcastUI.cs index 6d50184..eda5a53 100644 --- a/LightlessSync/UI/BroadcastUI.cs +++ b/LightlessSync/UI/BroadcastUI.cs @@ -309,7 +309,8 @@ namespace LightlessSync.UI ImGui.EndTabItem(); } -#if DEBUG + + #if DEBUG if (ImGui.BeginTabItem("Debug")) { ImGui.Text("Broadcast Cache"); @@ -365,10 +366,10 @@ namespace LightlessSync.UI ImGui.EndTable(); } -#endif ImGui.EndTabItem(); } + #endif ImGui.EndTabBar(); } diff --git a/LightlessSync/UI/DataAnalysisUi.cs b/LightlessSync/UI/DataAnalysisUi.cs index e1ef15c..5b750f3 100644 --- a/LightlessSync/UI/DataAnalysisUi.cs +++ b/LightlessSync/UI/DataAnalysisUi.cs @@ -547,73 +547,147 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase using var tab = ImRaii.TabItem(tabText + "###" + kvp.Key.ToString()); if (tab.Success) { - var groupedfiles = kvp.Value.Select(v => v.Value).GroupBy(f => f.FileType, StringComparer.Ordinal) - .OrderBy(k => k.Key, StringComparer.Ordinal).ToList(); + var groupedfiles = kvp.Value.Select(v => v.Value).GroupBy(f => f.FileType, StringComparer.Ordinal).OrderBy(k => k.Key, StringComparer.Ordinal).ToList(); - ImGui.TextUnformatted("Files for " + kvp.Key); - ImGui.SameLine(); - ImGui.TextUnformatted(kvp.Value.Count.ToString()); - ImGui.SameLine(); + ImGui.PushStyleVar(ImGuiStyleVar.CellPadding, new Vector2(1f, 1f)); + ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(1f, 1f)); - using (var font = ImRaii.PushFont(UiBuilder.IconFont)) + if (ImGui.BeginTable($"##fileStats_{kvp.Key}", 3, + ImGuiTableFlags.BordersInnerV | ImGuiTableFlags.SizingFixedFit)) { - ImGui.TextUnformatted(FontAwesomeIcon.InfoCircle.ToIconString()); - } - if (ImGui.IsItemHovered()) - { - string text = ""; - text = string.Join(Environment.NewLine, groupedfiles - .Select(f => f.Key + ": " + f.Count() + " files, size: " + UiSharedService.ByteToString(f.Sum(v => v.OriginalSize)) - + ", compressed: " + UiSharedService.ByteToString(f.Sum(v => v.CompressedSize)))); - ImGui.SetTooltip(text); - } - ImGui.TextUnformatted($"{kvp.Key} size (actual):"); - ImGui.SameLine(); - ImGui.TextUnformatted(UiSharedService.ByteToString(kvp.Value.Sum(c => c.Value.OriginalSize))); - ImGui.TextUnformatted($"{kvp.Key} size (compressed for up/download only):"); - ImGui.SameLine(); - ImGui.TextUnformatted(UiSharedService.ByteToString(kvp.Value.Sum(c => c.Value.CompressedSize))); - ImGui.Separator(); - - var vramUsage = groupedfiles.SingleOrDefault(v => string.Equals(v.Key, "tex", StringComparison.Ordinal)); - if (vramUsage != null) - { - var actualVramUsage = vramUsage.Sum(f => f.OriginalSize); - ImGui.TextUnformatted($"{kvp.Key} VRAM usage:"); + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.TextUnformatted($"Files for {kvp.Key}"); + ImGui.TableNextColumn(); + ImGui.TextUnformatted(kvp.Value.Count.ToString()); ImGui.SameLine(); - ImGui.TextUnformatted(UiSharedService.ByteToString(actualVramUsage)); + using (var font = ImRaii.PushFont(UiBuilder.IconFont)) + ImGui.TextUnformatted(FontAwesomeIcon.InfoCircle.ToIconString()); + if (ImGui.IsItemHovered()) + { + string text = string.Join(Environment.NewLine, groupedfiles.Select(f => + $"{f.Key}: {f.Count()} files, size: {UiSharedService.ByteToString(f.Sum(v => v.OriginalSize))}, compressed: {UiSharedService.ByteToString(f.Sum(v => v.CompressedSize))}")); + ImGui.SetTooltip(text); + } + ImGui.TableNextColumn(); + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.TextUnformatted($"{kvp.Key} size (actual):"); + ImGui.TableNextColumn(); + ImGui.TextUnformatted(UiSharedService.ByteToString(kvp.Value.Sum(c => c.Value.OriginalSize))); + ImGui.TableNextColumn(); + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.TextUnformatted($"{kvp.Key} size (compressed for up/download only):"); + _uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"), 1.5f); + ImGui.TableNextColumn(); + ImGui.TextUnformatted(UiSharedService.ByteToString(kvp.Value.Sum(c => c.Value.CompressedSize))); + _uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"), 1.5f); + ImGui.TableNextColumn(); + + var vramUsage = groupedfiles.SingleOrDefault(v => string.Equals(v.Key, "tex", StringComparison.Ordinal)); + if (vramUsage != null) + { + var actualVramUsage = vramUsage.Sum(f => f.OriginalSize); + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.TextUnformatted($"{kvp.Key} VRAM usage:"); + ImGui.TableNextColumn(); + ImGui.TextUnformatted(UiSharedService.ByteToString(actualVramUsage)); + ImGui.TableNextColumn(); + + if (_playerPerformanceConfig.Current.WarnOnExceedingThresholds + || _playerPerformanceConfig.Current.ShowPerformanceIndicator) + { + var currentVramWarning = _playerPerformanceConfig.Current.VRAMSizeWarningThresholdMiB; + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.TextUnformatted("Configured VRAM threshold:"); + ImGui.TableNextColumn(); + ImGui.TextUnformatted($"{currentVramWarning} MiB."); + ImGui.TableNextColumn(); + if (currentVramWarning * 1024 * 1024 < actualVramUsage) + { + UiSharedService.ColorText( + $"You exceed your own threshold by {UiSharedService.ByteToString(actualVramUsage - (currentVramWarning * 1024 * 1024))}", + UIColors.Get("LightlessYellow")); + } + } + } + + var actualTriCount = kvp.Value.Sum(f => f.Value.Triangles); + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.TextUnformatted($"{kvp.Key} modded model triangles:"); + ImGui.TableNextColumn(); + ImGui.TextUnformatted(actualTriCount.ToString()); + ImGui.TableNextColumn(); + if (_playerPerformanceConfig.Current.WarnOnExceedingThresholds || _playerPerformanceConfig.Current.ShowPerformanceIndicator) { - using var _ = ImRaii.PushIndent(10f); - var currentVramWarning = _playerPerformanceConfig.Current.VRAMSizeWarningThresholdMiB; - ImGui.TextUnformatted($"Configured VRAM warning threshold: {currentVramWarning} MiB."); - if (currentVramWarning * 1024 * 1024 < actualVramUsage) + var currentTriWarning = _playerPerformanceConfig.Current.TrisWarningThresholdThousands; + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.TextUnformatted("Configured triangle threshold:"); + ImGui.TableNextColumn(); + ImGui.TextUnformatted($"{currentTriWarning * 1000} triangles."); + ImGui.TableNextColumn(); + if (currentTriWarning * 1000 < actualTriCount) { - UiSharedService.ColorText($"You exceed your own threshold by " + - $"{UiSharedService.ByteToString(actualVramUsage - (currentVramWarning * 1024 * 1024))}.", + UiSharedService.ColorText( + $"You exceed your own threshold by {actualTriCount - (currentTriWarning * 1000)}", UIColors.Get("LightlessYellow")); } } + + ImGui.EndTable(); } - var actualTriCount = kvp.Value.Sum(f => f.Value.Triangles); - ImGui.TextUnformatted($"{kvp.Key} modded model triangles: {actualTriCount}"); - if (_playerPerformanceConfig.Current.WarnOnExceedingThresholds - || _playerPerformanceConfig.Current.ShowPerformanceIndicator) + ImGui.PopStyleVar(2); + + _uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 2f); + + _uiSharedService.MediumText("Selected file:", UIColors.Get("LightlessBlue")); + ImGui.SameLine(); + _uiSharedService.MediumText(_selectedHash, UIColors.Get("LightlessYellow")); + + if (_cachedAnalysis[_selectedObjectTab].TryGetValue(_selectedHash, out CharacterAnalyzer.FileDataEntry? item)) { - using var _ = ImRaii.PushIndent(10f); - var currentTriWarning = _playerPerformanceConfig.Current.TrisWarningThresholdThousands; - ImGui.TextUnformatted($"Configured triangle warning threshold: {currentTriWarning * 1000} triangles."); - if (currentTriWarning * 1000 < actualTriCount) + var filePaths = item.FilePaths; + UiSharedService.ColorText("Local file path:", UIColors.Get("LightlessBlue")); + ImGui.SameLine(); + UiSharedService.TextWrapped(filePaths[0]); + if (filePaths.Count > 1) { - UiSharedService.ColorText($"You exceed your own threshold by " + - $"{actualTriCount - (currentTriWarning * 1000)} triangles.", - UIColors.Get("LightlessYellow")); + ImGui.SameLine(); + ImGui.TextUnformatted($"(and {filePaths.Count - 1} more)"); + ImGui.SameLine(); + _uiSharedService.IconText(FontAwesomeIcon.InfoCircle); + UiSharedService.AttachToolTip(string.Join(Environment.NewLine, filePaths.Skip(1))); + } + + var gamepaths = item.GamePaths; + UiSharedService.ColorText("Used by game path:", UIColors.Get("LightlessBlue")); + ImGui.SameLine(); + UiSharedService.TextWrapped(gamepaths[0]); + if (gamepaths.Count > 1) + { + ImGui.SameLine(); + ImGui.TextUnformatted($"(and {gamepaths.Count - 1} more)"); + ImGui.SameLine(); + _uiSharedService.IconText(FontAwesomeIcon.InfoCircle); + UiSharedService.AttachToolTip(string.Join(Environment.NewLine, gamepaths.Skip(1))); } } ImGui.Separator(); + if (_selectedObjectTab != kvp.Key) { _selectedHash = string.Empty; @@ -692,41 +766,6 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase } } } - - ImGui.Separator(); - - ImGui.TextUnformatted("Selected file:"); - ImGui.SameLine(); - UiSharedService.ColorText(_selectedHash, UIColors.Get("LightlessYellow")); - - if (_cachedAnalysis[_selectedObjectTab].TryGetValue(_selectedHash, out CharacterAnalyzer.FileDataEntry? item)) - { - var filePaths = item.FilePaths; - ImGui.TextUnformatted("Local file path:"); - ImGui.SameLine(); - UiSharedService.TextWrapped(filePaths[0]); - if (filePaths.Count > 1) - { - ImGui.SameLine(); - ImGui.TextUnformatted($"(and {filePaths.Count - 1} more)"); - ImGui.SameLine(); - _uiSharedService.IconText(FontAwesomeIcon.InfoCircle); - UiSharedService.AttachToolTip(string.Join(Environment.NewLine, filePaths.Skip(1))); - } - - var gamepaths = item.GamePaths; - ImGui.TextUnformatted("Used by game path:"); - ImGui.SameLine(); - UiSharedService.TextWrapped(gamepaths[0]); - if (gamepaths.Count > 1) - { - ImGui.SameLine(); - ImGui.TextUnformatted($"(and {gamepaths.Count - 1} more)"); - ImGui.SameLine(); - _uiSharedService.IconText(FontAwesomeIcon.InfoCircle); - UiSharedService.AttachToolTip(string.Join(Environment.NewLine, gamepaths.Skip(1))); - } - } } public override void OnOpen() @@ -855,7 +894,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase } var filePath = item.FilePaths[0]; bool toConvert = _texturesToConvert.ContainsKey(filePath); - if (ImGui.Checkbox("###convert" + item.Hash, ref toConvert)) + if (UiSharedService.CheckboxWithBorder("###convert" + item.Hash, ref toConvert, UIColors.Get("LightlessPurple"), 1.5f)) { if (toConvert && !_texturesToConvert.ContainsKey(filePath)) { diff --git a/LightlessSync/UI/SettingsUi.cs b/LightlessSync/UI/SettingsUi.cs index fdbe821..95bb2af 100644 --- a/LightlessSync/UI/SettingsUi.cs +++ b/LightlessSync/UI/SettingsUi.cs @@ -979,9 +979,17 @@ public class SettingsUi : WindowMediatorSubscriberBase var colorNames = new[] { ("LightlessPurple", "Lightless Purple", "Primary colors"), + ("LightlessPurpleActive", "Lightless Purple Active", "Primary colors"), + ("LightlessPurpleDefault", "Lightless Purple Inactive", "Primary colors"), ("LightlessBlue", "Lightless Blue", "Secondary colors"), + + ("LightlessGreen", "Lightless Green", "Active elements"), + ("LightlessYellow", "Lightless Yellow", "Warning colors"), + ("LightlessYellow2", "Lightless Yellow 2", "Warning colors"), + ("PairBlue", "Pair Blue", "Pair UI elements"), + ("DimRed", "Dim Red", "Error and offline") }; @@ -1020,6 +1028,8 @@ public class SettingsUi : WindowMediatorSubscriberBase ImGui.Spacing(); + _uiShared.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"), 1.5f); + ImGui.TextUnformatted("Server Info Bar Colors"); if (ImGui.Checkbox("Color-code the Server Info Bar entry according to status", ref useColorsInDtr)) @@ -1054,6 +1064,9 @@ public class SettingsUi : WindowMediatorSubscriberBase } ImGui.Spacing(); + + _uiShared.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"), 1.5f); + ImGui.TextUnformatted("Nameplate Colors"); var nameColorsEnabled = _configService.Current.IsNameplateColorsEnabled; @@ -1092,6 +1105,10 @@ public class SettingsUi : WindowMediatorSubscriberBase } } + ImGui.Spacing(); + + _uiShared.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"), 1.5f); + if (ImGui.Checkbox("Use the complete redesign of the UI for Lightless client.", ref useLightlessRedesign)) { _configService.Current.UseLightlessRedesign = useLightlessRedesign; diff --git a/LightlessSync/UI/UISharedService.cs b/LightlessSync/UI/UISharedService.cs index 9a935ba..24899ee 100644 --- a/LightlessSync/UI/UISharedService.cs +++ b/LightlessSync/UI/UISharedService.cs @@ -512,6 +512,21 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase ImGui.Dummy(new Vector2(0, thickness * scale)); } + public static bool CheckboxWithBorder(string label, ref bool value, Vector4? borderColor = null, float borderThickness = 1.0f, float rounding = 3.0f) + { + var pos = ImGui.GetCursorScreenPos(); + + bool changed = ImGui.Checkbox(label, ref value); + + var min = pos; + var max = ImGui.GetItemRectMax(); + + var col = ImGui.GetColorU32(borderColor ?? ImGuiColors.DalamudGrey); + ImGui.GetWindowDrawList().AddRect(min, max, col, rounding, ImDrawFlags.None, borderThickness); + + return changed; + } + public void MediumText(string text, Vector4? color = null) { FontText(text, MediumFont, color); From 1fae47b1718a72ebe386dd2b3cd2a2be15930c1d Mon Sep 17 00:00:00 2001 From: CakeAndBanana Date: Tue, 30 Sep 2025 05:44:49 +0200 Subject: [PATCH 26/29] Removal of Logging of worlds for testing on what region I should needed --- LightlessSync/UI/ContextMenu.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/LightlessSync/UI/ContextMenu.cs b/LightlessSync/UI/ContextMenu.cs index 19b5955..6cdc90c 100644 --- a/LightlessSync/UI/ContextMenu.cs +++ b/LightlessSync/UI/ContextMenu.cs @@ -131,7 +131,7 @@ internal class ContextMenu : IHostedService var receiverCid = DalamudUtilService.GetHashedCIDFromPlayerPointer(targetData.Address); _logger.LogInformation("Sending pair request: sender {SenderCid}, receiver {ReceiverCid}", senderCid, receiverCid); - await _apiController.TryPairWithContentId(receiverCid, senderCid); + await _apiController.TryPairWithContentId(receiverCid, senderCid).ConfigureAwait(false); } catch (Exception ex) { @@ -160,12 +160,6 @@ internal class ContextMenu : IHostedService return x.DataCenter.Value.Region != 5 || x.RowId > 3001 && x.RowId != 1200 && IsChineseJapaneseKoreanString(name); }); - foreach (var world in luminaWorlds) - { - _logger.LogInformation(message: $"ID: {world.RowId} - World: {world.Name.ExtractText()}"); - } - _logger.LogInformation(message: $"Character is in World: {worldId}"); - return luminaWorlds.FirstOrDefault(x => x.RowId == worldId); } private static bool IsChineseJapaneseKoreanString(string text) => text.All(IsChineseJapaneseKoreanCharacter); From 597a0beb91aeceb8e14511e27980e8d74f8a06a1 Mon Sep 17 00:00:00 2001 From: CakeAndBanana Date: Tue, 30 Sep 2025 05:50:39 +0200 Subject: [PATCH 27/29] Added check if context user is in range or in object table. --- LightlessSync/UI/ContextMenu.cs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/LightlessSync/UI/ContextMenu.cs b/LightlessSync/UI/ContextMenu.cs index 6cdc90c..b828cdd 100644 --- a/LightlessSync/UI/ContextMenu.cs +++ b/LightlessSync/UI/ContextMenu.cs @@ -90,6 +90,10 @@ internal class ContextMenu : IHostedService if (string.IsNullOrEmpty(target.TargetName) || target.TargetObjectId == 0 || target.TargetHomeWorld.RowId == 0) return; + IPlayerCharacter? targetData = GetPlayerFromObjectTable(target); + if (targetData == null || targetData.Address == IntPtr.Zero) + return; + var world = GetWorld(target.TargetHomeWorld.RowId); if (!IsWorldValid(world)) return; @@ -100,7 +104,7 @@ internal class ContextMenu : IHostedService PrefixChar = 'L', UseDefaultPrefix = false, PrefixColor = 708, - OnClicked = async _ => await HandleSelection(args) + OnClicked = async _ => await HandleSelection(args).ConfigureAwait(false) }); } @@ -115,11 +119,7 @@ internal class ContextMenu : IHostedService try { - var targetData = _objectTable - .OfType() - .FirstOrDefault(p => - string.Equals(p.Name.TextValue, target.TargetName, StringComparison.OrdinalIgnoreCase) && - p.HomeWorld.RowId == target.TargetHomeWorld.RowId); + IPlayerCharacter? targetData = GetPlayerFromObjectTable(target); if (targetData == null || targetData.Address == IntPtr.Zero) { @@ -139,6 +139,15 @@ internal class ContextMenu : IHostedService } } + private IPlayerCharacter? GetPlayerFromObjectTable(MenuTargetDefault target) + { + return _objectTable + .OfType() + .FirstOrDefault(p => + string.Equals(p.Name.TextValue, target.TargetName, StringComparison.OrdinalIgnoreCase) && + p.HomeWorld.RowId == target.TargetHomeWorld.RowId); + } + private World GetWorld(uint worldId) { var sheet = _gameData.GetExcelSheet()!; @@ -162,6 +171,7 @@ internal class ContextMenu : IHostedService return luminaWorlds.FirstOrDefault(x => x.RowId == worldId); } + private static bool IsChineseJapaneseKoreanString(string text) => text.All(IsChineseJapaneseKoreanCharacter); private static bool IsChineseJapaneseKoreanCharacter(char c) => (c >= 0x4E00 && c <= 0x9FFF); From 36c1611486099fa3f293e4b2118a252fede661d6 Mon Sep 17 00:00:00 2001 From: choco Date: Tue, 30 Sep 2025 16:09:57 +0200 Subject: [PATCH 28/29] compactui settings button peepoo --- LightlessSync/UI/CompactUI.cs | 15 --------------- LightlessSync/UI/TopTabMenu.cs | 6 ++++-- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/LightlessSync/UI/CompactUI.cs b/LightlessSync/UI/CompactUI.cs index d56f035..5390c2f 100644 --- a/LightlessSync/UI/CompactUI.cs +++ b/LightlessSync/UI/CompactUI.cs @@ -109,21 +109,6 @@ public class CompactUi : WindowMediatorSubscriberBase AllowClickthrough = false; TitleBarButtons = new() { - new TitleBarButton() - { - Icon = FontAwesomeIcon.Cog, - Click = (msg) => - { - Mediator.Publish(new UiToggleMessage(typeof(SettingsUi))); - }, - IconOffset = new(2,1), - ShowTooltip = () => - { - ImGui.BeginTooltip(); - ImGui.Text("Open Lightless Settings"); - ImGui.EndTooltip(); - } - }, new TitleBarButton() { Icon = FontAwesomeIcon.Book, diff --git a/LightlessSync/UI/TopTabMenu.cs b/LightlessSync/UI/TopTabMenu.cs index 339b22c..fd7f72c 100644 --- a/LightlessSync/UI/TopTabMenu.cs +++ b/LightlessSync/UI/TopTabMenu.cs @@ -1,4 +1,4 @@ -using Dalamud.Bindings.ImGui; +using Dalamud.Bindings.ImGui; using Dalamud.Interface; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; @@ -148,11 +148,13 @@ public class TopTabMenu ImGui.SameLine(); using (ImRaii.PushFont(UiBuilder.IconFont)) { + var x = ImGui.GetCursorScreenPos(); if (ImGui.Button(FontAwesomeIcon.Cog.ToIconString(), buttonSize)) { _lightlessMediator.Publish(new UiToggleMessage(typeof(SettingsUi))); } - + ImGui.SameLine(); + var xAfter = ImGui.GetCursorScreenPos(); } UiSharedService.AttachToolTip("Open Lightless Settings"); From 571decd33b50614df0871aafc9650957080175f6 Mon Sep 17 00:00:00 2001 From: choco Date: Tue, 30 Sep 2025 16:14:53 +0200 Subject: [PATCH 29/29] removed unused cursor position --- LightlessSync/UI/TopTabMenu.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/LightlessSync/UI/TopTabMenu.cs b/LightlessSync/UI/TopTabMenu.cs index fd7f72c..52fcc13 100644 --- a/LightlessSync/UI/TopTabMenu.cs +++ b/LightlessSync/UI/TopTabMenu.cs @@ -154,7 +154,6 @@ public class TopTabMenu _lightlessMediator.Publish(new UiToggleMessage(typeof(SettingsUi))); } ImGui.SameLine(); - var xAfter = ImGui.GetCursorScreenPos(); } UiSharedService.AttachToolTip("Open Lightless Settings");