Improve UX and visuals of communications console (#41899)

* Implment mockup of comms console UI

* Fix broadcast broken by 31823

* Separate announce/broadcast functionality

* Tweak resizing behaviour; make window wider by default

* Split up comms console into different widgets

Makes each section of the UI easier to understand
Apply some cleanup, renames, make consistent with style guide, etc.

* Polishing, todos, missing pieces

* Consolidate button border images. Allow more accent colours with geometric_panel_border

* Resolve some TODOs, small polish

* Fix warning

* Revert modification to StripeBack

Old texture relied on two stripe colours, had to replicate with only styles.
Split out a new version with tweakable modulation

* Make UI more boring

* Some element shuffling, style changing

* Remove some leftover changes

* Remove more leftovers

* Fix warnings

* Feedback; remove unused bits, dependencies, formatting, highlight
This commit is contained in:
eoineoineoin 2026-08-14 18:38:21 +00:00 • committed by GitHub
parent 65f5f67cd5
commit 7c19a7d1de
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 535 additions and 220 deletions

View file

@ -0,0 +1,14 @@
using Content.Shared.Communications;
using Robust.Shared.Prototypes;
namespace Content.Client.Communications;
[RegisterComponent]
public sealed partial class CommunicationsConsoleComponent : SharedCommunicationsConsoleComponent
{
/// <summary>
/// The prototype ID to use in the UI to show what entities a broadcast will display on
/// </summary>
[DataField]
public EntProtoId ScreenDisplayId = "Screen";
}

View file

@ -1,4 +1,4 @@
using Content.Shared.CCVar;
using Content.Shared.CCVar;
using Content.Shared.Chat;
using Content.Shared.AlertLevel;
using Content.Shared.Communications;
@ -23,41 +23,36 @@ public sealed partial class CommunicationsConsoleBoundUserInterface(EntityUid ow
base.Open();
_menu = this.CreateWindow<CommunicationsConsoleMenu>();
_menu.OnAnnounce += AnnounceButtonPressed;
_menu.OnBroadcast += BroadcastButtonPressed;
_menu.OnAlertLevel += AlertLevelSelected;
_menu.OnEmergencyLevel += EmergencyShuttleButtonPressed;
_menu.OnRadioAnnounce += RadioAnnounceButtonPressed;
_menu.OnScreenBroadcast += ScreenBroadcastButtonPressed;
_menu.OnAlertLevelChanged += AlertLevelSelected;
_menu.OnShuttleCalled += CallShuttle;
_menu.OnShuttleRecalled += RecallShuttle;
if (EntMan.TryGetComponent<CommunicationsConsoleComponent>(Owner, out var console))
{
_menu.SetBroadcastDisplayEntity(console.ScreenDisplayId);
}
}
public void AlertLevelSelected(ProtoId<AlertLevelPrototype> level)
{
if (_menu!.AlertLevelSelectable)
{
// TODO: This does not work until the console UI is predicted and uses component states.
// Also someone decided to send BUI states regularly in an update loop, so this just gets randomly bulldozed until the message reaches the server.
// _menu.CurrentAlertLevel = level;
// _menu.AlertLevelSelectable = false;
// _menu.AlertLevelButton.Disabled = true;
SendMessage(new CommunicationsConsoleSelectAlertLevelMessage(level));
}
// TODO: This does not work until the console UI is predicted and uses component states.
// Also someone decided to send BUI states regularly in an update loop, so this just gets randomly bulldozed until the message reaches the server.
// _menu.CurrentAlertLevel = level;
// _menu.AlertLevelSelectable = false;
// _menu.AlertLevelButton.Disabled = true;
SendMessage(new CommunicationsConsoleSelectAlertLevelMessage(level));
}
public void EmergencyShuttleButtonPressed()
{
if (_menu!.CountdownStarted)
RecallShuttle();
else
CallShuttle();
}
public void AnnounceButtonPressed(string message)
public void RadioAnnounceButtonPressed(string message)
{
var maxLength = _cfg.GetCVar(CCVars.ChatMaxAnnouncementLength);
var msg = SharedChatSystem.SanitizeAnnouncement(message, maxLength);
SendMessage(new CommunicationsConsoleAnnounceMessage(msg));
}
public void BroadcastButtonPressed(string message)
public void ScreenBroadcastButtonPressed(string message)
{
SendMessage(new CommunicationsConsoleBroadcastMessage(message));
}
@ -87,23 +82,11 @@ public sealed partial class CommunicationsConsoleBoundUserInterface(EntityUid ow
if (_menu != null)
{
_menu.CanAnnounce = commsState.CanAnnounce;
_menu.CanBroadcast = commsState.CanBroadcast;
_menu.CanCall = commsState.CanCall;
_menu.CountdownStarted = commsState.CountdownStarted;
_menu.CountdownEnd = commsState.ExpectedCountdownEnd;
var currentAlertLevel = alertComp.CurrentAlertLevel;
var selectableAlertLevels = _alertLevel.GetSelectableAlertLevels((stationUid.Value, alertComp));
var canChangeAlertLevel = _alertLevel.CanChangeAlertLevel((stationUid.Value, alertComp));
_menu.CurrentAlertLevel = alertComp.CurrentAlertLevel;
_menu.SelectableAlertLevels = _alertLevel.GetSelectableAlertLevels((stationUid.Value, alertComp));
_menu.AlertLevelSelectable = _alertLevel.CanChangeAlertLevel((stationUid.Value, alertComp));
_menu.UpdateCountdown();
_menu.UpdateAlertLevels();
_menu.AlertLevelButton.Disabled = !_menu.AlertLevelSelectable;
_menu.EmergencyShuttleButton.Disabled = !_menu.CanCall;
_menu.AnnounceButton.Disabled = !_menu.CanAnnounce;
_menu.BroadcastButton.Disabled = !_menu.CanBroadcast;
_menu.UpdateState(commsState, currentAlertLevel, selectableAlertLevels, canChangeAlertLevel);
}
}
}

