2024-01-27 15:19:52 +03:00
|
|
|
|
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<IRobustRandom>().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();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Open on attached entity in the world.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void OpenAttached(EntityUid uid)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (uid == EntityUid.Invalid)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
AddToRoot();
|
|
|
|
|
|
_attachedEntity = uid;
|
|
|
|
|
|
_isAttached = true;
|
|
|
|
|
|
UpdateButtons();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Open on our (player) attached entity.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void OpenAttachedLocalPlayer()
|
|
|
|
|
|
{
|
|
|
|
|
|
var localPlayer = IoCManager.Resolve<IPlayerManager>().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<IResourceCache>().GetTexture(_backgroundTexture);
|
|
|
|
|
|
if (texture != null)
|
|
|
|
|
|
button.BackgroundTexture.Texture = IoCManager.Resolve<IResourceCache>().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<IResourceCache>().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);
|
2024-01-28 17:32:55 +07:00
|
|
|
|
RichTextLabelExt.SetMarkup(ActionLabel, button.Content ?? string.Empty);
|
2024-01-27 15:19:52 +03:00
|
|
|
|
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);
|
2024-01-28 17:32:55 +07:00
|
|
|
|
RichTextLabelExt.SetMarkup(ActionLabel, CloseButton.Content ?? string.Empty);
|
2024-01-27 15:19:52 +03:00
|
|
|
|
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<IEntityManager>();
|
|
|
|
|
|
var eyeManager = IoCManager.Resolve<IEyeManager>();
|
|
|
|
|
|
|
|
|
|
|
|
if (entityManager.Deleted(_attachedEntity))
|
|
|
|
|
|
{
|
|
|
|
|
|
Timer.Spawn(0, Die);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!entityManager.TryGetComponent<TransformComponent>(_attachedEntity, out var xform) || xform.MapID != eyeManager.CurrentMap)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var localPlayer = IoCManager.Resolve<IPlayerManager>().LocalPlayer;
|
|
|
|
|
|
if (localPlayer == null)
|
|
|
|
|
|
return;
|
|
|
|
|
|
// Check distance beetween entities
|
|
|
|
|
|
if (entityManager.TryGetComponent<TransformComponent>(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>("font", out var font))
|
|
|
|
|
|
{
|
|
|
|
|
|
return font;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return UserInterfaceManager.ThemeDefaults.DefaultFont;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private float GetDistance(float offset = 0.0f)
|
|
|
|
|
|
{
|
|
|
|
|
|
var distance = FocusSize * 1.2f;
|
|
|
|
|
|
|
2024-01-28 17:32:55 +07:00
|
|
|
|
if (Enumerable.Count<Control>(Layout.Children) <= MaxButtons)
|
2024-01-27 15:19:52 +03:00
|
|
|
|
return distance + offset;
|
|
|
|
|
|
|
2024-01-28 17:32:55 +07:00
|
|
|
|
for (var i = 0; i < (Enumerable.Count<Control>(Layout.Children) - MaxButtons); i++)
|
2024-01-27 15:19:52 +03:00
|
|
|
|
{
|
|
|
|
|
|
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));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|