diff --git a/Content.Client/Popups/PopupOverlay.cs b/Content.Client/Popups/PopupOverlay.cs new file mode 100644 index 0000000000..eb837f90cc --- /dev/null +++ b/Content.Client/Popups/PopupOverlay.cs @@ -0,0 +1,119 @@ +using Content.Shared.Popups; +using Robust.Client.Graphics; +using Robust.Client.ResourceManagement; +using Robust.Shared.Enums; +using Robust.Shared.Prototypes; + +namespace Content.Client.Popups; + +/// +/// Draws popup text, either in world or on screen. +/// +public sealed class PopupOverlay : Overlay +{ + private readonly IEntityManager _entManager; + private readonly PopupSystem _popup; + + private readonly ShaderInstance _shader; + private readonly Font _smallFont; + private readonly Font _mediumFont; + private readonly Font _largeFont; + + public override OverlaySpace Space => OverlaySpace.ScreenSpace; + + public PopupOverlay(IEntityManager entManager, IPrototypeManager protoManager, IResourceCache cache, PopupSystem popup) + { + _entManager = entManager; + _popup = popup; + + _shader = protoManager.Index("unshaded").Instance(); + _smallFont = new VectorFont(cache.GetResource("/Fonts/NotoSans/NotoSans-Italic.ttf"), 10); + _mediumFont = new VectorFont(cache.GetResource("/Fonts/NotoSans/NotoSans-Italic.ttf"), 12); + _largeFont = new VectorFont(cache.GetResource("/Fonts/NotoSans/NotoSans-BoldItalic.ttf"), 14); + } + + protected override void Draw(in OverlayDrawArgs args) + { + if (args.ViewportControl == null) + return; + + args.DrawingHandle.SetTransform(Matrix3.Identity); + args.DrawingHandle.UseShader(_shader); + + DrawWorld(args.ScreenHandle, args); + DrawScreen(args.ScreenHandle, args); + + args.DrawingHandle.UseShader(null); + } + + private void DrawWorld(DrawingHandleScreen worldHandle, OverlayDrawArgs args) + { + if (_popup.WorldLabels.Count == 0) + return; + + var matrix = args.ViewportControl!.GetWorldToScreenMatrix(); + + foreach (var popup in _popup.WorldLabels) + { + var mapPos = popup.InitialPos.ToMap(_entManager); + + if (mapPos.MapId != args.MapId) + continue; + + if (!args.WorldAABB.Contains(mapPos.Position)) + continue; + + var pos = matrix.Transform(mapPos.Position); + DrawPopup(popup, worldHandle, pos); + } + } + + private void DrawScreen(DrawingHandleScreen screenHandle, OverlayDrawArgs args) + { + foreach (var popup in _popup.CursorLabels) + { + // Different window + if (popup.InitialPos.Window != args.ViewportControl?.Window?.Id) + continue; + + DrawPopup(popup, screenHandle, popup.InitialPos.Position); + } + } + + private void DrawPopup(PopupSystem.PopupLabel popup, DrawingHandleScreen handle, Vector2 position) + { + const float alphaMinimum = 0.5f; + + var alpha = MathF.Min(1f, 1f - (popup.TotalTime - alphaMinimum) / (PopupSystem.PopupLifetime - alphaMinimum)); + var updatedPosition = position - new Vector2(0f, 20f * (popup.TotalTime * popup.TotalTime + popup.TotalTime)); + var font = _smallFont; + var dimensions = Vector2.Zero; + var color = Color.White.WithAlpha(alpha); + + switch (popup.Type) + { + case PopupType.SmallCaution: + color = Color.Red; + break; + case PopupType.Medium: + font = _mediumFont; + color = Color.LightGray; + break; + case PopupType.MediumCaution: + font = _mediumFont; + color = Color.Red; + break; + case PopupType.Large: + font = _largeFont; + color = Color.LightGray; + break; + case PopupType.LargeCaution: + font = _largeFont; + color = Color.Red; + break; + } + + dimensions = handle.GetDimensions(font, popup.Text, 1f); + handle.DrawString(font, updatedPosition - dimensions / 2f, popup.Text, color.WithAlpha(alpha)); + } +} diff --git a/Content.Client/Popups/PopupSystem.cs b/Content.Client/Popups/PopupSystem.cs index 5ecdd8cc4a..dae90602bb 100644 --- a/Content.Client/Popups/PopupSystem.cs +++ b/Content.Client/Popups/PopupSystem.cs @@ -1,16 +1,13 @@ -using Content.Client.Stylesheets; -using Content.Shared.Examine; using Content.Shared.GameTicking; using Content.Shared.Popups; using Robust.Client.Graphics; using Robust.Client.Input; using Robust.Client.Player; -using Robust.Client.UserInterface; -using Robust.Client.UserInterface.Controls; +using Robust.Client.ResourceManagement; using Robust.Shared.Map; using Robust.Shared.Player; using Robust.Shared.Players; -using Robust.Shared.Timing; +using Robust.Shared.Prototypes; using Robust.Shared.Utility; namespace Content.Client.Popups @@ -18,10 +15,13 @@ namespace Content.Client.Popups public sealed class PopupSystem : SharedPopupSystem { [Dependency] private readonly IInputManager _inputManager = default!; - [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; - [Dependency] private readonly IEyeManager _eyeManager = default!; + [Dependency] private readonly IOverlayManager _overlay = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; - [Dependency] private readonly ExamineSystemShared _examineSystem = default!; + [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] private readonly IResourceCache _resource = default!; + + public IReadOnlyList WorldLabels => _aliveWorldLabels; + public IReadOnlyList CursorLabels => _aliveCursorLabels; private readonly List _aliveWorldLabels = new(); private readonly List _aliveCursorLabels = new(); @@ -34,21 +34,25 @@ namespace Content.Client.Popups SubscribeNetworkEvent(OnPopupCoordinatesEvent); SubscribeNetworkEvent(OnPopupEntityEvent); SubscribeNetworkEvent(OnRoundRestart); + _overlay + .AddOverlay(new PopupOverlay(EntityManager, _prototype, _resource, this)); + } + + public override void Shutdown() + { + base.Shutdown(); + _overlay + .RemoveOverlay(); } private void PopupMessage(string message, PopupType type, EntityCoordinates coordinates, EntityUid? entity = null) { - var label = new WorldPopupLabel(_eyeManager, EntityManager) + var label = new WorldPopupLabel(coordinates) { - Entity = entity, Text = message, - StyleClasses = { GetStyleClass(type) }, + Type = type, }; - _userInterfaceManager.PopupRoot.AddChild(label); - label.Measure(Vector2.Infinity); - - label.InitialPos = coordinates; _aliveWorldLabels.Add(label); } @@ -69,15 +73,15 @@ namespace Content.Client.Popups if (_playerManager.LocalPlayer?.ControlledEntity == recipient) PopupMessage(message, type, coordinates, null); } - + public override void PopupCursor(string message, PopupType type = PopupType.Small) { var label = new CursorPopupLabel(_inputManager.MouseScreenPosition) { Text = message, - StyleClasses = { GetStyleClass(type) }, + Type = type, }; - _userInterfaceManager.PopupRoot.AddChild(label); + _aliveCursorLabels.Add(label); } @@ -146,159 +150,69 @@ namespace Content.Client.Popups private void OnRoundRestart(RoundRestartCleanupEvent ev) { - foreach (var label in _aliveWorldLabels) - { - label.Dispose(); - } - + _aliveCursorLabels.Clear(); _aliveWorldLabels.Clear(); } #endregion - private static string GetStyleClass(PopupType type) => - type switch - { - PopupType.Small => StyleNano.StyleClassPopupMessageSmall, - PopupType.SmallCaution => StyleNano.StyleClassPopupMessageSmallCaution, - PopupType.Medium => StyleNano.StyleClassPopupMessageMedium, - PopupType.MediumCaution => StyleNano.StyleClassPopupMessageMediumCaution, - PopupType.Large => StyleNano.StyleClassPopupMessageLarge, - PopupType.LargeCaution => StyleNano.StyleClassPopupMessageLargeCaution, - _ => StyleNano.StyleClassPopupMessageSmall - }; - public override void FrameUpdate(float frameTime) { if (_aliveWorldLabels.Count == 0 && _aliveCursorLabels.Count == 0) return; - var player = _playerManager.LocalPlayer?.ControlledEntity; - var playerPos = player != null ? Transform(player.Value).MapPosition : MapCoordinates.Nullspace; - - // ReSharper disable once ConvertToLocalFunction - var predicate = static (EntityUid uid, (EntityUid? compOwner, EntityUid? attachedEntity) data) - => uid == data.compOwner || uid == data.attachedEntity; - var occluded = player != null && _examineSystem.IsOccluded(player.Value); - - for (var i = _aliveWorldLabels.Count - 1; i >= 0; i--) + for (var i = 0; i < _aliveWorldLabels.Count; i++) { var label = _aliveWorldLabels[i]; - if (label.TotalTime > PopupLifetime || - label.Entity != null && Deleted(label.Entity)) + label.TotalTime += frameTime; + + if (label.TotalTime > PopupLifetime || Deleted(label.InitialPos.EntityId)) { - label.Dispose(); _aliveWorldLabels.RemoveSwap(i); - continue; + i--; } - - if (label.Entity == player) - { - label.Visible = true; - continue; - } - - var otherPos = label.Entity != null ? Transform(label.Entity.Value).MapPosition : label.InitialPos.ToMap(EntityManager); - - if (occluded && !ExamineSystemShared.InRangeUnOccluded( - playerPos, - otherPos, 0f, - (label.Entity, player), predicate)) - { - label.Visible = false; - continue; - } - - label.Visible = true; } - for (var i = _aliveCursorLabels.Count - 1; i >= 0; i--) + for (var i = 0; i < _aliveCursorLabels.Count; i++) { var label = _aliveCursorLabels[i]; + label.TotalTime += frameTime; + if (label.TotalTime > PopupLifetime) { - label.Dispose(); _aliveCursorLabels.RemoveSwap(i); + i--; } } } - private abstract class PopupLabel : Label + public abstract class PopupLabel { - public float TotalTime { get; protected set; } + public PopupType Type = PopupType.Small; + public string Text { get; set; } = string.Empty; + public float TotalTime { get; set; } + } - public PopupLabel() - { - ShadowOffsetXOverride = ShadowOffsetYOverride = 1; - FontColorShadowOverride = Color.Black; - Measure(Vector2.Infinity); - } + public sealed class CursorPopupLabel : PopupLabel + { + public ScreenCoordinates InitialPos; - protected override void FrameUpdate(FrameEventArgs eventArgs) + public CursorPopupLabel(ScreenCoordinates screenCoords) { - TotalTime += eventArgs.DeltaSeconds; - if (TotalTime > 0.5f) - Modulate = Color.White.WithAlpha(1f - 0.2f * MathF.Pow(TotalTime - 0.5f, 3f)); + InitialPos = screenCoords; } } - private sealed class CursorPopupLabel : PopupLabel + public sealed class WorldPopupLabel : PopupLabel { - public Vector2 InitialPos { get; set; } - - public CursorPopupLabel(ScreenCoordinates screenCoords) : base() - { - InitialPos = screenCoords.Position - DesiredSize / 2; - } - - protected override void FrameUpdate(FrameEventArgs eventArgs) - { - base.FrameUpdate(eventArgs); - LayoutContainer.SetPosition(this, InitialPos / UIScale - (0, 20 * (TotalTime * TotalTime + TotalTime))); - } - } - - private sealed class WorldPopupLabel : PopupLabel - { - private readonly IEyeManager _eyeManager; - private readonly IEntityManager _entityManager; - /// - /// The original Mapid and ScreenPosition of the label. + /// The original EntityCoordinates of the label. /// - /// - /// Yes that's right it's not technically MapCoordinates. - /// - public EntityCoordinates InitialPos { get; set; } - public EntityUid? Entity { get; set; } + public EntityCoordinates InitialPos; - public WorldPopupLabel(IEyeManager eyeManager, IEntityManager entityManager) : base() + public WorldPopupLabel(EntityCoordinates coordinates) { - _eyeManager = eyeManager; - _entityManager = entityManager; - } - - protected override void FrameUpdate(FrameEventArgs eventArgs) - { - base.FrameUpdate(eventArgs); - ScreenCoordinates screenCoords; - - if (Entity == null) - screenCoords = _eyeManager.CoordinatesToScreen(InitialPos); - else if (_entityManager.TryGetComponent(Entity.Value, out TransformComponent? xform) - && xform.MapID == _eyeManager.CurrentMap) - screenCoords = _eyeManager.CoordinatesToScreen(xform.Coordinates); - else - { - Visible = false; - if (Entity != null && _entityManager.Deleted(Entity)) - TotalTime += PopupLifetime; - return; - } - - Visible = true; - var position = screenCoords.Position / UIScale - DesiredSize / 2; - LayoutContainer.SetPosition(this, position - (0, 20 * (TotalTime * TotalTime + TotalTime))); + InitialPos = coordinates; } } } diff --git a/Content.Client/Stylesheets/StyleNano.cs b/Content.Client/Stylesheets/StyleNano.cs index b350602767..b22f0453d0 100644 --- a/Content.Client/Stylesheets/StyleNano.cs +++ b/Content.Client/Stylesheets/StyleNano.cs @@ -1031,49 +1031,6 @@ namespace Content.Client.Stylesheets new StyleProperty("font", notoSans16) }), - // Popup messages - new StyleRule(new SelectorElement(typeof(Label), new[] {StyleClassPopupMessageSmall}, null, null), - new[] - { - new StyleProperty("font", notoSansItalic10), - new StyleProperty("font-color", Color.White), - }), - - new StyleRule(new SelectorElement(typeof(Label), new[] {StyleClassPopupMessageSmallCaution}, null, null), - new[] - { - new StyleProperty("font", notoSansItalic10), - new StyleProperty("font-color", Color.Red), - }), - - new StyleRule(new SelectorElement(typeof(Label), new[] {StyleClassPopupMessageMedium}, null, null), - new[] - { - new StyleProperty("font", notoSansItalic12), - new StyleProperty("font-color", Color.LightGray), - }), - - new StyleRule(new SelectorElement(typeof(Label), new[] {StyleClassPopupMessageMediumCaution}, null, null), - new[] - { - new StyleProperty("font", notoSansItalic12), - new StyleProperty("font-color", Color.Red), - }), - - new StyleRule(new SelectorElement(typeof(Label), new[] {StyleClassPopupMessageLarge}, null, null), - new[] - { - new StyleProperty("font", notoSansBoldItalic14), - new StyleProperty("font-color", Color.LightGray), - }), - - new StyleRule(new SelectorElement(typeof(Label), new[] {StyleClassPopupMessageLargeCaution}, null, null), - new[] - { - new StyleProperty("font", notoSansBoldItalic14), - new StyleProperty("font-color", Color.Red), - }), - //APC and SMES power state label colors new StyleRule(new SelectorElement(typeof(Label), new[] {StyleClassPowerStateNone}, null, null), new[] {