StampCollection.xaml.cs 3.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. using System.Numerics;
  2. using Robust.Client.AutoGenerated;
  3. using Robust.Client.UserInterface.Controls;
  4. using Robust.Client.UserInterface.XAML;
  5. using Robust.Shared.Random;
  6. namespace Content.Client.Paper.UI;
  7. [GenerateTypedNameReferences]
  8. public sealed partial class StampCollection : Container
  9. {
  10. private List<StampWidget> _stamps = new();
  11. /// Seed for random number generator to place stamps deterministically
  12. public int PlacementSeed;
  13. public StampCollection()
  14. {
  15. RobustXamlLoader.Load(this);
  16. }
  17. /// <summary>
  18. /// Remove any stamps from the page
  19. /// </summary>
  20. public void RemoveStamps()
  21. {
  22. _stamps.Clear();
  23. InvalidateArrange();
  24. }
  25. /// <summary>
  26. /// Adds a stamp to the display; will perform
  27. /// automatic layout.
  28. /// </summary>
  29. public void AddStamp(StampWidget s)
  30. {
  31. _stamps.Add(s);
  32. AddChild(s);
  33. }
  34. protected override Vector2 ArrangeOverride(Vector2 finalSize)
  35. {
  36. var random = new Random(PlacementSeed);
  37. var r = (finalSize * 0.5f).Length();
  38. var dtheta = -MathHelper.DegreesToRadians(90);
  39. var theta0 = random.Next(0, 3) * dtheta;
  40. var thisCenter = PixelSizeBox.TopLeft + finalSize * UIScale * 0.5f;
  41. // Here's where we lay out the stamps. The first stamp goes in the
  42. // center of this container; subsequent stamps will chose an angle
  43. // (theta) to place the center of the stamp. The stamp is moved out
  44. // as far as it can in that direction, taking the size and
  45. // orientation of the stamp into account.
  46. for (var i = 0; i < _stamps.Count; i++)
  47. {
  48. var stampOrientation = MathHelper.DegreesToRadians((random.NextFloat() - 0.5f) * 10.0f) ;
  49. _stamps[i].Orientation = stampOrientation;
  50. 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
  51. var childCenterOnCircle = thisCenter;
  52. if (i > 0)
  53. {
  54. // First stamp can go in the center. Subsequent stamps have to find space.
  55. childCenterOnCircle += new Vector2(MathF.Cos(theta), MathF.Sin(theta)) * r * UIScale;
  56. }
  57. var childHeLocal = _stamps[i].DesiredPixelSize * 0.5f;
  58. var c = childHeLocal * MathF.Abs(MathF.Cos(stampOrientation));
  59. var s = childHeLocal * MathF.Abs(MathF.Sin(stampOrientation));
  60. var childHePage = new Vector2(c.X + s.Y, s.X + c.Y);
  61. var controlBox = new UIBox2(PixelSizeBox.TopLeft, PixelSizeBox.TopLeft + finalSize * UIScale);
  62. var clampedCenter = Clamp(Shrink(controlBox, childHePage), childCenterOnCircle);
  63. var finalPosition = clampedCenter - childHePage;
  64. var finalPositionAsInt = new Vector2i((int)finalPosition.X, (int)finalPosition.Y);
  65. _stamps[i].ArrangePixel(new UIBox2i(finalPositionAsInt, finalPositionAsInt + _stamps[i].DesiredPixelSize));
  66. }
  67. return finalSize;
  68. }
  69. /// <summary>
  70. /// Shrink a UIBox2 by a half extents, moving both the top-left and
  71. /// bottom-right closer together.
  72. /// </summary>
  73. private UIBox2 Shrink(UIBox2 box, Vector2 shrinkHe)
  74. {
  75. return new UIBox2(box.TopLeft + shrinkHe, box.BottomRight - shrinkHe);
  76. }
  77. /// <summary>
  78. /// Returns the input vector clamped to be within the UIBox
  79. /// </summary>
  80. private Vector2 Clamp(UIBox2 box, Vector2 point)
  81. {
  82. return Vector2.Min(box.BottomRight, Vector2.Max(box.TopLeft, point));
  83. }
  84. }