using System; using System.Collections.Generic; using System.Numerics; using Robust.Client.Graphics; using Robust.Shared.Graphics; using Robust.Shared.Maths; namespace Robust.Client.UserInterface.Controls { /// /// Like a TextureRect... but layered /// [Virtual] public class LayeredTextureRect : Control { public const string StylePropertyShader = "shader"; private bool _canShrink; private List _textures = new(); private Vector2 _textureScale = Vector2.One; public ShaderInstance? ShaderOverride { get; set; } /// /// The textures to draw. /// public List Textures { get => _textures; set { _textures = value; InvalidateMeasure(); } } /// /// Scales the texture displayed. /// /// /// This does not apply to the following stretch modes: . /// public Vector2 TextureScale { get => _textureScale; set { _textureScale = value; InvalidateMeasure(); } } /// /// If true, this control can shrink below the size of . /// /// /// This does not set . /// Certain stretch modes may display outside the area of the control unless it is set. /// public bool CanShrink { get => _canShrink; set { _canShrink = value; InvalidateMeasure(); } } /// /// Controls how the texture should be drawn if the control is larger than the size of the texture. /// public TextureRect.StretchMode Stretch { get; set; } = TextureRect.StretchMode.Keep; protected internal override void Draw(DrawingHandleScreen handle) { base.Draw(handle); ShaderInstance? shader = null; foreach (var texture in Textures) { if (ShaderOverride != null) { shader = ShaderOverride; } else if (TryGetStyleProperty(StylePropertyShader, out ShaderInstance? styleShader)) { shader = styleShader; } if (shader != null) { handle.UseShader(shader); } switch (Stretch) { case TextureRect.StretchMode.Scale: handle.DrawTextureRect(texture, UIBox2.FromDimensions(Vector2.Zero, PixelSize)); break; case TextureRect.StretchMode.Tile: // TODO: Implement Tile. case TextureRect.StretchMode.Keep: handle.DrawTextureRect(texture, UIBox2.FromDimensions(Vector2.Zero, texture.Size * _textureScale * UIScale)); break; case TextureRect.StretchMode.KeepCentered: { var position = (PixelSize - texture.Size * _textureScale * UIScale) / 2; handle.DrawTextureRect(texture, UIBox2.FromDimensions(position, texture.Size * _textureScale * UIScale)); break; } case TextureRect.StretchMode.KeepAspect: case TextureRect.StretchMode.KeepAspectCentered: { var (texWidth, texHeight) = texture.Size * _textureScale; var width = texWidth * (PixelSize.Y / texHeight); var height = (float)PixelSize.Y; if (width > PixelSize.X) { width = PixelSize.X; height = texHeight * (PixelSize.X / texWidth); } var size = new Vector2(width, height); var position = Vector2.Zero; if (Stretch == TextureRect.StretchMode.KeepAspectCentered) { position = (PixelSize - size) / 2; } handle.DrawTextureRectRegion(texture, UIBox2.FromDimensions(position, size)); break; } case TextureRect.StretchMode.KeepAspectCovered: var texSize = texture.Size * _textureScale; // Calculate the scale necessary to fit width and height to control size. var (scaleX, scaleY) = PixelSize / texSize; // Use whichever scale is greater. var scale = Math.Max(scaleX, scaleY); // Offset inside the actual texture. var offset = (texSize - PixelSize) / scale / 2f; handle.DrawTextureRectRegion(texture, PixelSizeBox, UIBox2.FromDimensions(offset, PixelSize / scale)); break; default: throw new ArgumentOutOfRangeException(); } } } protected override Vector2 MeasureOverride(Vector2 availableSize) { if (_textures.Count == 0 || CanShrink) { return Vector2.Zero; } var largestX = 0; var largestY = 0; foreach (var texture in Textures) { largestX = Math.Max(texture.Width, largestX); largestY = Math.Max(texture.Height, largestY); } return new Vector2(largestX, largestY) * TextureScale; } } }