#if FIVEM using CitizenFX.Core.Native; using CitizenFX.Core.UI; using Font = CitizenFX.Core.UI.Font; #elif RAGEMP using RAGE.Game; #elif RPH using Rage.Native; #elif SHVDN3 using GTA.UI; using GTA.Native; using Font = GTA.UI.Font; #endif using System.Collections.Generic; using System.Drawing; using System.Text; using LemonUI.Extensions; namespace LemonUI.Elements { /// /// A text string. /// public class ScaledText : IText { #region Consistent Values /// /// The size of every chunk of text. /// private const int chunkSize = 90; #endregion #region Private Fields /// /// The absolute 1080p based screen position. /// private PointF absolutePosition = PointF.Empty; /// /// The relative 0-1 relative position. /// private PointF relativePosition = PointF.Empty; /// /// The raw string of text. /// private string text = string.Empty; /// /// The raw string split into equally sized strings. /// private List chunks = new List(); /// /// The alignment of the item. /// private Alignment alignment = Alignment.Left; /// /// The word wrap value passed by the user. /// private float internalWrap = 0f; /// /// The real word wrap value based on the position of the text. /// private float realWrap = 0f; #endregion #region Public Properties /// /// The position of the text. /// public PointF Position { get => absolutePosition; set { absolutePosition = value; relativePosition = value.ToRelative(); } } /// /// The text to draw. /// public string Text { get => text; set { text = value; Slice(); } } /// /// The color of the text. /// public Color Color { get; set; } = Color.FromArgb(255, 255, 255, 255); /// /// The game font to use. /// public Font Font { get; set; } = Font.ChaletLondon; /// /// The scale of the text. /// public float Scale { get; set; } = 1f; /// /// If the text should have a drop down shadow. /// public bool Shadow { get; set; } = false; /// /// If the test should have an outline. /// public bool Outline { get; set; } = false; /// /// The alignment of the text. /// public Alignment Alignment { get => alignment; set { alignment = value; Recalculate(); } } /// /// The distance from the start position where the text will be wrapped into new lines. /// public float WordWrap { get { return internalWrap; } set { internalWrap = value; Recalculate(); } } /// /// The width that the text takes from the screen. /// public float Width { get { #if FIVEM API.BeginTextCommandWidth("CELL_EMAIL_BCON"); Add(); return API.EndTextCommandGetWidth(true) * 1f.ToXAbsolute(); #elif RAGEMP Invoker.Invoke(Natives.BeginTextCommandWidth, "CELL_EMAIL_BCON"); Add(); return Invoker.Invoke(Natives.EndTextCommandGetWidth) * 1f.ToXAbsolute(); #elif RPH NativeFunction.CallByHash(0x54CE8AC98E120CAB, "CELL_EMAIL_BCON"); Add(); return NativeFunction.CallByHash(0x85F061DA64ED2F67, true) * 1f.ToXAbsolute(); #elif SHVDN3 Function.Call(Hash._BEGIN_TEXT_COMMAND_GET_WIDTH, "CELL_EMAIL_BCON"); Add(); return Function.Call(Hash._END_TEXT_COMMAND_GET_WIDTH, true) * 1f.ToXAbsolute(); #endif } } /// /// The number of lines used by this text. /// public int LineCount { get { #if FIVEM API.BeginTextCommandLineCount("CELL_EMAIL_BCON"); #elif RAGEMP Invoker.Invoke(Natives.BeginTextCommandLineCount, "CELL_EMAIL_BCON"); #elif RPH NativeFunction.CallByHash(0x521FB041D93DD0E4, "CELL_EMAIL_BCON"); #elif SHVDN3 Function.Call(Hash._BEGIN_TEXT_COMMAND_LINE_COUNT, "CELL_EMAIL_BCON"); #endif Add(); #if FIVEM return API.EndTextCommandGetLineCount(relativePosition.X, relativePosition.Y); #elif RAGEMP return Invoker.Invoke(Natives.EndTextCommandGetLineCount, relativePosition.X, relativePosition.Y); #elif RPH return NativeFunction.CallByHash(0x9040DFB09BE75706, relativePosition.X, relativePosition.Y); #elif SHVDN3 return Function.Call(Hash._END_TEXT_COMMAND_LINE_COUNT, relativePosition.X, relativePosition.Y); #endif } } /// /// The relative height of each line in the text. /// public float LineHeight { get { // Height will always be 1080 #if FIVEM return 1080 * API.GetTextScaleHeight(Scale, (int)Font); #elif RAGEMP return 1080 * Invoker.Invoke(Natives.GetTextScaleHeight, Scale, (int)Font); #elif RPH return 1080 * NativeFunction.CallByHash(0xDB88A37483346780, Scale, (int)Font); #elif SHVDN3 return 1080 * Function.Call(Hash.GET_RENDERED_CHARACTER_HEIGHT, Scale, (int)Font); #endif } } #endregion #region Constructors /// /// Creates a text with the specified options. /// /// The position where the text should be located. /// The text to show. public ScaledText(PointF pos, string text) : this(pos, text, 1f, Font.ChaletLondon) { } /// /// Creates a text with the specified options. /// /// The position where the text should be located. /// The text to show. /// The scale of the text. public ScaledText(PointF pos, string text, float scale) : this(pos, text, scale, Font.ChaletLondon) { } /// /// Creates a text with the specified options /// /// The position where the text should be located. /// The text to show. /// The scale of the text. /// The font to use. public ScaledText(PointF pos, string text, float scale, Font font) { Position = pos; Text = text; Scale = scale; Font = font; } #endregion #region Tools /// /// Adds the text information for measurement. /// private void Add() { if (Scale == 0) { return; } #if FIVEM foreach (string chunk in chunks) { API.AddTextComponentString(chunk); } API.SetTextFont((int)Font); API.SetTextScale(1f, Scale); API.SetTextColour(Color.R, Color.G, Color.B, Color.A); API.SetTextJustification((int)Alignment); if (Shadow) { API.SetTextDropShadow(); } if (Outline) { API.SetTextOutline(); } if (WordWrap > 0) { switch (Alignment) { case Alignment.Center: API.SetTextWrap(relativePosition.X - (realWrap * 0.5f), relativePosition.X + (realWrap * 0.5f)); break; case Alignment.Left: API.SetTextWrap(relativePosition.X, relativePosition.X + realWrap); break; case Alignment.Right: API.SetTextWrap(relativePosition.X - realWrap, relativePosition.X); break; } } else if (Alignment == Alignment.Right) { API.SetTextWrap(0f, relativePosition.X); } #elif RAGEMP foreach (string chunk in chunks) { Invoker.Invoke(Natives.AddTextComponentSubstringPlayerName, chunk); } Invoker.Invoke(Natives.SetTextFont, (int)Font); Invoker.Invoke(Natives.SetTextScale, 1f, Scale); Invoker.Invoke(Natives.SetTextColour, Color.R, Color.G, Color.B, Color.A); Invoker.Invoke(Natives.SetTextJustification, (int)Alignment); if (Shadow) { Invoker.Invoke(Natives.SetTextDropShadow); } if (Outline) { Invoker.Invoke(Natives.SetTextOutline); } if (WordWrap > 0) { switch (Alignment) { case Alignment.Center: Invoker.Invoke(Natives.SetTextWrap, relativePosition.X - (realWrap * 0.5f), relativePosition.X + (realWrap * 0.5f)); break; case Alignment.Left: Invoker.Invoke(Natives.SetTextWrap, relativePosition.X, relativePosition.X + realWrap); break; case Alignment.Right: Invoker.Invoke(Natives.SetTextWrap, relativePosition.X - realWrap, relativePosition.X); break; } } else if (Alignment == Alignment.Right) { Invoker.Invoke(0x63145D9C883A1A70, 0f, relativePosition.X); } #elif RPH foreach (string chunk in chunks) { NativeFunction.CallByHash(0x6C188BE134E074AA, chunk); } NativeFunction.CallByHash(0x66E0276CC5F6B9DA, (int)Font); NativeFunction.CallByHash(0x07C837F9A01C34C9, 1f, Scale); NativeFunction.CallByHash(0xBE6B23FFA53FB442, Color.R, Color.G, Color.B, Color.A); NativeFunction.CallByHash(0x4E096588B13FFECA, (int)Alignment); if (Shadow) { NativeFunction.CallByHash(0x1CA3E9EAC9D93E5E); } if (Outline) { NativeFunction.CallByHash(0x2513DFB0FB8400FE); } if (WordWrap > 0) { switch (Alignment) { case Alignment.Center: NativeFunction.CallByHash(0x63145D9C883A1A70, relativePosition.X - (realWrap * 0.5f), relativePosition.X + (realWrap * 0.5f)); break; case Alignment.Left: NativeFunction.CallByHash(0x63145D9C883A1A70, relativePosition.X, relativePosition.X + realWrap); break; case Alignment.Right: NativeFunction.CallByHash(0x63145D9C883A1A70, relativePosition.X - realWrap, relativePosition.X); break; } } else if (Alignment == Alignment.Right) { NativeFunction.CallByHash(0x63145D9C883A1A70, 0f, relativePosition.X); } #elif SHVDN3 foreach (string chunk in chunks) { Function.Call((Hash)0x6C188BE134E074AA, chunk); // _ADD_TEXT_COMPONENT_STRING on v2, ADD_TEXT_COMPONENT_SUBSTRING_PLAYER_NAME on v3 } Function.Call(Hash.SET_TEXT_FONT, (int)Font); Function.Call(Hash.SET_TEXT_SCALE, 1f, Scale); Function.Call(Hash.SET_TEXT_COLOUR, Color.R, Color.G, Color.B, Color.A); Function.Call(Hash.SET_TEXT_JUSTIFICATION, (int)Alignment); if (Shadow) { Function.Call(Hash.SET_TEXT_DROP_SHADOW); } if (Outline) { Function.Call(Hash.SET_TEXT_OUTLINE); } if (WordWrap > 0) { switch (Alignment) { case Alignment.Center: Function.Call(Hash.SET_TEXT_WRAP, relativePosition.X - (realWrap * 0.5f), relativePosition.X + (realWrap * 0.5f)); break; case Alignment.Left: Function.Call(Hash.SET_TEXT_WRAP, relativePosition.X, relativePosition.X + realWrap); break; case Alignment.Right: Function.Call(Hash.SET_TEXT_WRAP, relativePosition.X - realWrap, relativePosition.X); break; } } else if (Alignment == Alignment.Right) { Function.Call(Hash.SET_TEXT_WRAP, 0f, relativePosition.X); } #endif } /// /// Slices the string of text into appropiately saved chunks. /// private void Slice() { // If the entire text is under 90 bytes, save it as is and return if (Encoding.UTF8.GetByteCount(text) <= chunkSize) { chunks.Clear(); chunks.Add(text); return; } // Create a new list of chunks and a temporary string List newChunks = new List(); string temp = string.Empty; // Iterate over the characters in the string foreach (char character in text) { // Create a temporary string with the character string with = string.Concat(temp, character); // If this string is higher than 90 bytes, add the existing string onto the list if (Encoding.UTF8.GetByteCount(with) > chunkSize) { newChunks.Add(temp); temp = character.ToString(); continue; } // And save the new string generated temp = with; } // If after finishing we still have a piece, save it if (temp != string.Empty) { newChunks.Add(temp); } // Once we have finished, replace the old chunks chunks = newChunks; } /// /// Recalculates the size, position and word wrap of this item. /// public void Recalculate() { // Do the normal Size and Position recalculation relativePosition = absolutePosition.ToRelative(); // And recalculate the word wrap if necessary if (internalWrap <= 0) { realWrap = 0; } else { realWrap = internalWrap.ToXRelative(); } } #endregion #region Public Functions /// /// Draws the text on the screen. /// public void Draw() { #if FIVEM API.SetTextEntry("CELL_EMAIL_BCON"); #elif RAGEMP Invoker.Invoke(Natives.BeginTextCommandDisplayText, "CELL_EMAIL_BCON"); #elif RPH NativeFunction.CallByHash(0x25FBB336DF1804CB, "CELL_EMAIL_BCON"); #elif SHVDN3 Function.Call((Hash)0x25FBB336DF1804CB, "CELL_EMAIL_BCON"); // _SET_TEXT_ENTRY on v2, BEGIN_TEXT_COMMAND_DISPLAY_TEXT on v3 #endif Add(); #if FIVEM API.DrawText(relativePosition.X, relativePosition.Y); #elif RAGEMP Invoker.Invoke(Natives.DrawDebugText, relativePosition.X, relativePosition.Y); #elif RPH NativeFunction.CallByHash(0xCD015E5BB0D96A57, relativePosition.X, relativePosition.Y); #elif SHVDN3 Function.Call((Hash)0xCD015E5BB0D96A57, relativePosition.X, relativePosition.Y); // _DRAW_TEXT on v2, END_TEXT_COMMAND_DISPLAY_TEXT on v3 #endif } #endregion } }