Alerts System and UI (#2529)

* #272 add bordered panel for effects bar

* #272 avoid mouse overlapping tooltip when near edges,
change tooltip colors to match mockups

* #272 WIP defining status effect states as YML and
sending them as encoded integers

* #272 refactor to use new alert system

* #272 refactor to use new alert system

* #272 fix various bugs with new alert system and update
alerts to have color

* #272 WIP

* #272 rename status effects to alerts

* #272 WIP reworking alert internals to avoid code dup
and eliminate enum

* #272 refactor alerts to use
categories and fix various bugs

* #272 more alert bugfixes

* #272 alert ordering

* #272 callback-based approach for alert clicks

* #272 add debug commands for alerts

* #272 utilize new GridContainer capabilities for sizing of alerts tab

* #272 scale alerts height based on
window size

* #272 fix tooltip flicker

* #272 transparent alert panel

* #272 adjust styles to match injazz mockups more, add cooldown info in tooltip

* #272 adjust styles to match injazz mockups more, add cooldown info in tooltip

* #272 alert prototype tests

* #272 alert manager tests

* #272 alert order tests

* #272 simple unit test for alerts component

* #272 integration test for alerts

* #272 rework alerts to use enums instead
of id / category

* #272 various cleanups for PR

* #272 use byte for more compact alert messages

* #272 rename StatusEffects folder to Alerts,
add missing NetSerializable
This commit is contained in:
chairbender
2020-11-09 20:22:19 -08:00
committed by GitHub
parent c82199610d
commit 5f788c3318
86 changed files with 2305 additions and 598 deletions

View File

@@ -11,6 +11,7 @@ using Content.Client.UserInterface.AdminMenu;
using Content.Client.UserInterface.Stylesheets;
using Content.Client.Utility;
using Content.Shared.Interfaces;
using Content.Shared.Alert;
using Robust.Shared.IoC;
namespace Content.Client
@@ -35,6 +36,7 @@ namespace Content.Client
IoCManager.Register<IClickMapManager, ClickMapManager>();
IoCManager.Register<IStationEventManager, StationEventManager>();
IoCManager.Register<IAdminMenuManager, AdminMenuManager>();
IoCManager.Register<AlertManager, AlertManager>();
}
}
}

View File

