using Barotrauma.Networking; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Xml.Linq; namespace Barotrauma.Items.Components { partial class ItemLabel : ItemComponent, IDrawableComponent, IHasExtraTextPickerEntries { private GUITextBlock textBlock; private Color textColor; private float scrollAmount; private string scrollingText; private float scrollPadding; private int scrollIndex; private bool needsScrolling; private float[] charWidths; private float prevScale; private Rectangle prevRect; private StringBuilder sb; private Vector4 padding; [Serialize("0,0,0,0", IsPropertySaveable.Yes, description: "The amount of padding around the text in pixels (left,top,right,bottom).")] public Vector4 Padding { get { return padding; } set { padding = value; TextBlock.Padding = value * item.Scale; } } private string text; [Serialize("", IsPropertySaveable.Yes, translationTextTag: "Label.", description: "The text displayed in the label.", alwaysUseInstanceValues: true), Editable(100)] public string Text { get { return text; } set { if (value == text || item.Rect.Width < 5) { return; } if (TextBlock.Rect.Width != item.Rect.Width || textBlock.Rect.Height != item.Rect.Height) { textBlock = null; } text = value; SetDisplayText(value); UpdateScrollingText(); } } private bool ignoreLocalization; [Editable, Serialize(false, IsPropertySaveable.Yes, "Whether or not to skip localization and always display the raw value.")] public bool IgnoreLocalization { get => ignoreLocalization; set { ignoreLocalization = value; SetDisplayText(Text); } } public LocalizedString DisplayText { get; private set; } [Editable, Serialize("0,0,0,255", IsPropertySaveable.Yes, description: "The color of the text displayed on the label (R,G,B,A).", alwaysUseInstanceValues: true)] public Color TextColor { get { return textColor; } set { if (textBlock != null) { textBlock.TextColor = value; } textColor = value; } } [Editable(0.0f, 10.0f), Serialize(1.0f, IsPropertySaveable.Yes, description: "The scale of the text displayed on the label.", alwaysUseInstanceValues: true)] public float TextScale { get { return textBlock == null ? 1.0f : textBlock.TextScale / BaseToRealTextScaleFactor; } set { if (textBlock != null) { float prevScale = TextBlock.TextScale; textBlock.TextScale = MathHelper.Clamp(value * BaseToRealTextScaleFactor, 0.1f, 10.0f); if (!MathUtils.NearlyEqual(prevScale, TextBlock.TextScale)) { SetScrollingText(); } } } } private bool scrollable; [Serialize(false, IsPropertySaveable.Yes, description: "Should the text scroll horizontally across the item if it's too long to be displayed all at once.")] public bool Scrollable { get { return scrollable; } set { scrollable = value; IsActive = value || parseSpecialTextTagOnStart; TextBlock.Wrap = !scrollable; TextBlock.TextAlignment = scrollable ? Alignment.CenterLeft : Alignment.Center; } } [Serialize(20.0f, IsPropertySaveable.Yes, description: "How fast the text scrolls across the item (only valid if Scrollable is set to true).")] public float ScrollSpeed { get; set; } private GUITextBlock TextBlock { get { if (textBlock == null) { RecreateTextBlock(); } return textBlock; } } public ItemLabel(Item item, ContentXElement element) : base(item, element) { } public IEnumerable GetExtraTextPickerEntries() { return SpecialTextTags; } private void SetScrollingText() { if (!scrollable) { return; } float totalWidth = textBlock.Font.MeasureString(DisplayText).X * TextBlock.TextScale; float textAreaWidth = Math.Max(textBlock.Rect.Width - textBlock.Padding.X - textBlock.Padding.Z, 0); if (totalWidth >= textAreaWidth) { //add enough spaces to fill the rect //(so the text can scroll entirely out of view before we reset it back to start) needsScrolling = true; float spaceWidth = textBlock.Font.MeasureChar(' ').X * TextBlock.TextScale; scrollingText = new string(' ', (int)Math.Ceiling(textAreaWidth / spaceWidth)) + DisplayText.Value; } else { //whole text can fit in the textblock, no need to scroll needsScrolling = false; TextBlock.Text = scrollingText = DisplayText.Value; scrollPadding = 0; scrollAmount = 0.0f; scrollIndex = 0; return; } //calculate character widths scrollPadding = 0; charWidths = new float[scrollingText.Length]; for (int i = 0; i < scrollingText.Length; i++) { float charWidth = TextBlock.Font.MeasureChar(scrollingText[i]).X * TextBlock.TextScale; scrollPadding = Math.Max(charWidth, scrollPadding); charWidths[i] = charWidth; } scrollIndex = MathHelper.Clamp(scrollIndex, 0, DisplayText.Length); } private static readonly string[] SpecialTextTags = new string[] { "[CurrentLocationName]", "[CurrentBiomeName]", "[CurrentSubName]" }; private bool parseSpecialTextTagOnStart; private void SetDisplayText(string value) { if (SpecialTextTags.Contains(value)) { parseSpecialTextTagOnStart = true; IsActive = true; } DisplayText = IgnoreLocalization ? value : TextManager.Get(value).Fallback(value); TextBlock.Text = DisplayText; if (Screen.Selected == GameMain.SubEditorScreen && Scrollable) { TextBlock.Text = ToolBox.LimitString(DisplayText, TextBlock.Font, item.Rect.Width); } SetScrollingText(); } private const float BaseTextSize = 12.0f; private float BaseToRealTextScaleFactor => BaseTextSize / GUIStyle.UnscaledSmallFont.Size; private void RecreateTextBlock() { textBlock = new GUITextBlock(new RectTransform(item.Rect.Size), "", textColor: textColor, font: GUIStyle.UnscaledSmallFont, textAlignment: scrollable ? Alignment.CenterLeft : Alignment.Center, wrap: !scrollable, style: null) { TextDepth = item.SpriteDepth - 0.00001f, RoundToNearestPixel = false, TextScale = TextScale * BaseToRealTextScaleFactor, Padding = padding * item.Scale }; } private void ParseSpecialTextTag() { switch (text) { case "[CurrentLocationName]": SetDisplayText(Level.Loaded?.StartLocation?.DisplayName.Value ?? string.Empty); break; case "[CurrentBiomeName]": SetDisplayText(Level.Loaded?.LevelData?.Biome?.DisplayName.Value ?? string.Empty); break; case "[CurrentSubName]": SetDisplayText(item.Submarine?.Info?.DisplayName.Value ?? string.Empty); break; default: break; } } public override void Update(float deltaTime, Camera cam) { if (parseSpecialTextTagOnStart) { ParseSpecialTextTag(); parseSpecialTextTagOnStart = false; } if (!scrollable) { IsActive = false; return; } if (scrollingText == null) { SetScrollingText(); } if (!needsScrolling) { return; } scrollAmount -= deltaTime * ScrollSpeed; UpdateScrollingText(); } private void UpdateScrollingText() { if (!scrollable || !needsScrolling) { return; } float currLength = 0; sb ??= new StringBuilder(); sb.Clear(); float textAreaWidth = textBlock.Rect.Width - textBlock.Padding.X - textBlock.Padding.Z; for (int i = scrollIndex; i < scrollingText.Length; i++) { //first character is out of view -> skip to next character if (i == scrollIndex && scrollAmount < -charWidths[i]) { scrollIndex++; scrollAmount = 0; if (scrollIndex >= scrollingText.Length) //reached the last character, reset { scrollIndex = 0; break; } continue; } //reached the right edge, stop adding more character if (scrollAmount + (currLength + charWidths[i] + scrollPadding) >= textAreaWidth) { break; } else { currLength += charWidths[i]; sb.Append(scrollingText[i]); } } TextBlock.Text = sb.ToString(); } public override void OnScaleChanged() { RecreateTextBlock(); SetDisplayText(Text); prevScale = item.Scale; prevRect = item.Rect; } public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1, Color? overrideColor = null) { if (item.ParentInventory != null) { return; } if (editing) { if (!MathUtils.NearlyEqual(prevScale, item.Scale) || prevRect != item.Rect) { RecreateTextBlock(); SetDisplayText(Text); prevScale = item.Scale; prevRect = item.Rect; } } var drawPos = new Vector2( item.DrawPosition.X - item.Rect.Width / 2.0f, -(item.DrawPosition.Y + item.Rect.Height / 2.0f)); textBlock.TextDepth = item.SpriteDepth - 0.0001f; textBlock.TextOffset = drawPos - textBlock.Rect.Location.ToVector2() + (editing ? Vector2.Zero : new Vector2(scrollAmount + scrollPadding, 0.0f)); textBlock.DrawManually(spriteBatch); } public void ClientEventRead(IReadMessage msg, float sendingTime) { Text = msg.ReadString(); } } }