View file

@ -1,62 +1,24 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
<comms:CommunicationsConsoleMenu xmlns="https://spacestation14.io"
xmlns:comms="clr-namespace:Content.Client.Communications.UI"
xmlns:widgets="clr-namespace:Content.Client.Communications.UI.Widgets"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc 'comms-console-menu-title'}"
MinSize="400 300">
MinSize="400 654" SetWidth="450"
Title="{Loc 'comms-console-menu-title'}">
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True" Margin="12">
<PanelContainer StyleClasses="PanelDark" VerticalExpand="True">
<widgets:MessagingControls Name="MessagingControls" VerticalExpand="True" />
</PanelContainer>
<!-- Main Container -->
<BoxContainer Orientation="Vertical"
HorizontalExpand="False"
VerticalExpand="True"
Margin="6 6 6 5">
<controls:HSpacer Spacing="12" />
<TextEdit Name="MessageInput"
VerticalExpand="True"
HorizontalExpand="True"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
MinHeight="100"/>
<PanelContainer StyleClasses="BackgroundPanelDark">
<widgets:AlertLevelControls Name="AlertLevelControls" Margin="0 12 0 12" />
</PanelContainer>
<!-- ButtonsPart -->
<BoxContainer Orientation="Vertical"
VerticalAlignment="Bottom"
SeparationOverride="4">
<controls:HSpacer Spacing="12" />
<!-- AnnouncePart -->
<BoxContainer Orientation="Vertical"
Margin="0 2">
<Button Name="AnnounceButton"
Access="Public"
Text="{Loc 'comms-console-menu-announcement-button'}"
ToolTip="{Loc 'comms-console-menu-announcement-button-tooltip'}"
StyleClasses="OpenLeft"
Margin="0 0 1 0"
Disabled="True"/>
<Button Name="BroadcastButton"
Access="Public"
Text="{Loc 'comms-console-menu-broadcast-button'}"
ToolTip="{Loc 'comms-console-menu-broadcast-button-tooltip'}"
StyleClasses="OpenBoth"/>
<OptionButton Name="AlertLevelButton"
Access="Public"
ToolTip="{Loc 'comms-console-menu-alert-level-button-tooltip'}"
StyleClasses="OpenRight"/>
</BoxContainer>
<!-- EmergencyPart -->
<BoxContainer Orientation="Vertical"
SeparationOverride="6">
<RichTextLabel Name="CountdownLabel"/>
<Button Name="EmergencyShuttleButton"
Access="Public"
Text="Placeholder Text"
ToolTip="{Loc 'comms-console-menu-emergency-shuttle-button-tooltip'}"/>
</BoxContainer>
</BoxContainer>
<PanelContainer StyleClasses="BackgroundPanelDark">
<widgets:ShuttleControls Name="ShuttleControls" Margin="0 12 0 12"/>
</PanelContainer>
</BoxContainer>
</controls:FancyWindow>
</comms:CommunicationsConsoleMenu>

