Compare commits

...

21 Commits

Author SHA1 Message Date
defnotken
6db9925693 Dev be building
All checks were successful
Tag and Release Lightless / tag-and-release (push) Successful in 2m27s
2025-12-20 13:45:34 -06:00
defnotken
0ba324bfe5 Merge branch '2.0.0' into dev 2025-12-20 13:43:35 -06:00
03105e0755 fix log level 2025-12-21 04:28:36 +09:00
b99f68a891 collapsible texture details 2025-12-21 02:23:18 +09:00
7c7a98f770 This looks better 2025-12-21 01:19:17 +09:00
ab369d008e can drag chat tabs around as much as u want
syncshell tabs can use notes instead by rightclicking and prefering it
added some visibility settings (hide in combat, etc)
and cleaned up some of the ui
2025-12-21 01:17:00 +09:00
defnotken
f4665a0909 Brio fix
All checks were successful
Tag and Release Lightless / tag-and-release (push) Successful in 2m20s
2025-12-20 00:41:10 -06:00
defnotken
cb4bcec5e9 Merge branch '2.0.0' into dev 2025-12-20 00:40:31 -06:00
Minmoose
e5fa477eee Fix Brio IPC 2025-12-19 19:06:34 -06:00
defnotken
af607e4380 fixes to plugin.
All checks were successful
Tag and Release Lightless / tag-and-release (push) Successful in 2m26s
2025-12-19 15:36:54 -06:00
cake
ac8270e4ad Added chat command in handler 2025-12-19 22:34:04 +01:00
defnotken
29e6555480 Merge branch '2.0.0' into dev 2025-12-19 15:31:20 -06:00
defnotken
9402731b2b Merge branch '2.0.0' into dev 2025-12-19 15:30:44 -06:00
cake
4d0bf2d57e Updated Brio SDK 2025-12-19 22:29:37 +01:00
7f74f88302 Merge branch '2.0.0' of https://git.lightless-sync.org/Lightless-Sync/LightlessClient into 2.0.0 2025-12-20 04:01:20 +09:00
cake
934cdfbcf0 updated nuget packages 2025-12-19 19:57:56 +01:00
cake
d2a68e6533 Disabled sort on payload on group submit 2025-12-19 19:49:30 +01:00
20008f904d fix send button and improve input focus 2025-12-20 03:39:28 +09:00
cake
54b50886c0 Fixed UID scaling on fontsize 2025-12-19 19:38:43 +01:00
cake
234fe5d360 Fixed font size issue on player names. 2025-12-19 19:20:41 +01:00
defnotken
05770d9a5b update workflow 2025-12-19 10:25:29 -06:00
17 changed files with 859 additions and 159 deletions

View File

@@ -13,7 +13,7 @@ namespace LightlessSync.Interop.Ipc;
public sealed class IpcCallerBrio : IpcServiceBase
{
private static readonly IpcServiceDescriptor BrioDescriptor = new("Brio", "Brio", new Version(3, 0, 0, 0));
private static readonly IpcServiceDescriptor BrioDescriptor = new("Brio", "Brio", new Version(0, 0, 0, 0));
private readonly ILogger<IpcCallerBrio> _logger;
private readonly DalamudUtilService _dalamudUtilService;
@@ -144,7 +144,7 @@ public sealed class IpcCallerBrio : IpcServiceBase
try
{
var version = _apiVersion.Invoke();
return version.Item1 == 3 && version.Item2 >= 0
return version.Breaking == 3 && version.Feature >= 0
? IpcConnectionState.Available
: IpcConnectionState.VersionMismatch;
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
namespace LightlessSync.LightlessConfiguration.Configurations;
@@ -13,4 +14,11 @@ public sealed class ChatConfig : ILightlessConfiguration
public bool IsWindowPinned { get; set; } = false;
public bool AutoOpenChatOnPluginLoad { get; set; } = false;
public float ChatFontScale { get; set; } = 1.0f;
public bool HideInCombat { get; set; } = false;
public bool HideInDuty { get; set; } = false;
public bool ShowWhenUiHidden { get; set; } = true;
public bool ShowInCutscenes { get; set; } = true;
public bool ShowInGpose { get; set; } = true;
public List<string> ChannelOrder { get; set; } = new();
public Dictionary<string, bool> PreferNotesForChannels { get; set; } = new(StringComparer.Ordinal);
}

View File

@@ -3,7 +3,7 @@
<PropertyGroup>
<Authors></Authors>
<Company></Company>
<Version>1.42.0.69</Version>
<Version>1.42.0.72</Version>
<Description></Description>
<Copyright></Copyright>
<PackageProjectUrl>https://github.com/Light-Public-Syncshells/LightlessClient</PackageProjectUrl>
@@ -28,10 +28,10 @@
<ItemGroup>
<PackageReference Include="Blake3" Version="2.0.0" />
<PackageReference Include="Brio.API" Version="3.0.0" />
<PackageReference Include="Brio.API" Version="3.0.1" />
<PackageReference Include="Downloader" Version="4.0.3" />
<PackageReference Include="K4os.Compression.LZ4.Legacy" Version="1.3.8" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.212">
<PackageReference Include="Meziantou.Analyzer" Version="2.0.264">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
@@ -39,13 +39,13 @@
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.1" />
<PackageReference Include="Glamourer.Api" Version="2.8.0" />
<PackageReference Include="NReco.Logging.File" Version="1.2.2" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.11" />
<PackageReference Include="SonarAnalyzer.CSharp" Version="10.7.0.110445">
<PackageReference Include="NReco.Logging.File" Version="1.3.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
<PackageReference Include="SonarAnalyzer.CSharp" Version="10.17.0.131074">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.7.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.15.0" />
<PackageReference Include="YamlDotNet" Version="16.3.0" />
</ItemGroup>

View File

@@ -1,5 +1,6 @@
using Dalamud.Game;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Interface;
using Dalamud.Interface.ImGuiFileDialog;
using Dalamud.Interface.Windowing;
using Dalamud.Plugin;
@@ -105,6 +106,7 @@ public sealed class Plugin : IDalamudPlugin
services.AddSingleton(new Dalamud.Localization("LightlessSync.Localization.", string.Empty, useEmbedded: true));
services.AddSingleton(gameGui);
services.AddSingleton(addonLifecycle);
services.AddSingleton<IUiBuilder>(pluginInterface.UiBuilder);
// Core singletons
services.AddSingleton<LightlessMediator>();

View File

@@ -23,6 +23,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
private readonly DalamudUtilService _dalamudUtilService;
private readonly ActorObjectService _actorObjectService;
private readonly PairUiService _pairUiService;
private readonly ChatConfigService _chatConfigService;
private readonly Lock _sync = new();
@@ -57,6 +58,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
_dalamudUtilService = dalamudUtilService;
_actorObjectService = actorObjectService;
_pairUiService = pairUiService;
_chatConfigService = chatConfigService;
_isLoggedIn = _dalamudUtilService.IsLoggedIn;
_isConnected = _apiController.IsConnected;
@@ -136,6 +138,42 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
}
}
public void MoveChannel(string draggedKey, string targetKey)
{
if (string.IsNullOrWhiteSpace(draggedKey) || string.IsNullOrWhiteSpace(targetKey))
{
return;
}
bool updated = false;
using (_sync.EnterScope())
{
if (!_channels.ContainsKey(draggedKey) || !_channels.ContainsKey(targetKey))
{
return;
}
var fromIndex = _channelOrder.IndexOf(draggedKey);
var toIndex = _channelOrder.IndexOf(targetKey);
if (fromIndex < 0 || toIndex < 0 || fromIndex == toIndex)
{
return;
}
_channelOrder.RemoveAt(fromIndex);
var insertIndex = Math.Clamp(toIndex, 0, _channelOrder.Count);
_channelOrder.Insert(insertIndex, draggedKey);
_chatConfigService.Current.ChannelOrder = new List<string>(_channelOrder);
_chatConfigService.Save();
updated = true;
}
if (updated)
{
PublishChannelListChanged();
}
}
public Task SetChatEnabledAsync(bool enabled)
=> enabled ? EnableChatAsync() : DisableChatAsync();
@@ -512,7 +550,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
if (!_isLoggedIn || !_apiController.IsConnected)
{
await LeaveCurrentZoneAsync(force, 0).ConfigureAwait(false);
await LeaveCurrentZoneAsync(force, 0, 0).ConfigureAwait(false);
return;
}
@@ -520,6 +558,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
{
var location = await _dalamudUtilService.GetMapDataAsync().ConfigureAwait(false);
var territoryId = (ushort)location.TerritoryId;
var worldId = (ushort)location.ServerId;
string? zoneKey;
ZoneChannelDefinition? definition = null;
@@ -536,14 +575,14 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
if (definition is null)
{
await LeaveCurrentZoneAsync(force, territoryId).ConfigureAwait(false);
await LeaveCurrentZoneAsync(force, territoryId, worldId).ConfigureAwait(false);
return;
}
var descriptor = await BuildZoneDescriptorAsync(definition.Value).ConfigureAwait(false);
if (descriptor is null)
{
await LeaveCurrentZoneAsync(force, territoryId).ConfigureAwait(false);
await LeaveCurrentZoneAsync(force, territoryId, worldId).ConfigureAwait(false);
return;
}
@@ -586,7 +625,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
}
}
private async Task LeaveCurrentZoneAsync(bool force, ushort territoryId)
private async Task LeaveCurrentZoneAsync(bool force, ushort territoryId, ushort worldId)
{
ChatChannelDescriptor? descriptor = null;
@@ -602,7 +641,27 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
state.StatusText = !_chatEnabled
? "Chat services disabled"
: (_isConnected ? ZoneUnavailableMessage : "Disconnected from chat server");
state.DisplayName = "Zone Chat";
if (territoryId != 0
&& _dalamudUtilService.TerritoryData.Value.TryGetValue(territoryId, out var territoryName)
&& !string.IsNullOrWhiteSpace(territoryName))
{
state.DisplayName = territoryName;
}
else
{
state.DisplayName = "Zone Chat";
}
if (worldId != 0)
{
state.Descriptor = new ChatChannelDescriptor
{
Type = ChatChannelType.Zone,
WorldId = worldId,
ZoneId = territoryId,
CustomKey = string.Empty
};
}
}
if (string.Equals(_activeChannelKey, ZoneChannelKey, StringComparison.Ordinal))
@@ -1092,17 +1151,50 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
{
_channelOrder.Clear();
if (_channels.ContainsKey(ZoneChannelKey))
var configuredOrder = _chatConfigService.Current.ChannelOrder;
if (configuredOrder.Count > 0)
{
_channelOrder.Add(ZoneChannelKey);
var seen = new HashSet<string>(StringComparer.Ordinal);
foreach (var key in configuredOrder)
{
if (_channels.ContainsKey(key) && seen.Add(key))
{
_channelOrder.Add(key);
}
}
var remaining = _channels.Values
.Where(state => !seen.Contains(state.Key))
.ToList();
if (remaining.Count > 0)
{
var zoneKeys = remaining
.Where(state => state.Type == ChatChannelType.Zone)
.Select(state => state.Key);
var groupKeys = remaining
.Where(state => state.Type == ChatChannelType.Group)
.OrderBy(state => state.DisplayName, StringComparer.OrdinalIgnoreCase)
.Select(state => state.Key);
_channelOrder.AddRange(zoneKeys);
_channelOrder.AddRange(groupKeys);
}
}
else
{
if (_channels.ContainsKey(ZoneChannelKey))
{
_channelOrder.Add(ZoneChannelKey);
}
var groups = _channels.Values
.Where(state => state.Type == ChatChannelType.Group)
.OrderBy(state => state.DisplayName, StringComparer.OrdinalIgnoreCase)
.Select(state => state.Key);
var groups = _channels.Values
.Where(state => state.Type == ChatChannelType.Group)
.OrderBy(state => state.DisplayName, StringComparer.OrdinalIgnoreCase)
.Select(state => state.Key);
_channelOrder.AddRange(groups);
_channelOrder.AddRange(groups);
}
if (_activeChannelKey is null && _channelOrder.Count > 0)
{

View File

@@ -48,7 +48,8 @@ public sealed class CommandManagerService : IDisposable
"\t /light gpose - Opens the Lightless Character Data Hub window" + Environment.NewLine +
"\t /light analyze - Opens the Lightless Character Data Analysis window" + Environment.NewLine +
"\t /light settings - Opens the Lightless Settings window" + Environment.NewLine +
"\t /light finder - Opens the Lightfinder window"
"\t /light finder - Opens the Lightfinder window" + Environment.NewLine +
"\t /light finder - Opens the Lightless Chat window"
});
}
@@ -133,5 +134,9 @@ public sealed class CommandManagerService : IDisposable
{
_mediator.Publish(new UiToggleMessage(typeof(LightFinderUI)));
}
else if (string.Equals(splitArgs[0], "chat", StringComparison.OrdinalIgnoreCase))
{
_mediator.Publish(new UiToggleMessage(typeof(ZoneChatUi)));
}
}
}

