using System.Numerics; using Robust.Client.AutoGenerated; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; using Robust.Shared.Random; namespace Content.Client.Paper.UI; [GenerateTypedNameReferences] public sealed partial class StampCollection : Container { private List _stamps = new(); /// Seed for random number generator to place stamps deterministically public int PlacementSeed; public StampCollection() { RobustXamlLoader.Load(this); } /// /// Remove any stamps from the page /// public void RemoveStamps() { _stamps.Clear(); InvalidateArrange(); } /// /// Adds a stamp to the display; will perform /// automatic layout. /// public void AddStamp(StampWidget s) { _stamps.Add(s); AddChild(s); } protected override Vector2 ArrangeOverride(Vector2 finalSize) { var random = new Random(PlacementSeed); var r = (finalSize * 0.5f).Length(); var dtheta = -MathHelper.DegreesToRadians(90); var theta0 = random.Next(0, 3) * dtheta; var thisCenter = PixelSizeBox.TopLeft + finalSize * UIScale * 0.5f; // Here's where we lay out the stamps. The first stamp goes in the // center of this container; subsequent stamps will chose an angle // (theta) to place the center of the stamp. The stamp is moved out // as far as it can in that direction, taking the size and // orientation of the stamp into account. for (var i = 0; i < _stamps.Count; i++) { var stampOrientation = MathHelper.DegreesToRadians((random.NextFloat() - 0.5f) * 10.0f) ; _stamps[i].Orientation = stampOrientation; var theta = theta0 + dtheta * 0.5f + dtheta * i + (i > 4 ? MathF.Log(1 + i / 4) * dtheta : 0); // There is probably a better way to lay these out, to minimize overlaps var childCenterOnCircle = thisCenter; if (i > 0) { // First stamp can go in the center. Subsequent stamps have to find space. childCenterOnCircle += new Vector2(MathF.Cos(theta), MathF.Sin(theta)) * r * UIScale; } var childHeLocal = _stamps[i].DesiredPixelSize * 0.5f; var c = childHeLocal * MathF.Abs(MathF.Cos(stampOrientation)); var s = childHeLocal * MathF.Abs(MathF.Sin(stampOrientation)); var childHePage = new Vector2(c.X + s.Y, s.X + c.Y); var controlBox = new UIBox2(PixelSizeBox.TopLeft, PixelSizeBox.TopLeft + finalSize * UIScale); var clampedCenter = Clamp(Shrink(controlBox, childHePage), childCenterOnCircle); var finalPosition = clampedCenter - childHePage; var finalPositionAsInt = new Vector2i((int)finalPosition.X, (int)finalPosition.Y); _stamps[i].ArrangePixel(new UIBox2i(finalPositionAsInt, finalPositionAsInt + _stamps[i].DesiredPixelSize)); } return finalSize; } /// /// Shrink a UIBox2 by a half extents, moving both the top-left and /// bottom-right closer together. /// private UIBox2 Shrink(UIBox2 box, Vector2 shrinkHe) { return new UIBox2(box.TopLeft + shrinkHe, box.BottomRight - shrinkHe); } /// /// Returns the input vector clamped to be within the UIBox /// private Vector2 Clamp(UIBox2 box, Vector2 point) { return Vector2.Min(box.BottomRight, Vector2.Max(box.TopLeft, point)); } }