View file

@ -1,133 +1,58 @@
using System.Globalization;
using Content.Client.UserInterface.Controls;
using Content.Shared.AlertLevel;
using Content.Shared.CCVar;
using Content.Shared.Communications;
using Content.Client.UserInterface.Controls;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Configuration;
using Robust.Shared.Timing;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.Communications.UI
namespace Content.Client.Communications.UI;
[GenerateTypedNameReferences]
public sealed partial class CommunicationsConsoleMenu : FancyWindow
{
[GenerateTypedNameReferences]
public sealed partial class CommunicationsConsoleMenu : FancyWindow
public event Action? OnShuttleCalled;
public event Action? OnShuttleRecalled;
public event Action<ProtoId<AlertLevelPrototype>>? OnAlertLevelChanged;
public event Action<string>? OnRadioAnnounce;
public event Action<string>? OnScreenBroadcast;
public CommunicationsConsoleMenu()
{
[Dependency] private IConfigurationManager _cfg = default!;
[Dependency] private IGameTiming _timing = default!;
[Dependency] private ILocalizationManager _loc = default!;
[Dependency] private IPrototypeManager _protoMan = default!;
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
public bool CanAnnounce;
public bool CanBroadcast;
public bool CanCall;
public bool CountdownStarted;
public TimeSpan? CountdownEnd;
MessagingControls.OnRadioAnnounce += message => OnRadioAnnounce?.Invoke(message);
MessagingControls.OnScreenBroadcast += message => OnScreenBroadcast?.Invoke(message);
public ProtoId<AlertLevelPrototype>? CurrentAlertLevel;
public List<ProtoId<AlertLevelPrototype>> SelectableAlertLevels = new();
public bool AlertLevelSelectable;
AlertLevelControls.OnAlertLevelChanged += newLevel => OnAlertLevelChanged?.Invoke(newLevel);
public event Action? OnEmergencyLevel;
public event Action<ProtoId<AlertLevelPrototype>>? OnAlertLevel;
public event Action<string>? OnAnnounce;
public event Action<string>? OnBroadcast;
ShuttleControls.OnShuttleCalled += () => OnShuttleCalled?.Invoke();
ShuttleControls.OnShuttleRecalled += () => OnShuttleRecalled?.Invoke();
}
public CommunicationsConsoleMenu()
{
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
/// <summary>
/// Use the specified prototype ID as an example display in the
/// UI subsection where users type broadcast messages
/// </summary>
public void SetBroadcastDisplayEntity(EntProtoId broadcastEntityId)
{
MessagingControls.SetBroadcastDisplayEntity(broadcastEntityId);
}
MessageInput.Placeholder = new Rope.Leaf(_loc.GetString("comms-console-menu-announcement-placeholder"));
/// <summary>
/// Configure UI to be consistent with the input state
/// </summary>
public void UpdateState(CommunicationsConsoleInterfaceState commsState,
ProtoId<AlertLevelPrototype> currentAlertLevel,
List<ProtoId<AlertLevelPrototype>>? selectableAlertLevels,
bool canChangeAlertLevel)
{
MessagingControls.CanRadioAnnounce = commsState.CanAnnounce;
MessagingControls.CanScreenBroadcast = commsState.CanBroadcast;
var maxAnnounceLength = _cfg.GetCVar(CCVars.ChatMaxAnnouncementLength);
MessageInput.OnTextChanged += (args) =>
{
if (args.Control.TextLength > maxAnnounceLength)
{
AnnounceButton.Disabled = true;
AnnounceButton.ToolTip = Loc.GetString("comms-console-message-too-long");
}
else
{
AnnounceButton.Disabled = !CanAnnounce;
AnnounceButton.ToolTip = null;
var alertLevelSelectable = selectableAlertLevels != null && canChangeAlertLevel;
AlertLevelControls.UpdateAlertLevels(selectableAlertLevels, currentAlertLevel, alertLevelSelectable);
}
};
AnnounceButton.OnPressed += _ => OnAnnounce?.Invoke(Rope.Collapse(MessageInput.TextRope));
AnnounceButton.Disabled = !CanAnnounce;
BroadcastButton.OnPressed += _ => OnBroadcast?.Invoke(Rope.Collapse(MessageInput.TextRope));
BroadcastButton.Disabled = !CanBroadcast;
AlertLevelButton.OnItemSelected += args =>
{
var metadata = AlertLevelButton.GetItemMetadata(args.Id);
if (metadata != null && metadata is ProtoId<AlertLevelPrototype> cast)
{
OnAlertLevel?.Invoke(cast);
}
};
AlertLevelButton.Disabled = !AlertLevelSelectable;
EmergencyShuttleButton.OnPressed += _ => OnEmergencyLevel?.Invoke();
EmergencyShuttleButton.Disabled = !CanCall;
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
UpdateCountdown();
// TODO: Countdown for alert level delay
}
public void UpdateAlertLevels()
{
AlertLevelButton.Clear();
if (CurrentAlertLevel == null)
return;
if (!AlertLevelSelectable)
{
// selection is locked, only show the current alert
var name = _protoMan.Index(CurrentAlertLevel).LocalizedName;
AlertLevelButton.AddItem(name);
AlertLevelButton.SetItemMetadata(AlertLevelButton.ItemCount - 1, CurrentAlertLevel);
}
else
{
foreach (var alert in SelectableAlertLevels)
{
var name = _protoMan.Index(alert).LocalizedName;
AlertLevelButton.AddItem(name);
AlertLevelButton.SetItemMetadata(AlertLevelButton.ItemCount - 1, alert);
if (alert == CurrentAlertLevel)
AlertLevelButton.Select(AlertLevelButton.ItemCount - 1);
}
}
}
public void UpdateCountdown()
{
if (!CountdownStarted)
{
CountdownLabel.SetMessage(string.Empty);
EmergencyShuttleButton.Text = Loc.GetString("comms-console-menu-call-shuttle");
return;
}
var diff = MathHelper.Max((CountdownEnd - _timing.CurTime) ?? TimeSpan.Zero, TimeSpan.Zero);
EmergencyShuttleButton.Text = Loc.GetString("comms-console-menu-recall-shuttle");
var infoText = Loc.GetString($"comms-console-menu-time-remaining",
("time", diff.ToString(@"hh\:mm\:ss", CultureInfo.CurrentCulture)));
CountdownLabel.SetMessage(infoText);
}
ShuttleControls.UpdateState(commsState.CanCall, commsState.CountdownStarted, commsState.ExpectedCountdownEnd);
}
}

