using Robust.Shared.Maths; namespace Robust.Client.UserInterface.Controls { /// /// Container that tries to put controls at their specified origin, /// moving them around if necessary to avoid them going outside of the bounds of the container. /// [Virtual] public sealed class PopupContainer : Control { /// /// The origin that the container tries to place the child at. /// public static readonly AttachedProperty PopupOriginProperty = AttachedProperty.Create("PopupOrigin", typeof(PopupContainer), typeof(Vector2), changed: PopupOriginChangedCallback); /// /// Alternative position to right-align the popup if /// would put it off-screen horizontally. /// /// /// You know how right click menus with sub menus put the submenu on the left /// if it's too close to the right of the screen? Yeah that. /// public static readonly AttachedProperty AltOriginProperty = AttachedProperty.Create("AltOrigin", typeof(PopupContainer), typeof(Vector2?), changed: PopupOriginChangedCallback); public PopupContainer() { RectClipContent = true; } public static void SetPopupOrigin(Control control, Vector2 origin) { control.SetValue(PopupOriginProperty, origin); } public static Vector2 GetPopupOrigin(Control control) { return control.GetValue(PopupOriginProperty); } public static Vector2? GetAltOrigin(Control control) { return control.GetValue(AltOriginProperty); } public static void SetAltOrigin(Control control, Vector2? origin) { control.SetValue(AltOriginProperty, origin); } private static void PopupOriginChangedCallback(Control owner, AttachedPropertyChangedEventArgs eventArgs) { if (owner.Parent is PopupContainer container) { container.InvalidateArrange(); } } protected override Vector2 ArrangeOverride(Vector2 finalSize) { foreach (var child in Children) { var size = child.DesiredSize; var offset = child.GetValue(PopupOriginProperty); var altPos = child.GetValue(AltOriginProperty); var (r, b) = size + offset; // bottom right corner. var isAltPos = false; // Clamp the right edge. if (r > Width) { // Try to position at alt pos. if (altPos != null && altPos.Value.X - size.X > 0) { // There is horizontal room at the alt pos so there we go. isAltPos = true; offset = (altPos.Value.X - size.X, altPos.Value.Y); (_, b) = size + offset; } else { offset -= (r - Width, 0); } } // Clamp the bottom edge. if (b > Height) { offset -= (0, b - Height); } // Try to clamp the left edge. if (offset.X < 0 && !isAltPos) { offset -= (offset.X, 0); } // Try to clamp the top edge. if (offset.Y < 0) { offset -= (0, offset.Y); } child.Arrange(UIBox2.FromDimensions(offset, size)); } return finalSize; } protected override Vector2 MeasureOverride(Vector2 availableSize) { // Measure to availableSize so that child controls never get too large to fit the whole screen. base.MeasureOverride(availableSize); return availableSize; } protected override void Resized() { base.Resized(); InvalidateArrange(); } } }