diff --git a/MareSynchronos/MareSynchronos.csproj b/MareSynchronos/MareSynchronos.csproj
index 4617480..1ad3913 100644
--- a/MareSynchronos/MareSynchronos.csproj
+++ b/MareSynchronos/MareSynchronos.csproj
@@ -3,7 +3,7 @@
UmbraSync
UmbraSync
- 0.1.6.3
+ 0.1.6.4
diff --git a/MareSynchronos/Services/Mediator/Messages.cs b/MareSynchronos/Services/Mediator/Messages.cs
index b6302c1..1bbfd71 100644
--- a/MareSynchronos/Services/Mediator/Messages.cs
+++ b/MareSynchronos/Services/Mediator/Messages.cs
@@ -108,7 +108,7 @@ public record GPoseLobbyReceiveCharaData(CharaDataDownloadDto CharaDataDownloadD
public record GPoseLobbyReceivePoseData(UserData UserData, PoseData PoseData) : MessageBase;
public record GPoseLobbyReceiveWorldData(UserData UserData, WorldData WorldData) : MessageBase;
-public record NearbyEntry(string Name, ushort WorldId, float Distance, bool IsMatch, string? Token, string? DisplayName, string? Uid);
+public record NearbyEntry(string Name, ushort WorldId, float Distance, bool IsMatch, string? Token, string? DisplayName, string? Uid, bool AcceptPairRequests = true);
public record DiscoveryListUpdated(List Entries) : MessageBase;
public record NearbyDetectionToggled(bool Enabled) : MessageBase;
public record AllowPairRequestsToggled(bool Enabled) : MessageBase;
diff --git a/MareSynchronos/UI/CompactUI.cs b/MareSynchronos/UI/CompactUI.cs
index 84b7005..0e457d2 100644
--- a/MareSynchronos/UI/CompactUI.cs
+++ b/MareSynchronos/UI/CompactUI.cs
@@ -377,7 +377,6 @@ public class CompactUi : WindowMediatorSubscriberBase
// Always show a Nearby group when detection is enabled, even if empty
if (_configService.Current.EnableAutoDetectDiscovery)
{
- ImGui.Separator();
using (ImRaii.PushId("group-Nearby"))
{
var icon = _nearbyOpen ? FontAwesomeIcon.CaretSquareDown : FontAwesomeIcon.CaretSquareRight;
@@ -401,14 +400,61 @@ public class CompactUi : WindowMediatorSubscriberBase
if (_nearbyOpen)
{
ImGui.Indent();
- if (onUmbra == 0)
+ var nearby = _nearbyEntries == null
+ ? new List()
+ : _nearbyEntries.Where(e => e.IsMatch)
+ .OrderBy(e => e.Distance)
+ .ToList();
+ if (nearby.Count == 0)
{
UiSharedService.ColorTextWrapped("No nearby players detected.", ImGuiColors.DalamudGrey3);
}
else
{
- UiSharedService.ColorTextWrapped("Open Nearby for details.", ImGuiColors.DalamudGrey3);
+ foreach (var e in nearby)
+ {
+ // name
+ var name = e.DisplayName ?? e.Name;
+ ImGui.AlignTextToFramePadding();
+ ImGui.TextUnformatted(name);
+
+ // right side status/action
+ var right = ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth();
+ ImGui.SameLine();
+
+ // detect if already paired (prefer UID if present)
+ bool isPaired = false;
+ try
+ {
+ isPaired = _pairManager.DirectPairs.Any(p => string.Equals(p.UserData.UID, e.Uid, StringComparison.Ordinal));
+ }
+ catch
+ {
+ // fall back to display name/alias matching if UID is not available
+ var key = (e.DisplayName ?? e.Name) ?? string.Empty;
+ isPaired = _pairManager.DirectPairs.Any(p => string.Equals(p.UserData.AliasOrUID, key, StringComparison.OrdinalIgnoreCase));
+ }
+
+ var statusText = isPaired ? "✔ Paired" : (e.AcceptPairRequests ? "➕ Invite" : "⛔ Requests disabled");
+ var statusSize = ImGui.CalcTextSize(statusText);
+ ImGui.SetCursorPosX(right - statusSize.X);
+
+ if (isPaired || !e.AcceptPairRequests)
+ {
+ // just paint the status
+ ImGui.TextUnformatted(statusText);
+ }
+ else
+ {
+ // no direct send from Compact; open Nearby window to send the invite there
+ if (_uiSharedService.IconTextButton(FontAwesomeIcon.UserPlus, "Invite", statusSize.X))
+ {
+ Mediator.Publish(new UiToggleMessage(typeof(AutoDetectUi)));
+ }
+ }
+ }
}
+
// Pending Nearby requests (Accept / Dismiss)
try
{
@@ -416,7 +462,7 @@ public class CompactUi : WindowMediatorSubscriberBase
if (inbox != null && inbox.Pending.Count > 0)
{
ImGuiHelpers.ScaledDummy(6);
- _uiSharedService.BigText("Requests");
+ _uiSharedService.BigText("Incoming requests");
foreach (var kv in inbox.Pending)
{
ImGui.AlignTextToFramePadding();
@@ -483,8 +529,9 @@ public class CompactUi : WindowMediatorSubscriberBase
{
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - ((userSize.Y + textSize.Y) / 2 + shardTextSize.Y) / 2 - ImGui.GetStyle().ItemSpacing.Y + buttonSize.Y / 2);
}
- var color = UiSharedService.GetBoolColor(!_serverManager.CurrentServer!.FullPause);
- var connectedIcon = !_serverManager.CurrentServer.FullPause ? FontAwesomeIcon.Link : FontAwesomeIcon.Unlink;
+ var isLinked = !_serverManager.CurrentServer!.FullPause;
+ var color = isLinked ? new Vector4(0.63f, 0.25f, 1f, 1f) : UiSharedService.GetBoolColor(isLinked);
+ var connectedIcon = isLinked ? FontAwesomeIcon.Link : FontAwesomeIcon.Unlink;
if (_apiController.ServerState is ServerState.Connected)
{
diff --git a/MareSynchronos/UI/Components/DrawGroupPair.cs b/MareSynchronos/UI/Components/DrawGroupPair.cs
index 64db21f..acdd871 100644
--- a/MareSynchronos/UI/Components/DrawGroupPair.cs
+++ b/MareSynchronos/UI/Components/DrawGroupPair.cs
@@ -1,4 +1,5 @@
-using Dalamud.Bindings.ImGui;
+using System.Numerics;
+using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Utility;
@@ -38,15 +39,19 @@ public class DrawGroupPair : DrawPairBase
var entryIsMod = _fullInfoDto.GroupPairStatusInfo.IsModerator();
var entryIsOwner = string.Equals(_pair.UserData.UID, _group.OwnerUID, StringComparison.Ordinal);
var entryIsPinned = _fullInfoDto.GroupPairStatusInfo.IsPinned();
- var presenceIcon = _pair.IsVisible ? FontAwesomeIcon.Eye : (_pair.IsOnline ? FontAwesomeIcon.Link : FontAwesomeIcon.Unlink);
- var presenceColor = (_pair.IsOnline || _pair.IsVisible) ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed;
+ var presenceIcon = _pair.IsVisible ? FontAwesomeIcon.Eye : FontAwesomeIcon.CloudMoon;
+ var presenceColor = (_pair.IsOnline || _pair.IsVisible) ? new Vector4(0.63f, 0.25f, 1f, 1f) : ImGuiColors.DalamudGrey;
var presenceText = entryUID + " is offline";
ImGui.SetCursorPosY(textPosY);
+
+ // We'll draw an optional prefix icon (pause or paired-moon). If we draw one,
+ // we add a SameLine() before drawing the presence icon. Otherwise, we draw
+ // the presence icon directly at the current cursor position to avoid gaps.
+ bool drewPrefixIcon = false;
+
if (_pair.IsPaused)
{
- presenceIcon = FontAwesomeIcon.Question;
- presenceColor = ImGuiColors.DalamudGrey;
presenceText = entryUID + " online status is unknown (paused)";
ImGui.PushFont(UiBuilder.IconFont);
@@ -54,24 +59,37 @@ public class DrawGroupPair : DrawPairBase
ImGui.PopFont();
UiSharedService.AttachToolTip("Pairing status with " + entryUID + " is paused");
+ drewPrefixIcon = true;
}
else
{
- ImGui.PushFont(UiBuilder.IconFont);
- UiSharedService.ColorText(FontAwesomeIcon.Check.ToIconString(), ImGuiColors.ParsedGreen);
- ImGui.PopFont();
+ bool individuallyPaired = _pair.UserPair != null;
+ var violet = new Vector4(0.63f, 0.25f, 1f, 1f);
- UiSharedService.AttachToolTip("You are paired with " + entryUID);
+ // Show a violet moon only when individually paired AND online/visible.
+ // If offline or not individually paired, do not draw a moon at all.
+ if (individuallyPaired && (_pair.IsOnline || _pair.IsVisible))
+ {
+ ImGui.PushFont(UiBuilder.IconFont);
+ UiSharedService.ColorText(FontAwesomeIcon.Moon.ToIconString(), violet);
+ ImGui.PopFont();
+ UiSharedService.AttachToolTip("You are individually paired with " + entryUID);
+ drewPrefixIcon = true;
+ }
}
- if (_pair.IsOnline && !_pair.IsVisible) presenceText = entryUID + " is online";
- else if (_pair.IsOnline && _pair.IsVisible) presenceText = entryUID + " is visible: " + _pair.PlayerName + Environment.NewLine + "Click to target this player";
+ // Presence status icon (eye when visible, cloud-moon otherwise)
+ if (drewPrefixIcon)
+ ImGui.SameLine();
- ImGui.SameLine();
ImGui.SetCursorPosY(textPosY);
ImGui.PushFont(UiBuilder.IconFont);
UiSharedService.ColorText(presenceIcon.ToIconString(), presenceColor);
ImGui.PopFont();
+
+ if (_pair.IsOnline && !_pair.IsVisible) presenceText = entryUID + " is online";
+ else if (_pair.IsOnline && _pair.IsVisible) presenceText = entryUID + " is visible: " + _pair.PlayerName + Environment.NewLine + "Click to target this player";
+
if (_pair.IsVisible)
{
if (ImGui.IsItemClicked())
@@ -94,6 +112,7 @@ public class DrawGroupPair : DrawPairBase
}
}
}
+
UiSharedService.AttachToolTip(presenceText);
if (entryIsOwner)
diff --git a/MareSynchronos/UI/Components/DrawUserPair.cs b/MareSynchronos/UI/Components/DrawUserPair.cs
index 23989b7..5131307 100644
--- a/MareSynchronos/UI/Components/DrawUserPair.cs
+++ b/MareSynchronos/UI/Components/DrawUserPair.cs
@@ -38,33 +38,41 @@ public class DrawUserPair : DrawPairBase
protected override void DrawLeftSide(float textPosY, float originalY)
{
- FontAwesomeIcon connectionIcon;
- Vector4 connectionColor;
- string connectionText;
- if (!(_pair.UserPair!.OwnPermissions.IsPaired() && _pair.UserPair!.OtherPermissions.IsPaired()))
- {
- connectionIcon = FontAwesomeIcon.ArrowUp;
- connectionText = _pair.UserData.AliasOrUID + " has not added you back";
- connectionColor = ImGuiColors.DalamudRed;
- }
- else if (_pair.UserPair!.OwnPermissions.IsPaused() || _pair.UserPair!.OtherPermissions.IsPaused())
- {
- connectionIcon = FontAwesomeIcon.PauseCircle;
- connectionText = "Pairing status with " + _pair.UserData.AliasOrUID + " is paused";
- connectionColor = ImGuiColors.DalamudYellow;
- }
- else
- {
- connectionIcon = FontAwesomeIcon.Check;
- connectionText = "You are paired with " + _pair.UserData.AliasOrUID;
- connectionColor = ImGuiColors.ParsedGreen;
- }
+ // Primary presence indicator: moon (online = violet, offline = grey)
+ var online = _pair.IsOnline;
+ // Violet accent (#BA70EF)
+ var violet = new Vector4(0.69f, 0.27f, 0.93f, 1f);
+ var offlineGrey = ImGuiColors.DalamudGrey3;
ImGui.SetCursorPosY(textPosY);
ImGui.PushFont(UiBuilder.IconFont);
- UiSharedService.ColorText(connectionIcon.ToIconString(), connectionColor);
+ UiSharedService.ColorText(FontAwesomeIcon.Moon.ToIconString(), online ? violet : offlineGrey);
ImGui.PopFont();
- UiSharedService.AttachToolTip(connectionText);
+ UiSharedService.AttachToolTip(online
+ ? "User is online"
+ : "User is offline");
+
+ // Secondary pairing state badges (only if needed)
+ // Not mutually paired -> red ArrowUp
+ if (!(_pair.UserPair!.OwnPermissions.IsPaired() && _pair.UserPair!.OtherPermissions.IsPaired()))
+ {
+ ImGui.SameLine();
+ ImGui.SetCursorPosY(textPosY);
+ ImGui.PushFont(UiBuilder.IconFont);
+ UiSharedService.ColorText(FontAwesomeIcon.ArrowUp.ToIconString(), ImGuiColors.DalamudRed);
+ ImGui.PopFont();
+ UiSharedService.AttachToolTip(_pair.UserData.AliasOrUID + " has not added you back");
+ }
+ // Paused (either side) -> yellow PauseCircle
+ else if (_pair.UserPair!.OwnPermissions.IsPaused() || _pair.UserPair!.OtherPermissions.IsPaused())
+ {
+ ImGui.SameLine();
+ ImGui.SetCursorPosY(textPosY);
+ ImGui.PushFont(UiBuilder.IconFont);
+ UiSharedService.ColorText(FontAwesomeIcon.PauseCircle.ToIconString(), ImGuiColors.DalamudYellow);
+ ImGui.PopFont();
+ UiSharedService.AttachToolTip("Pairing with " + _pair.UserData.AliasOrUID + " is paused");
+ }
if (_pair is { IsOnline: true, IsVisible: true })
{
ImGui.SameLine();
diff --git a/MareSynchronos/UI/Components/GroupPanel.cs b/MareSynchronos/UI/Components/GroupPanel.cs
index 067d591..0f19e8a 100644
--- a/MareSynchronos/UI/Components/GroupPanel.cs
+++ b/MareSynchronos/UI/Components/GroupPanel.cs
@@ -1,4 +1,4 @@
-using Dalamud.Bindings.ImGui;
+using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Utility;
@@ -464,7 +464,9 @@ internal sealed class GroupPanel
if (offlineUsers.Count > 0)
{
+ ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey);
ImGui.TextUnformatted("Offline/Unknown");
+ ImGui.PopStyleColor();
ImGui.Separator();
if (hideOfflineUsers)
{
diff --git a/MareSynchronos/UI/Components/PairGroupsUi.cs b/MareSynchronos/UI/Components/PairGroupsUi.cs
index 0778a02..6c8dd42 100644
--- a/MareSynchronos/UI/Components/PairGroupsUi.cs
+++ b/MareSynchronos/UI/Components/PairGroupsUi.cs
@@ -157,7 +157,7 @@ public class PairGroupsUi
{
TagHandler.CustomUnpairedTag => "Unpaired",
TagHandler.CustomOfflineTag => "Offline",
- TagHandler.CustomOnlineTag => _mareConfig.Current.ShowOfflineUsersSeparately ? "Online/Paused" : "Contacts",
+ TagHandler.CustomOnlineTag => _mareConfig.Current.ShowOfflineUsersSeparately ? "Online" : "Contacts",
TagHandler.CustomVisibleTag => "Visible",
_ => tag
};
diff --git a/MareSynchronos/UI/SettingsUi.cs b/MareSynchronos/UI/SettingsUi.cs
index 77168c5..76e4759 100644
--- a/MareSynchronos/UI/SettingsUi.cs
+++ b/MareSynchronos/UI/SettingsUi.cs
@@ -652,7 +652,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
ImGui.SameLine(300.0f * ImGuiHelpers.GlobalScale);
if (_ipcProvider.ImpersonationActive)
{
- UiSharedService.ColorTextWrapped("Umbra API active!", ImGuiColors.HealerGreen);
+ UiSharedService.ColorTextWrapped("Umbra API active!", UiSharedService.AccentColor);
}
else
{
@@ -991,7 +991,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
_uiShared.DrawHelpText("If this option is selected all already existing notes for UIDs will be overwritten by the imported notes.");
if (_notesSuccessfullyApplied.HasValue && _notesSuccessfullyApplied.Value)
{
- UiSharedService.ColorTextWrapped("User Notes successfully imported", ImGuiColors.HealerGreen);
+ UiSharedService.ColorTextWrapped("User Notes successfully imported", UiSharedService.AccentColor);
}
else if (_notesSuccessfullyApplied.HasValue && !_notesSuccessfullyApplied.Value)
{
@@ -1310,7 +1310,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
ImGui.TextUnformatted("Current VRAM utilization by all nearby players:");
ImGui.SameLine();
- using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.HealerGreen, totalVramBytes < 2.0 * 1024.0 * 1024.0 * 1024.0))
+ using (ImRaii.PushColor(ImGuiCol.Text, UiSharedService.AccentColor, totalVramBytes < 2.0 * 1024.0 * 1024.0 * 1024.0))
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow, totalVramBytes >= 4.0 * 1024.0 * 1024.0 * 1024.0))
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed, totalVramBytes >= 6.0 * 1024.0 * 1024.0 * 1024.0))
ImGui.TextUnformatted($"{totalVramBytes / 1024.0 / 1024.0 / 1024.0:0.00} GiB");
@@ -1903,11 +1903,11 @@ public class SettingsUi : WindowMediatorSubscriberBase
{
ImGui.TextUnformatted("Service " + _serverConfigurationManager.CurrentServer!.ServerName + ":");
ImGui.SameLine();
- ImGui.TextColored(ImGuiColors.ParsedGreen, "Available");
+ ImGui.TextColored(UiSharedService.AccentColor, "Available");
ImGui.SameLine();
ImGui.TextUnformatted("(");
ImGui.SameLine();
- ImGui.TextColored(ImGuiColors.ParsedGreen, _apiController.OnlineUsers.ToString(CultureInfo.InvariantCulture));
+ ImGui.TextColored(UiSharedService.AccentColor, _apiController.OnlineUsers.ToString(CultureInfo.InvariantCulture));
ImGui.SameLine();
ImGui.TextUnformatted("Users Online");
ImGui.SameLine();
diff --git a/MareSynchronos/UI/UISharedService.cs b/MareSynchronos/UI/UISharedService.cs
index 649e3d9..f5a207a 100644
--- a/MareSynchronos/UI/UISharedService.cs
+++ b/MareSynchronos/UI/UISharedService.cs
@@ -36,7 +36,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
ImGuiWindowFlags.NoScrollbar |
ImGuiWindowFlags.NoScrollWithMouse;
- public static Vector4 AccentColor { get; set; } = ImGuiColors.DalamudYellow;
+ public static Vector4 AccentColor { get; set; } = ImGuiColors.DalamudViolet;
public readonly FileDialogManager FileDialogManager;
@@ -310,7 +310,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
}
}
- public static Vector4 GetBoolColor(bool input) => input ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed;
+ public static Vector4 GetBoolColor(bool input) => input ? AccentColor : ImGuiColors.DalamudRed;
public float GetIconTextButtonSize(FontAwesomeIcon icon, string text)
{
@@ -517,7 +517,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
public void BooleanToColoredIcon(bool value, bool inline = true)
{
- using var colorgreen = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.HealerGreen, value);
+ using var colorgreen = ImRaii.PushColor(ImGuiCol.Text, AccentColor, value);
using var colorred = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed, !value);
if (inline) ImGui.SameLine();