View file

@ -0,0 +1,22 @@
<BoxContainer xmlns="https://spacestation14.io"
Orientation="Vertical">
<!-- Info about the current alert level -->
<BoxContainer HorizontalAlignment="Center">
<Label StyleClasses="StatusFieldTitle" Margin="0 0 9 0"
Text="{Loc 'comms-console-alert-current-level-header'}" />
<Label Name="CurrentAlertLevelLabel" Margin="9 0 0 0"/>
</BoxContainer>
<Label Name="CurrentAlertLevelFlavorLabel" Align="Center" />
<!-- Controls for changing the alert level -->
<BoxContainer Margin="9 12 9 6">
<OptionButton Name="AlertLevelSelector" StyleClasses="OpenRight"
ToolTip="{Loc 'comms-console-menu-alert-level-button-tooltip'}"
HorizontalExpand="True" SizeFlagsStretchRatio="4" />
<Button Name="ConfirmAlertLevelButton" StyleClasses="OpenLeft"
HorizontalExpand="True" SizeFlagsStretchRatio="1"
Text="{Loc 'comms-console-confirm-alert-level-button'}"
Disabled="True" />
</BoxContainer>
</BoxContainer>

View file

@ -0,0 +1,146 @@
using Content.Shared.AlertLevel;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
namespace Content.Client.Communications.UI.Widgets;
[GenerateTypedNameReferences]
public sealed partial class AlertLevelControls : BoxContainer
{
[Dependency] private ILocalizationManager _loc = default!;
[Dependency] private IPrototypeManager _protoman = default!;
private bool _alertLevelSelectable;
private ProtoId<AlertLevelPrototype>? _currentAlertLevel = null;
public event Action<string>? OnAlertLevelChanged;
public AlertLevelControls()
{
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
AlertLevelSelector.OnItemSelected += args =>
{
AlertLevelSelector.Select(args.Id);
EnableDisableConfirmLevelChangeButton();
};
ConfirmAlertLevelButton.OnPressed += _ =>
{
var metadata = AlertLevelSelector.GetItemMetadata(AlertLevelSelector.SelectedId);
if (metadata is ProtoId<AlertLevelPrototype> cast)
{
OnAlertLevelChanged?.Invoke(cast);
}
};
AlertLevelSelector.Disabled = !_alertLevelSelectable;
}
/// <summary>
/// Updates the UI components to display the current alert level and the
/// selectable alert levels
/// </summary>
public void UpdateAlertLevels(List<ProtoId<AlertLevelPrototype>>? selectableAlertLevels, ProtoId<AlertLevelPrototype> currentAlertLevel, bool alertLevelSelectable)
{
_alertLevelSelectable = alertLevelSelectable;
_currentAlertLevel = currentAlertLevel;
CurrentAlertLevelLabel.Text = GetLocalizedAlertName(currentAlertLevel);
if (_protoman.TryIndex(currentAlertLevel, out var alertData))
{
CurrentAlertLevelLabel.ModulateSelfOverride = alertData.Color;
}
else
{
CurrentAlertLevelLabel.ModulateSelfOverride = Color.White;
}
AlertLevelSelector.Disabled = selectableAlertLevels == null || !_alertLevelSelectable;
// If user had changed the selection, but hadn't pressed the confirm
// button at the point we received an update message, we want to
// remember what they had selected, so we can attempt to re-select it
// if it's still an option in the new set of alert level possibilities.
ProtoId<AlertLevelPrototype>? previousSelection = null;
if (AlertLevelSelector.ItemCount > 1)
{
var metadata = AlertLevelSelector.GetItemMetadata(AlertLevelSelector.SelectedId);
if (metadata is ProtoId<AlertLevelPrototype> cast)
{
previousSelection = cast;
}
}
// The server will only send alert levels which are selectable, but
// unfortunately, for a short time after changing the alert level, no
// level is selectable, so we won't have any levels to put into the
// alert level list. Here, we make a dummy item to handle that; this
// item also informs the user what this combo box is for.
AlertLevelSelector.Clear();
AlertLevelSelector.AddItem(Loc.GetString("comms-console-change-alert-level-button"));
AlertLevelSelector.Select(0);
if (selectableAlertLevels != null)
{
foreach (var alert in selectableAlertLevels)
{
AlertLevelSelector.AddItem(GetLocalizedAlertName(alert));
AlertLevelSelector.SetItemMetadata(AlertLevelSelector.ItemCount - 1, alert);
if (alert == previousSelection)
{
AlertLevelSelector.Select(AlertLevelSelector.ItemCount - 1);
}
}
}
if (_loc.TryGetString($"comms-console-level-{currentAlertLevel}-flavour-label", out var flavour))
{
CurrentAlertLevelFlavorLabel.Text = flavour;
}
else
{
CurrentAlertLevelFlavorLabel.Text = string.Empty;
}
EnableDisableConfirmLevelChangeButton();
}
/// <summary>
/// Configure the "confirm alert level changed" button to be consistent with
/// the server state and user interactions
/// </summary>
private void EnableDisableConfirmLevelChangeButton()
{
// Disable the button when:
// 1. Server says we can't change level
// 2. User selected special info option (id 0)
// 3. User selected the same level the station is currently on
var selectedId = AlertLevelSelector.SelectedId;
ConfirmAlertLevelButton.Disabled = !_alertLevelSelectable || selectedId == 0;
if (selectedId != 0)
{
var metadata = AlertLevelSelector.GetItemMetadata(selectedId);
if (metadata is ProtoId<AlertLevelPrototype> selected)
{
ConfirmAlertLevelButton.Disabled |= selected == _currentAlertLevel;
}
}
}
/// <summary>
/// Utility function to convert an alert level identifier into a localized string
/// </summary>
private string GetLocalizedAlertName(ProtoId<AlertLevelPrototype> alertProto)
{
if (_protoman.TryIndex(alertProto, out var alertData))
{
return alertData.LocalizedName;
}
return alertProto;
}
}

