diff --git a/MareAPI b/MareAPI index 9199d9c..d04781f 160000 --- a/MareAPI +++ b/MareAPI @@ -1 +1 @@ -Subproject commit 9199d9c66721a225e32083e76c44df0f8fdabd01 +Subproject commit d04781fddf11e3cece03d5bdd7acf249c453664c diff --git a/MareSynchronos/Services/CommandManagerService.cs b/MareSynchronos/Services/CommandManagerService.cs index db9285e..1c2688a 100644 --- a/MareSynchronos/Services/CommandManagerService.cs +++ b/MareSynchronos/Services/CommandManagerService.cs @@ -100,7 +100,7 @@ public sealed class CommandManagerService : IDisposable } else if (string.Equals(splitArgs[0], "analyze", StringComparison.OrdinalIgnoreCase)) { - _mediator.Publish(new OpenDataAnalysisUiMessage()); + _mediator.Publish(new UiToggleMessage(typeof(DataAnalysisUi))); } } } \ No newline at end of file diff --git a/MareSynchronos/Services/Mediator/Messages.cs b/MareSynchronos/Services/Mediator/Messages.cs index 2901258..05d0a4a 100644 --- a/MareSynchronos/Services/Mediator/Messages.cs +++ b/MareSynchronos/Services/Mediator/Messages.cs @@ -15,7 +15,6 @@ namespace MareSynchronos.Services.Mediator; public record SwitchToIntroUiMessage : MessageBase; public record SwitchToMainUiMessage : MessageBase; public record OpenSettingsUiMessage : MessageBase; -public record OpenDataAnalysisUiMessage : MessageBase; public record DalamudLoginMessage : MessageBase; public record DalamudLogoutMessage : MessageBase; public record FrameworkUpdateMessage : SameThreadMessage; diff --git a/MareSynchronos/UI/CompactUI.cs b/MareSynchronos/UI/CompactUI.cs index 5a3035b..e6715f1 100644 --- a/MareSynchronos/UI/CompactUI.cs +++ b/MareSynchronos/UI/CompactUI.cs @@ -38,20 +38,20 @@ public class CompactUi : WindowMediatorSubscriberBase private readonly PairManager _pairManager; private readonly SelectTagForPairUi _selectGroupForPairUi; private readonly SelectPairForTagUi _selectPairsForGroupUi; + private readonly TopTabMenu _tabMenu; private readonly ServerConfigurationManager _serverManager; private readonly TagHandler _tagHandler; private readonly UiSharedService _uiShared; - private string _characterOrCommentFilter = string.Empty; private List _drawFolders; private Pair? _lastAddedUser; private string _lastAddedUserComment = string.Empty; private Vector2 _lastPosition = Vector2.One; private Vector2 _lastSize = Vector2.One; - private string _pairToAdd = string.Empty; private int _secretKeyIdx = -1; private bool _showModalForUserAddition; private bool _wasOpen; + public CompactUi(ILogger logger, UiSharedService uiShared, MareConfigService configService, ApiController apiController, PairManager pairManager, ServerConfigurationManager serverManager, MareMediator mediator, FileUploadManager fileTransferManager, TagHandler tagHandler, DrawEntityFactory drawEntityFactory, SelectTagForPairUi selectTagForPairUi, SelectPairForTagUi selectPairForTagUi) @@ -67,6 +67,7 @@ public class CompactUi : WindowMediatorSubscriberBase _drawEntityFactory = drawEntityFactory; _selectGroupForPairUi = selectTagForPairUi; _selectPairsForGroupUi = selectPairForTagUi; + _tabMenu = new TopTabMenu(Mediator, _apiController, _pairManager); _drawFolders = GetDrawFolders().ToList(); @@ -121,11 +122,12 @@ public class CompactUi : WindowMediatorSubscriberBase if (_apiController.ServerState is ServerState.Connected) { + UiSharedService.DrawWithID("global-topmenu", () => _tabMenu.Draw()); UiSharedService.DrawWithID("pairlist", DrawPairList); ImGui.Separator(); UiSharedService.DrawWithID("transfers", DrawTransfers); - TransferPartHeight = ImGui.GetCursorPosY() - TransferPartHeight; + TransferPartHeight = ImGui.GetCursorPosY() - TransferPartHeight - ImGui.GetTextLineHeight(); UiSharedService.DrawWithID("group-user-popup", () => _selectPairsForGroupUi.Draw(_pairManager.DirectPairs)); UiSharedService.DrawWithID("grouping-popup", () => _selectGroupForPairUi.Draw()); } @@ -171,6 +173,28 @@ public class CompactUi : WindowMediatorSubscriberBase } } + private void DrawPairList() + { + UiSharedService.DrawWithID("pairs", DrawPairs); + TransferPartHeight = ImGui.GetCursorPosY(); + } + + private void DrawPairs() + { + var ySize = TransferPartHeight == 0 + ? 1 + : (ImGui.GetWindowContentRegionMax().Y - ImGui.GetWindowContentRegionMin().Y) - TransferPartHeight - ImGui.GetCursorPosY(); + + ImGui.BeginChild("list", new Vector2(WindowContentWidth, ySize), border: false); + + foreach (var item in _drawFolders) + { + item.Draw(); + } + + ImGui.EndChild(); + } + private void DrawAddCharacter() { ImGui.Dummy(new(10)); @@ -200,93 +224,6 @@ public class CompactUi : WindowMediatorSubscriberBase } } - private void DrawAddPair() - { - var buttonSize = UiSharedService.GetIconButtonSize(FontAwesomeIcon.UserPlus); - var usersButtonSize = UiSharedService.GetIconButtonSize(FontAwesomeIcon.Users); - ImGui.SetNextItemWidth(UiSharedService.GetWindowContentRegionWidth() - ImGui.GetWindowContentRegionMin().X - buttonSize.X - ImGui.GetStyle().ItemSpacing.X - usersButtonSize.X); - ImGui.InputTextWithHint("##otheruid", "Other players UID/Alias", ref _pairToAdd, 20); - ImGui.SameLine(); - var alreadyExisting = _pairManager.DirectPairs.Exists(p => string.Equals(p.UserData.UID, _pairToAdd, StringComparison.Ordinal) || string.Equals(p.UserData.Alias, _pairToAdd, StringComparison.Ordinal)); - using (ImRaii.Disabled(alreadyExisting || string.IsNullOrEmpty(_pairToAdd))) - { - if (ImGuiComponents.IconButton(FontAwesomeIcon.UserPlus)) - { - _ = _apiController.UserAddPair(new(new(_pairToAdd))); - _pairToAdd = string.Empty; - } - UiSharedService.AttachToolTip("Pair with " + (_pairToAdd.IsNullOrEmpty() ? "other user" : _pairToAdd)); - } - - ImGui.SameLine(); - if (ImGuiComponents.IconButton(FontAwesomeIcon.Users)) - { - ImGui.OpenPopup("Syncshell Menu"); - } - UiSharedService.AttachToolTip("Syncshell Menu"); - - if (ImGui.BeginPopup("Syncshell Menu")) - { - using (ImRaii.Disabled(_pairManager.GroupPairs.Select(k => k.Key).Distinct() - .Count(g => string.Equals(g.OwnerUID, _apiController.UID, StringComparison.Ordinal)) >= _apiController.ServerInfo.MaxGroupsCreatedByUser)) - { - if (UiSharedService.IconTextButton(FontAwesomeIcon.Plus, "Create new Syncshell", _syncshellMenuSize, true)) - { - Mediator.Publish(new UiToggleMessage(typeof(CreateSyncshellUI))); - ImGui.CloseCurrentPopup(); - } - } - - using (ImRaii.Disabled(_pairManager.GroupPairs.Select(k => k.Key).Distinct().Count() >= _apiController.ServerInfo.MaxGroupsJoinedByUser)) - { - if (UiSharedService.IconTextButton(FontAwesomeIcon.Users, "Join existing Syncshell", _syncshellMenuSize, true)) - { - Mediator.Publish(new UiToggleMessage(typeof(JoinSyncshellUI))); - ImGui.CloseCurrentPopup(); - } - } - _syncshellMenuSize = ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X; - ImGui.EndPopup(); - } - - ImGuiHelpers.ScaledDummy(2); - } - - private float _syncshellMenuSize = 0; - - private void DrawFilter() - { - ImGui.SetNextItemWidth(WindowContentWidth); - if (ImGui.InputTextWithHint("##filter", "Filter for UID/notes", ref _characterOrCommentFilter, 255)) - { - Mediator.Publish(new RefreshUiMessage()); - } - } - - private void DrawPairList() - { - UiSharedService.DrawWithID("addpair", DrawAddPair); - UiSharedService.DrawWithID("pairs", DrawPairs); - TransferPartHeight = ImGui.GetCursorPosY(); - UiSharedService.DrawWithID("filter", DrawFilter); - } - - private void DrawPairs() - { - var ySize = TransferPartHeight == 0 - ? 1 - : (ImGui.GetWindowContentRegionMax().Y - ImGui.GetWindowContentRegionMin().Y) - TransferPartHeight - ImGui.GetCursorPosY(); - - ImGui.BeginChild("list", new Vector2(WindowContentWidth, ySize), border: false); - - foreach (var item in _drawFolders) - { - item.Draw(); - } - - ImGui.EndChild(); - } - private void DrawServerStatus() { var buttonSize = UiSharedService.GetIconButtonSize(FontAwesomeIcon.Link); @@ -331,16 +268,6 @@ public class CompactUi : WindowMediatorSubscriberBase var color = UiSharedService.GetBoolColor(!_serverManager.CurrentServer!.FullPause); var connectedIcon = !_serverManager.CurrentServer.FullPause ? FontAwesomeIcon.Link : FontAwesomeIcon.Unlink; - if (_apiController.ServerState is ServerState.Connected) - { - ImGui.SetCursorPosX(0 + ImGui.GetStyle().ItemSpacing.X); - if (ImGuiComponents.IconButton(FontAwesomeIcon.UserCircle)) - { - Mediator.Publish(new UiToggleMessage(typeof(EditProfileUi))); - } - UiSharedService.AttachToolTip("Edit your Mare Profile"); - } - ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth() - buttonSize.X); if (printShard) { @@ -412,13 +339,6 @@ public class CompactUi : WindowMediatorSubscriberBase { ImGui.TextUnformatted("No downloads in progress"); } - - if (UiSharedService.IconTextButton(FontAwesomeIcon.PersonCircleQuestion, "Mare Character Data Analysis", WindowContentWidth)) - { - Mediator.Publish(new OpenDataAnalysisUiMessage()); - } - - ImGui.SameLine(); } private void DrawUIDHeader() @@ -495,7 +415,7 @@ public class CompactUi : WindowMediatorSubscriberBase } List groupFolders = new(); - foreach (var group in _pairManager.GroupPairs.Select(g => g.Key).OrderBy(g => g.GroupAliasOrGID, StringComparer.Ordinal)) + foreach (var group in _pairManager.GroupPairs.Select(g => g.Key).OrderBy(g => g.GroupAliasOrGID, StringComparer.OrdinalIgnoreCase)) { var groupUsers2 = users.Where(v => v.Value.Exists(g => string.Equals(g.GID, group.GID, StringComparison.Ordinal)) && (v.Key.IsOnline || (!v.Key.IsOnline && !_configService.Current.ShowOfflineUsersSeparately) @@ -514,7 +434,7 @@ public class CompactUi : WindowMediatorSubscriberBase .ThenBy( u => _configService.Current.ShowCharacterNameInsteadOfNotesForVisible && !string.IsNullOrEmpty(u.Key.PlayerName) ? (_configService.Current.PreferNotesOverNamesForVisible ? u.Key.GetNote() : u.Key.PlayerName) - : (u.Key.GetNote() ?? u.Key.UserData.AliasOrUID), StringComparer.Ordinal) + : (u.Key.GetNote() ?? u.Key.UserData.AliasOrUID), StringComparer.OrdinalIgnoreCase) .ToDictionary(k => k.Key, k => k.Value); groupFolders.Add(_drawEntityFactory.CreateDrawGroupFolder(group, groupUsers2, @@ -590,14 +510,14 @@ public class CompactUi : WindowMediatorSubscriberBase private Dictionary> GetFilteredGroupUsers() { - if (string.IsNullOrEmpty(_characterOrCommentFilter)) return _pairManager.PairsWithGroups; + if (string.IsNullOrEmpty(_tabMenu.Filter)) return _pairManager.PairsWithGroups; return _pairManager.PairsWithGroups.Where(p => { - if (_characterOrCommentFilter.IsNullOrEmpty()) return true; - return p.Key.UserData.AliasOrUID.Contains(_characterOrCommentFilter, StringComparison.OrdinalIgnoreCase) || - (p.Key.GetNote()?.Contains(_characterOrCommentFilter, StringComparison.OrdinalIgnoreCase) ?? false) || - (p.Key.PlayerName?.Contains(_characterOrCommentFilter, StringComparison.OrdinalIgnoreCase) ?? false); + if (_tabMenu.Filter.IsNullOrEmpty()) return true; + return p.Key.UserData.AliasOrUID.Contains(_tabMenu.Filter, StringComparison.OrdinalIgnoreCase) || + (p.Key.GetNote()?.Contains(_tabMenu.Filter, StringComparison.OrdinalIgnoreCase) ?? false) || + (p.Key.PlayerName?.Contains(_tabMenu.Filter, StringComparison.OrdinalIgnoreCase) ?? false); }).ToDictionary(k => k.Key, k => k.Value); } diff --git a/MareSynchronos/UI/Components/DrawFolderTag.cs b/MareSynchronos/UI/Components/DrawFolderTag.cs index 42b4cec..45db8f2 100644 --- a/MareSynchronos/UI/Components/DrawFolderTag.cs +++ b/MareSynchronos/UI/Components/DrawFolderTag.cs @@ -164,21 +164,25 @@ public class DrawFolderTag : DrawFolderBase private void PauseRemainingPairs(IEnumerable availablePairs) { - foreach (var pairToPause in availablePairs.Where(pair => !pair.UserPair!.OwnPermissions.IsPaused())) + _ = _apiController.SetBulkPermissions(new(availablePairs + .ToDictionary(g => g.UID, g => { - var perm = pairToPause.UserPair!.OwnPermissions; + var perm = g.UserPair.OwnPermissions; perm.SetPaused(paused: true); - _ = _apiController.UserSetPairPermissions(new(new(pairToPause.UID), perm)); - } + return perm; + }, StringComparer.Ordinal), new(StringComparer.Ordinal))) + .ConfigureAwait(false); } private void ResumeAllPairs(IEnumerable availablePairs) { - foreach (var pairToPause in availablePairs) - { - var perm = pairToPause.UserPair!.OwnPermissions; - perm.SetPaused(paused: false); - _ = _apiController.UserSetPairPermissions(new(new(pairToPause.UID), perm)); - } + _ = _apiController.SetBulkPermissions(new(availablePairs + .ToDictionary(g => g.UID, g => + { + var perm = g.UserPair.OwnPermissions; + perm.SetPaused(paused: false); + return perm; + }, StringComparer.Ordinal), new(StringComparer.Ordinal))) + .ConfigureAwait(false); } } \ No newline at end of file diff --git a/MareSynchronos/UI/DataAnalysisUi.cs b/MareSynchronos/UI/DataAnalysisUi.cs index 10cf97a..eb17d8a 100644 --- a/MareSynchronos/UI/DataAnalysisUi.cs +++ b/MareSynchronos/UI/DataAnalysisUi.cs @@ -39,7 +39,6 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase { _hasUpdate = true; }); - Mediator.Subscribe(this, (_) => Toggle()); SizeConstraints = new() { MinimumSize = new() diff --git a/MareSynchronos/UI/JoinSyncshellUI.cs b/MareSynchronos/UI/JoinSyncshellUI.cs index 339baa9..2a4c911 100644 --- a/MareSynchronos/UI/JoinSyncshellUI.cs +++ b/MareSynchronos/UI/JoinSyncshellUI.cs @@ -70,7 +70,7 @@ internal class JoinSyncshellUI : WindowMediatorSubscriberBase ImGui.AlignTextToFramePadding(); ImGui.TextUnformatted("Syncshell Password"); ImGui.SameLine(200); - ImGui.InputTextWithHint("##syncshellpw", "Password", ref _syncshellPassword, 20, ImGuiInputTextFlags.Password); + ImGui.InputTextWithHint("##syncshellpw", "Password", ref _syncshellPassword, 50, ImGuiInputTextFlags.Password); using (ImRaii.Disabled(string.IsNullOrEmpty(_desiredSyncshellToJoin) || string.IsNullOrEmpty(_syncshellPassword))) { if (UiSharedService.IconTextButton(Dalamud.Interface.FontAwesomeIcon.Plus, "Join Syncshell")) diff --git a/MareSynchronos/UI/TopTabMenu.cs b/MareSynchronos/UI/TopTabMenu.cs new file mode 100644 index 0000000..d3d7db0 --- /dev/null +++ b/MareSynchronos/UI/TopTabMenu.cs @@ -0,0 +1,557 @@ +using Dalamud.Interface; +using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; +using Dalamud.Utility; +using ImGuiNET; +using MareSynchronos.API.Data.Enum; +using MareSynchronos.API.Data.Extensions; +using MareSynchronos.PlayerData.Pairs; +using MareSynchronos.Services.Mediator; +using MareSynchronos.WebAPI; +using System.Numerics; + +namespace MareSynchronos.UI; + +public class TopTabMenu +{ + private readonly ApiController _apiController; + + private readonly MareMediator _mareMediator; + + private readonly PairManager _pairManager; + + private int _globalControlCountdown = 0; + + private string _pairToAdd = string.Empty; + + private SelectedTab _selectedTab = SelectedTab.None; + + public TopTabMenu(MareMediator mareMediator, ApiController apiController, PairManager pairManager) + { + _mareMediator = mareMediator; + _apiController = apiController; + _pairManager = pairManager; + } + + private enum SelectedTab + { + None, + Individual, + Syncshell, + Filter, + UserConfig + } + public string Filter { get; private set; } = string.Empty; + + public void Draw() + { + var availableWidth = ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X; + var spacing = ImGui.GetStyle().ItemSpacing; + var buttonX = (availableWidth - (spacing.X * 3)) / 4f; + var buttonY = UiSharedService.GetIconButtonSize(FontAwesomeIcon.Pause).Y; + var buttonSize = new Vector2(buttonX, buttonY); + var drawList = ImGui.GetWindowDrawList(); + var underlineColor = ImGui.GetColorU32(ImGuiCol.Separator); + var btncolor = ImRaii.PushColor(ImGuiCol.Button, ImGui.ColorConvertFloat4ToU32(new(0, 0, 0, 0))); + + ImGuiHelpers.ScaledDummy(spacing.Y / 2f); + + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + var x = ImGui.GetCursorScreenPos(); + if (ImGui.Button(FontAwesomeIcon.User.ToIconString(), buttonSize)) + { + _selectedTab = _selectedTab == SelectedTab.Individual ? SelectedTab.None : SelectedTab.Individual; + } + ImGui.SameLine(); + var xAfter = ImGui.GetCursorScreenPos(); + if (_selectedTab == SelectedTab.Individual) + drawList.AddLine(x with { Y = x.Y + buttonSize.Y + spacing.Y }, + xAfter with { Y = xAfter.Y + buttonSize.Y + spacing.Y, X = xAfter.X - spacing.X }, + underlineColor, 2); + } + UiSharedService.AttachToolTip("Individual Pair Menu"); + + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + var x = ImGui.GetCursorScreenPos(); + if (ImGui.Button(FontAwesomeIcon.Users.ToIconString(), buttonSize)) + { + _selectedTab = _selectedTab == SelectedTab.Syncshell ? SelectedTab.None : SelectedTab.Syncshell; + } + ImGui.SameLine(); + var xAfter = ImGui.GetCursorScreenPos(); + if (_selectedTab == SelectedTab.Syncshell) + drawList.AddLine(x with { Y = x.Y + buttonSize.Y + spacing.Y }, + xAfter with { Y = xAfter.Y + buttonSize.Y + spacing.Y, X = xAfter.X - spacing.X }, + underlineColor, 2); + } + UiSharedService.AttachToolTip("Syncshell Menu"); + + ImGui.SameLine(); + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + var x = ImGui.GetCursorScreenPos(); + if (ImGui.Button(FontAwesomeIcon.Filter.ToIconString(), buttonSize)) + { + _selectedTab = _selectedTab == SelectedTab.Filter ? SelectedTab.None : SelectedTab.Filter; + } + + ImGui.SameLine(); + var xAfter = ImGui.GetCursorScreenPos(); + if (_selectedTab == SelectedTab.Filter) + drawList.AddLine(x with { Y = x.Y + buttonSize.Y + spacing.Y }, + xAfter with { Y = xAfter.Y + buttonSize.Y + spacing.Y, X = xAfter.X - spacing.X }, + underlineColor, 2); + } + UiSharedService.AttachToolTip("Filter"); + + ImGui.SameLine(); + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + var x = ImGui.GetCursorScreenPos(); + if (ImGui.Button(FontAwesomeIcon.UserCog.ToIconString(), buttonSize)) + { + _selectedTab = _selectedTab == SelectedTab.UserConfig ? SelectedTab.None : SelectedTab.UserConfig; + } + + ImGui.SameLine(); + var xAfter = ImGui.GetCursorScreenPos(); + if (_selectedTab == SelectedTab.UserConfig) + drawList.AddLine(x with { Y = x.Y + buttonSize.Y + spacing.Y }, + xAfter with { Y = xAfter.Y + buttonSize.Y + spacing.Y, X = xAfter.X - spacing.X }, + underlineColor, 2); + } + UiSharedService.AttachToolTip("Your User Menu"); + + ImGui.NewLine(); + btncolor.Dispose(); + + ImGuiHelpers.ScaledDummy(spacing); + + if (_selectedTab == SelectedTab.Individual) + { + DrawAddPair(availableWidth, spacing.X); + DrawGlobalIndividualButtons(availableWidth, spacing.X); + } + else if (_selectedTab == SelectedTab.Syncshell) + { + DrawSyncshellMenu(availableWidth, spacing.X); + DrawGlobalSyncshellButtons(availableWidth, spacing.X); + } + else if (_selectedTab == SelectedTab.Filter) + { + DrawFilter(availableWidth); + } + else if (_selectedTab == SelectedTab.UserConfig) + { + DrawUserConfig(availableWidth, spacing.X); + } + + if (_selectedTab != SelectedTab.None) ImGuiHelpers.ScaledDummy(3f); + ImGui.Separator(); + ImGuiHelpers.ScaledDummy(1f); + } + + private void DrawAddPair(float availableXWidth, float spacingX) + { + var buttonSize = UiSharedService.GetIconTextButtonSize(FontAwesomeIcon.UserPlus, "Add"); + ImGui.SetNextItemWidth(availableXWidth - buttonSize.X - spacingX); + ImGui.InputTextWithHint("##otheruid", "Other players UID/Alias", ref _pairToAdd, 20); + ImGui.SameLine(); + var alreadyExisting = _pairManager.DirectPairs.Exists(p => string.Equals(p.UserData.UID, _pairToAdd, StringComparison.Ordinal) || string.Equals(p.UserData.Alias, _pairToAdd, StringComparison.Ordinal)); + using (ImRaii.Disabled(alreadyExisting || string.IsNullOrEmpty(_pairToAdd))) + { + if (UiSharedService.IconTextButton(FontAwesomeIcon.UserPlus, "Add")) + { + _ = _apiController.UserAddPair(new(new(_pairToAdd))); + _pairToAdd = string.Empty; + } + } + UiSharedService.AttachToolTip("Pair with " + (_pairToAdd.IsNullOrEmpty() ? "other user" : _pairToAdd)); + } + + private void DrawFilter(float availableWidth) + { + ImGui.SetNextItemWidth(availableWidth); + string filter = Filter; + if (ImGui.InputTextWithHint("##filter", "Filter for UID/notes", ref filter, 255)) + { + Filter = filter; + _mareMediator.Publish(new RefreshUiMessage()); + } + } + + private void DrawGlobalIndividualButtons(float availableXWidth, float spacingX) + { + var buttonX = (availableXWidth - (spacingX * 3)) / 4f; + var buttonY = UiSharedService.GetIconButtonSize(FontAwesomeIcon.Pause).Y; + var buttonSize = new Vector2(buttonX, buttonY); + + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + using var disabled = ImRaii.Disabled(_globalControlCountdown > 0); + + if (ImGui.Button(FontAwesomeIcon.Pause.ToIconString(), buttonSize)) + { + ImGui.OpenPopup("Individual Pause"); + } + } + UiSharedService.AttachToolTip("Globally resume or pause all individual pairs." + UiSharedService.TooltipSeparator + + (_globalControlCountdown > 0 ? UiSharedService.TooltipSeparator + "Available again in " + _globalControlCountdown + " seconds." : string.Empty)); + + ImGui.SameLine(); + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + using var disabled = ImRaii.Disabled(_globalControlCountdown > 0); + + if (ImGui.Button(FontAwesomeIcon.VolumeUp.ToIconString(), buttonSize)) + { + ImGui.OpenPopup("Individual Sounds"); + } + } + UiSharedService.AttachToolTip("Globally enable or disable sound sync with all individual pairs." + + (_globalControlCountdown > 0 ? UiSharedService.TooltipSeparator + "Available again in " + _globalControlCountdown + " seconds." : string.Empty)); + + ImGui.SameLine(); + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + using var disabled = ImRaii.Disabled(_globalControlCountdown > 0); + + if (ImGui.Button(FontAwesomeIcon.Running.ToIconString(), buttonSize)) + { + ImGui.OpenPopup("Individual Animations"); + } + } + UiSharedService.AttachToolTip("Globally enable or disable animation sync with all individual pairs." + UiSharedService.TooltipSeparator + + (_globalControlCountdown > 0 ? UiSharedService.TooltipSeparator + "Available again in " + _globalControlCountdown + " seconds." : string.Empty)); + + ImGui.SameLine(); + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + using var disabled = ImRaii.Disabled(_globalControlCountdown > 0); + + if (ImGui.Button(FontAwesomeIcon.Sun.ToIconString(), buttonSize)) + { + ImGui.OpenPopup("Individual VFX"); + } + } + UiSharedService.AttachToolTip("Globally enable or disable VFX sync with all individual pairs." + UiSharedService.TooltipSeparator + + (_globalControlCountdown > 0 ? UiSharedService.TooltipSeparator + "Available again in " + _globalControlCountdown + " seconds." : string.Empty)); + + + PopupIndividualSetting("Individual Pause", "Unpause all individuals", "Pause all individuals", + FontAwesomeIcon.Play, FontAwesomeIcon.Pause, + (perm) => + { + perm.SetPaused(false); + return perm; + }, + (perm) => + { + perm.SetPaused(true); + return perm; + }); + PopupIndividualSetting("Individual Sounds", "Enable sounds for all individuals", "Disable sounds for all individuals", + FontAwesomeIcon.VolumeUp, FontAwesomeIcon.VolumeMute, + (perm) => + { + perm.SetDisableSounds(false); + return perm; + }, + (perm) => + { + perm.SetDisableSounds(true); + return perm; + }); + PopupIndividualSetting("Individual Animations", "Enable sounds for all individuals", "Disable sounds for all individuals", + FontAwesomeIcon.Running, FontAwesomeIcon.Stop, + (perm) => + { + perm.SetDisableAnimations(false); + return perm; + }, + (perm) => + { + perm.SetDisableAnimations(true); + return perm; + }); + PopupIndividualSetting("Individual VFX", "Enable VFX for all individuals", "Disable VFX for all individuals", + FontAwesomeIcon.Sun, FontAwesomeIcon.Circle, + (perm) => + { + perm.SetDisableVFX(false); + return perm; + }, + (perm) => + { + perm.SetDisableVFX(true); + return perm; + }); + } + + private void DrawGlobalSyncshellButtons(float availableXWidth, float spacingX) + { + var buttonX = (availableXWidth - (spacingX * 4)) / 5f; + var buttonY = UiSharedService.GetIconButtonSize(FontAwesomeIcon.Pause).Y; + var buttonSize = new Vector2(buttonX, buttonY); + + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + using var disabled = ImRaii.Disabled(_globalControlCountdown > 0); + + if (ImGui.Button(FontAwesomeIcon.Pause.ToIconString(), buttonSize)) + { + ImGui.OpenPopup("Syncshell Pause"); + } + } + UiSharedService.AttachToolTip("Globally resume or pause all syncshells." + UiSharedService.TooltipSeparator + + "Note: This will not affect users with preferred permissions in syncshells." + + (_globalControlCountdown > 0 ? UiSharedService.TooltipSeparator + "Available again in " + _globalControlCountdown + " seconds." : string.Empty)); + + ImGui.SameLine(); + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + using var disabled = ImRaii.Disabled(_globalControlCountdown > 0); + + if (ImGui.Button(FontAwesomeIcon.VolumeUp.ToIconString(), buttonSize)) + { + ImGui.OpenPopup("Syncshell Sounds"); + } + } + UiSharedService.AttachToolTip("Globally enable or disable sound sync with all syncshells." + UiSharedService.TooltipSeparator + + "Note: This will not affect users with preferred permissions in syncshells." + + (_globalControlCountdown > 0 ? UiSharedService.TooltipSeparator + "Available again in " + _globalControlCountdown + " seconds." : string.Empty)); + + ImGui.SameLine(); + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + using var disabled = ImRaii.Disabled(_globalControlCountdown > 0); + + if (ImGui.Button(FontAwesomeIcon.Running.ToIconString(), buttonSize)) + { + ImGui.OpenPopup("Syncshell Animations"); + } + } + UiSharedService.AttachToolTip("Globally enable or disable animation sync with all syncshells." + UiSharedService.TooltipSeparator + + "Note: This will not affect users with preferred permissions in syncshells." + + (_globalControlCountdown > 0 ? UiSharedService.TooltipSeparator + "Available again in " + _globalControlCountdown + " seconds." : string.Empty)); + + ImGui.SameLine(); + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + using var disabled = ImRaii.Disabled(_globalControlCountdown > 0); + + if (ImGui.Button(FontAwesomeIcon.Sun.ToIconString(), buttonSize)) + { + ImGui.OpenPopup("Syncshell VFX"); + } + } + UiSharedService.AttachToolTip("Globally enable or disable VFX sync with all syncshells." + UiSharedService.TooltipSeparator + + "Note: This will not affect users with preferred permissions in syncshells." + + (_globalControlCountdown > 0 ? UiSharedService.TooltipSeparator + "Available again in " + _globalControlCountdown + " seconds." : string.Empty)); + + + PopupSyncshellSetting("Syncshell Pause", "Unpause all syncshells", "Pause all syncshells", + FontAwesomeIcon.Play, FontAwesomeIcon.Pause, + (perm) => + { + perm.SetPaused(false); + return perm; + }, + (perm) => + { + perm.SetPaused(true); + return perm; + }); + PopupSyncshellSetting("Syncshell Sounds", "Enable sounds for all syncshells", "Disable sounds for all syncshells", + FontAwesomeIcon.VolumeUp, FontAwesomeIcon.VolumeMute, + (perm) => + { + perm.SetDisableSounds(false); + return perm; + }, + (perm) => + { + perm.SetDisableSounds(true); + return perm; + }); + PopupSyncshellSetting("Syncshell Animations", "Enable sounds for all syncshells", "Disable sounds for all syncshells", + FontAwesomeIcon.Running, FontAwesomeIcon.Stop, + (perm) => + { + perm.SetDisableAnimations(false); + return perm; + }, + (perm) => + { + perm.SetDisableAnimations(true); + return perm; + }); + PopupSyncshellSetting("Syncshell VFX", "Enable VFX for all syncshells", "Disable VFX for all syncshells", + FontAwesomeIcon.Sun, FontAwesomeIcon.Circle, + (perm) => + { + perm.SetDisableVFX(false); + return perm; + }, + (perm) => + { + perm.SetDisableVFX(true); + return perm; + }); + + ImGui.SameLine(); + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + using var disabled = ImRaii.Disabled(_globalControlCountdown > 0); + + if (ImGui.Button(FontAwesomeIcon.Check.ToIconString(), buttonSize)) + { + _ = GlobalControlCountdown(10); + var bulkSyncshells = _pairManager.GroupPairs.Keys.OrderBy(g => g.GroupAliasOrGID, StringComparer.OrdinalIgnoreCase) + .ToDictionary(g => g.Group.GID, g => + { + var perm = g.GroupUserPermissions; + perm.SetDisableSounds(g.GroupPermissions.IsPreferDisableSounds()); + perm.SetDisableAnimations(g.GroupPermissions.IsPreferDisableAnimations()); + perm.SetDisableVFX(g.GroupPermissions.IsPreferDisableVFX()); + return perm; + }, StringComparer.Ordinal); + + _ = _apiController.SetBulkPermissions(new(new(StringComparer.Ordinal), bulkSyncshells)).ConfigureAwait(false); + } + } + UiSharedService.AttachToolTip("Globally align syncshell permissions to suggested syncshell permissions." + UiSharedService.TooltipSeparator + + "Note: This will not affect users with preferred permissions in syncshells." + Environment.NewLine + + "Note: If multiple users share one syncshell the permissions to that user will be set to " + Environment.NewLine + + "the ones of the last applied syncshell in alphabetical order." + + (_globalControlCountdown > 0 ? UiSharedService.TooltipSeparator + "Available again in " + _globalControlCountdown + " seconds." : string.Empty)); + } + + private void DrawSyncshellMenu(float availableWidth, float spacingX) + { + var buttonX = (availableWidth - (spacingX)) / 2f; + + using (ImRaii.Disabled(_pairManager.GroupPairs.Select(k => k.Key).Distinct() + .Count(g => string.Equals(g.OwnerUID, _apiController.UID, StringComparison.Ordinal)) >= _apiController.ServerInfo.MaxGroupsCreatedByUser)) + { + if (UiSharedService.IconTextButton(FontAwesomeIcon.Plus, "Create new Syncshell", buttonX)) + { + _mareMediator.Publish(new UiToggleMessage(typeof(CreateSyncshellUI))); + } + ImGui.SameLine(); + } + + using (ImRaii.Disabled(_pairManager.GroupPairs.Select(k => k.Key).Distinct().Count() >= _apiController.ServerInfo.MaxGroupsJoinedByUser)) + { + if (UiSharedService.IconTextButton(FontAwesomeIcon.Users, "Join existing Syncshell", buttonX)) + { + _mareMediator.Publish(new UiToggleMessage(typeof(JoinSyncshellUI))); + } + } + } + + private void DrawUserConfig(float availableWidth, float spacingX) + { + var buttonX = (availableWidth - spacingX) / 2f; + if (UiSharedService.IconTextButton(FontAwesomeIcon.UserCircle, "Edit Mare Profile", buttonX)) + { + _mareMediator.Publish(new UiToggleMessage(typeof(EditProfileUi))); + } + UiSharedService.AttachToolTip("Edit your Mare Profile"); + ImGui.SameLine(); + if (UiSharedService.IconTextButton(FontAwesomeIcon.PersonCircleQuestion, "Chara Data Analysis", buttonX)) + { + _mareMediator.Publish(new UiToggleMessage(typeof(DataAnalysisUi))); + } + UiSharedService.AttachToolTip("View and analyze your generated character data"); + } + + private async Task GlobalControlCountdown(int countdown) + { +#if DEBUG + return; +#endif + + _globalControlCountdown = countdown; + while (_globalControlCountdown > 0) + { + await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); + _globalControlCountdown--; + } + } + + private void PopupIndividualSetting(string popupTitle, string enableText, string disableText, + FontAwesomeIcon enableIcon, FontAwesomeIcon disableIcon, + Func actEnable, Func actDisable) + { + if (ImGui.BeginPopup(popupTitle)) + { + if (UiSharedService.IconTextButton(enableIcon, enableText, 0, true)) + { + _ = GlobalControlCountdown(10); + var bulkIndividualPairs = _pairManager.PairsWithGroups.Keys + .Where(g => g.IndividualPairStatus == API.Data.Enum.IndividualPairStatus.Bidirectional) + .ToDictionary(g => g.UserPair.User.UID, g => + { + return actEnable(g.UserPair.OwnPermissions); + }, StringComparer.Ordinal); + + _ = _apiController.SetBulkPermissions(new(bulkIndividualPairs, new(StringComparer.Ordinal))).ConfigureAwait(false); + ImGui.CloseCurrentPopup(); + } + + if (UiSharedService.IconTextButton(disableIcon, disableText, 0, true)) + { + _ = GlobalControlCountdown(10); + var bulkIndividualPairs = _pairManager.PairsWithGroups.Keys + .Where(g => g.IndividualPairStatus == API.Data.Enum.IndividualPairStatus.Bidirectional) + .ToDictionary(g => g.UserPair.User.UID, g => + { + return actDisable(g.UserPair.OwnPermissions); + }, StringComparer.Ordinal); + + _ = _apiController.SetBulkPermissions(new(bulkIndividualPairs, new(StringComparer.Ordinal))).ConfigureAwait(false); + ImGui.CloseCurrentPopup(); + } + ImGui.EndPopup(); + } + } + private void PopupSyncshellSetting(string popupTitle, string enableText, string disableText, + FontAwesomeIcon enableIcon, FontAwesomeIcon disableIcon, + Func actEnable, + Func actDisable) + { + if (ImGui.BeginPopup(popupTitle)) + { + + if (UiSharedService.IconTextButton(enableIcon, enableText, 0, true)) + { + _ = GlobalControlCountdown(10); + var bulkSyncshells = _pairManager.GroupPairs.Keys + .ToDictionary(g => g.Group.GID, g => + { + return actEnable(g.GroupUserPermissions); + }, StringComparer.Ordinal); + + _ = _apiController.SetBulkPermissions(new(new(StringComparer.Ordinal), bulkSyncshells)).ConfigureAwait(false); + ImGui.CloseCurrentPopup(); + } + + if (UiSharedService.IconTextButton(disableIcon, disableText, 0, true)) + { + _ = GlobalControlCountdown(10); + var bulkSyncshells = _pairManager.GroupPairs.Keys + .ToDictionary(g => g.Group.GID, g => + { + return actDisable(g.GroupUserPermissions); + }, StringComparer.Ordinal); + + _ = _apiController.SetBulkPermissions(new(new(StringComparer.Ordinal), bulkSyncshells)).ConfigureAwait(false); + ImGui.CloseCurrentPopup(); + } + ImGui.EndPopup(); + } + } +} diff --git a/MareSynchronos/UI/UISharedService.cs b/MareSynchronos/UI/UISharedService.cs index 341e7b8..a75ef59 100644 --- a/MareSynchronos/UI/UISharedService.cs +++ b/MareSynchronos/UI/UISharedService.cs @@ -133,7 +133,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase public static void AttachToolTip(string text) { - if (ImGui.IsItemHovered()) + if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) { ImGui.BeginTooltip(); if (text.Contains(TooltipSeparator, StringComparison.Ordinal)) @@ -349,6 +349,31 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase return ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X; } + public static Vector2 GetIconTextButtonSize(FontAwesomeIcon icon, string text, float? width = null, bool isInPopup = false) + { + var iconSize = GetIconSize(icon); + var textSize = ImGui.CalcTextSize(text); + var padding = ImGui.GetStyle().FramePadding; + var spacing = ImGui.GetStyle().ItemSpacing; + + var buttonSizeY = textSize.Y + padding.Y * 2; + var iconExtraSpacing = isInPopup ? padding.X * 2 : 0; + + var iconXoffset = iconSize.X <= iconSize.Y ? (iconSize.Y - iconSize.X) / 2f : 0; + var iconScaling = iconSize.X > iconSize.Y ? 1 / (iconSize.X / iconSize.Y) : 1; + + if (width == null || width <= 0) + { + var buttonSizeX = (iconScaling == 1 ? iconSize.Y : (iconSize.X * iconScaling)) + + textSize.X + padding.X * 2 + spacing.X + (iconXoffset * 2); + return new Vector2(buttonSizeX + iconExtraSpacing, buttonSizeY); + } + else + { + return new Vector2(width.Value, buttonSizeY); + } + } + public static bool IconTextButton(FontAwesomeIcon icon, string text, float? width = null, bool isInPopup = false) { var wasClicked = false; diff --git a/MareSynchronos/WebAPI/SignalR/ApIController.Functions.Users.cs b/MareSynchronos/WebAPI/SignalR/ApIController.Functions.Users.cs index 1ae9602..a552834 100644 --- a/MareSynchronos/WebAPI/SignalR/ApIController.Functions.Users.cs +++ b/MareSynchronos/WebAPI/SignalR/ApIController.Functions.Users.cs @@ -69,6 +69,20 @@ public partial class ApiController } } + public async Task SetBulkPermissions(BulkPermissionsDto dto) + { + CheckConnection(); + + try + { + await _mareHub!.InvokeAsync(nameof(SetBulkPermissions), dto).ConfigureAwait(false); + } + catch (Exception ex) + { + Logger.LogWarning(ex, "Failed to set permissions"); + } + } + public async Task UserRemovePair(UserDto userDto) { if (!IsConnected) return; @@ -83,7 +97,10 @@ public partial class ApiController public async Task UserSetPairPermissions(UserPermissionsDto userPermissions) { - await _mareHub!.SendAsync(nameof(UserSetPairPermissions), userPermissions).ConfigureAwait(false); + await SetBulkPermissions(new(new(StringComparer.Ordinal) + { + { userPermissions.User.UID, userPermissions.Permissions } + }, new(StringComparer.Ordinal))).ConfigureAwait(false); } public async Task UserSetProfile(UserProfileDto userDescription) diff --git a/MareSynchronos/WebAPI/SignalR/ApiController.Functions.Groups.cs b/MareSynchronos/WebAPI/SignalR/ApiController.Functions.Groups.cs index 69ea973..5c3cb31 100644 --- a/MareSynchronos/WebAPI/SignalR/ApiController.Functions.Groups.cs +++ b/MareSynchronos/WebAPI/SignalR/ApiController.Functions.Groups.cs @@ -21,7 +21,10 @@ public partial class ApiController public async Task GroupChangeIndividualPermissionState(GroupPairUserPermissionDto dto) { CheckConnection(); - await _mareHub!.SendAsync(nameof(GroupChangeIndividualPermissionState), dto).ConfigureAwait(false); + await SetBulkPermissions(new(new(StringComparer.Ordinal), + new(StringComparer.Ordinal) { + { dto.Group.GID, dto.GroupPairPermissions } + })).ConfigureAwait(false); } public async Task GroupChangeOwnership(GroupPairDto groupPair)