diff --git a/Content.Client/Administration/UI/AdminRemarks/AdminMessagePopupMessage.xaml.cs b/Content.Client/Administration/UI/AdminRemarks/AdminMessagePopupMessage.xaml.cs index 7bb425f618..c8d7e915a1 100644 --- a/Content.Client/Administration/UI/AdminRemarks/AdminMessagePopupMessage.xaml.cs +++ b/Content.Client/Administration/UI/AdminRemarks/AdminMessagePopupMessage.xaml.cs @@ -1,4 +1,4 @@ -using Content.Shared.Administration.Notes; +using Content.Shared.Administration.Notes; using Robust.Client.AutoGenerated; using Robust.Client.UserInterface; using Robust.Client.UserInterface.XAML; @@ -13,7 +13,7 @@ public AdminMessagePopupMessage(AdminMessageEuiState.Message message) { RobustXamlLoader.Load(this); - Admin.SetMessage(FormattedMessage.FromMarkup(Loc.GetString( + Admin.SetMessage(FormattedMessage.FromMarkupOrThrow(Loc.GetString( "admin-notes-message-admin", ("admin", message.AdminName), ("date", message.AddedOn.ToLocalTime())))); diff --git a/Content.Client/Administration/UI/AdminRemarks/AdminMessagePopupWindow.xaml.cs b/Content.Client/Administration/UI/AdminRemarks/AdminMessagePopupWindow.xaml.cs index bf2ca9bec4..148cbf4e18 100644 --- a/Content.Client/Administration/UI/AdminRemarks/AdminMessagePopupWindow.xaml.cs +++ b/Content.Client/Administration/UI/AdminRemarks/AdminMessagePopupWindow.xaml.cs @@ -49,7 +49,7 @@ public void SetState(AdminMessageEuiState state) MessageContainer.AddChild(new AdminMessagePopupMessage(message)); } - Description.SetMessage(FormattedMessage.FromMarkup(Loc.GetString("admin-notes-message-desc", ("count", state.Messages.Length)))); + Description.SetMessage(FormattedMessage.FromMarkupOrThrow(Loc.GetString("admin-notes-message-desc", ("count", state.Messages.Length)))); } private void OnDismissButtonPressed(BaseButton.ButtonEventArgs obj) diff --git a/Content.Client/Administration/UI/Bwoink/BwoinkPanel.xaml.cs b/Content.Client/Administration/UI/Bwoink/BwoinkPanel.xaml.cs index 7a032d0bbd..833514e4aa 100644 --- a/Content.Client/Administration/UI/Bwoink/BwoinkPanel.xaml.cs +++ b/Content.Client/Administration/UI/Bwoink/BwoinkPanel.xaml.cs @@ -59,7 +59,7 @@ public void ReceiveLine(SharedBwoinkSystem.BwoinkTextMessage message) Unread++; var formatted = new FormattedMessage(1); - formatted.AddMarkup($"[color=gray]{message.SentAt.ToShortTimeString()}[/color] {message.Text}"); + formatted.AddMarkupOrThrow($"[color=gray]{message.SentAt.ToShortTimeString()}[/color] {message.Text}"); TextOutput.AddMessage(formatted); LastMessage = message.SentAt; } diff --git a/Content.Client/Anomaly/Ui/AnomalyGeneratorBoundUserInterface.cs b/Content.Client/Anomaly/Ui/AnomalyGeneratorBoundUserInterface.cs index 5d1985485c..f088ac1976 100644 --- a/Content.Client/Anomaly/Ui/AnomalyGeneratorBoundUserInterface.cs +++ b/Content.Client/Anomaly/Ui/AnomalyGeneratorBoundUserInterface.cs @@ -1,7 +1,5 @@ using Content.Shared.Anomaly; -using Content.Shared.Gravity; using JetBrains.Annotations; -using Robust.Client.GameObjects; using Robust.Client.UserInterface; namespace Content.Client.Anomaly.Ui; diff --git a/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml b/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml new file mode 100644 index 0000000000..6bdfb3989f --- /dev/null +++ b/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml.cs b/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml.cs new file mode 100644 index 0000000000..79bb66560e --- /dev/null +++ b/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml.cs @@ -0,0 +1,215 @@ +using Content.Client.Stylesheets; +using Content.Shared.Atmos; +using Content.Shared.Atmos.Components; +using Content.Shared.Atmos.Monitor; +using Content.Shared.FixedPoint; +using Content.Shared.Temperature; +using Robust.Client.AutoGenerated; +using Robust.Client.Graphics; +using Robust.Client.ResourceManagement; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Map; +using System.Linq; + +namespace Content.Client.Atmos.Consoles; + +[GenerateTypedNameReferences] +public sealed partial class AtmosAlarmEntryContainer : BoxContainer +{ + public NetEntity NetEntity; + public EntityCoordinates? Coordinates; + + private readonly IEntityManager _entManager; + private readonly IResourceCache _cache; + + private Dictionary _alarmStrings = new Dictionary() + { + [AtmosAlarmType.Invalid] = "atmos-alerts-window-invalid-state", + [AtmosAlarmType.Normal] = "atmos-alerts-window-normal-state", + [AtmosAlarmType.Warning] = "atmos-alerts-window-warning-state", + [AtmosAlarmType.Danger] = "atmos-alerts-window-danger-state", + }; + + private Dictionary _gasShorthands = new Dictionary() + { + [Gas.Ammonia] = "NH₃", + [Gas.CarbonDioxide] = "CO₂", + [Gas.Frezon] = "F", + [Gas.Nitrogen] = "N₂", + [Gas.NitrousOxide] = "N₂O", + [Gas.Oxygen] = "O₂", + [Gas.Plasma] = "P", + [Gas.Tritium] = "T", + [Gas.WaterVapor] = "H₂O", + }; + + public AtmosAlarmEntryContainer(NetEntity uid, EntityCoordinates? coordinates) + { + RobustXamlLoader.Load(this); + + _entManager = IoCManager.Resolve(); + _cache = IoCManager.Resolve(); + + NetEntity = uid; + Coordinates = coordinates; + + // Load fonts + var headerFont = new VectorFont(_cache.GetResource("/Fonts/NotoSans/NotoSans-Bold.ttf"), 11); + var normalFont = new VectorFont(_cache.GetResource("/Fonts/NotoSansDisplay/NotoSansDisplay-Regular.ttf"), 11); + var smallFont = new VectorFont(_cache.GetResource("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10); + + // Set fonts + TemperatureHeaderLabel.FontOverride = headerFont; + PressureHeaderLabel.FontOverride = headerFont; + OxygenationHeaderLabel.FontOverride = headerFont; + GasesHeaderLabel.FontOverride = headerFont; + + TemperatureLabel.FontOverride = normalFont; + PressureLabel.FontOverride = normalFont; + OxygenationLabel.FontOverride = normalFont; + + NoDataLabel.FontOverride = headerFont; + + SilenceCheckBox.Label.FontOverride = smallFont; + SilenceCheckBox.Label.FontColorOverride = Color.DarkGray; + } + + public void UpdateEntry(AtmosAlertsComputerEntry entry, bool isFocus, AtmosAlertsFocusDeviceData? focusData = null) + { + NetEntity = entry.NetEntity; + Coordinates = _entManager.GetCoordinates(entry.Coordinates); + + // Load fonts + var normalFont = new VectorFont(_cache.GetResource("/Fonts/NotoSansDisplay/NotoSansDisplay-Regular.ttf"), 11); + + // Update alarm state + if (!_alarmStrings.TryGetValue(entry.AlarmState, out var alarmString)) + alarmString = "atmos-alerts-window-invalid-state"; + + AlarmStateLabel.Text = Loc.GetString(alarmString); + AlarmStateLabel.FontColorOverride = GetAlarmStateColor(entry.AlarmState); + + // Update alarm name + AlarmNameLabel.Text = Loc.GetString("atmos-alerts-window-alarm-label", ("name", entry.EntityName), ("address", entry.Address)); + + // Focus updates + FocusContainer.Visible = isFocus; + + if (isFocus) + SetAsFocus(); + else + RemoveAsFocus(); + + if (isFocus && entry.Group == AtmosAlertsComputerGroup.AirAlarm) + { + MainDataContainer.Visible = (entry.AlarmState != AtmosAlarmType.Invalid); + NoDataLabel.Visible = (entry.AlarmState == AtmosAlarmType.Invalid); + + if (focusData != null) + { + // Update temperature + var tempK = (FixedPoint2)focusData.Value.TemperatureData.Item1; + var tempC = (FixedPoint2)TemperatureHelpers.KelvinToCelsius(tempK.Float()); + + TemperatureLabel.Text = Loc.GetString("atmos-alerts-window-temperature-value", ("valueInC", tempC), ("valueInK", tempK)); + TemperatureLabel.FontColorOverride = GetAlarmStateColor(focusData.Value.TemperatureData.Item2); + + // Update pressure + PressureLabel.Text = Loc.GetString("atmos-alerts-window-pressure-value", ("value", (FixedPoint2)focusData.Value.PressureData.Item1)); + PressureLabel.FontColorOverride = GetAlarmStateColor(focusData.Value.PressureData.Item2); + + // Update oxygenation + var oxygenPercent = (FixedPoint2)0f; + var oxygenAlert = AtmosAlarmType.Invalid; + + if (focusData.Value.GasData.TryGetValue(Gas.Oxygen, out var oxygenData)) + { + oxygenPercent = oxygenData.Item2 * 100f; + oxygenAlert = oxygenData.Item3; + } + + OxygenationLabel.Text = Loc.GetString("atmos-alerts-window-oxygenation-value", ("value", oxygenPercent)); + OxygenationLabel.FontColorOverride = GetAlarmStateColor(oxygenAlert); + + // Update other present gases + GasGridContainer.RemoveAllChildren(); + + var gasData = focusData.Value.GasData.Where(g => g.Key != Gas.Oxygen); + + if (gasData.Count() == 0) + { + // No other gases + var gasLabel = new Label() + { + Text = Loc.GetString("atmos-alerts-window-other-gases-value-nil"), + FontOverride = normalFont, + FontColorOverride = StyleNano.DisabledFore, + HorizontalAlignment = HAlignment.Center, + VerticalAlignment = VAlignment.Center, + HorizontalExpand = true, + Margin = new Thickness(0, 2, 0, 0), + SetHeight = 24f, + }; + + GasGridContainer.AddChild(gasLabel); + } + + else + { + // Add an entry for each gas + foreach ((var gas, (var mol, var percent, var alert)) in gasData) + { + var gasPercent = (FixedPoint2)0f; + gasPercent = percent * 100f; + + if (!_gasShorthands.TryGetValue(gas, out var gasShorthand)) + gasShorthand = "X"; + + var gasLabel = new Label() + { + Text = Loc.GetString("atmos-alerts-window-other-gases-value", ("shorthand", gasShorthand), ("value", gasPercent)), + FontOverride = normalFont, + FontColorOverride = GetAlarmStateColor(alert), + HorizontalAlignment = HAlignment.Center, + VerticalAlignment = VAlignment.Center, + HorizontalExpand = true, + Margin = new Thickness(0, 2, 0, 0), + SetHeight = 24f, + }; + + GasGridContainer.AddChild(gasLabel); + } + } + } + } + } + + public void SetAsFocus() + { + FocusButton.AddStyleClass(StyleNano.StyleClassButtonColorGreen); + ArrowTexture.TexturePath = "/Textures/Interface/Nano/inverted_triangle.svg.png"; + } + + public void RemoveAsFocus() + { + FocusButton.RemoveStyleClass(StyleNano.StyleClassButtonColorGreen); + ArrowTexture.TexturePath = "/Textures/Interface/Nano/triangle_right.png"; + FocusContainer.Visible = false; + } + + private Color GetAlarmStateColor(AtmosAlarmType alarmType) + { + switch (alarmType) + { + case AtmosAlarmType.Normal: + return StyleNano.GoodGreenFore; + case AtmosAlarmType.Warning: + return StyleNano.ConcerningOrangeFore; + case AtmosAlarmType.Danger: + return StyleNano.DangerousRedFore; + } + + return StyleNano.DisabledFore; + } +} diff --git a/Content.Client/Atmos/Consoles/AtmosAlertsComputerBoundUserInterface.cs b/Content.Client/Atmos/Consoles/AtmosAlertsComputerBoundUserInterface.cs new file mode 100644 index 0000000000..08cae979b9 --- /dev/null +++ b/Content.Client/Atmos/Consoles/AtmosAlertsComputerBoundUserInterface.cs @@ -0,0 +1,52 @@ +using Content.Shared.Atmos.Components; + +namespace Content.Client.Atmos.Consoles; + +public sealed class AtmosAlertsComputerBoundUserInterface : BoundUserInterface +{ + [ViewVariables] + private AtmosAlertsComputerWindow? _menu; + + public AtmosAlertsComputerBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) { } + + protected override void Open() + { + _menu = new AtmosAlertsComputerWindow(this, Owner); + _menu.OpenCentered(); + _menu.OnClose += Close; + + EntMan.TryGetComponent(Owner, out var xform); + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + + var castState = (AtmosAlertsComputerBoundInterfaceState) state; + + if (castState == null) + return; + + EntMan.TryGetComponent(Owner, out var xform); + _menu?.UpdateUI(xform?.Coordinates, castState.AirAlarms, castState.FireAlarms, castState.FocusData); + } + + public void SendFocusChangeMessage(NetEntity? netEntity) + { + SendMessage(new AtmosAlertsComputerFocusChangeMessage(netEntity)); + } + + public void SendDeviceSilencedMessage(NetEntity netEntity, bool silenceDevice) + { + SendMessage(new AtmosAlertsComputerDeviceSilencedMessage(netEntity, silenceDevice)); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (!disposing) + return; + + _menu?.Dispose(); + } +} diff --git a/Content.Client/Atmos/Consoles/AtmosAlertsComputerWindow.xaml b/Content.Client/Atmos/Consoles/AtmosAlertsComputerWindow.xaml new file mode 100644 index 0000000000..8824a776ee --- /dev/null +++ b/Content.Client/Atmos/Consoles/AtmosAlertsComputerWindow.xaml @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Content.Client/Atmos/Consoles/AtmosAlertsComputerWindow.xaml.cs b/Content.Client/Atmos/Consoles/AtmosAlertsComputerWindow.xaml.cs new file mode 100644 index 0000000000..f0b7ffbe11 --- /dev/null +++ b/Content.Client/Atmos/Consoles/AtmosAlertsComputerWindow.xaml.cs @@ -0,0 +1,548 @@ +using Content.Client.Message; +using Content.Client.Pinpointer.UI; +using Content.Client.Stylesheets; +using Content.Client.UserInterface.Controls; +using Content.Shared.Atmos.Components; +using Content.Shared.Atmos.Monitor; +using Content.Shared.Pinpointer; +using Robust.Client.AutoGenerated; +using Robust.Client.GameObjects; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Map; +using Robust.Shared.Timing; +using Robust.Shared.Utility; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace Content.Client.Atmos.Consoles; + +[GenerateTypedNameReferences] +public sealed partial class AtmosAlertsComputerWindow : FancyWindow +{ + private readonly IEntityManager _entManager; + private readonly SpriteSystem _spriteSystem; + + private EntityUid? _owner; + private NetEntity? _trackedEntity; + + private AtmosAlertsComputerEntry[]? _airAlarms = null; + private AtmosAlertsComputerEntry[]? _fireAlarms = null; + private IEnumerable? _allAlarms = null; + + private IEnumerable? _activeAlarms = null; + private Dictionary _deviceSilencingProgress = new(); + + public event Action? SendFocusChangeMessageAction; + public event Action? SendDeviceSilencedMessageAction; + + private bool _autoScrollActive = false; + private bool _autoScrollAwaitsUpdate = false; + + private const float SilencingDuration = 2.5f; + + public AtmosAlertsComputerWindow(AtmosAlertsComputerBoundUserInterface userInterface, EntityUid? owner) + { + RobustXamlLoader.Load(this); + _entManager = IoCManager.Resolve(); + _spriteSystem = _entManager.System(); + + // Pass the owner to nav map + _owner = owner; + NavMap.Owner = _owner; + + // Set nav map colors + NavMap.WallColor = new Color(64, 64, 64); + NavMap.TileColor = Color.DimGray * NavMap.WallColor; + + // Set nav map grid uid + var stationName = Loc.GetString("atmos-alerts-window-unknown-location"); + + if (_entManager.TryGetComponent(owner, out var xform)) + { + NavMap.MapUid = xform.GridUid; + + // Assign station name + if (_entManager.TryGetComponent(xform.GridUid, out var stationMetaData)) + stationName = stationMetaData.EntityName; + + var msg = new FormattedMessage(); + msg.TryAddMarkup(Loc.GetString("atmos-alerts-window-station-name", ("stationName", stationName)), out _); + + StationName.SetMessage(msg); + } + + else + { + StationName.SetMessage(stationName); + NavMap.Visible = false; + } + + // Set trackable entity selected action + NavMap.TrackedEntitySelectedAction += SetTrackedEntityFromNavMap; + + // Update nav map + NavMap.ForceNavMapUpdate(); + + // Set tab container headers + MasterTabContainer.SetTabTitle(0, Loc.GetString("atmos-alerts-window-tab-no-alerts")); + MasterTabContainer.SetTabTitle(1, Loc.GetString("atmos-alerts-window-tab-air-alarms")); + MasterTabContainer.SetTabTitle(2, Loc.GetString("atmos-alerts-window-tab-fire-alarms")); + + // Set UI toggles + ShowInactiveAlarms.OnToggled += _ => OnShowAlarmsToggled(ShowInactiveAlarms, AtmosAlarmType.Invalid); + ShowNormalAlarms.OnToggled += _ => OnShowAlarmsToggled(ShowNormalAlarms, AtmosAlarmType.Normal); + ShowWarningAlarms.OnToggled += _ => OnShowAlarmsToggled(ShowWarningAlarms, AtmosAlarmType.Warning); + ShowDangerAlarms.OnToggled += _ => OnShowAlarmsToggled(ShowDangerAlarms, AtmosAlarmType.Danger); + + // Set atmos monitoring message action + SendFocusChangeMessageAction += userInterface.SendFocusChangeMessage; + SendDeviceSilencedMessageAction += userInterface.SendDeviceSilencedMessage; + } + + #region Toggle handling + + private void OnShowAlarmsToggled(CheckBox toggle, AtmosAlarmType toggledAlarmState) + { + if (_owner == null) + return; + + if (!_entManager.TryGetComponent(_owner.Value, out var console)) + return; + + foreach (var device in console.AtmosDevices) + { + var alarmState = GetAlarmState(device.NetEntity); + + if (toggledAlarmState != alarmState) + continue; + + if (toggle.Pressed) + AddTrackedEntityToNavMap(device, alarmState); + + else + NavMap.TrackedEntities.Remove(device.NetEntity); + } + } + + private void OnSilenceAlertsToggled(NetEntity netEntity, bool toggleState) + { + if (!_entManager.TryGetComponent(_owner, out var console)) + return; + + if (toggleState) + _deviceSilencingProgress[netEntity] = SilencingDuration; + + else + _deviceSilencingProgress.Remove(netEntity); + + foreach (AtmosAlarmEntryContainer entryContainer in AlertsTable.Children) + { + if (entryContainer.NetEntity == netEntity) + entryContainer.SilenceAlarmProgressBar.Visible = toggleState; + } + + SendDeviceSilencedMessageAction?.Invoke(netEntity, toggleState); + } + + #endregion + + public void UpdateUI(EntityCoordinates? consoleCoords, AtmosAlertsComputerEntry[] airAlarms, AtmosAlertsComputerEntry[] fireAlarms, AtmosAlertsFocusDeviceData? focusData) + { + if (_owner == null) + return; + + if (!_entManager.TryGetComponent(_owner.Value, out var console)) + return; + + if (_trackedEntity != focusData?.NetEntity) + { + SendFocusChangeMessageAction?.Invoke(_trackedEntity); + focusData = null; + } + + // Retain alarm data for use inbetween updates + _airAlarms = airAlarms; + _fireAlarms = fireAlarms; + _allAlarms = airAlarms.Concat(fireAlarms); + + var silenced = console.SilencedDevices; + + _activeAlarms = _allAlarms.Where(x => x.AlarmState > AtmosAlarmType.Normal && + (!silenced.Contains(x.NetEntity) || _deviceSilencingProgress.ContainsKey(x.NetEntity))); + + // Reset nav map data + NavMap.TrackedCoordinates.Clear(); + NavMap.TrackedEntities.Clear(); + + // Add tracked entities to the nav map + foreach (var device in console.AtmosDevices) + { + if (!NavMap.Visible) + continue; + + var alarmState = GetAlarmState(device.NetEntity); + + if (_trackedEntity != device.NetEntity) + { + // Skip air alarms if the appropriate overlay is off + if (!ShowInactiveAlarms.Pressed && alarmState == AtmosAlarmType.Invalid) + continue; + + if (!ShowNormalAlarms.Pressed && alarmState == AtmosAlarmType.Normal) + continue; + + if (!ShowWarningAlarms.Pressed && alarmState == AtmosAlarmType.Warning) + continue; + + if (!ShowDangerAlarms.Pressed && alarmState == AtmosAlarmType.Danger) + continue; + } + + AddTrackedEntityToNavMap(device, alarmState); + } + + // Show the monitor location + var consoleUid = _entManager.GetNetEntity(_owner); + + if (consoleCoords != null && consoleUid != null) + { + var texture = _spriteSystem.Frame0(new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_circle.png"))); + var blip = new NavMapBlip(consoleCoords.Value, texture, Color.Cyan, true, false); + NavMap.TrackedEntities[consoleUid.Value] = blip; + } + + // Update the nav map + NavMap.ForceNavMapUpdate(); + + // Clear excess children from the tables + var activeAlarmCount = _activeAlarms.Count(); + + while (AlertsTable.ChildCount > activeAlarmCount) + AlertsTable.RemoveChild(AlertsTable.GetChild(AlertsTable.ChildCount - 1)); + + while (AirAlarmsTable.ChildCount > airAlarms.Length) + AirAlarmsTable.RemoveChild(AirAlarmsTable.GetChild(AirAlarmsTable.ChildCount - 1)); + + while (FireAlarmsTable.ChildCount > fireAlarms.Length) + FireAlarmsTable.RemoveChild(FireAlarmsTable.GetChild(FireAlarmsTable.ChildCount - 1)); + + // Update all entries in each table + for (int index = 0; index < _activeAlarms.Count(); index++) + { + var entry = _activeAlarms.ElementAt(index); + UpdateUIEntry(entry, index, AlertsTable, console, focusData); + } + + for (int index = 0; index < airAlarms.Count(); index++) + { + var entry = airAlarms.ElementAt(index); + UpdateUIEntry(entry, index, AirAlarmsTable, console, focusData); + } + + for (int index = 0; index < fireAlarms.Count(); index++) + { + var entry = fireAlarms.ElementAt(index); + UpdateUIEntry(entry, index, FireAlarmsTable, console, focusData); + } + + // If no alerts are active, display a message + if (MasterTabContainer.CurrentTab == 0 && activeAlarmCount == 0) + { + var label = new RichTextLabel() + { + HorizontalExpand = true, + VerticalExpand = true, + HorizontalAlignment = HAlignment.Center, + VerticalAlignment = VAlignment.Center, + }; + + label.SetMarkup(Loc.GetString("atmos-alerts-window-no-active-alerts", ("color", StyleNano.GoodGreenFore.ToHexNoAlpha()))); + + AlertsTable.AddChild(label); + } + + // Update the alerts tab with the number of active alerts + if (activeAlarmCount == 0) + MasterTabContainer.SetTabTitle(0, Loc.GetString("atmos-alerts-window-tab-no-alerts")); + + else + MasterTabContainer.SetTabTitle(0, Loc.GetString("atmos-alerts-window-tab-alerts", ("value", activeAlarmCount))); + + // Auto-scroll re-enable + if (_autoScrollAwaitsUpdate) + { + _autoScrollActive = true; + _autoScrollAwaitsUpdate = false; + } + } + + private void AddTrackedEntityToNavMap(AtmosAlertsDeviceNavMapData metaData, AtmosAlarmType alarmState) + { + var data = GetBlipTexture(alarmState); + + if (data == null) + return; + + var texture = data.Value.Item1; + var color = data.Value.Item2; + var coords = _entManager.GetCoordinates(metaData.NetCoordinates); + + if (_trackedEntity != null && _trackedEntity != metaData.NetEntity) + color *= Color.DimGray; + + var selectable = true; + var blip = new NavMapBlip(coords, _spriteSystem.Frame0(texture), color, _trackedEntity == metaData.NetEntity, selectable); + + NavMap.TrackedEntities[metaData.NetEntity] = blip; + } + + private void UpdateUIEntry(AtmosAlertsComputerEntry entry, int index, Control table, AtmosAlertsComputerComponent console, AtmosAlertsFocusDeviceData? focusData = null) + { + // Make new UI entry if required + if (index >= table.ChildCount) + { + var newEntryContainer = new AtmosAlarmEntryContainer(entry.NetEntity, _entManager.GetCoordinates(entry.Coordinates)); + + // On click + newEntryContainer.FocusButton.OnButtonUp += args => + { + if (_trackedEntity == newEntryContainer.NetEntity) + { + _trackedEntity = null; + } + + else + { + _trackedEntity = newEntryContainer.NetEntity; + + if (newEntryContainer.Coordinates != null) + NavMap.CenterToCoordinates(newEntryContainer.Coordinates.Value); + } + + // Send message to console that the focus has changed + SendFocusChangeMessageAction?.Invoke(_trackedEntity); + + // Update affected UI elements across all tables + UpdateConsoleTable(console, AlertsTable, _trackedEntity); + UpdateConsoleTable(console, AirAlarmsTable, _trackedEntity); + UpdateConsoleTable(console, FireAlarmsTable, _trackedEntity); + }; + + // On toggling the silence check box + newEntryContainer.SilenceCheckBox.OnToggled += _ => OnSilenceAlertsToggled(newEntryContainer.NetEntity, newEntryContainer.SilenceCheckBox.Pressed); + + // Add the entry to the current table + table.AddChild(newEntryContainer); + } + + // Update values and UI elements + var tableChild = table.GetChild(index); + + if (tableChild is not AtmosAlarmEntryContainer) + { + table.RemoveChild(tableChild); + UpdateUIEntry(entry, index, table, console, focusData); + + return; + } + + var entryContainer = (AtmosAlarmEntryContainer)tableChild; + + entryContainer.UpdateEntry(entry, entry.NetEntity == _trackedEntity, focusData); + + if (_trackedEntity != entry.NetEntity) + { + var silenced = console.SilencedDevices; + entryContainer.SilenceCheckBox.Pressed = (silenced.Contains(entry.NetEntity) || _deviceSilencingProgress.ContainsKey(entry.NetEntity)); + } + + entryContainer.SilenceAlarmProgressBar.Visible = (table == AlertsTable && _deviceSilencingProgress.ContainsKey(entry.NetEntity)); + } + + private void UpdateConsoleTable(AtmosAlertsComputerComponent console, Control table, NetEntity? currTrackedEntity) + { + foreach (var tableChild in table.Children) + { + if (tableChild is not AtmosAlarmEntryContainer) + continue; + + var entryContainer = (AtmosAlarmEntryContainer)tableChild; + + if (entryContainer.NetEntity != currTrackedEntity) + entryContainer.RemoveAsFocus(); + + else if (entryContainer.NetEntity == currTrackedEntity) + entryContainer.SetAsFocus(); + } + } + + private void SetTrackedEntityFromNavMap(NetEntity? netEntity) + { + if (netEntity == null) + return; + + if (!_entManager.TryGetComponent(_owner, out var console)) + return; + + _trackedEntity = netEntity; + + if (netEntity != null) + { + // Tab switching + if (MasterTabContainer.CurrentTab != 0 || _activeAlarms?.Any(x => x.NetEntity == netEntity) == false) + { + var device = console.AtmosDevices.FirstOrNull(x => x.NetEntity == netEntity); + + switch (device?.Group) + { + case AtmosAlertsComputerGroup.AirAlarm: + MasterTabContainer.CurrentTab = 1; break; + case AtmosAlertsComputerGroup.FireAlarm: + MasterTabContainer.CurrentTab = 2; break; + } + } + + // Get the scroll position of the selected entity on the selected button the UI + ActivateAutoScrollToFocus(); + } + + // Send message to console that the focus has changed + SendFocusChangeMessageAction?.Invoke(_trackedEntity); + } + + protected override void FrameUpdate(FrameEventArgs args) + { + AutoScrollToFocus(); + + // Device silencing update + foreach ((var device, var remainingTime) in _deviceSilencingProgress) + { + var t = remainingTime - args.DeltaSeconds; + + if (t <= 0) + { + _deviceSilencingProgress.Remove(device); + + if (device == _trackedEntity) + _trackedEntity = null; + } + + else + _deviceSilencingProgress[device] = t; + } + } + + private void ActivateAutoScrollToFocus() + { + _autoScrollActive = false; + _autoScrollAwaitsUpdate = true; + } + + private void AutoScrollToFocus() + { + if (!_autoScrollActive) + return; + + var scroll = MasterTabContainer.Children.ElementAt(MasterTabContainer.CurrentTab) as ScrollContainer; + if (scroll == null) + return; + + if (!TryGetVerticalScrollbar(scroll, out var vScrollbar)) + return; + + if (!TryGetNextScrollPosition(out float? nextScrollPosition)) + return; + + vScrollbar.ValueTarget = nextScrollPosition.Value; + + if (MathHelper.CloseToPercent(vScrollbar.Value, vScrollbar.ValueTarget)) + _autoScrollActive = false; + } + + private bool TryGetVerticalScrollbar(ScrollContainer scroll, [NotNullWhen(true)] out VScrollBar? vScrollBar) + { + vScrollBar = null; + + foreach (var child in scroll.Children) + { + if (child is not VScrollBar) + continue; + + var castChild = child as VScrollBar; + + if (castChild != null) + { + vScrollBar = castChild; + return true; + } + } + + return false; + } + + private bool TryGetNextScrollPosition([NotNullWhen(true)] out float? nextScrollPosition) + { + nextScrollPosition = null; + + var scroll = MasterTabContainer.Children.ElementAt(MasterTabContainer.CurrentTab) as ScrollContainer; + if (scroll == null) + return false; + + var container = scroll.Children.ElementAt(0) as BoxContainer; + if (container == null || container.Children.Count() == 0) + return false; + + // Exit if the heights of the children haven't been initialized yet + if (!container.Children.Any(x => x.Height > 0)) + return false; + + nextScrollPosition = 0; + + foreach (var control in container.Children) + { + if (control == null || control is not AtmosAlarmEntryContainer) + continue; + + if (((AtmosAlarmEntryContainer)control).NetEntity == _trackedEntity) + return true; + + nextScrollPosition += control.Height; + } + + // Failed to find control + nextScrollPosition = null; + + return false; + } + + private AtmosAlarmType GetAlarmState(NetEntity netEntity) + { + var alarmState = _allAlarms?.FirstOrNull(x => x.NetEntity == netEntity)?.AlarmState; + + if (alarmState == null) + return AtmosAlarmType.Invalid; + + return alarmState.Value; + } + + private (SpriteSpecifier.Texture, Color)? GetBlipTexture(AtmosAlarmType alarmState) + { + (SpriteSpecifier.Texture, Color)? output = null; + + switch (alarmState) + { + case AtmosAlarmType.Invalid: + output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_circle.png")), StyleNano.DisabledFore); break; + case AtmosAlarmType.Normal: + output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_circle.png")), Color.LimeGreen); break; + case AtmosAlarmType.Warning: + output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_triangle.png")), new Color(255, 182, 72)); break; + case AtmosAlarmType.Danger: + output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_square.png")), new Color(255, 67, 67)); break; + } + + return output; + } +} diff --git a/Content.Client/Changelog/ChangelogTab.xaml.cs b/Content.Client/Changelog/ChangelogTab.xaml.cs index 8fbeaab5f4..ca86f8a6b2 100644 --- a/Content.Client/Changelog/ChangelogTab.xaml.cs +++ b/Content.Client/Changelog/ChangelogTab.xaml.cs @@ -131,13 +131,13 @@ public void PopulateChangelog(ChangelogManager.Changelog changelog) Margin = new Thickness(6, 0, 0, 0), }; authorLabel.SetMessage( - FormattedMessage.FromMarkup(Loc.GetString("changelog-author-changed", ("author", author)))); + FormattedMessage.FromMarkupOrThrow(Loc.GetString("changelog-author-changed", ("author", author)))); ChangelogBody.AddChild(authorLabel); foreach (var change in groupedEntry.SelectMany(c => c.Changes)) { var text = new RichTextLabel(); - text.SetMessage(FormattedMessage.FromMarkup(change.Message)); + text.SetMessage(FormattedMessage.FromMarkupOrThrow(change.Message)); ChangelogBody.AddChild(new BoxContainer { Orientation = LayoutOrientation.Horizontal, diff --git a/Content.Client/Chat/UI/SpeechBubble.cs b/Content.Client/Chat/UI/SpeechBubble.cs index adb61d10e6..32e9f4ae9b 100644 --- a/Content.Client/Chat/UI/SpeechBubble.cs +++ b/Content.Client/Chat/UI/SpeechBubble.cs @@ -180,7 +180,7 @@ protected FormattedMessage FormatSpeech(string message, Color? fontColor = null) var msg = new FormattedMessage(); if (fontColor != null) msg.PushColor(fontColor.Value); - msg.AddMarkup(message); + msg.AddMarkupOrThrow(message); return msg; } diff --git a/Content.Client/Chemistry/EntitySystems/ChemistryGuideDataSystem.cs b/Content.Client/Chemistry/EntitySystems/ChemistryGuideDataSystem.cs index a3cedb5f2f..7c7d824ee9 100644 --- a/Content.Client/Chemistry/EntitySystems/ChemistryGuideDataSystem.cs +++ b/Content.Client/Chemistry/EntitySystems/ChemistryGuideDataSystem.cs @@ -1,5 +1,5 @@ using System.Linq; -using Content.Client.Chemistry.Containers.EntitySystems; +using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Atmos.Prototypes; using Content.Shared.Body.Part; using Content.Shared.Chemistry; @@ -16,7 +16,7 @@ namespace Content.Client.Chemistry.EntitySystems; /// public sealed class ChemistryGuideDataSystem : SharedChemistryGuideDataSystem { - [Dependency] private readonly SolutionContainerSystem _solutionContainer = default!; + [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!; [ValidatePrototypeId] private const string DefaultMixingCategory = "DummyMix"; diff --git a/Content.Client/Clothing/ClientClothingSystem.cs b/Content.Client/Clothing/ClientClothingSystem.cs index 96bbcc54f2..27d77eda49 100644 --- a/Content.Client/Clothing/ClientClothingSystem.cs +++ b/Content.Client/Clothing/ClientClothingSystem.cs @@ -50,7 +50,6 @@ public sealed class ClientClothingSystem : ClothingSystem }; [Dependency] private readonly IResourceCache _cache = default!; - [Dependency] private readonly ISerializationManager _serialization = default!; [Dependency] private readonly InventorySystem _inventorySystem = default!; [Dependency] private readonly DisplacementMapSystem _displacement = default!; diff --git a/Content.Client/Commands/ActionsCommands.cs b/Content.Client/Commands/ActionsCommands.cs index 593b8e8256..c155c7a9de 100644 --- a/Content.Client/Commands/ActionsCommands.cs +++ b/Content.Client/Commands/ActionsCommands.cs @@ -1,6 +1,4 @@ using Content.Client.Actions; -using Content.Client.Actions; -using Content.Client.Mapping; using Content.Shared.Administration; using Robust.Shared.Console; diff --git a/Content.Client/Commands/MappingClientSideSetupCommand.cs b/Content.Client/Commands/MappingClientSideSetupCommand.cs index 3255e85e18..d17f1fccaf 100644 --- a/Content.Client/Commands/MappingClientSideSetupCommand.cs +++ b/Content.Client/Commands/MappingClientSideSetupCommand.cs @@ -13,7 +13,6 @@ internal sealed class MappingClientSideSetupCommand : LocalizedCommands { [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; [Dependency] private readonly ILightManager _lightManager = default!; - [Dependency] private readonly IStateManager _stateManager = default!; public override string Command => "mappingclientsidesetup"; diff --git a/Content.Client/Computer/ComputerBoundUserInterface.cs b/Content.Client/Computer/ComputerBoundUserInterface.cs index 11c26b252e..9f34eeda20 100644 --- a/Content.Client/Computer/ComputerBoundUserInterface.cs +++ b/Content.Client/Computer/ComputerBoundUserInterface.cs @@ -11,8 +11,6 @@ namespace Content.Client.Computer [Virtual] public class ComputerBoundUserInterface : ComputerBoundUserInterfaceBase where TWindow : BaseWindow, IComputerWindow, new() where TState : BoundUserInterfaceState { - [Dependency] private readonly IDynamicTypeFactory _dynamicTypeFactory = default!; - [ViewVariables] private TWindow? _window; diff --git a/Content.Client/Credits/CreditsWindow.xaml.cs b/Content.Client/Credits/CreditsWindow.xaml.cs index 60ac579845..ba24020953 100644 --- a/Content.Client/Credits/CreditsWindow.xaml.cs +++ b/Content.Client/Credits/CreditsWindow.xaml.cs @@ -145,7 +145,7 @@ void AddSection(string title, string path, bool markup = false) var text = _resourceManager.ContentFileReadAllText($"/Credits/{path}"); if (markup) { - label.SetMessage(FormattedMessage.FromMarkup(text.Trim())); + label.SetMessage(FormattedMessage.FromMarkupOrThrow(text.Trim())); } else { diff --git a/Content.Client/CriminalRecords/CriminalRecordsConsoleWindow.xaml.cs b/Content.Client/CriminalRecords/CriminalRecordsConsoleWindow.xaml.cs index 21aa54c962..7cae290fe1 100644 --- a/Content.Client/CriminalRecords/CriminalRecordsConsoleWindow.xaml.cs +++ b/Content.Client/CriminalRecords/CriminalRecordsConsoleWindow.xaml.cs @@ -227,7 +227,7 @@ private void PopulateRecordContainer(GeneralStationRecord stationRecord, Crimina StatusOptionButton.SelectId((int) criminalRecord.Status); if (criminalRecord.Reason is {} reason) { - var message = FormattedMessage.FromMarkup(Loc.GetString("criminal-records-console-wanted-reason")); + var message = FormattedMessage.FromMarkupOrThrow(Loc.GetString("criminal-records-console-wanted-reason")); message.AddText($": {reason}"); WantedReason.SetMessage(message); WantedReason.Visible = true; diff --git a/Content.Client/Gravity/GravitySystem.cs b/Content.Client/Gravity/GravitySystem.cs index 3e87f76ba2..dd51436a1f 100644 --- a/Content.Client/Gravity/GravitySystem.cs +++ b/Content.Client/Gravity/GravitySystem.cs @@ -1,4 +1,5 @@ using Content.Shared.Gravity; +using Content.Shared.Power; using Robust.Client.GameObjects; namespace Content.Client.Gravity; @@ -21,7 +22,7 @@ private void OnAppearanceChange(EntityUid uid, SharedGravityGeneratorComponent c if (args.Sprite == null) return; - if (_appearanceSystem.TryGetData(uid, GravityGeneratorVisuals.State, out var state, args.Component)) + if (_appearanceSystem.TryGetData(uid, PowerChargeVisuals.State, out var state, args.Component)) { if (comp.SpriteMap.TryGetValue(state, out var spriteState)) { @@ -30,7 +31,7 @@ private void OnAppearanceChange(EntityUid uid, SharedGravityGeneratorComponent c } } - if (_appearanceSystem.TryGetData(uid, GravityGeneratorVisuals.Charge, out var charge, args.Component)) + if (_appearanceSystem.TryGetData(uid, PowerChargeVisuals.Charge, out var charge, args.Component)) { var layer = args.Sprite.LayerMapGet(GravityGeneratorVisualLayers.Core); switch (charge) diff --git a/Content.Client/Gravity/UI/GravityGeneratorBoundUserInterface.cs b/Content.Client/Gravity/UI/GravityGeneratorBoundUserInterface.cs deleted file mode 100644 index 32b40747d5..0000000000 --- a/Content.Client/Gravity/UI/GravityGeneratorBoundUserInterface.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Content.Shared.Gravity; -using JetBrains.Annotations; -using Robust.Client.UserInterface; - -namespace Content.Client.Gravity.UI -{ - [UsedImplicitly] - public sealed class GravityGeneratorBoundUserInterface : BoundUserInterface - { - [ViewVariables] - private GravityGeneratorWindow? _window; - - public GravityGeneratorBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) - { - } - - protected override void Open() - { - base.Open(); - - _window = this.CreateWindow(); - _window.SetEntity(Owner); - } - - protected override void UpdateState(BoundUserInterfaceState state) - { - base.UpdateState(state); - - var castState = (SharedGravityGeneratorComponent.GeneratorState) state; - _window?.UpdateState(castState); - } - - public void SetPowerSwitch(bool on) - { - SendMessage(new SharedGravityGeneratorComponent.SwitchGeneratorMessage(on)); - } - } -} diff --git a/Content.Client/Gravity/UI/GravityGeneratorWindow.xaml.cs b/Content.Client/Gravity/UI/GravityGeneratorWindow.xaml.cs deleted file mode 100644 index 6f04133b59..0000000000 --- a/Content.Client/Gravity/UI/GravityGeneratorWindow.xaml.cs +++ /dev/null @@ -1,75 +0,0 @@ -using Content.Shared.Gravity; -using Robust.Client.AutoGenerated; -using Robust.Client.GameObjects; -using Robust.Client.UserInterface.Controls; -using Robust.Client.UserInterface.XAML; -using FancyWindow = Content.Client.UserInterface.Controls.FancyWindow; - -namespace Content.Client.Gravity.UI -{ - [GenerateTypedNameReferences] - public sealed partial class GravityGeneratorWindow : FancyWindow - { - private readonly ButtonGroup _buttonGroup = new(); - - public event Action? OnPowerSwitch; - - public GravityGeneratorWindow() - { - RobustXamlLoader.Load(this); - IoCManager.InjectDependencies(this); - - OnButton.Group = _buttonGroup; - OffButton.Group = _buttonGroup; - - OnButton.OnPressed += _ => OnPowerSwitch?.Invoke(true); - OffButton.OnPressed += _ => OnPowerSwitch?.Invoke(false); - } - - public void SetEntity(EntityUid uid) - { - EntityView.SetEntity(uid); - } - - public void UpdateState(SharedGravityGeneratorComponent.GeneratorState state) - { - if (state.On) - OnButton.Pressed = true; - else - OffButton.Pressed = true; - - PowerLabel.Text = Loc.GetString( - "gravity-generator-window-power-label", - ("draw", state.PowerDraw), - ("max", state.PowerDrawMax)); - - PowerLabel.SetOnlyStyleClass(MathHelper.CloseTo(state.PowerDraw, state.PowerDrawMax) ? "Good" : "Caution"); - - ChargeBar.Value = state.Charge; - ChargeText.Text = (state.Charge / 255f).ToString("P0"); - StatusLabel.Text = Loc.GetString(state.PowerStatus switch - { - GravityGeneratorPowerStatus.Off => "gravity-generator-window-status-off", - GravityGeneratorPowerStatus.Discharging => "gravity-generator-window-status-discharging", - GravityGeneratorPowerStatus.Charging => "gravity-generator-window-status-charging", - GravityGeneratorPowerStatus.FullyCharged => "gravity-generator-window-status-fully-charged", - _ => throw new ArgumentOutOfRangeException() - }); - - StatusLabel.SetOnlyStyleClass(state.PowerStatus switch - { - GravityGeneratorPowerStatus.Off => "Danger", - GravityGeneratorPowerStatus.Discharging => "Caution", - GravityGeneratorPowerStatus.Charging => "Caution", - GravityGeneratorPowerStatus.FullyCharged => "Good", - _ => throw new ArgumentOutOfRangeException() - }); - - EtaLabel.Text = state.EtaSeconds >= 0 - ? Loc.GetString("gravity-generator-window-eta-value", ("left", TimeSpan.FromSeconds(state.EtaSeconds))) - : Loc.GetString("gravity-generator-window-eta-none"); - - EtaLabel.SetOnlyStyleClass(state.EtaSeconds >= 0 ? "Caution" : "Disabled"); - } - } -} diff --git a/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs b/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs index 87931bf845..f8d1c7e972 100644 --- a/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs +++ b/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs @@ -140,7 +140,7 @@ private void GenerateControl(ReagentPrototype reagent) var i = 0; foreach (var effectString in effect.EffectDescriptions) { - descMsg.AddMarkup(effectString); + descMsg.AddMarkupOrThrow(effectString); i++; if (i < descriptionsCount) descMsg.PushNewline(); @@ -174,7 +174,7 @@ private void GenerateControl(ReagentPrototype reagent) var i = 0; foreach (var effectString in guideEntryRegistryPlant.PlantMetabolisms) { - descMsg.AddMarkup(effectString); + descMsg.AddMarkupOrThrow(effectString); i++; if (i < descriptionsCount) descMsg.PushNewline(); @@ -195,7 +195,7 @@ private void GenerateControl(ReagentPrototype reagent) FormattedMessage description = new(); description.AddText(reagent.LocalizedDescription); description.PushNewline(); - description.AddMarkup(Loc.GetString("guidebook-reagent-physical-description", + description.AddMarkupOrThrow(Loc.GetString("guidebook-reagent-physical-description", ("description", reagent.LocalizedPhysicalDescription))); ReagentDescription.SetMessage(description); } diff --git a/Content.Client/Guidebook/Controls/GuideReagentReaction.xaml.cs b/Content.Client/Guidebook/Controls/GuideReagentReaction.xaml.cs index 168f352d1a..135dc5522a 100644 --- a/Content.Client/Guidebook/Controls/GuideReagentReaction.xaml.cs +++ b/Content.Client/Guidebook/Controls/GuideReagentReaction.xaml.cs @@ -155,7 +155,7 @@ private void SetReagents(Dictionary reagents, ref RichTextL var i = 0; foreach (var (product, amount) in reagents.OrderByDescending(p => p.Value)) { - msg.AddMarkup(Loc.GetString("guidebook-reagent-recipes-reagent-display", + msg.AddMarkupOrThrow(Loc.GetString("guidebook-reagent-recipes-reagent-display", ("reagent", protoMan.Index(product).LocalizedName), ("ratio", amount))); i++; if (i < reagentCount) diff --git a/Content.Client/Info/InfoSection.xaml.cs b/Content.Client/Info/InfoSection.xaml.cs index ab9d352d32..9e10a4d7b4 100644 --- a/Content.Client/Info/InfoSection.xaml.cs +++ b/Content.Client/Info/InfoSection.xaml.cs @@ -1,4 +1,4 @@ -using Robust.Client.AutoGenerated; +using Robust.Client.AutoGenerated; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; using Robust.Shared.Utility; @@ -18,7 +18,7 @@ public void SetText(string title, string text, bool markup = false) { TitleLabel.Text = title; if (markup) - Content.SetMessage(FormattedMessage.FromMarkup(text.Trim())); + Content.SetMessage(FormattedMessage.FromMarkupOrThrow(text.Trim())); else Content.SetMessage(text); } diff --git a/Content.Client/Info/ServerInfo.cs b/Content.Client/Info/ServerInfo.cs index 23be750626..901fc91337 100644 --- a/Content.Client/Info/ServerInfo.cs +++ b/Content.Client/Info/ServerInfo.cs @@ -24,7 +24,7 @@ public ServerInfo() } public void SetInfoBlob(string markup) { - _richTextLabel.SetMessage(FormattedMessage.FromMarkup(markup)); + _richTextLabel.SetMessage(FormattedMessage.FromMarkupOrThrow(markup)); } } } diff --git a/Content.Client/Labels/UI/HandLabelerBoundUserInterface.cs b/Content.Client/Labels/UI/HandLabelerBoundUserInterface.cs index 6b65612341..b9b58f2322 100644 --- a/Content.Client/Labels/UI/HandLabelerBoundUserInterface.cs +++ b/Content.Client/Labels/UI/HandLabelerBoundUserInterface.cs @@ -26,6 +26,11 @@ protected override void Open() _window = this.CreateWindow(); + if (_entManager.TryGetComponent(Owner, out HandLabelerComponent? labeler)) + { + _window.SetMaxLabelLength(labeler!.MaxLabelChars); + } + _window.OnLabelChanged += OnLabelChanged; Reload(); } diff --git a/Content.Client/Labels/UI/HandLabelerWindow.xaml.cs b/Content.Client/Labels/UI/HandLabelerWindow.xaml.cs index 6482cdc1cc..7a0627b3e2 100644 --- a/Content.Client/Labels/UI/HandLabelerWindow.xaml.cs +++ b/Content.Client/Labels/UI/HandLabelerWindow.xaml.cs @@ -21,7 +21,7 @@ public HandLabelerWindow() { RobustXamlLoader.Load(this); - LabelLineEdit.OnTextEntered += e => + LabelLineEdit.OnTextChanged += e => { _label = e.Text; OnLabelChanged?.Invoke(_label); @@ -33,6 +33,10 @@ public HandLabelerWindow() _focused = false; LabelLineEdit.Text = _label; }; + + // Give the editor keybard focus, since that's the only + // thing the user will want to be doing with this UI + LabelLineEdit.GrabKeyboardFocus(); } public void SetCurrentLabel(string label) @@ -44,5 +48,10 @@ public void SetCurrentLabel(string label) if (!_focused) LabelLineEdit.Text = label; } + + public void SetMaxLabelLength(int maxLength) + { + LabelLineEdit.IsValid = s => s.Length <= maxLength; + } } } diff --git a/Content.Client/Light/Components/LightBehaviourComponent.cs b/Content.Client/Light/Components/LightBehaviourComponent.cs index 9df793ee93..246863ba60 100644 --- a/Content.Client/Light/Components/LightBehaviourComponent.cs +++ b/Content.Client/Light/Components/LightBehaviourComponent.cs @@ -359,9 +359,6 @@ void ISerializationHooks.AfterDeserialization() [RegisterComponent] public sealed partial class LightBehaviourComponent : SharedLightBehaviourComponent, ISerializationHooks { - [Dependency] private readonly IEntityManager _entMan = default!; - [Dependency] private readonly IRobustRandom _random = default!; - public const string KeyPrefix = nameof(LightBehaviourComponent); public sealed class AnimationContainer diff --git a/Content.Client/MachineLinking/UI/SignalTimerBoundUserInterface.cs b/Content.Client/MachineLinking/UI/SignalTimerBoundUserInterface.cs index 11abe8c245..0607c76831 100644 --- a/Content.Client/MachineLinking/UI/SignalTimerBoundUserInterface.cs +++ b/Content.Client/MachineLinking/UI/SignalTimerBoundUserInterface.cs @@ -7,8 +7,6 @@ namespace Content.Client.MachineLinking.UI; public sealed class SignalTimerBoundUserInterface : BoundUserInterface { - [Dependency] private readonly IGameTiming _gameTiming = default!; - [ViewVariables] private SignalTimerWindow? _window; diff --git a/Content.Client/Message/RichTextLabelExt.cs b/Content.Client/Message/RichTextLabelExt.cs index 7ff6390764..ee3c00fa1b 100644 --- a/Content.Client/Message/RichTextLabelExt.cs +++ b/Content.Client/Message/RichTextLabelExt.cs @@ -15,7 +15,7 @@ public static class RichTextLabelExt /// public static RichTextLabel SetMarkup(this RichTextLabel label, string markup) { - label.SetMessage(FormattedMessage.FromMarkup(markup)); + label.SetMessage(FormattedMessage.FromMarkupOrThrow(markup)); return label; } diff --git a/Content.Client/Physics/JointVisualsOverlay.cs b/Content.Client/Physics/JointVisualsOverlay.cs index e0b3499a97..9cc2831d21 100644 --- a/Content.Client/Physics/JointVisualsOverlay.cs +++ b/Content.Client/Physics/JointVisualsOverlay.cs @@ -1,9 +1,8 @@ +using System.Numerics; using Content.Shared.Physics; using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Shared.Enums; -using Robust.Shared.Physics; -using Robust.Shared.Physics.Dynamics.Joints; namespace Content.Client.Physics; @@ -16,8 +15,6 @@ public sealed class JointVisualsOverlay : Overlay private IEntityManager _entManager; - private HashSet _drawn = new(); - public JointVisualsOverlay(IEntityManager entManager) { _entManager = entManager; @@ -25,7 +22,6 @@ public JointVisualsOverlay(IEntityManager entManager) protected override void Draw(in OverlayDrawArgs args) { - _drawn.Clear(); var worldHandle = args.WorldHandle; var spriteSystem = _entManager.System(); @@ -33,12 +29,14 @@ protected override void Draw(in OverlayDrawArgs args) var joints = _entManager.EntityQueryEnumerator(); var xformQuery = _entManager.GetEntityQuery(); + args.DrawingHandle.SetTransform(Matrix3x2.Identity); + while (joints.MoveNext(out var visuals, out var xform)) { if (xform.MapID != args.MapId) continue; - var other = visuals.Target; + var other = _entManager.GetEntity(visuals.Target); if (!xformQuery.TryGetComponent(other, out var otherXform)) continue; diff --git a/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs b/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs index 736c80e17c..42a79aec95 100644 --- a/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs +++ b/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs @@ -1,4 +1,4 @@ -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; using Content.Client.Lobby; using Content.Shared.CCVar; using Content.Shared.Players; @@ -143,7 +143,7 @@ public bool CheckRoleRequirements(HashSet? requirements, Humanoi reasons.Add(jobReason.ToMarkup()); } - reason = reasons.Count == 0 ? null : FormattedMessage.FromMarkup(string.Join('\n', reasons)); + reason = reasons.Count == 0 ? null : FormattedMessage.FromMarkupOrThrow(string.Join('\n', reasons)); return reason == null; } diff --git a/Content.Client/Power/PowerCharge/PowerChargeBoundUserInterface.cs b/Content.Client/Power/PowerCharge/PowerChargeBoundUserInterface.cs new file mode 100644 index 0000000000..7a36b8ddf5 --- /dev/null +++ b/Content.Client/Power/PowerCharge/PowerChargeBoundUserInterface.cs @@ -0,0 +1,38 @@ +using Content.Shared.Power; +using Robust.Client.UserInterface; + +namespace Content.Client.Power.PowerCharge; + +public sealed class PowerChargeBoundUserInterface : BoundUserInterface +{ + [ViewVariables] + private PowerChargeWindow? _window; + + public PowerChargeBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + } + + public void SetPowerSwitch(bool on) + { + SendMessage(new SwitchChargingMachineMessage(on)); + } + + protected override void Open() + { + base.Open(); + if (!EntMan.TryGetComponent(Owner, out PowerChargeComponent? component)) + return; + + _window = this.CreateWindow(); + _window.UpdateWindow(this, Loc.GetString(component.WindowTitle)); + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + if (state is not PowerChargeState chargeState) + return; + + _window?.UpdateState(chargeState); + } +} diff --git a/Content.Client/Power/PowerCharge/PowerChargeComponent.cs b/Content.Client/Power/PowerCharge/PowerChargeComponent.cs new file mode 100644 index 0000000000..ab5baa4e2f --- /dev/null +++ b/Content.Client/Power/PowerCharge/PowerChargeComponent.cs @@ -0,0 +1,10 @@ +using Content.Shared.Power; + +namespace Content.Client.Power.PowerCharge; + +/// +[RegisterComponent] +public sealed partial class PowerChargeComponent : SharedPowerChargeComponent +{ + +} diff --git a/Content.Client/Gravity/UI/GravityGeneratorWindow.xaml b/Content.Client/Power/PowerCharge/PowerChargeWindow.xaml similarity index 60% rename from Content.Client/Gravity/UI/GravityGeneratorWindow.xaml rename to Content.Client/Power/PowerCharge/PowerChargeWindow.xaml index 853f437a2b..4e61255326 100644 --- a/Content.Client/Gravity/UI/GravityGeneratorWindow.xaml +++ b/Content.Client/Power/PowerCharge/PowerChargeWindow.xaml @@ -1,27 +1,26 @@  -