2019-08-27 21:44:02 +02:00
using System ;
2019-08-09 22:23:16 +02:00
using Microsoft.Xna.Framework ;
using Microsoft.Xna.Framework.Graphics ;
2022-04-25 15:25:58 +02:00
using MLEM.Graphics ;
2019-11-02 14:53:59 +01:00
using MLEM.Misc ;
2019-08-09 22:23:16 +02:00
using MLEM.Textures ;
2019-10-14 21:28:12 +02:00
using MLEM.Ui.Style ;
2022-08-20 11:39:28 +02:00
#if FNA
using MLEM.Extensions ;
#endif
2019-08-09 22:23:16 +02:00
namespace MLEM.Ui.Elements {
2020-05-22 17:02:24 +02:00
/// <summary>
/// An image element to be used inside of a <see cref="UiSystem"/>.
/// An image is simply an element that displays a supplied <see cref="TextureRegion"/> and optionally allows for the texture region to remain at its original aspect ratio, regardless of the element's size.
/// </summary>
2019-08-09 22:23:16 +02:00
public class Image : Element {
2020-05-22 17:02:24 +02:00
/// <summary>
/// The color to render the image at
/// </summary>
2019-10-14 21:28:12 +02:00
public StyleProp < Color > Color ;
2020-05-22 17:02:24 +02:00
/// <summary>
/// A callback to retrieve the <see cref="TextureRegion"/> that this image should render.
/// This can be used if the image changes frequently.
/// </summary>
2019-09-26 17:39:38 +02:00
public TextureCallback GetTextureCallback ;
2020-05-22 17:02:24 +02:00
/// <summary>
/// The texture that this <see cref="TextureRegion"/> should render
/// </summary>
2019-09-11 21:01:08 +02:00
public TextureRegion Texture {
2020-05-17 00:59:15 +02:00
get {
2023-02-03 11:31:18 +01:00
this . CheckTextureChange ( ) ;
return this . displayedTexture ;
2020-05-17 00:59:15 +02:00
}
2019-09-11 21:01:08 +02:00
set {
2023-02-03 11:31:18 +01:00
this . explicitlySetTexture = value ;
this . CheckTextureChange ( ) ;
2019-09-11 21:01:08 +02:00
}
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// Whether this image element's <see cref="Element.Size"/> should be based on the size of the <see cref="TextureRegion"/> given.
2021-10-29 17:11:45 +02:00
/// Note that, when scaling to the image's size, the <see cref="Element.Scale"/> is also taken into account.
2020-05-22 17:02:24 +02:00
/// </summary>
2019-09-11 21:01:08 +02:00
public bool ScaleToImage {
get = > this . scaleToImage ;
set {
if ( this . scaleToImage ! = value ) {
this . scaleToImage = value ;
this . SetAreaDirty ( ) ;
}
}
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// Whether to cause the <see cref="TextureRegion"/> to be rendered at its proper aspect ratio.
/// If this is false, the image will be stretched according to this component's size.
/// </summary>
2019-08-27 21:44:02 +02:00
public bool MaintainImageAspect = true ;
2020-05-22 17:02:24 +02:00
/// <summary>
/// The <see cref="SpriteEffects"/> that the texture should be rendered with
/// </summary>
2019-09-12 18:44:24 +02:00
public SpriteEffects ImageEffects = SpriteEffects . None ;
2020-05-22 17:02:24 +02:00
/// <summary>
/// The scale that the image should be rendered with
/// </summary>
2019-09-12 18:44:24 +02:00
public Vector2 ImageScale = Vector2 . One ;
2020-05-22 17:02:24 +02:00
/// <summary>
/// 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.
/// </summary>
2019-09-12 18:44:24 +02:00
public float ImageRotation ;
2023-11-23 22:16:31 +01:00
/// <summary>
/// Whether this image's width should automatically be calculated based on this image's calculated height in relation to its <see cref="Texture"/>'s aspect ratio.
/// Note that, if this is <see langword="true"/>, the <see cref="Element.AutoSizeAddedAbsolute"/> value will still be applied to this image's width.
/// </summary>
public bool SetWidthBasedOnAspect {
get = > this . setWidthBasedOnAspect ;
set {
if ( this . setWidthBasedOnAspect ! = value ) {
this . setWidthBasedOnAspect = value ;
this . SetAreaDirty ( ) ;
}
}
}
/// <summary>
/// Whether this image's height should automatically be calculated based on this image's calculated width in relation to its <see cref="Texture"/>'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 <see langword="true"/>, the <see cref="Element.AutoSizeAddedAbsolute"/> value will still be applied to this image's height.
/// </summary>
public bool SetHeightBasedOnAspect {
get = > this . setHeightBasedOnAspect ;
set {
if ( this . setHeightBasedOnAspect ! = value ) {
this . setHeightBasedOnAspect = value ;
this . SetAreaDirty ( ) ;
}
}
}
2019-08-09 22:23:16 +02:00
2022-12-21 21:47:49 +01:00
/// <inheritdoc />
public override bool IsHidden = > base . IsHidden | | this . Texture = = null ;
2021-10-30 15:01:04 +02:00
private bool scaleToImage ;
2023-11-23 22:16:31 +01:00
private bool setWidthBasedOnAspect ;
private bool setHeightBasedOnAspect ;
2023-02-03 11:31:18 +01:00
private TextureRegion explicitlySetTexture ;
private TextureRegion displayedTexture ;
2021-10-30 15:01:04 +02:00
2020-05-22 17:02:24 +02:00
/// <summary>
/// Creates a new image with the given settings
/// </summary>
/// <param name="anchor">The image's anchor</param>
/// <param name="size">The image's size</param>
/// <param name="texture">The texture the image should render</param>
/// <param name="scaleToImage">Whether this image's size should be based on the texture's size</param>
2019-08-09 22:23:16 +02:00
public Image ( Anchor anchor , Vector2 size , TextureRegion texture , bool scaleToImage = false ) : base ( anchor , size ) {
2019-09-26 17:39:38 +02:00
this . Texture = texture ;
2023-11-23 22:16:31 +01:00
this . ScaleToImage = scaleToImage ;
2019-09-26 17:39:38 +02:00
this . CanBeSelected = false ;
this . CanBeMoused = false ;
}
2020-05-22 17:02:24 +02:00
/// <inheritdoc cref="Image(Anchor,Vector2,TextureRegion,bool)"/>
2019-09-26 17:39:38 +02:00
public Image ( Anchor anchor , Vector2 size , TextureCallback getTextureCallback , bool scaleToImage = false ) : base ( anchor , size ) {
this . GetTextureCallback = getTextureCallback ;
this . Texture = getTextureCallback ( this ) ;
2023-11-23 22:16:31 +01:00
this . ScaleToImage = scaleToImage ;
2019-08-28 18:27:17 +02:00
this . CanBeSelected = false ;
2019-09-11 20:10:28 +02:00
this . CanBeMoused = false ;
2019-08-09 22:23:16 +02:00
}
2020-05-22 17:02:24 +02:00
/// <inheritdoc />
2019-11-02 14:53:59 +01:00
protected override Vector2 CalcActualSize ( RectangleF parentArea ) {
2023-11-23 22:16:31 +01:00
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 ;
2019-09-26 17:39:38 +02:00
}
2023-02-03 11:31:18 +01:00
/// <inheritdoc />
public override void Update ( GameTime time ) {
this . CheckTextureChange ( ) ;
base . Update ( time ) ;
}
2020-05-22 17:02:24 +02:00
/// <inheritdoc />
2022-04-25 15:25:58 +02:00
public override void Draw ( GameTime time , SpriteBatch batch , float alpha , SpriteBatchContext context ) {
2020-05-17 00:59:15 +02:00
if ( this . Texture = = null )
2019-09-26 17:39:38 +02:00
return ;
2020-05-17 00:59:15 +02:00
var center = new Vector2 ( this . Texture . Width / 2F , this . Texture . Height / 2F ) ;
2019-11-05 13:28:41 +01:00
var color = this . Color . OrDefault ( Microsoft . Xna . Framework . Color . White ) * alpha ;
2019-08-27 21:44:02 +02:00
if ( this . MaintainImageAspect ) {
2020-05-17 00:59:15 +02:00
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 ) ;
2019-08-27 21:44:02 +02:00
} else {
2020-05-17 00:59:15 +02:00
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 ) ;
2019-08-27 21:44:02 +02:00
}
2022-04-25 15:25:58 +02:00
base . Draw ( time , batch , alpha , context ) ;
2019-08-09 22:23:16 +02:00
}
2023-02-03 11:31:18 +01:00
private void CheckTextureChange ( ) {
var newTexture = this . GetTextureCallback ? . Invoke ( this ) ? ? this . explicitlySetTexture ;
if ( this . displayedTexture = = newTexture )
2022-12-21 21:47:49 +01:00
return ;
2023-02-03 11:31:18 +01:00
var nullChanged = this . displayedTexture = = null ! = ( newTexture = = null ) ;
this . displayedTexture = newTexture ;
2023-11-23 22:16:31 +01:00
if ( nullChanged | | this . ScaleToImage | | this . SetWidthBasedOnAspect | | this . SetHeightBasedOnAspect )
2022-12-21 21:47:49 +01:00
this . SetAreaDirty ( ) ;
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// A delegate method used for <see cref="Image.GetTextureCallback"/>
/// </summary>
/// <param name="image">The current image element</param>
2019-09-26 17:39:38 +02:00
public delegate TextureRegion TextureCallback ( Image image ) ;
2019-08-09 22:23:16 +02:00
}
2022-06-17 18:23:47 +02:00
}