Merge pull request 'Merge master to profiles' (#61) from master into syncshell-profiles
Reviewed-on: #61
This commit was merged in pull request #61.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Utility;
|
||||
@@ -209,7 +210,7 @@ namespace LightlessSync.UI
|
||||
else
|
||||
{
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, UIColors.Get("DimRed"));
|
||||
ImGui.Text("The Lightfinder<EFBFBD>s light wanes, but not in vain."); // cringe..
|
||||
ImGui.Text("The Lightfinder’s light wanes, but not in vain."); // cringe..
|
||||
ImGui.PopStyleColor();
|
||||
}
|
||||
}
|
||||
@@ -252,12 +253,27 @@ namespace LightlessSync.UI
|
||||
_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();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Utility;
|
||||
@@ -87,7 +87,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
||||
IpcManager ipcManager,
|
||||
BroadcastService broadcastService,
|
||||
CharacterAnalyzer characterAnalyzer,
|
||||
PlayerPerformanceConfigService playerPerformanceConfig, PairRequestService pairRequestService, DalamudUtilService dalamudUtilService) : base(logger, mediator, "###LightlessSyncMainUI", performanceCollectorService)
|
||||
PlayerPerformanceConfigService playerPerformanceConfig, PairRequestService pairRequestService, DalamudUtilService dalamudUtilService, NotificationService lightlessNotificationService) : base(logger, mediator, "###LightlessSyncMainUI", performanceCollectorService)
|
||||
{
|
||||
_uiSharedService = uiShared;
|
||||
_configService = configService;
|
||||
@@ -105,7 +105,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
||||
_renamePairTagUi = renameTagUi;
|
||||
_ipcManager = ipcManager;
|
||||
_broadcastService = broadcastService;
|
||||
_tabMenu = new TopTabMenu(Mediator, _apiController, _pairManager, _uiSharedService, pairRequestService, dalamudUtilService);
|
||||
_tabMenu = new TopTabMenu(Mediator, _apiController, _pairManager, _uiSharedService, pairRequestService, dalamudUtilService, lightlessNotificationService);
|
||||
|
||||
AllowPinning = true;
|
||||
AllowClickthrough = false;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface.Colors;
|
||||
using LightlessSync.LightlessConfiguration;
|
||||
using LightlessSync.LightlessConfiguration.Models;
|
||||
using LightlessSync.PlayerData.Handlers;
|
||||
using LightlessSync.Services;
|
||||
using LightlessSync.Services.Mediator;
|
||||
@@ -22,9 +22,12 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
||||
private readonly UiSharedService _uiShared;
|
||||
private readonly PairProcessingLimiter _pairProcessingLimiter;
|
||||
private readonly ConcurrentDictionary<GameObjectHandler, bool> _uploadingPlayers = new();
|
||||
private readonly NotificationService _notificationService;
|
||||
private bool _notificationDismissed = true;
|
||||
|
||||
public DownloadUi(ILogger<DownloadUi> logger, DalamudUtilService dalamudUtilService, LightlessConfigService configService,
|
||||
PairProcessingLimiter pairProcessingLimiter, FileUploadManager fileTransferManager, LightlessMediator mediator, UiSharedService uiShared, PerformanceCollectorService performanceCollectorService)
|
||||
PairProcessingLimiter pairProcessingLimiter, FileUploadManager fileTransferManager, LightlessMediator mediator, UiSharedService uiShared,
|
||||
PerformanceCollectorService performanceCollectorService, NotificationService notificationService)
|
||||
: base(logger, mediator, "Lightless Sync Downloads", performanceCollectorService)
|
||||
{
|
||||
_dalamudUtilService = dalamudUtilService;
|
||||
@@ -32,6 +35,7 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
||||
_pairProcessingLimiter = pairProcessingLimiter;
|
||||
_fileTransferManager = fileTransferManager;
|
||||
_uiShared = uiShared;
|
||||
_notificationService = notificationService;
|
||||
|
||||
SizeConstraints = new WindowSizeConstraints()
|
||||
{
|
||||
@@ -56,7 +60,14 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
||||
IsOpen = true;
|
||||
|
||||
Mediator.Subscribe<DownloadStartedMessage>(this, (msg) => _currentDownloads[msg.DownloadId] = msg.DownloadStatus);
|
||||
Mediator.Subscribe<DownloadFinishedMessage>(this, (msg) => _currentDownloads.TryRemove(msg.DownloadId, out _));
|
||||
Mediator.Subscribe<DownloadFinishedMessage>(this, (msg) =>
|
||||
{
|
||||
_currentDownloads.TryRemove(msg.DownloadId, out _);
|
||||
if (!_currentDownloads.Any())
|
||||
{
|
||||
_notificationService.DismissPairDownloadNotification();
|
||||
}
|
||||
});
|
||||
Mediator.Subscribe<GposeStartMessage>(this, (_) => IsOpen = false);
|
||||
Mediator.Subscribe<GposeEndMessage>(this, (_) => IsOpen = true);
|
||||
Mediator.Subscribe<PlayerUploadingMessage>(this, (msg) =>
|
||||
@@ -77,19 +88,7 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
||||
if (_configService.Current.ShowTransferWindow)
|
||||
{
|
||||
var limiterSnapshot = _pairProcessingLimiter.GetSnapshot();
|
||||
if (limiterSnapshot.IsEnabled)
|
||||
{
|
||||
var queueColor = limiterSnapshot.Waiting > 0 ? ImGuiColors.DalamudYellow : ImGuiColors.DalamudGrey;
|
||||
var queueText = $"Pair queue {limiterSnapshot.InFlight}/{limiterSnapshot.Limit}";
|
||||
queueText += limiterSnapshot.Waiting > 0 ? $" ({limiterSnapshot.Waiting} waiting, {limiterSnapshot.Remaining} free)" : $" ({limiterSnapshot.Remaining} free)";
|
||||
UiSharedService.DrawOutlinedFont(queueText, queueColor, new Vector4(0, 0, 0, 255), 1);
|
||||
ImGui.NewLine();
|
||||
}
|
||||
else
|
||||
{
|
||||
UiSharedService.DrawOutlinedFont("Pair apply limiter disabled", ImGuiColors.DalamudGrey, new Vector4(0, 0, 0, 255), 1);
|
||||
ImGui.NewLine();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (_fileTransferManager.IsUploading)
|
||||
@@ -122,28 +121,64 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var item in _currentDownloads.ToList())
|
||||
// Check if download notifications are enabled (not set to TextOverlay)
|
||||
var useNotifications = _configService.Current.UseLightlessNotifications
|
||||
? _configService.Current.LightlessDownloadNotification != NotificationLocation.TextOverlay
|
||||
: _configService.Current.UseNotificationsForDownloads;
|
||||
|
||||
if (useNotifications)
|
||||
{
|
||||
var dlSlot = item.Value.Count(c => c.Value.DownloadStatus == DownloadStatus.WaitingForSlot);
|
||||
var dlQueue = item.Value.Count(c => c.Value.DownloadStatus == DownloadStatus.WaitingForQueue);
|
||||
var dlProg = item.Value.Count(c => c.Value.DownloadStatus == DownloadStatus.Downloading);
|
||||
var dlDecomp = item.Value.Count(c => c.Value.DownloadStatus == DownloadStatus.Decompressing);
|
||||
var totalFiles = item.Value.Sum(c => c.Value.TotalFiles);
|
||||
var transferredFiles = item.Value.Sum(c => c.Value.TransferredFiles);
|
||||
var totalBytes = item.Value.Sum(c => c.Value.TotalBytes);
|
||||
var transferredBytes = item.Value.Sum(c => c.Value.TransferredBytes);
|
||||
// Use notification system
|
||||
if (_currentDownloads.Any())
|
||||
{
|
||||
UpdateDownloadNotification(limiterSnapshot);
|
||||
_notificationDismissed = false;
|
||||
}
|
||||
else if (!_notificationDismissed)
|
||||
{
|
||||
_notificationService.DismissPairDownloadNotification();
|
||||
_notificationDismissed = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (limiterSnapshot.IsEnabled)
|
||||
{
|
||||
var queueColor = limiterSnapshot.Waiting > 0 ? ImGuiColors.DalamudYellow : ImGuiColors.DalamudGrey;
|
||||
var queueText = $"Pair queue {limiterSnapshot.InFlight}/{limiterSnapshot.Limit}";
|
||||
queueText += limiterSnapshot.Waiting > 0 ? $" ({limiterSnapshot.Waiting} waiting, {limiterSnapshot.Remaining} free)" : $" ({limiterSnapshot.Remaining} free)";
|
||||
UiSharedService.DrawOutlinedFont(queueText, queueColor, new Vector4(0, 0, 0, 255), 1);
|
||||
ImGui.NewLine();
|
||||
}
|
||||
else
|
||||
{
|
||||
UiSharedService.DrawOutlinedFont("Pair apply limiter disabled", ImGuiColors.DalamudGrey, new Vector4(0, 0, 0, 255), 1);
|
||||
ImGui.NewLine();
|
||||
}
|
||||
|
||||
foreach (var item in _currentDownloads.ToList())
|
||||
{
|
||||
var dlSlot = item.Value.Count(c => c.Value.DownloadStatus == DownloadStatus.WaitingForSlot);
|
||||
var dlQueue = item.Value.Count(c => c.Value.DownloadStatus == DownloadStatus.WaitingForQueue);
|
||||
var dlProg = item.Value.Count(c => c.Value.DownloadStatus == DownloadStatus.Downloading);
|
||||
var dlDecomp = item.Value.Count(c => c.Value.DownloadStatus == DownloadStatus.Decompressing);
|
||||
var totalFiles = item.Value.Sum(c => c.Value.TotalFiles);
|
||||
var transferredFiles = item.Value.Sum(c => c.Value.TransferredFiles);
|
||||
var totalBytes = item.Value.Sum(c => c.Value.TotalBytes);
|
||||
var transferredBytes = item.Value.Sum(c => c.Value.TransferredBytes);
|
||||
|
||||
UiSharedService.DrawOutlinedFont($"▼", ImGuiColors.DalamudWhite, new Vector4(0, 0, 0, 255), 1);
|
||||
ImGui.SameLine();
|
||||
var xDistance = ImGui.GetCursorPosX();
|
||||
UiSharedService.DrawOutlinedFont(
|
||||
$"{item.Key.Name} [W:{dlSlot}/Q:{dlQueue}/P:{dlProg}/D:{dlDecomp}]",
|
||||
ImGuiColors.DalamudWhite, new Vector4(0, 0, 0, 255), 1);
|
||||
ImGui.NewLine();
|
||||
ImGui.SameLine(xDistance);
|
||||
UiSharedService.DrawOutlinedFont(
|
||||
$"{transferredFiles}/{totalFiles} ({UiSharedService.ByteToString(transferredBytes, addSuffix: false)}/{UiSharedService.ByteToString(totalBytes)})",
|
||||
ImGuiColors.DalamudWhite, new Vector4(0, 0, 0, 255), 1);
|
||||
UiSharedService.DrawOutlinedFont($"▼", ImGuiColors.DalamudWhite, new Vector4(0, 0, 0, 255), 1);
|
||||
ImGui.SameLine();
|
||||
var xDistance = ImGui.GetCursorPosX();
|
||||
UiSharedService.DrawOutlinedFont(
|
||||
$"{item.Key.Name} [W:{dlSlot}/Q:{dlQueue}/P:{dlProg}/D:{dlDecomp}]",
|
||||
ImGuiColors.DalamudWhite, new Vector4(0, 0, 0, 255), 1);
|
||||
ImGui.NewLine();
|
||||
ImGui.SameLine(xDistance);
|
||||
UiSharedService.DrawOutlinedFont(
|
||||
$"{transferredFiles}/{totalFiles} ({UiSharedService.ByteToString(transferredBytes, addSuffix: false)}/{UiSharedService.ByteToString(totalBytes)})",
|
||||
ImGuiColors.DalamudWhite, new Vector4(0, 0, 0, 255), 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
@@ -262,4 +297,40 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
||||
MaximumSize = new Vector2(300, maxHeight),
|
||||
};
|
||||
}
|
||||
|
||||
private void UpdateDownloadNotification(PairProcessingLimiterSnapshot limiterSnapshot)
|
||||
{
|
||||
var downloadStatus = new List<(string playerName, float progress, string status)>();
|
||||
|
||||
foreach (var item in _currentDownloads.ToList())
|
||||
{
|
||||
var dlSlot = item.Value.Count(c => c.Value.DownloadStatus == DownloadStatus.WaitingForSlot);
|
||||
var dlQueue = item.Value.Count(c => c.Value.DownloadStatus == DownloadStatus.WaitingForQueue);
|
||||
var dlProg = item.Value.Count(c => c.Value.DownloadStatus == DownloadStatus.Downloading);
|
||||
var dlDecomp = item.Value.Count(c => c.Value.DownloadStatus == DownloadStatus.Decompressing);
|
||||
var totalFiles = item.Value.Sum(c => c.Value.TotalFiles);
|
||||
var transferredFiles = item.Value.Sum(c => c.Value.TransferredFiles);
|
||||
var totalBytes = item.Value.Sum(c => c.Value.TotalBytes);
|
||||
var transferredBytes = item.Value.Sum(c => c.Value.TransferredBytes);
|
||||
|
||||
var progress = totalBytes > 0 ? (float)transferredBytes / totalBytes : 0f;
|
||||
|
||||
string status;
|
||||
if (dlDecomp > 0) status = "decompressing";
|
||||
else if (dlProg > 0) status = "downloading";
|
||||
else if (dlQueue > 0) status = "queued";
|
||||
else if (dlSlot > 0) status = "waiting";
|
||||
else status = "completed";
|
||||
|
||||
downloadStatus.Add((item.Key.Name, progress, status));
|
||||
}
|
||||
|
||||
// Pass queue waiting count separately, show notification if there are downloads or queue items
|
||||
var queueWaiting = limiterSnapshot.IsEnabled ? limiterSnapshot.Waiting : 0;
|
||||
if (downloadStatus.Any() || queueWaiting > 0)
|
||||
{
|
||||
_notificationService.ShowPairDownloadNotification(downloadStatus, queueWaiting);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,56 +1,92 @@
|
||||
using Dalamud.Game.Gui.Dtr;
|
||||
using Dalamud.Game.Gui.Dtr;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Dalamud.Plugin.Services;
|
||||
using LightlessSync.LightlessConfiguration;
|
||||
using LightlessSync.LightlessConfiguration.Configurations;
|
||||
using LightlessSync.PlayerData.Pairs;
|
||||
using LightlessSync.Services;
|
||||
using LightlessSync.Services.Mediator;
|
||||
using LightlessSync.Services.ServerConfiguration;
|
||||
using LightlessSync.WebAPI;
|
||||
using LightlessSync.WebAPI.SignalR.Utils;
|
||||
using LightlessSync.Utils;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace LightlessSync.UI;
|
||||
|
||||
public sealed class DtrEntry : IDisposable, IHostedService
|
||||
{
|
||||
private static readonly TimeSpan _localHashedCidCacheDuration = TimeSpan.FromMinutes(2);
|
||||
private static readonly TimeSpan _localHashedCidErrorCooldown = TimeSpan.FromMinutes(1);
|
||||
|
||||
private readonly ApiController _apiController;
|
||||
private readonly ServerConfigurationManager _serverManager;
|
||||
private readonly CancellationTokenSource _cancellationTokenSource = new();
|
||||
private readonly ConfigurationServiceBase<LightlessConfig> _configService;
|
||||
private readonly IDtrBar _dtrBar;
|
||||
private readonly Lazy<IDtrBarEntry> _entry;
|
||||
private readonly Lazy<IDtrBarEntry> _statusEntry;
|
||||
private readonly Lazy<IDtrBarEntry> _lightfinderEntry;
|
||||
private readonly ILogger<DtrEntry> _logger;
|
||||
private readonly BroadcastService _broadcastService;
|
||||
private readonly BroadcastScannerService _broadcastScannerService;
|
||||
private readonly LightlessMediator _lightlessMediator;
|
||||
private readonly PairManager _pairManager;
|
||||
private readonly PairRequestService _pairRequestService;
|
||||
private readonly DalamudUtilService _dalamudUtilService;
|
||||
private Task? _runTask;
|
||||
private string? _text;
|
||||
private string? _tooltip;
|
||||
private Colors _colors;
|
||||
private string? _statusText;
|
||||
private string? _statusTooltip;
|
||||
private Colors _statusColors;
|
||||
private string? _lightfinderText;
|
||||
private string? _lightfinderTooltip;
|
||||
private Colors _lightfinderColors;
|
||||
private string? _localHashedCid;
|
||||
private DateTime _localHashedCidFetchedAt = DateTime.MinValue;
|
||||
private DateTime _localHashedCidNextErrorLog = DateTime.MinValue;
|
||||
private DateTime _pairRequestNextErrorLog = DateTime.MinValue;
|
||||
|
||||
public DtrEntry(ILogger<DtrEntry> logger, IDtrBar dtrBar, ConfigurationServiceBase<LightlessConfig> configService, LightlessMediator lightlessMediator, PairManager pairManager, ApiController apiController, ServerConfigurationManager serverManager)
|
||||
public DtrEntry(
|
||||
ILogger<DtrEntry> logger,
|
||||
IDtrBar dtrBar,
|
||||
ConfigurationServiceBase<LightlessConfig> configService,
|
||||
LightlessMediator lightlessMediator,
|
||||
PairManager pairManager,
|
||||
PairRequestService pairRequestService,
|
||||
ApiController apiController,
|
||||
ServerConfigurationManager serverManager,
|
||||
BroadcastService broadcastService,
|
||||
BroadcastScannerService broadcastScannerService,
|
||||
DalamudUtilService dalamudUtilService)
|
||||
{
|
||||
_logger = logger;
|
||||
_dtrBar = dtrBar;
|
||||
_entry = new(CreateEntry);
|
||||
_statusEntry = new(CreateStatusEntry);
|
||||
_lightfinderEntry = new(CreateLightfinderEntry);
|
||||
_configService = configService;
|
||||
_lightlessMediator = lightlessMediator;
|
||||
_pairManager = pairManager;
|
||||
_pairRequestService = pairRequestService;
|
||||
_apiController = apiController;
|
||||
_serverManager = serverManager;
|
||||
_broadcastService = broadcastService;
|
||||
_broadcastScannerService = broadcastScannerService;
|
||||
_dalamudUtilService = dalamudUtilService;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_entry.IsValueCreated)
|
||||
if (_statusEntry.IsValueCreated)
|
||||
{
|
||||
_logger.LogDebug("Disposing DtrEntry");
|
||||
Clear();
|
||||
_entry.Value.Remove();
|
||||
_statusEntry.Value.Remove();
|
||||
}
|
||||
if (_lightfinderEntry.IsValueCreated)
|
||||
_lightfinderEntry.Value.Remove();
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
@@ -70,7 +106,7 @@ public sealed class DtrEntry : IDisposable, IHostedService
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// ignore cancelled
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -80,33 +116,66 @@ public sealed class DtrEntry : IDisposable, IHostedService
|
||||
|
||||
private void Clear()
|
||||
{
|
||||
if (!_entry.IsValueCreated) return;
|
||||
_logger.LogInformation("Clearing entry");
|
||||
_text = null;
|
||||
_tooltip = null;
|
||||
_colors = default;
|
||||
|
||||
_entry.Value.Shown = false;
|
||||
HideStatusEntry();
|
||||
HideLightfinderEntry();
|
||||
}
|
||||
|
||||
private IDtrBarEntry CreateEntry()
|
||||
private void HideStatusEntry()
|
||||
{
|
||||
_logger.LogTrace("Creating new DtrBar entry");
|
||||
if (_statusEntry.IsValueCreated && _statusEntry.Value.Shown)
|
||||
{
|
||||
_logger.LogInformation("Hiding status entry");
|
||||
_statusEntry.Value.Shown = false;
|
||||
}
|
||||
|
||||
_statusText = null;
|
||||
_statusTooltip = null;
|
||||
_statusColors = default;
|
||||
}
|
||||
|
||||
private void HideLightfinderEntry()
|
||||
{
|
||||
if (_lightfinderEntry.IsValueCreated && _lightfinderEntry.Value.Shown)
|
||||
{
|
||||
_logger.LogInformation("Hiding Lightfinder entry");
|
||||
_lightfinderEntry.Value.Shown = false;
|
||||
}
|
||||
|
||||
_lightfinderText = null;
|
||||
_lightfinderTooltip = null;
|
||||
_lightfinderColors = default;
|
||||
}
|
||||
|
||||
private IDtrBarEntry CreateStatusEntry()
|
||||
{
|
||||
_logger.LogTrace("Creating status DtrBar entry");
|
||||
var entry = _dtrBar.Get("Lightless Sync");
|
||||
entry.OnClick = interactionEvent => OnClickEvent(interactionEvent);
|
||||
entry.OnClick = interactionEvent => OnStatusEntryClick(interactionEvent);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
private void OnClickEvent(DtrInteractionEvent interactionEvent)
|
||||
|
||||
private IDtrBarEntry CreateLightfinderEntry()
|
||||
{
|
||||
if (interactionEvent.ClickType.Equals(MouseClickType.Left) && !interactionEvent.ModifierKeys.Equals(ClickModifierKeys.Shift))
|
||||
_logger.LogTrace("Creating Lightfinder DtrBar entry");
|
||||
var entry = _dtrBar.Get("Lightfinder");
|
||||
entry.OnClick = interactionEvent => OnLightfinderEntryClick(interactionEvent);
|
||||
return entry;
|
||||
}
|
||||
|
||||
private void OnStatusEntryClick(DtrInteractionEvent interactionEvent)
|
||||
{
|
||||
if (interactionEvent.ClickType.Equals(MouseClickType.Left))
|
||||
{
|
||||
_lightlessMediator.Publish(new UiToggleMessage(typeof(CompactUi)));
|
||||
}
|
||||
else if (interactionEvent.ClickType.Equals(MouseClickType.Left) && interactionEvent.ModifierKeys.Equals(ClickModifierKeys.Shift))
|
||||
{
|
||||
_lightlessMediator.Publish(new UiToggleMessage(typeof(SettingsUi)));
|
||||
if (interactionEvent.ModifierKeys.HasFlag(ClickModifierKeys.Shift))
|
||||
{
|
||||
_lightlessMediator.Publish(new UiToggleMessage(typeof(SettingsUi)));
|
||||
}
|
||||
else
|
||||
{
|
||||
_lightlessMediator.Publish(new UiToggleMessage(typeof(CompactUi)));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (interactionEvent.ClickType.Equals(MouseClickType.Right))
|
||||
@@ -131,6 +200,17 @@ public sealed class DtrEntry : IDisposable, IHostedService
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLightfinderEntryClick(DtrInteractionEvent interactionEvent)
|
||||
{
|
||||
if (!_configService.Current.ShowLightfinderInDtr)
|
||||
return;
|
||||
|
||||
if (interactionEvent.ClickType.Equals(MouseClickType.Left))
|
||||
{
|
||||
_broadcastService.ToggleBroadcast();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RunAsync()
|
||||
{
|
||||
while (!_cancellationTokenSource.IsCancellationRequested)
|
||||
@@ -143,96 +223,278 @@ public sealed class DtrEntry : IDisposable, IHostedService
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!_configService.Current.EnableDtrEntry || !_configService.Current.HasValidSetup())
|
||||
{
|
||||
if (_entry.IsValueCreated && _entry.Value.Shown)
|
||||
{
|
||||
_logger.LogInformation("Disabling entry");
|
||||
var config = _configService.Current;
|
||||
|
||||
Clear();
|
||||
}
|
||||
if (!config.HasValidSetup())
|
||||
{
|
||||
HideStatusEntry();
|
||||
HideLightfinderEntry();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_entry.Value.Shown)
|
||||
{
|
||||
_logger.LogInformation("Showing entry");
|
||||
_entry.Value.Shown = true;
|
||||
}
|
||||
if (config.EnableDtrEntry)
|
||||
UpdateStatusEntry(config);
|
||||
else
|
||||
HideStatusEntry();
|
||||
|
||||
if (config.ShowLightfinderInDtr)
|
||||
UpdateLightfinderEntry(config);
|
||||
else
|
||||
HideLightfinderEntry();
|
||||
}
|
||||
|
||||
private void UpdateStatusEntry(LightlessConfig config)
|
||||
{
|
||||
string text;
|
||||
string tooltip;
|
||||
Colors colors;
|
||||
|
||||
if (_apiController.IsConnected)
|
||||
{
|
||||
var pairCount = _pairManager.GetVisibleUserCount();
|
||||
text = $"\uE044 {pairCount}";
|
||||
if (pairCount > 0)
|
||||
{
|
||||
IEnumerable<string> visiblePairs;
|
||||
if (_configService.Current.ShowUidInDtrTooltip)
|
||||
{
|
||||
visiblePairs = _pairManager.GetOnlineUserPairs()
|
||||
.Where(x => x.IsVisible)
|
||||
.Select(x => string.Format("{0} ({1})", _configService.Current.PreferNoteInDtrTooltip ? x.GetNote() ?? x.PlayerName : x.PlayerName, x.UserData.AliasOrUID));
|
||||
}
|
||||
else
|
||||
{
|
||||
visiblePairs = _pairManager.GetOnlineUserPairs()
|
||||
.Where(x => x.IsVisible)
|
||||
.Select(x => string.Format("{0}", _configService.Current.PreferNoteInDtrTooltip ? x.GetNote() ?? x.PlayerName : x.PlayerName));
|
||||
}
|
||||
var preferNote = config.PreferNoteInDtrTooltip;
|
||||
var showUid = config.ShowUidInDtrTooltip;
|
||||
|
||||
var visiblePairsQuery = _pairManager.GetOnlineUserPairs()
|
||||
.Where(x => x.IsVisible);
|
||||
|
||||
IEnumerable<string> visiblePairs = showUid
|
||||
? visiblePairsQuery.Select(x => string.Format("{0} ({1})", preferNote ? x.GetNote() ?? x.PlayerName : x.PlayerName, x.UserData.AliasOrUID))
|
||||
: visiblePairsQuery.Select(x => string.Format("{0}", preferNote ? x.GetNote() ?? x.PlayerName : x.PlayerName));
|
||||
|
||||
tooltip = $"Lightless Sync: Connected{Environment.NewLine}----------{Environment.NewLine}{string.Join(Environment.NewLine, visiblePairs)}";
|
||||
colors = _configService.Current.DtrColorsPairsInRange;
|
||||
colors = config.DtrColorsPairsInRange;
|
||||
}
|
||||
else
|
||||
{
|
||||
tooltip = "Lightless Sync: Connected";
|
||||
colors = _configService.Current.DtrColorsDefault;
|
||||
colors = config.DtrColorsDefault;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
text = "\uE044 \uE04C";
|
||||
tooltip = "Lightless Sync: Not Connected";
|
||||
colors = _configService.Current.DtrColorsNotConnected;
|
||||
colors = config.DtrColorsNotConnected;
|
||||
}
|
||||
|
||||
if (!_configService.Current.UseColorsInDtr)
|
||||
if (!config.UseColorsInDtr)
|
||||
colors = default;
|
||||
|
||||
if (!string.Equals(text, _text, StringComparison.Ordinal) || !string.Equals(tooltip, _tooltip, StringComparison.Ordinal) || colors != _colors)
|
||||
var statusEntry = _statusEntry.Value;
|
||||
if (!statusEntry.Shown)
|
||||
{
|
||||
_text = text;
|
||||
_tooltip = tooltip;
|
||||
_colors = colors;
|
||||
_entry.Value.Text = BuildColoredSeString(text, colors);
|
||||
_entry.Value.Tooltip = tooltip;
|
||||
_logger.LogInformation("Showing status entry");
|
||||
statusEntry.Shown = true;
|
||||
}
|
||||
|
||||
bool statusNeedsUpdate =
|
||||
!string.Equals(text, _statusText, StringComparison.Ordinal) ||
|
||||
!string.Equals(tooltip, _statusTooltip, StringComparison.Ordinal) ||
|
||||
colors != _statusColors;
|
||||
|
||||
if (statusNeedsUpdate)
|
||||
{
|
||||
statusEntry.Text = BuildColoredSeString(text, colors);
|
||||
statusEntry.Tooltip = tooltip;
|
||||
_statusText = text;
|
||||
_statusTooltip = tooltip;
|
||||
_statusColors = colors;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateLightfinderEntry(LightlessConfig config)
|
||||
{
|
||||
var lightfinderEntry = _lightfinderEntry.Value;
|
||||
if (!lightfinderEntry.Shown)
|
||||
{
|
||||
_logger.LogInformation("Showing Lightfinder entry");
|
||||
lightfinderEntry.Shown = true;
|
||||
}
|
||||
|
||||
var indicator = BuildLightfinderIndicator();
|
||||
var lightfinderText = indicator.Text ?? string.Empty;
|
||||
var lightfinderColors = config.UseLightfinderColorsInDtr ? indicator.Colors : default;
|
||||
var lightfinderTooltip = BuildLightfinderTooltip(indicator.Tooltip);
|
||||
|
||||
bool lightfinderNeedsUpdate =
|
||||
!string.Equals(lightfinderText, _lightfinderText, StringComparison.Ordinal) ||
|
||||
!string.Equals(lightfinderTooltip, _lightfinderTooltip, StringComparison.Ordinal) ||
|
||||
lightfinderColors != _lightfinderColors;
|
||||
|
||||
if (lightfinderNeedsUpdate)
|
||||
{
|
||||
lightfinderEntry.Text = BuildColoredSeString(lightfinderText, lightfinderColors);
|
||||
lightfinderEntry.Tooltip = lightfinderTooltip;
|
||||
_lightfinderText = lightfinderText;
|
||||
_lightfinderTooltip = lightfinderTooltip;
|
||||
_lightfinderColors = lightfinderColors;
|
||||
}
|
||||
}
|
||||
|
||||
private string? GetLocalHashedCid()
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
if (_localHashedCid is not null && now - _localHashedCidFetchedAt < _localHashedCidCacheDuration)
|
||||
return _localHashedCid;
|
||||
|
||||
try
|
||||
{
|
||||
var cid = _dalamudUtilService.GetCIDAsync().GetAwaiter().GetResult();
|
||||
var hashedCid = cid.ToString().GetHash256();
|
||||
_localHashedCid = hashedCid;
|
||||
_localHashedCidFetchedAt = now;
|
||||
return hashedCid;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (now >= _localHashedCidNextErrorLog)
|
||||
{
|
||||
_logger.LogDebug(ex, "Failed to refresh local hashed CID for Lightfinder DTR entry.");
|
||||
_localHashedCidNextErrorLog = now + _localHashedCidErrorCooldown;
|
||||
}
|
||||
|
||||
_localHashedCid = null;
|
||||
_localHashedCidFetchedAt = now;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private int GetNearbyBroadcastCount()
|
||||
{
|
||||
var localHashedCid = GetLocalHashedCid();
|
||||
return _broadcastScannerService.CountActiveBroadcasts(
|
||||
string.IsNullOrEmpty(localHashedCid) ? null : localHashedCid);
|
||||
}
|
||||
|
||||
private int GetPendingPairRequestCount()
|
||||
{
|
||||
try
|
||||
{
|
||||
return _pairRequestService.GetActiveRequests().Count;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
if (now >= _pairRequestNextErrorLog)
|
||||
{
|
||||
_logger.LogDebug(ex, "Failed to retrieve pair request count for Lightfinder DTR entry.");
|
||||
_pairRequestNextErrorLog = now + _localHashedCidErrorCooldown;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private (string Text, Colors Colors, string Tooltip) BuildLightfinderIndicator()
|
||||
{
|
||||
var config = _configService.Current;
|
||||
const string icon = "\uE048";
|
||||
if (!_broadcastService.IsLightFinderAvailable)
|
||||
{
|
||||
return ($"{icon} --", SwapColorChannels(config.DtrColorsLightfinderUnavailable), "Lightfinder - Unavailable on this server.");
|
||||
}
|
||||
|
||||
if (_broadcastService.IsBroadcasting)
|
||||
{
|
||||
var tooltipBuilder = new StringBuilder("Lightfinder - Enabled");
|
||||
|
||||
switch (config.LightfinderDtrDisplayMode)
|
||||
{
|
||||
case LightfinderDtrDisplayMode.PendingPairRequests:
|
||||
{
|
||||
var requestCount = GetPendingPairRequestCount();
|
||||
tooltipBuilder.AppendLine();
|
||||
tooltipBuilder.Append("Pending pair requests: ").Append(requestCount);
|
||||
return ($"{icon} Requests {requestCount}", SwapColorChannels(config.DtrColorsLightfinderEnabled), tooltipBuilder.ToString());
|
||||
}
|
||||
default:
|
||||
{
|
||||
var broadcastCount = GetNearbyBroadcastCount();
|
||||
tooltipBuilder.AppendLine();
|
||||
tooltipBuilder.Append("Nearby Lightfinder users: ").Append(broadcastCount);
|
||||
return ($"{icon} {broadcastCount}", SwapColorChannels(config.DtrColorsLightfinderEnabled), tooltipBuilder.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var tooltip = new StringBuilder("Lightfinder - Disabled");
|
||||
var colors = SwapColorChannels(config.DtrColorsLightfinderDisabled);
|
||||
if (_broadcastService.RemainingCooldown is { } cooldown && cooldown > TimeSpan.Zero)
|
||||
{
|
||||
tooltip.AppendLine();
|
||||
tooltip.Append("Cooldown: ").Append(Math.Ceiling(cooldown.TotalSeconds)).Append("s");
|
||||
colors = SwapColorChannels(config.DtrColorsLightfinderCooldown);
|
||||
}
|
||||
|
||||
return ($"{icon} OFF", colors, tooltip.ToString());
|
||||
}
|
||||
|
||||
private static string BuildLightfinderTooltip(string baseTooltip)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
if (!string.IsNullOrWhiteSpace(baseTooltip))
|
||||
builder.Append(baseTooltip.TrimEnd());
|
||||
else
|
||||
builder.Append("Lightfinder status unavailable.");
|
||||
|
||||
return builder.ToString().TrimEnd();
|
||||
}
|
||||
|
||||
private static void AppendColoredSegment(SeStringBuilder builder, string? text, Colors colors)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text))
|
||||
return;
|
||||
|
||||
if (colors.Foreground != default)
|
||||
builder.Add(BuildColorStartPayload(_colorTypeForeground, colors.Foreground));
|
||||
if (colors.Glow != default)
|
||||
builder.Add(BuildColorStartPayload(_colorTypeGlow, colors.Glow));
|
||||
|
||||
builder.AddText(text);
|
||||
|
||||
if (colors.Glow != default)
|
||||
builder.Add(BuildColorEndPayload(_colorTypeGlow));
|
||||
if (colors.Foreground != default)
|
||||
builder.Add(BuildColorEndPayload(_colorTypeForeground));
|
||||
}
|
||||
|
||||
#region Colored SeString
|
||||
private const byte _colorTypeForeground = 0x13;
|
||||
private const byte _colorTypeGlow = 0x14;
|
||||
|
||||
private static Colors SwapColorChannels(Colors colors)
|
||||
=> new(SwapColorComponent(colors.Foreground), SwapColorComponent(colors.Glow));
|
||||
|
||||
private static uint SwapColorComponent(uint color)
|
||||
{
|
||||
if (color == 0)
|
||||
return 0;
|
||||
|
||||
return ((color & 0xFFu) << 16) | (color & 0xFF00u) | ((color >> 16) & 0xFFu);
|
||||
}
|
||||
|
||||
private static SeString BuildColoredSeString(string text, Colors colors)
|
||||
{
|
||||
var ssb = new SeStringBuilder();
|
||||
if (colors.Foreground != default)
|
||||
ssb.Add(BuildColorStartPayload(_colorTypeForeground, colors.Foreground));
|
||||
if (colors.Glow != default)
|
||||
ssb.Add(BuildColorStartPayload(_colorTypeGlow, colors.Glow));
|
||||
ssb.AddText(text);
|
||||
if (colors.Glow != default)
|
||||
ssb.Add(BuildColorEndPayload(_colorTypeGlow));
|
||||
if (colors.Foreground != default)
|
||||
ssb.Add(BuildColorEndPayload(_colorTypeForeground));
|
||||
AppendColoredSegment(ssb, text, colors);
|
||||
return ssb.Build();
|
||||
}
|
||||
|
||||
private static RawPayload BuildColorStartPayload(byte colorType, uint color)
|
||||
=> new(unchecked([0x02, colorType, 0x05, 0xF6, byte.Max((byte)color, 0x01), byte.Max((byte)(color >> 8), 0x01), byte.Max((byte)(color >> 16), 0x01), 0x03]));
|
||||
=> new(unchecked([
|
||||
0x02,
|
||||
colorType,
|
||||
0x05,
|
||||
0xF6,
|
||||
byte.Max((byte)color, (byte)0x01),
|
||||
byte.Max((byte)(color >> 8), (byte)0x01),
|
||||
byte.Max((byte)(color >> 16), (byte)0x01),
|
||||
0x03
|
||||
]));
|
||||
|
||||
private static RawPayload BuildColorEndPayload(byte colorType)
|
||||
=> new([0x02, colorType, 0x02, 0xEC, 0x03]);
|
||||
|
||||
596
LightlessSync/UI/LightlessNotificationUI.cs
Normal file
596
LightlessSync/UI/LightlessNotificationUI.cs
Normal file
@@ -0,0 +1,596 @@
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using LightlessSync.LightlessConfiguration;
|
||||
using LightlessSync.LightlessConfiguration.Models;
|
||||
using LightlessSync.Services;
|
||||
using LightlessSync.Services.Mediator;
|
||||
using LightlessSync.UI.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using System.Numerics;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace LightlessSync.UI;
|
||||
|
||||
public class LightlessNotificationUI : WindowMediatorSubscriberBase
|
||||
{
|
||||
private const float NotificationMinHeight = 60f;
|
||||
private const float NotificationMaxHeight = 250f;
|
||||
private const float WindowPaddingOffset = 6f;
|
||||
private const float SlideAnimationDistance = 100f;
|
||||
private const float OutAnimationSpeedMultiplier = 0.7f;
|
||||
|
||||
private readonly List<LightlessNotification> _notifications = new();
|
||||
private readonly object _notificationLock = new();
|
||||
private readonly LightlessConfigService _configService;
|
||||
|
||||
public LightlessNotificationUI(ILogger<LightlessNotificationUI> logger, LightlessMediator mediator, PerformanceCollectorService performanceCollector, LightlessConfigService configService)
|
||||
: base(logger, mediator, "Lightless Notifications##LightlessNotifications", performanceCollector)
|
||||
{
|
||||
_configService = configService;
|
||||
Flags = ImGuiWindowFlags.NoDecoration |
|
||||
ImGuiWindowFlags.NoMove |
|
||||
ImGuiWindowFlags.NoResize |
|
||||
ImGuiWindowFlags.NoSavedSettings |
|
||||
ImGuiWindowFlags.NoFocusOnAppearing |
|
||||
ImGuiWindowFlags.NoNav |
|
||||
ImGuiWindowFlags.NoBackground |
|
||||
ImGuiWindowFlags.NoCollapse |
|
||||
ImGuiWindowFlags.AlwaysAutoResize;
|
||||
|
||||
PositionCondition = ImGuiCond.Always;
|
||||
SizeCondition = ImGuiCond.FirstUseEver;
|
||||
IsOpen = false;
|
||||
RespectCloseHotkey = false;
|
||||
DisableWindowSounds = true;
|
||||
|
||||
Mediator.Subscribe<LightlessNotificationMessage>(this, HandleNotificationMessage);
|
||||
Mediator.Subscribe<LightlessNotificationDismissMessage>(this, HandleNotificationDismissMessage);
|
||||
}
|
||||
private void HandleNotificationMessage(LightlessNotificationMessage message) =>
|
||||
AddNotification(message.Notification);
|
||||
|
||||
private void HandleNotificationDismissMessage(LightlessNotificationDismissMessage message) =>
|
||||
RemoveNotification(message.NotificationId);
|
||||
|
||||
public void AddNotification(LightlessNotification notification)
|
||||
{
|
||||
lock (_notificationLock)
|
||||
{
|
||||
var existingNotification = _notifications.FirstOrDefault(n => n.Id == notification.Id);
|
||||
if (existingNotification != null)
|
||||
{
|
||||
UpdateExistingNotification(existingNotification, notification);
|
||||
}
|
||||
else
|
||||
{
|
||||
_notifications.Add(notification);
|
||||
_logger.LogDebug("Added new notification: {Title}", notification.Title);
|
||||
}
|
||||
|
||||
if (!IsOpen) IsOpen = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateExistingNotification(LightlessNotification existing, LightlessNotification updated)
|
||||
{
|
||||
existing.Message = updated.Message;
|
||||
existing.Progress = updated.Progress;
|
||||
existing.ShowProgress = updated.ShowProgress;
|
||||
existing.Title = updated.Title;
|
||||
_logger.LogDebug("Updated existing notification: {Title}", updated.Title);
|
||||
}
|
||||
|
||||
public void RemoveNotification(string id)
|
||||
{
|
||||
lock (_notificationLock)
|
||||
{
|
||||
var notification = _notifications.FirstOrDefault(n => n.Id == id);
|
||||
if (notification != null)
|
||||
{
|
||||
StartOutAnimation(notification);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void StartOutAnimation(LightlessNotification notification)
|
||||
{
|
||||
notification.IsAnimatingOut = true;
|
||||
notification.IsAnimatingIn = false;
|
||||
}
|
||||
|
||||
protected override void DrawInternal()
|
||||
{
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
|
||||
|
||||
lock (_notificationLock)
|
||||
{
|
||||
UpdateNotifications();
|
||||
|
||||
if (_notifications.Count == 0)
|
||||
{
|
||||
ImGui.PopStyleVar();
|
||||
IsOpen = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var viewport = ImGui.GetMainViewport();
|
||||
Position = CalculateWindowPosition(viewport);
|
||||
DrawAllNotifications();
|
||||
}
|
||||
|
||||
ImGui.PopStyleVar();
|
||||
}
|
||||
|
||||
private Vector2 CalculateWindowPosition(ImGuiViewportPtr viewport)
|
||||
{
|
||||
var x = viewport.WorkPos.X + viewport.WorkSize.X -
|
||||
_configService.Current.NotificationWidth -
|
||||
_configService.Current.NotificationOffsetX -
|
||||
WindowPaddingOffset;
|
||||
var y = viewport.WorkPos.Y + _configService.Current.NotificationOffsetY;
|
||||
return new Vector2(x, y);
|
||||
}
|
||||
|
||||
private void DrawAllNotifications()
|
||||
{
|
||||
for (int i = 0; i < _notifications.Count; i++)
|
||||
{
|
||||
DrawNotification(_notifications[i], i);
|
||||
|
||||
if (i < _notifications.Count - 1)
|
||||
{
|
||||
ImGui.Dummy(new Vector2(0, _configService.Current.NotificationSpacing));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateNotifications()
|
||||
{
|
||||
var deltaTime = ImGui.GetIO().DeltaTime;
|
||||
EnforceMaxNotificationLimit();
|
||||
UpdateAnimationsAndRemoveExpired(deltaTime);
|
||||
}
|
||||
|
||||
private void EnforceMaxNotificationLimit()
|
||||
{
|
||||
var maxNotifications = _configService.Current.MaxSimultaneousNotifications;
|
||||
while (_notifications.Count(n => !n.IsAnimatingOut) > maxNotifications)
|
||||
{
|
||||
var oldestNotification = _notifications
|
||||
.Where(n => !n.IsAnimatingOut)
|
||||
.OrderBy(n => n.CreatedAt)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (oldestNotification != null)
|
||||
{
|
||||
StartOutAnimation(oldestNotification);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateAnimationsAndRemoveExpired(float deltaTime)
|
||||
{
|
||||
for (int i = _notifications.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var notification = _notifications[i];
|
||||
UpdateNotificationAnimation(notification, deltaTime);
|
||||
|
||||
if (ShouldRemoveNotification(notification))
|
||||
{
|
||||
_notifications.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateNotificationAnimation(LightlessNotification notification, float deltaTime)
|
||||
{
|
||||
if (notification.IsAnimatingIn && notification.AnimationProgress < 1f)
|
||||
{
|
||||
notification.AnimationProgress = Math.Min(1f,
|
||||
notification.AnimationProgress + deltaTime * _configService.Current.NotificationAnimationSpeed);
|
||||
}
|
||||
else if (notification.IsAnimatingOut && notification.AnimationProgress > 0f)
|
||||
{
|
||||
notification.AnimationProgress = Math.Max(0f,
|
||||
notification.AnimationProgress - deltaTime * _configService.Current.NotificationAnimationSpeed * OutAnimationSpeedMultiplier);
|
||||
}
|
||||
else if (!notification.IsAnimatingOut && !notification.IsDismissed)
|
||||
{
|
||||
notification.IsAnimatingIn = false;
|
||||
|
||||
if (notification.IsExpired)
|
||||
{
|
||||
StartOutAnimation(notification);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldRemoveNotification(LightlessNotification notification) =>
|
||||
notification.IsAnimatingOut && notification.AnimationProgress <= 0.01f;
|
||||
|
||||
private void DrawNotification(LightlessNotification notification, int index)
|
||||
{
|
||||
var alpha = notification.AnimationProgress;
|
||||
if (alpha <= 0f) return;
|
||||
|
||||
var slideOffset = (1f - alpha) * SlideAnimationDistance;
|
||||
var originalCursorPos = ImGui.GetCursorPos();
|
||||
ImGui.SetCursorPosX(originalCursorPos.X + slideOffset);
|
||||
|
||||
var notificationHeight = CalculateNotificationHeight(notification);
|
||||
var notificationWidth = _configService.Current.NotificationWidth - slideOffset;
|
||||
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
|
||||
|
||||
using var child = ImRaii.Child($"notification_{notification.Id}",
|
||||
new Vector2(notificationWidth, notificationHeight),
|
||||
false, ImGuiWindowFlags.NoScrollbar);
|
||||
|
||||
if (child.Success)
|
||||
{
|
||||
DrawNotificationContent(notification, alpha);
|
||||
}
|
||||
|
||||
ImGui.PopStyleVar();
|
||||
}
|
||||
|
||||
private void DrawNotificationContent(LightlessNotification notification, float alpha)
|
||||
{
|
||||
var drawList = ImGui.GetWindowDrawList();
|
||||
var windowPos = ImGui.GetWindowPos();
|
||||
var windowSize = ImGui.GetWindowSize();
|
||||
|
||||
var bgColor = CalculateBackgroundColor(alpha, ImGui.IsWindowHovered());
|
||||
var accentColor = GetNotificationAccentColor(notification.Type);
|
||||
accentColor.W *= alpha;
|
||||
|
||||
DrawShadow(drawList, windowPos, windowSize, alpha);
|
||||
HandleClickToDismiss(notification);
|
||||
DrawBackground(drawList, windowPos, windowSize, bgColor);
|
||||
DrawAccentBar(drawList, windowPos, windowSize, accentColor);
|
||||
DrawDurationProgressBar(notification, alpha, windowPos, windowSize, drawList);
|
||||
DrawNotificationText(notification, alpha);
|
||||
}
|
||||
|
||||
private Vector4 CalculateBackgroundColor(float alpha, bool isHovered)
|
||||
{
|
||||
var baseOpacity = _configService.Current.NotificationOpacity;
|
||||
var finalOpacity = baseOpacity * alpha;
|
||||
var bgColor = new Vector4(30f/255f, 30f/255f, 30f/255f, finalOpacity);
|
||||
|
||||
if (isHovered)
|
||||
{
|
||||
bgColor *= 1.1f;
|
||||
bgColor.W = Math.Min(bgColor.W, 0.98f);
|
||||
}
|
||||
|
||||
return bgColor;
|
||||
}
|
||||
|
||||
private void DrawShadow(ImDrawListPtr drawList, Vector2 windowPos, Vector2 windowSize, float alpha)
|
||||
{
|
||||
var shadowOffset = new Vector2(1f, 1f);
|
||||
var shadowColor = new Vector4(0f, 0f, 0f, 0.4f * alpha);
|
||||
drawList.AddRectFilled(
|
||||
windowPos + shadowOffset,
|
||||
windowPos + windowSize + shadowOffset,
|
||||
ImGui.ColorConvertFloat4ToU32(shadowColor),
|
||||
3f
|
||||
);
|
||||
}
|
||||
|
||||
private void HandleClickToDismiss(LightlessNotification notification)
|
||||
{
|
||||
if (ImGui.IsWindowHovered() &&
|
||||
_configService.Current.DismissNotificationOnClick &&
|
||||
!notification.Actions.Any() &&
|
||||
ImGui.IsMouseClicked(ImGuiMouseButton.Left))
|
||||
{
|
||||
notification.IsDismissed = true;
|
||||
StartOutAnimation(notification);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawBackground(ImDrawListPtr drawList, Vector2 windowPos, Vector2 windowSize, Vector4 bgColor)
|
||||
{
|
||||
drawList.AddRectFilled(
|
||||
windowPos,
|
||||
windowPos + windowSize,
|
||||
ImGui.ColorConvertFloat4ToU32(bgColor),
|
||||
3f
|
||||
);
|
||||
}
|
||||
|
||||
private void DrawAccentBar(ImDrawListPtr drawList, Vector2 windowPos, Vector2 windowSize, Vector4 accentColor)
|
||||
{
|
||||
var accentWidth = _configService.Current.NotificationAccentBarWidth;
|
||||
if (accentWidth > 0f)
|
||||
{
|
||||
drawList.AddRectFilled(
|
||||
windowPos,
|
||||
windowPos + new Vector2(accentWidth, windowSize.Y),
|
||||
ImGui.ColorConvertFloat4ToU32(accentColor),
|
||||
3f
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawDurationProgressBar(LightlessNotification notification, float alpha, Vector2 windowPos, Vector2 windowSize, ImDrawListPtr drawList)
|
||||
{
|
||||
var progress = CalculateProgress(notification);
|
||||
var progressBarColor = UIColors.Get("LightlessBlue");
|
||||
var progressHeight = 2f;
|
||||
var progressY = windowPos.Y + windowSize.Y - progressHeight;
|
||||
var progressWidth = windowSize.X * progress;
|
||||
|
||||
DrawProgressBackground(drawList, windowPos, windowSize, progressY, progressHeight, progressBarColor, alpha);
|
||||
|
||||
if (progress > 0)
|
||||
{
|
||||
DrawProgressForeground(drawList, windowPos, progressY, progressHeight, progressWidth, progressBarColor, alpha);
|
||||
}
|
||||
}
|
||||
|
||||
private float CalculateProgress(LightlessNotification notification)
|
||||
{
|
||||
if (notification.Type == NotificationType.Download && notification.ShowProgress)
|
||||
{
|
||||
return Math.Clamp(notification.Progress, 0f, 1f);
|
||||
}
|
||||
|
||||
var elapsed = DateTime.UtcNow - notification.CreatedAt;
|
||||
return Math.Min(1.0f, (float)(elapsed.TotalSeconds / notification.Duration.TotalSeconds));
|
||||
}
|
||||
|
||||
private void DrawProgressBackground(ImDrawListPtr drawList, Vector2 windowPos, Vector2 windowSize, float progressY, float progressHeight, Vector4 progressBarColor, float alpha)
|
||||
{
|
||||
var bgProgressColor = new Vector4(progressBarColor.X * 0.3f, progressBarColor.Y * 0.3f, progressBarColor.Z * 0.3f, 0.5f * alpha);
|
||||
drawList.AddRectFilled(
|
||||
new Vector2(windowPos.X, progressY),
|
||||
new Vector2(windowPos.X + windowSize.X, progressY + progressHeight),
|
||||
ImGui.ColorConvertFloat4ToU32(bgProgressColor),
|
||||
0f
|
||||
);
|
||||
}
|
||||
|
||||
private void DrawProgressForeground(ImDrawListPtr drawList, Vector2 windowPos, float progressY, float progressHeight, float progressWidth, Vector4 progressBarColor, float alpha)
|
||||
{
|
||||
var progressColor = progressBarColor;
|
||||
progressColor.W *= alpha;
|
||||
drawList.AddRectFilled(
|
||||
new Vector2(windowPos.X, progressY),
|
||||
new Vector2(windowPos.X + progressWidth, progressY + progressHeight),
|
||||
ImGui.ColorConvertFloat4ToU32(progressColor),
|
||||
0f
|
||||
);
|
||||
}
|
||||
|
||||
private void DrawNotificationText(LightlessNotification notification, float alpha)
|
||||
{
|
||||
var padding = new Vector2(10f, 6f);
|
||||
var contentPos = new Vector2(padding.X, padding.Y);
|
||||
var windowSize = ImGui.GetWindowSize();
|
||||
var contentSize = new Vector2(windowSize.X - padding.X, windowSize.Y - padding.Y * 2);
|
||||
|
||||
ImGui.SetCursorPos(contentPos);
|
||||
|
||||
var titleHeight = DrawTitle(notification, contentSize.X, alpha);
|
||||
DrawMessage(notification, contentPos, contentSize.X, titleHeight, alpha);
|
||||
|
||||
if (notification.Actions.Count > 0)
|
||||
{
|
||||
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + ImGui.GetStyle().ItemSpacing.Y);
|
||||
ImGui.SetCursorPosX(contentPos.X);
|
||||
DrawNotificationActions(notification, contentSize.X, alpha);
|
||||
}
|
||||
}
|
||||
|
||||
private float DrawTitle(LightlessNotification notification, float contentWidth, float alpha)
|
||||
{
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, new Vector4(1f, 1f, 1f, alpha)))
|
||||
{
|
||||
ImGui.PushTextWrapPos(ImGui.GetCursorPosX() + contentWidth);
|
||||
var titleStartY = ImGui.GetCursorPosY();
|
||||
|
||||
var titleText = _configService.Current.ShowNotificationTimestamp
|
||||
? $"[{notification.CreatedAt.ToLocalTime():HH:mm:ss}] {notification.Title}"
|
||||
: notification.Title;
|
||||
|
||||
ImGui.TextWrapped(titleText);
|
||||
var titleHeight = ImGui.GetCursorPosY() - titleStartY;
|
||||
ImGui.PopTextWrapPos();
|
||||
return titleHeight;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawMessage(LightlessNotification notification, Vector2 contentPos, float contentWidth, float titleHeight, float alpha)
|
||||
{
|
||||
if (string.IsNullOrEmpty(notification.Message)) return;
|
||||
|
||||
ImGui.SetCursorPos(contentPos + new Vector2(0f, titleHeight + 4f));
|
||||
ImGui.PushTextWrapPos(ImGui.GetCursorPosX() + contentWidth);
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, new Vector4(0.9f, 0.9f, 0.9f, alpha)))
|
||||
{
|
||||
ImGui.TextWrapped(notification.Message);
|
||||
}
|
||||
ImGui.PopTextWrapPos();
|
||||
}
|
||||
|
||||
private void DrawNotificationActions(LightlessNotification notification, float availableWidth, float alpha)
|
||||
{
|
||||
var buttonSpacing = 8f;
|
||||
var rightPadding = 10f;
|
||||
var usableWidth = availableWidth - rightPadding;
|
||||
var totalSpacing = (notification.Actions.Count - 1) * buttonSpacing;
|
||||
var buttonWidth = (usableWidth - totalSpacing) / notification.Actions.Count;
|
||||
|
||||
_logger.LogDebug("Drawing {ActionCount} notification actions, buttonWidth: {ButtonWidth}, availableWidth: {AvailableWidth}",
|
||||
notification.Actions.Count, buttonWidth, availableWidth);
|
||||
|
||||
var startCursorPos = ImGui.GetCursorPos();
|
||||
|
||||
for (int i = 0; i < notification.Actions.Count; i++)
|
||||
{
|
||||
var action = notification.Actions[i];
|
||||
|
||||
if (i > 0)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
var currentX = startCursorPos.X + i * (buttonWidth + buttonSpacing);
|
||||
ImGui.SetCursorPosX(currentX);
|
||||
}
|
||||
DrawActionButton(action, notification, alpha, buttonWidth);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawActionButton(LightlessNotificationAction action, LightlessNotification notification, float alpha, float buttonWidth)
|
||||
{
|
||||
_logger.LogDebug("Drawing action button: {ActionId} - {ActionLabel}, width: {ButtonWidth}", action.Id, action.Label, buttonWidth);
|
||||
|
||||
var buttonColor = action.Color;
|
||||
buttonColor.W *= alpha;
|
||||
|
||||
var hoveredColor = buttonColor * 1.1f;
|
||||
hoveredColor.W = buttonColor.W;
|
||||
|
||||
var activeColor = buttonColor * 0.9f;
|
||||
activeColor.W = buttonColor.W;
|
||||
|
||||
using (ImRaii.PushColor(ImGuiCol.Button, buttonColor))
|
||||
using (ImRaii.PushColor(ImGuiCol.ButtonHovered, hoveredColor))
|
||||
using (ImRaii.PushColor(ImGuiCol.ButtonActive, activeColor))
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, new Vector4(1f, 1f, 1f, alpha)))
|
||||
{
|
||||
var buttonPressed = false;
|
||||
|
||||
if (action.Icon != FontAwesomeIcon.None)
|
||||
{
|
||||
buttonPressed = DrawIconTextButton(action.Icon, action.Label, buttonWidth, alpha);
|
||||
}
|
||||
else
|
||||
{
|
||||
buttonPressed = ImGui.Button(action.Label, new Vector2(buttonWidth, 0));
|
||||
}
|
||||
|
||||
_logger.LogDebug("Button {ActionId} pressed: {ButtonPressed}", action.Id, buttonPressed);
|
||||
|
||||
if (buttonPressed)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogDebug("Executing action: {ActionId}", action.Id);
|
||||
action.OnClick(notification);
|
||||
_logger.LogDebug("Action executed successfully: {ActionId}", action.Id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error executing notification action: {ActionId}", action.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool DrawIconTextButton(FontAwesomeIcon icon, string text, float width, float alpha)
|
||||
{
|
||||
var drawList = ImGui.GetWindowDrawList();
|
||||
var cursorPos = ImGui.GetCursorScreenPos();
|
||||
var frameHeight = ImGui.GetFrameHeight();
|
||||
|
||||
Vector2 iconSize;
|
||||
using (ImRaii.PushFont(UiBuilder.IconFont))
|
||||
{
|
||||
iconSize = ImGui.CalcTextSize(icon.ToIconString());
|
||||
}
|
||||
|
||||
var textSize = ImGui.CalcTextSize(text);
|
||||
var spacing = 3f * ImGuiHelpers.GlobalScale;
|
||||
var totalTextWidth = iconSize.X + spacing + textSize.X;
|
||||
|
||||
var buttonPressed = ImGui.InvisibleButton($"btn_{icon}_{text}", new Vector2(width, frameHeight));
|
||||
|
||||
var buttonMin = ImGui.GetItemRectMin();
|
||||
var buttonMax = ImGui.GetItemRectMax();
|
||||
var buttonSize = buttonMax - buttonMin;
|
||||
|
||||
var buttonColor = ImGui.GetColorU32(ImGuiCol.Button);
|
||||
if (ImGui.IsItemHovered())
|
||||
buttonColor = ImGui.GetColorU32(ImGuiCol.ButtonHovered);
|
||||
if (ImGui.IsItemActive())
|
||||
buttonColor = ImGui.GetColorU32(ImGuiCol.ButtonActive);
|
||||
|
||||
drawList.AddRectFilled(buttonMin, buttonMax, buttonColor, 3f);
|
||||
|
||||
var iconPos = buttonMin + new Vector2((buttonSize.X - totalTextWidth) / 2f, (buttonSize.Y - iconSize.Y) / 2f);
|
||||
var textPos = iconPos + new Vector2(iconSize.X + spacing, (iconSize.Y - textSize.Y) / 2f);
|
||||
|
||||
var textColor = ImGui.GetColorU32(ImGuiCol.Text);
|
||||
|
||||
// Draw icon
|
||||
using (ImRaii.PushFont(UiBuilder.IconFont))
|
||||
{
|
||||
drawList.AddText(iconPos, textColor, icon.ToIconString());
|
||||
}
|
||||
|
||||
// Draw text
|
||||
drawList.AddText(textPos, textColor, text);
|
||||
|
||||
return buttonPressed;
|
||||
}
|
||||
|
||||
private float CalculateNotificationHeight(LightlessNotification notification)
|
||||
{
|
||||
var contentWidth = _configService.Current.NotificationWidth - 35f;
|
||||
var height = 12f;
|
||||
|
||||
height += CalculateTitleHeight(notification, contentWidth);
|
||||
height += CalculateMessageHeight(notification, contentWidth);
|
||||
|
||||
if (notification.ShowProgress)
|
||||
{
|
||||
height += 12f;
|
||||
}
|
||||
|
||||
if (notification.Actions.Count > 0)
|
||||
{
|
||||
height += ImGui.GetStyle().ItemSpacing.Y;
|
||||
height += ImGui.GetFrameHeight();
|
||||
height += 12f;
|
||||
}
|
||||
|
||||
return Math.Clamp(height, NotificationMinHeight, NotificationMaxHeight);
|
||||
}
|
||||
|
||||
private float CalculateTitleHeight(LightlessNotification notification, float contentWidth)
|
||||
{
|
||||
var titleText = _configService.Current.ShowNotificationTimestamp
|
||||
? $"[{notification.CreatedAt.ToLocalTime():HH:mm:ss}] {notification.Title}"
|
||||
: notification.Title;
|
||||
|
||||
return ImGui.CalcTextSize(titleText, true, contentWidth).Y;
|
||||
}
|
||||
|
||||
private float CalculateMessageHeight(LightlessNotification notification, float contentWidth)
|
||||
{
|
||||
if (string.IsNullOrEmpty(notification.Message)) return 0f;
|
||||
|
||||
var messageHeight = ImGui.CalcTextSize(notification.Message, true, contentWidth).Y;
|
||||
return 4f + messageHeight;
|
||||
}
|
||||
|
||||
private Vector4 GetNotificationAccentColor(NotificationType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
NotificationType.Info => UIColors.Get("LightlessPurple"),
|
||||
NotificationType.Warning => UIColors.Get("LightlessYellow"),
|
||||
NotificationType.Error => UIColors.Get("DimRed"),
|
||||
NotificationType.PairRequest => UIColors.Get("LightlessBlue"),
|
||||
NotificationType.Download => UIColors.Get("LightlessGreen"),
|
||||
_ => UIColors.Get("LightlessPurple")
|
||||
};
|
||||
}
|
||||
}
|
||||
32
LightlessSync/UI/Models/LightlessNotification.cs
Normal file
32
LightlessSync/UI/Models/LightlessNotification.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using Dalamud.Interface;
|
||||
using LightlessSync.LightlessConfiguration.Models;
|
||||
using System.Numerics;
|
||||
namespace LightlessSync.UI.Models;
|
||||
public class LightlessNotification
|
||||
{
|
||||
public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string Message { get; set; } = string.Empty;
|
||||
public NotificationType Type { get; set; } = NotificationType.Info;
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public TimeSpan Duration { get; set; } = TimeSpan.FromSeconds(5);
|
||||
public bool IsExpired => DateTime.UtcNow - CreatedAt > Duration;
|
||||
public bool IsDismissed { get; set; } = false;
|
||||
public List<LightlessNotificationAction> Actions { get; set; } = new();
|
||||
public bool ShowProgress { get; set; } = false;
|
||||
public float Progress { get; set; } = 0f;
|
||||
public float AnimationProgress { get; set; } = 0f;
|
||||
public bool IsAnimatingIn { get; set; } = true;
|
||||
public bool IsAnimatingOut { get; set; } = false;
|
||||
public uint? SoundEffectId { get; set; } = null;
|
||||
}
|
||||
public class LightlessNotificationAction
|
||||
{
|
||||
public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||
public string Label { get; set; } = string.Empty;
|
||||
public FontAwesomeIcon Icon { get; set; } = FontAwesomeIcon.None;
|
||||
public Vector4 Color { get; set; } = Vector4.One;
|
||||
public Action<LightlessNotification> OnClick { get; set; } = _ => { };
|
||||
public bool IsPrimary { get; set; } = false;
|
||||
public bool IsDestructive { get; set; } = false;
|
||||
}
|
||||
72
LightlessSync/UI/Models/NotificationSounds.cs
Normal file
72
LightlessSync/UI/Models/NotificationSounds.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using LightlessSync.LightlessConfiguration.Models;
|
||||
|
||||
namespace LightlessSync.UI.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Common FFXIV <se.#> sound effect IDs for notifications.
|
||||
/// These correspond to the same sound IDs used in macros (1–16).
|
||||
/// </summary>
|
||||
public static class NotificationSounds
|
||||
{
|
||||
// ─────────────────────────────────────────────
|
||||
// Base <se.#> IDs (1–16)
|
||||
// https://ffxiv.consolegameswiki.com/wiki/Macros#Sound_Effects
|
||||
// ─────────────────────────────────────────────
|
||||
public const uint Se1 = 1; // Soft chime
|
||||
public const uint Se2 = 2; // Higher chime
|
||||
public const uint Se3 = 3; // Bell tone
|
||||
public const uint Se4 = 4; // Harp tone
|
||||
public const uint Se5 = 5; // Mechanical click
|
||||
public const uint Se6 = 6; // Drum / percussion
|
||||
public const uint Se7 = 7; // Metallic chime
|
||||
public const uint Se8 = 8; // Wooden tone
|
||||
public const uint Se9 = 9; // Wind / flute tone
|
||||
public const uint Se10 = 11; // Magical sparkle (ID 10 is skipped in game)
|
||||
public const uint Se11 = 12; // Metallic ring
|
||||
public const uint Se12 = 13; // Deep thud
|
||||
public const uint Se13 = 14; // "Tell received" ping
|
||||
public const uint Se14 = 15; // Success fanfare
|
||||
public const uint Se15 = 16; // System warning
|
||||
// Note: Se16 doesn't exist - Se15 is the last available sound
|
||||
|
||||
/// <summary>
|
||||
/// General notification sound (<se.2>)
|
||||
/// </summary>
|
||||
public const uint Info = Se2;
|
||||
|
||||
/// <summary>
|
||||
/// Warning/alert sound (<se.15>)
|
||||
/// </summary>
|
||||
public const uint Warning = Se15;
|
||||
|
||||
/// <summary>
|
||||
/// Error sound (<se.15> - System warning, used for errors)
|
||||
/// </summary>
|
||||
public const uint Error = Se15;
|
||||
|
||||
/// <summary>
|
||||
/// Success sound (<se.14>)
|
||||
/// </summary>
|
||||
public const uint Success = Se14;
|
||||
|
||||
/// <summary>
|
||||
/// Pair request sound (<se.13>, same as tell notification)
|
||||
/// </summary>
|
||||
public const uint PairRequest = Se13;
|
||||
|
||||
/// <summary>
|
||||
/// Download complete sound (<se.10>, a clean sparkle tone)
|
||||
/// </summary>
|
||||
public const uint DownloadComplete = Se10;
|
||||
|
||||
/// <summary>
|
||||
/// Get default sound for notification type
|
||||
/// </summary>
|
||||
public static uint GetDefaultSound(NotificationType type) => type switch
|
||||
{
|
||||
NotificationType.Info => Info,
|
||||
NotificationType.Warning => Warning,
|
||||
NotificationType.Error => Error,
|
||||
_ => Info
|
||||
};
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,169 +1,231 @@
|
||||
// inspiration: brio because it's style is fucking amazing
|
||||
// inspiration: brio because it's style is fucking amazing
|
||||
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using LightlessSync.LightlessConfiguration;
|
||||
using LightlessSync.LightlessConfiguration.Configurations;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
|
||||
namespace LightlessSync.UI.Style
|
||||
namespace LightlessSync.UI.Style;
|
||||
|
||||
internal static class MainStyle
|
||||
{
|
||||
internal static class MainStyle
|
||||
public readonly record struct StyleColorOption(string Key, string Label, Func<Vector4> DefaultValue, ImGuiCol Target, string? Description = null, string? UiColorKey = null);
|
||||
public readonly record struct StyleFloatOption(string Key, string Label, float DefaultValue, ImGuiStyleVar Target, float? Min = null, float? Max = null, float Speed = 0.25f, string? Description = null);
|
||||
public readonly record struct StyleVector2Option(string Key, string Label, Func<Vector2> DefaultValue, ImGuiStyleVar Target, Vector2? Min = null, Vector2? Max = null, float Speed = 0.25f, string? Description = null);
|
||||
|
||||
private static LightlessConfigService? _config;
|
||||
private static UiThemeConfigService? _themeConfig;
|
||||
public static void Init(LightlessConfigService config, UiThemeConfigService themeConfig)
|
||||
{
|
||||
private static LightlessConfigService? _config;
|
||||
public static void Init(LightlessConfigService config) => _config = config;
|
||||
public static bool ShouldUseTheme => _config?.Current.UseLightlessRedesign ?? false;
|
||||
|
||||
private static bool _hasPushed;
|
||||
private static int _pushedColorCount;
|
||||
private static int _pushedStyleVarCount;
|
||||
|
||||
public static void PushStyle()
|
||||
{
|
||||
if (_hasPushed)
|
||||
PopStyle();
|
||||
|
||||
if (!ShouldUseTheme)
|
||||
{
|
||||
_hasPushed = false;
|
||||
return;
|
||||
}
|
||||
|
||||
_hasPushed = true;
|
||||
_pushedColorCount = 0;
|
||||
_pushedStyleVarCount = 0;
|
||||
|
||||
Push(ImGuiCol.Text, new Vector4(255, 255, 255, 255));
|
||||
Push(ImGuiCol.TextDisabled, new Vector4(128, 128, 128, 255));
|
||||
|
||||
Push(ImGuiCol.WindowBg, new Vector4(23, 23, 23, 248));
|
||||
Push(ImGuiCol.ChildBg, new Vector4(23, 23, 23, 66));
|
||||
Push(ImGuiCol.PopupBg, new Vector4(23, 23, 23, 248));
|
||||
|
||||
Push(ImGuiCol.Border, new Vector4(65, 65, 65, 255));
|
||||
Push(ImGuiCol.BorderShadow, new Vector4(0, 0, 0, 150));
|
||||
|
||||
Push(ImGuiCol.FrameBg, new Vector4(40, 40, 40, 255));
|
||||
Push(ImGuiCol.FrameBgHovered, new Vector4(50, 50, 50, 255));
|
||||
Push(ImGuiCol.FrameBgActive, new Vector4(30, 30, 30, 255));
|
||||
|
||||
Push(ImGuiCol.TitleBg, new Vector4(24, 24, 24, 232));
|
||||
Push(ImGuiCol.TitleBgActive, new Vector4(30, 30, 30, 255));
|
||||
Push(ImGuiCol.TitleBgCollapsed, new Vector4(27, 27, 27, 255));
|
||||
|
||||
Push(ImGuiCol.MenuBarBg, new Vector4(36, 36, 36, 255));
|
||||
Push(ImGuiCol.ScrollbarBg, new Vector4(0, 0, 0, 0));
|
||||
Push(ImGuiCol.ScrollbarGrab, new Vector4(62, 62, 62, 255));
|
||||
Push(ImGuiCol.ScrollbarGrabHovered, new Vector4(70, 70, 70, 255));
|
||||
Push(ImGuiCol.ScrollbarGrabActive, new Vector4(70, 70, 70, 255));
|
||||
|
||||
Push(ImGuiCol.CheckMark, UIColors.Get("LightlessPurple"));
|
||||
|
||||
Push(ImGuiCol.SliderGrab, new Vector4(101, 101, 101, 255));
|
||||
Push(ImGuiCol.SliderGrabActive, new Vector4(123, 123, 123, 255));
|
||||
|
||||
Push(ImGuiCol.Button, UIColors.Get("ButtonDefault"));
|
||||
Push(ImGuiCol.ButtonHovered, UIColors.Get("LightlessPurple"));
|
||||
Push(ImGuiCol.ButtonActive, UIColors.Get("LightlessPurpleActive"));
|
||||
|
||||
Push(ImGuiCol.Header, new Vector4(0, 0, 0, 60));
|
||||
Push(ImGuiCol.HeaderHovered, new Vector4(0, 0, 0, 90));
|
||||
Push(ImGuiCol.HeaderActive, new Vector4(0, 0, 0, 120));
|
||||
|
||||
Push(ImGuiCol.Separator, new Vector4(75, 75, 75, 121));
|
||||
Push(ImGuiCol.SeparatorHovered, UIColors.Get("LightlessPurple"));
|
||||
Push(ImGuiCol.SeparatorActive, UIColors.Get("LightlessPurpleActive"));
|
||||
|
||||
Push(ImGuiCol.ResizeGrip, new Vector4(0, 0, 0, 0));
|
||||
Push(ImGuiCol.ResizeGripHovered, new Vector4(0, 0, 0, 0));
|
||||
Push(ImGuiCol.ResizeGripActive, UIColors.Get("LightlessPurpleActive"));
|
||||
|
||||
Push(ImGuiCol.Tab, new Vector4(40, 40, 40, 255));
|
||||
Push(ImGuiCol.TabHovered, UIColors.Get("LightlessPurple"));
|
||||
Push(ImGuiCol.TabActive, UIColors.Get("LightlessPurpleActive"));
|
||||
Push(ImGuiCol.TabUnfocused, new Vector4(40, 40, 40, 255));
|
||||
Push(ImGuiCol.TabUnfocusedActive, UIColors.Get("LightlessPurpleActive"));
|
||||
|
||||
Push(ImGuiCol.DockingPreview, UIColors.Get("LightlessPurpleActive"));
|
||||
Push(ImGuiCol.DockingEmptyBg, new Vector4(50, 50, 50, 255));
|
||||
|
||||
Push(ImGuiCol.PlotLines, new Vector4(150, 150, 150, 255));
|
||||
|
||||
Push(ImGuiCol.TableHeaderBg, new Vector4(48, 48, 48, 255));
|
||||
Push(ImGuiCol.TableBorderStrong, new Vector4(79, 79, 89, 255));
|
||||
Push(ImGuiCol.TableBorderLight, new Vector4(59, 59, 64, 255));
|
||||
Push(ImGuiCol.TableRowBg, new Vector4(0, 0, 0, 0));
|
||||
Push(ImGuiCol.TableRowBgAlt, new Vector4(255, 255, 255, 15));
|
||||
|
||||
Push(ImGuiCol.TextSelectedBg, new Vector4(98, 75, 224, 255));
|
||||
Push(ImGuiCol.DragDropTarget, new Vector4(98, 75, 224, 255));
|
||||
|
||||
Push(ImGuiCol.NavHighlight, new Vector4(98, 75, 224, 179));
|
||||
Push(ImGuiCol.NavWindowingDimBg, new Vector4(204, 204, 204, 51));
|
||||
Push(ImGuiCol.NavWindowingHighlight, new Vector4(204, 204, 204, 89));
|
||||
|
||||
PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(6, 6));
|
||||
PushStyleVar(ImGuiStyleVar.FramePadding, new Vector2(4, 3));
|
||||
PushStyleVar(ImGuiStyleVar.CellPadding, new Vector2(4, 4));
|
||||
PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(4, 4));
|
||||
PushStyleVar(ImGuiStyleVar.ItemInnerSpacing, new Vector2(4, 4));
|
||||
|
||||
PushStyleVar(ImGuiStyleVar.IndentSpacing, 21.0f);
|
||||
PushStyleVar(ImGuiStyleVar.ScrollbarSize, 10.0f);
|
||||
PushStyleVar(ImGuiStyleVar.GrabMinSize, 20.0f);
|
||||
|
||||
PushStyleVar(ImGuiStyleVar.WindowBorderSize, 1.5f);
|
||||
PushStyleVar(ImGuiStyleVar.ChildBorderSize, 1.5f);
|
||||
PushStyleVar(ImGuiStyleVar.PopupBorderSize, 1.5f);
|
||||
PushStyleVar(ImGuiStyleVar.FrameBorderSize, 0f);
|
||||
|
||||
PushStyleVar(ImGuiStyleVar.WindowRounding, 7f);
|
||||
PushStyleVar(ImGuiStyleVar.ChildRounding, 4f);
|
||||
PushStyleVar(ImGuiStyleVar.FrameRounding, 4f);
|
||||
PushStyleVar(ImGuiStyleVar.PopupRounding, 4f);
|
||||
PushStyleVar(ImGuiStyleVar.ScrollbarRounding, 4f);
|
||||
PushStyleVar(ImGuiStyleVar.GrabRounding, 4f);
|
||||
PushStyleVar(ImGuiStyleVar.TabRounding, 4f);
|
||||
}
|
||||
|
||||
public static void PopStyle()
|
||||
{
|
||||
if (!_hasPushed)
|
||||
return;
|
||||
|
||||
if (_pushedStyleVarCount > 0)
|
||||
ImGui.PopStyleVar(_pushedStyleVarCount);
|
||||
if (_pushedColorCount > 0)
|
||||
ImGui.PopStyleColor(_pushedColorCount);
|
||||
|
||||
_hasPushed = false;
|
||||
_pushedColorCount = 0;
|
||||
_pushedStyleVarCount = 0;
|
||||
}
|
||||
|
||||
private static void Push(ImGuiCol col, Vector4 rgba)
|
||||
{
|
||||
if (rgba.X > 1f || rgba.Y > 1f || rgba.Z > 1f || rgba.W > 1f)
|
||||
rgba /= 255f;
|
||||
|
||||
ImGui.PushStyleColor(col, rgba);
|
||||
_pushedColorCount++;
|
||||
}
|
||||
|
||||
private static void Push(ImGuiCol col, uint packedRgba)
|
||||
{
|
||||
ImGui.PushStyleColor(col, packedRgba);
|
||||
_pushedColorCount++;
|
||||
}
|
||||
|
||||
private static void PushStyleVar(ImGuiStyleVar var, float value)
|
||||
{
|
||||
ImGui.PushStyleVar(var, value);
|
||||
_pushedStyleVarCount++;
|
||||
}
|
||||
|
||||
private static void PushStyleVar(ImGuiStyleVar var, Vector2 value)
|
||||
{
|
||||
ImGui.PushStyleVar(var, value);
|
||||
_pushedStyleVarCount++;
|
||||
}
|
||||
_config = config;
|
||||
_themeConfig = themeConfig;
|
||||
}
|
||||
public static bool ShouldUseTheme => _config?.Current.UseLightlessRedesign ?? false;
|
||||
|
||||
private static bool _hasPushed;
|
||||
private static int _pushedColorCount;
|
||||
private static int _pushedStyleVarCount;
|
||||
|
||||
private static readonly StyleColorOption[] _colorOptions =
|
||||
[
|
||||
new("color.text", "Text", () => Rgba(255, 255, 255, 255), ImGuiCol.Text),
|
||||
new("color.textDisabled", "Text (Disabled)", () => Rgba(128, 128, 128, 255), ImGuiCol.TextDisabled),
|
||||
new("color.windowBg", "Window Background", () => Rgba(23, 23, 23, 248), ImGuiCol.WindowBg),
|
||||
new("color.childBg", "Child Background", () => Rgba(23, 23, 23, 66), ImGuiCol.ChildBg),
|
||||
new("color.popupBg", "Popup Background", () => Rgba(23, 23, 23, 248), ImGuiCol.PopupBg),
|
||||
new("color.border", "Border", () => Rgba(65, 65, 65, 255), ImGuiCol.Border),
|
||||
new("color.borderShadow", "Border Shadow", () => Rgba(0, 0, 0, 150), ImGuiCol.BorderShadow),
|
||||
new("color.frameBg", "Frame Background", () => Rgba(40, 40, 40, 255), ImGuiCol.FrameBg),
|
||||
new("color.frameBgHovered", "Frame Background (Hover)", () => Rgba(50, 50, 50, 255), ImGuiCol.FrameBgHovered),
|
||||
new("color.frameBgActive", "Frame Background (Active)", () => Rgba(30, 30, 30, 255), ImGuiCol.FrameBgActive),
|
||||
new("color.titleBg", "Title Background", () => Rgba(24, 24, 24, 232), ImGuiCol.TitleBg),
|
||||
new("color.titleBgActive", "Title Background (Active)", () => Rgba(30, 30, 30, 255), ImGuiCol.TitleBgActive),
|
||||
new("color.titleBgCollapsed", "Title Background (Collapsed)", () => Rgba(27, 27, 27, 255), ImGuiCol.TitleBgCollapsed),
|
||||
new("color.menuBarBg", "Menu Bar Background", () => Rgba(36, 36, 36, 255), ImGuiCol.MenuBarBg),
|
||||
new("color.scrollbarBg", "Scrollbar Background", () => Rgba(0, 0, 0, 0), ImGuiCol.ScrollbarBg),
|
||||
new("color.scrollbarGrab", "Scrollbar Grab", () => Rgba(62, 62, 62, 255), ImGuiCol.ScrollbarGrab),
|
||||
new("color.scrollbarGrabHovered", "Scrollbar Grab (Hover)", () => Rgba(70, 70, 70, 255), ImGuiCol.ScrollbarGrabHovered),
|
||||
new("color.scrollbarGrabActive", "Scrollbar Grab (Active)", () => Rgba(70, 70, 70, 255), ImGuiCol.ScrollbarGrabActive),
|
||||
new("color.checkMark", "Check Mark", () => UIColors.Get("LightlessPurple"), ImGuiCol.CheckMark, UiColorKey: "LightlessPurple"),
|
||||
new("color.sliderGrab", "Slider Grab", () => Rgba(101, 101, 101, 255), ImGuiCol.SliderGrab),
|
||||
new("color.sliderGrabActive", "Slider Grab (Active)", () => Rgba(123, 123, 123, 255), ImGuiCol.SliderGrabActive),
|
||||
new("color.button", "Button", () => UIColors.Get("ButtonDefault"), ImGuiCol.Button, UiColorKey: "ButtonDefault"),
|
||||
new("color.buttonHovered", "Button (Hover)", () => UIColors.Get("LightlessPurple"), ImGuiCol.ButtonHovered, UiColorKey: "LightlessPurple"),
|
||||
new("color.buttonActive", "Button (Active)", () => UIColors.Get("LightlessPurpleActive"), ImGuiCol.ButtonActive, UiColorKey: "LightlessPurpleActive"),
|
||||
new("color.header", "Header", () => Rgba(0, 0, 0, 60), ImGuiCol.Header),
|
||||
new("color.headerHovered", "Header (Hover)", () => Rgba(0, 0, 0, 90), ImGuiCol.HeaderHovered),
|
||||
new("color.headerActive", "Header (Active)", () => Rgba(0, 0, 0, 120), ImGuiCol.HeaderActive),
|
||||
new("color.separator", "Separator", () => Rgba(75, 75, 75, 121), ImGuiCol.Separator),
|
||||
new("color.separatorHovered", "Separator (Hover)", () => UIColors.Get("LightlessPurple"), ImGuiCol.SeparatorHovered, UiColorKey: "LightlessPurple"),
|
||||
new("color.separatorActive", "Separator (Active)", () => UIColors.Get("LightlessPurpleActive"), ImGuiCol.SeparatorActive, UiColorKey: "LightlessPurpleActive"),
|
||||
new("color.resizeGrip", "Resize Grip", () => Rgba(0, 0, 0, 0), ImGuiCol.ResizeGrip),
|
||||
new("color.resizeGripHovered", "Resize Grip (Hover)", () => Rgba(0, 0, 0, 0), ImGuiCol.ResizeGripHovered),
|
||||
new("color.resizeGripActive", "Resize Grip (Active)", () => UIColors.Get("LightlessPurpleActive"), ImGuiCol.ResizeGripActive, UiColorKey: "LightlessPurpleActive"),
|
||||
new("color.tab", "Tab", () => Rgba(40, 40, 40, 255), ImGuiCol.Tab),
|
||||
new("color.tabHovered", "Tab (Hover)", () => UIColors.Get("LightlessPurple"), ImGuiCol.TabHovered, UiColorKey: "LightlessPurple"),
|
||||
new("color.tabActive", "Tab (Active)", () => UIColors.Get("LightlessPurpleActive"), ImGuiCol.TabActive, UiColorKey: "LightlessPurpleActive"),
|
||||
new("color.tabUnfocused", "Tab (Unfocused)", () => Rgba(40, 40, 40, 255), ImGuiCol.TabUnfocused),
|
||||
new("color.tabUnfocusedActive", "Tab (Unfocused Active)", () => UIColors.Get("LightlessPurpleActive"), ImGuiCol.TabUnfocusedActive, UiColorKey: "LightlessPurpleActive"),
|
||||
new("color.dockingPreview", "Docking Preview", () => UIColors.Get("LightlessPurpleActive"), ImGuiCol.DockingPreview, UiColorKey: "LightlessPurpleActive"),
|
||||
new("color.dockingEmptyBg", "Docking Empty Background", () => Rgba(50, 50, 50, 255), ImGuiCol.DockingEmptyBg),
|
||||
new("color.plotLines", "Plot Lines", () => Rgba(150, 150, 150, 255), ImGuiCol.PlotLines),
|
||||
new("color.tableHeaderBg", "Table Header Background", () => Rgba(48, 48, 48, 255), ImGuiCol.TableHeaderBg),
|
||||
new("color.tableBorderStrong", "Table Border Strong", () => Rgba(79, 79, 89, 255), ImGuiCol.TableBorderStrong),
|
||||
new("color.tableBorderLight", "Table Border Light", () => Rgba(59, 59, 64, 255), ImGuiCol.TableBorderLight),
|
||||
new("color.tableRowBg", "Table Row Background", () => Rgba(0, 0, 0, 0), ImGuiCol.TableRowBg),
|
||||
new("color.tableRowBgAlt", "Table Row Background (Alt)", () => Rgba(255, 255, 255, 15), ImGuiCol.TableRowBgAlt),
|
||||
new("color.textSelectedBg", "Text Selection Background", () => Rgba(173, 138, 245, 255), ImGuiCol.TextSelectedBg),
|
||||
new("color.dragDropTarget", "Drag & Drop Target", () => Rgba(173, 138, 245, 255), ImGuiCol.DragDropTarget),
|
||||
new("color.navHighlight", "Navigation Highlight", () => Rgba(173, 138, 245, 179), ImGuiCol.NavHighlight),
|
||||
new("color.navWindowingDimBg", "Navigation Window Dim", () => Rgba(204, 204, 204, 51), ImGuiCol.NavWindowingDimBg),
|
||||
new("color.navWindowingHighlight", "Navigation Window Highlight", () => Rgba(204, 204, 204, 89), ImGuiCol.NavWindowingHighlight)
|
||||
];
|
||||
|
||||
private static readonly StyleVector2Option[] _vector2Options =
|
||||
[
|
||||
new("vector.windowPadding", "Window Padding", () => new Vector2(6f, 6f), ImGuiStyleVar.WindowPadding),
|
||||
new("vector.framePadding", "Frame Padding", () => new Vector2(4f, 3f), ImGuiStyleVar.FramePadding),
|
||||
new("vector.cellPadding", "Cell Padding", () => new Vector2(4f, 4f), ImGuiStyleVar.CellPadding),
|
||||
new("vector.itemSpacing", "Item Spacing", () => new Vector2(4f, 4f), ImGuiStyleVar.ItemSpacing),
|
||||
new("vector.itemInnerSpacing", "Item Inner Spacing", () => new Vector2(4f, 4f), ImGuiStyleVar.ItemInnerSpacing)
|
||||
];
|
||||
|
||||
private static readonly StyleFloatOption[] _floatOptions =
|
||||
[
|
||||
new("float.indentSpacing", "Indent Spacing", 21f, ImGuiStyleVar.IndentSpacing, 0f, 100f, 0.5f),
|
||||
new("float.scrollbarSize", "Scrollbar Size", 10f, ImGuiStyleVar.ScrollbarSize, 4f, 30f, 0.5f),
|
||||
new("float.grabMinSize", "Grab Minimum Size", 20f, ImGuiStyleVar.GrabMinSize, 1f, 80f, 0.5f),
|
||||
new("float.windowBorderSize", "Window Border Size", 1.5f, ImGuiStyleVar.WindowBorderSize, 0f, 5f, 0.1f),
|
||||
new("float.childBorderSize", "Child Border Size", 1.5f, ImGuiStyleVar.ChildBorderSize, 0f, 5f, 0.1f),
|
||||
new("float.popupBorderSize", "Popup Border Size", 1.5f, ImGuiStyleVar.PopupBorderSize, 0f, 5f, 0.1f),
|
||||
new("float.frameBorderSize", "Frame Border Size", 0f, ImGuiStyleVar.FrameBorderSize, 0f, 5f, 0.1f),
|
||||
new("float.windowRounding", "Window Rounding", 7f, ImGuiStyleVar.WindowRounding, 0f, 20f, 0.2f),
|
||||
new("float.childRounding", "Child Rounding", 4f, ImGuiStyleVar.ChildRounding, 0f, 20f, 0.2f),
|
||||
new("float.frameRounding", "Frame Rounding", 4f, ImGuiStyleVar.FrameRounding, 0f, 20f, 0.2f),
|
||||
new("float.popupRounding", "Popup Rounding", 4f, ImGuiStyleVar.PopupRounding, 0f, 20f, 0.2f),
|
||||
new("float.scrollbarRounding", "Scrollbar Rounding", 4f, ImGuiStyleVar.ScrollbarRounding, 0f, 20f, 0.2f),
|
||||
new("float.grabRounding", "Grab Rounding", 4f, ImGuiStyleVar.GrabRounding, 0f, 20f, 0.2f),
|
||||
new("float.tabRounding", "Tab Rounding", 4f, ImGuiStyleVar.TabRounding, 0f, 20f, 0.2f)
|
||||
];
|
||||
|
||||
public static IReadOnlyList<StyleColorOption> ColorOptions => _colorOptions;
|
||||
public static IReadOnlyList<StyleFloatOption> FloatOptions => _floatOptions;
|
||||
public static IReadOnlyList<StyleVector2Option> Vector2Options => _vector2Options;
|
||||
|
||||
public static void PushStyle()
|
||||
{
|
||||
if (_hasPushed)
|
||||
PopStyle();
|
||||
|
||||
if (!ShouldUseTheme)
|
||||
{
|
||||
_hasPushed = false;
|
||||
return;
|
||||
}
|
||||
|
||||
_hasPushed = true;
|
||||
_pushedColorCount = 0;
|
||||
_pushedStyleVarCount = 0;
|
||||
|
||||
foreach (var option in _colorOptions)
|
||||
Push(option.Target, ResolveColor(option));
|
||||
|
||||
foreach (var option in _vector2Options)
|
||||
PushStyleVar(option.Target, ResolveVector(option));
|
||||
|
||||
foreach (var option in _floatOptions)
|
||||
PushStyleVar(option.Target, ResolveFloat(option));
|
||||
}
|
||||
|
||||
public static void PopStyle()
|
||||
{
|
||||
if (!_hasPushed)
|
||||
return;
|
||||
|
||||
if (_pushedStyleVarCount > 0)
|
||||
ImGui.PopStyleVar(_pushedStyleVarCount);
|
||||
if (_pushedColorCount > 0)
|
||||
ImGui.PopStyleColor(_pushedColorCount);
|
||||
|
||||
_hasPushed = false;
|
||||
_pushedColorCount = 0;
|
||||
_pushedStyleVarCount = 0;
|
||||
}
|
||||
|
||||
private static Vector4 ResolveColor(StyleColorOption option)
|
||||
{
|
||||
var defaultValue = NormalizeColorVector(option.DefaultValue());
|
||||
if (_themeConfig?.Current.StyleOverrides.TryGetValue(option.Key, out var overrideValue) == true && overrideValue.Color is { } packed)
|
||||
return PackedColorToVector4(packed);
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
private static Vector2 ResolveVector(StyleVector2Option option)
|
||||
{
|
||||
var value = option.DefaultValue();
|
||||
if (_themeConfig?.Current.StyleOverrides.TryGetValue(option.Key, out var overrideValue) == true && overrideValue.Vector2 is { } vectorOverride)
|
||||
{
|
||||
value = vectorOverride;
|
||||
}
|
||||
|
||||
if (option.Min is { } min)
|
||||
value = Vector2.Max(value, min);
|
||||
if (option.Max is { } max)
|
||||
value = Vector2.Min(value, max);
|
||||
return value;
|
||||
}
|
||||
|
||||
private static float ResolveFloat(StyleFloatOption option)
|
||||
{
|
||||
var value = option.DefaultValue;
|
||||
if (_themeConfig?.Current.StyleOverrides.TryGetValue(option.Key, out var overrideValue) == true && overrideValue.Float is { } floatOverride)
|
||||
{
|
||||
value = floatOverride;
|
||||
}
|
||||
|
||||
if (option.Min.HasValue)
|
||||
value = MathF.Max(option.Min.Value, value);
|
||||
if (option.Max.HasValue)
|
||||
value = MathF.Min(option.Max.Value, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
private static void Push(ImGuiCol col, Vector4 rgba)
|
||||
{
|
||||
rgba = NormalizeColorVector(rgba);
|
||||
ImGui.PushStyleColor(col, rgba);
|
||||
_pushedColorCount++;
|
||||
}
|
||||
|
||||
private static void PushStyleVar(ImGuiStyleVar var, float value)
|
||||
{
|
||||
ImGui.PushStyleVar(var, value);
|
||||
_pushedStyleVarCount++;
|
||||
}
|
||||
|
||||
private static void PushStyleVar(ImGuiStyleVar var, Vector2 value)
|
||||
{
|
||||
ImGui.PushStyleVar(var, value);
|
||||
_pushedStyleVarCount++;
|
||||
}
|
||||
|
||||
private static Vector4 Rgba(byte r, byte g, byte b, byte a = 255)
|
||||
=> new Vector4(r / 255f, g / 255f, b / 255f, a / 255f);
|
||||
|
||||
internal static Vector4 NormalizeColorVector(Vector4 rgba)
|
||||
{
|
||||
if (rgba.X > 1f || rgba.Y > 1f || rgba.Z > 1f || rgba.W > 1f)
|
||||
rgba /= 255f;
|
||||
return rgba;
|
||||
}
|
||||
|
||||
internal static Vector4 PackedColorToVector4(uint color)
|
||||
=> new(
|
||||
(color & 0xFF) / 255f,
|
||||
((color >> 8) & 0xFF) / 255f,
|
||||
((color >> 16) & 0xFF) / 255f,
|
||||
((color >> 24) & 0xFF) / 255f);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
@@ -11,12 +11,8 @@ using LightlessSync.Services;
|
||||
using LightlessSync.Services.Mediator;
|
||||
using LightlessSync.Utils;
|
||||
using LightlessSync.WebAPI;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Reflection.Emit;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
namespace LightlessSync.UI;
|
||||
|
||||
@@ -33,13 +29,14 @@ public class TopTabMenu
|
||||
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;
|
||||
public TopTabMenu(LightlessMediator lightlessMediator, ApiController apiController, PairManager pairManager, UiSharedService uiSharedService, PairRequestService pairRequestService, DalamudUtilService dalamudUtilService)
|
||||
public TopTabMenu(LightlessMediator lightlessMediator, ApiController apiController, PairManager pairManager, UiSharedService uiSharedService, PairRequestService pairRequestService, DalamudUtilService dalamudUtilService, NotificationService lightlessNotificationService)
|
||||
{
|
||||
_lightlessMediator = lightlessMediator;
|
||||
_apiController = apiController;
|
||||
@@ -47,6 +44,7 @@ public class TopTabMenu
|
||||
_pairRequestService = pairRequestService;
|
||||
_dalamudUtilService = dalamudUtilService;
|
||||
_uiSharedService = uiSharedService;
|
||||
_lightlessNotificationService = lightlessNotificationService;
|
||||
}
|
||||
|
||||
private enum SelectedTab
|
||||
@@ -199,16 +197,79 @@ public class TopTabMenu
|
||||
if (TabSelection != SelectedTab.None) ImGuiHelpers.ScaledDummy(3f);
|
||||
|
||||
#if DEBUG
|
||||
if (ImGui.Button("Add Test Pair Request"))
|
||||
if (ImGui.Button("Test Pair Request"))
|
||||
{
|
||||
_lightlessNotificationService.ShowPairRequestNotification(
|
||||
"Debug User",
|
||||
"debug-user-id",
|
||||
onAccept: () =>
|
||||
{
|
||||
_lightlessMediator.Publish(new NotificationMessage(
|
||||
"Pair Accepted",
|
||||
"Debug pair request was accepted!",
|
||||
NotificationType.Info,
|
||||
TimeSpan.FromSeconds(3)));
|
||||
},
|
||||
onDecline: () =>
|
||||
{
|
||||
_lightlessMediator.Publish(new NotificationMessage(
|
||||
"Pair Declined",
|
||||
"Debug pair request was declined.",
|
||||
NotificationType.Warning,
|
||||
TimeSpan.FromSeconds(3)));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Test Info"))
|
||||
{
|
||||
var fakeCid = Guid.NewGuid().ToString("N");
|
||||
var display = _pairRequestService.RegisterIncomingRequest(fakeCid, "Debug pair request");
|
||||
_lightlessMediator.Publish(new NotificationMessage(
|
||||
"Pair request received (debug)",
|
||||
display.Message,
|
||||
"Information",
|
||||
"This is a test ifno notification with some longer text to see how it wraps. This is a test ifno notification with some longer text to see how it wraps. This is a test ifno notification with some longer text to see how it wraps. This is a test ifno notification with some longer text to see how it wraps.",
|
||||
NotificationType.Info,
|
||||
TimeSpan.FromSeconds(5)));
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Test Warning"))
|
||||
{
|
||||
_lightlessMediator.Publish(new NotificationMessage(
|
||||
"Warning",
|
||||
"This is a test warning notification.",
|
||||
NotificationType.Warning,
|
||||
TimeSpan.FromSeconds(7)));
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Test Error"))
|
||||
{
|
||||
_lightlessMediator.Publish(new NotificationMessage(
|
||||
"Error",
|
||||
"This is a test error notification erp police",
|
||||
NotificationType.Error,
|
||||
TimeSpan.FromSeconds(10)));
|
||||
}
|
||||
|
||||
if (ImGui.Button("Test Download Progress"))
|
||||
{
|
||||
var downloadStatus = new List<(string playerName, float progress, string status)>
|
||||
{
|
||||
("Mauwmauw Nekochan", 0.85f, "downloading"),
|
||||
("Raelynn Kitsune", 0.34f, "downloading"),
|
||||
("Jaina Elraeth", 0.67f, "downloading"),
|
||||
("Vaelstra Bloodthorn", 0.19f, "downloading"),
|
||||
("Lydia Hera Moondrop", 0.86f, "downloading"),
|
||||
("C'liina Star", 1.0f, "completed")
|
||||
};
|
||||
|
||||
_lightlessNotificationService.ShowPairDownloadNotification(downloadStatus);
|
||||
}
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Dismiss Download"))
|
||||
{
|
||||
_lightlessNotificationService.DismissPairDownloadNotification();
|
||||
}
|
||||
#endif
|
||||
|
||||
DrawIncomingPairRequests(availableWidth);
|
||||
@@ -401,7 +462,7 @@ public class TopTabMenu
|
||||
try
|
||||
{
|
||||
var myCidHash = (await _dalamudUtilService.GetCIDAsync().ConfigureAwait(false)).ToString().GetHash256();
|
||||
await _apiController.TryPairWithContentId(request.HashedCid, myCidHash).ConfigureAwait(false);
|
||||
await _apiController.TryPairWithContentId(request.HashedCid).ConfigureAwait(false);
|
||||
_pairRequestService.RemoveRequest(request.HashedCid);
|
||||
|
||||
var display = string.IsNullOrEmpty(request.DisplayName) ? request.HashedCid : request.DisplayName;
|
||||
@@ -850,4 +911,4 @@ public class TopTabMenu
|
||||
ImGui.EndPopup();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,9 @@ namespace LightlessSync.UI
|
||||
{ "LightlessAdminGlow", "#b09343" },
|
||||
{ "LightlessModeratorText", "#94ffda" },
|
||||
{ "LightlessModeratorGlow", "#599c84" },
|
||||
|
||||
{ "Lightfinder", "#ad8af5" },
|
||||
{ "LightfinderEdge", "#000000" },
|
||||
};
|
||||
|
||||
private static LightlessConfigService? _configService;
|
||||
|
||||
Reference in New Issue
Block a user