Files
barotrauma-localmods/Quick Interactions/CSharp/Client/CrabUI/Components/CUITextBlock.cs

212 lines
6.1 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using Barotrauma;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
using System.Xml.Linq;
namespace QICrabUI
{
/// <summary>
/// Passive block of text
/// </summary>
public class CUITextBlock : CUIComponent
{
public event Action OnTextChanged;
public Action AddOnTextChanged { set { OnTextChanged += value; } }
//TODO move padding here, it makes no sense in CUIComponent
private bool wrap;
[CUISerializable]
public bool Wrap
{
get => wrap;
set
{
wrap = value;
MeasureUnwrapped();
TextPropChanged = true;
}
}
[CUISerializable] public Color TextColor { get; set; }
private GUIFont font = GUIStyle.Font;
[CUISerializable]
public GUIFont Font
{
get => font;
set
{
font = value;
MeasureUnwrapped();
TextPropChanged = true;
}
}
/// <summary>
/// A Vector2 ([0..1],[0..1])
/// </summary>
[CUISerializable] public Vector2 TextAlign { get; set; }
[CUISerializable] public bool Vertical { get; set; }
/// <summary>
/// Lil optimization: ghost text won't set forsed size and parent won't be able to fit to it
/// But it will increase performance in large lists
/// </summary>
[CUISerializable] public bool GhostText { get; set; }
[CUISerializable]
public string Text { get => text; set => SetText(value); }
[CUISerializable]
public float TextScale { get => textScale; set => SetTextScale(value); }
#region Cringe
protected Vector2 RealTextSize;
[Calculated] protected Vector2 TextDrawPos { get; set; }
[Calculated] protected string WrappedText { get; set; } = "";
protected Vector2? WrappedForThisSize;
[Calculated] protected Vector2 WrappedSize { get; set; }
public Vector2 UnwrappedTextSize { get; set; }
public Vector2 UnwrappedMinSize { get; set; }
protected bool TextPropChanged;
#endregion
protected string text = ""; internal void SetText(string value)
{
text = value ?? "";
OnTextChanged?.Invoke();
MeasureUnwrapped();
TextPropChanged = true;
OnPropChanged();
OnAbsolutePropChanged();
}
protected float textScale = 0.9f; internal void SetTextScale(float value)
{
textScale = value;
MeasureUnwrapped();
TextPropChanged = true;
OnPropChanged();
OnAbsolutePropChanged();
}
//Note: works only on unwrapped text for now because WrappedText is delayed
/// <summary>
/// X coordinate of caret if there was one
/// Used in CUITextInput, you don't need this
/// </summary>
/// <param name="i"></param>
/// <returns></returns>
public float CaretPos(int i)
{
return Font.MeasureString(Text.SubstringSafe(0, i)).X * TextScale + Padding.X;
}
//Note: works only on unwrapped text for now because WrappedText is delayed
/// <summary>
/// Tndex of caret if there was one
/// Used in CUITextInput, you don't need this
/// </summary>
/// <param name="i"></param>
/// <returns></returns>
public int CaretIndex(float x)
{
int Aprox = (int)Math.Round((x - Padding.X) / Font.MeasureString(Text).X * Text.Length);
int closestCaretPos = Aprox;
float smallestDif = Math.Abs(x - CaretPos(Aprox));
for (int i = Aprox - 2; i <= Aprox + 2; i++)
{
float dif = Math.Abs(x - CaretPos(i));
if (dif < smallestDif)
{
closestCaretPos = i;
smallestDif = dif;
}
}
return closestCaretPos;
}
// Small optimisation, doesn't seem to save much
protected virtual void MeasureUnwrapped()
{
UnwrappedTextSize = Font.MeasureString(Text) * TextScale;
UnwrappedMinSize = UnwrappedTextSize + Padding * 2;
}
protected virtual Vector2 DoWrapFor(Vector2 size)
{
// To prevent loop
if (!(WrappedForThisSize.HasValue && WrappedForThisSize != size) && !TextPropChanged) return WrappedSize;
TextPropChanged = false;
WrappedForThisSize = size;
// There's no way to wrap vertical text
bool isInWrapZone = Vertical ? false : size.X <= UnwrappedMinSize.X;
bool isSolid = Vertical || !Wrap;
if (Vertical) size = new Vector2(0, size.Y);
if ((Wrap && isInWrapZone) || Vertical)
{
WrappedText = Font.WrapText(Text, size.X / TextScale - Padding.X * 2).Trim('\n');
RealTextSize = Font.MeasureString(WrappedText) * TextScale;
}
else
{
WrappedText = Text;
RealTextSize = UnwrappedTextSize;
}
if (WrappedText == "") RealTextSize = new Vector2(0, 0);
RealTextSize = new Vector2((float)Math.Round(RealTextSize.X), (float)Math.Round(RealTextSize.Y));
Vector2 minSize = RealTextSize + Padding * 2;
if (isSolid && !GhostText)
{
SetForcedMinSize(new CUINullVector2(minSize));
}
WrappedSize = new Vector2(Math.Max(size.X, minSize.X), Math.Max(size.Y, minSize.Y));
return WrappedSize;
}
internal override Vector2 AmIOkWithThisSize(Vector2 size)
{
return DoWrapFor(size);
}
//Note: This is a bottleneck for large lists of text
internal override void UpdatePseudoChildren()
{
if (CulledOut) return;
TextDrawPos = CUIAnchor.GetChildPos(Real, TextAlign, Vector2.Zero, RealTextSize / Scale) + Padding * CUIAnchor.Direction(TextAlign) / Scale;
//CUIDebug.Capture(null, this, "UpdatePseudoChildren", "", "TextDrawPos", $"{TextDrawPos - Real.Position}");
}
public override void Draw(SpriteBatch spriteBatch)
{
base.Draw(spriteBatch);
// Font.DrawString(spriteBatch, WrappedText, TextDrawPos, TextColor, rotation: 0, origin: Vector2.Zero, TextScale, spriteEffects: SpriteEffects.None, layerDepth: 0.1f);
Font.Value.DrawString(spriteBatch, WrappedText, TextDrawPos, TextColor, rotation: 0, origin: Vector2.Zero, TextScale / Scale, se: SpriteEffects.None, layerDepth: 0.1f);
}
public CUITextBlock() { }
public CUITextBlock(string text) : this()
{
Text = text;
}
}
}