Skip to content

File ScaledText.cs

File List > Elements > ScaledText.cs

Go to the documentation of this file


#if FIVEM
using CitizenFX.Core.Native;
using CitizenFX.Core.UI;
using Font = CitizenFX.Core.UI.Font;
#elif ALTV
using AltV.Net.Client;
#elif RAGEMP
using RAGE.Game;
#elif RPH
using Rage.Native;
#elif SHVDN3 || SHVDNC
using GTA.Native;
using GTA.UI;
using Font = GTA.UI.Font;
#endif
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using LemonUI.Tools;

namespace LemonUI.Elements
{
    public class ScaledText : IText
    {
        #region Constants

        private const int chunkSize = 90;

        #endregion

        #region Fields

        private PointF scaledPosition = PointF.Empty;
        private PointF relativePosition = PointF.Empty;
        private string text = string.Empty;
        private List<string> chunks = new List<string>();
        private Alignment alignment = Alignment.Left;
        private float internalWrap = 0f;
        private float realWrap = 0f;

        #endregion

        #region Properties

        public PointF Position
        {
            get => scaledPosition;
            set
            {
                scaledPosition = value;
                relativePosition = value.ToRelative();
            }
        }
        public string Text
        {
            get => text;
            set
            {
                text = value ?? throw new ArgumentNullException(nameof(value));
                Slice();
            }
        }
        public Color Color { get; set; } = Color.FromArgb(255, 255, 255, 255);
        public Font Font { get; set; } = Font.ChaletLondon;
        public float Scale { get; set; } = 1f;
        public bool Shadow { get; set; } = false;
        public bool Outline { get; set; } = false;
        public Alignment Alignment
        {
            get => alignment;
            set
            {
                alignment = value;
                Recalculate();
            }
        }
        public float WordWrap
        {
            get
            {
                return internalWrap;
            }
            set
            {
                internalWrap = value;
                Recalculate();
            }
        }
        public float Width
        {
            get
            {
#if FIVEM
                API.BeginTextCommandWidth("CELL_EMAIL_BCON");
                Add();
                return API.EndTextCommandGetWidth(true) * 1f.ToXScaled();
#elif ALTV
                Alt.Natives.BeginTextCommandGetScreenWidthOfDisplayText("CELL_EMAIL_BCON");
                Add();
                return Alt.Natives.EndTextCommandGetScreenWidthOfDisplayText(true) * 1f.ToXScaled();
#elif RAGEMP
                Invoker.Invoke(Natives.BeginTextCommandWidth, "CELL_EMAIL_BCON");
                Add();
                return Invoker.Invoke<float>(Natives.EndTextCommandGetWidth) * 1f.ToXScaled();
#elif RPH
                NativeFunction.CallByHash<int>(0x54CE8AC98E120CAB, "CELL_EMAIL_BCON");
                Add();
                return NativeFunction.CallByHash<float>(0x85F061DA64ED2F67, true) * 1f.ToXScaled();
#elif SHVDN3 || SHVDNC
                Function.Call(Hash.BEGIN_TEXT_COMMAND_GET_SCREEN_WIDTH_OF_DISPLAY_TEXT, "CELL_EMAIL_BCON");
                Add();
                return Function.Call<float>(Hash.END_TEXT_COMMAND_GET_SCREEN_WIDTH_OF_DISPLAY_TEXT, true) * 1f.ToXScaled();
#endif
            }
        }
        public int LineCount
        {
            get
            {
#if FIVEM
                API.BeginTextCommandLineCount("CELL_EMAIL_BCON");
#elif ALTV
                Alt.Natives.BeginTextCommandGetNumberOfLinesForString("CELL_EMAIL_BCON");
#elif RAGEMP
                Invoker.Invoke(Natives.BeginTextCommandLineCount, "CELL_EMAIL_BCON");
#elif RPH
                NativeFunction.CallByHash<int>(0x521FB041D93DD0E4, "CELL_EMAIL_BCON");
#elif SHVDN3 || SHVDNC
                Function.Call(Hash.BEGIN_TEXT_COMMAND_GET_NUMBER_OF_LINES_FOR_STRING, "CELL_EMAIL_BCON");
#endif
                Add();
#if FIVEM
                return API.EndTextCommandGetLineCount(relativePosition.X, relativePosition.Y);
#elif ALTV
                return Alt.Natives.EndTextCommandGetNumberOfLinesForString(relativePosition.X, relativePosition.Y);
#elif RAGEMP
                return Invoker.Invoke<int>(Natives.EndTextCommandGetLineCount, relativePosition.X, relativePosition.Y);
#elif RPH
                return NativeFunction.CallByHash<int>(0x9040DFB09BE75706, relativePosition.X, relativePosition.Y);
#elif SHVDN3 || SHVDNC
                return Function.Call<int>(Hash.END_TEXT_COMMAND_GET_NUMBER_OF_LINES_FOR_STRING, relativePosition.X, relativePosition.Y);
#endif
            }
        }
        public float LineHeight
        {
            get
            {
                // Height will always be 1080
#if FIVEM
                return 1080 * API.GetTextScaleHeight(Scale, (int)Font);
#elif ALTV
                return 1080 * Alt.Natives.GetRenderedCharacterHeight(Scale, (int)Font);
#elif RAGEMP
                return 1080 * Invoker.Invoke<float>(Natives.GetTextScaleHeight, Scale, (int)Font);
#elif RPH
                return 1080 * NativeFunction.CallByHash<float>(0xDB88A37483346780, Scale, (int)Font);
#elif SHVDN3 || SHVDNC
                return 1080 * Function.Call<float>(Hash.GET_RENDERED_CHARACTER_HEIGHT, Scale, (int)Font);
#endif
            }
        }

