using System.Linq; using System.Numerics; using System.Text; using Content.Client.Message; using Content.Client.Resources; using Robust.Client.Animations; using Robust.Client.AutoGenerated; using Robust.Client.Graphics; using Robust.Client.Player; using Robust.Client.ResourceManagement; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; using Robust.Shared.Animations; using Robust.Shared.Console; using Robust.Shared.Random; using Robust.Shared.Timing; namespace Content.Client._White.UserInterface.Radial; public sealed class RadialContainerCommandTest : LocalizedCommands { public override string Command => "radialtest"; public override void Execute(IConsoleShell shell, string argStr, string[] args) { string[] tips = { "Testovый туултип. Здесь можете расписать разную инфу о кнопке/действии", "Из окна дуло. Штирлиц закрыл окно. Дуло исчезло.", "Негры пидорасы Негры пидорасы Негры пидорасы Негры пидорасы" }; var radial = new RadialContainer(); for (int i = 0; i < 8; i++) { var testButton = radial.AddButton("Action " + i, "/Textures/Interface/emotions.svg.192dpi.png"); testButton.Tooltip = tips[IoCManager.Resolve().Next(0, 2)]; testButton.Controller.OnPressed += (_) => { Logger.Debug("Press gay"); }; } radial.CloseButton.Controller.OnPressed += (_) => { Logger.Debug("Close event for your own logic"); }; radial.OpenAttachedLocalPlayer(); } } [GenerateTypedNameReferences, Virtual] public partial class RadialContainer : Control { private EntityUid? _attachedEntity; private bool _isAttached = false; private bool _isOpened = false; private Vector2 _focusSize = new Vector2(64, 64); private Vector2 _normalSize = new Vector2(50, 50); private float _moveAniTime = 0.3f; private float _focusAniTime = 0.25f; private string _backgroundTexture = "/Textures/Interface/Default/SlotBackground.png"; private const int MaxButtons = 8; public const string MoveAnimationKey = "move"; public const string InSizeAnimationKey = "insize"; public const string OutSizeAnimationKey = "outsize"; public Action? Closed; public float FocusSize { get => _focusSize.Y; set => _focusSize = new Vector2(value, value); } public float NormalSize { get => _normalSize.Y; set => _normalSize = new Vector2(value, value); } public float MoveAnimationTime { get => _moveAniTime; set => _moveAniTime = value; } public float FocusAnimationTime { get => _focusAniTime; set => _focusAniTime = value; } public bool IsAction = true; public float VerticalOffset = 0.0f; public float DistanceAvaible = 10.0f; public RadialContainer() : base() { RobustXamlLoader.Load(this); IoCManager.InjectDependencies(this); } public void Open(Vector2 position) { AddToRoot(); LayoutContainer.SetPosition(this, position); UpdateButtons(); } public void OpenCentered() { AddToRoot(); if (Parent != null) LayoutContainer.SetPosition(this, (Parent.Size/2) - (this.Size/2)); else LayoutContainer.SetPosition(this, (UserInterfaceManager.MainViewport.Size/2) - (this.Size/2)); UpdateButtons(); } public void OpenCenteredLeft() => OpenCenteredAt(new Vector2(0.37f, 0.5f)); public void OpenCenteredAt(Vector2 position) { AddToRoot(); if (Parent == null) return; LayoutContainer.SetPosition(this, (Parent.Size * position) - (this.Size/2)); UpdateButtons(); } /// /// Open on attached entity in the world. /// public void OpenAttached(EntityUid uid) { if (uid == EntityUid.Invalid) return; AddToRoot(); _attachedEntity = uid; _isAttached = true; UpdateButtons(); } /// /// Open on our (player) attached entity. /// public void OpenAttachedLocalPlayer() { var localPlayer = IoCManager.Resolve().LocalPlayer; if (localPlayer == null) return; AddToRoot(); _attachedEntity = localPlayer.ControlledEntity; _isAttached = true; UpdateButtons(); } public void Close(bool canDispose = true) { Parent?.RemoveChild(this); Visible = false; _isOpened = false; Closed?.Invoke(); if (canDispose) Dispose(); } public RadialButton AddButton(string action, string? texture = null) { var button = new RadialButton(); button.Content = action; button.Controller.TextureNormal = IoCManager.Resolve().GetTexture(_backgroundTexture); if (texture != null) button.BackgroundTexture.Texture = IoCManager.Resolve().GetTexture(texture); Layout.AddChild(button); return button; } public RadialButton AddButton(string action, Texture? texture) { var button = new RadialButton(); button.Content = action; button.Controller.TextureNormal = IoCManager.Resolve().GetTexture(_backgroundTexture); if (texture != null) button.BackgroundTexture.Texture = texture; Layout.AddChild(button); return button; } private void AddToRoot() { if (_isOpened) return; UserInterfaceManager.WindowRoot.AddChild(this); _isOpened = !_isOpened; } private void UpdateButtons() { Visible = true; var angleDegrees = 360/Layout.ChildCount; var stepAngle = -angleDegrees + -90; var distance = GetDistance(); foreach (var child in Layout.Children) { var button = (RadialButton)child; button.ButtonSize = _normalSize; stepAngle += angleDegrees; var pos = GetPointFromPolar(stepAngle, distance); PlayRadialAnimation(button, pos, MoveAnimationKey); button.Controller.OnMouseEntered += (_) => { PlaySizeAnimation(button, _focusSize, OutSizeAnimationKey, InSizeAnimationKey); RichTextLabelExt.SetMarkup(ActionLabel, button.Content ?? string.Empty); ActionLabel.Visible = IsAction; }; button.Controller.OnMouseExited += (_) => { PlaySizeAnimation(button, _normalSize, InSizeAnimationKey, OutSizeAnimationKey); ActionLabel.Visible = false; }; } CloseButton.ButtonSize = _normalSize; CloseButton.Controller.OnMouseEntered += (_) => { PlaySizeAnimation(CloseButton, _focusSize, OutSizeAnimationKey, InSizeAnimationKey); RichTextLabelExt.SetMarkup(ActionLabel, CloseButton.Content ?? string.Empty); ActionLabel.Visible = true; }; CloseButton.Controller.OnMouseExited += (_) => { PlaySizeAnimation(CloseButton, _normalSize, InSizeAnimationKey, OutSizeAnimationKey); ActionLabel.Visible = false; }; CloseButton.Controller.OnPressed += (_) => { Close(); }; } private void PlayRadialAnimation(Control button, Vector2 pos, string playKey) { var anim = new Animation { Length = TimeSpan.FromMilliseconds(_moveAniTime * 1000), AnimationTracks = { new AnimationTrackControlProperty { Property = nameof(RadialButton.Offset), InterpolationMode = AnimationInterpolationMode.Linear, KeyFrames = { new AnimationTrackProperty.KeyFrame(new Vector2(0,0), 0f), new AnimationTrackProperty.KeyFrame(pos, _moveAniTime) } } } }; if (!button.HasRunningAnimation(playKey)) button.PlayAnimation(anim, playKey); } private void PlaySizeAnimation(Control button, Vector2 size, string playKey, string? stopKey) { var anim = new Animation { Length = TimeSpan.FromMilliseconds(_focusAniTime * 1000), AnimationTracks = { new AnimationTrackControlProperty { Property = nameof(RadialButton.ButtonSize), InterpolationMode = AnimationInterpolationMode.Linear, KeyFrames = { new AnimationTrackProperty.KeyFrame(button.Size, 0f), new AnimationTrackProperty.KeyFrame(size, _focusAniTime) } } } }; if (stopKey != null && button.HasRunningAnimation(stopKey)) button.StopAnimation(stopKey); if (!button.HasRunningAnimation(playKey)) button.PlayAnimation(anim, playKey); } protected override void Draw(DrawingHandleScreen handle) { base.Draw(handle); foreach (var child in Layout.Children) { var button = (RadialButton)child; LayoutContainer.SetPosition(child, button.Offset - (button.Size/2)); } LayoutContainer.SetPosition(CloseButton, CloseButton.Offset - (CloseButton.Size/2)); LayoutContainer.SetPosition(ActionLabel, new Vector2(0 - (GetTextWidth(ActionLabel) / 4), GetDistance(4.5f) )); } protected override void FrameUpdate(FrameEventArgs args) { if (!_isAttached) return; base.FrameUpdate(args); var entityManager = IoCManager.Resolve(); var eyeManager = IoCManager.Resolve(); if (entityManager.Deleted(_attachedEntity)) { Timer.Spawn(0, Die); return; } if (!entityManager.TryGetComponent(_attachedEntity, out var xform) || xform.MapID != eyeManager.CurrentMap) { return; } var localPlayer = IoCManager.Resolve().LocalPlayer; if (localPlayer == null) return; // Check distance beetween entities if (entityManager.TryGetComponent(localPlayer.ControlledEntity, out var myxform)) { var onePoint = xform.WorldPosition; var twoPoint = myxform.WorldPosition; var distance = (onePoint - twoPoint).Length(); if (DistanceAvaible < distance) { Timer.Spawn(0, Die); return; } } var offset = (-eyeManager.CurrentEye.Rotation).ToWorldVec() * -VerticalOffset; var worldPos = xform.WorldPosition + offset; var lowerCenter = eyeManager.WorldToScreen(worldPos) / UIScale; var screenPos = lowerCenter - new Vector2(DesiredSize.X / 2, DesiredSize.Y / 2); // Round to nearest 0.5 screenPos = (screenPos * 2).Rounded() / 2; LayoutContainer.SetPosition(this, screenPos); } private int GetTextWidth(RichTextLabel text) { var font = GetFont(text); var msg = text.GetMessage(); if (msg == null) return 0; var width = 0; foreach (var t in msg) { var metrics = font.GetCharMetrics(new Rune(t), 1); if (metrics != null) width += metrics.Value.Width + metrics.Value.Advance; } return width; } private void Die() { if (Disposed) return; Close(); } private Font GetFont(Control element) { if (element.TryGetStyleProperty("font", out var font)) { return font; } return UserInterfaceManager.ThemeDefaults.DefaultFont; } private float GetDistance(float offset = 0.0f) { var distance = FocusSize * 1.2f; if (Enumerable.Count(Layout.Children) <= MaxButtons) return distance + offset; for (var i = 0; i < (Enumerable.Count(Layout.Children) - MaxButtons); i++) { distance += (NormalSize/3); } return distance + offset; } private static Vector2 GetPointFromPolar(double angleDegrees, double distance) { var angleRadians = angleDegrees * (Math.PI / 180.0); var x = distance * Math.Cos(angleRadians); var y = distance * Math.Sin(angleRadians); return new Vector2((int)Math.Round(x), (int)Math.Round(y)); } }