View file

@ -0,0 +1,30 @@
<TabContainer xmlns="https://spacestation14.io" >
<!-- Controls for sending radio announcements -->
<BoxContainer Orientation="Vertical" VerticalExpand="True"
TabContainer.TabTitle="{Loc 'comms-console-announce-tab-title'}">
<Label StyleClasses="StatusFieldTitle" Align="Center"
Text="{Loc 'comms-console-station-announcements-header'}" />
<PanelContainer StyleClasses="BackgroundDark" Margin="10 6 6 6"
HorizontalExpand="True" VerticalExpand="True">
<TextEdit Name="RadioMessageInput" MinHeight="100"
HorizontalExpand="True" VerticalExpand="True" />
</PanelContainer>
<Button Name="AnnounceButton" HorizontalExpand="True" Margin="10 6 6 6"
Text="{Loc 'comms-console-menu-announcement-button'}"
ToolTip="{Loc 'comms-console-menu-announcement-button-tooltip'}" />
</BoxContainer>
<!-- Controls for putting messages on station screens (aka broadcasting) -->
<BoxContainer Orientation="Vertical" VerticalExpand="True"
TabContainer.TabTitle="{Loc 'comms-console-broadcast-tab-title'}">
<Label StyleClasses="StatusFieldTitle" Align="Center" VAlign="Top"
VerticalExpand="True" VerticalAlignment="Top"
Text="{Loc 'comms-console-station-broadcast-header'}" />
<SpriteView Name="BroadcastEntityDisplay" Scale="8 8" />
<LineEdit Name="ScreenMessageInput" Margin="10 0 6 0"
PlaceHolder="{Loc 'comms-console-menu-broadcast-placeholder'}" />
<Button Name="BroadcastButton" HorizontalExpand="True" Margin="10 6 6 6"
Text="{Loc 'comms-console-menu-broadcast-button'}"
ToolTip="{Loc 'comms-console-menu-broadcast-button-tooltip'}" />
</BoxContainer>
</TabContainer>