View File

@@ -239,6 +239,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
public bool IsInCombat { get; private set; } = false;
public bool IsPerforming { get; private set; } = false;
public bool IsInInstance { get; private set; } = false;
public bool IsInDuty => _condition[ConditionFlag.BoundByDuty];
public bool HasModifiedGameFiles => _gameData.HasModifiedGameDataFiles;
public uint ClassJobId => _classJobId!.Value;
public Lazy<Dictionary<uint, string>> JobData { get; private set; }
@@ -248,6 +249,32 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
public bool IsLodEnabled { get; private set; }
public LightlessMediator Mediator { get; }
public bool IsInFieldOperation
{
get
{
if (!IsInDuty)
{
return false;
}
var territoryId = _clientState.TerritoryType;
if (territoryId == 0)
{
return false;
}
if (!TerritoryData.Value.TryGetValue(territoryId, out var name) || string.IsNullOrWhiteSpace(name))
{
return false;
}
return name.Contains("Eureka", StringComparison.OrdinalIgnoreCase)
|| name.Contains("Bozja", StringComparison.OrdinalIgnoreCase)
|| name.Contains("Zadnor", StringComparison.OrdinalIgnoreCase);
}
}
public IGameObject? CreateGameObject(IntPtr reference)
{
EnsureIsOnFramework();

View File

@@ -641,8 +641,8 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
return;
_activeBroadcastingCids = newSet;
if (_logger.IsEnabled(LogLevel.Information))
_logger.LogInformation("Active broadcast CIDs: {Cids}", string.Join(',', _activeBroadcastingCids));
if (_logger.IsEnabled(LogLevel.Trace))
_logger.LogTrace("Active broadcast IDs: {Cids}", string.Join(',', _activeBroadcastingCids));
FlagRefresh();
}

View File

