1121 lines
50 KiB
C#
1121 lines
50 KiB
C#
using Dalamud.Bindings.ImGui;
|
|
using Dalamud.Interface;
|
|
using Dalamud.Interface.Utility;
|
|
using Dalamud.Interface.Utility.Raii;
|
|
using LightlessSync.API.Data.Extensions;
|
|
using LightlessSync.API.Dto.Group;
|
|
using LightlessSync.Interop.Ipc;
|
|
using LightlessSync.LightlessConfiguration;
|
|
using LightlessSync.PlayerData.Handlers;
|
|
using LightlessSync.PlayerData.Pairs;
|
|
using LightlessSync.Services;
|
|
using LightlessSync.Services.LightFinder;
|
|
using LightlessSync.Services.Mediator;
|
|
using LightlessSync.Services.ServerConfiguration;
|
|
using LightlessSync.UI.Components;
|
|
using LightlessSync.UI.Handlers;
|
|
using LightlessSync.UI.Models;
|
|
using LightlessSync.UI.Services;
|
|
using LightlessSync.UI.Style;
|
|
using LightlessSync.Utils;
|
|
using LightlessSync.WebAPI;
|
|
using LightlessSync.WebAPI.Files;
|
|
using LightlessSync.WebAPI.Files.Models;
|
|
using LightlessSync.WebAPI.SignalR.Utils;
|
|
using Microsoft.Extensions.Logging;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Immutable;
|
|
using System.Globalization;
|
|
using System.Numerics;
|
|
using System.Reflection;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace LightlessSync.UI;
|
|
|
|
public class CompactUi : WindowMediatorSubscriberBase
|
|
{
|
|
private readonly CharacterAnalyzer _characterAnalyzer;
|
|
private readonly ApiController _apiController;
|
|
private readonly LightlessConfigService _configService;
|
|
private readonly LightlessMediator _lightlessMediator;
|
|
private readonly PairLedger _pairLedger;
|
|
private readonly ConcurrentDictionary<GameObjectHandler, Dictionary<string, FileDownloadStatus>> _currentDownloads = new();
|
|
private readonly DrawEntityFactory _drawEntityFactory;
|
|
private readonly FileUploadManager _fileTransferManager;
|
|
private readonly PlayerPerformanceConfigService _playerPerformanceConfig;
|
|
private readonly PairUiService _pairUiService;
|
|
private readonly SelectTagForPairUi _selectTagForPairUi;
|
|
private readonly SelectTagForSyncshellUi _selectTagForSyncshellUi;
|
|
private readonly SelectSyncshellForTagUi _selectSyncshellForTagUi;
|
|
private readonly RenameSyncshellTagUi _renameSyncshellTagUi;
|
|
private readonly SelectPairForTagUi _selectPairsForGroupUi;
|
|
private readonly RenamePairTagUi _renamePairTagUi;
|
|
private readonly IpcManager _ipcManager;
|
|
private readonly ServerConfigurationManager _serverManager;
|
|
private readonly TopTabMenu _tabMenu;
|
|
private readonly TagHandler _tagHandler;
|
|
private readonly UiSharedService _uiSharedService;
|
|
private readonly LightFinderService _broadcastService;
|
|
|
|
private List<IDrawFolder> _drawFolders;
|
|
private Pair? _lastAddedUser;
|
|
private string _lastAddedUserComment = string.Empty;
|
|
private Vector2 _lastPosition = Vector2.One;
|
|
private Vector2 _lastSize = Vector2.One;
|
|
private bool _showModalForUserAddition;
|
|
private float _transferPartHeight;
|
|
private bool _wasOpen;
|
|
private float _windowContentWidth;
|
|
private readonly SeluneBrush _seluneBrush = new();
|
|
private const float _connectButtonHighlightThickness = 14f;
|
|
|
|
public CompactUi(
|
|
ILogger<CompactUi> logger,
|
|
UiSharedService uiShared,
|
|
LightlessConfigService configService,
|
|
ApiController apiController,
|
|
PairUiService pairUiService,
|
|
ServerConfigurationManager serverManager,
|
|
LightlessMediator mediator,
|
|
FileUploadManager fileTransferManager,
|
|
TagHandler tagHandler,
|
|
DrawEntityFactory drawEntityFactory,
|
|
SelectTagForPairUi selectTagForPairUi,
|
|
SelectPairForTagUi selectPairForTagUi,
|
|
RenamePairTagUi renameTagUi,
|
|
SelectTagForSyncshellUi selectTagForSyncshellUi,
|
|
SelectSyncshellForTagUi selectSyncshellForTagUi,
|
|
RenameSyncshellTagUi renameSyncshellTagUi,
|
|
PerformanceCollectorService performanceCollectorService,
|
|
IpcManager ipcManager,
|
|
LightFinderService broadcastService,
|
|
CharacterAnalyzer characterAnalyzer,
|
|
PlayerPerformanceConfigService playerPerformanceConfig, PairRequestService pairRequestService, DalamudUtilService dalamudUtilService, NotificationService lightlessNotificationService, PairLedger pairLedger) : base(logger, mediator, "###LightlessSyncMainUI", performanceCollectorService)
|
|
{
|
|
_uiSharedService = uiShared;
|
|
_configService = configService;
|
|
_apiController = apiController;
|
|
_pairUiService = pairUiService;
|
|
_serverManager = serverManager;
|
|
_fileTransferManager = fileTransferManager;
|
|
_tagHandler = tagHandler;
|
|
_drawEntityFactory = drawEntityFactory;
|
|
_selectTagForPairUi = selectTagForPairUi;
|
|
_selectTagForSyncshellUi = selectTagForSyncshellUi;
|
|
_selectSyncshellForTagUi = selectSyncshellForTagUi;
|
|
_renameSyncshellTagUi = renameSyncshellTagUi;
|
|
_selectPairsForGroupUi = selectPairForTagUi;
|
|
_renamePairTagUi = renameTagUi;
|
|
_ipcManager = ipcManager;
|
|
_broadcastService = broadcastService;
|
|
_pairLedger = pairLedger;
|
|
_tabMenu = new TopTabMenu(Mediator, _apiController, _uiSharedService, pairRequestService, dalamudUtilService, lightlessNotificationService);
|
|
|
|
AllowPinning = true;
|
|
AllowClickthrough = false;
|
|
TitleBarButtons =
|
|
[
|
|
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,
|
|
Click = (msg) =>
|
|
{
|
|
Mediator.Publish(new UiToggleMessage(typeof(EventViewerUI)));
|
|
},
|
|
IconOffset = new(2,1),
|
|
ShowTooltip = () =>
|
|
{
|
|
ImGui.BeginTooltip();
|
|
ImGui.Text("Open Lightless Event Viewer");
|
|
ImGui.EndTooltip();
|
|
}
|
|
},
|
|
];
|
|
|
|
_drawFolders = [.. DrawFolders];
|
|
|
|
#if DEBUG
|
|
string dev = "Dev Build";
|
|
var ver = Assembly.GetExecutingAssembly().GetName().Version!;
|
|
WindowName = $"Lightless Sync {dev} ({ver.Major}.{ver.Minor}.{ver.Build})###LightlessSyncMainUI";
|
|
Toggle();
|
|
#else
|
|
var ver = Assembly.GetExecutingAssembly().GetName().Version;
|
|
WindowName = "Lightless Sync " + ver.Major + "." + ver.Minor + "." + ver.Build + "###LightlessSyncMainUI";
|
|
#endif
|
|
Mediator.Subscribe<SwitchToMainUiMessage>(this, (_) => IsOpen = true);
|
|
Mediator.Subscribe<SwitchToIntroUiMessage>(this, (_) => IsOpen = false);
|
|
Mediator.Subscribe<CutsceneStartMessage>(this, (_) => UiSharedService_GposeStart());
|
|
Mediator.Subscribe<CutsceneEndMessage>(this, (_) => UiSharedService_GposeEnd());
|
|
Mediator.Subscribe<DownloadStartedMessage>(this, (msg) => _currentDownloads[msg.DownloadId] = msg.DownloadStatus);
|
|
Mediator.Subscribe<DownloadFinishedMessage>(this, (msg) => _currentDownloads.TryRemove(msg.DownloadId, out _));
|
|
Mediator.Subscribe<RefreshUiMessage>(this, (msg) => _drawFolders = DrawFolders.ToList());
|
|
|
|
Flags |= ImGuiWindowFlags.NoDocking;
|
|
|
|
SizeConstraints = new WindowSizeConstraints()
|
|
{
|
|
MinimumSize = new Vector2(375, 400),
|
|
MaximumSize = new Vector2(375, 2000),
|
|
};
|
|
_characterAnalyzer = characterAnalyzer;
|
|
_playerPerformanceConfig = playerPerformanceConfig;
|
|
_lightlessMediator = mediator;
|
|
}
|
|
|
|
protected override void DrawInternal()
|
|
{
|
|
var drawList = ImGui.GetWindowDrawList();
|
|
var windowPos = ImGui.GetWindowPos();
|
|
var windowSize = ImGui.GetWindowSize();
|
|
using var selune = Selune.Begin(_seluneBrush, drawList, windowPos, windowSize);
|
|
|
|
_windowContentWidth = UiSharedService.GetWindowContentRegionWidth();
|
|
if (!_apiController.IsCurrentVersion)
|
|
{
|
|
var ver = _apiController.CurrentClientVersion;
|
|
var unsupported = "UNSUPPORTED VERSION";
|
|
using (_uiSharedService.UidFont.Push())
|
|
{
|
|
var uidTextSize = ImGui.CalcTextSize(unsupported);
|
|
ImGui.SetCursorPosX((ImGui.GetWindowContentRegionMax().X + ImGui.GetWindowContentRegionMin().X) / 2 - uidTextSize.X / 2);
|
|
ImGui.AlignTextToFramePadding();
|
|
ImGui.TextColored(UIColors.Get("DimRed"), unsupported);
|
|
}
|
|
UiSharedService.ColorTextWrapped($"Your Lightless Sync installation is out of date, the current version is {ver.Major}.{ver.Minor}.{ver.Build}. " +
|
|
$"It is highly recommended to keep Lightless Sync up to date. Open /xlplugins and update the plugin.", UIColors.Get("DimRed"));
|
|
}
|
|
|
|
if (!_ipcManager.Initialized)
|
|
{
|
|
var unsupported = "MISSING ESSENTIAL PLUGINS";
|
|
|
|
using (_uiSharedService.UidFont.Push())
|
|
{
|
|
var uidTextSize = ImGui.CalcTextSize(unsupported);
|
|
ImGui.SetCursorPosX((ImGui.GetWindowContentRegionMax().X + ImGui.GetWindowContentRegionMin().X) / 2 - uidTextSize.X / 2);
|
|
ImGui.AlignTextToFramePadding();
|
|
ImGui.TextColored(UIColors.Get("DimRed"), unsupported);
|
|
}
|
|
var penumAvailable = _ipcManager.Penumbra.APIAvailable;
|
|
var glamAvailable = _ipcManager.Glamourer.APIAvailable;
|
|
|
|
UiSharedService.ColorTextWrapped($"One or more Plugins essential for Lightless operation are unavailable. Enable or update following plugins:", UIColors.Get("DimRed"));
|
|
using var indent = ImRaii.PushIndent(10f);
|
|
if (!penumAvailable)
|
|
{
|
|
UiSharedService.TextWrapped("Penumbra");
|
|
_uiSharedService.BooleanToColoredIcon(penumAvailable, true);
|
|
}
|
|
if (!glamAvailable)
|
|
{
|
|
UiSharedService.TextWrapped("Glamourer");
|
|
_uiSharedService.BooleanToColoredIcon(glamAvailable, true);
|
|
}
|
|
ImGui.Separator();
|
|
}
|
|
|
|
using (ImRaii.PushId("header")) DrawUIDHeader();
|
|
_uiSharedService.RoundedSeparator(UIColors.Get("LightlessPurple"), 2.5f, 1f, 12f);
|
|
using (ImRaii.PushId("serverstatus"))
|
|
{
|
|
DrawServerStatus();
|
|
}
|
|
selune.DrawHighlightOnly(ImGui.GetIO().DeltaTime);
|
|
var style = ImGui.GetStyle();
|
|
var contentMinY = windowPos.Y + ImGui.GetWindowContentRegionMin().Y;
|
|
var gradientInset = 4f * ImGuiHelpers.GlobalScale;
|
|
var gradientTop = MathF.Max(contentMinY, ImGui.GetCursorScreenPos().Y - style.ItemSpacing.Y + gradientInset);
|
|
ImGui.Separator();
|
|
|
|
if (_apiController.ServerState is ServerState.Connected)
|
|
{
|
|
var pairSnapshot = _pairUiService.GetSnapshot();
|
|
|
|
using (ImRaii.PushId("global-topmenu")) _tabMenu.Draw(pairSnapshot);
|
|
using (ImRaii.PushId("pairlist")) DrawPairs();
|
|
ImGui.Separator();
|
|
var transfersTop = ImGui.GetCursorScreenPos().Y;
|
|
var gradientBottom = MathF.Max(gradientTop, transfersTop - style.ItemSpacing.Y - gradientInset);
|
|
selune.DrawGradient(gradientTop, gradientBottom, ImGui.GetIO().DeltaTime);
|
|
float pairlistEnd = ImGui.GetCursorPosY();
|
|
using (ImRaii.PushId("transfers")) DrawTransfers();
|
|
_transferPartHeight = ImGui.GetCursorPosY() - pairlistEnd - ImGui.GetTextLineHeight();
|
|
using (ImRaii.PushId("group-pair-popup")) _selectPairsForGroupUi.Draw(pairSnapshot.DirectPairs);
|
|
using (ImRaii.PushId("group-syncshell-popup")) _selectSyncshellForTagUi.Draw(pairSnapshot.Groups);
|
|
using (ImRaii.PushId("group-pair-edit")) _renamePairTagUi.Draw();
|
|
using (ImRaii.PushId("group-syncshell-edit")) _renameSyncshellTagUi.Draw();
|
|
using (ImRaii.PushId("grouping-pair-popup")) _selectTagForPairUi.Draw();
|
|
using (ImRaii.PushId("grouping-syncshell-popup")) _selectTagForSyncshellUi.Draw();
|
|
}
|
|
else
|
|
{
|
|
selune.Animate(ImGui.GetIO().DeltaTime);
|
|
}
|
|
|
|
var lastAddedPair = _pairUiService.GetLastAddedPair();
|
|
if (_configService.Current.OpenPopupOnAdd && lastAddedPair is not null)
|
|
{
|
|
_lastAddedUser = lastAddedPair;
|
|
_pairUiService.ClearLastAddedPair();
|
|
ImGui.OpenPopup("Set Notes for New User");
|
|
_showModalForUserAddition = true;
|
|
_lastAddedUserComment = string.Empty;
|
|
}
|
|
|
|
if (ImGui.BeginPopupModal("Set Notes for New User", ref _showModalForUserAddition, UiSharedService.PopupWindowFlags))
|
|
{
|
|
if (_lastAddedUser == null)
|
|
{
|
|
_showModalForUserAddition = false;
|
|
}
|
|
else
|
|
{
|
|
UiSharedService.TextWrapped($"You have successfully added {_lastAddedUser.UserData.AliasOrUID}. Set a local note for the user in the field below:");
|
|
ImGui.InputTextWithHint("##noteforuser", $"Note for {_lastAddedUser.UserData.AliasOrUID}", ref _lastAddedUserComment, 100);
|
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Save, "Save Note"))
|
|
{
|
|
_serverManager.SetNoteForUid(_lastAddedUser.UserData.UID, _lastAddedUserComment);
|
|
_lastAddedUser = null;
|
|
_lastAddedUserComment = string.Empty;
|
|
_showModalForUserAddition = false;
|
|
}
|
|
}
|
|
UiSharedService.SetScaledWindowSize(275);
|
|
ImGui.EndPopup();
|
|
}
|
|
|
|
var pos = ImGui.GetWindowPos();
|
|
var size = ImGui.GetWindowSize();
|
|
if (_lastSize != size || _lastPosition != pos)
|
|
{
|
|
_lastSize = size;
|
|
_lastPosition = pos;
|
|
Mediator.Publish(new CompactUiChange(_lastSize, _lastPosition));
|
|
}
|
|
}
|
|
|
|
private void DrawPairs()
|
|
{
|
|
float ySize = Math.Abs(_transferPartHeight) < 0.0001f
|
|
? 1
|
|
: (ImGui.GetWindowContentRegionMax().Y - ImGui.GetWindowContentRegionMin().Y
|
|
+ ImGui.GetTextLineHeight() - ImGui.GetStyle().WindowPadding.Y - ImGui.GetStyle().WindowBorderSize) - _transferPartHeight - ImGui.GetCursorPosY();
|
|
|
|
if (ImGui.BeginChild("list", new Vector2(_windowContentWidth, ySize), border: false))
|
|
{
|
|
foreach (var item in _drawFolders)
|
|
{
|
|
item.Draw();
|
|
}
|
|
}
|
|
|
|
ImGui.EndChild();
|
|
}
|
|
|
|
private void DrawServerStatus()
|
|
{
|
|
var buttonSize = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Link);
|
|
var userCount = _apiController.OnlineUsers.ToString(CultureInfo.InvariantCulture);
|
|
var userSize = ImGui.CalcTextSize(userCount);
|
|
var textSize = ImGui.CalcTextSize("Users Online");
|
|
#if DEBUG
|
|
string shardConnection = $"Shard: {_apiController.ServerInfo.ShardName}";
|
|
#else
|
|
string shardConnection = string.Equals(_apiController.ServerInfo.ShardName, "Main", StringComparison.OrdinalIgnoreCase) ? string.Empty : $"Shard: {_apiController.ServerInfo.ShardName}";
|
|
#endif
|
|
var shardTextSize = ImGui.CalcTextSize(shardConnection);
|
|
var printShard = !string.IsNullOrEmpty(_apiController.ServerInfo.ShardName) && shardConnection != string.Empty;
|
|
|
|
if (_apiController.ServerState is ServerState.Connected)
|
|
{
|
|
ImGui.SetCursorPosX((ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth()) / 2 - (userSize.X + textSize.X) / 2 - ImGui.GetStyle().ItemSpacing.X / 2);
|
|
if (!printShard) ImGui.AlignTextToFramePadding();
|
|
ImGui.TextColored(UIColors.Get("LightlessPurple"), userCount);
|
|
ImGui.SameLine();
|
|
if (!printShard) ImGui.AlignTextToFramePadding();
|
|
ImGui.TextUnformatted("Users Online");
|
|
}
|
|
else
|
|
{
|
|
ImGui.AlignTextToFramePadding();
|
|
ImGui.TextColored(UIColors.Get("DimRed"), "Not connected to any server");
|
|
}
|
|
|
|
if (printShard)
|
|
{
|
|
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - ImGui.GetStyle().ItemSpacing.Y);
|
|
ImGui.SetCursorPosX((ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth()) / 2 - shardTextSize.X / 2);
|
|
ImGui.TextUnformatted(shardConnection);
|
|
}
|
|
|
|
ImGui.SameLine();
|
|
if (printShard)
|
|
{
|
|
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - ((userSize.Y + textSize.Y) / 2 + shardTextSize.Y) / 2 - ImGui.GetStyle().ItemSpacing.Y + buttonSize.Y / 2);
|
|
}
|
|
bool isConnectingOrConnected = _apiController.ServerState is ServerState.Connected or ServerState.Connecting or ServerState.Reconnecting;
|
|
var color = UiSharedService.GetBoolColor(!isConnectingOrConnected);
|
|
var connectedIcon = isConnectingOrConnected ? FontAwesomeIcon.Unlink : FontAwesomeIcon.Link;
|
|
|
|
ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth() - buttonSize.X);
|
|
if (printShard)
|
|
{
|
|
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - ((userSize.Y + textSize.Y) / 2 + shardTextSize.Y) / 2 - ImGui.GetStyle().ItemSpacing.Y + buttonSize.Y / 2);
|
|
}
|
|
|
|
if (_apiController.ServerState is not (ServerState.Reconnecting or ServerState.Disconnecting))
|
|
{
|
|
using (ImRaii.PushColor(ImGuiCol.Text, color))
|
|
{
|
|
if (_uiSharedService.IconButton(connectedIcon))
|
|
{
|
|
if (isConnectingOrConnected && !_serverManager.CurrentServer.FullPause)
|
|
{
|
|
_serverManager.CurrentServer.FullPause = true;
|
|
_serverManager.Save();
|
|
}
|
|
else if (!isConnectingOrConnected && _serverManager.CurrentServer.FullPause)
|
|
{
|
|
_serverManager.CurrentServer.FullPause = false;
|
|
_serverManager.Save();
|
|
}
|
|
|
|
_ = _apiController.CreateConnectionsAsync();
|
|
}
|
|
}
|
|
|
|
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenBlockedByActiveItem) || ImGui.IsItemActive())
|
|
{
|
|
Selune.RegisterHighlight(
|
|
ImGui.GetItemRectMin(),
|
|
ImGui.GetItemRectMax(),
|
|
SeluneHighlightMode.Both,
|
|
borderOnly: true,
|
|
borderThicknessOverride: _connectButtonHighlightThickness,
|
|
exactSize: true,
|
|
clipToElement: true,
|
|
roundingOverride: ImGui.GetStyle().FrameRounding);
|
|
}
|
|
|
|
UiSharedService.AttachToolTip(isConnectingOrConnected ? "Disconnect from " + _serverManager.CurrentServer.ServerName : "Connect to " + _serverManager.CurrentServer.ServerName);
|
|
}
|
|
}
|
|
|
|
private void DrawTransfers()
|
|
{
|
|
var currentUploads = _fileTransferManager.GetCurrentUploadsSnapshot();
|
|
ImGui.AlignTextToFramePadding();
|
|
_uiSharedService.IconText(FontAwesomeIcon.Upload);
|
|
ImGui.SameLine(35 * ImGuiHelpers.GlobalScale);
|
|
|
|
if (currentUploads.Count > 0)
|
|
{
|
|
int totalUploads = currentUploads.Count;
|
|
int doneUploads = 0;
|
|
long totalUploaded = 0;
|
|
long totalToUpload = 0;
|
|
|
|
foreach (var upload in currentUploads)
|
|
{
|
|
if (upload.IsTransferred)
|
|
{
|
|
doneUploads++;
|
|
}
|
|
|
|
totalUploaded += upload.Transferred;
|
|
totalToUpload += upload.Total;
|
|
}
|
|
|
|
int activeUploads = totalUploads - doneUploads;
|
|
var uploadSlotLimit = Math.Clamp(_configService.Current.ParallelUploads, 1, 8);
|
|
|
|
ImGui.TextUnformatted($"{doneUploads}/{totalUploads} (slots {activeUploads}/{uploadSlotLimit})");
|
|
var uploadText = $"({UiSharedService.ByteToString(totalUploaded)}/{UiSharedService.ByteToString(totalToUpload)})";
|
|
var textSize = ImGui.CalcTextSize(uploadText);
|
|
ImGui.SameLine(_windowContentWidth - textSize.X);
|
|
ImGui.AlignTextToFramePadding();
|
|
ImGui.TextUnformatted(uploadText);
|
|
}
|
|
else
|
|
{
|
|
ImGui.AlignTextToFramePadding();
|
|
ImGui.TextUnformatted("No uploads in progress");
|
|
}
|
|
|
|
var downloadSummary = GetDownloadSummary();
|
|
ImGui.AlignTextToFramePadding();
|
|
_uiSharedService.IconText(FontAwesomeIcon.Download);
|
|
ImGui.SameLine(35 * ImGuiHelpers.GlobalScale);
|
|
|
|
if (downloadSummary.HasDownloads)
|
|
{
|
|
var totalDownloads = downloadSummary.TotalFiles;
|
|
var doneDownloads = downloadSummary.TransferredFiles;
|
|
var totalDownloaded = downloadSummary.TransferredBytes;
|
|
var totalToDownload = downloadSummary.TotalBytes;
|
|
|
|
ImGui.TextUnformatted($"{doneDownloads}/{totalDownloads}");
|
|
var downloadText =
|
|
$"({UiSharedService.ByteToString(totalDownloaded)}/{UiSharedService.ByteToString(totalToDownload)})";
|
|
var textSize = ImGui.CalcTextSize(downloadText);
|
|
ImGui.SameLine(_windowContentWidth - textSize.X);
|
|
ImGui.AlignTextToFramePadding();
|
|
ImGui.TextUnformatted(downloadText);
|
|
}
|
|
else
|
|
{
|
|
ImGui.AlignTextToFramePadding();
|
|
ImGui.TextUnformatted("No downloads in progress");
|
|
}
|
|
}
|
|
|
|
|
|
private DownloadSummary GetDownloadSummary()
|
|
{
|
|
long totalBytes = 0;
|
|
long transferredBytes = 0;
|
|
int totalFiles = 0;
|
|
int transferredFiles = 0;
|
|
|
|
foreach (var kvp in _currentDownloads.ToArray())
|
|
{
|
|
if (kvp.Value is not { Count: > 0 } statuses)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
foreach (var status in statuses.Values)
|
|
{
|
|
totalBytes += status.TotalBytes;
|
|
transferredBytes += status.TransferredBytes;
|
|
totalFiles += status.TotalFiles;
|
|
transferredFiles += status.TransferredFiles;
|
|
}
|
|
}
|
|
|
|
return new DownloadSummary(totalFiles, transferredFiles, transferredBytes, totalBytes);
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Auto)]
|
|
private readonly record struct DownloadSummary(int TotalFiles, int TransferredFiles, long TransferredBytes, long TotalBytes)
|
|
{
|
|
public bool HasDownloads => TotalFiles > 0 || TotalBytes > 0;
|
|
}
|
|
|
|
private void DrawUIDHeader()
|
|
{
|
|
var uidText = GetUidText();
|
|
|
|
Vector4? vanityTextColor = null;
|
|
Vector4? vanityGlowColor = null;
|
|
bool useVanityColors = false;
|
|
|
|
if (_configService.Current.useColoredUIDs && _apiController.HasVanity)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(_apiController.TextColorHex))
|
|
{
|
|
vanityTextColor = UIColors.HexToRgba(_apiController.TextColorHex);
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(_apiController.TextGlowColorHex))
|
|
{
|
|
vanityGlowColor = UIColors.HexToRgba(_apiController.TextGlowColorHex);
|
|
}
|
|
|
|
useVanityColors = vanityTextColor is not null || vanityGlowColor is not null;
|
|
}
|
|
|
|
//Getting information of character and triangles threshold to show overlimit status in UID bar.
|
|
var analysisSummary = _characterAnalyzer.LatestSummary;
|
|
|
|
Vector2 uidTextSize, iconSize;
|
|
using (_uiSharedService.UidFont.Push())
|
|
uidTextSize = ImGui.CalcTextSize(uidText);
|
|
|
|
using (_uiSharedService.IconFont.Push())
|
|
iconSize = ImGui.CalcTextSize(FontAwesomeIcon.PersonCirclePlus.ToIconString());
|
|
|
|
float contentWidth = ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X;
|
|
float uidStartX = (contentWidth - uidTextSize.X) / 2f;
|
|
float cursorY = ImGui.GetCursorPosY();
|
|
|
|
if (_configService.Current.BroadcastEnabled && _apiController.IsConnected)
|
|
{
|
|
float iconYOffset = (uidTextSize.Y - iconSize.Y) * 0.5f;
|
|
var buttonSize = new Vector2(iconSize.X, uidTextSize.Y);
|
|
|
|
ImGui.SetCursorPos(new Vector2(ImGui.GetStyle().ItemSpacing.X + 5f, cursorY));
|
|
ImGui.InvisibleButton("BroadcastIcon", buttonSize);
|
|
|
|
var iconPos = ImGui.GetItemRectMin() + new Vector2(0f, iconYOffset);
|
|
using (_uiSharedService.IconFont.Push())
|
|
ImGui.GetWindowDrawList().AddText(iconPos, ImGui.GetColorU32(UIColors.Get("LightlessGreen")), FontAwesomeIcon.PersonCirclePlus.ToIconString());
|
|
|
|
|
|
if (ImGui.IsItemHovered())
|
|
{
|
|
var padding = new Vector2(10f * ImGuiHelpers.GlobalScale);
|
|
Selune.RegisterHighlight(
|
|
ImGui.GetItemRectMin() - padding,
|
|
ImGui.GetItemRectMax() + padding,
|
|
SeluneHighlightMode.Point,
|
|
exactSize: true,
|
|
clipToElement: true,
|
|
clipPadding: padding,
|
|
highlightColorOverride: UIColors.Get("LightlessGreen"),
|
|
highlightAlphaOverride: 0.2f);
|
|
|
|
ImGui.BeginTooltip();
|
|
ImGui.PushTextWrapPos(ImGui.GetFontSize() * 32f);
|
|
|
|
ImGui.PushStyleColor(ImGuiCol.Text, UIColors.Get("PairBlue"));
|
|
ImGui.Text("Lightfinder");
|
|
ImGui.PopStyleColor();
|
|
|
|
ImGui.PushStyleColor(ImGuiCol.Text, UIColors.Get("DimRed"));
|
|
ImGui.TextWrapped("Use Lightfinder when you're okay with being visible to other users and understand that you are responsible for your own experience.");
|
|
ImGui.PopStyleColor();
|
|
|
|
ImGuiHelpers.ScaledDummy(0.2f);
|
|
UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 2f);
|
|
|
|
if (_configService.Current.BroadcastEnabled)
|
|
{
|
|
ImGui.PushStyleColor(ImGuiCol.Text, UIColors.Get("LightlessGreen"));
|
|
ImGui.Text("The Lightfinder calls, and somewhere, a soul may answer."); // cringe..
|
|
ImGui.PopStyleColor();
|
|
|
|
var ttl = _broadcastService.RemainingTtl;
|
|
if (ttl is { } remaining && remaining > TimeSpan.Zero)
|
|
{
|
|
ImGui.PushStyleColor(ImGuiCol.Text, UIColors.Get("LightlessYellow"));
|
|
ImGui.Text($"Still shining, for {remaining:hh\\:mm\\:ss}");
|
|
ImGui.PopStyleColor();
|
|
}
|
|
else
|
|
{
|
|
ImGui.PushStyleColor(ImGuiCol.Text, UIColors.Get("DimRed"));
|
|
ImGui.Text("The Lightfinder's light wanes, but not in vain."); // cringe..
|
|
ImGui.PopStyleColor();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ImGui.PushStyleColor(ImGuiCol.Text, UIColors.Get("DimRed"));
|
|
ImGui.Text("The Lightfinder rests, waiting to shine again."); // cringe..
|
|
ImGui.PopStyleColor();
|
|
}
|
|
|
|
var cooldown = _broadcastService.RemainingCooldown;
|
|
if (cooldown is { } cd)
|
|
{
|
|
ImGui.PushStyleColor(ImGuiCol.Text, UIColors.Get("DimRed"));
|
|
ImGui.Text($"The Lightfinder gathers its strength... ({Math.Ceiling(cd.TotalSeconds)}s)");
|
|
ImGui.PopStyleColor();
|
|
}
|
|
|
|
ImGui.PopTextWrapPos();
|
|
ImGui.EndTooltip();
|
|
}
|
|
|
|
if (ImGui.IsItemClicked())
|
|
_lightlessMediator.Publish(new UiToggleMessage(typeof(LightFinderUI)));
|
|
}
|
|
|
|
ImGui.SetCursorPosY(cursorY);
|
|
ImGui.SetCursorPosX(uidStartX);
|
|
|
|
bool headerItemClicked;
|
|
using (_uiSharedService.UidFont.Push())
|
|
{
|
|
if (useVanityColors)
|
|
{
|
|
var seString = SeStringUtils.BuildFormattedPlayerName(uidText, vanityTextColor, vanityGlowColor);
|
|
var cursorPos = ImGui.GetCursorScreenPos();
|
|
var fontPtr = ImGui.GetFont();
|
|
SeStringUtils.RenderSeStringWithHitbox(seString, cursorPos, fontPtr, "uid-header");
|
|
}
|
|
else
|
|
{
|
|
ImGui.TextColored(GetUidColor(), uidText);
|
|
}
|
|
}
|
|
|
|
if (ImGui.IsItemHovered())
|
|
{
|
|
var padding = new Vector2(35f * ImGuiHelpers.GlobalScale);
|
|
Selune.RegisterHighlight(
|
|
ImGui.GetItemRectMin() - padding,
|
|
ImGui.GetItemRectMax() + padding,
|
|
SeluneHighlightMode.Point,
|
|
exactSize: true,
|
|
clipToElement: true,
|
|
clipPadding: padding,
|
|
highlightColorOverride: vanityGlowColor,
|
|
highlightAlphaOverride: 0.05f);
|
|
}
|
|
|
|
headerItemClicked = ImGui.IsItemClicked();
|
|
|
|
if (headerItemClicked)
|
|
{
|
|
ImGui.SetClipboardText(uidText);
|
|
}
|
|
|
|
UiSharedService.AttachToolTip("Click to copy");
|
|
|
|
if (_apiController.ServerState is ServerState.Connected && analysisSummary.HasData)
|
|
{
|
|
var objectSummary = analysisSummary.Objects.Values.FirstOrDefault(summary => summary.HasEntries);
|
|
if (objectSummary.HasEntries)
|
|
{
|
|
var actualVramUsage = objectSummary.TexOriginalBytes;
|
|
var actualTriCount = objectSummary.TotalTriangles;
|
|
|
|
var isOverVRAMUsage = _playerPerformanceConfig.Current.VRAMSizeWarningThresholdMiB * 1024 * 1024 < actualVramUsage;
|
|
var isOverTriHold = actualTriCount > (_playerPerformanceConfig.Current.TrisWarningThresholdThousands * 1000);
|
|
|
|
if ((isOverTriHold || isOverVRAMUsage) && _playerPerformanceConfig.Current.WarnOnExceedingThresholds)
|
|
{
|
|
ImGui.SameLine();
|
|
ImGui.SetCursorPosY(cursorY + 15f);
|
|
_uiSharedService.IconText(FontAwesomeIcon.ExclamationTriangle, UIColors.Get("LightlessYellow"));
|
|
|
|
string warningMessage = "";
|
|
if (isOverTriHold)
|
|
{
|
|
warningMessage += $"You exceed your own triangles threshold by " +
|
|
$"{actualTriCount - _playerPerformanceConfig.Current.TrisWarningThresholdThousands * 1000} triangles.";
|
|
warningMessage += Environment.NewLine;
|
|
|
|
}
|
|
if (isOverVRAMUsage)
|
|
{
|
|
warningMessage += $"You exceed your own VRAM threshold by " +
|
|
$"{UiSharedService.ByteToString(actualVramUsage - (_playerPerformanceConfig.Current.VRAMSizeWarningThresholdMiB * 1024 * 1024))}.";
|
|
}
|
|
UiSharedService.AttachToolTip(warningMessage);
|
|
if (ImGui.IsItemClicked())
|
|
{
|
|
_lightlessMediator.Publish(new UiToggleMessage(typeof(DataAnalysisUi)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_apiController.ServerState is ServerState.Connected)
|
|
{
|
|
if (headerItemClicked)
|
|
{
|
|
ImGui.SetClipboardText(_apiController.DisplayName);
|
|
}
|
|
|
|
if (!string.Equals(_apiController.DisplayName, _apiController.UID, StringComparison.Ordinal))
|
|
{
|
|
var origTextSize = ImGui.CalcTextSize(_apiController.UID);
|
|
ImGui.SetCursorPosX((ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X) / 2 - (origTextSize.X / 2));
|
|
|
|
if (useVanityColors)
|
|
{
|
|
var seString = SeStringUtils.BuildFormattedPlayerName(_apiController.UID, vanityTextColor, vanityGlowColor);
|
|
var cursorPos = ImGui.GetCursorScreenPos();
|
|
var fontPtr = ImGui.GetFont();
|
|
SeStringUtils.RenderSeStringWithHitbox(seString, cursorPos, fontPtr, "uid-footer");
|
|
}
|
|
else
|
|
{
|
|
ImGui.TextColored(GetUidColor(), _apiController.UID);
|
|
}
|
|
|
|
if (ImGui.IsItemHovered())
|
|
{
|
|
var padding = new Vector2(30f * ImGuiHelpers.GlobalScale);
|
|
Selune.RegisterHighlight(
|
|
ImGui.GetItemRectMin() - padding,
|
|
ImGui.GetItemRectMax() + padding,
|
|
SeluneHighlightMode.Point,
|
|
exactSize: true,
|
|
clipToElement: true,
|
|
clipPadding: padding,
|
|
highlightColorOverride: vanityGlowColor,
|
|
highlightAlphaOverride: 0.05f);
|
|
}
|
|
|
|
bool uidFooterClicked = ImGui.IsItemClicked();
|
|
UiSharedService.AttachToolTip("Click to copy");
|
|
if (uidFooterClicked)
|
|
{
|
|
ImGui.SetClipboardText(_apiController.UID);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UiSharedService.ColorTextWrapped(GetServerError(), GetUidColor());
|
|
}
|
|
}
|
|
|
|
private IEnumerable<IDrawFolder> DrawFolders
|
|
{
|
|
get
|
|
{
|
|
var drawFolders = new List<IDrawFolder>();
|
|
var filter = _tabMenu.Filter;
|
|
|
|
var allEntries = _drawEntityFactory.GetAllEntries().ToList();
|
|
var filteredEntries = string.IsNullOrEmpty(filter)
|
|
? allEntries
|
|
: allEntries.Where(e => PassesFilter(e, filter)).ToList();
|
|
|
|
var syncshells = _pairLedger.GetAllSyncshells();
|
|
var groupInfos = syncshells.Values
|
|
.Select(s => s.GroupFullInfo)
|
|
.OrderBy(g => g.GroupAliasOrGID, StringComparer.OrdinalIgnoreCase)
|
|
.ToList();
|
|
|
|
var entryLookup = allEntries.ToDictionary(e => e.DisplayEntry.Ident.UserId, StringComparer.Ordinal);
|
|
var filteredEntryLookup = filteredEntries.ToDictionary(e => e.DisplayEntry.Ident.UserId, StringComparer.Ordinal);
|
|
|
|
//Filter of online/visible pairs
|
|
if (_configService.Current.ShowVisibleUsersSeparately)
|
|
{
|
|
var allVisiblePairs = SortVisibleEntries(allEntries.Where(FilterVisibleUsers));
|
|
if (allVisiblePairs.Count > 0)
|
|
{
|
|
var filteredVisiblePairs = SortVisibleEntries(filteredEntries.Where(FilterVisibleUsers));
|
|
drawFolders.Add(_drawEntityFactory.CreateTagFolder(TagHandler.CustomVisibleTag, filteredVisiblePairs, allVisiblePairs));
|
|
}
|
|
}
|
|
|
|
//Filter of not foldered syncshells
|
|
var groupFolders = new List<GroupFolder>();
|
|
foreach (var group in groupInfos)
|
|
{
|
|
if (!FilterNotTaggedSyncshells(group))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var allGroupEntries = ResolveGroupEntries(entryLookup, syncshells, group, applyFilters: false);
|
|
var filteredGroupEntries = ResolveGroupEntries(filteredEntryLookup, syncshells, group, applyFilters: true);
|
|
// Always create the folder so empty syncshells remain visible in the UI.
|
|
var drawGroupFolder = _drawEntityFactory.CreateGroupFolder(group.Group.GID, group, filteredGroupEntries, allGroupEntries);
|
|
groupFolders.Add(new GroupFolder(group, drawGroupFolder));
|
|
}
|
|
|
|
//Filter of grouped up syncshells (All Syncshells Folder)
|
|
if (_configService.Current.GroupUpSyncshells)
|
|
drawFolders.Add(new DrawGroupedGroupFolder(groupFolders, _tagHandler, _apiController, _uiSharedService,
|
|
_selectSyncshellForTagUi, _renameSyncshellTagUi, ""));
|
|
else
|
|
drawFolders.AddRange(groupFolders.Select(v => v.GroupDrawFolder));
|
|
|
|
//Filter of grouped/foldered pairs
|
|
foreach (var tag in _tagHandler.GetAllPairTagsSorted())
|
|
{
|
|
var allTagPairs = SortEntries(allEntries.Where(e => FilterTagUsers(e, tag)));
|
|
if (allTagPairs.Count > 0)
|
|
{
|
|
var filteredTagPairs = SortEntries(filteredEntries.Where(e => FilterTagUsers(e, tag) && FilterOnlineOrPausedSelf(e)));
|
|
drawFolders.Add(_drawEntityFactory.CreateTagFolder(tag, filteredTagPairs, allTagPairs));
|
|
}
|
|
}
|
|
|
|
//Filter of grouped/foldered syncshells
|
|
foreach (var syncshellTag in _tagHandler.GetAllSyncshellTagsSorted())
|
|
{
|
|
var syncshellFolderTags = new List<GroupFolder>();
|
|
foreach (var group in groupInfos)
|
|
{
|
|
if (!_tagHandler.HasSyncshellTag(group.Group.GID, syncshellTag))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var allGroupEntries = ResolveGroupEntries(entryLookup, syncshells, group, applyFilters: false);
|
|
var filteredGroupEntries = ResolveGroupEntries(filteredEntryLookup, syncshells, group, applyFilters: true);
|
|
// Keep tagged syncshells rendered regardless of whether membership info has loaded.
|
|
var taggedGroupFolder = _drawEntityFactory.CreateGroupFolder($"tag_{group.Group.GID}", group, filteredGroupEntries, allGroupEntries);
|
|
syncshellFolderTags.Add(new GroupFolder(group, taggedGroupFolder));
|
|
}
|
|
|
|
drawFolders.Add(new DrawGroupedGroupFolder(syncshellFolderTags, _tagHandler, _apiController, _uiSharedService, _selectSyncshellForTagUi, _renameSyncshellTagUi, syncshellTag));
|
|
}
|
|
|
|
//Filter of not grouped/foldered and offline pairs
|
|
var allOnlineNotTaggedPairs = SortEntries(allEntries.Where(FilterNotTaggedUsers));
|
|
var onlineNotTaggedPairs = SortEntries(filteredEntries.Where(e => FilterNotTaggedUsers(e) && FilterOnlineOrPausedSelf(e)));
|
|
|
|
if (allOnlineNotTaggedPairs.Count > 0)
|
|
{
|
|
drawFolders.Add(_drawEntityFactory.CreateTagFolder(
|
|
_configService.Current.ShowOfflineUsersSeparately ? TagHandler.CustomOnlineTag : TagHandler.CustomAllTag,
|
|
onlineNotTaggedPairs,
|
|
allOnlineNotTaggedPairs));
|
|
}
|
|
|
|
if (_configService.Current.ShowOfflineUsersSeparately)
|
|
{
|
|
var allOfflinePairs = SortEntries(allEntries.Where(FilterOfflineUsers));
|
|
if (allOfflinePairs.Count > 0)
|
|
{
|
|
var filteredOfflinePairs = SortEntries(filteredEntries.Where(FilterOfflineUsers));
|
|
drawFolders.Add(_drawEntityFactory.CreateTagFolder(TagHandler.CustomOfflineTag, filteredOfflinePairs, allOfflinePairs));
|
|
}
|
|
|
|
if (_configService.Current.ShowSyncshellOfflineUsersSeparately)
|
|
{
|
|
var allOfflineSyncshellUsers = SortEntries(allEntries.Where(FilterOfflineSyncshellUsers));
|
|
if (allOfflineSyncshellUsers.Count > 0)
|
|
{
|
|
var filteredOfflineSyncshellUsers = SortEntries(filteredEntries.Where(FilterOfflineSyncshellUsers));
|
|
drawFolders.Add(_drawEntityFactory.CreateTagFolder(TagHandler.CustomOfflineSyncshellTag, filteredOfflineSyncshellUsers, allOfflineSyncshellUsers));
|
|
}
|
|
}
|
|
}
|
|
|
|
//Unpaired
|
|
var unpairedAllEntries = SortEntries(allEntries.Where(e => e.IsOneSided));
|
|
if (unpairedAllEntries.Count > 0)
|
|
{
|
|
var unpairedFiltered = SortEntries(filteredEntries.Where(e => e.IsOneSided));
|
|
drawFolders.Add(_drawEntityFactory.CreateTagFolder(TagHandler.CustomUnpairedTag, unpairedFiltered, unpairedAllEntries));
|
|
}
|
|
|
|
return drawFolders;
|
|
}
|
|
}
|
|
|
|
private bool PassesFilter(PairUiEntry entry, string filter)
|
|
{
|
|
if (string.IsNullOrEmpty(filter)) return true;
|
|
|
|
return entry.AliasOrUid.Contains(filter, StringComparison.OrdinalIgnoreCase)
|
|
|| (!string.IsNullOrEmpty(entry.Note) && entry.Note.Contains(filter, StringComparison.OrdinalIgnoreCase))
|
|
|| (!string.IsNullOrEmpty(entry.DisplayName) && entry.DisplayName.Contains(filter, StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
|
|
private string AlphabeticalSortKey(PairUiEntry entry)
|
|
{
|
|
if (_configService.Current.ShowCharacterNameInsteadOfNotesForVisible && !string.IsNullOrEmpty(entry.DisplayName))
|
|
{
|
|
return _configService.Current.PreferNotesOverNamesForVisible ? (entry.Note ?? string.Empty) : entry.DisplayName;
|
|
}
|
|
|
|
return !string.IsNullOrEmpty(entry.Note) ? entry.Note : entry.AliasOrUid;
|
|
}
|
|
|
|
private bool FilterOnlineOrPausedSelf(PairUiEntry entry) => entry.IsOnline || (!entry.IsOnline && !_configService.Current.ShowOfflineUsersSeparately) || entry.SelfPermissions.IsPaused();
|
|
|
|
private bool FilterVisibleUsers(PairUiEntry entry) => entry.IsVisible && entry.IsOnline && (_configService.Current.ShowSyncshellUsersInVisible || entry.IsDirectlyPaired);
|
|
|
|
private bool FilterTagUsers(PairUiEntry entry, string tag) => entry.IsDirectlyPaired && !entry.IsOneSided && _tagHandler.HasPairTag(entry.DisplayEntry.Ident.UserId, tag);
|
|
|
|
private bool FilterNotTaggedUsers(PairUiEntry entry) => entry.IsDirectlyPaired && !entry.IsOneSided && !_tagHandler.HasAnyPairTag(entry.DisplayEntry.Ident.UserId);
|
|
|
|
private bool FilterNotTaggedSyncshells(GroupFullInfoDto group) => !_tagHandler.HasAnySyncshellTag(group.GID) || _configService.Current.ShowGroupedSyncshellsInAll;
|
|
|
|
private bool FilterOfflineUsers(PairUiEntry entry)
|
|
{
|
|
var groups = entry.DisplayEntry.Groups;
|
|
var includeDirect = _configService.Current.ShowSyncshellOfflineUsersSeparately ? entry.IsDirectlyPaired : true;
|
|
var includeGroup = !entry.IsOneSided || groups.Count != 0;
|
|
return includeDirect && includeGroup && !entry.IsOnline && !entry.SelfPermissions.IsPaused();
|
|
}
|
|
|
|
private static bool FilterOfflineSyncshellUsers(PairUiEntry entry) => !entry.IsDirectlyPaired && !entry.IsOnline && !entry.SelfPermissions.IsPaused();
|
|
|
|
private ImmutableList<PairUiEntry> SortEntries(IEnumerable<PairUiEntry> entries)
|
|
{
|
|
return entries
|
|
.OrderByDescending(e => e.IsVisible)
|
|
.ThenByDescending(e => e.IsOnline)
|
|
.ThenBy(e => AlphabeticalSortKey(e), StringComparer.OrdinalIgnoreCase)
|
|
.ToImmutableList();
|
|
}
|
|
|
|
private ImmutableList<PairUiEntry> SortVisibleEntries(IEnumerable<PairUiEntry> entries)
|
|
{
|
|
var entryList = entries.ToList();
|
|
return _configService.Current.VisiblePairSortMode switch
|
|
{
|
|
VisiblePairSortMode.VramUsage => SortVisibleByMetric(entryList, e => e.LastAppliedApproximateVramBytes),
|
|
VisiblePairSortMode.EffectiveVramUsage => SortVisibleByMetric(entryList, e => e.LastAppliedApproximateEffectiveVramBytes),
|
|
VisiblePairSortMode.TriangleCount => SortVisibleByMetric(entryList, e => e.LastAppliedDataTris),
|
|
VisiblePairSortMode.Alphabetical => entryList
|
|
.OrderBy(e => AlphabeticalSortKey(e), StringComparer.OrdinalIgnoreCase)
|
|
.ToImmutableList(),
|
|
VisiblePairSortMode.PreferredDirectPairs => SortVisibleByPreferred(entryList),
|
|
_ => SortEntries(entryList),
|
|
};
|
|
}
|
|
|
|
private ImmutableList<PairUiEntry> SortVisibleByMetric(IEnumerable<PairUiEntry> entries, Func<PairUiEntry, long> selector)
|
|
{
|
|
return entries
|
|
.OrderByDescending(entry => selector(entry) >= 0)
|
|
.ThenByDescending(selector)
|
|
.ThenByDescending(entry => entry.IsOnline)
|
|
.ThenBy(entry => AlphabeticalSortKey(entry), StringComparer.OrdinalIgnoreCase)
|
|
.ToImmutableList();
|
|
}
|
|
|
|
private ImmutableList<PairUiEntry> SortVisibleByPreferred(IEnumerable<PairUiEntry> entries)
|
|
{
|
|
return entries
|
|
.OrderByDescending(entry => entry.IsDirectlyPaired && entry.SelfPermissions.IsSticky())
|
|
.ThenByDescending(entry => entry.IsDirectlyPaired)
|
|
.ThenByDescending(entry => entry.IsOnline)
|
|
.ThenBy(entry => AlphabeticalSortKey(entry), StringComparer.OrdinalIgnoreCase)
|
|
.ToImmutableList();
|
|
}
|
|
|
|
private ImmutableList<PairUiEntry> SortGroupEntries(IEnumerable<PairUiEntry> entries, GroupFullInfoDto group)
|
|
{
|
|
return entries
|
|
.OrderByDescending(e => e.IsOnline)
|
|
.ThenBy(e => GroupSortWeight(e, group))
|
|
.ThenBy(e => AlphabeticalSortKey(e), StringComparer.OrdinalIgnoreCase)
|
|
.ToImmutableList();
|
|
}
|
|
|
|
private int GroupSortWeight(PairUiEntry entry, GroupFullInfoDto group)
|
|
{
|
|
if (string.Equals(entry.DisplayEntry.Ident.UserId, group.OwnerUID, StringComparison.Ordinal))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (group.GroupPairUserInfos.TryGetValue(entry.DisplayEntry.Ident.UserId, out var info))
|
|
{
|
|
if (info.IsModerator()) return 1;
|
|
if (info.IsPinned()) return 2;
|
|
}
|
|
|
|
return entry.IsVisible ? 3 : 4;
|
|
}
|
|
|
|
private ImmutableList<PairUiEntry> ResolveGroupEntries(
|
|
IReadOnlyDictionary<string, PairUiEntry> entryLookup,
|
|
IReadOnlyDictionary<string, Syncshell> syncshells,
|
|
GroupFullInfoDto group,
|
|
bool applyFilters)
|
|
{
|
|
if (!syncshells.TryGetValue(group.Group.GID, out var shell))
|
|
{
|
|
return ImmutableList<PairUiEntry>.Empty;
|
|
}
|
|
|
|
var entries = shell.Users.Keys
|
|
.Select(id => entryLookup.TryGetValue(id, out var entry) ? entry : null)
|
|
.Where(entry => entry is not null)
|
|
.Cast<PairUiEntry>();
|
|
|
|
if (applyFilters && _configService.Current.ShowOfflineUsersSeparately)
|
|
{
|
|
entries = entries.Where(entry => !FilterOfflineUsers(entry));
|
|
}
|
|
|
|
if (applyFilters && _configService.Current.ShowSyncshellOfflineUsersSeparately)
|
|
{
|
|
entries = entries.Where(entry => !FilterOfflineSyncshellUsers(entry));
|
|
}
|
|
|
|
return SortGroupEntries(entries, group);
|
|
}
|
|
|
|
private string GetServerError()
|
|
{
|
|
return _apiController.ServerState switch
|
|
{
|
|
ServerState.Connecting => "Attempting to connect to the server.",
|
|
ServerState.Reconnecting => "Connection to server interrupted, attempting to reconnect to the server.",
|
|
ServerState.Disconnected => "You are currently disconnected from the Lightless Sync server.",
|
|
ServerState.Disconnecting => "Disconnecting from the server",
|
|
ServerState.Unauthorized => "Server Response: " + _apiController.AuthFailureMessage,
|
|
ServerState.Offline => "Your selected Lightless Sync server is currently offline.",
|
|
ServerState.VersionMisMatch =>
|
|
"Your plugin or the server you are connecting to is out of date. Please update your plugin now. If you already did so, contact the server provider to update their server to the latest version.",
|
|
ServerState.RateLimited => "You are rate limited for (re)connecting too often. Disconnect, wait 10 minutes and try again.",
|
|
ServerState.Connected => string.Empty,
|
|
ServerState.NoSecretKey => "You have no secret key set for this current character. Open Settings -> Service Settings and set a secret key for the current character. You can reuse the same secret key for multiple characters.",
|
|
ServerState.MultiChara => "Your Character Configuration has multiple characters configured with same name and world. You will not be able to connect until you fix this issue. Remove the duplicates from the configuration in Settings -> Service Settings -> Character Management and reconnect manually after.",
|
|
ServerState.OAuthMisconfigured => "OAuth2 is enabled but not fully configured, verify in the Settings -> Service Settings that you have OAuth2 connected and, importantly, a UID assigned to your current character.",
|
|
ServerState.OAuthLoginTokenStale => "Your OAuth2 login token is stale and cannot be used to renew. Go to the Settings -> Service Settings and unlink then relink your OAuth2 configuration.",
|
|
ServerState.NoAutoLogon => "This character has automatic login into Lightless disabled. Press the connect button to connect to Lightless.",
|
|
_ => string.Empty
|
|
};
|
|
}
|
|
|
|
private Vector4 GetUidColor()
|
|
{
|
|
return _apiController.ServerState switch
|
|
{
|
|
ServerState.Connecting => UIColors.Get("LightlessYellow"),
|
|
ServerState.Reconnecting => UIColors.Get("DimRed"),
|
|
ServerState.Connected => UIColors.Get("LightlessPurple"),
|
|
ServerState.Disconnected => UIColors.Get("LightlessYellow"),
|
|
ServerState.Disconnecting => UIColors.Get("LightlessYellow"),
|
|
ServerState.Unauthorized => UIColors.Get("DimRed"),
|
|
ServerState.VersionMisMatch => UIColors.Get("DimRed"),
|
|
ServerState.Offline => UIColors.Get("DimRed"),
|
|
ServerState.RateLimited => UIColors.Get("LightlessYellow"),
|
|
ServerState.NoSecretKey => UIColors.Get("LightlessYellow"),
|
|
ServerState.MultiChara => UIColors.Get("LightlessYellow"),
|
|
ServerState.OAuthMisconfigured => UIColors.Get("DimRed"),
|
|
ServerState.OAuthLoginTokenStale => UIColors.Get("DimRed"),
|
|
ServerState.NoAutoLogon => UIColors.Get("LightlessYellow"),
|
|
_ => UIColors.Get("DimRed")
|
|
};
|
|
}
|
|
|
|
private string GetUidText()
|
|
{
|
|
return _apiController.ServerState switch
|
|
{
|
|
ServerState.Reconnecting => "Reconnecting",
|
|
ServerState.Connecting => "Connecting",
|
|
ServerState.Disconnected => "Disconnected",
|
|
ServerState.Disconnecting => "Disconnecting",
|
|
ServerState.Unauthorized => "Unauthorized",
|
|
ServerState.VersionMisMatch => "Version mismatch",
|
|
ServerState.Offline => "Unavailable",
|
|
ServerState.RateLimited => "Rate Limited",
|
|
ServerState.NoSecretKey => "No Secret Key",
|
|
ServerState.MultiChara => "Duplicate Characters",
|
|
ServerState.OAuthMisconfigured => "Misconfigured OAuth2",
|
|
ServerState.OAuthLoginTokenStale => "Stale OAuth2",
|
|
ServerState.NoAutoLogon => "Auto Login disabled",
|
|
ServerState.Connected => _apiController.DisplayName,
|
|
_ => string.Empty
|
|
};
|
|
}
|
|
|
|
private void UiSharedService_GposeEnd()
|
|
{
|
|
IsOpen = _wasOpen;
|
|
}
|
|
|
|
private void UiSharedService_GposeStart()
|
|
{
|
|
_wasOpen = IsOpen;
|
|
IsOpen = false;
|
|
}
|
|
}
|