View file

@ -0,0 +1,116 @@
using Content.Shared.CCVar;
using Content.Shared.TextScreen;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.Communications.UI.Widgets;
[GenerateTypedNameReferences]
public sealed partial class MessagingControls : TabContainer
{
[Dependency] private IConfigurationManager _cfg = default!;
[Dependency] private IEntityManager _entMan = default!;
// Entity temporarily created to display a screen preview
private EntityUid _broadcastDisplayEntity = EntityUid.Invalid;
public event Action<string>? OnRadioAnnounce;
public event Action<string>? OnScreenBroadcast;
private bool _canRadioAnnounce;
public bool CanRadioAnnounce
{
set
{
_canRadioAnnounce = value;
SyncButtonState();
}
}
private bool _canScreenBroadcast;
public bool CanScreenBroadcast
{
set
{
_canScreenBroadcast = value;
SyncButtonState();
}
}
public MessagingControls()
{
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
RadioMessageInput.Placeholder = new Rope.Leaf(Loc.GetString("comms-console-menu-announcement-placeholder"));
RadioMessageInput.OnTextChanged += (_) => SyncButtonState();
AnnounceButton.OnPressed += _ =>
{
OnRadioAnnounce?.Invoke(Rope.Collapse(RadioMessageInput.TextRope));
};
var appearanceSystem = _entMan.System<SharedAppearanceSystem>();
ScreenMessageInput.OnTextChanged += args =>
{
if (_broadcastDisplayEntity.IsValid())
{
appearanceSystem.SetData(_broadcastDisplayEntity, TextScreenVisuals.ScreenText, args.Text);
}
};
BroadcastButton.OnPressed += _ =>
{
OnScreenBroadcast?.Invoke(ScreenMessageInput.Text);
};
SyncButtonState();
}
public void SetBroadcastDisplayEntity(EntProtoId broadcastEntityId)
{
_broadcastDisplayEntity = _entMan.Spawn(broadcastEntityId);
if (_broadcastDisplayEntity.IsValid())
{
BroadcastEntityDisplay.SetEntity(_broadcastDisplayEntity);
}
}
protected override void ExitedTree()
{
if (_broadcastDisplayEntity.IsValid())
{
_entMan.DeleteEntity(_broadcastDisplayEntity);
}
}
private void SyncButtonState()
{
if (_canRadioAnnounce)
{
var maxAnnounceLength = _cfg.GetCVar(CCVars.ChatMaxAnnouncementLength);
if (RadioMessageInput.TextLength > maxAnnounceLength)
{
AnnounceButton.Disabled = true;
AnnounceButton.ToolTip = Loc.GetString("comms-console-message-too-long");
}
else
{
AnnounceButton.Disabled = false;
AnnounceButton.ToolTip = Loc.GetString("comms-console-menu-announcement-button-tooltip");
}
}
else
{
AnnounceButton.Disabled = true;
AnnounceButton.ToolTip = Loc.GetString("comms-console-message-cannot-send");
}
BroadcastButton.Disabled = !_canScreenBroadcast;
}
}

