using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace Barotrauma { public class GUIImage : GUIComponent { //paths of the textures that are being loaded asynchronously private static readonly List activeTextureLoads = new List(); private static bool loadingTextures; public static bool LoadingTextures { get { return loadingTextures; } } public float Rotation; private Sprite sprite; private Rectangle sourceRect; private bool crop; private readonly bool scaleToFit; private bool lazyLoaded, loading; public bool LoadAsynchronously; public bool Crop { get { return crop; } } public void SetCrop(bool state, bool center = true) { crop = state; if (crop && sprite != null) { sourceRect.Width = Math.Min(sprite.SourceRect.Width, (int)(Rect.Width / Scale)); sourceRect.Height = Math.Min(sprite.SourceRect.Height, (int)(Rect.Height / Scale)); if (center) { sourceRect.X = (sprite.SourceRect.Width - sourceRect.Width) / 2; sourceRect.Y = (sprite.SourceRect.Height - sourceRect.Height) / 2; } origin = sourceRect.Size.ToVector2() / 2; } else { origin = sprite == null ? Vector2.Zero : sprite.size / 2; } } private Vector2 origin; public float Scale { get; set; } public Rectangle SourceRect { get { return sourceRect; } set { sourceRect = value; } } public Sprite Sprite { get { return sprite; } set { if (sprite == value) { return; } sprite = value; sourceRect = value == null ? Rectangle.Empty : value.SourceRect; origin = value == null ? Vector2.Zero : value.size / 2; if (scaleToFit) { RecalculateScale(); } } } public BlendState BlendState; public ComponentState? OverrideState = null; public GUIImage(RectTransform rectT, string style, bool scaleToFit = false) : this(rectT, null, null, scaleToFit, style) { } public GUIImage(RectTransform rectT, Sprite sprite, Rectangle? sourceRect = null, bool scaleToFit = false) : this(rectT, sprite, sourceRect, scaleToFit, null) { } private GUIImage(RectTransform rectT, Sprite sprite, Rectangle? sourceRect, bool scaleToFit, string style) : base(style, rectT) { this.scaleToFit = scaleToFit; Sprite = sprite; if (sourceRect.HasValue) { this.sourceRect = sourceRect.Value; } else { this.sourceRect = sprite == null ? Rectangle.Empty : sprite.SourceRect; } if (style == null) { color = hoverColor = selectedColor = pressedColor = disabledColor = Color.White; } if (!scaleToFit) { Scale = 1.0f; } else { if (Sprite != null && !Sprite.LazyLoad) { rectT.SizeChanged += RecalculateScale; } } Enabled = true; } protected override void Draw(SpriteBatch spriteBatch) { if (!Visible || loading) { return; } if (Parent != null) { State = Parent.State; } if (OverrideState != null) { State = OverrideState.Value; } if (Sprite != null && Sprite.LazyLoad && !lazyLoaded) { if (LoadAsynchronously) { loadingTextures = true; loading = true; TaskPool.Add("LoadTextureAsync", LoadTextureAsync(), task => { if (task.Exception != null) { var innerMost = task.Exception.GetInnermost(); DebugConsole.ThrowError($"Failed to load \"{Sprite.FilePath}\"", innerMost); } loading = false; lazyLoaded = true; RectTransform.SizeChanged += RecalculateScale; RecalculateScale(); }); return; } else { Sprite.EnsureLazyLoaded(); RectTransform.SizeChanged += RecalculateScale; RecalculateScale(); lazyLoaded = true; } } Color currentColor = GetColor(State); if (BlendState != null) { spriteBatch.End(); spriteBatch.Begin(blendState: BlendState, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable); } var style = Style; if (style != null) { foreach (UISprite uiSprite in style.Sprites[State]) { if (Math.Abs(Rotation) > float.Epsilon) { float scale = Math.Min(Rect.Width / uiSprite.Sprite.size.X, Rect.Height / uiSprite.Sprite.size.Y); spriteBatch.Draw(uiSprite.Sprite.Texture, Rect.Center.ToVector2(), uiSprite.Sprite.SourceRect, currentColor * (currentColor.A / 255.0f), Rotation, uiSprite.Sprite.size / 2, Scale * scale, SpriteEffects, 0.0f); } else { uiSprite.Draw(spriteBatch, Rect, currentColor * (currentColor.A / 255.0f), SpriteEffects); } } } else if (sprite?.Texture is { IsDisposed: false }) { spriteBatch.Draw(sprite.Texture, Rect.Center.ToVector2(), sourceRect, currentColor * (currentColor.A / 255.0f), Rotation, origin, Scale, SpriteEffects, 0.0f); } if (BlendState != null) { spriteBatch.End(); spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable); } } private void RecalculateScale() { if (sourceRect == Rectangle.Empty && sprite != null) { sourceRect = sprite.SourceRect; } Scale = sprite == null || sprite.SourceRect.Width == 0 || sprite.SourceRect.Height == 0 ? 1.0f : Math.Min(RectTransform.Rect.Width / (float)sprite.SourceRect.Width, RectTransform.Rect.Height / (float)sprite.SourceRect.Height); } private async Task LoadTextureAsync() { await Task.Yield(); bool wait = true; { //if another GUIImage is already loading the same texture, wait for it to finish while (wait) { await Task.Delay(5); lock (activeTextureLoads) { wait = activeTextureLoads.Contains(Sprite.FullPath); } } } try { lock (activeTextureLoads) { activeTextureLoads.Add(Sprite.FullPath); } await Sprite.LazyLoadAsync(); } finally { DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, 10); while (!Sprite.Loaded && DateTime.Now < timeOut) { await Task.Delay(5); } lock (activeTextureLoads) { activeTextureLoads.Remove(Sprite.FullPath); loadingTextures = activeTextureLoads.Count > 0; } } return true; } } }