@@ -629,8 +629,9 @@ public class CompactUi : WindowMediatorSubscriberBase
{
var seString = SeStringUtils.BuildFormattedPlayerName(uidText, vanityTextColor, vanityGlowColor);
var cursorPos = ImGui.GetCursorScreenPos();
var fontPtr = ImGui.GetFont();
SeStringUtils.RenderSeStringWithHitbox(seString, cursorPos, fontPtr, "uid-header");
var targetFontSize = ImGui.GetFontSize();
var font = ImGui.GetFont();
SeStringUtils.RenderSeStringWithHitbox(seString, cursorPos, targetFontSize ,font , "uid-header");
}
else
{
@@ -716,8 +717,9 @@ public class CompactUi : WindowMediatorSubscriberBase
{
var seString = SeStringUtils.BuildFormattedPlayerName(_apiController.UID, vanityTextColor, vanityGlowColor);
var cursorPos = ImGui.GetCursorScreenPos();
var fontPtr = ImGui.GetFont();
SeStringUtils.RenderSeStringWithHitbox(seString, cursorPos, fontPtr, "uid-footer");
var targetFontSize = ImGui.GetFontSize();
var font = ImGui.GetFont();
SeStringUtils.RenderSeStringWithHitbox(seString, cursorPos, targetFontSize, font, "uid-footer");
}
else
{

View File

@@ -27,8 +27,11 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
{
private const float MinTextureFilterPaneWidth = 305f;
private const float MaxTextureFilterPaneWidth = 405f;
private const float MinTextureDetailPaneWidth = 580f;
private const float MinTextureDetailPaneWidth = 480f;
private const float MaxTextureDetailPaneWidth = 720f;
private const float TextureFilterSplitterWidth = 8f;
private const float TextureDetailSplitterWidth = 12f;
private const float TextureDetailSplitterCollapsedWidth = 18f;
private const float SelectedFilePanelLogicalHeight = 90f;
private static readonly Vector4 SelectedTextureRowTextColor = new(0f, 0f, 0f, 1f);
@@ -80,6 +83,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
private bool _modalOpen = false;
private bool _showModal = false;
private bool _textureRowsDirty = true;
private bool _textureDetailCollapsed = false;
private bool _conversionFailed;
private bool _showAlreadyAddedTransients = false;
private bool _acknowledgeReview = false;
@@ -111,7 +115,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
_hasUpdate = true;
});
WindowBuilder.For(this)
.SetSizeConstraints(new Vector2(1650, 1000), new Vector2(3840, 2160))
.SetSizeConstraints(new Vector2(1240, 680), new Vector2(3840, 2160))
.Apply();
_conversionProgress.ProgressChanged += ConversionProgress_ProgressChanged;
@@ -1205,35 +1209,52 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
var availableSize = ImGui.GetContentRegionAvail();
var windowPos = ImGui.GetWindowPos();
var spacingX = ImGui.GetStyle().ItemSpacing.X;
var splitterWidth = 6f * scale;
var filterSplitterWidth = TextureFilterSplitterWidth * scale;
var detailSplitterWidth = (_textureDetailCollapsed ? TextureDetailSplitterCollapsedWidth : TextureDetailSplitterWidth) * scale;
var totalSplitterWidth = filterSplitterWidth + detailSplitterWidth;
var totalSpacing = 2 * spacingX;
const float minFilterWidth = MinTextureFilterPaneWidth;
const float minDetailWidth = MinTextureDetailPaneWidth;
const float minCenterWidth = 340f;
var dynamicFilterMax = Math.Max(minFilterWidth, availableSize.X - minDetailWidth - minCenterWidth - 2 * (splitterWidth + spacingX));
var detailMinForLayout = _textureDetailCollapsed ? 0f : minDetailWidth;
var dynamicFilterMax = Math.Max(minFilterWidth, availableSize.X - detailMinForLayout - minCenterWidth - totalSplitterWidth - totalSpacing);
var filterMaxBound = Math.Min(MaxTextureFilterPaneWidth, dynamicFilterMax);
var filterWidth = Math.Clamp(_textureFilterPaneWidth, minFilterWidth, filterMaxBound);
var dynamicDetailMax = Math.Max(minDetailWidth, availableSize.X - filterWidth - minCenterWidth - 2 * (splitterWidth + spacingX));
var detailMaxBound = Math.Min(MaxTextureDetailPaneWidth, dynamicDetailMax);
var detailWidth = Math.Clamp(_textureDetailPaneWidth, minDetailWidth, detailMaxBound);
var dynamicDetailMax = Math.Max(detailMinForLayout, availableSize.X - filterWidth - minCenterWidth - totalSplitterWidth - totalSpacing);
var detailMaxBound = _textureDetailCollapsed ? 0f : Math.Min(MaxTextureDetailPaneWidth, dynamicDetailMax);
var detailWidth = _textureDetailCollapsed ? 0f : Math.Clamp(_textureDetailPaneWidth, minDetailWidth, detailMaxBound);
var centerWidth = availableSize.X - filterWidth - detailWidth - 2 * (splitterWidth + spacingX);
var centerWidth = availableSize.X - filterWidth - detailWidth - totalSplitterWidth - totalSpacing;
if (centerWidth < minCenterWidth)
{
var deficit = minCenterWidth - centerWidth;
detailWidth = Math.Clamp(detailWidth - deficit, minDetailWidth,
Math.Min(MaxTextureDetailPaneWidth, Math.Max(minDetailWidth, availableSize.X - filterWidth - minCenterWidth - 2 * (splitterWidth + spacingX))));
centerWidth = availableSize.X - filterWidth - detailWidth - 2 * (splitterWidth + spacingX);
if (centerWidth < minCenterWidth)
if (!_textureDetailCollapsed)
{
detailWidth = Math.Clamp(detailWidth - deficit, minDetailWidth,
Math.Min(MaxTextureDetailPaneWidth, Math.Max(minDetailWidth, availableSize.X - filterWidth - minCenterWidth - totalSplitterWidth - totalSpacing)));
centerWidth = availableSize.X - filterWidth - detailWidth - totalSplitterWidth - totalSpacing;
if (centerWidth < minCenterWidth)
{
deficit = minCenterWidth - centerWidth;
filterWidth = Math.Clamp(filterWidth - deficit, minFilterWidth,
Math.Min(MaxTextureFilterPaneWidth, Math.Max(minFilterWidth, availableSize.X - detailWidth - minCenterWidth - totalSplitterWidth - totalSpacing)));
detailWidth = Math.Clamp(detailWidth, minDetailWidth,
Math.Min(MaxTextureDetailPaneWidth, Math.Max(minDetailWidth, availableSize.X - filterWidth - minCenterWidth - totalSplitterWidth - totalSpacing)));
centerWidth = availableSize.X - filterWidth - detailWidth - totalSplitterWidth - totalSpacing;
if (centerWidth < minCenterWidth)
{
centerWidth = minCenterWidth;
}
}
}
else
{
deficit = minCenterWidth - centerWidth;
filterWidth = Math.Clamp(filterWidth - deficit, minFilterWidth,
Math.Min(MaxTextureFilterPaneWidth, Math.Max(minFilterWidth, availableSize.X - detailWidth - minCenterWidth - 2 * (splitterWidth + spacingX))));
detailWidth = Math.Clamp(detailWidth, minDetailWidth,
Math.Min(MaxTextureDetailPaneWidth, Math.Max(minDetailWidth, availableSize.X - filterWidth - minCenterWidth - 2 * (splitterWidth + spacingX))));
centerWidth = availableSize.X - filterWidth - detailWidth - 2 * (splitterWidth + spacingX);
Math.Min(MaxTextureFilterPaneWidth, Math.Max(minFilterWidth, availableSize.X - minCenterWidth - totalSplitterWidth - totalSpacing)));
centerWidth = availableSize.X - filterWidth - detailWidth - totalSplitterWidth - totalSpacing;
if (centerWidth < minCenterWidth)
{
centerWidth = minCenterWidth;
@@ -1242,7 +1263,10 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
}
_textureFilterPaneWidth = filterWidth;
_textureDetailPaneWidth = detailWidth;
if (!_textureDetailCollapsed)
{
_textureDetailPaneWidth = detailWidth;
}
ImGui.BeginGroup();
using (var filters = ImRaii.Child("textureFilters", new Vector2(filterWidth, 0), true))
@@ -1264,8 +1288,8 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
var filterMax = ImGui.GetItemRectMax();
var filterHeight = filterMax.Y - filterMin.Y;
var filterTopLocal = filterMin - windowPos;
var maxFilterResize = Math.Min(MaxTextureFilterPaneWidth, Math.Max(minFilterWidth, availableSize.X - minCenterWidth - minDetailWidth - 2 * (splitterWidth + spacingX)));
DrawVerticalResizeHandle("##textureFilterSplitter", filterTopLocal.Y, filterHeight, ref _textureFilterPaneWidth, minFilterWidth, maxFilterResize);
var maxFilterResize = Math.Min(MaxTextureFilterPaneWidth, Math.Max(minFilterWidth, availableSize.X - minCenterWidth - detailMinForLayout - totalSplitterWidth - totalSpacing));
DrawVerticalResizeHandle("##textureFilterSplitter", filterTopLocal.Y, filterHeight, ref _textureFilterPaneWidth, minFilterWidth, maxFilterResize, out _);
TextureRow? selectedRow;
ImGui.BeginGroup();
@@ -1279,15 +1303,36 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
var tableMax = ImGui.GetItemRectMax();
var tableHeight = tableMax.Y - tableMin.Y;
var tableTopLocal = tableMin - windowPos;
var maxDetailResize = Math.Min(MaxTextureDetailPaneWidth, Math.Max(minDetailWidth, availableSize.X - _textureFilterPaneWidth - minCenterWidth - 2 * (splitterWidth + spacingX)));
DrawVerticalResizeHandle("##textureDetailSplitter", tableTopLocal.Y, tableHeight, ref _textureDetailPaneWidth, minDetailWidth, maxDetailResize, invert: true);
ImGui.BeginGroup();
using (var detailChild = ImRaii.Child("textureDetailPane", new Vector2(detailWidth, 0), true))
var maxDetailResize = Math.Min(MaxTextureDetailPaneWidth, Math.Max(minDetailWidth, availableSize.X - _textureFilterPaneWidth - minCenterWidth - totalSplitterWidth - totalSpacing));
var detailToggle = DrawVerticalResizeHandle(
"##textureDetailSplitter",
tableTopLocal.Y,
tableHeight,
ref _textureDetailPaneWidth,
minDetailWidth,
maxDetailResize,
out var detailDragging,
invert: true,
showToggle: true,
isCollapsed: _textureDetailCollapsed);
if (detailToggle)
{
DrawTextureDetail(selectedRow);
_textureDetailCollapsed = !_textureDetailCollapsed;
}
if (_textureDetailCollapsed && detailDragging)
{
_textureDetailCollapsed = false;
}
if (!_textureDetailCollapsed)
{
ImGui.BeginGroup();
using (var detailChild = ImRaii.Child("textureDetailPane", new Vector2(detailWidth, 0), true))
{
DrawTextureDetail(selectedRow);
}
ImGui.EndGroup();
}
ImGui.EndGroup();
}
private void DrawTextureFilters(
@@ -1935,26 +1980,118 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
}
}
private void DrawVerticalResizeHandle(string id, float topY, float height, ref float leftWidth, float minWidth, float maxWidth, bool invert = false)
private bool DrawVerticalResizeHandle(
string id,
float topY,
float height,
ref float leftWidth,
float minWidth,
float maxWidth,
out bool isDragging,
bool invert = false,
bool showToggle = false,
bool isCollapsed = false)
{
var scale = ImGuiHelpers.GlobalScale;
var splitterWidth = 8f * scale;
var splitterWidth = (showToggle
? (isCollapsed ? TextureDetailSplitterCollapsedWidth : TextureDetailSplitterWidth)
: TextureFilterSplitterWidth) * scale;
ImGui.SameLine();
var cursor = ImGui.GetCursorPos();
ImGui.SetCursorPos(new Vector2(cursor.X, topY));
ImGui.PushStyleColor(ImGuiCol.Button, UIColors.Get("ButtonDefault"));
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, UIColors.Get("LightlessPurple"));
ImGui.PushStyleColor(ImGuiCol.ButtonActive, UIColors.Get("LightlessPurpleActive"));
ImGui.Button(id, new Vector2(splitterWidth, height));
ImGui.PopStyleColor(3);
var contentMin = ImGui.GetWindowContentRegionMin();
var contentMax = ImGui.GetWindowContentRegionMax();
var clampedTop = MathF.Max(topY, contentMin.Y);
var clampedBottom = MathF.Min(topY + height, contentMax.Y);
var clampedHeight = MathF.Max(0f, clampedBottom - clampedTop);
var splitterRounding = ImGui.GetStyle().FrameRounding;
ImGui.SetCursorPos(new Vector2(cursor.X, clampedTop));
if (clampedHeight <= 0f)
{
isDragging = false;
ImGui.SetCursorPos(new Vector2(cursor.X + splitterWidth + ImGui.GetStyle().ItemSpacing.X, cursor.Y));
return false;
}
if (ImGui.IsItemActive())
ImGui.InvisibleButton(id, new Vector2(splitterWidth, clampedHeight));
var drawList = ImGui.GetWindowDrawList();
var rectMin = ImGui.GetItemRectMin();
var rectMax = ImGui.GetItemRectMax();
var windowPos = ImGui.GetWindowPos();
var clipMin = windowPos + contentMin;
var clipMax = windowPos + contentMax;
drawList.PushClipRect(clipMin, clipMax, true);
var clipInset = 1f * scale;
var drawMin = new Vector2(
MathF.Max(rectMin.X, clipMin.X),
MathF.Max(rectMin.Y, clipMin.Y));
var drawMax = new Vector2(
MathF.Min(rectMax.X, clipMax.X - clipInset),
MathF.Min(rectMax.Y, clipMax.Y));
var hovered = ImGui.IsItemHovered();
isDragging = ImGui.IsItemActive();
var baseColor = UIColors.Get("ButtonDefault");
var hoverColor = UIColors.Get("LightlessPurple");
var activeColor = UIColors.Get("LightlessPurpleActive");
var handleColor = isDragging ? activeColor : hovered ? hoverColor : baseColor;
drawList.AddRectFilled(drawMin, drawMax, UiSharedService.Color(handleColor), splitterRounding);
drawList.AddRect(drawMin, drawMax, UiSharedService.Color(new Vector4(1f, 1f, 1f, 0.12f)), splitterRounding);
bool toggleHovered = false;
bool toggleClicked = false;
if (showToggle)
{
var icon = isCollapsed ? FontAwesomeIcon.ChevronRight : FontAwesomeIcon.ChevronLeft;
Vector2 iconSize;
using (_uiSharedService.IconFont.Push())
{
iconSize = ImGui.CalcTextSize(icon.ToIconString());
}
var toggleHeight = MathF.Min(clampedHeight, 64f * scale);
var toggleMin = new Vector2(
drawMin.X,
drawMin.Y + (drawMax.Y - drawMin.Y - toggleHeight) / 2f);
var toggleMax = new Vector2(
drawMax.X,
toggleMin.Y + toggleHeight);
var toggleColorBase = UIColors.Get("LightlessPurple");
toggleHovered = ImGui.IsMouseHoveringRect(toggleMin, toggleMax);
var toggleBg = toggleHovered
? new Vector4(toggleColorBase.X, toggleColorBase.Y, toggleColorBase.Z, 0.65f)
: new Vector4(toggleColorBase.X, toggleColorBase.Y, toggleColorBase.Z, 0.35f);
if (toggleHovered)
{
UiSharedService.AttachToolTip(isCollapsed ? "Show texture details." : "Hide texture details.");
}
drawList.AddRectFilled(toggleMin, toggleMax, UiSharedService.Color(toggleBg), splitterRounding);
drawList.AddRect(toggleMin, toggleMax, UiSharedService.Color(toggleColorBase), splitterRounding);
var iconPos = new Vector2(
drawMin.X + (drawMax.X - drawMin.X - iconSize.X) / 2f,
drawMin.Y + (drawMax.Y - drawMin.Y - iconSize.Y) / 2f);
using (_uiSharedService.IconFont.Push())
{
drawList.AddText(iconPos, ImGui.GetColorU32(ImGuiCol.Text), icon.ToIconString());
}
if (toggleHovered && ImGui.IsMouseReleased(ImGuiMouseButton.Left) && !ImGui.IsMouseDragging(ImGuiMouseButton.Left))
{
toggleClicked = true;
}
}
if (isDragging && !toggleHovered)
{
var delta = ImGui.GetIO().MouseDelta.X / scale;
leftWidth += invert ? -delta : delta;
leftWidth = Math.Clamp(leftWidth, minWidth, maxWidth);
}
drawList.PopClipRect();
ImGui.SetCursorPos(new Vector2(cursor.X + splitterWidth + ImGui.GetStyle().ItemSpacing.X, cursor.Y));
return toggleClicked;
}
private (IDalamudTextureWrap? Texture, bool IsLoading, string? Error) GetTexturePreview(TextureRow row)
@@ -2094,7 +2231,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
}
else
{
ImGui.TextDisabled("-");
_uiSharedService.IconText(FontAwesomeIcon.Check, ImGuiColors.DalamudWhite);
UiSharedService.AttachToolTip("Already stored in a compressed format; additional compression is disabled.");
}
@@ -2175,6 +2312,10 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
_textureSelections[key] = target;
currentSelection = target;
}
if (TextureMetadataHelper.TryGetRecommendationInfo(target, out var targetInfo))
{
UiSharedService.AttachToolTip($"{targetInfo.Title}{UiSharedService.TooltipSeparator}{targetInfo.Description}");
}
if (targetSelected)
{
ImGui.SetItemDefaultFocus();

View File

@@ -332,7 +332,7 @@ public partial class EditProfileUi
saveTooltip: "Apply the selected tags to this syncshell profile.",
submitAction: payload => SubmitGroupTagChanges(payload),
allowReorder: true,
sortPayloadBeforeSubmit: true,
sortPayloadBeforeSubmit: false,
onPayloadPrepared: payload =>
{
_tagEditorSelection.Clear();
@@ -586,7 +586,7 @@ public partial class EditProfileUi
IsNsfw: null,
IsDisabled: null)).ConfigureAwait(false);
_profileTagIds = payload.Length == 0 ? Array.Empty<int>() : payload.ToArray();
_profileTagIds = payload.Length == 0 ? [] : [.. payload];
Mediator.Publish(new ClearProfileGroupDataMessage(_groupInfo.Group));
}
catch (Exception ex)

