Files
tbd-station-14/Content.Client/Paper/UI/StampCollection.xaml.cs
2023-08-13 14:28:10 -04:00

99 lines
3.6 KiB
C#

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<StampWidget> _stamps = new();
/// Seed for random number generator to place stamps deterministically
public int PlacementSeed;
public StampCollection()
{
RobustXamlLoader.Load(this);
}
/// <summary>
/// Remove any stamps from the page
/// </summary>
public void RemoveStamps()
{
_stamps.Clear();
InvalidateArrange();
}
/// <summary>
/// Adds a stamp to the display; will perform
/// automatic layout.
/// </summary>
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;
}
/// <summary>
/// Shrink a UIBox2 by a half extents, moving both the top-left and
/// bottom-right closer together.
/// </summary>
private UIBox2 Shrink(UIBox2 box, Vector2 shrinkHe)
{
return new UIBox2(box.TopLeft + shrinkHe, box.BottomRight - shrinkHe);
}
/// <summary>
/// Returns the input vector clamped to be within the UIBox
/// </summary>
private Vector2 Clamp(UIBox2 box, Vector2 point)
{
return Vector2.Min(box.BottomRight, Vector2.Max(box.TopLeft, point));
}
}