2.0.0 #92
@@ -11,6 +11,8 @@ public sealed class ChatConfig : ILightlessConfiguration
|
||||
public bool ShowRulesOverlayOnOpen { get; set; } = true;
|
||||
public bool ShowMessageTimestamps { get; set; } = true;
|
||||
public float ChatWindowOpacity { get; set; } = .97f;
|
||||
public bool FadeWhenUnfocused { get; set; } = false;
|
||||
public float UnfocusedWindowOpacity { get; set; } = 0.6f;
|
||||
public bool IsWindowPinned { get; set; } = false;
|
||||
public bool AutoOpenChatOnPluginLoad { get; set; } = false;
|
||||
public float ChatFontScale { get; set; } = 1.0f;
|
||||
|
||||
@@ -11,9 +11,11 @@ using LightlessSync.LightlessConfiguration;
|
||||
using LightlessSync.LightlessConfiguration.Models;
|
||||
using LightlessSync.Services;
|
||||
using LightlessSync.Services.Chat;
|
||||
using LightlessSync.Services.LightFinder;
|
||||
using LightlessSync.Services.Mediator;
|
||||
using LightlessSync.Services.ServerConfiguration;
|
||||
using LightlessSync.UI.Services;
|
||||
using LightlessSync.UI.Style;
|
||||
using LightlessSync.Utils;
|
||||
using LightlessSync.WebAPI;
|
||||
using LightlessSync.WebAPI.SignalR.Utils;
|
||||
@@ -29,10 +31,13 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
||||
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 DefaultUnfocusedWindowOpacity = 0.6f;
|
||||
private const float MinWindowOpacity = 0.05f;
|
||||
private const float MaxWindowOpacity = 1f;
|
||||
private const float MinChatFontScale = 0.75f;
|
||||
private const float MaxChatFontScale = 1.5f;
|
||||
private const float UnfocusedFadeOutSpeed = 0.22f;
|
||||
private const float FocusFadeInSpeed = 2.0f;
|
||||
private const int ReportReasonMaxLength = 500;
|
||||
private const int ReportContextMaxLength = 1000;
|
||||
private const int MaxChannelNoteTabLength = 25;
|
||||
@@ -40,6 +45,7 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
||||
private readonly UiSharedService _uiSharedService;
|
||||
private readonly ZoneChatService _zoneChatService;
|
||||
private readonly PairUiService _pairUiService;
|
||||
private readonly LightFinderService _lightFinderService;
|
||||
private readonly LightlessProfileManager _profileManager;
|
||||
private readonly ApiController _apiController;
|
||||
private readonly ChatConfigService _chatConfigService;
|
||||
@@ -49,16 +55,20 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
||||
private readonly Dictionary<string, string> _draftMessages = new(StringComparer.Ordinal);
|
||||
private readonly ImGuiWindowFlags _unpinnedWindowFlags;
|
||||
private float _currentWindowOpacity = DefaultWindowOpacity;
|
||||
private float _baseWindowOpacity = DefaultWindowOpacity;
|
||||
private bool _isWindowPinned;
|
||||
private bool _showRulesOverlay;
|
||||
private bool _refocusChatInput;
|
||||
private string? _refocusChatInputKey;
|
||||
private bool _isWindowFocused = true;
|
||||
private int _titleBarStylePopCount;
|
||||
|
||||
private string? _selectedChannelKey;
|
||||
private bool _scrollToBottom = true;
|
||||
private float? _pendingChannelScroll;
|
||||
private float _channelScroll;
|
||||
private float _channelScrollMax;
|
||||
private readonly SeluneBrush _seluneBrush = new();
|
||||
private ChatChannelSnapshot? _reportTargetChannel;
|
||||
private ChatMessageEntry? _reportTargetMessage;
|
||||
private string _reportReason = string.Empty;
|
||||
@@ -79,6 +89,7 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
||||
UiSharedService uiSharedService,
|
||||
ZoneChatService zoneChatService,
|
||||
PairUiService pairUiService,
|
||||
LightFinderService lightFinderService,
|
||||
LightlessProfileManager profileManager,
|
||||
ChatConfigService chatConfigService,
|
||||
ServerConfigurationManager serverConfigurationManager,
|
||||
@@ -91,6 +102,7 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
||||
_uiSharedService = uiSharedService;
|
||||
_zoneChatService = zoneChatService;
|
||||
_pairUiService = pairUiService;
|
||||
_lightFinderService = lightFinderService;
|
||||
_profileManager = profileManager;
|
||||
_chatConfigService = chatConfigService;
|
||||
_serverConfigurationManager = serverConfigurationManager;
|
||||
@@ -124,8 +136,25 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
||||
{
|
||||
RefreshWindowFlags();
|
||||
base.PreDraw();
|
||||
_currentWindowOpacity = Math.Clamp(_chatConfigService.Current.ChatWindowOpacity, MinWindowOpacity, MaxWindowOpacity);
|
||||
var config = _chatConfigService.Current;
|
||||
var baseOpacity = Math.Clamp(config.ChatWindowOpacity, MinWindowOpacity, MaxWindowOpacity);
|
||||
_baseWindowOpacity = baseOpacity;
|
||||
|
||||
if (config.FadeWhenUnfocused)
|
||||
{
|
||||
var unfocusedOpacity = Math.Clamp(config.UnfocusedWindowOpacity, MinWindowOpacity, MaxWindowOpacity);
|
||||
var targetOpacity = _isWindowFocused ? baseOpacity : Math.Min(baseOpacity, unfocusedOpacity);
|
||||
var delta = ImGui.GetIO().DeltaTime;
|
||||
var speed = _isWindowFocused ? FocusFadeInSpeed : UnfocusedFadeOutSpeed;
|
||||
_currentWindowOpacity = MoveTowards(_currentWindowOpacity, targetOpacity, speed * delta);
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentWindowOpacity = baseOpacity;
|
||||
}
|
||||
|
||||
ImGui.SetNextWindowBgAlpha(_currentWindowOpacity);
|
||||
PushTitleBarFadeColors(_currentWindowOpacity);
|
||||
}
|
||||
|
||||
private void UpdateHideState()
|
||||
@@ -179,8 +208,36 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
||||
|
||||
protected override void DrawInternal()
|
||||
{
|
||||
if (_titleBarStylePopCount > 0)
|
||||
{
|
||||
ImGui.PopStyleColor(_titleBarStylePopCount);
|
||||
_titleBarStylePopCount = 0;
|
||||
}
|
||||
|
||||
var config = _chatConfigService.Current;
|
||||
var isFocused = ImGui.IsWindowFocused(ImGuiFocusedFlags.RootAndChildWindows);
|
||||
var isHovered = ImGui.IsWindowHovered(ImGuiHoveredFlags.RootAndChildWindows);
|
||||
if (config.FadeWhenUnfocused && isHovered && !isFocused)
|
||||
{
|
||||
ImGui.SetWindowFocus();
|
||||
}
|
||||
|
||||
_isWindowFocused = config.FadeWhenUnfocused ? (isFocused || isHovered) : isFocused;
|
||||
|
||||
var contentAlpha = 1f;
|
||||
if (config.FadeWhenUnfocused)
|
||||
{
|
||||
var baseOpacity = MathF.Max(_baseWindowOpacity, 0.001f);
|
||||
contentAlpha = Math.Clamp(_currentWindowOpacity / baseOpacity, 0f, 1f);
|
||||
}
|
||||
|
||||
using var alpha = ImRaii.PushStyle(ImGuiStyleVar.Alpha, contentAlpha);
|
||||
var drawList = ImGui.GetWindowDrawList();
|
||||
var windowPos = ImGui.GetWindowPos();
|
||||
var windowSize = ImGui.GetWindowSize();
|
||||
using var selune = Selune.Begin(_seluneBrush, drawList, windowPos, windowSize);
|
||||
var childBgColor = ImGui.GetStyle().Colors[(int)ImGuiCol.ChildBg];
|
||||
childBgColor.W *= _currentWindowOpacity;
|
||||
childBgColor.W *= _baseWindowOpacity;
|
||||
using var childBg = ImRaii.PushColor(ImGuiCol.ChildBg, childBgColor);
|
||||
DrawConnectionControls();
|
||||
|
||||
@@ -192,36 +249,58 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey3);
|
||||
ImGui.TextWrapped("No chat channels available.");
|
||||
ImGui.PopStyleColor();
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureSelectedChannel(channels);
|
||||
CleanupDrafts(channels);
|
||||
|
||||
DrawChannelButtons(channels);
|
||||
|
||||
if (_selectedChannelKey is null)
|
||||
return;
|
||||
|
||||
var activeChannel = channels.FirstOrDefault(channel => string.Equals(channel.Key, _selectedChannelKey, StringComparison.Ordinal));
|
||||
if (activeChannel.Equals(default(ChatChannelSnapshot)))
|
||||
else
|
||||
{
|
||||
activeChannel = channels[0];
|
||||
_selectedChannelKey = activeChannel.Key;
|
||||
EnsureSelectedChannel(channels);
|
||||
CleanupDrafts(channels);
|
||||
|
||||
DrawChannelButtons(channels);
|
||||
|
||||
if (_selectedChannelKey is null)
|
||||
{
|
||||
selune.DrawHighlightOnly(ImGui.GetIO().DeltaTime);
|
||||
return;
|
||||
}
|
||||
|
||||
var activeChannel = channels.FirstOrDefault(channel => string.Equals(channel.Key, _selectedChannelKey, StringComparison.Ordinal));
|
||||
if (activeChannel.Equals(default(ChatChannelSnapshot)))
|
||||
{
|
||||
activeChannel = channels[0];
|
||||
_selectedChannelKey = activeChannel.Key;
|
||||
}
|
||||
|
||||
_zoneChatService.SetActiveChannel(activeChannel.Key);
|
||||
|
||||
DrawHeader(activeChannel);
|
||||
ImGui.Separator();
|
||||
DrawMessageArea(activeChannel, _currentWindowOpacity);
|
||||
ImGui.Separator();
|
||||
DrawInput(activeChannel);
|
||||
}
|
||||
|
||||
_zoneChatService.SetActiveChannel(activeChannel.Key);
|
||||
|
||||
DrawHeader(activeChannel);
|
||||
ImGui.Separator();
|
||||
DrawMessageArea(activeChannel, _currentWindowOpacity);
|
||||
ImGui.Separator();
|
||||
DrawInput(activeChannel);
|
||||
|
||||
if (_showRulesOverlay)
|
||||
{
|
||||
DrawRulesOverlay();
|
||||
}
|
||||
|
||||
selune.DrawHighlightOnly(ImGui.GetIO().DeltaTime);
|
||||
}
|
||||
|
||||
private void PushTitleBarFadeColors(float opacity)
|
||||
{
|
||||
_titleBarStylePopCount = 0;
|
||||
var alpha = Math.Clamp(opacity, 0f, 1f);
|
||||
var colors = ImGui.GetStyle().Colors;
|
||||
|
||||
var titleBg = colors[(int)ImGuiCol.TitleBg];
|
||||
var titleBgActive = colors[(int)ImGuiCol.TitleBgActive];
|
||||
var titleBgCollapsed = colors[(int)ImGuiCol.TitleBgCollapsed];
|
||||
|
||||
ImGui.PushStyleColor(ImGuiCol.TitleBg, new Vector4(titleBg.X, titleBg.Y, titleBg.Z, titleBg.W * alpha));
|
||||
ImGui.PushStyleColor(ImGuiCol.TitleBgActive, new Vector4(titleBgActive.X, titleBgActive.Y, titleBgActive.Z, titleBgActive.W * alpha));
|
||||
ImGui.PushStyleColor(ImGuiCol.TitleBgCollapsed, new Vector4(titleBgCollapsed.X, titleBgCollapsed.Y, titleBgCollapsed.Z, titleBgCollapsed.W * alpha));
|
||||
_titleBarStylePopCount = 3;
|
||||
}
|
||||
|
||||
private void DrawHeader(ChatChannelSnapshot channel)
|
||||
@@ -1119,18 +1198,56 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
||||
var groupSize = ImGui.GetItemRectSize();
|
||||
var minBlockX = cursorStart.X + groupSize.X + style.ItemSpacing.X;
|
||||
var availableAfterGroup = contentRightX - (cursorStart.X + groupSize.X);
|
||||
var lightfinderButtonWidth = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.PersonCirclePlus).X;
|
||||
var settingsButtonWidth = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Cog).X;
|
||||
var pinIcon = _isWindowPinned ? FontAwesomeIcon.Lock : FontAwesomeIcon.Unlock;
|
||||
var pinButtonWidth = _uiSharedService.GetIconButtonSize(pinIcon).X;
|
||||
var blockWidth = rulesButtonWidth + style.ItemSpacing.X + settingsButtonWidth + style.ItemSpacing.X + pinButtonWidth;
|
||||
var blockWidth = lightfinderButtonWidth + style.ItemSpacing.X + rulesButtonWidth + style.ItemSpacing.X + settingsButtonWidth + style.ItemSpacing.X + pinButtonWidth;
|
||||
var desiredBlockX = availableAfterGroup > blockWidth + style.ItemSpacing.X
|
||||
? contentRightX - blockWidth
|
||||
: minBlockX;
|
||||
desiredBlockX = Math.Max(cursorStart.X, desiredBlockX);
|
||||
var rulesPos = new Vector2(desiredBlockX, cursorStart.Y);
|
||||
var settingsPos = new Vector2(desiredBlockX + rulesButtonWidth + style.ItemSpacing.X, cursorStart.Y);
|
||||
var lightfinderPos = new Vector2(desiredBlockX, cursorStart.Y);
|
||||
var rulesPos = new Vector2(lightfinderPos.X + lightfinderButtonWidth + style.ItemSpacing.X, cursorStart.Y);
|
||||
var settingsPos = new Vector2(rulesPos.X + rulesButtonWidth + style.ItemSpacing.X, cursorStart.Y);
|
||||
var pinPos = new Vector2(settingsPos.X + settingsButtonWidth + style.ItemSpacing.X, cursorStart.Y);
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.SetCursorPos(lightfinderPos);
|
||||
var lightfinderEnabled = _lightFinderService.IsBroadcasting;
|
||||
var lightfinderColor = lightfinderEnabled ? UIColors.Get("LightlessGreen") : ImGuiColors.DalamudGrey3;
|
||||
var lightfinderButtonSize = new Vector2(lightfinderButtonWidth, ImGui.GetFrameHeight());
|
||||
ImGui.InvisibleButton("zone_chat_lightfinder_button", lightfinderButtonSize);
|
||||
var lightfinderMin = ImGui.GetItemRectMin();
|
||||
var lightfinderMax = ImGui.GetItemRectMax();
|
||||
var iconSize = _uiSharedService.GetIconSize(FontAwesomeIcon.PersonCirclePlus);
|
||||
var iconPos = new Vector2(
|
||||
lightfinderMin.X + (lightfinderButtonSize.X - iconSize.X) * 0.5f,
|
||||
lightfinderMin.Y + (lightfinderButtonSize.Y - iconSize.Y) * 0.5f);
|
||||
using (_uiSharedService.IconFont.Push())
|
||||
{
|
||||
ImGui.GetWindowDrawList().AddText(iconPos, ImGui.GetColorU32(lightfinderColor), FontAwesomeIcon.PersonCirclePlus.ToIconString());
|
||||
}
|
||||
|
||||
if (ImGui.IsItemClicked())
|
||||
{
|
||||
Mediator.Publish(new UiToggleMessage(typeof(LightFinderUI)));
|
||||
}
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
var padding = new Vector2(8f * ImGuiHelpers.GlobalScale);
|
||||
Selune.RegisterHighlight(
|
||||
lightfinderMin - padding,
|
||||
lightfinderMax + padding,
|
||||
SeluneHighlightMode.Point,
|
||||
exactSize: true,
|
||||
clipToElement: true,
|
||||
clipPadding: padding,
|
||||
highlightColorOverride: lightfinderColor,
|
||||
highlightAlphaOverride: 0.2f);
|
||||
ImGui.SetTooltip("If Lightfinder is enabled, you will be able to see the character names of other Lightfinder users in the same zone when they send a message.");
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.SetCursorPos(rulesPos);
|
||||
if (ImGui.Button("Rules", new Vector2(rulesButtonWidth, 0f)))
|
||||
@@ -1376,9 +1493,55 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
||||
ImGui.SetTooltip("Adjust chat window transparency.\nRight-click to reset to default.");
|
||||
}
|
||||
|
||||
var fadeUnfocused = chatConfig.FadeWhenUnfocused;
|
||||
if (ImGui.Checkbox("Fade window when unfocused", ref fadeUnfocused))
|
||||
{
|
||||
chatConfig.FadeWhenUnfocused = fadeUnfocused;
|
||||
_chatConfigService.Save();
|
||||
}
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.SetTooltip("When enabled, the chat window fades after it loses focus.\nHovering the window restores focus.");
|
||||
}
|
||||
|
||||
ImGui.BeginDisabled(!fadeUnfocused);
|
||||
var unfocusedOpacity = Math.Clamp(chatConfig.UnfocusedWindowOpacity, MinWindowOpacity, MaxWindowOpacity);
|
||||
var unfocusedChanged = ImGui.SliderFloat("Unfocused transparency", ref unfocusedOpacity, MinWindowOpacity, MaxWindowOpacity, "%.2f");
|
||||
var resetUnfocused = ImGui.IsItemClicked(ImGuiMouseButton.Right);
|
||||
if (resetUnfocused)
|
||||
{
|
||||
unfocusedOpacity = DefaultUnfocusedWindowOpacity;
|
||||
unfocusedChanged = true;
|
||||
}
|
||||
if (unfocusedChanged)
|
||||
{
|
||||
chatConfig.UnfocusedWindowOpacity = unfocusedOpacity;
|
||||
_chatConfigService.Save();
|
||||
}
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.SetTooltip("Target transparency while the chat window is unfocused.\nRight-click to reset to default.");
|
||||
}
|
||||
ImGui.EndDisabled();
|
||||
|
||||
ImGui.EndPopup();
|
||||
}
|
||||
|
||||
private static float MoveTowards(float current, float target, float maxDelta)
|
||||
{
|
||||
if (current < target)
|
||||
{
|
||||
return MathF.Min(current + maxDelta, target);
|
||||
}
|
||||
|
||||
if (current > target)
|
||||
{
|
||||
return MathF.Max(current - maxDelta, target);
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
private void ToggleChatConnection(bool currentlyEnabled)
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
|
||||
Reference in New Issue
Block a user