        #endregion

        #region Constructors

        public ScaledText(PointF pos, string text) : this(pos, text, 1f, Font.ChaletLondon)
        {
        }
        public ScaledText(PointF pos, string text, float scale) : this(pos, text, scale, Font.ChaletLondon)
        {
        }
        public ScaledText(PointF pos, string text, float scale, Font font)
        {
            Position = pos;
            Text = text ?? throw new ArgumentNullException(nameof(text));
            Scale = scale;
            Font = font;
        }

        #endregion

        #region Tools

        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 ALTV
            foreach (var chunk in chunks)
            {
                Alt.Natives.AddTextComponentSubstringPlayerName(chunk);
            }
            Alt.Natives.SetTextFont((int)Font);
            Alt.Natives.SetTextScale(1f, Scale);
            Alt.Natives.SetTextColour(Color.R, Color.G, Color.B, Color.A);
            Alt.Natives.SetTextJustification((int)Alignment);
            if (Shadow)
            {
                Alt.Natives.SetTextDropShadow();
            }
            if (Outline)
            {
                Alt.Natives.SetTextOutline();
            }
            if (WordWrap > 0)
            {
                switch (Alignment)
                {
                    case Alignment.Center:
                        Alt.Natives.SetTextWrap(relativePosition.X - (realWrap * 0.5f), relativePosition.X + (realWrap * 0.5f));
                        break;
                    case Alignment.Left:
                        Alt.Natives.SetTextWrap(relativePosition.X, relativePosition.X + realWrap);
                        break;
                    case Alignment.Right:
                        Alt.Natives.SetTextWrap(relativePosition.X - realWrap, relativePosition.X);
                        break;
                }
            }
            else if (Alignment == Alignment.Right)
            {
                Alt.Natives.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<int>(0x6C188BE134E074AA, chunk);
            }
            NativeFunction.CallByHash<int>(0x66E0276CC5F6B9DA, (int)Font);
            NativeFunction.CallByHash<int>(0x07C837F9A01C34C9, 1f, Scale);
            NativeFunction.CallByHash<int>(0xBE6B23FFA53FB442, Color.R, Color.G, Color.B, Color.A);
            NativeFunction.CallByHash<int>(0x4E096588B13FFECA, (int)Alignment);
            if (Shadow)
            {
                NativeFunction.CallByHash<int>(0x1CA3E9EAC9D93E5E);
            }
            if (Outline)
            {
                NativeFunction.CallByHash<int>(0x2513DFB0FB8400FE);
            }
            if (WordWrap > 0)
            {
                switch (Alignment)
                {
                    case Alignment.Center:
                        NativeFunction.CallByHash<int>(0x63145D9C883A1A70, relativePosition.X - (realWrap * 0.5f), relativePosition.X + (realWrap * 0.5f));
                        break;
                    case Alignment.Left:
                        NativeFunction.CallByHash<int>(0x63145D9C883A1A70, relativePosition.X, relativePosition.X + realWrap);
                        break;
                    case Alignment.Right:
                        NativeFunction.CallByHash<int>(0x63145D9C883A1A70, relativePosition.X - realWrap, relativePosition.X);
                        break;
                }
            }
            else if (Alignment == Alignment.Right)
            {
                NativeFunction.CallByHash<int>(0x63145D9C883A1A70, 0f, relativePosition.X);
            }
#elif SHVDN3 || SHVDNC
            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
        }
        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<string> newChunks = new List<string>();
            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;
        }
        public void Recalculate()
        {
            // Do the normal Size and Position recalculation
            relativePosition = scaledPosition.ToRelative();
            // And recalculate the word wrap if necessary
            if (internalWrap <= 0)
            {
                realWrap = 0;
            }
            else
            {
                realWrap = internalWrap.ToXRelative();
            }
        }

        #endregion

        #region Functions

        public void Draw()
        {
#if FIVEM
            API.SetTextEntry("CELL_EMAIL_BCON");
#elif ALTV
            Alt.Natives.BeginTextCommandDisplayText("CELL_EMAIL_BCON");
#elif RAGEMP
            Invoker.Invoke(Natives.BeginTextCommandDisplayText, "CELL_EMAIL_BCON");
#elif RPH
            NativeFunction.CallByHash<int>(0x25FBB336DF1804CB, "CELL_EMAIL_BCON");
#elif SHVDN3 || SHVDNC
            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 ALTV
            Alt.Natives.EndTextCommandDisplayText(relativePosition.X, relativePosition.Y, 0);
#elif RAGEMP
            Invoker.Invoke(Natives.EndTextCommandDisplayText, relativePosition.X, relativePosition.Y);
#elif RPH
            NativeFunction.CallByHash<int>(0xCD015E5BB0D96A57, relativePosition.X, relativePosition.Y);
#elif SHVDN3 || SHVDNC
            Function.Call((Hash)0xCD015E5BB0D96A57, relativePosition.X, relativePosition.Y); // _DRAW_TEXT on v2, END_TEXT_COMMAND_DISPLAY_TEXT on v3
#endif
        }

        #endregion
    }
}