using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using MLEM.Graphics; using MLEM.Misc; using MLEM.Textures; using MLEM.Ui.Style; #if FNA using MLEM.Extensions; #endif namespace MLEM.Ui.Elements { /// /// An image element to be used inside of a . /// An image is simply an element that displays a supplied and optionally allows for the texture region to remain at its original aspect ratio, regardless of the element's size. /// public class Image : Element { /// /// The color to render the image at /// public StyleProp Color; /// /// A callback to retrieve the that this image should render. /// This can be used if the image changes frequently. /// public TextureCallback GetTextureCallback; /// /// The texture that this should render /// public TextureRegion Texture { get { this.CheckTextureChange(); return this.displayedTexture; } set { this.explicitlySetTexture = value; this.CheckTextureChange(); } } /// /// Whether this image element's should be based on the size of the given. /// Note that, when scaling to the image's size, the is also taken into account. /// public bool ScaleToImage { get => this.scaleToImage; set { if (this.scaleToImage != value) { this.scaleToImage = value; this.SetAreaDirty(); } } } /// /// Whether to cause the to be rendered at its proper aspect ratio. /// If this is false, the image will be stretched according to this component's size. /// public bool MaintainImageAspect = true; /// /// The that the texture should be rendered with /// public SpriteEffects ImageEffects = SpriteEffects.None; /// /// The scale that the image should be rendered with /// public Vector2 ImageScale = Vector2.One; /// /// The rotation that the image should be rendered with. /// Note that increased rotation does not increase this component's size, even if the rotated texture would go out of bounds of this component. /// public float ImageRotation; /// /// Whether this image's width should automatically be calculated based on this image's calculated height in relation to its 's aspect ratio. /// Note that, if this is , the value will still be applied to this image's width. /// public bool SetWidthBasedOnAspect { get => this.setWidthBasedOnAspect; set { if (this.setWidthBasedOnAspect != value) { this.setWidthBasedOnAspect = value; this.SetAreaDirty(); } } } /// /// Whether this image's height should automatically be calculated based on this image's calculated width in relation to its 's aspect ratio. /// This behavior is useful if an image should take up a certain width, but the aspect ratio of its texture can vary and the image should not take up more height than is necessary. /// Note that, if this is , the value will still be applied to this image's height. /// public bool SetHeightBasedOnAspect { get => this.setHeightBasedOnAspect; set { if (this.setHeightBasedOnAspect != value) { this.setHeightBasedOnAspect = value; this.SetAreaDirty(); } } } /// public override bool IsHidden => base.IsHidden || this.Texture == null; private bool scaleToImage; private bool setWidthBasedOnAspect; private bool setHeightBasedOnAspect; private TextureRegion explicitlySetTexture; private TextureRegion displayedTexture; /// /// Creates a new image with the given settings /// /// The image's anchor /// The image's size /// The texture the image should render /// Whether this image's size should be based on the texture's size public Image(Anchor anchor, Vector2 size, TextureRegion texture, bool scaleToImage = false) : base(anchor, size) { this.Texture = texture; this.ScaleToImage = scaleToImage; this.CanBeSelected = false; this.CanBeMoused = false; } /// public Image(Anchor anchor, Vector2 size, TextureCallback getTextureCallback, bool scaleToImage = false) : base(anchor, size) { this.GetTextureCallback = getTextureCallback; this.Texture = getTextureCallback(this); this.ScaleToImage = scaleToImage; this.CanBeSelected = false; this.CanBeMoused = false; } /// protected override Vector2 CalcActualSize(RectangleF parentArea) { var ret = base.CalcActualSize(parentArea); if (this.Texture != null) { if (this.ScaleToImage) ret = this.Texture.Size.ToVector2() * this.Scale; if (this.SetWidthBasedOnAspect) ret.X = ret.Y * this.Texture.Width / this.Texture.Height + this.ScaledAutoSizeAddedAbsolute.X; if (this.SetHeightBasedOnAspect) ret.Y = ret.X * this.Texture.Height / this.Texture.Width + this.ScaledAutoSizeAddedAbsolute.Y; } return ret; } /// public override void Update(GameTime time) { this.CheckTextureChange(); base.Update(time); } /// public override void Draw(GameTime time, SpriteBatch batch, float alpha, SpriteBatchContext context) { if (this.Texture == null) return; var center = new Vector2(this.Texture.Width / 2F, this.Texture.Height / 2F); var color = this.Color.OrDefault(Microsoft.Xna.Framework.Color.White) * alpha; if (this.MaintainImageAspect) { var scale = Math.Min(this.DisplayArea.Width / this.Texture.Width, this.DisplayArea.Height / this.Texture.Height); var imageOffset = new Vector2(this.DisplayArea.Width / 2F - this.Texture.Width * scale / 2, this.DisplayArea.Height / 2F - this.Texture.Height * scale / 2); batch.Draw(this.Texture, this.DisplayArea.Location + center * scale + imageOffset, color, this.ImageRotation, center, scale * this.ImageScale, this.ImageEffects, 0); } else { var scale = new Vector2(1F / this.Texture.Width, 1F / this.Texture.Height) * this.DisplayArea.Size; batch.Draw(this.Texture, this.DisplayArea.Location + center * scale, color, this.ImageRotation, center, scale * this.ImageScale, this.ImageEffects, 0); } base.Draw(time, batch, alpha, context); } private void CheckTextureChange() { var newTexture = this.GetTextureCallback?.Invoke(this) ?? this.explicitlySetTexture; if (this.displayedTexture == newTexture) return; var nullChanged = this.displayedTexture == null != (newTexture == null); this.displayedTexture = newTexture; if (nullChanged || this.ScaleToImage || this.SetWidthBasedOnAspect || this.SetHeightBasedOnAspect) this.SetAreaDirty(); } /// /// A delegate method used for /// /// The current image element public delegate TextureRegion TextureCallback(Image image); } }