using System; using System.Numerics; using JetBrains.Annotations; using Robust.Shared.Maths; namespace Robust.Client.Graphics { /// /// This is for drawing modestly fancy boxes using minimal code. /// [PublicAPI] public abstract class StyleBox { private float? _contentMarginTopOverride; private float? _contentMarginLeftOverride; private float? _contentMarginBottomOverride; private float? _contentMarginRightOverride; private float _paddingLeft; private float _paddingBottom; private float _paddingRight; private float _paddingTop; protected StyleBox() { } protected StyleBox(StyleBox other) { _contentMarginBottomOverride = other._contentMarginBottomOverride; _contentMarginTopOverride = other._contentMarginTopOverride; _contentMarginLeftOverride = other._contentMarginLeftOverride; _contentMarginRightOverride = other._contentMarginRightOverride; _paddingLeft = other._paddingLeft; _paddingBottom = other._paddingBottom; _paddingRight = other._paddingRight; _paddingTop = other._paddingTop; } /// /// Minimum size, in virtual pixels. /// public Vector2 MinimumSize => new(GetContentMargin(Margin.Left) + GetContentMargin(Margin.Right), GetContentMargin(Margin.Top) + GetContentMargin(Margin.Bottom)); /// /// Left content margin, in virtual pixels. /// public float? ContentMarginLeftOverride { get => _contentMarginLeftOverride; set { if (value < 0) { throw new ArgumentOutOfRangeException(nameof(value), value, "Value cannot be less than zero."); } _contentMarginLeftOverride = value; } } /// /// Top content margin, in virtual pixels. /// public float? ContentMarginTopOverride { get => _contentMarginTopOverride; set { if (value < 0) { throw new ArgumentOutOfRangeException(nameof(value), value, "Value cannot be less than zero."); } _contentMarginTopOverride = value; } } /// /// Right content margin, in virtual pixels. /// public float? ContentMarginRightOverride { get => _contentMarginRightOverride; set { if (value < 0) { throw new ArgumentOutOfRangeException(nameof(value), value, "Value cannot be less than zero."); } _contentMarginRightOverride = value; } } /// /// Bottom content margin, in virtual pixels. /// public float? ContentMarginBottomOverride { get => _contentMarginBottomOverride; set { if (value < 0) { throw new ArgumentOutOfRangeException(nameof(value), value, "Value cannot be less than zero."); } _contentMarginBottomOverride = value; } } /// /// Left padding, in virtual pixels. /// public float PaddingLeft { get => _paddingLeft; set { if (value < 0) { throw new ArgumentOutOfRangeException(nameof(value), value, "Value cannot be less than zero."); } _paddingLeft = value; } } /// /// Bottom padding, in virtual pixels. /// public float PaddingBottom { get => _paddingBottom; set { if (value < 0) { throw new ArgumentOutOfRangeException(nameof(value), value, "Value cannot be less than zero."); } _paddingBottom = value; } } /// /// Right padding, in virtual pixels. /// public float PaddingRight { get => _paddingRight; set { if (value < 0) { throw new ArgumentOutOfRangeException(nameof(value), value, "Value cannot be less than zero."); } _paddingRight = value; } } /// /// Top padding, in virtual pixels. /// public float PaddingTop { get => _paddingTop; set { if (value < 0) { throw new ArgumentOutOfRangeException(nameof(value), value, "Value cannot be less than zero."); } _paddingTop = value; } } /// /// Padding, in virtual pixels. /// public Thickness Padding { set { PaddingLeft = value.Left; PaddingTop = value.Top; PaddingRight = value.Right; PaddingBottom = value.Bottom; } } /// /// Draw this style box to the screen at the specified coordinates. This is using physical pixels, not virtual pixels. /// /// /// public void Draw(DrawingHandleScreen handle, UIBox2 box, float uiScale) { box = new UIBox2( box.Left + PaddingLeft, box.Top + PaddingTop, box.Right - PaddingRight, box.Bottom - PaddingBottom ); DoDraw(handle, box, uiScale); } /// /// Gets the offset from a margin of the box to where content should actually be drawn. This is in virtual pixels. /// /// /// Thrown if is a compound is not a single margin flag. /// public float GetContentMargin(Margin margin) { float? marginData; switch (margin) { case Margin.Top: marginData = ContentMarginTopOverride; break; case Margin.Bottom: marginData = ContentMarginBottomOverride; break; case Margin.Right: marginData = ContentMarginRightOverride; break; case Margin.Left: marginData = ContentMarginLeftOverride; break; default: throw new ArgumentException("Margin must be a single margin flag.", nameof(margin)); } var contentMargin = marginData ?? GetDefaultContentMargin(margin); return contentMargin + GetPadding(margin); } /// /// Gets the padding. This is in virtual pixels. /// public float GetPadding(Margin margin) { switch (margin) { case Margin.Top: return PaddingTop; case Margin.Bottom: return PaddingBottom; case Margin.Right: return PaddingRight; case Margin.Left: return PaddingLeft; default: throw new ArgumentException("Margin must be a single margin flag.", nameof(margin)); } } /// /// Returns the offsets of the content region of this box when drawn from the given position. Input and /// output positions are in real pixels, though virtual pixels can also be used if the ui scale is set to 1. /// public Vector2 GetContentOffset(Vector2 basePosition, float uiScale) { return basePosition + uiScale * new Vector2(GetContentMargin(Margin.Left), GetContentMargin(Margin.Top)); } /// /// Gets the box considered the "contents" of this style box, when drawn at a specific size. Input and output /// boxes are in virtual pixels, though virtual pixels can also be used if the ui scale is set to 1. /// /// /// is too small and the resultant box would have negative dimensions. /// public UIBox2 GetContentBox(UIBox2 baseBox, float uiScale) { var left = baseBox.Left + GetContentMargin(Margin.Left) * uiScale; var top = baseBox.Top + GetContentMargin(Margin.Top) * uiScale; var right = baseBox.Right - GetContentMargin(Margin.Right) * uiScale; var bottom = baseBox.Bottom - GetContentMargin(Margin.Bottom) * uiScale; return new UIBox2(left, top, right, bottom); } /// /// Gets the draw box, positioned at , /// that envelops a box with the given dimensions perfectly given this box's content margins. /// Input and output values are in physical pixels, though virtual pixels can also be used if the ui scale /// is set to 1. /// /// /// It's basically a reverse . /// /// The position at which the new box should be drawn. /// The dimensions of the content box inside this new box. /// Scales the content margin border size /// /// A box that, when ran through , /// has a content box of size /// public UIBox2 GetEnvelopBox(Vector2 position, Vector2 dimensions, float uiScale) { return UIBox2.FromDimensions(position, dimensions + MinimumSize * uiScale); } public void SetContentMarginOverride(Margin margin, float value) { if ((margin & Margin.Left) != 0) { ContentMarginLeftOverride = value; } if ((margin & Margin.Top) != 0) { ContentMarginTopOverride = value; } if ((margin & Margin.Right) != 0) { ContentMarginRightOverride = value; } if ((margin & Margin.Bottom) != 0) { ContentMarginBottomOverride = value; } } public void SetPadding(Margin margin, float value) { if ((margin & Margin.Left) != 0) { PaddingLeft = value; } if ((margin & Margin.Top) != 0) { PaddingTop = value; } if ((margin & Margin.Right) != 0) { PaddingRight = value; } if ((margin & Margin.Bottom) != 0) { PaddingBottom = value; } } /// /// Draw the style box in the given UI Box. /// /// /// The area to draw in, in physical pixels /// The UI scale to use. protected abstract void DoDraw(DrawingHandleScreen handle, UIBox2 box, float uiScale); protected virtual float GetDefaultContentMargin(Margin margin) { return 0; } /// /// Describes margins of a style box. /// [Flags] public enum Margin : byte { None = 0, /// /// The top margin. /// Top = 1, /// /// The bottom margin. /// Bottom = 2, /// /// The right margin. /// Right = 4, /// /// The left margin. /// Left = 8, /// /// All margins. /// All = Top | Bottom | Right | Left, /// /// The vertical margins. /// Vertical = Top | Bottom, /// /// The horizontal margins. /// Horizontal = Left | Right, } } }