@@ -22,6 +22,7 @@ using Content.Shared.GameObjects.Components.Power.AME;
using Content.Shared.GameObjects.Components.Research;
using Content.Shared.GameObjects.Components.VendingMachines;
using Content.Shared.Kitchen;
using Content.Shared.Alert;
using Robust.Client;
using Robust.Client.Interfaces;
using Robust.Client.Interfaces.Graphics.Overlays;
@@ -150,6 +151,7 @@ namespace Content.Client
IoCManager.Resolve<IClientPreferencesManager>().Initialize();
IoCManager.Resolve<IStationEventManager>().Initialize();
IoCManager.Resolve<IAdminMenuManager>().Initialize();
IoCManager.Resolve<AlertManager>().Initialize();
_baseClient.RunLevelChanged += (sender, args) =>
{

View File

@@ -0,0 +1,93 @@
#nullable enable
using System;
using Content.Client.UserInterface;
using Content.Client.Utility;
using Content.Shared.Alert;
using OpenToolkit.Mathematics;
using Robust.Client.Interfaces.ResourceManagement;
using Robust.Client.UserInterface.Controls;
namespace Content.Client.GameObjects.Components.Mobs
{
public class AlertControl : BaseButton
{
public AlertPrototype Alert { get; }
/// <summary>
/// Total duration of the cooldown in seconds. Null if no duration / cooldown.
/// </summary>
public int? TotalDuration { get; set; }
private short? _severity;
private readonly TextureRect _icon;
private CooldownGraphic _cooldownGraphic;
private readonly IResourceCache _resourceCache;
/// <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>
/// <param name="resourceCache">resourceCache to use to load alert icon textures</param>
public AlertControl(AlertPrototype alert, short? severity, IResourceCache resourceCache)
{
_resourceCache = resourceCache;
Alert = alert;
_severity = severity;
var texture = _resourceCache.GetTexture(alert.GetIconPath(_severity));
_icon = new TextureRect
{
TextureScale = (2, 2),
Texture = texture
};
Children.Add(_icon);
_cooldownGraphic = new CooldownGraphic();
Children.Add(_cooldownGraphic);
}
/// <summary>
/// Change the alert severity, changing the displayed icon
/// </summary>
public void SetSeverity(short? severity)
{
if (_severity != severity)
{
_severity = severity;
_icon.Texture = _resourceCache.GetTexture(Alert.GetIconPath(_severity));
}
}
/// <summary>
/// Updates the displayed cooldown amount, doing nothing if alertCooldown is null
/// </summary>
/// <param name="alertCooldown">cooldown start and end</param>
/// <param name="curTime">current game time</param>
public void UpdateCooldown((TimeSpan Start, TimeSpan End)? alertCooldown, in TimeSpan curTime)
{
if (!alertCooldown.HasValue)
{
_cooldownGraphic.Progress = 0;
_cooldownGraphic.Visible = false;
TotalDuration = null;
}
else
{
var start = alertCooldown.Value.Start;
var end = alertCooldown.Value.End;
var length = (end - start).TotalSeconds;
var progress = (curTime - start).TotalSeconds / length;
var ratio = (progress <= 1 ? (1 - progress) : (curTime - end).TotalSeconds * -5);
TotalDuration = (int?) Math.Round(length);
_cooldownGraphic.Progress = MathHelper.Clamp((float)ratio, -1, 1);
_cooldownGraphic.Visible = ratio > -1f;
}
}
}
}

View File

@@ -0,0 +1,345 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Client.UserInterface;
using Content.Client.UserInterface.Stylesheets;
using Content.Client.Utility;
using Content.Shared.Alert;
using Content.Shared.GameObjects.Components.Mobs;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.Graphics;
using Robust.Client.Interfaces.ResourceManagement;
using Robust.Client.Interfaces.UserInterface;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects;
using Robust.Shared.Input;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using Serilog;
namespace Content.Client.GameObjects.Components.Mobs
{
/// <inheritdoc/>
[RegisterComponent]
[ComponentReference(typeof(SharedAlertsComponent))]
public sealed class ClientAlertsComponent : SharedAlertsComponent
{
private static readonly float TooltipTextMaxWidth = 265;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
private AlertsUI _ui;
private PanelContainer _tooltip;
private RichTextLabel _stateName;
private RichTextLabel _stateDescription;
private RichTextLabel _stateCooldown;
private AlertOrderPrototype _alertOrder;
private bool _tooltipReady;
[ViewVariables]
private Dictionary<AlertKey, AlertControl> _alertControls
= new Dictionary<AlertKey, AlertControl>();
/// <summary>
/// Allows calculating if we need to act due to this component being controlled by the current mob
/// TODO: should be revisited after space-wizards/RobustToolbox#1255
/// </summary>
[ViewVariables]
private bool CurrentlyControlled => _playerManager.LocalPlayer != null && _playerManager.LocalPlayer.ControlledEntity == Owner;
protected override void Shutdown()
{
base.Shutdown();
PlayerDetached();
}
public override void HandleMessage(ComponentMessage message, IComponent component)
{
base.HandleMessage(message, component);
switch (message)
{
case PlayerAttachedMsg _:
PlayerAttached();
break;
case PlayerDetachedMsg _:
PlayerDetached();
break;
}
}
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
{
base.HandleComponentState(curState, nextState);
if (!(curState is AlertsComponentState state))
{
return;
}
// update the dict of states based on the array we got in the message
SetAlerts(state.Alerts);
UpdateAlertsControls();
}
private void PlayerAttached()
{
if (!CurrentlyControlled || _ui != null)
{
return;
}
_alertOrder = IoCManager.Resolve<IPrototypeManager>().EnumeratePrototypes<AlertOrderPrototype>().FirstOrDefault();
if (_alertOrder == null)
{
Logger.ErrorS("alert", "no alertOrder prototype found, alerts will be in random order");
}
_ui = new AlertsUI(IoCManager.Resolve<IClyde>());
var uiManager = IoCManager.Resolve<IUserInterfaceManager>();
uiManager.StateRoot.AddChild(_ui);
_tooltip = new PanelContainer
{
Visible = false,
StyleClasses = { StyleNano.StyleClassTooltipPanel }
};
var tooltipVBox = new VBoxContainer
{
RectClipContent = true
};
_tooltip.AddChild(tooltipVBox);
_stateName = new RichTextLabel
{
MaxWidth = TooltipTextMaxWidth,
StyleClasses = { StyleNano.StyleClassTooltipAlertTitle }
};
tooltipVBox.AddChild(_stateName);
_stateDescription = new RichTextLabel
{
MaxWidth = TooltipTextMaxWidth,
StyleClasses = { StyleNano.StyleClassTooltipAlertDescription }
};
tooltipVBox.AddChild(_stateDescription);
_stateCooldown = new RichTextLabel
{
MaxWidth = TooltipTextMaxWidth,
StyleClasses = { StyleNano.StyleClassTooltipAlertCooldown }
};
tooltipVBox.AddChild(_stateCooldown);
uiManager.PopupRoot.AddChild(_tooltip);
UpdateAlertsControls();
}
private void PlayerDetached()
{
_ui?.Dispose();
_ui = null;
_alertControls.Clear();
}
/// <summary>
/// Updates the displayed alerts based on current state of Alerts, performing
/// a diff to ensure we only change what's changed (this avoids active tooltips disappearing any
/// time state changes)
/// </summary>
private void UpdateAlertsControls()
{
if (!CurrentlyControlled || _ui == null)
{
return;
}
// remove any controls with keys no longer present
var toRemove = new List<AlertKey>();
foreach (var existingKey in _alertControls.Keys)
{
if (!IsShowingAlert(existingKey))
{
toRemove.Add(existingKey);
}
}
foreach (var alertKeyToRemove in toRemove)
{
// remove and dispose the control
_alertControls.Remove(alertKeyToRemove, out var control);
control?.Dispose();
}
// 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
foreach (var alertStatus in EnumerateAlertStates())
{
if (!AlertManager.TryDecode(alertStatus.AlertEncoded, out var newAlert))
{
Logger.ErrorS("alert", "Unable to decode alert {0}", alertStatus.AlertEncoded);
continue;
}
if (_alertControls.TryGetValue(newAlert.AlertKey, out var existingAlertControl) &&
existingAlertControl.Alert.AlertType == newAlert.AlertType)
{
// id is the same, simply update the existing control severity
existingAlertControl.SetSeverity(alertStatus.Severity);
}
else
{
existingAlertControl?.Dispose();
// 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, alertStatus);
if (_alertOrder != null)
{
var added = false;
foreach (var alertControl in _ui.Grid.Children)
{
if (_alertOrder.Compare(newAlert, ((AlertControl) alertControl).Alert) < 0)
{
var idx = alertControl.GetPositionInParent();
_ui.Grid.Children.Add(newAlertControl);
newAlertControl.SetPositionInParent(idx);
added = true;
break;
}
}
if (!added)
{
_ui.Grid.Children.Add(newAlertControl);
}
}
else
{
_ui.Grid.Children.Add(newAlertControl);
}
_alertControls[newAlert.AlertKey] = newAlertControl;
}
}
}
private AlertControl CreateAlertControl(AlertPrototype alert, AlertState alertState)
{
var alertControl = new AlertControl(alert, alertState.Severity, _resourceCache);
// show custom tooltip for the status control
alertControl.OnShowTooltip += AlertOnOnShowTooltip;
alertControl.OnHideTooltip += AlertOnOnHideTooltip;
alertControl.OnPressed += AlertControlOnPressed;
return alertControl;
}
private void AlertControlOnPressed(BaseButton.ButtonEventArgs args)
{
AlertPressed(args, args.Button as AlertControl);
}
private void AlertOnOnHideTooltip(object sender, EventArgs e)
{
_tooltipReady = false;
_tooltip.Visible = false;
}
private void AlertOnOnShowTooltip(object sender, EventArgs e)
{
var alertControl = (AlertControl) sender;
_stateName.SetMessage(alertControl.Alert.Name);
_stateDescription.SetMessage(alertControl.Alert.Description);
// check for a cooldown
if (alertControl.TotalDuration != null && alertControl.TotalDuration > 0)
{
_stateCooldown.SetMessage(FormattedMessage.FromMarkup("[color=#776a6a]" +
alertControl.TotalDuration +
" sec cooldown[/color]"));
_stateCooldown.Visible = true;
}
else
{
_stateCooldown.Visible = false;
}
// TODO: Text display of cooldown
Tooltips.PositionTooltip(_tooltip);
// if we set it visible here the size of the previous tooltip will flicker for a frame,
// so instead we wait until FrameUpdate to make it visible
_tooltipReady = true;
}
private void AlertPressed(BaseButton.ButtonEventArgs args, AlertControl alert)
{
if (args.Event.Function != EngineKeyFunctions.UIClick)
{
return;
}
if (AlertManager.TryEncode(alert.Alert, out var encoded))
{
SendNetworkMessage(new ClickAlertMessage(encoded));
}
else
{
Logger.ErrorS("alert", "unable to encode alert {0}", alert.Alert.AlertType);
}
}
public void FrameUpdate(float frameTime)
{
if (_tooltipReady)
{
_tooltipReady = false;
_tooltip.Visible = true;
}
foreach (var (alertKey, alertControl) in _alertControls)
{
// reconcile all alert controls with their current cooldowns
if (TryGetAlertState(alertKey, out var alertState))
{
alertControl.UpdateCooldown(alertState.Cooldown, _gameTiming.CurTime);
}
else
{
Logger.WarningS("alert", "coding error - no alert state for alert {0} " +
"even though we had an AlertControl for it, this" +
" should never happen", alertControl.Alert.AlertType);
}
}
}
protected override void AfterClearAlert()
{
UpdateAlertsControls();
}
public override void OnRemove()
{
base.OnRemove();
foreach (var alertControl in _alertControls.Values)
{
alertControl.OnShowTooltip -= AlertOnOnShowTooltip;
alertControl.OnHideTooltip -= AlertOnOnHideTooltip;
alertControl.OnPressed -= AlertControlOnPressed;
}
}
}
}

View File

@@ -1,200 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Client.UserInterface;
using Content.Client.Utility;
using Content.Shared.GameObjects.Components.Mobs;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.ResourceManagement;
using Robust.Client.Interfaces.UserInterface;
using Robust.Client.Player;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects;
using Robust.Shared.Input;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.ViewVariables;
namespace Content.Client.GameObjects.Components.Mobs
{
/// <inheritdoc/>
[RegisterComponent]
[ComponentReference(typeof(SharedStatusEffectsComponent))]
public sealed class ClientStatusEffectsComponent : SharedStatusEffectsComponent
{
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
private StatusEffectsUI _ui;
[ViewVariables]
private Dictionary<StatusEffect, StatusEffectStatus> _status = new Dictionary<StatusEffect, StatusEffectStatus>();
[ViewVariables]
private Dictionary<StatusEffect, CooldownGraphic> _cooldown = new Dictionary<StatusEffect, CooldownGraphic>();
public override IReadOnlyDictionary<StatusEffect, StatusEffectStatus> Statuses => _status;
/// <summary>
/// Allows calculating if we need to act due to this component being controlled by the current mob
/// </summary>
[ViewVariables]
private bool CurrentlyControlled => _playerManager.LocalPlayer != null && _playerManager.LocalPlayer.ControlledEntity == Owner;
protected override void Shutdown()
{
base.Shutdown();
PlayerDetached();
}
public override void HandleMessage(ComponentMessage message, IComponent component)
{
base.HandleMessage(message, component);
switch (message)
{
case PlayerAttachedMsg _:
PlayerAttached();
break;
case PlayerDetachedMsg _:
PlayerDetached();
break;
}
}
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
{
base.HandleComponentState(curState, nextState);
if (!(curState is StatusEffectComponentState state) || _status == state.StatusEffects)
{
return;
}
_status = state.StatusEffects;
UpdateStatusEffects();
}
private void PlayerAttached()
{
if (!CurrentlyControlled || _ui != null)
{
return;
}
_ui = new StatusEffectsUI();
_userInterfaceManager.StateRoot.AddChild(_ui);
UpdateStatusEffects();
}
private void PlayerDetached()
{
_ui?.Dispose();
_ui = null;
_cooldown.Clear();
}
public override void ChangeStatusEffectIcon(StatusEffect effect, string icon)
{
if (_status.TryGetValue(effect, out var value) &&
value.Icon == icon)
{
return;
}
_status[effect] = new StatusEffectStatus
{
Icon = icon,
Cooldown = value.Cooldown
};
Dirty();
}
public void UpdateStatusEffects()
{
if (!CurrentlyControlled || _ui == null)
{
return;
}
_cooldown.Clear();
_ui.VBox.DisposeAllChildren();
foreach (var (key, effect) in _status.OrderBy(x => (int) x.Key))
{
var texture = _resourceCache.GetTexture(effect.Icon);
var status = new StatusControl(key, texture)
{
ToolTip = key.ToString()
};
if (effect.Cooldown.HasValue)
{
var cooldown = new CooldownGraphic();
status.Children.Add(cooldown);
_cooldown[key] = cooldown;
}
status.OnPressed += args => StatusPressed(args, status);
_ui.VBox.AddChild(status);
}
}
private void StatusPressed(BaseButton.ButtonEventArgs args, StatusControl status)
{
if (args.Event.Function != EngineKeyFunctions.UIClick)
{
return;
}
SendNetworkMessage(new ClickStatusMessage(status.Effect));
}
public override void RemoveStatusEffect(StatusEffect effect)
{
if (!_status.Remove(effect))
{
return;
}
UpdateStatusEffects();
Dirty();
}
public void FrameUpdate(float frameTime)
{
foreach (var (effect, cooldownGraphic) in _cooldown)
{
var status = _status[effect];
if (!status.Cooldown.HasValue)
{
cooldownGraphic.Progress = 0;
cooldownGraphic.Visible = false;
continue;
}
var start = status.Cooldown.Value.Item1;
var end = status.Cooldown.Value.Item2;
var length = (end - start).TotalSeconds;
var progress = (_gameTiming.CurTime - start).TotalSeconds / length;
var ratio = (progress <= 1 ? (1 - progress) : (_gameTiming.CurTime - end).TotalSeconds * -5);
cooldownGraphic.Progress = MathHelper.Clamp((float)ratio, -1, 1);
cooldownGraphic.Visible = ratio > -1f;
}
}
public override void ChangeStatusEffect(StatusEffect effect, string icon, (TimeSpan, TimeSpan)? cooldown)
{
_status[effect] = new StatusEffectStatus()
{
Icon = icon,
Cooldown = cooldown
};
Dirty();
}
}
}

View File

@@ -1,25 +0,0 @@
#nullable enable
using Content.Shared.GameObjects.Components.Mobs;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
namespace Content.Client.GameObjects.Components.Mobs
{
public class StatusControl : BaseButton
{
public readonly StatusEffect Effect;
public StatusControl(StatusEffect effect, Texture? texture)
{
Effect = effect;
var item = new TextureRect
{
TextureScale = (2, 2),
Texture = texture
};
Children.Add(item);
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using Content.Client.UserInterface.Stylesheets;
using Content.Shared.AI;
using Robust.Client.Interfaces.Graphics.ClientEye;
using Robust.Client.Interfaces.UserInterface;
@@ -178,7 +179,7 @@ namespace Content.Client.GameObjects.EntitySystems.AI
var panel = new PanelContainer
{
StyleClasses = {"tooltipBox"},
StyleClasses = { StyleNano.StyleClassTooltipPanel },
Children = {vBox},
MouseFilter = Control.MouseFilterMode.Ignore,
ModulateSelfOverride = Color.White.WithAlpha(0.75f),

View File

@@ -5,7 +5,7 @@ using Robust.Shared.IoC;
namespace Content.Client.GameObjects.EntitySystems
{
public class StatusEffectsSystem : EntitySystem
public class AlertsSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
@@ -16,9 +16,9 @@ namespace Content.Client.GameObjects.EntitySystems
if (!_gameTiming.IsFirstTimePredicted)
return;
foreach (var clientStatusEffectsComponent in EntityManager.ComponentManager.EntityQuery<ClientStatusEffectsComponent>())
foreach (var clientAlertsComponent in EntityManager.ComponentManager.EntityQuery<ClientAlertsComponent>())
{
clientStatusEffectsComponent.FrameUpdate(frameTime);
clientAlertsComponent.FrameUpdate(frameTime);
}
}
}

View File

@@ -0,0 +1,73 @@
using System;
using Content.Client.UserInterface.Stylesheets;
using Robust.Client.Graphics.Drawing;
using Robust.Client.Interfaces.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Maths;
namespace Content.Client.UserInterface
{
/// <summary>
/// The status effects display on the right side of the screen.
/// </summary>
public sealed class AlertsUI : Control
{
public GridContainer Grid { get; }
private readonly IClyde _clyde;
public AlertsUI(IClyde clyde)
{
_clyde = clyde;
var panelContainer = new PanelContainer
{
StyleClasses = {StyleNano.StyleClassTransparentBorderedWindowPanel},
SizeFlagsVertical = SizeFlags.FillExpand,
};
AddChild(panelContainer);
Grid = new GridContainer
{
MaxHeight = CalcMaxHeight(clyde.ScreenSize),
ExpandBackwards = true
};
panelContainer.AddChild(Grid);
clyde.OnWindowResized += ClydeOnOnWindowResized;
LayoutContainer.SetGrowHorizontal(this, LayoutContainer.GrowDirection.Begin);
LayoutContainer.SetAnchorAndMarginPreset(this, LayoutContainer.LayoutPreset.TopRight, margin: 10);
LayoutContainer.SetMarginTop(this, 250);
}
protected override void UIScaleChanged()
{
Grid.MaxHeight = CalcMaxHeight(_clyde.ScreenSize);
base.UIScaleChanged();
}
private void ClydeOnOnWindowResized(WindowResizedEventArgs obj)
{
// TODO: Can rework this once https://github.com/space-wizards/RobustToolbox/issues/1392 is done,
// this is here because there isn't currently a good way to allow the grid to adjust its height based
// on constraints, otherwise we would use anchors to lay it out
Grid.MaxHeight = CalcMaxHeight(obj.NewSize);;
}
private float CalcMaxHeight(Vector2i screenSize)
{
return Math.Max(((screenSize.Y) / UIScale) - 420, 1);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
_clyde.OnWindowResized -= ClydeOnOnWindowResized;
}
}
}
}

View File

@@ -1,23 +0,0 @@
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
namespace Content.Client.UserInterface
{
/// <summary>
/// The status effects display on the right side of the screen.
/// </summary>
public sealed class StatusEffectsUI : Control
{
public VBoxContainer VBox { get; }
public StatusEffectsUI()
{
VBox = new VBoxContainer();
AddChild(VBox);
LayoutContainer.SetGrowHorizontal(this, LayoutContainer.GrowDirection.Begin);
LayoutContainer.SetAnchorAndMarginPreset(this, LayoutContainer.LayoutPreset.TopRight, margin: 10);
LayoutContainer.SetMarginTop(this, 250);
}
}
}

View File

@@ -13,6 +13,13 @@ namespace Content.Client.UserInterface.Stylesheets
{
public sealed class StyleNano : StyleBase
{
public const string StyleClassBorderedWindowPanel = "BorderedWindowPanel";
public const string StyleClassTransparentBorderedWindowPanel = "TransparentBorderedWindowPanel";
public const string StyleClassTooltipPanel = "tooltipBox";
public const string StyleClassTooltipAlertTitle = "tooltipAlertTitle";
public const string StyleClassTooltipAlertDescription = "tooltipAlertDesc";
public const string StyleClassTooltipAlertCooldown = "tooltipAlertCooldown";
public const string StyleClassSliderRed = "Red";
public const string StyleClassSliderGreen = "Green";
public const string StyleClassSliderBlue = "Blue";
@@ -55,6 +62,7 @@ namespace Content.Client.UserInterface.Stylesheets
var notoSansDisplayBold14 = resCache.GetFont("/Fonts/NotoSansDisplay/NotoSansDisplay-Bold.ttf", 14);
var notoSans16 = resCache.GetFont("/Fonts/NotoSans/NotoSans-Regular.ttf", 16);
var notoSansBold16 = resCache.GetFont("/Fonts/NotoSans/NotoSans-Bold.ttf", 16);
var notoSansBold18 = resCache.GetFont("/Fonts/NotoSans/NotoSans-Bold.ttf", 18);
var notoSansBold20 = resCache.GetFont("/Fonts/NotoSans/NotoSans-Bold.ttf", 20);
var textureCloseButton = resCache.GetTexture("/Textures/Interface/Nano/cross.svg.png");
var windowHeaderTex = resCache.GetTexture("/Textures/Interface/Nano/window_header.png");
@@ -73,6 +81,20 @@ namespace Content.Client.UserInterface.Stylesheets
windowBackground.SetPatchMargin(StyleBox.Margin.Horizontal | StyleBox.Margin.Bottom, 2);
windowBackground.SetExpandMargin(StyleBox.Margin.Horizontal | StyleBox.Margin.Bottom, 2);
var borderedWindowBackgroundTex = resCache.GetTexture("/Textures/Interface/Nano/window_background_bordered.png");
var borderedWindowBackground = new StyleBoxTexture
{
Texture = borderedWindowBackgroundTex,
};
borderedWindowBackground.SetPatchMargin(StyleBox.Margin.All, 2);
var borderedTransparentWindowBackgroundTex = resCache.GetTexture("/Textures/Interface/Nano/transparent_window_background_bordered.png");
var borderedTransparentWindowBackground = new StyleBoxTexture
{
Texture = borderedTransparentWindowBackgroundTex,
};
borderedTransparentWindowBackground.SetPatchMargin(StyleBox.Margin.All, 2);
var textureInvertedTriangle = resCache.GetTexture("/Textures/Interface/Nano/inverted_triangle.svg.png");
var lineEditTex = resCache.GetTexture("/Textures/Interface/Nano/lineedit.png");
@@ -147,7 +169,7 @@ namespace Content.Client.UserInterface.Stylesheets
Texture = tooltipTexture,
};
tooltipBox.SetPatchMargin(StyleBox.Margin.All, 2);
tooltipBox.SetContentMarginOverride(StyleBox.Margin.Horizontal, 5);
tooltipBox.SetContentMarginOverride(StyleBox.Margin.Horizontal, 7);
// Placeholder
var placeholderTexture = resCache.GetTexture("/Textures/Interface/Nano/placeholder.png");
@@ -245,6 +267,19 @@ namespace Content.Client.UserInterface.Stylesheets
{
new StyleProperty(PanelContainer.StylePropertyPanel, windowBackground),
}),
// bordered window background
new StyleRule(
new SelectorElement(null, new[] {StyleClassBorderedWindowPanel}, null, null),
new[]
{
new StyleProperty(PanelContainer.StylePropertyPanel, borderedWindowBackground),
}),
new StyleRule(
new SelectorElement(null, new[] {StyleClassTransparentBorderedWindowPanel}, null, null),
new[]
{
new StyleProperty(PanelContainer.StylePropertyPanel, borderedTransparentWindowBackground),
}),
// Window header.
new StyleRule(
new SelectorElement(typeof(PanelContainer), new[] {SS14Window.StyleClassWindowHeader}, null, null),
@@ -464,7 +499,7 @@ namespace Content.Client.UserInterface.Stylesheets
new StyleProperty(PanelContainer.StylePropertyPanel, tooltipBox)
}),
new StyleRule(new SelectorElement(typeof(PanelContainer), new[] {"tooltipBox"}, null, null), new[]
new StyleRule(new SelectorElement(typeof(PanelContainer), new [] { StyleClassTooltipPanel }, null, null), new[]
{
new StyleProperty(PanelContainer.StylePropertyPanel, tooltipBox)
}),
@@ -482,6 +517,20 @@ namespace Content.Client.UserInterface.Stylesheets
new StyleProperty("font", notoSansItalic12),
}),
// alert tooltip
new StyleRule(new SelectorElement(typeof(RichTextLabel), new[] {StyleClassTooltipAlertTitle}, null, null), new[]
{
new StyleProperty("font", notoSansBold18)
}),
new StyleRule(new SelectorElement(typeof(RichTextLabel), new[] {StyleClassTooltipAlertDescription}, null, null), new[]
{
new StyleProperty("font", notoSans16)
}),
new StyleRule(new SelectorElement(typeof(RichTextLabel), new[] {StyleClassTooltipAlertCooldown}, null, null), new[]
{
new StyleProperty("font", notoSans16)
}),
// Entity tooltip
new StyleRule(
new SelectorElement(typeof(PanelContainer), new[] {ExamineSystem.StyleClassEntityTooltip}, null,