- use IPlayerState for DalamudUtilService and make things less async - make LocationInfo work with ContentFinderData Co-authored-by: Tsubasahane <wozaiha@gmail.com> Co-authored-by: defnotken <itsdefnotken@gmail.com> Reviewed-on: #113 Reviewed-by: cake <cake@noreply.git.lightless-sync.org> Co-authored-by: Tsubasa <tsubasa@noreply.git.lightless-sync.org> Co-committed-by: Tsubasa <tsubasa@noreply.git.lightless-sync.org>
936 lines
40 KiB
C#
936 lines
40 KiB
C#
using Dalamud.Bindings.ImGui;
|
|
using Dalamud.Interface;
|
|
using Dalamud.Interface.Utility;
|
|
using Dalamud.Interface.Utility.Raii;
|
|
using Dalamud.Plugin.Services;
|
|
using Dalamud.Utility;
|
|
using LightlessSync.API.Data.Enum;
|
|
using LightlessSync.API.Data.Extensions;
|
|
using LightlessSync.LightlessConfiguration.Models;
|
|
using LightlessSync.Services;
|
|
using LightlessSync.Services.LightFinder;
|
|
using LightlessSync.Services.Mediator;
|
|
using LightlessSync.UI.Models;
|
|
using LightlessSync.UI.Style;
|
|
using LightlessSync.Utils;
|
|
using LightlessSync.WebAPI;
|
|
using System;
|
|
using System.Numerics;
|
|
|
|
namespace LightlessSync.UI;
|
|
|
|
public class TopTabMenu
|
|
{
|
|
private readonly ApiController _apiController;
|
|
|
|
private readonly LightlessMediator _lightlessMediator;
|
|
|
|
private readonly PairRequestService _pairRequestService;
|
|
private readonly DalamudUtilService _dalamudUtilService;
|
|
private readonly LightFinderService _lightFinderService;
|
|
private readonly LightFinderScannerService _lightFinderScannerService;
|
|
private readonly HashSet<string> _pendingPairRequestActions = new(StringComparer.Ordinal);
|
|
private bool _pairRequestsExpanded; // useless for now
|
|
private int _lastRequestCount;
|
|
private readonly UiSharedService _uiSharedService;
|
|
private readonly NotificationService _lightlessNotificationService;
|
|
private string _filter = string.Empty;
|
|
private int _globalControlCountdown = 0;
|
|
private float _pairRequestsHeight = 150f;
|
|
private string _pairToAdd = string.Empty;
|
|
|
|
private SelectedTab _selectedTab = SelectedTab.None;
|
|
private PairUiSnapshot? _currentSnapshot;
|
|
|
|
public TopTabMenu(LightlessMediator lightlessMediator, ApiController apiController, UiSharedService uiSharedService, PairRequestService pairRequestService, DalamudUtilService dalamudUtilService, NotificationService lightlessNotificationService, LightFinderService lightFinderService, LightFinderScannerService lightFinderScannerService)
|
|
{
|
|
_lightlessMediator = lightlessMediator;
|
|
_apiController = apiController;
|
|
_pairRequestService = pairRequestService;
|
|
_dalamudUtilService = dalamudUtilService;
|
|
_uiSharedService = uiSharedService;
|
|
_lightlessNotificationService = lightlessNotificationService;
|
|
_lightFinderService = lightFinderService;
|
|
_lightFinderScannerService = lightFinderScannerService;
|
|
}
|
|
|
|
private enum SelectedTab
|
|
{
|
|
None,
|
|
Individual,
|
|
Syncshell,
|
|
Lightfinder,
|
|
UserConfig,
|
|
Settings
|
|
}
|
|
|
|
public string Filter
|
|
{
|
|
get => _filter;
|
|
private set
|
|
{
|
|
if (!string.Equals(_filter, value, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
_lightlessMediator.Publish(new RefreshUiMessage());
|
|
}
|
|
|
|
_filter = value;
|
|
}
|
|
}
|
|
private SelectedTab TabSelection
|
|
{
|
|
get => _selectedTab; set
|
|
{
|
|
_selectedTab = value;
|
|
}
|
|
}
|
|
|
|
private PairUiSnapshot Snapshot => _currentSnapshot ?? throw new InvalidOperationException("Pair UI snapshot is not available outside of Draw.");
|
|
|
|
public void Draw(PairUiSnapshot snapshot)
|
|
{
|
|
_currentSnapshot = snapshot;
|
|
try
|
|
{
|
|
var availableWidth = ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X;
|
|
var spacing = ImGui.GetStyle().ItemSpacing;
|
|
var buttonX = (availableWidth - (spacing.X * 5)) / 6f;
|
|
var buttonY = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Pause).Y;
|
|
var buttonSize = new Vector2(buttonX, buttonY);
|
|
const float buttonBorderThickness = 12f;
|
|
var buttonRounding = ImGui.GetStyle().FrameRounding;
|
|
var drawList = ImGui.GetWindowDrawList();
|
|
var underlineColor = ImGui.GetColorU32(UIColors.Get("LightlessPurpleActive")); // ImGui.GetColorU32(ImGuiCol.Separator);
|
|
var btncolor = ImRaii.PushColor(ImGuiCol.Button, ImGui.ColorConvertFloat4ToU32(new(0, 0, 0, 0)));
|
|
|
|
ImGuiHelpers.ScaledDummy(spacing.Y / 2f);
|
|
|
|
using (ImRaii.PushFont(UiBuilder.IconFont))
|
|
{
|
|
var x = ImGui.GetCursorScreenPos();
|
|
if (ImGui.Button(FontAwesomeIcon.User.ToIconString(), buttonSize))
|
|
{
|
|
TabSelection = TabSelection == SelectedTab.Individual ? SelectedTab.None : SelectedTab.Individual;
|
|
}
|
|
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenBlockedByActiveItem) || ImGui.IsItemActive())
|
|
{
|
|
Selune.RegisterHighlight(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), SeluneHighlightMode.Both, true, buttonBorderThickness, exactSize: true, clipToElement: true, roundingOverride: buttonRounding);
|
|
}
|
|
ImGui.SameLine();
|
|
var xAfter = ImGui.GetCursorScreenPos();
|
|
if (TabSelection == SelectedTab.Individual)
|
|
drawList.AddLine(x with { Y = x.Y + buttonSize.Y + spacing.Y },
|
|
xAfter with { Y = xAfter.Y + buttonSize.Y + spacing.Y, X = xAfter.X - spacing.X },
|
|
underlineColor, 2);
|
|
}
|
|
UiSharedService.AttachToolTip("Individual Pair Menu");
|
|
|
|
using (ImRaii.PushFont(UiBuilder.IconFont))
|
|
{
|
|
var x = ImGui.GetCursorScreenPos();
|
|
if (ImGui.Button(FontAwesomeIcon.Users.ToIconString(), buttonSize))
|
|
{
|
|
TabSelection = TabSelection == SelectedTab.Syncshell ? SelectedTab.None : SelectedTab.Syncshell;
|
|
}
|
|
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenBlockedByActiveItem) || ImGui.IsItemActive())
|
|
{
|
|
Selune.RegisterHighlight(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), SeluneHighlightMode.Both, true, buttonBorderThickness, exactSize: true, clipToElement: true, roundingOverride: buttonRounding);
|
|
}
|
|
ImGui.SameLine();
|
|
var xAfter = ImGui.GetCursorScreenPos();
|
|
if (TabSelection == SelectedTab.Syncshell)
|
|
drawList.AddLine(x with { Y = x.Y + buttonSize.Y + spacing.Y },
|
|
xAfter with { Y = xAfter.Y + buttonSize.Y + spacing.Y, X = xAfter.X - spacing.X },
|
|
underlineColor, 2);
|
|
}
|
|
UiSharedService.AttachToolTip("Syncshell Menu");
|
|
|
|
using (ImRaii.PushFont(UiBuilder.IconFont))
|
|
{
|
|
if (ImGui.Button(FontAwesomeIcon.Comments.ToIconString(), buttonSize))
|
|
{
|
|
_lightlessMediator.Publish(new UiToggleMessage(typeof(ZoneChatUi)));
|
|
}
|
|
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenBlockedByActiveItem) || ImGui.IsItemActive())
|
|
{
|
|
Selune.RegisterHighlight(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), SeluneHighlightMode.Both, true, buttonBorderThickness, exactSize: true, clipToElement: true, roundingOverride: buttonRounding);
|
|
}
|
|
}
|
|
UiSharedService.AttachToolTip("Lightless Chat");
|
|
ImGui.SameLine();
|
|
|
|
ImGui.SameLine();
|
|
using (ImRaii.PushFont(UiBuilder.IconFont))
|
|
{
|
|
var x = ImGui.GetCursorScreenPos();
|
|
if (ImGui.Button(FontAwesomeIcon.Compass.ToIconString(), buttonSize))
|
|
{
|
|
TabSelection = TabSelection == SelectedTab.Lightfinder ? SelectedTab.None : SelectedTab.Lightfinder;
|
|
}
|
|
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenBlockedByActiveItem) || ImGui.IsItemActive())
|
|
{
|
|
Selune.RegisterHighlight(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), SeluneHighlightMode.Both, true, buttonBorderThickness, exactSize: true, clipToElement: true, roundingOverride: buttonRounding);
|
|
}
|
|
|
|
ImGui.SameLine();
|
|
var xAfter = ImGui.GetCursorScreenPos();
|
|
if (TabSelection == SelectedTab.Lightfinder)
|
|
drawList.AddLine(x with { Y = x.Y + buttonSize.Y + spacing.Y },
|
|
xAfter with { Y = xAfter.Y + buttonSize.Y + spacing.Y, X = xAfter.X - spacing.X },
|
|
underlineColor, 2);
|
|
}
|
|
UiSharedService.AttachToolTip("Lightfinder");
|
|
|
|
ImGui.SameLine();
|
|
using (ImRaii.PushFont(UiBuilder.IconFont))
|
|
{
|
|
var x = ImGui.GetCursorScreenPos();
|
|
if (ImGui.Button(FontAwesomeIcon.UserCog.ToIconString(), buttonSize))
|
|
{
|
|
TabSelection = TabSelection == SelectedTab.UserConfig ? SelectedTab.None : SelectedTab.UserConfig;
|
|
}
|
|
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenBlockedByActiveItem) || ImGui.IsItemActive())
|
|
{
|
|
Selune.RegisterHighlight(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), SeluneHighlightMode.Both, true, buttonBorderThickness, exactSize: true, clipToElement: true, roundingOverride: buttonRounding);
|
|
}
|
|
|
|
ImGui.SameLine();
|
|
var xAfter = ImGui.GetCursorScreenPos();
|
|
if (TabSelection == SelectedTab.UserConfig)
|
|
drawList.AddLine(x with { Y = x.Y + buttonSize.Y + spacing.Y },
|
|
xAfter with { Y = xAfter.Y + buttonSize.Y + spacing.Y, X = xAfter.X - spacing.X },
|
|
underlineColor, 2);
|
|
}
|
|
UiSharedService.AttachToolTip("Your User Menu");
|
|
|
|
ImGui.SameLine();
|
|
using (ImRaii.PushFont(UiBuilder.IconFont))
|
|
{
|
|
var x = ImGui.GetCursorScreenPos();
|
|
if (ImGui.Button(FontAwesomeIcon.Cog.ToIconString(), buttonSize))
|
|
{
|
|
_lightlessMediator.Publish(new UiToggleMessage(typeof(SettingsUi)));
|
|
}
|
|
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenBlockedByActiveItem) || ImGui.IsItemActive())
|
|
{
|
|
Selune.RegisterHighlight(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), SeluneHighlightMode.Both, true, buttonBorderThickness, exactSize: true, clipToElement: true, roundingOverride: buttonRounding);
|
|
}
|
|
ImGui.SameLine();
|
|
}
|
|
UiSharedService.AttachToolTip("Open Lightless Settings");
|
|
|
|
ImGui.NewLine();
|
|
btncolor.Dispose();
|
|
|
|
ImGuiHelpers.ScaledDummy(spacing);
|
|
|
|
if (TabSelection == SelectedTab.Individual)
|
|
{
|
|
DrawAddPair(availableWidth, spacing.X);
|
|
DrawGlobalIndividualButtons(availableWidth, spacing.X);
|
|
}
|
|
else if (TabSelection == SelectedTab.Syncshell)
|
|
{
|
|
DrawSyncshellMenu(availableWidth, spacing.X);
|
|
DrawGlobalSyncshellButtons(availableWidth, spacing.X);
|
|
}
|
|
else if (TabSelection == SelectedTab.Lightfinder)
|
|
{
|
|
DrawLightfinderMenu(availableWidth, spacing.X);
|
|
}
|
|
else if (TabSelection == SelectedTab.UserConfig)
|
|
{
|
|
DrawUserConfig(availableWidth, spacing.X);
|
|
}
|
|
|
|
if (TabSelection != SelectedTab.None) ImGuiHelpers.ScaledDummy(3f);
|
|
|
|
|
|
DrawIncomingPairRequests(availableWidth);
|
|
|
|
ImGui.Separator();
|
|
|
|
DrawFilter(availableWidth, spacing.X);
|
|
}
|
|
finally
|
|
{
|
|
_currentSnapshot = null;
|
|
}
|
|
}
|
|
|
|
private void DrawAddPair(float availableXWidth, float spacingX)
|
|
{
|
|
var buttonSize = _uiSharedService.GetIconTextButtonSize(FontAwesomeIcon.UserPlus, "Add");
|
|
ImGui.SetNextItemWidth(availableXWidth - buttonSize - spacingX);
|
|
ImGui.InputTextWithHint("##otheruid", "Other players UID/Alias", ref _pairToAdd, 20);
|
|
ImGui.SameLine();
|
|
var alreadyExisting = Snapshot.DirectPairs.Any(p => string.Equals(p.UserData.UID, _pairToAdd, StringComparison.Ordinal) || string.Equals(p.UserData.Alias, _pairToAdd, StringComparison.Ordinal));
|
|
using (ImRaii.Disabled(alreadyExisting || string.IsNullOrEmpty(_pairToAdd)))
|
|
{
|
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.UserPlus, "Add"))
|
|
{
|
|
_ = _apiController.UserAddPair(new(new(_pairToAdd)));
|
|
_pairToAdd = string.Empty;
|
|
}
|
|
}
|
|
UiSharedService.AttachToolTip("Pair with " + (_pairToAdd.IsNullOrEmpty() ? "other user" : _pairToAdd));
|
|
}
|
|
|
|
private void DrawIncomingPairRequests(float availableWidth)
|
|
{
|
|
var requests = _pairRequestService.GetActiveRequests();
|
|
var count = requests.Count;
|
|
if (count == 0)
|
|
{
|
|
_pairRequestsExpanded = false;
|
|
_lastRequestCount = 0;
|
|
return;
|
|
}
|
|
|
|
if (count > _lastRequestCount)
|
|
{
|
|
_pairRequestsExpanded = true;
|
|
}
|
|
_lastRequestCount = count;
|
|
|
|
var label = $"Incoming Pair Requests - {count}##IncomingPairRequestsHeader";
|
|
|
|
using (ImRaii.PushColor(ImGuiCol.Header, UIColors.Get("LightlessPurple")))
|
|
using (ImRaii.PushColor(ImGuiCol.HeaderHovered, UIColors.Get("LightlessPurpleActive")))
|
|
using (ImRaii.PushColor(ImGuiCol.HeaderActive, UIColors.Get("LightlessPurple")))
|
|
{
|
|
bool open = ImGui.CollapsingHeader(label, ImGuiTreeNodeFlags.DefaultOpen);
|
|
_pairRequestsExpanded = open;
|
|
|
|
if (ImGui.IsItemHovered())
|
|
UiSharedService.AttachToolTip("Expand to view incoming pair requests.");
|
|
|
|
if (open)
|
|
{
|
|
var lineHeight = ImGui.GetTextLineHeightWithSpacing();
|
|
//var desiredHeight = Math.Clamp(count * lineHeight * 2f, 130f * ImGuiHelpers.GlobalScale, 185f * ImGuiHelpers.GlobalScale); we use resize bar instead
|
|
|
|
ImGui.SetCursorPosX(ImGui.GetCursorPosX() - 2f);
|
|
|
|
using (ImRaii.PushColor(ImGuiCol.ChildBg, UIColors.Get("LightlessPurple")))
|
|
using (ImRaii.PushStyle(ImGuiStyleVar.ChildRounding, 6f))
|
|
{
|
|
if (ImGui.BeginChild("##IncomingPairRequestsOuter", new Vector2(availableWidth + 5f, _pairRequestsHeight), true))
|
|
{
|
|
var defaultChildBg = ImGui.GetStyle().Colors[(int)ImGuiCol.WindowBg];
|
|
using (ImRaii.PushColor(ImGuiCol.ChildBg, defaultChildBg))
|
|
using (ImRaii.PushStyle(ImGuiStyleVar.ChildRounding, 5f))
|
|
using (ImRaii.PushStyle(ImGuiStyleVar.WindowPadding, new Vector2(6, 6)))
|
|
{
|
|
if (ImGui.BeginChild("##IncomingPairRequestsInner", new Vector2(0, 0), true))
|
|
{
|
|
using (ImRaii.PushColor(ImGuiCol.TableBorderStrong, ImGui.GetStyle().Colors[(int)ImGuiCol.Border]))
|
|
using (ImRaii.PushColor(ImGuiCol.TableBorderLight, ImGui.GetStyle().Colors[(int)ImGuiCol.Border]))
|
|
{
|
|
DrawPairRequestList(requests);
|
|
}
|
|
}
|
|
ImGui.EndChild();
|
|
}
|
|
}
|
|
ImGui.EndChild();
|
|
|
|
ImGui.PushStyleColor(ImGuiCol.Button, UIColors.Get("ButtonDefault"));
|
|
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, UIColors.Get("LightlessPurple"));
|
|
ImGui.PushStyleColor(ImGuiCol.ButtonActive, UIColors.Get("LightlessPurpleActive"));
|
|
ImGui.Button("##resizeHandle", new Vector2(availableWidth, 4f));
|
|
ImGui.PopStyleColor(3);
|
|
|
|
if (ImGui.IsItemActive())
|
|
{
|
|
_pairRequestsHeight += ImGui.GetIO().MouseDelta.Y;
|
|
_pairRequestsHeight = Math.Clamp(_pairRequestsHeight, 100f, 300f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void DrawPairRequestList(IReadOnlyList<PairRequestService.PairRequestDisplay> requests)
|
|
{
|
|
float playerColWidth = 207f * ImGuiHelpers.GlobalScale;
|
|
float receivedColWidth = 73f * ImGuiHelpers.GlobalScale;
|
|
float actionsColWidth = 50f * ImGuiHelpers.GlobalScale;
|
|
|
|
ImGui.Separator();
|
|
ImGui.TextUnformatted("Player");
|
|
ImGui.SameLine(playerColWidth + 2f);
|
|
ImGui.TextUnformatted("Received");
|
|
ImGui.SameLine(playerColWidth + receivedColWidth + 12f);
|
|
ImGui.TextUnformatted("Actions");
|
|
ImGui.Separator();
|
|
|
|
foreach (var request in requests)
|
|
{
|
|
ImGui.BeginGroup();
|
|
|
|
var label = string.IsNullOrEmpty(request.DisplayName) ? request.HashedCid : request.DisplayName;
|
|
|
|
ImGui.TextUnformatted(label.Truncate(26));
|
|
if (ImGui.IsItemHovered())
|
|
{
|
|
ImGui.BeginTooltip();
|
|
ImGui.TextUnformatted(label);
|
|
ImGui.EndTooltip();
|
|
}
|
|
|
|
ImGui.SameLine(playerColWidth);
|
|
|
|
ImGui.TextUnformatted(GetRelativeTime(request.ReceivedAt));
|
|
ImGui.SameLine(playerColWidth + receivedColWidth);
|
|
|
|
DrawPairRequestActions(request);
|
|
|
|
ImGui.EndGroup();
|
|
}
|
|
}
|
|
|
|
private void DrawPairRequestActions(PairRequestService.PairRequestDisplay request)
|
|
{
|
|
using var id = ImRaii.PushId(request.HashedCid);
|
|
var label = string.IsNullOrEmpty(request.DisplayName) ? request.HashedCid : request.DisplayName;
|
|
var inFlight = _pendingPairRequestActions.Contains(request.HashedCid);
|
|
using (ImRaii.Disabled(inFlight))
|
|
{
|
|
if (_uiSharedService.IconButton(FontAwesomeIcon.Check))
|
|
{
|
|
_ = AcceptPairRequestAsync(request);
|
|
}
|
|
if (ImGui.IsItemHovered())
|
|
ImGui.SetTooltip("Accept request");
|
|
|
|
ImGui.SameLine();
|
|
|
|
if (_uiSharedService.IconButton(FontAwesomeIcon.Times))
|
|
{
|
|
RejectPairRequest(request.HashedCid, label);
|
|
}
|
|
if (ImGui.IsItemHovered())
|
|
ImGui.SetTooltip("Decline request");
|
|
}
|
|
}
|
|
|
|
private static string GetRelativeTime(DateTime receivedAt)
|
|
{
|
|
var delta = DateTime.UtcNow - receivedAt;
|
|
if (delta <= TimeSpan.FromSeconds(10))
|
|
{
|
|
return "Just now";
|
|
}
|
|
|
|
if (delta.TotalMinutes >= 1)
|
|
{
|
|
return $"{Math.Floor(delta.TotalMinutes)}m {delta.Seconds:D2}s ago";
|
|
}
|
|
|
|
return $"{delta.Seconds}s ago";
|
|
}
|
|
|
|
private async Task AcceptPairRequestAsync(PairRequestService.PairRequestDisplay request)
|
|
{
|
|
if (!_pendingPairRequestActions.Add(request.HashedCid))
|
|
{
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
var myCidHash = _dalamudUtilService.GetCID().ToString().GetHash256();
|
|
await _apiController.TryPairWithContentId(request.HashedCid).ConfigureAwait(false);
|
|
_pairRequestService.RemoveRequest(request.HashedCid);
|
|
|
|
var display = string.IsNullOrEmpty(request.DisplayName) ? request.HashedCid : request.DisplayName;
|
|
_lightlessMediator.Publish(new NotificationMessage(
|
|
"Pair request accepted",
|
|
$"Sent a pair request back to {display}.",
|
|
NotificationType.Info,
|
|
TimeSpan.FromSeconds(3)));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_lightlessMediator.Publish(new NotificationMessage(
|
|
"Failed to accept pair request",
|
|
ex.Message,
|
|
NotificationType.Error,
|
|
TimeSpan.FromSeconds(5)));
|
|
}
|
|
finally
|
|
{
|
|
_pendingPairRequestActions.Remove(request.HashedCid);
|
|
}
|
|
}
|
|
|
|
private void RejectPairRequest(string hashedCid, string playerName)
|
|
{
|
|
if (!_pairRequestService.RemoveRequest(hashedCid))
|
|
{
|
|
return;
|
|
}
|
|
|
|
_lightlessMediator.Publish(new NotificationMessage("Pair request declined", "Declined " + playerName + "'s pending pair request.",
|
|
NotificationType.Info,
|
|
TimeSpan.FromSeconds(3)));
|
|
}
|
|
|
|
private void DrawFilter(float availableWidth, float spacingX)
|
|
{
|
|
var buttonSize = _uiSharedService.GetIconTextButtonSize(FontAwesomeIcon.Ban, "Clear");
|
|
ImGui.SetNextItemWidth(availableWidth - buttonSize - spacingX);
|
|
string filter = Filter;
|
|
if (ImGui.InputTextWithHint("##filter", "Filter for UID/notes", ref filter, 255))
|
|
{
|
|
Filter = filter;
|
|
}
|
|
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenBlockedByActiveItem) || ImGui.IsItemActive())
|
|
{
|
|
Selune.RegisterHighlight(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), SeluneHighlightMode.Both, true, 10, exactSize: true, clipToElement: true, roundingOverride: ImGui.GetStyle().FrameRounding);
|
|
}
|
|
ImGui.SameLine();
|
|
var disableClear = string.IsNullOrEmpty(Filter);
|
|
using var disabled = ImRaii.Disabled(disableClear);
|
|
var clearHovered = false;
|
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Ban, "Clear"))
|
|
{
|
|
Filter = string.Empty;
|
|
}
|
|
clearHovered = ImGui.IsItemHovered();
|
|
if (!disableClear && clearHovered)
|
|
{
|
|
Selune.RegisterHighlight(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), SeluneHighlightMode.Both, true, 10, exactSize: true, clipToElement: true, roundingOverride: ImGui.GetStyle().FrameRounding);
|
|
}
|
|
}
|
|
|
|
private void DrawGlobalIndividualButtons(float availableXWidth, float spacingX)
|
|
{
|
|
var buttonX = (availableXWidth - (spacingX * 3)) / 4f;
|
|
var buttonY = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Pause).Y;
|
|
var buttonSize = new Vector2(buttonX, buttonY);
|
|
|
|
using (ImRaii.PushFont(UiBuilder.IconFont))
|
|
{
|
|
using var disabled = ImRaii.Disabled(_globalControlCountdown > 0);
|
|
|
|
if (ImGui.Button(FontAwesomeIcon.Pause.ToIconString(), buttonSize))
|
|
{
|
|
ImGui.OpenPopup("Individual Pause");
|
|
}
|
|
}
|
|
UiSharedService.AttachToolTip("Globally resume or pause all individual pairs." + UiSharedService.TooltipSeparator
|
|
+ (_globalControlCountdown > 0 ? UiSharedService.TooltipSeparator + "Available again in " + _globalControlCountdown + " seconds." : string.Empty));
|
|
|
|
ImGui.SameLine();
|
|
using (ImRaii.PushFont(UiBuilder.IconFont))
|
|
{
|
|
using var disabled = ImRaii.Disabled(_globalControlCountdown > 0);
|
|
|
|
if (ImGui.Button(FontAwesomeIcon.VolumeUp.ToIconString(), buttonSize))
|
|
{
|
|
ImGui.OpenPopup("Individual Sounds");
|
|
}
|
|
}
|
|
UiSharedService.AttachToolTip("Globally enable or disable sound sync with all individual pairs."
|
|
+ (_globalControlCountdown > 0 ? UiSharedService.TooltipSeparator + "Available again in " + _globalControlCountdown + " seconds." : string.Empty));
|
|
|
|
ImGui.SameLine();
|
|
using (ImRaii.PushFont(UiBuilder.IconFont))
|
|
{
|
|
using var disabled = ImRaii.Disabled(_globalControlCountdown > 0);
|
|
|
|
if (ImGui.Button(FontAwesomeIcon.Running.ToIconString(), buttonSize))
|
|
{
|
|
ImGui.OpenPopup("Individual Animations");
|
|
}
|
|
}
|
|
UiSharedService.AttachToolTip("Globally enable or disable animation sync with all individual pairs." + UiSharedService.TooltipSeparator
|
|
+ (_globalControlCountdown > 0 ? UiSharedService.TooltipSeparator + "Available again in " + _globalControlCountdown + " seconds." : string.Empty));
|
|
|
|
ImGui.SameLine();
|
|
using (ImRaii.PushFont(UiBuilder.IconFont))
|
|
{
|
|
using var disabled = ImRaii.Disabled(_globalControlCountdown > 0);
|
|
|
|
if (ImGui.Button(FontAwesomeIcon.Sun.ToIconString(), buttonSize))
|
|
{
|
|
ImGui.OpenPopup("Individual VFX");
|
|
}
|
|
}
|
|
UiSharedService.AttachToolTip("Globally enable or disable VFX sync with all individual pairs." + UiSharedService.TooltipSeparator
|
|
+ (_globalControlCountdown > 0 ? UiSharedService.TooltipSeparator + "Available again in " + _globalControlCountdown + " seconds." : string.Empty));
|
|
|
|
|
|
PopupIndividualSetting("Individual Pause", "Unpause all individuals", "Pause all individuals",
|
|
FontAwesomeIcon.Play, FontAwesomeIcon.Pause,
|
|
(perm) =>
|
|
{
|
|
perm.SetPaused(false);
|
|
return perm;
|
|
},
|
|
(perm) =>
|
|
{
|
|
perm.SetPaused(true);
|
|
return perm;
|
|
});
|
|
PopupIndividualSetting("Individual Sounds", "Enable sounds for all individuals", "Disable sounds for all individuals",
|
|
FontAwesomeIcon.VolumeUp, FontAwesomeIcon.VolumeMute,
|
|
(perm) =>
|
|
{
|
|
perm.SetDisableSounds(false);
|
|
return perm;
|
|
},
|
|
(perm) =>
|
|
{
|
|
perm.SetDisableSounds(true);
|
|
return perm;
|
|
});
|
|
PopupIndividualSetting("Individual Animations", "Enable animations for all individuals", "Disable animations for all individuals",
|
|
FontAwesomeIcon.Running, FontAwesomeIcon.Stop,
|
|
(perm) =>
|
|
{
|
|
perm.SetDisableAnimations(false);
|
|
return perm;
|
|
},
|
|
(perm) =>
|
|
{
|
|
perm.SetDisableAnimations(true);
|
|
return perm;
|
|
});
|
|
PopupIndividualSetting("Individual VFX", "Enable VFX for all individuals", "Disable VFX for all individuals",
|
|
FontAwesomeIcon.Sun, FontAwesomeIcon.Circle,
|
|
(perm) =>
|
|
{
|
|
perm.SetDisableVFX(false);
|
|
return perm;
|
|
},
|
|
(perm) =>
|
|
{
|
|
perm.SetDisableVFX(true);
|
|
return perm;
|
|
});
|
|
}
|
|
|
|
private void DrawGlobalSyncshellButtons(float availableXWidth, float spacingX)
|
|
{
|
|
var buttonX = (availableXWidth - (spacingX * 4)) / 5f;
|
|
var buttonY = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Pause).Y;
|
|
var buttonSize = new Vector2(buttonX, buttonY);
|
|
|
|
using (ImRaii.PushFont(UiBuilder.IconFont))
|
|
{
|
|
using var disabled = ImRaii.Disabled(_globalControlCountdown > 0);
|
|
|
|
if (ImGui.Button(FontAwesomeIcon.Pause.ToIconString(), buttonSize))
|
|
{
|
|
ImGui.OpenPopup("Syncshell Pause");
|
|
}
|
|
}
|
|
UiSharedService.AttachToolTip("Globally resume or pause all syncshells." + UiSharedService.TooltipSeparator
|
|
+ "Note: This will not affect users with preferred permissions in syncshells."
|
|
+ (_globalControlCountdown > 0 ? UiSharedService.TooltipSeparator + "Available again in " + _globalControlCountdown + " seconds." : string.Empty));
|
|
|
|
ImGui.SameLine();
|
|
using (ImRaii.PushFont(UiBuilder.IconFont))
|
|
{
|
|
using var disabled = ImRaii.Disabled(_globalControlCountdown > 0);
|
|
|
|
if (ImGui.Button(FontAwesomeIcon.VolumeUp.ToIconString(), buttonSize))
|
|
{
|
|
ImGui.OpenPopup("Syncshell Sounds");
|
|
}
|
|
}
|
|
UiSharedService.AttachToolTip("Globally enable or disable sound sync with all syncshells." + UiSharedService.TooltipSeparator
|
|
+ "Note: This will not affect users with preferred permissions in syncshells."
|
|
+ (_globalControlCountdown > 0 ? UiSharedService.TooltipSeparator + "Available again in " + _globalControlCountdown + " seconds." : string.Empty));
|
|
|
|
ImGui.SameLine();
|
|
using (ImRaii.PushFont(UiBuilder.IconFont))
|
|
{
|
|
using var disabled = ImRaii.Disabled(_globalControlCountdown > 0);
|
|
|
|
if (ImGui.Button(FontAwesomeIcon.Running.ToIconString(), buttonSize))
|
|
{
|
|
ImGui.OpenPopup("Syncshell Animations");
|
|
}
|
|
}
|
|
UiSharedService.AttachToolTip("Globally enable or disable animation sync with all syncshells." + UiSharedService.TooltipSeparator
|
|
+ "Note: This will not affect users with preferred permissions in syncshells."
|
|
+ (_globalControlCountdown > 0 ? UiSharedService.TooltipSeparator + "Available again in " + _globalControlCountdown + " seconds." : string.Empty));
|
|
|
|
ImGui.SameLine();
|
|
using (ImRaii.PushFont(UiBuilder.IconFont))
|
|
{
|
|
using var disabled = ImRaii.Disabled(_globalControlCountdown > 0);
|
|
|
|
if (ImGui.Button(FontAwesomeIcon.Sun.ToIconString(), buttonSize))
|
|
{
|
|
ImGui.OpenPopup("Syncshell VFX");
|
|
}
|
|
}
|
|
UiSharedService.AttachToolTip("Globally enable or disable VFX sync with all syncshells." + UiSharedService.TooltipSeparator
|
|
+ "Note: This will not affect users with preferred permissions in syncshells."
|
|
+ (_globalControlCountdown > 0 ? UiSharedService.TooltipSeparator + "Available again in " + _globalControlCountdown + " seconds." : string.Empty));
|
|
|
|
|
|
PopupSyncshellSetting("Syncshell Pause", "Unpause all syncshells", "Pause all syncshells",
|
|
FontAwesomeIcon.Play, FontAwesomeIcon.Pause,
|
|
(perm) =>
|
|
{
|
|
perm.SetPaused(false);
|
|
return perm;
|
|
},
|
|
(perm) =>
|
|
{
|
|
perm.SetPaused(true);
|
|
return perm;
|
|
});
|
|
PopupSyncshellSetting("Syncshell Sounds", "Enable sounds for all syncshells", "Disable sounds for all syncshells",
|
|
FontAwesomeIcon.VolumeUp, FontAwesomeIcon.VolumeMute,
|
|
(perm) =>
|
|
{
|
|
perm.SetDisableSounds(false);
|
|
return perm;
|
|
},
|
|
(perm) =>
|
|
{
|
|
perm.SetDisableSounds(true);
|
|
return perm;
|
|
});
|
|
PopupSyncshellSetting("Syncshell Animations", "Enable animations for all syncshells", "Disable animations for all syncshells",
|
|
FontAwesomeIcon.Running, FontAwesomeIcon.Stop,
|
|
(perm) =>
|
|
{
|
|
perm.SetDisableAnimations(false);
|
|
return perm;
|
|
},
|
|
(perm) =>
|
|
{
|
|
perm.SetDisableAnimations(true);
|
|
return perm;
|
|
});
|
|
PopupSyncshellSetting("Syncshell VFX", "Enable VFX for all syncshells", "Disable VFX for all syncshells",
|
|
FontAwesomeIcon.Sun, FontAwesomeIcon.Circle,
|
|
(perm) =>
|
|
{
|
|
perm.SetDisableVFX(false);
|
|
return perm;
|
|
},
|
|
(perm) =>
|
|
{
|
|
perm.SetDisableVFX(true);
|
|
return perm;
|
|
});
|
|
|
|
ImGui.SameLine();
|
|
using (ImRaii.PushFont(UiBuilder.IconFont))
|
|
{
|
|
using var disabled = ImRaii.Disabled(_globalControlCountdown > 0 || !UiSharedService.CtrlPressed());
|
|
|
|
if (ImGui.Button(FontAwesomeIcon.Check.ToIconString(), buttonSize))
|
|
{
|
|
_ = GlobalControlCountdown(10);
|
|
var bulkSyncshells = Snapshot.GroupPairs.Keys.OrderBy(g => g.GroupAliasOrGID, StringComparer.OrdinalIgnoreCase)
|
|
.ToDictionary(g => g.Group.GID, g =>
|
|
{
|
|
var perm = g.GroupUserPermissions;
|
|
perm.SetDisableSounds(g.GroupPermissions.IsPreferDisableSounds());
|
|
perm.SetDisableAnimations(g.GroupPermissions.IsPreferDisableAnimations());
|
|
perm.SetDisableVFX(g.GroupPermissions.IsPreferDisableVFX());
|
|
return perm;
|
|
}, StringComparer.Ordinal);
|
|
|
|
_ = _apiController.SetBulkPermissions(new(new(StringComparer.Ordinal), bulkSyncshells)).ConfigureAwait(false);
|
|
}
|
|
}
|
|
UiSharedService.AttachToolTip("Globally align syncshell permissions to suggested syncshell permissions." + UiSharedService.TooltipSeparator
|
|
+ "Note: This will not affect users with preferred permissions in syncshells." + Environment.NewLine
|
|
+ "Note: If multiple users share one syncshell the permissions to that user will be set to " + Environment.NewLine
|
|
+ "the ones of the last applied syncshell in alphabetical order." + UiSharedService.TooltipSeparator
|
|
+ "Hold CTRL to enable this button"
|
|
+ (_globalControlCountdown > 0 ? UiSharedService.TooltipSeparator + "Available again in " + _globalControlCountdown + " seconds." : string.Empty));
|
|
}
|
|
|
|
private void DrawSyncshellMenu(float availableWidth, float spacingX)
|
|
{
|
|
var buttonX = (availableWidth - (spacingX)) / 2f;
|
|
|
|
using (ImRaii.Disabled(Snapshot.GroupPairs.Keys
|
|
.Distinct()
|
|
.Count(g => string.Equals(g.OwnerUID, _apiController.UID, StringComparison.Ordinal)) >= _apiController.ServerInfo.MaxGroupsCreatedByUser))
|
|
{
|
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Plus, "Create new Syncshell", buttonX))
|
|
{
|
|
_lightlessMediator.Publish(new UiToggleMessage(typeof(CreateSyncshellUI)));
|
|
}
|
|
ImGui.SameLine();
|
|
}
|
|
|
|
using (ImRaii.Disabled(Snapshot.GroupPairs.Keys.Distinct().Count() >= _apiController.ServerInfo.MaxGroupsJoinedByUser))
|
|
{
|
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Users, "Join existing Syncshell", buttonX))
|
|
{
|
|
_lightlessMediator.Publish(new UiToggleMessage(typeof(JoinSyncshellUI)));
|
|
}
|
|
}
|
|
}
|
|
|
|
private void DrawLightfinderMenu(float availableWidth, float spacingX)
|
|
{
|
|
var buttonX = (availableWidth - (spacingX)) / 2f;
|
|
|
|
var lightFinderLabel = GetLightfinderFinderLabel();
|
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.PersonCirclePlus, lightFinderLabel, buttonX, center: true))
|
|
{
|
|
_lightlessMediator.Publish(new UiToggleMessage(typeof(LightFinderUI)));
|
|
}
|
|
|
|
ImGui.SameLine();
|
|
|
|
var syncshellFinderLabel = GetSyncshellFinderLabel();
|
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Globe, syncshellFinderLabel, buttonX, center: true))
|
|
{
|
|
_lightlessMediator.Publish(new UiToggleMessage(typeof(SyncshellFinderUI)));
|
|
}
|
|
}
|
|
|
|
private string GetLightfinderFinderLabel()
|
|
{
|
|
string label = "Lightfinder";
|
|
|
|
if (_lightFinderService.IsBroadcasting)
|
|
{
|
|
var hashExclude = _dalamudUtilService.GetCID().ToString().GetHash256();
|
|
var nearbyCount = _lightFinderScannerService.GetActiveBroadcasts(hashExclude).Count;
|
|
return $"{label} ({nearbyCount})";
|
|
}
|
|
|
|
return label;
|
|
}
|
|
|
|
private string GetSyncshellFinderLabel()
|
|
{
|
|
if (!_lightFinderService.IsBroadcasting)
|
|
return "Syncshell Finder";
|
|
|
|
var nearbyCount = _lightFinderScannerService
|
|
.GetActiveSyncshellBroadcasts(excludeLocal: true)
|
|
.Where(b => !string.IsNullOrEmpty(b.GID))
|
|
.Select(b => b.GID!)
|
|
.Distinct(StringComparer.Ordinal)
|
|
.Count();
|
|
|
|
return nearbyCount > 0 ? $"Syncshell Finder ({nearbyCount})" : "Syncshell Finder";
|
|
}
|
|
|
|
private void DrawUserConfig(float availableWidth, float spacingX)
|
|
{
|
|
var buttonX = (availableWidth - spacingX) / 2f;
|
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.UserCircle, "Edit Lightless Profile", buttonX))
|
|
{
|
|
_lightlessMediator.Publish(new UiToggleMessage(typeof(EditProfileUi)));
|
|
}
|
|
UiSharedService.AttachToolTip("Edit your Lightless Profile");
|
|
ImGui.SameLine();
|
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.PersonCircleQuestion, "Chara Data Analysis", buttonX))
|
|
{
|
|
_lightlessMediator.Publish(new UiToggleMessage(typeof(DataAnalysisUi)));
|
|
}
|
|
UiSharedService.AttachToolTip("View and analyze your generated character data");
|
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Running, "Character Data Hub", availableWidth))
|
|
{
|
|
_lightlessMediator.Publish(new UiToggleMessage(typeof(CharaDataHubUi)));
|
|
}
|
|
}
|
|
|
|
private async Task GlobalControlCountdown(int countdown)
|
|
{
|
|
#if DEBUG
|
|
return;
|
|
#endif
|
|
|
|
_globalControlCountdown = countdown;
|
|
while (_globalControlCountdown > 0)
|
|
{
|
|
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
|
|
_globalControlCountdown--;
|
|
}
|
|
}
|
|
|
|
private void PopupIndividualSetting(string popupTitle, string enableText, string disableText,
|
|
FontAwesomeIcon enableIcon, FontAwesomeIcon disableIcon,
|
|
Func<UserPermissions, UserPermissions> actEnable, Func<UserPermissions, UserPermissions> actDisable)
|
|
{
|
|
if (ImGui.BeginPopup(popupTitle))
|
|
{
|
|
if (_uiSharedService.IconTextButton(enableIcon, enableText, null, true))
|
|
{
|
|
_ = GlobalControlCountdown(10);
|
|
var bulkIndividualPairs = Snapshot.PairsWithGroups.Keys
|
|
.Where(g => g.IndividualPairStatus == IndividualPairStatus.Bidirectional)
|
|
.ToDictionary(g => g.UserPair.User.UID, g =>
|
|
{
|
|
return actEnable(g.UserPair.OwnPermissions);
|
|
}, StringComparer.Ordinal);
|
|
|
|
_ = _apiController.SetBulkPermissions(new(bulkIndividualPairs, new(StringComparer.Ordinal))).ConfigureAwait(false);
|
|
ImGui.CloseCurrentPopup();
|
|
}
|
|
|
|
if (_uiSharedService.IconTextButton(disableIcon, disableText, null, true))
|
|
{
|
|
_ = GlobalControlCountdown(10);
|
|
var bulkIndividualPairs = Snapshot.PairsWithGroups.Keys
|
|
.Where(g => g.IndividualPairStatus == IndividualPairStatus.Bidirectional)
|
|
.ToDictionary(g => g.UserPair.User.UID, g =>
|
|
{
|
|
return actDisable(g.UserPair.OwnPermissions);
|
|
}, StringComparer.Ordinal);
|
|
|
|
_ = _apiController.SetBulkPermissions(new(bulkIndividualPairs, new(StringComparer.Ordinal))).ConfigureAwait(false);
|
|
ImGui.CloseCurrentPopup();
|
|
}
|
|
ImGui.EndPopup();
|
|
}
|
|
}
|
|
private void PopupSyncshellSetting(string popupTitle, string enableText, string disableText,
|
|
FontAwesomeIcon enableIcon, FontAwesomeIcon disableIcon,
|
|
Func<GroupUserPreferredPermissions, GroupUserPreferredPermissions> actEnable,
|
|
Func<GroupUserPreferredPermissions, GroupUserPreferredPermissions> actDisable)
|
|
{
|
|
if (ImGui.BeginPopup(popupTitle))
|
|
{
|
|
|
|
if (_uiSharedService.IconTextButton(enableIcon, enableText, null, true))
|
|
{
|
|
_ = GlobalControlCountdown(10);
|
|
var bulkSyncshells = Snapshot.GroupPairs.Keys
|
|
.OrderBy(u => u.GroupAliasOrGID, StringComparer.OrdinalIgnoreCase)
|
|
.ToDictionary(g => g.Group.GID, g =>
|
|
{
|
|
return actEnable(g.GroupUserPermissions);
|
|
}, StringComparer.Ordinal);
|
|
|
|
_ = _apiController.SetBulkPermissions(new(new(StringComparer.Ordinal), bulkSyncshells)).ConfigureAwait(false);
|
|
ImGui.CloseCurrentPopup();
|
|
}
|
|
|
|
if (_uiSharedService.IconTextButton(disableIcon, disableText, null, true))
|
|
{
|
|
_ = GlobalControlCountdown(10);
|
|
var bulkSyncshells = Snapshot.GroupPairs.Keys
|
|
.OrderBy(u => u.GroupAliasOrGID, StringComparer.OrdinalIgnoreCase)
|
|
.ToDictionary(g => g.Group.GID, g =>
|
|
{
|
|
return actDisable(g.GroupUserPermissions);
|
|
}, StringComparer.Ordinal);
|
|
|
|
_ = _apiController.SetBulkPermissions(new(new(StringComparer.Ordinal), bulkSyncshells)).ConfigureAwait(false);
|
|
ImGui.CloseCurrentPopup();
|
|
}
|
|
ImGui.EndPopup();
|
|
}
|
|
}
|
|
} |