487 lines
21 KiB
C#
487 lines
21 KiB
C#
using Dalamud.Bindings.ImGui;
|
||
using Dalamud.Interface;
|
||
using Dalamud.Interface.Colors;
|
||
using Dalamud.Interface.Utility;
|
||
using Dalamud.Utility;
|
||
using LightlessSync.API.Data.Extensions;
|
||
using LightlessSync.API.Dto.Group;
|
||
using LightlessSync.LightlessConfiguration;
|
||
using LightlessSync.Services;
|
||
using LightlessSync.Services.LightFinder;
|
||
using LightlessSync.Services.Mediator;
|
||
using LightlessSync.Utils;
|
||
using LightlessSync.WebAPI;
|
||
using Microsoft.Extensions.Logging;
|
||
using System.Numerics;
|
||
|
||
namespace LightlessSync.UI
|
||
{
|
||
public class LightFinderUI : WindowMediatorSubscriberBase
|
||
{
|
||
private readonly ApiController _apiController;
|
||
private readonly LightlessConfigService _configService;
|
||
private readonly LightFinderService _broadcastService;
|
||
private readonly UiSharedService _uiSharedService;
|
||
private readonly LightFinderScannerService _broadcastScannerService;
|
||
private readonly LightFinderPlateHandler _lightFinderPlateHandler;
|
||
|
||
private IReadOnlyList<GroupFullInfoDto> _allSyncshells = Array.Empty<GroupFullInfoDto>();
|
||
private string _userUid = string.Empty;
|
||
|
||
private readonly List<(string Label, string? GID, bool IsAvailable)> _syncshellOptions = new();
|
||
|
||
public LightFinderUI(
|
||
ILogger<LightFinderUI> logger,
|
||
LightlessMediator mediator,
|
||
PerformanceCollectorService performanceCollectorService,
|
||
LightFinderService broadcastService,
|
||
LightlessConfigService configService,
|
||
UiSharedService uiShared,
|
||
ApiController apiController,
|
||
LightFinderScannerService broadcastScannerService
|
||
,
|
||
LightFinderPlateHandler lightFinderPlateHandler) : base(logger, mediator, "Lightfinder###LightlessLightfinderUI", performanceCollectorService)
|
||
{
|
||
_broadcastService = broadcastService;
|
||
_uiSharedService = uiShared;
|
||
_configService = configService;
|
||
_apiController = apiController;
|
||
_broadcastScannerService = broadcastScannerService;
|
||
|
||
IsOpen = false;
|
||
WindowBuilder.For(this)
|
||
.SetSizeConstraints(new Vector2(600, 465), new Vector2(750, 525))
|
||
.Apply();
|
||
_lightFinderPlateHandler = lightFinderPlateHandler;
|
||
}
|
||
|
||
private void RebuildSyncshellDropdownOptions()
|
||
{
|
||
var selectedGid = _configService.Current.SelectedFinderSyncshell;
|
||
var allSyncshells = _allSyncshells ?? [];
|
||
var filteredSyncshells = allSyncshells
|
||
.Where(g => string.Equals(g.OwnerUID, _userUid, StringComparison.Ordinal) || g.GroupUserInfo.IsModerator())
|
||
.ToList();
|
||
|
||
_syncshellOptions.Clear();
|
||
_syncshellOptions.Add(("None", null, true));
|
||
|
||
var addedGids = new HashSet<string>(StringComparer.Ordinal);
|
||
|
||
foreach (var shell in filteredSyncshells)
|
||
{
|
||
var label = shell.GroupAliasOrGID ?? shell.GID;
|
||
_syncshellOptions.Add((label, shell.GID, true));
|
||
addedGids.Add(shell.GID);
|
||
}
|
||
|
||
if (!string.IsNullOrEmpty(selectedGid) && !addedGids.Contains(selectedGid))
|
||
{
|
||
var matching = allSyncshells.FirstOrDefault(g => string.Equals(g.GID, selectedGid, StringComparison.Ordinal));
|
||
if (matching != null)
|
||
{
|
||
var label = matching.GroupAliasOrGID ?? matching.GID;
|
||
_syncshellOptions.Add((label, matching.GID, true));
|
||
addedGids.Add(matching.GID);
|
||
}
|
||
}
|
||
|
||
if (!string.IsNullOrEmpty(selectedGid) && !addedGids.Contains(selectedGid))
|
||
{
|
||
_syncshellOptions.Add(($"[Unavailable] {selectedGid}", selectedGid, false));
|
||
}
|
||
}
|
||
|
||
public Task RefreshSyncshells()
|
||
{
|
||
return RefreshSyncshellsInternal();
|
||
}
|
||
|
||
private async Task RefreshSyncshellsInternal()
|
||
{
|
||
if (!_apiController.IsConnected)
|
||
{
|
||
_allSyncshells = [];
|
||
RebuildSyncshellDropdownOptions();
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
_allSyncshells = await _apiController.GroupsGetAll().ConfigureAwait(false);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Failed to fetch Syncshells.");
|
||
_allSyncshells = [];
|
||
}
|
||
|
||
RebuildSyncshellDropdownOptions();
|
||
}
|
||
|
||
|
||
public override void OnOpen()
|
||
{
|
||
_userUid = _apiController.UID;
|
||
_ = RefreshSyncshells();
|
||
}
|
||
|
||
protected override void DrawInternal()
|
||
{
|
||
if (!_broadcastService.IsLightFinderAvailable)
|
||
{
|
||
_uiSharedService.MediumText("This server doesn't support Lightfinder.", UIColors.Get("LightlessYellow"));
|
||
|
||
ImGuiHelpers.ScaledDummy(0.25f);
|
||
}
|
||
|
||
if (ImGui.BeginTabBar("##BroadcastTabs"))
|
||
{
|
||
if (ImGui.BeginTabItem("Lightfinder"))
|
||
{
|
||
_uiSharedService.MediumText("Lightfinder", UIColors.Get("PairBlue"));
|
||
|
||
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(1, -2));
|
||
|
||
_uiSharedService.DrawNoteLine("# ", UIColors.Get("LightlessPurple"),"This lets other Lightless users know you use Lightless. While enabled, you and others using Lightfinder can see each other identified as Lightless users.");
|
||
|
||
ImGui.Indent(15f);
|
||
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey);
|
||
ImGui.Text("- This is done using a 'Lightless' label above player nameplates.");
|
||
ImGui.PopStyleColor();
|
||
ImGui.Unindent(15f);
|
||
|
||
ImGuiHelpers.ScaledDummy(3f);
|
||
|
||
_uiSharedService.MediumText("Pairing", UIColors.Get("PairBlue"));
|
||
_uiSharedService.DrawNoteLine("# ", UIColors.Get("LightlessPurple"), "Pairing may be initiated via the right-click context menu on another player." +
|
||
" The process requires mutual confirmation: the sender initiates the request, and the recipient completes it by responding with a request in return.");
|
||
|
||
_uiSharedService.DrawNoteLine(
|
||
"! ",
|
||
UIColors.Get("LightlessYellow"),
|
||
new SeStringUtils.RichTextEntry("If Lightfinder is "),
|
||
new SeStringUtils.RichTextEntry("ENABLED", UIColors.Get("LightlessGreen"), true),
|
||
new SeStringUtils.RichTextEntry(" when a pair request is made, the receiving user will get notified about it."));
|
||
|
||
_uiSharedService.DrawNoteLine(
|
||
"! ",
|
||
UIColors.Get("LightlessYellow"),
|
||
new SeStringUtils.RichTextEntry("If Lightfinder is "),
|
||
new SeStringUtils.RichTextEntry("DISABLED", UIColors.Get("DimRed"), true),
|
||
new SeStringUtils.RichTextEntry(" when a pair request is made, the receiving user will "),
|
||
new SeStringUtils.RichTextEntry("NOT", UIColors.Get("DimRed"), true),
|
||
new SeStringUtils.RichTextEntry(" get a notification, and the request will not be visible to them in any way."));
|
||
|
||
ImGuiHelpers.ScaledDummy(3f);
|
||
|
||
_uiSharedService.MediumText("Privacy", UIColors.Get("PairBlue"));
|
||
|
||
_uiSharedService.DrawNoteLine(
|
||
"! ",
|
||
UIColors.Get("DimRed"),
|
||
new SeStringUtils.RichTextEntry("Lightfinder is entirely "),
|
||
new SeStringUtils.RichTextEntry("opt-in", UIColors.Get("LightlessYellow"), true),
|
||
new SeStringUtils.RichTextEntry(" and does not share any data with other users. All identifying information remains private to the server."));
|
||
|
||
_uiSharedService.DrawNoteLine("! ", UIColors.Get("DimRed"), "Pairing is intended as a mutual agreement between both parties. A pair request will not be visible to the recipient unless Lightfinder is enabled.");
|
||
|
||
ImGuiHelpers.ScaledDummy(5f);
|
||
|
||
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();
|
||
|
||
ImGui.PopStyleVar();
|
||
|
||
ImGuiHelpers.ScaledDummy(3f);
|
||
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();
|
||
}
|
||
|
||
ImGuiHelpers.ScaledDummy(0.5f);
|
||
|
||
bool isBroadcasting = _broadcastService.IsBroadcasting;
|
||
bool isOnCooldown = cooldown.HasValue && cooldown.Value.TotalSeconds > 0;
|
||
|
||
ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 10.0f);
|
||
|
||
if (isOnCooldown)
|
||
ImGui.PushStyleColor(ImGuiCol.Button, UIColors.Get("DimRed"));
|
||
else if (isBroadcasting)
|
||
ImGui.PushStyleColor(ImGuiCol.Button, UIColors.Get("LightlessGreen"));
|
||
else
|
||
ImGui.PushStyleColor(ImGuiCol.Button, UIColors.Get("PairBlue"));
|
||
|
||
if (isOnCooldown || !_broadcastService.IsLightFinderAvailable)
|
||
ImGui.BeginDisabled();
|
||
|
||
string buttonText = isBroadcasting ? "Disable Lightfinder" : "Enable Lightfinder";
|
||
|
||
if (ImGui.Button(buttonText, new Vector2(200 * ImGuiHelpers.GlobalScale, 0)))
|
||
{
|
||
_broadcastService.ToggleBroadcast();
|
||
}
|
||
|
||
var toggleButtonHeight = ImGui.GetItemRectSize().Y;
|
||
|
||
if (isOnCooldown || !_broadcastService.IsLightFinderAvailable)
|
||
ImGui.EndDisabled();
|
||
|
||
ImGui.PopStyleColor();
|
||
ImGui.PopStyleVar();
|
||
|
||
ImGui.SameLine();
|
||
if (_uiSharedService.IconButton(FontAwesomeIcon.Cog, toggleButtonHeight))
|
||
{
|
||
Mediator.Publish(new OpenLightfinderSettingsMessage());
|
||
}
|
||
|
||
if (ImGui.IsItemHovered())
|
||
{
|
||
ImGui.BeginTooltip();
|
||
ImGui.TextUnformatted("Open Lightfinder settings.");
|
||
ImGui.EndTooltip();
|
||
}
|
||
|
||
ImGui.EndTabItem();
|
||
}
|
||
|
||
if (ImGui.BeginTabItem("Syncshell Finder"))
|
||
{
|
||
if (_allSyncshells == null)
|
||
{
|
||
ImGui.Text("Loading Syncshells...");
|
||
_ = RefreshSyncshells();
|
||
return;
|
||
}
|
||
|
||
_uiSharedService.MediumText("Syncshell Finder", UIColors.Get("PairBlue"));
|
||
|
||
UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 2f);
|
||
|
||
ImGui.PushTextWrapPos();
|
||
ImGui.Text("Allow your owned Syncshell to be indexed by the Nearby Syncshell Finder.");
|
||
ImGui.Text("To enable this, select one of your owned Syncshells from the dropdown menu below and ensure that \"Toggle Syncshell Finder\" is enabled. Your Syncshell will be visible in the Nearby Syncshell Finder as long as Lightfinder is active.");
|
||
ImGui.PopTextWrapPos();
|
||
|
||
ImGuiHelpers.ScaledDummy(0.2f);
|
||
UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 2f);
|
||
|
||
bool ShellFinderEnabled = _configService.Current.SyncshellFinderEnabled;
|
||
bool isBroadcasting = _broadcastService.IsBroadcasting;
|
||
|
||
if (isBroadcasting)
|
||
{
|
||
var warningColor = UIColors.Get("LightlessYellow");
|
||
_uiSharedService.DrawNoteLine("! ", warningColor,
|
||
new SeStringUtils.RichTextEntry("Syncshell Finder can only be changed while Lightfinder is disabled.", warningColor));
|
||
ImGuiHelpers.ScaledDummy(0.2f);
|
||
}
|
||
|
||
if (isBroadcasting)
|
||
ImGui.BeginDisabled();
|
||
|
||
if (ImGui.Checkbox("Toggle Syncshell Finder", ref ShellFinderEnabled))
|
||
{
|
||
_configService.Current.SyncshellFinderEnabled = ShellFinderEnabled;
|
||
_configService.Save();
|
||
}
|
||
|
||
if (ImGui.IsItemHovered())
|
||
{
|
||
ImGui.BeginTooltip();
|
||
ImGui.Text("Toggle to broadcast specified Syncshell.");
|
||
ImGui.EndTooltip();
|
||
}
|
||
|
||
var selectedGid = _configService.Current.SelectedFinderSyncshell;
|
||
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 = string.Equals(gid, selectedGid, StringComparison.Ordinal);
|
||
|
||
if (!available)
|
||
ImGui.PushStyleColor(ImGuiCol.Text, UIColors.Get("DimRed"));
|
||
|
||
if (ImGui.Selectable(label, isSelected))
|
||
{
|
||
_configService.Current.SelectedFinderSyncshell = gid;
|
||
_configService.Save();
|
||
_ = RefreshSyncshells();
|
||
}
|
||
|
||
if (!available && ImGui.IsItemHovered())
|
||
{
|
||
ImGui.BeginTooltip();
|
||
ImGui.Text("This Syncshell is not available on the current service.");
|
||
ImGui.EndTooltip();
|
||
}
|
||
|
||
if (!available)
|
||
ImGui.PopStyleColor();
|
||
|
||
if (isSelected)
|
||
ImGui.SetItemDefaultFocus();
|
||
}
|
||
|
||
ImGui.EndCombo();
|
||
}
|
||
|
||
|
||
if (ImGui.IsItemHovered())
|
||
{
|
||
ImGui.BeginTooltip();
|
||
ImGui.Text("Choose one of the available options.");
|
||
ImGui.EndTooltip();
|
||
}
|
||
|
||
|
||
if (isBroadcasting)
|
||
ImGui.EndDisabled();
|
||
|
||
ImGui.EndTabItem();
|
||
}
|
||
|
||
#if DEBUG
|
||
if (ImGui.BeginTabItem("Debug"))
|
||
{
|
||
if (ImGui.CollapsingHeader("LightFinder Plates", ImGuiTreeNodeFlags.DefaultOpen))
|
||
{
|
||
var h = _lightFinderPlateHandler;
|
||
|
||
var enabled = h.DebugEnabled;
|
||
if (ImGui.Checkbox("Enable LightFinder debug", ref enabled))
|
||
h.DebugEnabled = enabled;
|
||
|
||
if (h.DebugEnabled)
|
||
{
|
||
ImGui.Indent();
|
||
|
||
var disableOcc = h.DebugDisableOcclusion;
|
||
if (ImGui.Checkbox("Disable occlusion (force draw)", ref disableOcc))
|
||
h.DebugDisableOcclusion = disableOcc;
|
||
|
||
var drawUiRects = h.DebugDrawUiRects;
|
||
if (ImGui.Checkbox("Draw UI rects", ref drawUiRects))
|
||
h.DebugDrawUiRects = drawUiRects;
|
||
|
||
var drawLabelRects = h.DebugDrawLabelRects;
|
||
if (ImGui.Checkbox("Draw label rects", ref drawLabelRects))
|
||
h.DebugDrawLabelRects = drawLabelRects;
|
||
|
||
ImGui.Separator();
|
||
ImGui.TextUnformatted($"Labels last frame: {h.DebugLabelCountLastFrame}");
|
||
ImGui.TextUnformatted($"UI rects last frame: {h.DebugUiRectCountLastFrame}");
|
||
ImGui.TextUnformatted($"Occluded last frame: {h.DebugOccludedCountLastFrame}");
|
||
ImGui.TextUnformatted($"Last NamePlate frame: {h.DebugLastNameplateFrame}");
|
||
|
||
ImGui.Unindent();
|
||
}
|
||
}
|
||
|
||
ImGui.Separator();
|
||
|
||
ImGui.Text("Broadcast Cache");
|
||
|
||
if (ImGui.BeginTable("##BroadcastCacheTable", 4,
|
||
ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders | ImGuiTableFlags.ScrollY,
|
||
new Vector2(-1, 225f)))
|
||
{
|
||
ImGui.TableSetupColumn("CID", ImGuiTableColumnFlags.WidthStretch);
|
||
ImGui.TableSetupColumn("IsBroadcasting", ImGuiTableColumnFlags.WidthStretch);
|
||
ImGui.TableSetupColumn("Expires In", ImGuiTableColumnFlags.WidthStretch);
|
||
ImGui.TableSetupColumn("Syncshell GID", ImGuiTableColumnFlags.WidthStretch);
|
||
ImGui.TableHeadersRow();
|
||
|
||
var now = DateTime.UtcNow;
|
||
|
||
foreach (var (cid, entry) in _broadcastScannerService.BroadcastCache)
|
||
{
|
||
ImGui.TableNextRow();
|
||
|
||
ImGui.TableNextColumn();
|
||
ImGui.TextUnformatted(cid.Truncate(12));
|
||
if (ImGui.IsItemHovered())
|
||
{
|
||
ImGui.BeginTooltip();
|
||
ImGui.TextUnformatted(cid);
|
||
ImGui.EndTooltip();
|
||
}
|
||
|
||
ImGui.TableNextColumn();
|
||
var colorBroadcast = entry.IsBroadcasting
|
||
? UIColors.Get("LightlessGreen")
|
||
: UIColors.Get("DimRed");
|
||
|
||
ImGui.TableSetBgColor(ImGuiTableBgTarget.CellBg, ImGui.GetColorU32(colorBroadcast));
|
||
ImGui.TextUnformatted(entry.IsBroadcasting.ToString());
|
||
|
||
ImGui.TableNextColumn();
|
||
var remaining = entry.ExpiryTime - now;
|
||
var colorTtl =
|
||
remaining <= TimeSpan.Zero ? UIColors.Get("DimRed") :
|
||
remaining < TimeSpan.FromSeconds(10) ? UIColors.Get("LightlessYellow") :
|
||
(Vector4?)null;
|
||
|
||
if (colorTtl != null)
|
||
ImGui.TableSetBgColor(ImGuiTableBgTarget.CellBg, ImGui.GetColorU32(colorTtl.Value));
|
||
|
||
ImGui.TextUnformatted(remaining > TimeSpan.Zero
|
||
? remaining.ToString("hh\\:mm\\:ss")
|
||
: "Expired");
|
||
|
||
ImGui.TableNextColumn();
|
||
ImGui.TextUnformatted(entry.GID ?? "-");
|
||
}
|
||
|
||
ImGui.EndTable();
|
||
}
|
||
|
||
ImGui.EndTabItem();
|
||
}
|
||
#endif
|
||
|
||
ImGui.EndTabBar();
|
||
}
|
||
}
|
||
}
|
||
}
|