Merge branch '1.12.2' into pair-notifs-ui

# Conflicts:
#	LightlessSync/UI/SettingsUi.cs
This commit is contained in:
choco
2025-10-11 21:42:14 +02:00
23 changed files with 1738 additions and 453 deletions

View File

@@ -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 Lightfinders 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();
}

View File

@@ -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]);

View File

@@ -18,6 +18,7 @@ using LightlessSync.PlayerData.Pairs;
using LightlessSync.Services;
using LightlessSync.Services.Mediator;
using LightlessSync.Services.ServerConfiguration;
using LightlessSync.UI.Style;
using LightlessSync.Utils;
using LightlessSync.UtilsEnum.Enum;
using LightlessSync.WebAPI;
@@ -44,10 +45,8 @@ public class SettingsUi : WindowMediatorSubscriberBase
private readonly ApiController _apiController;
private readonly CacheMonitor _cacheMonitor;
private readonly LightlessConfigService _configService;
private readonly ConcurrentDictionary<GameObjectHandler, Dictionary<string, FileDownloadStatus>> _currentDownloads =
new();
private readonly UiThemeConfigService _themeConfigService;
private readonly ConcurrentDictionary<GameObjectHandler, Dictionary<string, FileDownloadStatus>> _currentDownloads = new();
private readonly DalamudUtilService _dalamudUtilService;
private readonly HttpClient _httpClient;
private readonly FileCacheManager _fileCacheManager;
@@ -77,6 +76,9 @@ public class SettingsUi : WindowMediatorSubscriberBase
private string _lightfinderIconInput = string.Empty;
private bool _lightfinderIconInputInitialized = false;
private int _lightfinderIconPresetIndex = -1;
private bool _selectGeneralTabOnNextDraw = false;
private bool _openLightfinderSectionOnNextDraw = false;
private static readonly LightlessConfig DefaultConfig = new();
private static readonly (string Label, SeIconChar Icon)[] LightfinderIconPresets = new[]
{
@@ -92,7 +94,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
private bool _wasOpen = false;
public SettingsUi(ILogger<SettingsUi> logger,
UiSharedService uiShared, LightlessConfigService configService,
UiSharedService uiShared, LightlessConfigService configService, UiThemeConfigService themeConfigService,
PairManager pairManager,
ServerConfigurationManager serverConfigurationManager,
PlayerPerformanceConfigService playerPerformanceConfigService,
@@ -110,6 +112,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
performanceCollector)
{
_configService = configService;
_themeConfigService = themeConfigService;
_pairManager = pairManager;
_serverConfigurationManager = serverConfigurationManager;
_playerPerformanceConfigService = playerPerformanceConfigService;
@@ -138,6 +141,12 @@ public class SettingsUi : WindowMediatorSubscriberBase
};
Mediator.Subscribe<OpenSettingsUiMessage>(this, (_) => Toggle());
Mediator.Subscribe<OpenLightfinderSettingsMessage>(this, (_) =>
{
IsOpen = true;
_selectGeneralTabOnNextDraw = true;
_openLightfinderSectionOnNextDraw = true;
});
Mediator.Subscribe<SwitchToIntroUiMessage>(this, (_) => IsOpen = false);
Mediator.Subscribe<CutsceneStartMessage>(this, (_) => UiSharedService_GposeStart());
Mediator.Subscribe<CutsceneEndMessage>(this, (_) => UiSharedService_GposeEnd());
@@ -177,39 +186,360 @@ public class SettingsUi : WindowMediatorSubscriberBase
DrawSettingsContent();
}
private static Vector3 PackedColorToVector3(uint color)
=> new(
(color & 0xFF) / 255f,
((color >> 8) & 0xFF) / 255f,
((color >> 16) & 0xFF) / 255f);
private static uint Vector3ToPackedColor(Vector3 color)
{
static byte ToByte(float channel)
{
var scaled = MathF.Round(Math.Clamp(channel, 0f, 1f) * 255.0f);
return (byte)Math.Clamp((int)scaled, 0, 255);
}
var r = ToByte(color.X);
var g = ToByte(color.Y);
var b = ToByte(color.Z);
return (uint)(r | (g << 8) | (b << 16));
}
private static bool DrawDtrColorEditors(ref DtrEntry.Colors colors)
{
var innerSpacing = ImGui.GetStyle().ItemInnerSpacing.X;
var foregroundColor = PackedColorToVector3(colors.Foreground);
var glowColor = PackedColorToVector3(colors.Glow);
const ImGuiColorEditFlags colorFlags = ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.NoLabel;
var changed = ImGui.ColorEdit3("###foreground", ref foregroundColor, colorFlags);
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Foreground Color - Set to pure black (#000000) to use the default color");
ImGui.SameLine(0.0f, innerSpacing);
changed |= ImGui.ColorEdit3("###glow", ref glowColor, colorFlags);
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Glow Color - Set to pure black (#000000) to use the default color");
if (changed)
colors = new(Vector3ToPackedColor(foregroundColor), Vector3ToPackedColor(glowColor));
return changed;
}
private void DrawDtrColorRow(string id, string label, string description, ref DtrEntry.Colors colors, DtrEntry.Colors defaultDisplay, Action<DtrEntry.Colors> applyConfig)
{
ImGui.TableNextRow();
ImGui.TableSetColumnIndex(0);
using (ImRaii.PushId(id))
{
var edited = DrawDtrColorEditors(ref colors);
ImGui.SameLine(0.0f, ImGui.GetStyle().ItemInnerSpacing.X);
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted(label);
if (edited)
{
applyConfig(colors);
_configService.Save();
}
}
ImGui.TableSetColumnIndex(1);
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted(description);
ImGui.TableSetColumnIndex(2);
using var resetId = ImRaii.PushId($"reset-{id}");
var availableWidth = ImGui.GetContentRegionAvail().X;
var isDefault = colors == defaultDisplay;
using (ImRaii.Disabled(isDefault))
{
using (ImRaii.PushFont(UiBuilder.IconFont))
{
if (ImGui.Button(FontAwesomeIcon.Undo.ToIconString(), new Vector2(availableWidth, 0)))
{
colors = defaultDisplay;
applyConfig(defaultDisplay);
_configService.Save();
}
}
}
UiSharedService.AttachToolTip(isDefault ? "Colors already match the default value." : "Reset these colors to their default values.");
}
private static bool InputDtrColors(string label, ref DtrEntry.Colors colors)
{
using var id = ImRaii.PushId(label);
var innerSpacing = ImGui.GetStyle().ItemInnerSpacing.X;
var foregroundColor = ConvertColor(colors.Foreground);
var glowColor = ConvertColor(colors.Glow);
var ret = ImGui.ColorEdit3("###foreground", ref foregroundColor,
ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.NoLabel | ImGuiColorEditFlags.Uint8);
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Foreground Color - Set to pure black (#000000) to use the default color");
ImGui.SameLine(0.0f, innerSpacing);
ret |= ImGui.ColorEdit3("###glow", ref glowColor,
ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.NoLabel | ImGuiColorEditFlags.Uint8);
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Glow Color - Set to pure black (#000000) to use the default color");
var ret = DrawDtrColorEditors(ref colors);
ImGui.SameLine(0.0f, innerSpacing);
ImGui.TextUnformatted(label);
if (ret)
colors = new(ConvertBackColor(foregroundColor), ConvertBackColor(glowColor));
return ret;
}
static Vector3 ConvertColor(uint color)
=> unchecked(new((byte)color / 255.0f, (byte)(color >> 8) / 255.0f, (byte)(color >> 16) / 255.0f));
private static DtrEntry.Colors SwapColorChannels(DtrEntry.Colors colors)
=> new(SwapColorChannels(colors.Foreground), SwapColorChannels(colors.Glow));
static uint ConvertBackColor(Vector3 color)
=> byte.CreateSaturating(color.X * 255.0f) | ((uint)byte.CreateSaturating(color.Y * 255.0f) << 8) |
((uint)byte.CreateSaturating(color.Z * 255.0f) << 16);
private static uint SwapColorChannels(uint color)
{
if (color == 0)
return 0;
return ((color & 0xFFu) << 16) | (color & 0xFF00u) | ((color >> 16) & 0xFFu);
}
private static Vector4 PackedThemeColorToVector4(uint packed)
=> new(
(packed & 0xFF) / 255f,
((packed >> 8) & 0xFF) / 255f,
((packed >> 16) & 0xFF) / 255f,
((packed >> 24) & 0xFF) / 255f);
private static uint ThemeVector4ToPackedColor(Vector4 color)
{
static byte ToByte(float channel)
{
var scaled = MathF.Round(Math.Clamp(channel, 0f, 1f) * 255.0f);
return (byte)Math.Clamp((int)scaled, 0, 255);
}
var r = ToByte(color.X);
var g = ToByte(color.Y);
var b = ToByte(color.Z);
var a = ToByte(color.W);
return (uint)(r | (g << 8) | (b << 16) | (a << 24));
}
private void UpdateStyleOverride(string key, Action<UiStyleOverride> updater)
{
var overrides = _themeConfigService.Current.StyleOverrides;
if (!overrides.TryGetValue(key, out var entry))
entry = new UiStyleOverride();
updater(entry);
if (entry.IsEmpty)
overrides.Remove(key);
else
overrides[key] = entry;
_themeConfigService.Save();
}
private void DrawThemeOverridesSection()
{
ImGui.TextUnformatted("Lightless Theme Overrides");
_uiShared.DrawHelpText("Adjust the Lightless redesign theme. Overrides only apply when the redesign is enabled.");
if (!_configService.Current.UseLightlessRedesign)
UiSharedService.ColorTextWrapped("The Lightless redesign is currently disabled. Enable it to see these changes take effect.", UIColors.Get("DimRed"));
const ImGuiTableFlags flags = ImGuiTableFlags.Borders | ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingStretchProp;
if (!ImGui.BeginTable("##ThemeOverridesTable", 3, flags))
return;
ImGui.TableSetupColumn("Element", ImGuiTableColumnFlags.WidthFixed, 325f);
ImGui.TableSetupColumn("Value", ImGuiTableColumnFlags.WidthStretch);
ImGui.TableSetupColumn("Reset", ImGuiTableColumnFlags.WidthFixed, 70f);
ImGui.TableHeadersRow();
DrawThemeCategoryRow("Colors");
foreach (var option in MainStyle.ColorOptions)
DrawThemeColorRow(option);
DrawThemeCategoryRow("Spacing & Padding");
foreach (var option in MainStyle.Vector2Options)
DrawThemeVectorRow(option);
DrawThemeCategoryRow("Rounding & Sizes");
foreach (var option in MainStyle.FloatOptions)
DrawThemeFloatRow(option);
ImGui.EndTable();
}
private static void DrawThemeCategoryRow(string label)
{
ImGui.TableNextRow();
ImGui.TableSetColumnIndex(0);
ImGui.TextColored(UIColors.Get("LightlessPurple"), label);
ImGui.TableSetColumnIndex(1);
ImGui.TableSetColumnIndex(2);
}
private void DrawThemeColorRow(MainStyle.StyleColorOption option)
{
ImGui.TableNextRow();
ImGui.TableSetColumnIndex(0);
ImGui.TextUnformatted(option.Label);
bool showTooltip = ImGui.IsItemHovered();
var tooltip = string.Empty;
if (!string.IsNullOrEmpty(option.Description))
tooltip = option.Description;
var overrides = _themeConfigService.Current.StyleOverrides;
overrides.TryGetValue(option.Key, out var existing);
if (!string.IsNullOrEmpty(option.UiColorKey))
{
if (!string.IsNullOrEmpty(tooltip))
tooltip += "\n";
tooltip += $"Default uses UIColors[\"{option.UiColorKey}\"]";
ImGui.SameLine();
ImGui.TextDisabled($"(UIColors.{option.UiColorKey})");
if (ImGui.IsItemHovered())
showTooltip = true;
}
ImGui.TableSetColumnIndex(2);
if (DrawStyleResetButton(option.Key, existing?.Color is not null))
{
UpdateStyleOverride(option.Key, entry =>
{
entry.Color = null;
entry.Float = null;
entry.Vector2 = null;
});
existing = null;
}
if (showTooltip && !string.IsNullOrEmpty(tooltip))
ImGui.SetTooltip(tooltip);
var defaultColor = MainStyle.NormalizeColorVector(option.DefaultValue());
var current = existing?.Color is { } packed ? PackedThemeColorToVector4(packed) : defaultColor;
var edit = current;
ImGui.TableSetColumnIndex(1);
if (ImGui.ColorEdit4($"##theme-color-{option.Key}", ref edit, ImGuiColorEditFlags.AlphaPreviewHalf))
{
UpdateStyleOverride(option.Key, entry =>
{
entry.Color = ThemeVector4ToPackedColor(edit);
entry.Float = null;
entry.Vector2 = null;
});
}
}
private void DrawThemeVectorRow(MainStyle.StyleVector2Option option)
{
ImGui.TableNextRow();
ImGui.TableSetColumnIndex(0);
ImGui.TextUnformatted(option.Label);
if (!string.IsNullOrEmpty(option.Description) && ImGui.IsItemHovered())
ImGui.SetTooltip(option.Description);
var overrides = _themeConfigService.Current.StyleOverrides;
overrides.TryGetValue(option.Key, out var existing);
ImGui.TableSetColumnIndex(2);
if (DrawStyleResetButton(option.Key, existing?.Vector2 is not null))
{
UpdateStyleOverride(option.Key, entry =>
{
entry.Vector2 = null;
entry.Color = null;
entry.Float = null;
});
existing = null;
}
var defaultValue = option.DefaultValue();
var current = existing?.Vector2 is { } vectorOverride ? (Vector2)vectorOverride : defaultValue;
var edit = current;
ImGui.TableSetColumnIndex(1);
if (ImGui.DragFloat2($"##theme-vector-{option.Key}", ref edit, option.Speed))
{
if (option.Min is { } min)
edit = Vector2.Max(edit, min);
if (option.Max is { } max)
edit = Vector2.Min(edit, max);
UpdateStyleOverride(option.Key, entry =>
{
entry.Vector2 = new Vector2Config(edit.X, edit.Y);
entry.Color = null;
entry.Float = null;
});
}
}
private void DrawThemeFloatRow(MainStyle.StyleFloatOption option)
{
ImGui.TableNextRow();
ImGui.TableSetColumnIndex(0);
ImGui.TextUnformatted(option.Label);
if (!string.IsNullOrEmpty(option.Description) && ImGui.IsItemHovered())
ImGui.SetTooltip(option.Description);
var overrides = _themeConfigService.Current.StyleOverrides;
overrides.TryGetValue(option.Key, out var existing);
ImGui.TableSetColumnIndex(2);
if (DrawStyleResetButton(option.Key, existing?.Float is not null))
{
UpdateStyleOverride(option.Key, entry =>
{
entry.Float = null;
entry.Color = null;
entry.Vector2 = null;
});
existing = null;
}
var current = existing?.Float ?? option.DefaultValue;
var edit = current;
var min = option.Min ?? float.MinValue;
var max = option.Max ?? float.MaxValue;
ImGui.TableSetColumnIndex(1);
if (ImGui.DragFloat($"##theme-float-{option.Key}", ref edit, option.Speed, min, max, "%.2f"))
{
if (option.Min.HasValue)
edit = MathF.Max(option.Min.Value, edit);
if (option.Max.HasValue)
edit = MathF.Min(option.Max.Value, edit);
UpdateStyleOverride(option.Key, entry =>
{
entry.Float = edit;
entry.Color = null;
entry.Vector2 = null;
});
}
}
private bool DrawStyleResetButton(string key, bool hasOverride, string? tooltipOverride = null)
{
using var id = ImRaii.PushId($"reset-{key}");
using var disabled = ImRaii.Disabled(!hasOverride);
var availableWidth = ImGui.GetContentRegionAvail().X;
bool pressed = false;
using (ImRaii.PushFont(UiBuilder.IconFont))
{
if (ImGui.Button(FontAwesomeIcon.Undo.ToIconString(), new Vector2(availableWidth, 0)))
pressed = true;
}
var tooltip = tooltipOverride ?? (hasOverride
? "Reset this style override to its default value."
: "Value already matches the default.");
UiSharedService.AttachToolTip(tooltip);
return pressed;
}
private void DrawBlockedTransfers()
@@ -1187,12 +1517,193 @@ public class SettingsUi : WindowMediatorSubscriberBase
ImGui.Separator();
var forceOpenLightfinder = _openLightfinderSectionOnNextDraw;
if (_openLightfinderSectionOnNextDraw)
{
ImGui.SetNextItemOpen(true, ImGuiCond.Always);
}
if (_uiShared.MediumTreeNode("Lightfinder", UIColors.Get("LightlessPurple")))
{
if (forceOpenLightfinder)
{
ImGui.SetScrollHereY();
}
_openLightfinderSectionOnNextDraw = false;
bool autoEnable = _configService.Current.LightfinderAutoEnableOnConnect;
var autoAlign = _configService.Current.LightfinderAutoAlign;
var offsetX = (int)_configService.Current.LightfinderLabelOffsetX;
var offsetY = (int)_configService.Current.LightfinderLabelOffsetY;
var labelScale = _configService.Current.LightfinderLabelScale;
bool showLightfinderInDtr = _configService.Current.ShowLightfinderInDtr;
var dtrLightfinderEnabled = SwapColorChannels(_configService.Current.DtrColorsLightfinderEnabled);
var dtrLightfinderDisabled = SwapColorChannels(_configService.Current.DtrColorsLightfinderDisabled);
var dtrLightfinderCooldown = SwapColorChannels(_configService.Current.DtrColorsLightfinderCooldown);
var dtrLightfinderUnavailable = SwapColorChannels(_configService.Current.DtrColorsLightfinderUnavailable);
ImGui.TextUnformatted("Connection");
if (ImGui.Checkbox("Auto-enable Lightfinder on server connection", ref autoEnable))
{
_configService.Current.LightfinderAutoEnableOnConnect = autoEnable;
_configService.Save();
}
_uiShared.DrawHelpText("When enabled, Lightfinder will automatically turn on after reconnecting to the Lightless server.");
_uiShared.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"), 1.5f);
ImGui.TextUnformatted("Lightfinder Nameplate Colors");
if (ImGui.BeginTable("##LightfinderColorTable", 3, ImGuiTableFlags.Borders | ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit))
{
ImGui.TableSetupColumn("Color", ImGuiTableColumnFlags.WidthFixed);
ImGui.TableSetupColumn("Description", ImGuiTableColumnFlags.WidthStretch);
ImGui.TableSetupColumn("Reset", ImGuiTableColumnFlags.WidthFixed, 40f);
ImGui.TableHeadersRow();
var lightfinderColors = new (string Key, string Label, string Description)[]
{
("Lightfinder", "Nameplate Text", "Color used for Lightfinder nameplate text."),
("LightfinderEdge", "Nameplate Outline", "Outline color applied around Lightfinder nameplate text.")
};
foreach (var (key, label, description) in lightfinderColors)
{
ImGui.TableNextRow();
ImGui.TableSetColumnIndex(0);
var colorValue = UIColors.Get(key);
if (ImGui.ColorEdit4($"##color_{key}", ref colorValue, ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.AlphaPreviewHalf))
{
UIColors.Set(key, colorValue);
}
ImGui.SameLine();
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted(label);
ImGui.TableSetColumnIndex(1);
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted(description);
ImGui.TableSetColumnIndex(2);
using var resetId = ImRaii.PushId($"Reset_{key}");
var availableWidth = ImGui.GetContentRegionAvail().X;
var isCustom = UIColors.IsCustom(key);
using (ImRaii.Disabled(!isCustom))
{
using (ImRaii.PushFont(UiBuilder.IconFont))
{
if (ImGui.Button(FontAwesomeIcon.Undo.ToIconString(), new Vector2(availableWidth, 0)))
{
UIColors.Reset(key);
}
}
}
UiSharedService.AttachToolTip(isCustom ? "Reset this color to default" : "Color is already at default value");
}
ImGui.EndTable();
}
ImGui.Spacing();
_uiShared.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"), 1.5f);
ImGui.TextUnformatted("Lightfinder Info Bar");
if (ImGui.Checkbox("Show Lightfinder status in Server info bar", ref showLightfinderInDtr))
{
_configService.Current.ShowLightfinderInDtr = showLightfinderInDtr;
_configService.Save();
}
_uiShared.DrawHelpText("Adds a Lightfinder status to the Server info bar. Left click toggles Lightfinder when visible.");
var lightfinderDisplayMode = _configService.Current.LightfinderDtrDisplayMode;
var lightfinderDisplayLabel = lightfinderDisplayMode switch
{
LightfinderDtrDisplayMode.PendingPairRequests => "Pending pair requests",
_ => "Nearby Lightfinder users",
};
ImGui.BeginDisabled(!showLightfinderInDtr);
if (ImGui.BeginCombo("Info display", lightfinderDisplayLabel))
{
foreach (var option in Enum.GetValues<LightfinderDtrDisplayMode>())
{
var optionLabel = option switch
{
LightfinderDtrDisplayMode.PendingPairRequests => "Pending pair requests",
_ => "Nearby Lightfinder users",
};
var selected = option == lightfinderDisplayMode;
if (ImGui.Selectable(optionLabel, selected))
{
_configService.Current.LightfinderDtrDisplayMode = option;
_configService.Save();
}
if (selected)
ImGui.SetItemDefaultFocus();
}
ImGui.EndCombo();
}
ImGui.EndDisabled();
_uiShared.DrawHelpText("Choose what the Lightfinder info bar displays while Lightfinder is active.");
bool useLightfinderColors = _configService.Current.UseLightfinderColorsInDtr;
if (ImGui.Checkbox("Color-code the Lightfinder info bar according to status", ref useLightfinderColors))
{
_configService.Current.UseLightfinderColorsInDtr = useLightfinderColors;
_configService.Save();
}
ImGui.BeginDisabled(!showLightfinderInDtr || !useLightfinderColors);
const ImGuiTableFlags lightfinderInfoTableFlags = ImGuiTableFlags.Borders | ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit;
if (ImGui.BeginTable("##LightfinderInfoBarColorTable", 3, lightfinderInfoTableFlags))
{
ImGui.TableSetupColumn("Status", ImGuiTableColumnFlags.WidthFixed, 220f);
ImGui.TableSetupColumn("Description", ImGuiTableColumnFlags.WidthStretch);
ImGui.TableSetupColumn("Reset", ImGuiTableColumnFlags.WidthFixed, 40f);
ImGui.TableHeadersRow();
DrawDtrColorRow(
"enabled",
"Enabled",
"Displayed when Lightfinder is active.",
ref dtrLightfinderEnabled,
SwapColorChannels(DefaultConfig.DtrColorsLightfinderEnabled),
value => _configService.Current.DtrColorsLightfinderEnabled = SwapColorChannels(value));
DrawDtrColorRow(
"disabled",
"Disabled",
"Shown when Lightfinder is turned off.",
ref dtrLightfinderDisabled,
SwapColorChannels(DefaultConfig.DtrColorsLightfinderDisabled),
value => _configService.Current.DtrColorsLightfinderDisabled = SwapColorChannels(value));
DrawDtrColorRow(
"cooldown",
"Cooldown",
"Displayed while Lightfinder is on cooldown.",
ref dtrLightfinderCooldown,
SwapColorChannels(DefaultConfig.DtrColorsLightfinderCooldown),
value => _configService.Current.DtrColorsLightfinderCooldown = SwapColorChannels(value));
DrawDtrColorRow(
"unavailable",
"Unavailable",
"Used when Lightfinder is not available on the current server.",
ref dtrLightfinderUnavailable,
SwapColorChannels(DefaultConfig.DtrColorsLightfinderUnavailable),
value => _configService.Current.DtrColorsLightfinderUnavailable = SwapColorChannels(value));
ImGui.EndTable();
}
ImGui.EndDisabled();
_uiShared.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"), 1.5f);
ImGui.TextUnformatted("Alignment");
ImGui.BeginDisabled(autoAlign);
@@ -1344,6 +1855,17 @@ public class SettingsUi : WindowMediatorSubscriberBase
_uiShared.DrawHelpText("Toggles paired player(s) Lightfinder label.");
var showHidden = _configService.Current.LightfinderLabelShowHidden;
if (ImGui.Checkbox("Show Lightfinder label when no nameplate(s) is visible", ref showHidden))
{
_configService.Current.LightfinderLabelShowHidden = showHidden;
_configService.Save();
_nameplateHandler.ClearNameplateCaches();
_nameplateHandler.FlagRefresh();
_nameplateService.RequestRedraw();
}
_uiShared.DrawHelpText("Toggles Lightfinder label when no nameplate(s) is visible.");
_uiShared.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"), 1.5f);
ImGui.TextUnformatted("Label");
@@ -1389,7 +1911,8 @@ public class SettingsUi : WindowMediatorSubscriberBase
var selected = i == _lightfinderIconPresetIndex;
if (ImGui.Selectable(preview, selected))
{
ApplyLightfinderIcon(optionGlyph, i);
_lightfinderIconInput = NameplateHandler.ToIconEditorString(optionGlyph);
_lightfinderIconPresetIndex = i;
}
}
@@ -1550,29 +2073,42 @@ public class SettingsUi : WindowMediatorSubscriberBase
_uiShared.DrawHelpText(
"This will color the Server Info Bar entry based on connection status and visible pairs.");
using (ImRaii.Disabled(!useColorsInDtr))
ImGui.BeginDisabled(!useColorsInDtr);
const ImGuiTableFlags serverInfoTableFlags = ImGuiTableFlags.Borders | ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit;
if (ImGui.BeginTable("##ServerInfoBarColorTable", 3, serverInfoTableFlags))
{
using var indent = ImRaii.PushIndent();
if (InputDtrColors("Default", ref dtrColorsDefault))
{
_configService.Current.DtrColorsDefault = dtrColorsDefault;
_configService.Save();
}
ImGui.TableSetupColumn("Status", ImGuiTableColumnFlags.WidthFixed, 220f);
ImGui.TableSetupColumn("Description", ImGuiTableColumnFlags.WidthStretch);
ImGui.TableSetupColumn("Reset", ImGuiTableColumnFlags.WidthFixed, 40f);
ImGui.TableHeadersRow();
ImGui.SameLine();
if (InputDtrColors("Not Connected", ref dtrColorsNotConnected))
{
_configService.Current.DtrColorsNotConnected = dtrColorsNotConnected;
_configService.Save();
}
DrawDtrColorRow(
"server-default",
"Default",
"Displayed when connected without any special status.",
ref dtrColorsDefault,
DefaultConfig.DtrColorsDefault,
value => _configService.Current.DtrColorsDefault = value);
ImGui.SameLine();
if (InputDtrColors("Pairs in Range", ref dtrColorsPairsInRange))
{
_configService.Current.DtrColorsPairsInRange = dtrColorsPairsInRange;
_configService.Save();
}
DrawDtrColorRow(
"server-not-connected",
"Not Connected",
"Shown while disconnected from the Lightless server.",
ref dtrColorsNotConnected,
DefaultConfig.DtrColorsNotConnected,
value => _configService.Current.DtrColorsNotConnected = value);
DrawDtrColorRow(
"server-pairs",
"Pairs in Range",
"Used when nearby paired players are detected.",
ref dtrColorsPairsInRange,
DefaultConfig.DtrColorsPairsInRange,
value => _configService.Current.DtrColorsPairsInRange = value);
ImGui.EndTable();
}
ImGui.EndDisabled();
ImGui.Spacing();
@@ -1649,6 +2185,8 @@ public class SettingsUi : WindowMediatorSubscriberBase
_uiShared.DrawHelpText("This changes the vanity colored UID's in pair list.");
DrawThemeOverridesSection();
_uiShared.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f);
ImGui.TreePop();
}
@@ -2872,14 +3410,21 @@ public class SettingsUi : WindowMediatorSubscriberBase
ImGui.SameLine();
if (ImGui.Button("Lightless Sync Discord"))
{
Util.OpenLink("https://discord.gg/mpNdkrTRjW");
Util.OpenLink("https://discord.gg/Lightless");
}
ImGui.Separator();
if (ImGui.BeginTabBar("mainTabBar"))
{
if (ImGui.BeginTabItem("General"))
var generalTabFlags = ImGuiTabItemFlags.None;
if (_selectGeneralTabOnNextDraw)
{
generalTabFlags |= ImGuiTabItemFlags.SetSelected;
}
if (ImGui.BeginTabItem("General", generalTabFlags))
{
_selectGeneralTabOnNextDraw = false;
DrawGeneral();
ImGui.EndTabItem();
}

View File

@@ -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);
}

View File

@@ -462,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;

View File

@@ -26,6 +26,9 @@ namespace LightlessSync.UI
{ "LightlessAdminGlow", "#b09343" },
{ "LightlessModeratorText", "#94ffda" },
{ "LightlessModeratorGlow", "#599c84" },
{ "Lightfinder", "#ad8af5" },
{ "LightfinderEdge", "#000000" },
};
private static LightlessConfigService? _configService;