using Robust.Client.Graphics; using Robust.Client.UserInterface.Controls; using System.Linq; using System.Numerics; namespace Content.Client.UserInterface.Controls; [Virtual] public class RadialContainer : LayoutContainer { /// /// Specifies the anglular range, in radians, in which child elements will be placed. /// The first value denotes the angle at which the first element is to be placed, and /// the second value denotes the angle at which the last element is to be placed. /// Both values must be between 0 and 2 PI radians /// /// /// The top of the screen is at 0 radians, and the bottom of the screen is at PI radians /// [ViewVariables(VVAccess.ReadWrite)] public Vector2 AngularRange { get { return _angularRange; } set { var x = value.X; var y = value.Y; x = x > MathF.Tau ? x % MathF.Tau : x; y = y > MathF.Tau ? y % MathF.Tau : y; x = x < 0 ? MathF.Tau + x : x; y = y < 0 ? MathF.Tau + y : y; _angularRange = new Vector2(x, y); } } private Vector2 _angularRange = new Vector2(0f, MathF.Tau - float.Epsilon); /// /// Determines the direction in which child elements will be arranged /// [ViewVariables(VVAccess.ReadWrite)] public RAlignment RadialAlignment { get; set; } = RAlignment.Clockwise; /// /// Determines how far from the radial container's center that its child elements will be placed /// [ViewVariables(VVAccess.ReadWrite)] public float Radius { get; set; } = 100f; /// /// Sets whether the container should reserve a space on the layout for child which are not currently visible /// [ViewVariables(VVAccess.ReadWrite)] public bool ReserveSpaceForHiddenChildren { get; set; } = true; /// /// This container arranges its children, evenly separated, in a radial pattern /// public RadialContainer() { } protected override void Draw(DrawingHandleScreen handle) { const float baseRadius = 100f; const float radiusIncrement = 5f; var children = ReserveSpaceForHiddenChildren ? Children : Children.Where(x => x.Visible); var childCount = children.Count(); // Add padding from the center at higher child counts so they don't overlap. Radius = baseRadius + (childCount * radiusIncrement); // Determine the size of the arc, accounting for clockwise and anti-clockwise arrangements var arc = AngularRange.Y - AngularRange.X; arc = (arc < 0) ? MathF.Tau + arc : arc; arc = (RadialAlignment == RAlignment.AntiClockwise) ? MathF.Tau - arc : arc; // Account for both circular arrangements and arc-based arrangements var childMod = MathHelper.CloseTo(arc, MathF.Tau, 0.01f) ? 0 : 1; // Determine the separation between child elements var sepAngle = arc / (childCount - childMod); sepAngle *= (RadialAlignment == RAlignment.AntiClockwise) ? -1f : 1f; // Adjust the positions of all the child elements foreach (var (i, child) in children.Select((x, i) => (i, x))) { var position = new Vector2(Radius * MathF.Sin(AngularRange.X + sepAngle * i) + Width / 2f - child.Width / 2f, -Radius * MathF.Cos(AngularRange.X + sepAngle * i) + Height / 2f - child.Height / 2f); SetPosition(child, position); } } /// /// Specifies the different radial alignment modes /// /// public enum RAlignment : byte { Clockwise, AntiClockwise, } }