diff --git a/LightlessSync/UI/SyncshellAdminUI.cs b/LightlessSync/UI/SyncshellAdminUI.cs index 3db3682..a60924c 100644 --- a/LightlessSync/UI/SyncshellAdminUI.cs +++ b/LightlessSync/UI/SyncshellAdminUI.cs @@ -14,6 +14,7 @@ using LightlessSync.Services.Profiles; using LightlessSync.UI.Services; using LightlessSync.WebAPI; using Microsoft.Extensions.Logging; +using SharpDX.DirectWrite; using SixLabors.ImageSharp; using System.Globalization; using System.Numerics; @@ -556,7 +557,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase + UiSharedService.TooltipSeparator + "Hold CTRL to enable this button"); ImGuiHelpers.ScaledDummy(2f); - UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.0f); + UiSharedService.ColoredSeparator(UIColors.Get("DimRed"), 1.0f); ImGuiHelpers.ScaledDummy(2f); if (_uiSharedService.IconTextButton(FontAwesomeIcon.Unlink, "Check for Inactive Users")) @@ -621,7 +622,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase } ImGuiHelpers.ScaledDummy(4f); - UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.0f); + UiSharedService.ColoredSeparator(UIColors.Get("DimRed"), 1.0f); ImGuiHelpers.ScaledDummy(2f); DrawAutoPruneSettings(); @@ -636,50 +637,28 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase { _bannedUsers = _apiController.GroupGetBannedUsers(new GroupDto(GroupFullInfo.Group)).Result; } + ImGuiHelpers.ScaledDummy(2f); - var tableFlags = ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingStretchProp; - if (_bannedUsers.Count > 10) - tableFlags |= ImGuiTableFlags.ScrollY; + ImGui.BeginChild("bannedListScroll#" + GroupFullInfo.GID, new Vector2(0, 0), true); - if (ImGui.BeginTable("bannedusertable" + GroupFullInfo.GID, 6, tableFlags)) + var style = ImGui.GetStyle(); + float fullW = ImGui.GetContentRegionAvail().X; + + float colIdentity = fullW * 0.45f; + float colMeta = fullW * 0.35f; + float colActions = fullW - colIdentity - colMeta - style.ItemSpacing.X * 2.0f; + + // Header + DrawBannedListHeader(colIdentity, colMeta); + + int rowIndex = 0; + foreach (var bannedUser in _bannedUsers.ToList()) { - ImGui.TableSetupColumn("UID", ImGuiTableColumnFlags.None, 1); - ImGui.TableSetupColumn("Alias", ImGuiTableColumnFlags.None, 1); - ImGui.TableSetupColumn("By", ImGuiTableColumnFlags.None, 1); - ImGui.TableSetupColumn("Date", ImGuiTableColumnFlags.None, 2); - ImGui.TableSetupColumn("Reason", ImGuiTableColumnFlags.None, 3); - ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.None, 1); - - ImGui.TableHeadersRow(); - - foreach (var bannedUser in _bannedUsers.ToList()) - { - ImGui.TableNextColumn(); - ImGui.TextUnformatted(bannedUser.UID); - - ImGui.TableNextColumn(); - ImGui.TextUnformatted(bannedUser.UserAlias ?? string.Empty); - - ImGui.TableNextColumn(); - ImGui.TextUnformatted(bannedUser.BannedBy); - - ImGui.TableNextColumn(); - ImGui.TextUnformatted(bannedUser.BannedOn.ToLocalTime().ToString(CultureInfo.CurrentCulture)); - - ImGui.TableNextColumn(); - UiSharedService.TextWrapped(bannedUser.Reason); - - ImGui.TableNextColumn(); - using var _ = ImRaii.PushId(bannedUser.UID); - if (_uiSharedService.IconTextButton(FontAwesomeIcon.Check, "Unban")) - { - _apiController.GroupUnbanUser(bannedUser); - _bannedUsers.RemoveAll(b => string.Equals(b.UID, bannedUser.UID, StringComparison.Ordinal)); - } - } - - ImGui.EndTable(); + // Each row + DrawBannedRow(bannedUser, rowIndex++, colIdentity, colMeta, colActions); } + + ImGui.EndChild(); } private void DrawInvites(GroupPermissions perm) @@ -729,7 +708,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase private void DrawUserListCustom(IReadOnlyList pairs, GroupFullInfoDto GroupFullInfo) { - // Search bar (unchanged) + // Search bar ImGui.PushItemWidth(0); _uiSharedService.IconText(FontAwesomeIcon.Search, UIColors.Get("LightlessPurple")); ImGui.SameLine(); @@ -768,78 +747,105 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase .ThenBy(p => p.Key.GetNote() ?? p.Key.UserData.AliasOrUID, StringComparer.OrdinalIgnoreCase) .ToList(); + DrawUserListHeader(); + ImGui.BeginChild("userListScroll#" + GroupFullInfo.Group.AliasOrGID, new Vector2(0, 0), true); - var style = ImGui.GetStyle(); - float fullW = ImGui.GetContentRegionAvail().X; - - float colUid = fullW * 0.50f; - float colFlags = fullW * 0.10f; - float colActions = fullW - colUid - colFlags - style.ItemSpacing.X * 2.0f; - - DrawUserListHeader(colUid, colFlags); - int rowIndex = 0; foreach (var kv in orderedPairs) { var pair = kv.Key; var userInfoOpt = kv.Value; - DrawUserRowCustom(pair, userInfoOpt, GroupFullInfo, rowIndex++, colUid, colFlags, colActions); + DrawUserRowCustom(pair, userInfoOpt, GroupFullInfo, rowIndex++); } ImGui.EndChild(); } - private static void DrawUserListHeader(float colUid, float colFlags) + private static void DrawUserListHeader() { var style = ImGui.GetStyle(); float x0 = ImGui.GetCursorPosX(); + float fullW = ImGui.GetContentRegionAvail().X; ImGui.PushStyleColor(ImGuiCol.Text, UIColors.Get("LightlessPurple")); - // Alias / UID / Note ImGui.SetCursorPosX(x0); - ImGui.TextUnformatted("Alias / UID / Note"); + ImGui.TextUnformatted("User"); - // Flags - ImGui.SameLine(); - ImGui.SetCursorPosX(x0 + colUid + style.ItemSpacing.X); - ImGui.TextUnformatted("Flags"); + const string actionsLabel = "Actions"; + float labelWidth = ImGui.CalcTextSize(actionsLabel).X; - // Actions ImGui.SameLine(); - ImGui.SetCursorPosX(x0 + colUid + colFlags + style.ItemSpacing.X * 2.0f); - ImGui.TextUnformatted("Actions"); + ImGui.SetCursorPosX(x0 + fullW - labelWidth); + ImGui.TextUnformatted(actionsLabel); ImGui.PopStyleColor(); UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.0f); } - private void DrawUserRowCustom(Pair pair, GroupPairUserInfo? userInfoOpt, GroupFullInfoDto GroupFullInfo, int rowIndex, float colUid, float colFlags, float colActions) + private void DrawUserRowCustom(Pair pair, GroupPairUserInfo? userInfoOpt, GroupFullInfoDto GroupFullInfo, int rowIndex) { using var id = ImRaii.PushId("userRow_" + pair.UserData.UID); var style = ImGui.GetStyle(); - float x0 = ImGui.GetCursorPosX(); + Vector2 rowStart = ImGui.GetCursorPos(); + Vector2 rowStartScr = ImGui.GetCursorScreenPos(); + float fullW = ImGui.GetContentRegionAvail().X; + + float frameH = ImGui.GetFrameHeight(); + float textH = ImGui.GetTextLineHeight(); + float rowHeight = frameH; if (rowIndex % 2 == 0) { var drawList = ImGui.GetWindowDrawList(); - var pMin = ImGui.GetCursorScreenPos(); - var rowHeight = ImGui.GetTextLineHeightWithSpacing() * 2.5f; - var pMax = new Vector2(pMin.X + colUid + colFlags + colActions + style.ItemSpacing.X * 2.0f, - pMin.Y + rowHeight); + var pMin = rowStartScr; + var pMax = new Vector2(pMin.X + fullW, pMin.Y + rowHeight); - var bgColor = UIColors.Get("FullBlack") with { W = 0.0f }; + var bgColor = UIColors.Get("FullBlack").WithAlpha(0.05f); drawList.AddRectFilled(pMin, pMax, ImGui.ColorConvertFloat4ToU32(bgColor)); } var isUserOwner = string.Equals(pair.UserData.UID, GroupFullInfo.OwnerUID, StringComparison.Ordinal); var userInfo = userInfoOpt ?? GroupPairUserInfo.None; - ImGui.SetCursorPosX(x0); - ImGui.AlignTextToFramePadding(); + float baselineY = rowStart.Y + (rowHeight - textH) / 2f; + + ImGui.SetCursorPos(new Vector2(rowStart.X, baselineY)); + + bool hasFlag = false; + if (userInfoOpt != null && (userInfo.IsModerator() || userInfo.IsPinned() || isUserOwner)) + { + if (userInfo.IsModerator()) + { + _uiSharedService.IconText(FontAwesomeIcon.UserShield, UIColors.Get("LightlessPurple")); + UiSharedService.AttachToolTip("Moderator"); + hasFlag = true; + } + + if (userInfo.IsPinned() && !isUserOwner) + { + if (hasFlag) ImGui.SameLine(0f, style.ItemSpacing.X); + _uiSharedService.IconText(FontAwesomeIcon.Thumbtack); + UiSharedService.AttachToolTip("Pinned"); + hasFlag = true; + } + + if (isUserOwner) + { + if (hasFlag) ImGui.SameLine(0f, style.ItemSpacing.X); + _uiSharedService.IconText(FontAwesomeIcon.Crown, UIColors.Get("LightlessYellow")); + UiSharedService.AttachToolTip("Owner"); + hasFlag = true; + } + } + + if (hasFlag) + ImGui.SameLine(0f, style.ItemSpacing.X); + + ImGui.SetCursorPosY(baselineY); var note = pair.GetNote(); var text = note == null @@ -848,55 +854,47 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase var boolcolor = UiSharedService.GetBoolColor(pair.IsOnline); UiSharedService.ColorText(text, boolcolor); + if (ImGui.IsItemClicked()) - { ImGui.SetClipboardText(text); - } if (!string.IsNullOrEmpty(pair.PlayerName)) - { UiSharedService.AttachToolTip(pair.PlayerName); - } - ImGui.SameLine(); - ImGui.SetCursorPosX(x0 + colUid + style.ItemSpacing.X); + DrawUserActions(pair, GroupFullInfo, userInfo, isUserOwner, baselineY); - if (userInfoOpt != null && (userInfo.IsModerator() || userInfo.IsPinned() || isUserOwner)) - { - if (userInfo.IsModerator()) - { - _uiSharedService.IconText(FontAwesomeIcon.UserShield, UIColors.Get("LightlessPurple")); - UiSharedService.AttachToolTip("Moderator"); - } - if (userInfo.IsPinned() && !isUserOwner) - { - _uiSharedService.IconText(FontAwesomeIcon.Thumbtack); - UiSharedService.AttachToolTip("Pinned"); - } - if (isUserOwner) - { - _uiSharedService.IconText(FontAwesomeIcon.Crown, UIColors.Get("LightlessYellow")); - UiSharedService.AttachToolTip("Owner"); - } - } - else - { - _uiSharedService.IconText(FontAwesomeIcon.None); - } - - ImGui.SameLine(); - ImGui.SetCursorPosX(x0 + colUid + colFlags + style.ItemSpacing.X * 2.0f); - - DrawUserActions(pair, GroupFullInfo, userInfo, isUserOwner); - - ImGui.Dummy(new Vector2(0, 4 * ImGuiHelpers.GlobalScale)); + // Move cursor to next row + ImGui.SetCursorPos(new Vector2(rowStart.X, rowStart.Y + rowHeight + style.ItemSpacing.Y)); } - private void DrawUserActions(Pair pair, GroupFullInfoDto GroupFullInfo, GroupPairUserInfo userInfo, bool isUserOwner) + private void DrawUserActions(Pair pair, GroupFullInfoDto GroupFullInfo, GroupPairUserInfo userInfo, bool isUserOwner, float baselineY) { + var style = ImGui.GetStyle(); + float frameH = ImGui.GetFrameHeight(); + + int buttonCount = 0; + if (_isOwner) + buttonCount += 2; // Crown + Mod + if (userInfo == GroupPairUserInfo.None || (!userInfo.IsModerator() && !isUserOwner)) + buttonCount += 3; // Pin + Trash + Ban + + if (buttonCount == 0) + return; + + float totalWidth = buttonCount * frameH + (buttonCount - 1) * style.ItemSpacing.X; + + float curX = ImGui.GetCursorPosX(); + float avail = ImGui.GetContentRegionAvail().X; + + float startX = curX + MathF.Max(0, avail - (totalWidth + 30f)); + + ImGui.SetCursorPos(new Vector2(startX, baselineY)); + + bool first = true; + if (_isOwner) { - // Transfer ownership to user + // Transfer ownership using (ImRaii.PushColor(ImGuiCol.Text, UIColors.Get("LightlessYellow"))) using (ImRaii.Disabled(!UiSharedService.ShiftPressed())) { @@ -910,12 +908,15 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase UiSharedService.AttachToolTip("Hold SHIFT and click to transfer ownership of this Syncshell to " + pair.UserData.AliasOrUID + Environment.NewLine + "WARNING: This action is irreversible and will close screen."); - ImGui.SameLine(); - // Mod / Demod user + first = false; + + // Mod / Demod using (ImRaii.PushColor(ImGuiCol.Text, userInfo.IsModerator() ? UIColors.Get("DimRed") : UIColors.Get("PairBlue"))) { + if (!first) ImGui.SameLine(0f, style.ItemSpacing.X); + if (_uiSharedService.IconButton(FontAwesomeIcon.UserShield)) { userInfo.SetModerator(!userInfo.IsModerator()); @@ -926,15 +927,17 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase UiSharedService.AttachToolTip( userInfo.IsModerator() ? $"Demod {pair.UserData.AliasOrUID}" : $"Mod {pair.UserData.AliasOrUID}"); - ImGui.SameLine(); + first = false; } if (userInfo == GroupPairUserInfo.None || (!userInfo.IsModerator() && !isUserOwner)) { - // Pin user + // Pin using (ImRaii.PushColor(ImGuiCol.Text, userInfo.IsPinned() ? UIColors.Get("DimRed") : UIColors.Get("PairBlue"))) { + if (!first) ImGui.SameLine(0f, style.ItemSpacing.X); + if (_uiSharedService.IconButton(FontAwesomeIcon.Thumbtack)) { userInfo.SetPinned(!userInfo.IsPinned()); @@ -946,12 +949,14 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase UiSharedService.AttachToolTip( userInfo.IsPinned() ? $"Unpin {pair.UserData.AliasOrUID}" : $"Pin {pair.UserData.AliasOrUID}"); - ImGui.SameLine(); + first = false; - // Remove user + // Trash using (ImRaii.PushColor(ImGuiCol.Text, UIColors.Get("DimRed"))) using (ImRaii.Disabled(!UiSharedService.CtrlPressed())) { + if (!first) ImGui.SameLine(0f, style.ItemSpacing.X); + if (_uiSharedService.IconButton(FontAwesomeIcon.Trash)) { _ = _apiController.GroupRemoveUser(new GroupPairDto(GroupFullInfo.Group, pair.UserData)); @@ -960,12 +965,14 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase UiSharedService.AttachToolTip($"Remove {pair.UserData.AliasOrUID} from Syncshell" + UiSharedService.TooltipSeparator + "Hold CTRL to enable this button"); - ImGui.SameLine(); + first = false; - // Ban user + // Ban using (ImRaii.PushColor(ImGuiCol.Text, UIColors.Get("DimRed"))) using (ImRaii.Disabled(!UiSharedService.CtrlPressed())) { + if (!first) ImGui.SameLine(0f, style.ItemSpacing.X); + if (_uiSharedService.IconButton(FontAwesomeIcon.Ban)) { Mediator.Publish(new OpenBanUserPopupMessage(pair, GroupFullInfo)); @@ -977,6 +984,96 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase } } + private static void DrawBannedListHeader(float colIdentity, float colMeta) + { + var style = ImGui.GetStyle(); + float x0 = ImGui.GetCursorPosX(); + + ImGui.PushStyleColor(ImGuiCol.Text, UIColors.Get("LightlessYellow")); + + // User and reason + ImGui.SetCursorPosX(x0); + ImGui.TextUnformatted("User / Reason"); + + // Moderator and Date + ImGui.SameLine(); + ImGui.SetCursorPosX(x0 + colIdentity + style.ItemSpacing.X); + ImGui.TextUnformatted("Moderator / Date"); + + // Actions + ImGui.SameLine(); + ImGui.SetCursorPosX(x0 + colIdentity + colMeta + style.ItemSpacing.X * 2.0f); + ImGui.TextUnformatted("Actions"); + + ImGui.PopStyleColor(); + + UiSharedService.ColoredSeparator(UIColors.Get("LightlessYellow"), 1.0f); + } + + private void DrawBannedRow(BannedGroupUserDto bannedUser, int rowIndex, float colIdentity, float colMeta, float colActions) + { + using var id = ImRaii.PushId("banRow_" + bannedUser.UID); + + var style = ImGui.GetStyle(); + float x0 = ImGui.GetCursorPosX(); + + if (rowIndex % 2 == 0) + { + var drawList = ImGui.GetWindowDrawList(); + var pMin = ImGui.GetCursorScreenPos(); + var rowHeight = ImGui.GetTextLineHeightWithSpacing() * 2.6f; + var pMax = new Vector2( + pMin.X + colIdentity + colMeta + colActions + style.ItemSpacing.X * 2.0f, + pMin.Y + rowHeight); + + var bgColor = UIColors.Get("FullBlack").WithAlpha(0.10f); + drawList.AddRectFilled(pMin, pMax, ImGui.ColorConvertFloat4ToU32(bgColor)); + } + + ImGui.SetCursorPosX(x0); + ImGui.AlignTextToFramePadding(); + + string alias = bannedUser.UserAlias ?? string.Empty; + string line1 = string.IsNullOrEmpty(alias) + ? bannedUser.UID + : $"{alias} ({bannedUser.UID})"; + + ImGui.TextUnformatted(line1); + + var reason = bannedUser.Reason ?? string.Empty; + if (!string.IsNullOrWhiteSpace(reason)) + { + var reasonPos = new Vector2(x0, ImGui.GetCursorPosY()); + ImGui.SetCursorPos(reasonPos); + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey); + UiSharedService.TextWrapped(reason); + ImGui.PopStyleColor(); + } + + ImGui.SameLine(); + ImGui.SetCursorPosX(x0 + colIdentity + style.ItemSpacing.X); + + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted($"By: {bannedUser.BannedBy}"); + + var dateText = bannedUser.BannedOn.ToLocalTime().ToString(CultureInfo.CurrentCulture); + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey); + ImGui.TextUnformatted(dateText); + ImGui.PopStyleColor(); + + ImGui.SameLine(); + ImGui.SetCursorPosX(x0 + colIdentity + colMeta + style.ItemSpacing.X * 2.0f); + + if (_uiSharedService.IconTextButton(FontAwesomeIcon.Check, "Unban")) + { + _apiController.GroupUnbanUser(bannedUser); + _bannedUsers.RemoveAll(b => string.Equals(b.UID, bannedUser.UID, StringComparison.Ordinal)); + } + + UiSharedService.AttachToolTip($"Unban {alias} ({bannedUser.UID}) from this Syncshell"); + + ImGui.Dummy(new Vector2(0, 4 * ImGuiHelpers.GlobalScale)); + } private void SavePruneSettings() { if (_autoPruneDays <= 0)