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
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -27,7 +27,7 @@ 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 SelectedFilePanelLogicalHeight = 90f;
|
||||
private static readonly Vector4 SelectedTextureRowTextColor = new(0f, 0f, 0f, 1f);
|
||||
@@ -111,7 +111,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;
|
||||
|
||||
@@ -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,6 +43,9 @@ 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;
|
||||
@@ -61,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,
|
||||
@@ -70,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)
|
||||
@@ -79,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;
|
||||
@@ -88,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)
|
||||
@@ -98,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()
|
||||
@@ -108,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];
|
||||
@@ -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";
|
||||
Vector4 color;
|
||||
@@ -178,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);
|
||||
@@ -324,6 +400,15 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
||||
_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;
|
||||
@@ -480,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));
|
||||
|
||||
@@ -1187,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);
|
||||
@@ -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 baseFramePadding = style.FramePadding;
|
||||
@@ -1305,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)
|
||||
{
|
||||
@@ -1315,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
|
||||
@@ -1343,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)
|
||||
{
|
||||
@@ -1359,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");
|
||||
@@ -1390,6 +1610,11 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
||||
first = false;
|
||||
}
|
||||
|
||||
if (dragActive && !hoveredTargetThisFrame)
|
||||
{
|
||||
_dragHoverKey = null;
|
||||
}
|
||||
|
||||
if (_pendingChannelScroll.HasValue)
|
||||
{
|
||||
ImGui.SetScrollX(_pendingChannelScroll.Value);
|
||||
@@ -1430,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;
|
||||
|
||||
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user