diff --git a/Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRoleButtonsBox.xaml b/Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRoleButtonsBox.xaml
new file mode 100644
index 0000000000..32d611e771
--- /dev/null
+++ b/Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRoleButtonsBox.xaml
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRolesEntry.xaml.cs b/Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRoleButtonsBox.xaml.cs
similarity index 86%
rename from Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRolesEntry.xaml.cs
rename to Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRoleButtonsBox.xaml.cs
index fc53cc72ae..7df0243416 100644
--- a/Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRolesEntry.xaml.cs
+++ b/Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRoleButtonsBox.xaml.cs
@@ -10,20 +10,17 @@ using Robust.Shared.Utility;
namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles
{
[GenerateTypedNameReferences]
- public sealed partial class GhostRolesEntry : BoxContainer
+ public sealed partial class GhostRoleButtonsBox : BoxContainer
{
private SpriteSystem _spriteSystem;
public event Action? OnRoleSelected;
public event Action? OnRoleFollow;
- public GhostRolesEntry(string name, string description, bool hasAccess, FormattedMessage? reason, IEnumerable roles, SpriteSystem spriteSystem)
+ public GhostRoleButtonsBox(bool hasAccess, FormattedMessage? reason, IEnumerable roles, SpriteSystem spriteSystem)
{
RobustXamlLoader.Load(this);
_spriteSystem = spriteSystem;
- Title.Text = name;
- Description.SetMessage(description);
-
foreach (var role in roles)
{
var button = new GhostRoleEntryButtons(role);
diff --git a/Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRoleEntryButtons.xaml b/Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRoleEntryButtons.xaml
index ffde5d69f7..05c52deef1 100644
--- a/Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRoleEntryButtons.xaml
+++ b/Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRoleEntryButtons.xaml
@@ -1,15 +1,15 @@
+ Orientation="Horizontal"
+ HorizontalAlignment="Stretch">
+ HorizontalExpand="True"
+ SizeFlagsStretchRatio="3"/>
+ HorizontalExpand="True"/>
diff --git a/Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRoleInfoBox.xaml b/Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRoleInfoBox.xaml
new file mode 100644
index 0000000000..e24455bdf5
--- /dev/null
+++ b/Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRoleInfoBox.xaml
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRoleInfoBox.xaml.cs b/Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRoleInfoBox.xaml.cs
new file mode 100644
index 0000000000..705a9f0bb8
--- /dev/null
+++ b/Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRoleInfoBox.xaml.cs
@@ -0,0 +1,18 @@
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles
+{
+ [GenerateTypedNameReferences]
+ public sealed partial class GhostRoleInfoBox : BoxContainer
+ {
+ public GhostRoleInfoBox(string name, string description)
+ {
+ RobustXamlLoader.Load(this);
+
+ Title.Text = name;
+ Description.SetMessage(description);
+ }
+ }
+}
diff --git a/Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRolesEntry.xaml b/Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRolesEntry.xaml
deleted file mode 100644
index d9ed172810..0000000000
--- a/Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRolesEntry.xaml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRolesEui.cs b/Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRolesEui.cs
index 6b183362e5..1cf1e55103 100644
--- a/Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRolesEui.cs
+++ b/Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRolesEui.cs
@@ -5,7 +5,6 @@ using Content.Shared.Eui;
using Content.Shared.Ghost.Roles;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
-using Robust.Shared.Utility;
namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles
{
@@ -77,6 +76,13 @@ namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles
if (state is not GhostRolesEuiState ghostState)
return;
+
+ // We must save BodyVisible state, so all Collapsible boxes will not close
+ // on adding new ghost role.
+ // Save the current state of each Collapsible box being visible or not
+ _window.SaveCollapsibleBoxesStates();
+
+ // Clearing the container before adding new roles
_window.ClearEntries();
var entityManager = IoCManager.Resolve();
@@ -84,28 +90,32 @@ namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles
var spriteSystem = sysManager.GetEntitySystem();
var requirementsManager = IoCManager.Resolve();
+ // TODO: role.Requirements value doesn't work at all as an equality key, this must be fixed
+ // Grouping roles
var groupedRoles = ghostState.GhostRoles.GroupBy(
role => (role.Name, role.Description, role.Requirements));
+
+ // Add a new entry for each role group
foreach (var group in groupedRoles)
{
var name = group.Key.Name;
var description = group.Key.Description;
- bool hasAccess = true;
- FormattedMessage? reason;
-
- if (!requirementsManager.CheckRoleRequirements(group.Key.Requirements, null, out reason))
- {
- hasAccess = false;
- }
+ var hasAccess = requirementsManager.CheckRoleRequirements(
+ group.Key.Requirements,
+ null,
+ out var reason);
+ // Adding a new role
_window.AddEntry(name, description, hasAccess, reason, group, spriteSystem);
}
+ // Restore the Collapsible box state if it is saved
+ _window.RestoreCollapsibleBoxesStates();
+
+ // Close the rules window if it is no longer needed
var closeRulesWindow = ghostState.GhostRoles.All(role => role.Identifier != _windowRulesId);
if (closeRulesWindow)
- {
_windowRules?.Close();
- }
}
}
}
diff --git a/Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRolesWindow.xaml.cs b/Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRolesWindow.xaml.cs
index 2e7c99641b..627ecfe987 100644
--- a/Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRolesWindow.xaml.cs
+++ b/Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRolesWindow.xaml.cs
@@ -1,7 +1,10 @@
+using System.Linq;
using Content.Shared.Ghost.Roles;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
+using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
+using Robust.Client.UserInterface.XAML;
using Robust.Shared.Utility;
namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles
@@ -12,20 +15,86 @@ namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles
public event Action? OnRoleRequestButtonClicked;
public event Action? OnRoleFollow;
+ private Dictionary<(string name, string description), Collapsible> _collapsibleBoxes = new();
+ private HashSet<(string name, string description)> _uncollapsedStates = new();
+
+ public GhostRolesWindow()
+ {
+ RobustXamlLoader.Load(this);
+ }
+
public void ClearEntries()
{
NoRolesMessage.Visible = true;
EntryContainer.DisposeAllChildren();
+ _collapsibleBoxes.Clear();
+ }
+
+ public void SaveCollapsibleBoxesStates()
+ {
+ _uncollapsedStates.Clear();
+ foreach (var (key, collapsible) in _collapsibleBoxes)
+ {
+ if (collapsible.BodyVisible)
+ {
+ _uncollapsedStates.Add(key);
+ }
+ }
+ }
+
+ public void RestoreCollapsibleBoxesStates()
+ {
+ foreach (var (key, collapsible) in _collapsibleBoxes)
+ {
+ collapsible.BodyVisible = _uncollapsedStates.Contains(key);
+ }
}
public void AddEntry(string name, string description, bool hasAccess, FormattedMessage? reason, IEnumerable roles, SpriteSystem spriteSystem)
{
NoRolesMessage.Visible = false;
- var entry = new GhostRolesEntry(name, description, hasAccess, reason, roles, spriteSystem);
- entry.OnRoleSelected += OnRoleRequestButtonClicked;
- entry.OnRoleFollow += OnRoleFollow;
- EntryContainer.AddChild(entry);
+ var ghostRoleInfos = roles.ToList();
+ var rolesCount = ghostRoleInfos.Count;
+
+ var info = new GhostRoleInfoBox(name, description);
+ var buttons = new GhostRoleButtonsBox(hasAccess, reason, ghostRoleInfos, spriteSystem);
+ buttons.OnRoleSelected += OnRoleRequestButtonClicked;
+ buttons.OnRoleFollow += OnRoleFollow;
+
+ EntryContainer.AddChild(info);
+
+ if (rolesCount > 1)
+ {
+ var buttonHeading = new CollapsibleHeading(Loc.GetString("ghost-roles-window-available-button", ("rolesCount", rolesCount)));
+
+ buttonHeading.AddStyleClass(ContainerButton.StyleClassButton);
+ buttonHeading.Label.HorizontalAlignment = HAlignment.Center;
+ buttonHeading.Label.HorizontalExpand = true;
+
+ var body = new CollapsibleBody
+ {
+ Margin = new Thickness(0, 5, 0, 0),
+ };
+
+ // TODO: Add Requirements to this key when it'll be fixed and work as an equality key in GhostRolesEui
+ var key = (name, description);
+
+ var collapsible = new Collapsible(buttonHeading, body)
+ {
+ Orientation = BoxContainer.LayoutOrientation.Vertical,
+ Margin = new Thickness(0, 0, 0, 8),
+ };
+
+ body.AddChild(buttons);
+
+ EntryContainer.AddChild(collapsible);
+ _collapsibleBoxes.Add(key, collapsible);
+ }
+ else
+ {
+ EntryContainer.AddChild(buttons);
+ }
}
}
}
diff --git a/Resources/Locale/en-US/ghost/ghost-gui.ftl b/Resources/Locale/en-US/ghost/ghost-gui.ftl
index cd4559e148..7d3939abb1 100644
--- a/Resources/Locale/en-US/ghost/ghost-gui.ftl
+++ b/Resources/Locale/en-US/ghost/ghost-gui.ftl
@@ -14,6 +14,7 @@ ghost-target-window-current-button = Warp: {$name}
ghost-target-window-warp-to-most-followed = Warp to Most Followed
ghost-roles-window-title = Ghost Roles
+ghost-roles-window-available-button = Available ({$rolesCount})
ghost-roles-window-join-raffle-button = Join raffle
ghost-roles-window-raffle-in-progress-button =
Join raffle ({$time} left, { $players ->