diff --git a/MareSynchronos/PlayerData/Pairs/Pair.cs b/MareSynchronos/PlayerData/Pairs/Pair.cs index cbeb5e5..764be38 100644 --- a/MareSynchronos/PlayerData/Pairs/Pair.cs +++ b/MareSynchronos/PlayerData/Pairs/Pair.cs @@ -57,9 +57,11 @@ public class Pair SeStringBuilder seStringBuilder = new(); SeStringBuilder seStringBuilder2 = new(); SeStringBuilder seStringBuilder3 = new(); + SeStringBuilder seStringBuilder4 = new(); var openProfileSeString = seStringBuilder.AddUiForeground(526).AddText(" ").AddUiForegroundOff().AddText("Open Profile").Build(); var reapplyDataSeString = seStringBuilder2.AddUiForeground(526).AddText(" ").AddUiForegroundOff().AddText("Reapply last data").Build(); var cyclePauseState = seStringBuilder3.AddUiForeground(526).AddText(" ").AddUiForegroundOff().AddText("Cycle pause state").Build(); + var changePermissions = seStringBuilder4.AddUiForeground(526).AddText(" ").AddUiForegroundOff().AddText("Change Permissions").Build(); args.AddCustomItem(new GameObjectContextMenuItem(openProfileSeString, (a) => { _mediator.Publish(new ProfileOpenStandaloneMessage(this)); @@ -68,6 +70,10 @@ public class Pair { ApplyLastReceivedData(forced: true); }, useDalamudIndicator: false)); + args.AddCustomItem(new GameObjectContextMenuItem(changePermissions, (a) => + { + _mediator.Publish(new OpenPermissionWindow(this)); + }, useDalamudIndicator: false)); args.AddCustomItem(new GameObjectContextMenuItem(cyclePauseState, (a) => { _mediator.Publish(new CyclePauseMessage(UserData)); diff --git a/MareSynchronos/Services/Mediator/Messages.cs b/MareSynchronos/Services/Mediator/Messages.cs index 943433c..85a69d4 100644 --- a/MareSynchronos/Services/Mediator/Messages.cs +++ b/MareSynchronos/Services/Mediator/Messages.cs @@ -73,6 +73,7 @@ public record RefreshUiMessage : MessageBase; public record OpenReportPopupMessage(Pair PairToReport) : MessageBase; public record OpenBanUserPopupMessage(Pair PairToBan, GroupFullInfoDto GroupFullInfoDto) : MessageBase; public record OpenSyncshellAdminPanel(GroupFullInfoDto GroupInfo) : MessageBase; +public record OpenPermissionWindow(Pair Pair) : MessageBase; #pragma warning restore S2094 #pragma warning restore MA0048 // File name must match type name \ No newline at end of file diff --git a/MareSynchronos/Services/UiFactory.cs b/MareSynchronos/Services/UiFactory.cs index 8d611b2..7db5232 100644 --- a/MareSynchronos/Services/UiFactory.cs +++ b/MareSynchronos/Services/UiFactory.cs @@ -43,4 +43,10 @@ public class UiFactory return new StandaloneProfileUi(_loggerFactory.CreateLogger(), _mareMediator, _uiSharedService, _serverConfigManager, _mareProfileManager, _pairManager, pair); } + + public PermissionWindowUI CreatePermissionPopupUi(Pair pair) + { + return new PermissionWindowUI(_loggerFactory.CreateLogger(), pair, + _mareMediator, _uiSharedService, _apiController); + } } diff --git a/MareSynchronos/Services/UiService.cs b/MareSynchronos/Services/UiService.cs index a8151e5..54a8053 100644 --- a/MareSynchronos/Services/UiService.cs +++ b/MareSynchronos/Services/UiService.cs @@ -65,6 +65,17 @@ public sealed class UiService : DisposableMediatorSubscriberBase } }); + Mediator.Subscribe(this, (msg) => + { + if (!_createdWindows.Exists(p => p is PermissionWindowUI ui + && msg.Pair == ui.Pair)) + { + var window = _uiFactory.CreatePermissionPopupUi(msg.Pair); + _createdWindows.Add(window); + _windowSystem.AddWindow(window); + } + }); + Mediator.Subscribe(this, (msg) => { _windowSystem.RemoveWindow(msg.Window); diff --git a/MareSynchronos/UI/Components/DrawUserPair.cs b/MareSynchronos/UI/Components/DrawUserPair.cs index 96c12ef..572915c 100644 --- a/MareSynchronos/UI/Components/DrawUserPair.cs +++ b/MareSynchronos/UI/Components/DrawUserPair.cs @@ -87,6 +87,13 @@ public class DrawUserPair ImGui.Separator(); ImGui.TextUnformatted("Pair Permission Functions"); + if (UiSharedService.NormalizedIconTextButton(FontAwesomeIcon.WindowMaximize, "Open Permissions Window", _menuRenderWidth, true)) + { + _mediator.Publish(new OpenPermissionWindow(_pair)); + ImGui.CloseCurrentPopup(); + } + UiSharedService.AttachToolTip("Opens the Permissions Window which allows you to manage multiple permissions at once."); + var isSticky = _pair.UserPair!.OwnPermissions.IsSticky(); string stickyText = isSticky ? "Disable Preferred Permissions" : "Enable Preferred Permissions"; var stickyIcon = isSticky ? FontAwesomeIcon.ArrowCircleDown : FontAwesomeIcon.ArrowCircleUp; @@ -201,7 +208,7 @@ public class DrawUserPair else if (_pair.IsVisible) { UiSharedService.NormalizedIcon(FontAwesomeIcon.Eye, ImGuiColors.ParsedGreen); - userPairText = _pair.UserData.AliasOrUID + " is visible"; + userPairText = _pair.UserData.AliasOrUID + " is visible: " + _pair.PlayerName; } else { @@ -306,7 +313,7 @@ public class DrawUserPair currentRightSide -= (UiSharedService.GetIconData(individualIcon).NormalizedIconScale.X + spacingX); ImGui.SameLine(currentRightSide); - using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.HealerGreen, individualIsSticky)) + using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow, individualAnimDisabled || individualSoundsDisabled || individualVFXDisabled)) UiSharedService.NormalizedIcon(individualIcon); if (ImGui.IsItemHovered()) { diff --git a/MareSynchronos/UI/PermissionWindowUI.cs b/MareSynchronos/UI/PermissionWindowUI.cs new file mode 100644 index 0000000..a816178 --- /dev/null +++ b/MareSynchronos/UI/PermissionWindowUI.cs @@ -0,0 +1,186 @@ +using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; +using ImGuiNET; +using MareSynchronos.API.Data.Enum; +using MareSynchronos.API.Data.Extensions; +using MareSynchronos.PlayerData.Pairs; +using MareSynchronos.Services.Mediator; +using MareSynchronos.Utils; +using MareSynchronos.WebAPI; +using Microsoft.Extensions.Logging; + +namespace MareSynchronos.UI; + +public class PermissionWindowUI : WindowMediatorSubscriberBase +{ + public Pair Pair { get; init; } + + private readonly UiSharedService _uiSharedService; + private readonly ApiController _apiController; + private UserPermissions _ownPermissions; + + public PermissionWindowUI(ILogger logger, Pair pair, MareMediator mediator, UiSharedService uiSharedService, + ApiController apiController) : base(logger, mediator, "Permissions for " + pair.UserData.AliasOrUID + "###MareSynchronosPermissions" + pair.UserData.UID) + { + Pair = pair; + _uiSharedService = uiSharedService; + _apiController = apiController; + _ownPermissions = pair.UserPair.OwnPermissions.DeepClone(); + Flags = ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoResize; + SizeConstraints = new() + { + MinimumSize = new(450, 100), + MaximumSize = new(450, 500) + }; + IsOpen = true; + } + + public override void Draw() + { + var sticky = _ownPermissions.IsSticky(); + var paused = _ownPermissions.IsPaused(); + var disableSounds = _ownPermissions.IsDisableSounds(); + var disableAnimations = _ownPermissions.IsDisableAnimations(); + var disableVfx = _ownPermissions.IsDisableVFX(); + var style = ImGui.GetStyle(); + var indentSize = ImGui.GetFrameHeight() + style.ItemSpacing.X; + + _uiSharedService.BigText("Permissions for " + Pair.UserData.AliasOrUID); + ImGuiHelpers.ScaledDummy(1f); + + if (ImGui.Checkbox("Preferred Permissions", ref sticky)) + { + _ownPermissions.SetSticky(sticky); + } + UiSharedService.DrawHelpText("Preferred Permissions, when enabled, will exclude this user from any permission changes on any syncshells you share with this user."); + + ImGuiHelpers.ScaledDummy(1f); + + + if (ImGui.Checkbox("Pause Sync", ref paused)) + { + _ownPermissions.SetPaused(paused); + } + UiSharedService.DrawHelpText("Pausing will completely cease any sync with this user." + UiSharedService.TooltipSeparator + + "Note: this is bidirectional, either user pausing will cease sync completely."); + var otherPerms = Pair.UserPair.OtherPermissions; + + var otherIsPaused = otherPerms.IsPaused(); + var otherDisableSounds = otherPerms.IsDisableSounds(); + var otherDisableAnimations = otherPerms.IsDisableAnimations(); + var otherDisableVFX = otherPerms.IsDisableVFX(); + + using (ImRaii.PushIndent(indentSize, false)) + { + UiSharedService.BooleanToColoredIcon(!otherIsPaused, false); + ImGui.SameLine(); + ImGui.AlignTextToFramePadding(); + ImGui.Text(Pair.UserData.AliasOrUID + " has " + (!otherIsPaused ? "not " : string.Empty) + "paused you"); + } + + ImGuiHelpers.ScaledDummy(0.5f); + ImGui.Separator(); + ImGuiHelpers.ScaledDummy(0.5f); + + if (ImGui.Checkbox("Disable Sounds", ref disableSounds)) + { + _ownPermissions.SetDisableSounds(disableSounds); + } + UiSharedService.DrawHelpText("Disabling sounds will remove all sounds synced with this user on both sides." + UiSharedService.TooltipSeparator + + "Note: this is bidirectional, either user disabling sound sync will stop sound sync on both sides."); + using (ImRaii.PushIndent(indentSize, false)) + { + UiSharedService.BooleanToColoredIcon(!otherDisableSounds, false); + ImGui.SameLine(); + ImGui.AlignTextToFramePadding(); + ImGui.Text(Pair.UserData.AliasOrUID + " has " + (!otherDisableSounds ? "not " : string.Empty) + "disabled sound sync with you"); + } + + if (ImGui.Checkbox("Disable Animations", ref disableAnimations)) + { + _ownPermissions.SetDisableAnimations(disableAnimations); + } + UiSharedService.DrawHelpText("Disabling sounds will remove all animations synced with this user on both sides." + UiSharedService.TooltipSeparator + + "Note: this is bidirectional, either user disabling animation sync will stop animation sync on both sides."); + using (ImRaii.PushIndent(indentSize, false)) + { + UiSharedService.BooleanToColoredIcon(!otherDisableAnimations, false); + ImGui.SameLine(); + ImGui.AlignTextToFramePadding(); + ImGui.Text(Pair.UserData.AliasOrUID + " has " + (!otherDisableAnimations ? "not " : string.Empty) + "disabled animation sync with you"); + } + + if (ImGui.Checkbox("Disable VFX", ref disableVfx)) + { + _ownPermissions.SetDisableVFX(disableVfx); + } + UiSharedService.DrawHelpText("Disabling sounds will remove all VFX synced with this user on both sides." + UiSharedService.TooltipSeparator + + "Note: this is bidirectional, either user disabling VFX sync will stop VFX sync on both sides."); + using (ImRaii.PushIndent(indentSize, false)) + { + UiSharedService.BooleanToColoredIcon(!otherDisableVFX, false); + ImGui.SameLine(); + ImGui.AlignTextToFramePadding(); + ImGui.Text(Pair.UserData.AliasOrUID + " has " + (!otherDisableVFX ? "not " : string.Empty) + "disabled VFX sync with you"); + } + + ImGuiHelpers.ScaledDummy(0.5f); + ImGui.Separator(); + ImGuiHelpers.ScaledDummy(0.5f); + + bool hasChanges = _ownPermissions != Pair.UserPair.OwnPermissions; + + using (ImRaii.Disabled(!hasChanges)) + if (UiSharedService.NormalizedIconTextButton(Dalamud.Interface.FontAwesomeIcon.Save, "Save")) + { + _ = _apiController.SetBulkPermissions(new( + new(StringComparer.Ordinal) + { + { Pair.UserData.UID, _ownPermissions } + }, + new(StringComparer.Ordinal) + )); + } + UiSharedService.AttachToolTip("Save and apply all changes"); + + var rightSideButtons = UiSharedService.GetNormalizedIconTextButtonSize(Dalamud.Interface.FontAwesomeIcon.Undo, "Revert").X + + UiSharedService.GetNormalizedIconTextButtonSize(Dalamud.Interface.FontAwesomeIcon.ArrowsSpin, "Reset to Default Permissions").X; + var availableWidth = ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X; + + ImGui.SameLine(availableWidth - rightSideButtons); + + using (ImRaii.Disabled(!hasChanges)) + if (UiSharedService.NormalizedIconTextButton(Dalamud.Interface.FontAwesomeIcon.Undo, "Revert")) + { + _ownPermissions = Pair.UserPair.OwnPermissions.DeepClone(); + } + UiSharedService.AttachToolTip("Revert all changes"); + + ImGui.SameLine(); + if (UiSharedService.NormalizedIconTextButton(Dalamud.Interface.FontAwesomeIcon.ArrowsSpin, "Reset to Default Permissions")) + { + var defaultPermissions = _apiController.DefaultPermissions!; + _ownPermissions.SetSticky(Pair.IsDirectlyPaired || defaultPermissions.IndividualIsSticky); + _ownPermissions.SetPaused(false); + _ownPermissions.SetDisableVFX(Pair.IsDirectlyPaired ? defaultPermissions.DisableIndividualVFX : defaultPermissions.DisableGroupVFX); + _ownPermissions.SetDisableSounds(Pair.IsDirectlyPaired ? defaultPermissions.DisableIndividualSounds : defaultPermissions.DisableGroupSounds); + _ownPermissions.SetDisableAnimations(Pair.IsDirectlyPaired ? defaultPermissions.DisableIndividualAnimations : defaultPermissions.DisableGroupAnimations); + _ = _apiController.SetBulkPermissions(new( + new(StringComparer.Ordinal) + { + { Pair.UserData.UID, _ownPermissions } + }, + new(StringComparer.Ordinal) + )); + } + UiSharedService.AttachToolTip("This will set all permissions to your defined default permissions in the Mare Settings"); + + var ySize = ImGui.GetCursorPosY() + style.FramePadding.Y * ImGuiHelpers.GlobalScale + style.FrameBorderSize; + ImGui.SetWindowSize(new(400, ySize)); + } + + public override void OnClose() + { + Mediator.Publish(new RemoveWindowMessage(this)); + } +} diff --git a/MareSynchronos/UI/UISharedService.cs b/MareSynchronos/UI/UISharedService.cs index 4183180..ef497a3 100644 --- a/MareSynchronos/UI/UISharedService.cs +++ b/MareSynchronos/UI/UISharedService.cs @@ -134,6 +134,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) { ImGui.BeginTooltip(); + ImGui.PushTextWrapPos(ImGui.GetFontSize() * 35f); if (text.Contains(TooltipSeparator, StringComparison.Ordinal)) { var splitText = text.Split(TooltipSeparator, StringSplitOptions.RemoveEmptyEntries); @@ -147,6 +148,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase { ImGui.TextUnformatted(text); } + ImGui.PopTextWrapPos(); ImGui.EndTooltip(); } } @@ -219,20 +221,8 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase public static void DrawHelpText(string helpText) { ImGui.SameLine(); - using (ImRaii.PushFont(UiBuilder.IconFont)) - { - ImGui.SetWindowFontScale(0.8f); - ImGui.TextDisabled(FontAwesomeIcon.Question.ToIconString()); - ImGui.SetWindowFontScale(1.0f); - } - - if (ImGui.IsItemHovered()) - { - using var tooltip = ImRaii.Tooltip(); - ImGui.PushTextWrapPos(ImGui.GetFontSize() * 35.0f); - ImGui.TextUnformatted(helpText); - ImGui.PopTextWrapPos(); - } + NormalizedIcon(FontAwesomeIcon.QuestionCircle, ImGui.GetColorU32(ImGuiCol.TextDisabled)); + AttachToolTip(helpText); } public static void DrawOutlinedFont(string text, Vector4 fontColor, Vector4 outlineColor, int thickness) @@ -418,7 +408,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase return wasClicked; } - public static void NormalizedIcon(FontAwesomeIcon icon, Vector4? color = null) + public static void NormalizedIcon(FontAwesomeIcon icon, uint color) { var cursorPos = ImGui.GetCursorPos(); var iconData = GetIconData(icon); @@ -433,11 +423,16 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase drawList.AddText(UiBuilder.IconFont, ImGui.GetFontSize() * iconData.IconScaling, new(windowPos.X - scrollPosX + cursorPos.X + iconData.OffsetX, windowPos.Y - scrollPosY + cursorPos.Y + frameOffsetY), - color != null ? ImGui.GetColorU32(color.Value) : ImGui.GetColorU32(ImGuiCol.Text), icon.ToIconString()); + color, icon.ToIconString()); ImGui.Dummy(new(iconData.NormalizedIconScale.X, ImGui.GetFrameHeight())); } + public static void NormalizedIcon(FontAwesomeIcon icon, Vector4? color = null) + { + NormalizedIcon(icon, color == null ? ImGui.GetColorU32(ImGuiCol.Text) : ImGui.GetColorU32(color.Value)); + } + private static IconScaleData CalcIconScaleData(FontAwesomeIcon icon) { using var font = ImRaii.PushFont(UiBuilder.IconFont);