using Dalamud.Bindings.ImGui; using Dalamud.Interface; using Dalamud.Interface.Colors; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using MareSynchronos.API.Data.Extensions; using MareSynchronos.API.Dto.User; using MareSynchronos.Localization; using MareSynchronos.PlayerData.Pairs; using MareSynchronos.Services; using MareSynchronos.Services.Mediator; using MareSynchronos.UI.Handlers; using MareSynchronos.WebAPI; using System.Numerics; using System.Globalization; using System; namespace MareSynchronos.UI.Components; public class DrawUserPair : DrawPairBase { private static readonly Vector4 Violet = new(0.63f, 0.25f, 1f, 1f); protected readonly MareMediator _mediator; private readonly SelectGroupForPairUi _selectGroupForPairUi; private readonly CharaDataManager _charaDataManager; private static string L(string key, string fallback, params object[] args) { var safeArgs = args ?? Array.Empty(); return LocalizationService.Instance?.GetString(key, fallback, safeArgs) ?? string.Format(CultureInfo.CurrentCulture, fallback, safeArgs); } public DrawUserPair(string id, Pair entry, UidDisplayHandler displayHandler, ApiController apiController, MareMediator mareMediator, SelectGroupForPairUi selectGroupForPairUi, UiSharedService uiSharedService, CharaDataManager charaDataManager) : base(id, entry, apiController, displayHandler, uiSharedService) { if (_pair.UserPair == null) throw new ArgumentException("Pair must be UserPair", nameof(entry)); _pair = entry; _selectGroupForPairUi = selectGroupForPairUi; _mediator = mareMediator; _charaDataManager = charaDataManager; } public bool IsOnline => _pair.IsOnline; public bool IsVisible => _pair.IsVisible; public UserPairDto UserPair => _pair.UserPair!; protected override void DrawLeftSide(float textPosY, float originalY) { var online = _pair.IsOnline; var offlineGrey = ImGuiColors.DalamudGrey3; ImGui.SetCursorPosY(textPosY); ImGui.PushFont(UiBuilder.IconFont); UiSharedService.ColorText(FontAwesomeIcon.Moon.ToIconString(), online ? Violet : offlineGrey); ImGui.PopFont(); UiSharedService.AttachToolTip(online ? L("UserPair.Status.Online", "User is online") : L("UserPair.Status.Offline", "User is offline")); 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(L("UserPair.Tooltip.NotAddedBack", "{0} has not added you back", _pair.UserData.AliasOrUID)); } 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(L("UserPair.Tooltip.Paused", "Pairing with {0} is paused", _pair.UserData.AliasOrUID)); } if (_pair is { IsOnline: true, IsVisible: true }) { ImGui.SameLine(); ImGui.SetCursorPosY(textPosY); ImGui.PushFont(UiBuilder.IconFont); UiSharedService.ColorText(FontAwesomeIcon.Eye.ToIconString(), Violet); if (ImGui.IsItemClicked()) { _mediator.Publish(new TargetPairMessage(_pair)); } ImGui.PopFont(); var visibleTooltip = L("UserPair.Tooltip.Visible", "{0} is visible: {1}\nClick to target this player", _pair.UserData.AliasOrUID, _pair.PlayerName!); if (_pair.LastAppliedDataBytes >= 0) { visibleTooltip += UiSharedService.TooltipSeparator; visibleTooltip += (!_pair.IsVisible ? L("UserPair.Tooltip.Visible.LastPrefix", "(Last) ") : string.Empty) + L("UserPair.Tooltip.Visible.ModsInfo", "Mods Info") + Environment.NewLine; visibleTooltip += L("UserPair.Tooltip.Visible.FilesSize", "Files Size: {0}", UiSharedService.ByteToString(_pair.LastAppliedDataBytes, true)); if (_pair.LastAppliedApproximateVRAMBytes >= 0) { visibleTooltip += Environment.NewLine + L("UserPair.Tooltip.Visible.Vram", "Approx. VRAM Usage: {0}", UiSharedService.ByteToString(_pair.LastAppliedApproximateVRAMBytes, true)); } if (_pair.LastAppliedDataTris >= 0) { var trisValue = _pair.LastAppliedDataTris > 1000 ? (_pair.LastAppliedDataTris / 1000d).ToString("0.0'k'", CultureInfo.CurrentCulture) : _pair.LastAppliedDataTris.ToString(CultureInfo.CurrentCulture); visibleTooltip += Environment.NewLine + L("UserPair.Tooltip.Visible.Tris", "Triangle Count (excl. Vanilla): {0}", trisValue); } } UiSharedService.AttachToolTip(visibleTooltip); } } protected override float DrawRightSide(float textPosY, float originalY) { var pauseIcon = _pair.UserPair!.OwnPermissions.IsPaused() ? FontAwesomeIcon.Play : FontAwesomeIcon.Pause; var pauseIconSize = _uiSharedService.GetIconButtonSize(pauseIcon); var barButtonSize = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Bars); var entryUID = _pair.UserData.AliasOrUID; var spacingX = ImGui.GetStyle().ItemSpacing.X; var windowEndX = ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth(); var rightSidePos = windowEndX - barButtonSize.X; // Flyout Menu ImGui.SameLine(rightSidePos); ImGui.SetCursorPosY(originalY); if (_uiSharedService.IconButton(FontAwesomeIcon.Bars)) { ImGui.OpenPopup("User Flyout Menu"); } if (ImGui.BeginPopup("User Flyout Menu")) { using (ImRaii.PushId($"buttons-{_pair.UserData.UID}")) DrawPairedClientMenu(_pair); ImGui.EndPopup(); } // Pause (mutual pairs only) if (_pair.UserPair!.OwnPermissions.IsPaired() && _pair.UserPair!.OtherPermissions.IsPaired()) { rightSidePos -= pauseIconSize.X + spacingX; ImGui.SameLine(rightSidePos); ImGui.SetCursorPosY(originalY); if (_uiSharedService.IconButton(pauseIcon)) { var perm = _pair.UserPair!.OwnPermissions; perm.SetPaused(!perm.IsPaused()); _ = _apiController.UserSetPairPermissions(new(_pair.UserData, perm)); } UiSharedService.AttachToolTip(!_pair.UserPair!.OwnPermissions.IsPaused() ? L("UserPair.Tooltip.Pause", "Pause pairing with {0}", entryUID) : L("UserPair.Tooltip.Resume", "Resume pairing with {0}", entryUID)); var individualSoundsDisabled = (_pair.UserPair?.OwnPermissions.IsDisableSounds() ?? false) || (_pair.UserPair?.OtherPermissions.IsDisableSounds() ?? false); var individualAnimDisabled = (_pair.UserPair?.OwnPermissions.IsDisableAnimations() ?? false) || (_pair.UserPair?.OtherPermissions.IsDisableAnimations() ?? false); var individualVFXDisabled = (_pair.UserPair?.OwnPermissions.IsDisableVFX() ?? false) || (_pair.UserPair?.OtherPermissions.IsDisableVFX() ?? false); if (individualSoundsDisabled || individualAnimDisabled || individualVFXDisabled) { var icon = FontAwesomeIcon.ExclamationTriangle; var iconwidth = _uiSharedService.GetIconButtonSize(icon); rightSidePos -= iconwidth.X + spacingX / 2f; ImGui.SameLine(rightSidePos); ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudYellow); _uiSharedService.IconText(icon); ImGui.PopStyleColor(); if (ImGui.IsItemHovered()) { ImGui.BeginTooltip(); ImGui.TextUnformatted(L("UserPair.Tooltip.Permission.Header", "Individual user permissions")); if (individualSoundsDisabled) { var userSoundsText = L("UserPair.Tooltip.Permission.Sound", "Sound sync disabled with {0}", _pair.UserData.AliasOrUID); _uiSharedService.IconText(FontAwesomeIcon.VolumeOff); ImGui.SameLine(40 * ImGuiHelpers.GlobalScale); ImGui.TextUnformatted(userSoundsText); ImGui.NewLine(); ImGui.SameLine(40 * ImGuiHelpers.GlobalScale); var youStatus = _pair.UserPair!.OwnPermissions.IsDisableSounds() ? L("UserPair.Tooltip.Permission.State.Disabled", "Disabled") : L("UserPair.Tooltip.Permission.State.Enabled", "Enabled"); var theyStatus = _pair.UserPair!.OtherPermissions.IsDisableSounds() ? L("UserPair.Tooltip.Permission.State.Disabled", "Disabled") : L("UserPair.Tooltip.Permission.State.Enabled", "Enabled"); ImGui.TextUnformatted(L("UserPair.Tooltip.Permission.Status", "You: {0}, They: {1}", youStatus, theyStatus)); } if (individualAnimDisabled) { var userAnimText = L("UserPair.Tooltip.Permission.Animation", "Animation sync disabled with {0}", _pair.UserData.AliasOrUID); _uiSharedService.IconText(FontAwesomeIcon.Stop); ImGui.SameLine(40 * ImGuiHelpers.GlobalScale); ImGui.TextUnformatted(userAnimText); ImGui.NewLine(); ImGui.SameLine(40 * ImGuiHelpers.GlobalScale); var youStatus = _pair.UserPair!.OwnPermissions.IsDisableAnimations() ? L("UserPair.Tooltip.Permission.State.Disabled", "Disabled") : L("UserPair.Tooltip.Permission.State.Enabled", "Enabled"); var theyStatus = _pair.UserPair!.OtherPermissions.IsDisableAnimations() ? L("UserPair.Tooltip.Permission.State.Disabled", "Disabled") : L("UserPair.Tooltip.Permission.State.Enabled", "Enabled"); ImGui.TextUnformatted(L("UserPair.Tooltip.Permission.Status", "You: {0}, They: {1}", youStatus, theyStatus)); } if (individualVFXDisabled) { var userVFXText = L("UserPair.Tooltip.Permission.Vfx", "VFX sync disabled with {0}", _pair.UserData.AliasOrUID); _uiSharedService.IconText(FontAwesomeIcon.Circle); ImGui.SameLine(40 * ImGuiHelpers.GlobalScale); ImGui.TextUnformatted(userVFXText); ImGui.NewLine(); ImGui.SameLine(40 * ImGuiHelpers.GlobalScale); var youStatus = _pair.UserPair!.OwnPermissions.IsDisableVFX() ? L("UserPair.Tooltip.Permission.State.Disabled", "Disabled") : L("UserPair.Tooltip.Permission.State.Enabled", "Enabled"); var theyStatus = _pair.UserPair!.OtherPermissions.IsDisableVFX() ? L("UserPair.Tooltip.Permission.State.Disabled", "Disabled") : L("UserPair.Tooltip.Permission.State.Enabled", "Enabled"); ImGui.TextUnformatted(L("UserPair.Tooltip.Permission.Status", "You: {0}, They: {1}", youStatus, theyStatus)); } ImGui.EndTooltip(); } } } if (_charaDataManager.SharedWithYouData.TryGetValue(_pair.UserData, out var sharedData)) { var icon = FontAwesomeIcon.Running; var iconwidth = _uiSharedService.GetIconButtonSize(icon); rightSidePos -= iconwidth.X + spacingX / 2f; ImGui.SameLine(rightSidePos); _uiSharedService.IconText(icon); UiSharedService.AttachToolTip(L("UserPair.Tooltip.SharedData", "This user has shared {0} Character Data Sets with you.", sharedData.Count) + UiSharedService.TooltipSeparator + L("UserPair.Tooltip.SharedData.OpenHub", "Click to open the Character Data Hub and show the entries.")); if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) { _mediator.Publish(new OpenCharaDataHubWithFilterMessage(_pair.UserData)); } } return rightSidePos - spacingX; } private void DrawPairedClientMenu(Pair entry) { if (entry.IsVisible) { if (_uiSharedService.IconTextButton(FontAwesomeIcon.Eye, L("UserPair.Menu.Target", "Target player"))) { _mediator.Publish(new TargetPairMessage(entry)); ImGui.CloseCurrentPopup(); } } if (!entry.IsPaused) { if (_uiSharedService.IconTextButton(FontAwesomeIcon.User, L("UserPair.Menu.OpenProfile", "Open Profile"))) { _displayHandler.OpenProfile(entry); ImGui.CloseCurrentPopup(); } UiSharedService.AttachToolTip(L("UserPair.Menu.OpenProfile.Tooltip", "Opens the profile for this user in a new window")); } if (entry.IsVisible) { #if DEBUG if (_uiSharedService.IconTextButton(FontAwesomeIcon.PersonCircleQuestion, L("UserPair.Menu.OpenAnalysis", "Open Analysis"))) { _displayHandler.OpenAnalysis(_pair); ImGui.CloseCurrentPopup(); } #endif if (_uiSharedService.IconTextButton(FontAwesomeIcon.Sync, L("UserPair.Menu.ReloadData", "Reload last data"))) { entry.ApplyLastReceivedData(forced: true); ImGui.CloseCurrentPopup(); } UiSharedService.AttachToolTip(L("UserPair.Menu.ReloadData.Tooltip", "This reapplies the last received character data to this character")); } if (_uiSharedService.IconTextButton(FontAwesomeIcon.PlayCircle, L("UserPair.Menu.CyclePause", "Cycle pause state"))) { _ = _apiController.CyclePause(entry.UserData); ImGui.CloseCurrentPopup(); } var entryUID = entry.UserData.AliasOrUID; if (_uiSharedService.IconTextButton(FontAwesomeIcon.Folder, L("UserPair.Menu.PairGroups", "Pair Groups"))) { _selectGroupForPairUi.Open(entry); } UiSharedService.AttachToolTip(L("UserPair.Menu.PairGroups.Tooltip", "Choose pair groups for {0}", entryUID)); var isDisableSounds = entry.UserPair!.OwnPermissions.IsDisableSounds(); string disableSoundsText = isDisableSounds ? L("UserPair.Menu.EnableSoundSync", "Enable sound sync") : L("UserPair.Menu.DisableSoundSync", "Disable sound sync"); var disableSoundsIcon = isDisableSounds ? FontAwesomeIcon.VolumeUp : FontAwesomeIcon.VolumeMute; if (_uiSharedService.IconTextButton(disableSoundsIcon, disableSoundsText)) { var permissions = entry.UserPair.OwnPermissions; permissions.SetDisableSounds(!isDisableSounds); _ = _apiController.UserSetPairPermissions(new UserPermissionsDto(entry.UserData, permissions)); } var isDisableAnims = entry.UserPair!.OwnPermissions.IsDisableAnimations(); string disableAnimsText = isDisableAnims ? L("UserPair.Menu.EnableAnimationSync", "Enable animation sync") : L("UserPair.Menu.DisableAnimationSync", "Disable animation sync"); var disableAnimsIcon = isDisableAnims ? FontAwesomeIcon.Running : FontAwesomeIcon.Stop; if (_uiSharedService.IconTextButton(disableAnimsIcon, disableAnimsText)) { var permissions = entry.UserPair.OwnPermissions; permissions.SetDisableAnimations(!isDisableAnims); _ = _apiController.UserSetPairPermissions(new UserPermissionsDto(entry.UserData, permissions)); } var isDisableVFX = entry.UserPair!.OwnPermissions.IsDisableVFX(); string disableVFXText = isDisableVFX ? L("UserPair.Menu.EnableVfxSync", "Enable VFX sync") : L("UserPair.Menu.DisableVfxSync", "Disable VFX sync"); var disableVFXIcon = isDisableVFX ? FontAwesomeIcon.Sun : FontAwesomeIcon.Circle; if (_uiSharedService.IconTextButton(disableVFXIcon, disableVFXText)) { var permissions = entry.UserPair.OwnPermissions; permissions.SetDisableVFX(!isDisableVFX); _ = _apiController.UserSetPairPermissions(new UserPermissionsDto(entry.UserData, permissions)); } if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, L("UserPair.Menu.Unpair", "Unpair Permanently")) && UiSharedService.CtrlPressed()) { _ = _apiController.UserRemovePair(new(entry.UserData)); } UiSharedService.AttachToolTip(L("UserPair.Menu.Unpair.Tooltip", "Hold CTRL and click to unpair permanently from {0}", entryUID)); } }