View file

@ -0,0 +1,31 @@
<Control xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<!-- Controls for calling, recalling and displaying info for the emergency shuttle -->
<BoxContainer Orientation="Vertical">
<Label StyleClasses="FancyWindowTitle" Align="Center"
Text="{Loc 'comms-console-shuttle-controls-header'}" />
<!-- This contains some blank `Controls` to act as dummies. This is because the
GridContainer will always left-align the children, while we want them centered.
The dummy Controls will attempt to take an even amount of space each, which
will re-center our real controls -->
<BoxContainer>
<!-- Row 1: the buttons themselves -->
<Control HorizontalExpand="True" />
<Button Name="EmergencyShuttleCallButton" VerticalAlignment="Center"
Text="{Loc 'comms-console-call-button-label'}" />
<Control HorizontalExpand="True" />
<BoxContainer Orientation="Vertical">
<Label StyleClasses="LabelSubText" Align="Center"
Text="{Loc 'comms-console-shuttle-status-label'}" />
<Label Name="CountdownLabel" ReservesSpace="True" Align="Center"/>
</BoxContainer>
<Control HorizontalExpand="True" />
<Button Name="EmergencyShuttleRecallButton" VerticalAlignment="Center"
Text="{Loc 'comms-console-recall-button-label'}" />
<Control HorizontalExpand="True" />
</BoxContainer>
</BoxContainer>
</Control>

View file