View File

@@ -122,6 +122,7 @@ public class IdDisplayHandler
if (!string.Equals(_editEntry, pair.UserData.UID, StringComparison.Ordinal))
{
var targetFontSize = ImGui.GetFontSize();
var font = textIsUid ? UiBuilder.MonoFont : ImGui.GetFont();
var rowWidth = MathF.Max(editBoxWidth.Invoke(), 0f);
float rowRightLimit = 0f;
@@ -183,7 +184,7 @@ public class IdDisplayHandler
}
}
SeStringUtils.RenderSeStringWithHitbox(seString, rowStart, font, pair.UserData.UID);
SeStringUtils.RenderSeStringWithHitbox(seString, rowStart, targetFontSize, font, pair.UserData.UID);
nameRectMin = ImGui.GetItemRectMin();
nameRectMax = ImGui.GetItemRectMax();

View File

@@ -12,6 +12,7 @@ using LightlessSync.LightlessConfiguration.Models;
using LightlessSync.Services;
using LightlessSync.Services.Chat;
using LightlessSync.Services.Mediator;
using LightlessSync.Services.ServerConfiguration;
using LightlessSync.UI.Services;
using LightlessSync.Utils;
using LightlessSync.WebAPI;
@@ -23,8 +24,10 @@ namespace LightlessSync.UI;
public sealed class ZoneChatUi : WindowMediatorSubscriberBase
{
private const string ChatDisabledStatus = "Chat services disabled";
private const string ZoneUnavailableStatus = "Zone chat is only available in major cities.";
private const string SettingsPopupId = "zone_chat_settings_popup";
private const string ReportPopupId = "Report Message##zone_chat_report_popup";
private const string ChannelDragPayloadId = "zone_chat_channel_drag";
private const float DefaultWindowOpacity = .97f;
private const float MinWindowOpacity = 0.05f;
private const float MaxWindowOpacity = 1f;
@@ -32,6 +35,7 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
private const float MaxChatFontScale = 1.5f;
private const int ReportReasonMaxLength = 500;
private const int ReportContextMaxLength = 1000;
private const int MaxChannelNoteTabLength = 25;
private readonly UiSharedService _uiSharedService;
private readonly ZoneChatService _zoneChatService;
@@ -39,11 +43,16 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
private readonly LightlessProfileManager _profileManager;
private readonly ApiController _apiController;
private readonly ChatConfigService _chatConfigService;
private readonly ServerConfigurationManager _serverConfigurationManager;
private readonly DalamudUtilService _dalamudUtilService;
private readonly IUiBuilder _uiBuilder;
private readonly Dictionary<string, string> _draftMessages = new(StringComparer.Ordinal);
private readonly ImGuiWindowFlags _unpinnedWindowFlags;
private float _currentWindowOpacity = DefaultWindowOpacity;
private bool _isWindowPinned;
private bool _showRulesOverlay;
private bool _refocusChatInput;
private string? _refocusChatInputKey;
private string? _selectedChannelKey;
private bool _scrollToBottom = true;
@@ -59,6 +68,10 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
private bool _reportSubmitting;
private string? _reportError;
private ChatReportResult? _reportSubmissionResult;
private string? _dragChannelKey;
private string? _dragHoverKey;
private bool _HideStateActive;
private bool _HideStateWasOpen;
public ZoneChatUi(
ILogger<ZoneChatUi> logger,
@@ -68,6 +81,9 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
PairUiService pairUiService,
LightlessProfileManager profileManager,
ChatConfigService chatConfigService,
ServerConfigurationManager serverConfigurationManager,
DalamudUtilService dalamudUtilService,
IUiBuilder uiBuilder,
ApiController apiController,
PerformanceCollectorService performanceCollectorService)
: base(logger, mediator, "Lightless Chat", performanceCollectorService)
@@ -77,6 +93,9 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
_pairUiService = pairUiService;
_profileManager = profileManager;
_chatConfigService = chatConfigService;
_serverConfigurationManager = serverConfigurationManager;
_dalamudUtilService = dalamudUtilService;
_uiBuilder = uiBuilder;
_apiController = apiController;
_isWindowPinned = _chatConfigService.Current.IsWindowPinned;
_showRulesOverlay = _chatConfigService.Current.ShowRulesOverlayOnOpen;
@@ -86,6 +105,7 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
}
_unpinnedWindowFlags = Flags;
RefreshWindowFlags();
ApplyUiVisibilitySettings();
Size = new Vector2(450, 420) * ImGuiHelpers.GlobalScale;
SizeCondition = ImGuiCond.FirstUseEver;
WindowBuilder.For(this)
@@ -96,6 +116,8 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
Mediator.Subscribe<ChatChannelMessageAdded>(this, OnChatChannelMessageAdded);
Mediator.Subscribe<ChatChannelsUpdated>(this, _ => _scrollToBottom = true);
Mediator.Subscribe<PriorityFrameworkUpdateMessage>(this, _ => UpdateHideState());
Mediator.Subscribe<CutsceneFrameworkUpdateMessage>(this, _ => UpdateHideState());
}
public override void PreDraw()
@@ -106,6 +128,55 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
ImGui.SetNextWindowBgAlpha(_currentWindowOpacity);
}
private void UpdateHideState()
{
ApplyUiVisibilitySettings();
var shouldHide = ShouldHide();
if (shouldHide)
{
_HideStateWasOpen |= IsOpen;
if (IsOpen)
{
IsOpen = false;
}
_HideStateActive = true;
}
else if (_HideStateActive)
{
if (_HideStateWasOpen)
{
IsOpen = true;
}
_HideStateActive = false;
_HideStateWasOpen = false;
}
}
private void ApplyUiVisibilitySettings()
{
var config = _chatConfigService.Current;
_uiBuilder.DisableAutomaticUiHide = config.ShowWhenUiHidden;
_uiBuilder.DisableCutsceneUiHide = config.ShowInCutscenes;
_uiBuilder.DisableGposeUiHide = config.ShowInGpose;
}
private bool ShouldHide()
{
var config = _chatConfigService.Current;
if (config.HideInCombat && _dalamudUtilService.IsInCombat)
{
return true;
}
if (config.HideInDuty && _dalamudUtilService.IsInDuty && !_dalamudUtilService.IsInFieldOperation)
{
return true;
}
return false;
}
protected override void DrawInternal()
{
var childBgColor = ImGui.GetStyle().Colors[(int)ImGuiCol.ChildBg];
@@ -153,7 +224,7 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
}
}
private static void DrawHeader(ChatChannelSnapshot channel)
private void DrawHeader(ChatChannelSnapshot channel)
{
var prefix = channel.Type == ChatChannelType.Zone ? "Zone" : "Syncshell";
Vector4 color;
@@ -176,11 +247,18 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
if (channel.Type == ChatChannelType.Zone && channel.Descriptor.WorldId != 0)
{
ImGui.SameLine();
ImGui.TextUnformatted($"World #{channel.Descriptor.WorldId}");
var worldId = channel.Descriptor.WorldId;
var worldName = _dalamudUtilService.WorldData.Value.TryGetValue(worldId, out var name) ? name : $"World #{worldId}";
ImGui.TextUnformatted(worldName);
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip($"World ID: {worldId}");
}
}
var showInlineDisabled = string.Equals(channel.StatusText, ChatDisabledStatus, StringComparison.OrdinalIgnoreCase);
if (showInlineDisabled)
var showInlineStatus = string.Equals(channel.StatusText, ChatDisabledStatus, StringComparison.OrdinalIgnoreCase)
|| string.Equals(channel.StatusText, ZoneUnavailableStatus, StringComparison.OrdinalIgnoreCase);
if (showInlineStatus)
{
ImGui.SameLine();
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey3);
@@ -308,46 +386,69 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
_draftMessages.TryGetValue(channel.Key, out var draft);
draft ??= string.Empty;
var style = ImGui.GetStyle();
var sendButtonWidth = 100f * ImGuiHelpers.GlobalScale;
var counterWidth = ImGui.CalcTextSize($"{MaxMessageLength}/{MaxMessageLength}").X;
var reservedWidth = sendButtonWidth + counterWidth + style.ItemSpacing.X * 2f;
ImGui.SetNextItemWidth(-reservedWidth);
var inputId = $"##chat-input-{channel.Key}";
if (_refocusChatInput && string.Equals(_refocusChatInputKey, channel.Key, StringComparison.Ordinal))
{
ImGui.SetKeyboardFocusHere();
_refocusChatInput = false;
_refocusChatInputKey = null;
}
ImGui.InputText(inputId, ref draft, MaxMessageLength);
if (ImGui.IsItemActive() || ImGui.IsItemFocused())
{
var drawList = ImGui.GetWindowDrawList();
var itemMin = ImGui.GetItemRectMin();
var itemMax = ImGui.GetItemRectMax();
var highlight = UIColors.Get("LightlessPurple").WithAlpha(0.35f);
var highlightU32 = ImGui.ColorConvertFloat4ToU32(highlight);
drawList.AddRect(itemMin, itemMax, highlightU32, style.FrameRounding, ImDrawFlags.None, Math.Max(1f, ImGuiHelpers.GlobalScale));
}
var enterPressed = ImGui.IsItemFocused()
&& (ImGui.IsKeyPressed(ImGuiKey.Enter) || ImGui.IsKeyPressed(ImGuiKey.KeypadEnter));
_draftMessages[channel.Key] = draft;
ImGui.SameLine();
ImGui.AlignTextToFramePadding();
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey3);
ImGui.TextUnformatted($"{draft.Length}/{MaxMessageLength}");
ImGui.PopStyleColor();
ImGui.SameLine();
var buttonScreenPos = ImGui.GetCursorScreenPos();
var rightEdgeScreen = ImGui.GetWindowPos().X + ImGui.GetWindowContentRegionMax().X;
var desiredButtonX = rightEdgeScreen - sendButtonWidth;
var minButtonX = buttonScreenPos.X + style.ItemSpacing.X;
var finalButtonX = MathF.Max(minButtonX, desiredButtonX);
ImGui.SetCursorScreenPos(new Vector2(finalButtonX, buttonScreenPos.Y));
var sendColor = UIColors.Get("LightlessPurpleDefault");
var sendHovered = UIColors.Get("LightlessPurple");
var sendActive = UIColors.Get("LightlessPurpleActive");
ImGui.PushStyleColor(ImGuiCol.Button, sendColor);
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, sendHovered);
ImGui.PushStyleColor(ImGuiCol.ButtonActive, sendActive);
ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 6f * ImGuiHelpers.GlobalScale);
var sendClicked = false;
using (ImRaii.Disabled(!canSend))
{
var style = ImGui.GetStyle();
var sendButtonWidth = 100f * ImGuiHelpers.GlobalScale;
var counterWidth = ImGui.CalcTextSize($"{MaxMessageLength}/{MaxMessageLength}").X;
var reservedWidth = sendButtonWidth + counterWidth + style.ItemSpacing.X * 2f;
ImGui.SetNextItemWidth(-reservedWidth);
var inputId = $"##chat-input-{channel.Key}";
var send = ImGui.InputText(inputId, ref draft, MaxMessageLength, ImGuiInputTextFlags.EnterReturnsTrue);
_draftMessages[channel.Key] = draft;
ImGui.SameLine();
ImGui.AlignTextToFramePadding();
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey3);
ImGui.TextUnformatted($"{draft.Length}/{MaxMessageLength}");
ImGui.PopStyleColor();
ImGui.SameLine();
var buttonScreenPos = ImGui.GetCursorScreenPos();
var rightEdgeScreen = ImGui.GetWindowPos().X + ImGui.GetWindowContentRegionMax().X;
var desiredButtonX = rightEdgeScreen - sendButtonWidth;
var minButtonX = buttonScreenPos.X + style.ItemSpacing.X;
var finalButtonX = MathF.Max(minButtonX, desiredButtonX);
ImGui.SetCursorScreenPos(new Vector2(finalButtonX, buttonScreenPos.Y));
var sendColor = UIColors.Get("LightlessPurpleDefault");
var sendHovered = UIColors.Get("LightlessPurple");
var sendActive = UIColors.Get("LightlessPurpleActive");
ImGui.PushStyleColor(ImGuiCol.Button, sendColor);
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, sendHovered);
ImGui.PushStyleColor(ImGuiCol.ButtonActive, sendActive);
ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 6f * ImGuiHelpers.GlobalScale);
if (_uiSharedService.IconTextButton(FontAwesomeIcon.PaperPlane, "Send", 100f * ImGuiHelpers.GlobalScale, center: true))
if (_uiSharedService.IconTextButton(FontAwesomeIcon.PaperPlane, $"Send##chat-send-{channel.Key}", 100f * ImGuiHelpers.GlobalScale, center: true))
{
send = true;
sendClicked = true;
}
ImGui.PopStyleVar();
ImGui.PopStyleColor(3);
}
ImGui.PopStyleVar();
ImGui.PopStyleColor(3);
if (send && TrySendDraft(channel, draft))
if (canSend && (enterPressed || sendClicked))
{
_refocusChatInput = true;
_refocusChatInputKey = channel.Key;
if (TrySendDraft(channel, draft))
{
_draftMessages[channel.Key] = string.Empty;
_scrollToBottom = true;
@@ -464,7 +565,7 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
ImGui.Separator();
_uiSharedService.MediumText("Syncshell Chat Rules", UIColors.Get("LightlessYellow"));
_uiSharedService.DrawNoteLine("! ", UIColors.Get("LightlessYellow"), new SeStringUtils.RichTextEntry("Syncshell chats are self-moderated (their own set rules) by it's owner and appointed moderators. If they fail to enforce chat rules within their syncshell, the owner (and its moderators) may face punishment."));
_uiSharedService.DrawNoteLine("! ", UIColors.Get("LightlessYellow"), new SeStringUtils.RichTextEntry("Syncshell chats are self-moderated (their own set rules) by it's owner and appointed moderators."));
ImGui.Dummy(new Vector2(5));
@@ -969,6 +1070,12 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
_draftMessages.Remove(key);
}
}
if (_refocusChatInputKey is not null && !existingKeys.Contains(_refocusChatInputKey))
{
_refocusChatInputKey = null;
_refocusChatInput = false;
}
}
private void DrawConnectionControls()
@@ -1165,6 +1272,71 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
ImGui.SetTooltip("Toggles the timestamp prefix on messages.");
}
ImGui.Separator();
ImGui.TextUnformatted("Chat Visibility");
var autoHideCombat = chatConfig.HideInCombat;
if (ImGui.Checkbox("Hide in combat", ref autoHideCombat))
{
chatConfig.HideInCombat = autoHideCombat;
_chatConfigService.Save();
UpdateHideState();
}
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip("Temporarily hides the chat window while in combat.");
}
var autoHideDuty = chatConfig.HideInDuty;
if (ImGui.Checkbox("Hide in duty (Not in field operations)", ref autoHideDuty))
{
chatConfig.HideInDuty = autoHideDuty;
_chatConfigService.Save();
UpdateHideState();
}
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip("Hides the chat window inside duties.");
}
var showWhenUiHidden = chatConfig.ShowWhenUiHidden;
if (ImGui.Checkbox("Show when game UI is hidden", ref showWhenUiHidden))
{
chatConfig.ShowWhenUiHidden = showWhenUiHidden;
_chatConfigService.Save();
UpdateHideState();
}
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip("Allow the chat window to remain visible when the game UI is hidden.");
}
var showInCutscenes = chatConfig.ShowInCutscenes;
if (ImGui.Checkbox("Show in cutscenes", ref showInCutscenes))
{
chatConfig.ShowInCutscenes = showInCutscenes;
_chatConfigService.Save();
UpdateHideState();
}
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip("Allow the chat window to remain visible during cutscenes.");
}
var showInGpose = chatConfig.ShowInGpose;
if (ImGui.Checkbox("Show in group pose", ref showInGpose))
{
chatConfig.ShowInGpose = showInGpose;
_chatConfigService.Save();
UpdateHideState();
}
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip("Allow the chat window to remain visible in /gpose.");
}
ImGui.Separator();
var fontScale = Math.Clamp(chatConfig.ChatFontScale, MinChatFontScale, MaxChatFontScale);
var fontScaleChanged = ImGui.SliderFloat("Message font scale", ref fontScale, MinChatFontScale, MaxChatFontScale, "%.2fx");
var resetFontScale = ImGui.IsItemClicked(ImGuiMouseButton.Right);
@@ -1222,7 +1394,7 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
});
}
private void DrawChannelButtons(IReadOnlyList<ChatChannelSnapshot> channels)
private unsafe void DrawChannelButtons(IReadOnlyList<ChatChannelSnapshot> channels)
{
var style = ImGui.GetStyle();
var baseFramePadding = style.FramePadding;
@@ -1283,6 +1455,8 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
{
if (child)
{
var dragActive = _dragChannelKey is not null && ImGui.IsMouseDragging(ImGuiMouseButton.Left);
var hoveredTargetThisFrame = false;
var first = true;
foreach (var channel in channels)
{
@@ -1293,6 +1467,7 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
var showBadge = !isSelected && channel.UnreadCount > 0;
var isZoneChannel = channel.Type == ChatChannelType.Zone;
(string Text, Vector2 TextSize, float Width, float Height)? badgeMetrics = null;
var channelLabel = GetChannelTabLabel(channel);
var normal = isSelected ? UIColors.Get("LightlessPurpleDefault") : UIColors.Get("ButtonDefault");
var hovered = isSelected
@@ -1321,7 +1496,7 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
badgeMetrics = (badgeText, badgeTextSize, badgeWidth, badgeHeight);
}
var clicked = ImGui.Button($"{channel.DisplayName}##chat_channel_{channel.Key}");
var clicked = ImGui.Button($"{channelLabel}##chat_channel_{channel.Key}");
if (showBadge)
{
@@ -1337,10 +1512,77 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
_scrollToBottom = true;
}
if (ShouldShowChannelTabContextMenu(channel)
&& ImGui.BeginPopupContextItem($"chat_channel_ctx##{channel.Key}"))
{
DrawChannelTabContextMenu(channel);
ImGui.EndPopup();
}
if (ImGui.BeginDragDropSource(ImGuiDragDropFlags.None))
{
if (!string.Equals(_dragChannelKey, channel.Key, StringComparison.Ordinal))
{
_dragHoverKey = null;
}
_dragChannelKey = channel.Key;
ImGui.SetDragDropPayload(ChannelDragPayloadId, null, 0);
ImGui.TextUnformatted(channelLabel);
ImGui.EndDragDropSource();
}
var isDragTarget = false;
if (ImGui.BeginDragDropTarget())
{
var acceptFlags = ImGuiDragDropFlags.AcceptBeforeDelivery | ImGuiDragDropFlags.AcceptNoDrawDefaultRect;
var payload = ImGui.AcceptDragDropPayload(ChannelDragPayloadId, acceptFlags);
if (!payload.IsNull && _dragChannelKey is { } draggedKey
&& !string.Equals(draggedKey, channel.Key, StringComparison.Ordinal))
{
isDragTarget = true;
if (!string.Equals(_dragHoverKey, channel.Key, StringComparison.Ordinal))
{
_dragHoverKey = channel.Key;
_zoneChatService.MoveChannel(draggedKey, channel.Key);
}
}
ImGui.EndDragDropTarget();
}
var isHoveredDuringDrag = dragActive
&& ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenBlockedByActiveItem | ImGuiHoveredFlags.AllowWhenOverlapped);
if (!isDragTarget && isHoveredDuringDrag
&& !string.Equals(_dragChannelKey, channel.Key, StringComparison.Ordinal))
{
isDragTarget = true;
if (!string.Equals(_dragHoverKey, channel.Key, StringComparison.Ordinal))
{
_dragHoverKey = channel.Key;
_zoneChatService.MoveChannel(_dragChannelKey!, channel.Key);
}
}
var drawList = ImGui.GetWindowDrawList();
var itemMin = ImGui.GetItemRectMin();
var itemMax = ImGui.GetItemRectMax();
if (isHoveredDuringDrag)
{
var highlight = UIColors.Get("LightlessPurple").WithAlpha(0.35f);
var highlightU32 = ImGui.ColorConvertFloat4ToU32(highlight);
drawList.AddRectFilled(itemMin, itemMax, highlightU32, style.FrameRounding);
drawList.AddRect(itemMin, itemMax, highlightU32, style.FrameRounding, ImDrawFlags.None, Math.Max(1f, ImGuiHelpers.GlobalScale));
}
if (isDragTarget)
{
hoveredTargetThisFrame = true;
}
if (isZoneChannel)
{
var borderColor = UIColors.Get("LightlessOrange");
@@ -1368,6 +1610,11 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
first = false;
}
if (dragActive && !hoveredTargetThisFrame)
{
_dragHoverKey = null;
}
if (_pendingChannelScroll.HasValue)
{
ImGui.SetScrollX(_pendingChannelScroll.Value);
@@ -1408,9 +1655,123 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
_channelScroll = currentScroll;
_channelScrollMax = maxScroll;
if (_dragChannelKey is not null && !ImGui.IsMouseDown(ImGuiMouseButton.Left))
{
_dragChannelKey = null;
_dragHoverKey = null;
}
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - style.ItemSpacing.Y * 0.3f);
}
private string GetChannelTabLabel(ChatChannelSnapshot channel)
{
if (channel.Type != ChatChannelType.Group)
{
return channel.DisplayName;
}
if (!_chatConfigService.Current.PreferNotesForChannels.TryGetValue(channel.Key, out var preferNote) || !preferNote)
{
return channel.DisplayName;
}
var note = GetChannelNote(channel);
if (string.IsNullOrWhiteSpace(note))
{
return channel.DisplayName;
}
return TruncateChannelNoteForTab(note);
}
private static string TruncateChannelNoteForTab(string note)
{
if (note.Length <= MaxChannelNoteTabLength)
{
return note;
}
var ellipsis = "...";
var maxPrefix = Math.Max(0, MaxChannelNoteTabLength - ellipsis.Length);
return note[..maxPrefix] + ellipsis;
}
private bool ShouldShowChannelTabContextMenu(ChatChannelSnapshot channel)
{
if (channel.Type != ChatChannelType.Group)
{
return false;
}
if (_chatConfigService.Current.PreferNotesForChannels.TryGetValue(channel.Key, out var preferNote) && preferNote)
{
return true;
}
var note = GetChannelNote(channel);
return !string.IsNullOrWhiteSpace(note);
}
private void DrawChannelTabContextMenu(ChatChannelSnapshot channel)
{
var preferNote = _chatConfigService.Current.PreferNotesForChannels.TryGetValue(channel.Key, out var value) && value;
var note = GetChannelNote(channel);
var hasNote = !string.IsNullOrWhiteSpace(note);
if (preferNote || hasNote)
{
var label = preferNote ? "Prefer Name Instead" : "Prefer Note Instead";
if (ImGui.MenuItem(label))
{
SetPreferNoteForChannel(channel.Key, !preferNote);
}
}
if (preferNote)
{
ImGui.Separator();
ImGui.TextDisabled("Name:");
ImGui.TextWrapped(channel.DisplayName);
}
if (hasNote)
{
ImGui.Separator();
ImGui.TextDisabled("Note:");
ImGui.TextWrapped(note);
}
}
private string? GetChannelNote(ChatChannelSnapshot channel)
{
if (channel.Type != ChatChannelType.Group)
{
return null;
}
var gid = channel.Descriptor.CustomKey;
if (string.IsNullOrWhiteSpace(gid))
{
return null;
}
return _serverConfigurationManager.GetNoteForGid(gid);
}
private void SetPreferNoteForChannel(string channelKey, bool preferNote)
{
if (preferNote)
{
_chatConfigService.Current.PreferNotesForChannels[channelKey] = true;
}
else
{
_chatConfigService.Current.PreferNotesForChannels.Remove(channelKey);
}
_chatConfigService.Save();
}
private void DrawSystemEntry(ChatMessageEntry entry)
{
var system = entry.SystemMessage;

View File

@@ -559,17 +559,11 @@ public static class SeStringUtils
ImGui.Dummy(new Vector2(0f, textSize.Y));
}
public static Vector2 RenderSeStringWithHitbox(DalamudSeString seString, Vector2 position, ImFontPtr? font = null, string? id = null)
{
var drawList = ImGui.GetWindowDrawList();
var usedFont = font ?? UiBuilder.MonoFont;
var drawParams = new SeStringDrawParams
{
Font = usedFont,
Color = 0xFFFFFFFF,
WrapWidth = float.MaxValue,
TargetDrawList = drawList
};
var textSize = ImGui.CalcTextSize(seString.TextValue);
if (textSize.Y <= 0f)
@@ -584,11 +578,17 @@ public static class SeStringUtils
var verticalOffset = MathF.Max((hitboxHeight - textSize.Y) * 0.5f, 0f);
var drawPos = new Vector2(position.X, position.Y + verticalOffset);
ImGui.SetCursorScreenPos(drawPos);
var drawParams = new SeStringDrawParams
{
FontSize = usedFont.FontSize,
ScreenOffset = drawPos,
Font = usedFont,
Color = 0xFFFFFFFF,
WrapWidth = float.MaxValue,
TargetDrawList = drawList
};
drawParams.ScreenOffset = drawPos;
drawParams.Font = usedFont;
drawParams.FontSize = usedFont.FontSize;
ImGui.SetCursorScreenPos(drawPos);
ImGuiHelpers.SeStringWrapped(seString.Encode(), drawParams);
@@ -614,6 +614,64 @@ public static class SeStringUtils
return new Vector2(textSize.X, hitboxHeight);
}
public static Vector2 RenderSeStringWithHitbox(DalamudSeString seString, Vector2 position, float? targetFontSize, ImFontPtr? font = null, string? id = null)
{
var drawList = ImGui.GetWindowDrawList();
var usedFont = font ?? ImGui.GetFont();
ImGui.PushFont(usedFont);
Vector2 rawSize;
float usedEffectiveSize;
try
{
usedEffectiveSize = ImGui.GetFontSize();
rawSize = ImGui.CalcTextSize(seString.TextValue);
}
finally
{
ImGui.PopFont();
}
var desiredSize = targetFontSize ?? usedEffectiveSize;
var scale = usedEffectiveSize > 0 ? (desiredSize / usedEffectiveSize) : 1f;
var textSize = rawSize * scale;
var style = ImGui.GetStyle();
var frameHeight = desiredSize + style.FramePadding.Y * 2f;
var hitboxHeight = MathF.Max(frameHeight, textSize.Y);
var verticalOffset = MathF.Max((hitboxHeight - textSize.Y) * 0.5f, 0f);
var drawPos = new Vector2(position.X, position.Y + verticalOffset);
var drawParams = new SeStringDrawParams
{
TargetDrawList = drawList,
ScreenOffset = drawPos,
Font = usedFont,
FontSize = desiredSize,
Color = 0xFFFFFFFF,
WrapWidth = float.MaxValue,
};
ImGui.SetCursorScreenPos(drawPos);
ImGuiHelpers.SeStringWrapped(seString.Encode(), drawParams);
ImGui.SetCursorScreenPos(position);
ImGui.PushID(id ?? Interlocked.Increment(ref _seStringHitboxCounter).ToString());
try
{
ImGui.InvisibleButton("##hitbox", new Vector2(textSize.X, hitboxHeight));
}
finally
{
ImGui.PopID();
}
return new Vector2(textSize.X, hitboxHeight);
}
public static Vector2 RenderIconWithHitbox(int iconId, Vector2 position, ImFontPtr? font = null, string? id = null)
{
var drawList = ImGui.GetWindowDrawList();

View File

@@ -584,7 +584,10 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
OnGroupSendInfo((dto) => _ = Client_GroupSendInfo(dto));
OnGroupUpdateProfile((dto) => _ = Client_GroupSendProfile(dto));
OnGroupChangeUserPairPermissions((dto) => _ = Client_GroupChangeUserPairPermissions(dto));
_lightlessHub.On(nameof(Client_ChatReceive), (Func<ChatMessageDto, Task>)Client_ChatReceive);
if (!_initialized)
{
_lightlessHub.On(nameof(Client_ChatReceive), (Func<ChatMessageDto, Task>)Client_ChatReceive);
}
OnGposeLobbyJoin((dto) => _ = Client_GposeLobbyJoin(dto));
OnGposeLobbyLeave((dto) => _ = Client_GposeLobbyLeave(dto));

View File

@@ -10,9 +10,9 @@
},
"Brio.API": {
"type": "Direct",
"requested": "[3.0.0, )",
"resolved": "3.0.0",
"contentHash": "0g7BTpSj/Nwfnpkz3R2FCzDIauhUdCb5zEt9cBWB0xrDrhugvUW7/irRyB48gyHDaK4Cv13al2IGrfW7l/jBUg=="
"requested": "[3.0.1, )",
"resolved": "3.0.1",
"contentHash": "40MD49ETqyGsdHGoG3JF/BFcNAphRqi27+ZxfDk2Aj7gAkzDFe7C2UVGirUByrUIj8lxiz9eEoB2i7O9lefEPQ=="
},
"DalamudPackager": {
"type": "Direct",
@@ -52,9 +52,9 @@
},
"Meziantou.Analyzer": {
"type": "Direct",
"requested": "[2.0.212, )",
"resolved": "2.0.212",
"contentHash": "U91ktjjTRTccUs3Lk+hrLD9vW+2+lhnsOf4G1GpRSJi1pLn3uK5CU6wGP9Bmz1KlJs6Oz1GGoMhxQBoqQsmAuQ=="
"requested": "[2.0.264, )",
"resolved": "2.0.264",
"contentHash": "zRG13RDG446rZNdd/YjKRd4utpbjleRDUqNQSrX0etMnH8Rz9NBlXUpS5aR2ExoOokhNfkdOW8HpLzjLj5x0hQ=="
},
"Microsoft.AspNetCore.SignalR.Client": {
"type": "Direct",
@@ -108,35 +108,35 @@
},
"NReco.Logging.File": {
"type": "Direct",
"requested": "[1.2.2, )",
"resolved": "1.2.2",
"contentHash": "UyUIkyDiHi2HAJlmEWqeKN9/FxTF0DPNdyatzMDMTXvUpgvqBFneJ2qDtZkXRJNG8eR6jU+KsbGeMmChgUdRUg==",
"requested": "[1.3.1, )",
"resolved": "1.3.1",
"contentHash": "4aFUEW1OFJsuKtg46dnqxZUyb37f9dzaWOXjUv2x/wzoHKovR9yqiMzXtCZt3+a9G78YCIAtSEz2g/GaNYbxSQ==",
"dependencies": {
"Microsoft.Extensions.Logging": "8.0.1",
"Microsoft.Extensions.Logging.Configuration": "8.0.1",
"Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0"
"Microsoft.Extensions.Logging": "10.0.0",
"Microsoft.Extensions.Logging.Configuration": "10.0.0",
"Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.0"
}
},
"SixLabors.ImageSharp": {
"type": "Direct",
"requested": "[3.1.11, )",
"resolved": "3.1.11",
"contentHash": "JfPLyigLthuE50yi6tMt7Amrenr/fA31t2CvJyhy/kQmfulIBAqo5T/YFUSRHtuYPXRSaUHygFeh6Qd933EoSw=="
"requested": "[3.1.12, )",
"resolved": "3.1.12",
"contentHash": "iAg6zifihXEFS/t7fiHhZBGAdCp3FavsF4i2ZIDp0JfeYeDVzvmlbY1CNhhIKimaIzrzSi5M/NBFcWvZT2rB/A=="
},
"SonarAnalyzer.CSharp": {
"type": "Direct",
"requested": "[10.7.0.110445, )",
"resolved": "10.7.0.110445",
"contentHash": "U4v2LWopxADYkUv7Z5CX7ifKMdDVqHb7a1bzppIQnQi4WQR6z1Zi5rDkCHlVYGEd1U/WMz1IJCU8OmFZLJpVig=="
"requested": "[10.17.0.131074, )",
"resolved": "10.17.0.131074",
"contentHash": "N8agHzX1pK3Xv/fqMig/mHspPAmh/aKkGg7lUC1xfezAhFtPTuRqBjuyas622Tvy5jnsN5zCXJVclvNkfJJ4rQ=="
},
"System.IdentityModel.Tokens.Jwt": {
"type": "Direct",
"requested": "[8.7.0, )",
"resolved": "8.7.0",
"contentHash": "8dKL3A9pVqYCJIXHd4H2epQqLxSvKeNxGonR0e5g89yMchyvsM/NLuB06otx29BicUd6+LUJZgNZmvYjjPsPGg==",
"requested": "[8.15.0, )",
"resolved": "8.15.0",
"contentHash": "dpodi7ixz6hxK8YCBYAWzm0IA8JYXoKcz0hbCbNifo519//rjUI0fBD8rfNr+IGqq+2gm4oQoXwHk09LX5SqqQ==",
"dependencies": {
"Microsoft.IdentityModel.JsonWebTokens": "8.7.0",
"Microsoft.IdentityModel.Tokens": "8.7.0"
"Microsoft.IdentityModel.JsonWebTokens": "8.15.0",
"Microsoft.IdentityModel.Tokens": "8.15.0"
}
},
"YamlDotNet": {
@@ -490,32 +490,32 @@
},
"Microsoft.IdentityModel.Abstractions": {
"type": "Transitive",
"resolved": "8.7.0",
"contentHash": "OQd5aVepYvh5evOmBMeAYjMIpEcTf1ZCBZaU7Nh/RlhhdXefjFDJeP1L2F2zeNT1unFr+wUu/h3Ac2Xb4BXU6w=="
"resolved": "8.15.0",
"contentHash": "e/DApa1GfxUqHSBHcpiQg8yaghKAvFVBQFcWh25jNoRobDZbduTUACY8bZ54eeGWXvimGmEDdF0zkS5Dq16XPQ=="
},
"Microsoft.IdentityModel.JsonWebTokens": {
"type": "Transitive",
"resolved": "8.7.0",
"contentHash": "uzsSAWhNhbrkWbQKBTE8QhzviU6sr3bJ1Bkv7gERlhswfSKOp7HsxTRLTPBpx/whQ/GRRHEwMg8leRIPbMrOgw==",
"resolved": "8.15.0",
"contentHash": "3513f5VzvOZy3ELd42wGnh1Q3e83tlGAuXFSNbENpgWYoAhLLzgFtd5PiaOPGAU0gqKhYGVzKavghLUGfX3HQg==",
"dependencies": {
"Microsoft.IdentityModel.Tokens": "8.7.0"
"Microsoft.IdentityModel.Tokens": "8.15.0"
}
},
"Microsoft.IdentityModel.Logging": {
"type": "Transitive",
"resolved": "8.7.0",
"contentHash": "Bs0TznPAu+nxa9rAVHJ+j3CYECHJkT3tG8AyBfhFYlT5ldsDhoxFT7J+PKxJHLf+ayqWfvDZHHc4639W2FQCxA==",
"resolved": "8.15.0",
"contentHash": "1gJLjhy0LV2RQMJ9NGzi5Tnb2l+c37o8D8Lrk2mrvmb6OQHZ7XJstd/XxvncXgBpad4x9CGXdipbZzJJCXKyAg==",
"dependencies": {
"Microsoft.IdentityModel.Abstractions": "8.7.0"
"Microsoft.IdentityModel.Abstractions": "8.15.0"
}
},
"Microsoft.IdentityModel.Tokens": {
"type": "Transitive",
"resolved": "8.7.0",
"contentHash": "5Z6voXjRXAnGklhmZd1mKz89UhcF5ZQQZaZc2iKrOuL4Li1UihG2vlJx8IbiFAOIxy/xdbsAm0A+WZEaH5fxng==",
"resolved": "8.15.0",
"contentHash": "zUE9ysJXBtXlHHRtcRK3Sp8NzdCI1z/BRDTXJQ2TvBoI0ENRtnufYIep0O5TSCJRJGDwwuLTUx+l/bEYZUxpCA==",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "8.0.2",
"Microsoft.IdentityModel.Logging": "8.7.0"
"Microsoft.Extensions.Logging.Abstractions": "10.0.0",
"Microsoft.IdentityModel.Logging": "8.15.0"
}
},
"Microsoft.NET.StringTools": {
@@ -619,7 +619,7 @@
"FlatSharp.Runtime": "[7.9.0, )",
"OtterGui": "[1.0.0, )",
"Penumbra.Api": "[5.13.0, )",
"Penumbra.String": "[1.0.6, )"
"Penumbra.String": "[1.0.7, )"
}
},
"penumbra.string": {