2.0.0 #92

Merged
defnotken merged 171 commits from 2.0.0 into master 2025-12-21 17:19:36 +00:00
7 changed files with 493 additions and 22 deletions
Showing only changes of commit ab369d008e - Show all commits

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
namespace LightlessSync.LightlessConfiguration.Configurations; namespace LightlessSync.LightlessConfiguration.Configurations;
@@ -13,4 +14,11 @@ public sealed class ChatConfig : ILightlessConfiguration
public bool IsWindowPinned { get; set; } = false; public bool IsWindowPinned { get; set; } = false;
public bool AutoOpenChatOnPluginLoad { get; set; } = false; public bool AutoOpenChatOnPluginLoad { get; set; } = false;
public float ChatFontScale { get; set; } = 1.0f; 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

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

View File

@@ -23,6 +23,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
private readonly DalamudUtilService _dalamudUtilService; private readonly DalamudUtilService _dalamudUtilService;
private readonly ActorObjectService _actorObjectService; private readonly ActorObjectService _actorObjectService;
private readonly PairUiService _pairUiService; private readonly PairUiService _pairUiService;
private readonly ChatConfigService _chatConfigService;
private readonly Lock _sync = new(); private readonly Lock _sync = new();
@@ -57,6 +58,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
_dalamudUtilService = dalamudUtilService; _dalamudUtilService = dalamudUtilService;
_actorObjectService = actorObjectService; _actorObjectService = actorObjectService;
_pairUiService = pairUiService; _pairUiService = pairUiService;
_chatConfigService = chatConfigService;
_isLoggedIn = _dalamudUtilService.IsLoggedIn; _isLoggedIn = _dalamudUtilService.IsLoggedIn;
_isConnected = _apiController.IsConnected; _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) public Task SetChatEnabledAsync(bool enabled)
=> enabled ? EnableChatAsync() : DisableChatAsync(); => enabled ? EnableChatAsync() : DisableChatAsync();
@@ -512,7 +550,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
if (!_isLoggedIn || !_apiController.IsConnected) if (!_isLoggedIn || !_apiController.IsConnected)
{ {
await LeaveCurrentZoneAsync(force, 0).ConfigureAwait(false); await LeaveCurrentZoneAsync(force, 0, 0).ConfigureAwait(false);
return; return;
} }
@@ -520,6 +558,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
{ {
var location = await _dalamudUtilService.GetMapDataAsync().ConfigureAwait(false); var location = await _dalamudUtilService.GetMapDataAsync().ConfigureAwait(false);
var territoryId = (ushort)location.TerritoryId; var territoryId = (ushort)location.TerritoryId;
var worldId = (ushort)location.ServerId;
string? zoneKey; string? zoneKey;
ZoneChannelDefinition? definition = null; ZoneChannelDefinition? definition = null;
@@ -536,14 +575,14 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
if (definition is null) if (definition is null)
{ {
await LeaveCurrentZoneAsync(force, territoryId).ConfigureAwait(false); await LeaveCurrentZoneAsync(force, territoryId, worldId).ConfigureAwait(false);
return; return;
} }
var descriptor = await BuildZoneDescriptorAsync(definition.Value).ConfigureAwait(false); var descriptor = await BuildZoneDescriptorAsync(definition.Value).ConfigureAwait(false);
if (descriptor is null) if (descriptor is null)
{ {
await LeaveCurrentZoneAsync(force, territoryId).ConfigureAwait(false); await LeaveCurrentZoneAsync(force, territoryId, worldId).ConfigureAwait(false);
return; 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; ChatChannelDescriptor? descriptor = null;
@@ -602,7 +641,27 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
state.StatusText = !_chatEnabled state.StatusText = !_chatEnabled
? "Chat services disabled" ? "Chat services disabled"
: (_isConnected ? ZoneUnavailableMessage : "Disconnected from chat server"); : (_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)) if (string.Equals(_activeChannelKey, ZoneChannelKey, StringComparison.Ordinal))
@@ -1092,17 +1151,50 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
{ {
_channelOrder.Clear(); _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 var groups = _channels.Values
.Where(state => state.Type == ChatChannelType.Group) .Where(state => state.Type == ChatChannelType.Group)
.OrderBy(state => state.DisplayName, StringComparer.OrdinalIgnoreCase) .OrderBy(state => state.DisplayName, StringComparer.OrdinalIgnoreCase)
.Select(state => state.Key); .Select(state => state.Key);
_channelOrder.AddRange(groups); _channelOrder.AddRange(groups);
}
if (_activeChannelKey is null && _channelOrder.Count > 0) if (_activeChannelKey is null && _channelOrder.Count > 0)
{ {

View File

@@ -239,6 +239,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
public bool IsInCombat { get; private set; } = false; public bool IsInCombat { get; private set; } = false;
public bool IsPerforming { get; private set; } = false; public bool IsPerforming { get; private set; } = false;
public bool IsInInstance { get; private set; } = false; public bool IsInInstance { get; private set; } = false;
public bool IsInDuty => _condition[ConditionFlag.BoundByDuty];
public bool HasModifiedGameFiles => _gameData.HasModifiedGameDataFiles; public bool HasModifiedGameFiles => _gameData.HasModifiedGameDataFiles;
public uint ClassJobId => _classJobId!.Value; public uint ClassJobId => _classJobId!.Value;
public Lazy<Dictionary<uint, string>> JobData { get; private set; } 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 bool IsLodEnabled { get; private set; }
public LightlessMediator Mediator { get; } 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) public IGameObject? CreateGameObject(IntPtr reference)
{ {
EnsureIsOnFramework(); EnsureIsOnFramework();

View File

@@ -27,7 +27,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
{ {
private const float MinTextureFilterPaneWidth = 305f; private const float MinTextureFilterPaneWidth = 305f;
private const float MaxTextureFilterPaneWidth = 405f; private const float MaxTextureFilterPaneWidth = 405f;
private const float MinTextureDetailPaneWidth = 580f; private const float MinTextureDetailPaneWidth = 480f;
private const float MaxTextureDetailPaneWidth = 720f; private const float MaxTextureDetailPaneWidth = 720f;
private const float SelectedFilePanelLogicalHeight = 90f; private const float SelectedFilePanelLogicalHeight = 90f;
private static readonly Vector4 SelectedTextureRowTextColor = new(0f, 0f, 0f, 1f); private static readonly Vector4 SelectedTextureRowTextColor = new(0f, 0f, 0f, 1f);
@@ -111,7 +111,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
_hasUpdate = true; _hasUpdate = true;
}); });
WindowBuilder.For(this) WindowBuilder.For(this)
.SetSizeConstraints(new Vector2(1650, 1000), new Vector2(3840, 2160)) .SetSizeConstraints(new Vector2(1240, 680), new Vector2(3840, 2160))
.Apply(); .Apply();
_conversionProgress.ProgressChanged += ConversionProgress_ProgressChanged; _conversionProgress.ProgressChanged += ConversionProgress_ProgressChanged;

View File

@@ -12,6 +12,7 @@ using LightlessSync.LightlessConfiguration.Models;
using LightlessSync.Services; using LightlessSync.Services;
using LightlessSync.Services.Chat; using LightlessSync.Services.Chat;
using LightlessSync.Services.Mediator; using LightlessSync.Services.Mediator;
using LightlessSync.Services.ServerConfiguration;
using LightlessSync.UI.Services; using LightlessSync.UI.Services;
using LightlessSync.Utils; using LightlessSync.Utils;
using LightlessSync.WebAPI; using LightlessSync.WebAPI;
@@ -23,8 +24,10 @@ namespace LightlessSync.UI;
public sealed class ZoneChatUi : WindowMediatorSubscriberBase public sealed class ZoneChatUi : WindowMediatorSubscriberBase
{ {
private const string ChatDisabledStatus = "Chat services disabled"; 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 SettingsPopupId = "zone_chat_settings_popup";
private const string ReportPopupId = "Report Message##zone_chat_report_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 DefaultWindowOpacity = .97f;
private const float MinWindowOpacity = 0.05f; private const float MinWindowOpacity = 0.05f;
private const float MaxWindowOpacity = 1f; private const float MaxWindowOpacity = 1f;
@@ -32,6 +35,7 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
private const float MaxChatFontScale = 1.5f; private const float MaxChatFontScale = 1.5f;
private const int ReportReasonMaxLength = 500; private const int ReportReasonMaxLength = 500;
private const int ReportContextMaxLength = 1000; private const int ReportContextMaxLength = 1000;
private const int MaxChannelNoteTabLength = 25;
private readonly UiSharedService _uiSharedService; private readonly UiSharedService _uiSharedService;
private readonly ZoneChatService _zoneChatService; private readonly ZoneChatService _zoneChatService;
@@ -39,6 +43,9 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
private readonly LightlessProfileManager _profileManager; private readonly LightlessProfileManager _profileManager;
private readonly ApiController _apiController; private readonly ApiController _apiController;
private readonly ChatConfigService _chatConfigService; 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 Dictionary<string, string> _draftMessages = new(StringComparer.Ordinal);
private readonly ImGuiWindowFlags _unpinnedWindowFlags; private readonly ImGuiWindowFlags _unpinnedWindowFlags;
private float _currentWindowOpacity = DefaultWindowOpacity; private float _currentWindowOpacity = DefaultWindowOpacity;
@@ -61,6 +68,10 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
private bool _reportSubmitting; private bool _reportSubmitting;
private string? _reportError; private string? _reportError;
private ChatReportResult? _reportSubmissionResult; private ChatReportResult? _reportSubmissionResult;
private string? _dragChannelKey;
private string? _dragHoverKey;
private bool _HideStateActive;
private bool _HideStateWasOpen;
public ZoneChatUi( public ZoneChatUi(
ILogger<ZoneChatUi> logger, ILogger<ZoneChatUi> logger,
@@ -70,6 +81,9 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
PairUiService pairUiService, PairUiService pairUiService,
LightlessProfileManager profileManager, LightlessProfileManager profileManager,
ChatConfigService chatConfigService, ChatConfigService chatConfigService,
ServerConfigurationManager serverConfigurationManager,
DalamudUtilService dalamudUtilService,
IUiBuilder uiBuilder,
ApiController apiController, ApiController apiController,
PerformanceCollectorService performanceCollectorService) PerformanceCollectorService performanceCollectorService)
: base(logger, mediator, "Lightless Chat", performanceCollectorService) : base(logger, mediator, "Lightless Chat", performanceCollectorService)
@@ -79,6 +93,9 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
_pairUiService = pairUiService; _pairUiService = pairUiService;
_profileManager = profileManager; _profileManager = profileManager;
_chatConfigService = chatConfigService; _chatConfigService = chatConfigService;
_serverConfigurationManager = serverConfigurationManager;
_dalamudUtilService = dalamudUtilService;
_uiBuilder = uiBuilder;
_apiController = apiController; _apiController = apiController;
_isWindowPinned = _chatConfigService.Current.IsWindowPinned; _isWindowPinned = _chatConfigService.Current.IsWindowPinned;
_showRulesOverlay = _chatConfigService.Current.ShowRulesOverlayOnOpen; _showRulesOverlay = _chatConfigService.Current.ShowRulesOverlayOnOpen;
@@ -88,6 +105,7 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
} }
_unpinnedWindowFlags = Flags; _unpinnedWindowFlags = Flags;
RefreshWindowFlags(); RefreshWindowFlags();
ApplyUiVisibilitySettings();
Size = new Vector2(450, 420) * ImGuiHelpers.GlobalScale; Size = new Vector2(450, 420) * ImGuiHelpers.GlobalScale;
SizeCondition = ImGuiCond.FirstUseEver; SizeCondition = ImGuiCond.FirstUseEver;
WindowBuilder.For(this) WindowBuilder.For(this)
@@ -98,6 +116,8 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
Mediator.Subscribe<ChatChannelMessageAdded>(this, OnChatChannelMessageAdded); Mediator.Subscribe<ChatChannelMessageAdded>(this, OnChatChannelMessageAdded);
Mediator.Subscribe<ChatChannelsUpdated>(this, _ => _scrollToBottom = true); Mediator.Subscribe<ChatChannelsUpdated>(this, _ => _scrollToBottom = true);
Mediator.Subscribe<PriorityFrameworkUpdateMessage>(this, _ => UpdateHideState());
Mediator.Subscribe<CutsceneFrameworkUpdateMessage>(this, _ => UpdateHideState());
} }
public override void PreDraw() public override void PreDraw()
@@ -108,6 +128,55 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
ImGui.SetNextWindowBgAlpha(_currentWindowOpacity); 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() protected override void DrawInternal()
{ {
var childBgColor = ImGui.GetStyle().Colors[(int)ImGuiCol.ChildBg]; var childBgColor = ImGui.GetStyle().Colors[(int)ImGuiCol.ChildBg];
@@ -155,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"; var prefix = channel.Type == ChatChannelType.Zone ? "Zone" : "Syncshell";
Vector4 color; Vector4 color;
@@ -178,11 +247,18 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
if (channel.Type == ChatChannelType.Zone && channel.Descriptor.WorldId != 0) if (channel.Type == ChatChannelType.Zone && channel.Descriptor.WorldId != 0)
{ {
ImGui.SameLine(); 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); var showInlineStatus = string.Equals(channel.StatusText, ChatDisabledStatus, StringComparison.OrdinalIgnoreCase)
if (showInlineDisabled) || string.Equals(channel.StatusText, ZoneUnavailableStatus, StringComparison.OrdinalIgnoreCase);
if (showInlineStatus)
{ {
ImGui.SameLine(); ImGui.SameLine();
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey3); ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey3);
@@ -324,6 +400,15 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
_refocusChatInputKey = null; _refocusChatInputKey = null;
} }
ImGui.InputText(inputId, ref draft, MaxMessageLength); 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() var enterPressed = ImGui.IsItemFocused()
&& (ImGui.IsKeyPressed(ImGuiKey.Enter) || ImGui.IsKeyPressed(ImGuiKey.KeypadEnter)); && (ImGui.IsKeyPressed(ImGuiKey.Enter) || ImGui.IsKeyPressed(ImGuiKey.KeypadEnter));
_draftMessages[channel.Key] = draft; _draftMessages[channel.Key] = draft;
@@ -480,7 +565,7 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
ImGui.Separator(); ImGui.Separator();
_uiSharedService.MediumText("Syncshell Chat Rules", UIColors.Get("LightlessYellow")); _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)); ImGui.Dummy(new Vector2(5));
@@ -1187,6 +1272,71 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
ImGui.SetTooltip("Toggles the timestamp prefix on messages."); 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 fontScale = Math.Clamp(chatConfig.ChatFontScale, MinChatFontScale, MaxChatFontScale);
var fontScaleChanged = ImGui.SliderFloat("Message font scale", ref fontScale, MinChatFontScale, MaxChatFontScale, "%.2fx"); var fontScaleChanged = ImGui.SliderFloat("Message font scale", ref fontScale, MinChatFontScale, MaxChatFontScale, "%.2fx");
var resetFontScale = ImGui.IsItemClicked(ImGuiMouseButton.Right); var resetFontScale = ImGui.IsItemClicked(ImGuiMouseButton.Right);
@@ -1244,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 style = ImGui.GetStyle();
var baseFramePadding = style.FramePadding; var baseFramePadding = style.FramePadding;
@@ -1305,6 +1455,8 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
{ {
if (child) if (child)
{ {
var dragActive = _dragChannelKey is not null && ImGui.IsMouseDragging(ImGuiMouseButton.Left);
var hoveredTargetThisFrame = false;
var first = true; var first = true;
foreach (var channel in channels) foreach (var channel in channels)
{ {
@@ -1315,6 +1467,7 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
var showBadge = !isSelected && channel.UnreadCount > 0; var showBadge = !isSelected && channel.UnreadCount > 0;
var isZoneChannel = channel.Type == ChatChannelType.Zone; var isZoneChannel = channel.Type == ChatChannelType.Zone;
(string Text, Vector2 TextSize, float Width, float Height)? badgeMetrics = null; (string Text, Vector2 TextSize, float Width, float Height)? badgeMetrics = null;
var channelLabel = GetChannelTabLabel(channel);
var normal = isSelected ? UIColors.Get("LightlessPurpleDefault") : UIColors.Get("ButtonDefault"); var normal = isSelected ? UIColors.Get("LightlessPurpleDefault") : UIColors.Get("ButtonDefault");
var hovered = isSelected var hovered = isSelected
@@ -1343,7 +1496,7 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
badgeMetrics = (badgeText, badgeTextSize, badgeWidth, badgeHeight); 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) if (showBadge)
{ {
@@ -1359,10 +1512,77 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
_scrollToBottom = true; _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 drawList = ImGui.GetWindowDrawList();
var itemMin = ImGui.GetItemRectMin(); var itemMin = ImGui.GetItemRectMin();
var itemMax = ImGui.GetItemRectMax(); 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) if (isZoneChannel)
{ {
var borderColor = UIColors.Get("LightlessOrange"); var borderColor = UIColors.Get("LightlessOrange");
@@ -1390,6 +1610,11 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
first = false; first = false;
} }
if (dragActive && !hoveredTargetThisFrame)
{
_dragHoverKey = null;
}
if (_pendingChannelScroll.HasValue) if (_pendingChannelScroll.HasValue)
{ {
ImGui.SetScrollX(_pendingChannelScroll.Value); ImGui.SetScrollX(_pendingChannelScroll.Value);
@@ -1430,9 +1655,123 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
_channelScroll = currentScroll; _channelScroll = currentScroll;
_channelScrollMax = maxScroll; _channelScrollMax = maxScroll;
if (_dragChannelKey is not null && !ImGui.IsMouseDown(ImGuiMouseButton.Left))
{
_dragChannelKey = null;
_dragHoverKey = null;
}
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - style.ItemSpacing.Y * 0.3f); 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) private void DrawSystemEntry(ChatMessageEntry entry)
{ {
var system = entry.SystemMessage; var system = entry.SystemMessage;

View File

@@ -584,7 +584,10 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
OnGroupSendInfo((dto) => _ = Client_GroupSendInfo(dto)); OnGroupSendInfo((dto) => _ = Client_GroupSendInfo(dto));
OnGroupUpdateProfile((dto) => _ = Client_GroupSendProfile(dto)); OnGroupUpdateProfile((dto) => _ = Client_GroupSendProfile(dto));
OnGroupChangeUserPairPermissions((dto) => _ = Client_GroupChangeUserPairPermissions(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)); OnGposeLobbyJoin((dto) => _ = Client_GposeLobbyJoin(dto));
OnGposeLobbyLeave((dto) => _ = Client_GposeLobbyLeave(dto)); OnGposeLobbyLeave((dto) => _ = Client_GposeLobbyLeave(dto));