Hud refactor (#7202)
Co-authored-by: DrSmugleaf <DrSmugleaf@users.noreply.github.com> Co-authored-by: Jezithyr <jmaster9999@gmail.com> Co-authored-by: Jezithyr <Jezithyr@gmail.com> Co-authored-by: Visne <39844191+Visne@users.noreply.github.com> Co-authored-by: wrexbe <wrexbe@protonmail.com> Co-authored-by: wrexbe <81056464+wrexbe@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
using Content.Client.Alerts;
|
||||
using Content.Client.Gameplay;
|
||||
using Content.Client.UserInterface.Systems.Alerts.Widgets;
|
||||
using Content.Shared.Alert;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
|
||||
namespace Content.Client.UserInterface.Systems.Alerts;
|
||||
|
||||
public sealed class AlertsUIController : UIController, IOnStateEntered<GameplayState>, IOnSystemChanged<ClientAlertsSystem>
|
||||
{
|
||||
[UISystemDependency] private readonly ClientAlertsSystem? _alertsSystem = default;
|
||||
|
||||
private AlertsUI? UI => UIManager.GetActiveUIWidgetOrNull<AlertsUI>();
|
||||
|
||||
private void OnAlertPressed(object? sender, AlertType e)
|
||||
{
|
||||
_alertsSystem?.AlertClicked(e);
|
||||
}
|
||||
|
||||
private void SystemOnClearAlerts(object? sender, EventArgs e)
|
||||
{
|
||||
UI?.ClearAllControls();
|
||||
}
|
||||
|
||||
private void SystemOnSyncAlerts(object? sender, IReadOnlyDictionary<AlertKey, AlertState> e)
|
||||
{
|
||||
if (sender is ClientAlertsSystem system)
|
||||
UI?.SyncControls(system, system.AlertOrder, e);
|
||||
}
|
||||
|
||||
public void OnSystemLoaded(ClientAlertsSystem system)
|
||||
{
|
||||
system.SyncAlerts += SystemOnSyncAlerts;
|
||||
system.ClearAlerts += SystemOnClearAlerts;
|
||||
}
|
||||
|
||||
public void OnSystemUnloaded(ClientAlertsSystem system)
|
||||
{
|
||||
system.SyncAlerts -= SystemOnSyncAlerts;
|
||||
system.ClearAlerts -= SystemOnClearAlerts;
|
||||
}
|
||||
|
||||
public void OnStateEntered(GameplayState state)
|
||||
{
|
||||
if (UI != null)
|
||||
{
|
||||
UI.AlertPressed += OnAlertPressed;
|
||||
}
|
||||
|
||||
// initially populate the frame if system is available
|
||||
var alerts = _alertsSystem?.ActiveAlerts;
|
||||
if (alerts != null)
|
||||
{
|
||||
SystemOnSyncAlerts(_alertsSystem, alerts);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
using Content.Client.Actions.UI;
|
||||
using Content.Client.Cooldown;
|
||||
using Content.Shared.Alert;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.UserInterface.Systems.Alerts.Controls
|
||||
{
|
||||
public sealed class AlertControl : BaseButton
|
||||
{
|
||||
// shorter than default tooltip delay so user can more easily
|
||||
// see what alerts they have
|
||||
private const float CustomTooltipDelay = 0.5f;
|
||||
|
||||
public AlertPrototype Alert { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Current cooldown displayed in this slot. Set to null to show no cooldown.
|
||||
/// </summary>
|
||||
public (TimeSpan Start, TimeSpan End)? Cooldown
|
||||
{
|
||||
get => _cooldown;
|
||||
set
|
||||
{
|
||||
_cooldown = value;
|
||||
if (SuppliedTooltip is ActionAlertTooltip actionAlertTooltip)
|
||||
{
|
||||
actionAlertTooltip.Cooldown = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private (TimeSpan Start, TimeSpan End)? _cooldown;
|
||||
|
||||
private short? _severity;
|
||||
private readonly IGameTiming _gameTiming;
|
||||
private readonly AnimatedTextureRect _icon;
|
||||
private readonly CooldownGraphic _cooldownGraphic;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an alert control reflecting the indicated alert + state
|
||||
/// </summary>
|
||||
/// <param name="alert">alert to display</param>
|
||||
/// <param name="severity">severity of alert, null if alert doesn't have severity levels</param>
|
||||
public AlertControl(AlertPrototype alert, short? severity)
|
||||
{
|
||||
_gameTiming = IoCManager.Resolve<IGameTiming>();
|
||||
TooltipDelay = CustomTooltipDelay;
|
||||
TooltipSupplier = SupplyTooltip;
|
||||
Alert = alert;
|
||||
_severity = severity;
|
||||
var specifier = alert.GetIcon(_severity);
|
||||
_icon = new AnimatedTextureRect
|
||||
{
|
||||
DisplayRect = {TextureScale = (2, 2)}
|
||||
};
|
||||
|
||||
_icon.SetFromSpriteSpecifier(specifier);
|
||||
|
||||
Children.Add(_icon);
|
||||
_cooldownGraphic = new CooldownGraphic();
|
||||
Children.Add(_cooldownGraphic);
|
||||
}
|
||||
|
||||
private Control SupplyTooltip(Control? sender)
|
||||
{
|
||||
return new ActionAlertTooltip(Alert.Name, Alert.Description) {Cooldown = Cooldown};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Change the alert severity, changing the displayed icon
|
||||
/// </summary>
|
||||
public void SetSeverity(short? severity)
|
||||
{
|
||||
if (_severity != severity)
|
||||
{
|
||||
_severity = severity;
|
||||
_icon.SetFromSpriteSpecifier(Alert.GetIcon(_severity));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
if (!Cooldown.HasValue)
|
||||
{
|
||||
_cooldownGraphic.Visible = false;
|
||||
_cooldownGraphic.Progress = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
_cooldownGraphic.FromTime(Cooldown.Value.Start, Cooldown.Value.End);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<widgets:AlertsUI xmlns="https://spacestation14.io"
|
||||
xmlns:widgets="clr-namespace:Content.Client.UserInterface.Systems.Alerts.Widgets"
|
||||
MinSize="64 64">
|
||||
<PanelContainer HorizontalAlignment="Right" VerticalAlignment="Top">
|
||||
<BoxContainer Name="AlertContainer" Access="Public" Orientation="Vertical" />
|
||||
</PanelContainer>
|
||||
</widgets:AlertsUI>
|
||||
@@ -0,0 +1,154 @@
|
||||
using Content.Client.UserInterface.Systems.Alerts.Controls;
|
||||
using Content.Shared.Alert;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Input;
|
||||
|
||||
namespace Content.Client.UserInterface.Systems.Alerts.Widgets;
|
||||
|
||||
/// <summary>
|
||||
/// The status effects display on the right side of the screen.
|
||||
/// </summary>
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class AlertsUI : UIWidget
|
||||
{
|
||||
// also known as Control.Children?
|
||||
private readonly Dictionary<AlertKey, AlertControl> _alertControls = new();
|
||||
|
||||
public AlertsUI()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public void SyncControls(AlertsSystem alertsSystem, AlertOrderPrototype? alertOrderPrototype,
|
||||
IReadOnlyDictionary<AlertKey, AlertState> alertStates)
|
||||
{
|
||||
// remove any controls with keys no longer present
|
||||
if (SyncRemoveControls(alertStates))
|
||||
return;
|
||||
|
||||
// now we know that alertControls contains alerts that should still exist but
|
||||
// may need to updated,
|
||||
// also there may be some new alerts we need to show.
|
||||
// further, we need to ensure they are ordered w.r.t their configured order
|
||||
SyncUpdateControls(alertsSystem, alertOrderPrototype, alertStates);
|
||||
}
|
||||
|
||||
public void ClearAllControls()
|
||||
{
|
||||
foreach (var alertControl in _alertControls.Values)
|
||||
{
|
||||
alertControl.OnPressed -= AlertControlPressed;
|
||||
alertControl.Dispose();
|
||||
}
|
||||
|
||||
_alertControls.Clear();
|
||||
}
|
||||
|
||||
public event EventHandler<AlertType>? AlertPressed;
|
||||
|
||||
private bool SyncRemoveControls(IReadOnlyDictionary<AlertKey, AlertState> alertStates)
|
||||
{
|
||||
var toRemove = new List<AlertKey>();
|
||||
foreach (var existingKey in _alertControls.Keys)
|
||||
{
|
||||
if (!alertStates.ContainsKey(existingKey))
|
||||
toRemove.Add(existingKey);
|
||||
}
|
||||
|
||||
foreach (var alertKeyToRemove in toRemove)
|
||||
{
|
||||
_alertControls.Remove(alertKeyToRemove, out var control);
|
||||
if (control == null)
|
||||
return true;
|
||||
AlertContainer.Children.Remove(control);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void SyncUpdateControls(AlertsSystem alertsSystem, AlertOrderPrototype? alertOrderPrototype,
|
||||
IReadOnlyDictionary<AlertKey, AlertState> alertStates)
|
||||
{
|
||||
foreach (var (alertKey, alertState) in alertStates)
|
||||
{
|
||||
if (!alertKey.AlertType.HasValue)
|
||||
{
|
||||
Logger.WarningS("alert", "found alertkey without alerttype," +
|
||||
" alert keys should never be stored without an alerttype set: {0}", alertKey);
|
||||
continue;
|
||||
}
|
||||
|
||||
var alertType = alertKey.AlertType.Value;
|
||||
if (!alertsSystem.TryGet(alertType, out var newAlert))
|
||||
{
|
||||
Logger.ErrorS("alert", "Unrecognized alertType {0}", alertType);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_alertControls.TryGetValue(newAlert.AlertKey, out var existingAlertControl) &&
|
||||
existingAlertControl.Alert.AlertType == newAlert.AlertType)
|
||||
{
|
||||
// key is the same, simply update the existing control severity / cooldown
|
||||
existingAlertControl.SetSeverity(alertState.Severity);
|
||||
existingAlertControl.Cooldown = alertState.Cooldown;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (existingAlertControl != null) AlertContainer.Children.Remove(existingAlertControl);
|
||||
|
||||
// this is a new alert + alert key or just a different alert with the same
|
||||
// key, create the control and add it in the appropriate order
|
||||
var newAlertControl = CreateAlertControl(newAlert, alertState);
|
||||
|
||||
//TODO: Can the presenter sort the states before giving it to us?
|
||||
if (alertOrderPrototype != null)
|
||||
{
|
||||
var added = false;
|
||||
foreach (var alertControl in AlertContainer.Children)
|
||||
{
|
||||
if (alertOrderPrototype.Compare(newAlert, ((AlertControl) alertControl).Alert) >= 0)
|
||||
continue;
|
||||
|
||||
var idx = alertControl.GetPositionInParent();
|
||||
AlertContainer.Children.Add(newAlertControl);
|
||||
newAlertControl.SetPositionInParent(idx);
|
||||
added = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!added)
|
||||
AlertContainer.Children.Add(newAlertControl);
|
||||
}
|
||||
else
|
||||
{
|
||||
AlertContainer.Children.Add(newAlertControl);
|
||||
}
|
||||
|
||||
_alertControls[newAlert.AlertKey] = newAlertControl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private AlertControl CreateAlertControl(AlertPrototype alert, AlertState alertState)
|
||||
{
|
||||
var alertControl = new AlertControl(alert, alertState.Severity)
|
||||
{
|
||||
Cooldown = alertState.Cooldown
|
||||
};
|
||||
alertControl.OnPressed += AlertControlPressed;
|
||||
return alertControl;
|
||||
}
|
||||
|
||||
private void AlertControlPressed(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
if (args.Button is not AlertControl control)
|
||||
return;
|
||||
|
||||
if (args.Event.Function != EngineKeyFunctions.UIClick)
|
||||
return;
|
||||
|
||||
AlertPressed?.Invoke(this, control.Alert.AlertType);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user