@ -0,0 +1,67 @@
using Content.Shared.CCVar;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Configuration;
using Robust.Shared.Timing;
namespace Content.Client.Communications.UI.Widgets;
[GenerateTypedNameReferences]
public sealed partial class ShuttleControls : Control
{
[Dependency] private IGameTiming _timing = default!;
private bool _canCall;
private bool _countdownStarted;
private TimeSpan? _countdownEnd;
public event Action? OnShuttleCalled;
public event Action? OnShuttleRecalled;
public ShuttleControls()
{
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
EmergencyShuttleCallButton.OnPressed += _ => OnShuttleCalled?.Invoke();
EmergencyShuttleRecallButton.OnPressed += _ => OnShuttleRecalled?.Invoke();
SyncButtonState();
}
public void UpdateState(bool canCallShuttle, bool countdownStarted, TimeSpan? expectedCountdownEnd)
{
_canCall = canCallShuttle;
_countdownStarted = countdownStarted;
_countdownEnd = expectedCountdownEnd;
SyncButtonState();
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
UpdateCountdown();
}
/// <summary>
/// Syncs and animates the display of the time-to-shuttle-arrival label
/// </summary>
private void UpdateCountdown()
{
// Set the label on the LCD countdown
var countdown = _countdownEnd == null ? 0.0f : (float)Math.Max(_countdownEnd.Value.Subtract(_timing.CurTime).TotalSeconds, 0);
var remainingWholeSeconds = _countdownStarted ? (int)Math.Ceiling(countdown) : 0;
var message = (remainingWholeSeconds / 60).ToString("D2") + ":" + (remainingWholeSeconds % 60).ToString("D2");
CountdownLabel.Text = message;
}
/// <summary>
/// Configure the shuttle call/recall buttons to have a consistent state
/// </summary>
private void SyncButtonState()
{
EmergencyShuttleCallButton.Disabled = !(_canCall && !_countdownStarted);
EmergencyShuttleRecallButton.Disabled = !(_canCall && _countdownStarted);
}
}

View file

@ -1,20 +1,29 @@
# User interface
comms-console-menu-title = Communications Console
comms-console-menu-announcement-placeholder = Announcement text...
comms-console-menu-announcement-button = Announce
comms-console-menu-broadcast-placeholder = Broadcast text...
comms-console-menu-announcement-button = Announce over radio
comms-console-menu-announcement-button-tooltip = Send your message as a station-wide radio announcement.
comms-console-menu-broadcast-button = Broadcast
comms-console-menu-broadcast-button = Display on screens
comms-console-menu-broadcast-button-tooltip = Broadcast your message to wall-mounted screens around the station. Note: They fit only ten characters!
comms-console-menu-alert-level-button-tooltip = Change the station alert level. Applies immediately on selecting.
comms-console-menu-call-shuttle = Call emergency shuttle
comms-console-menu-recall-shuttle = Recall emergency shuttle
comms-console-menu-emergency-shuttle-button-tooltip = Calls or recalls the emergency shuttle. You can only recall when there's enough time left.
comms-console-menu-time-remaining = Time remaining: {$time}
comms-console-menu-alert-level-button-tooltip = Change the station alert level.
comms-console-change-alert-level-button = Change status
comms-console-confirm-alert-level-button = Confirm
comms-console-call-button-label = Call
comms-console-recall-button-label = Recall
comms-console-shuttle-status-label = Arrival time
comms-console-shuttle-controls-header = EMERGENCY SHUTTLE CONTROLS
comms-console-alert-current-level-header = Alert level
comms-console-station-announcements-header = Station Announcements
comms-console-station-broadcast-header = Station Broadcast
comms-console-announce-tab-title = Radio
comms-console-broadcast-tab-title = Screen
# Popup
comms-console-permission-denied = Permission denied
comms-console-shuttle-unavailable = Shuttle is currently unavailable
comms-console-message-too-long = Message is too long
comms-console-message-cannot-send = Communications temporarily disabled
# Placeholder values
comms-console-announcement-sent-by = Sent by
@ -26,3 +35,13 @@ comms-console-announcement-title-centcom = Central Command
comms-console-announcement-title-nukie = Syndicate Nuclear Operative
comms-console-announcement-title-station-ai = Station AI
comms-console-announcement-title-wizard = Wizard
# Flavor text for alert levels
comms-console-level-Green-flavour-label = Work is to proceed as normal
comms-console-level-Blue-flavour-label = Record suspicious activity with form NT-4772-b
comms-console-level-Violet-flavour-label = Maintain social distancing
comms-console-level-Yellow-flavour-label = Prepare to hold your breath
comms-console-level-Red-flavour-label = Remain vigilant
comms-console-level-Gamma-flavour-label = Suggest extreme caution
comms-console-level-Delta-flavour-label = Good luck
comms-console-level-Epsilon-flavour-label = You're fired