507 lines
17 KiB
C#
507 lines
17 KiB
C#
#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
|
|
{
|
|
/// <summary>
|
|
/// A text string.
|
|
/// </summary>
|
|
public class ScaledText : IText
|
|
{
|
|
#region Consistent Values
|
|
|
|
/// <summary>
|
|
/// The size of every chunk of text.
|
|
/// </summary>
|
|
private const int chunkSize = 90;
|
|
|
|
#endregion
|
|
|
|
#region Private Fields
|
|
|
|
/// <summary>
|
|
/// The absolute 1080p based screen position.
|
|
/// </summary>
|
|
private PointF absolutePosition = PointF.Empty;
|
|
/// <summary>
|
|
/// The relative 0-1 relative position.
|
|
/// </summary>
|
|
private PointF relativePosition = PointF.Empty;
|
|
/// <summary>
|
|
/// The raw string of text.
|
|
/// </summary>
|
|
private string text = string.Empty;
|
|
/// <summary>
|
|
/// The raw string split into equally sized strings.
|
|
/// </summary>
|
|
private List<string> chunks = new List<string>();
|
|
/// <summary>
|
|
/// The alignment of the item.
|
|
/// </summary>
|
|
private Alignment alignment = Alignment.Left;
|
|
/// <summary>
|
|
/// The word wrap value passed by the user.
|
|
/// </summary>
|
|
private float internalWrap = 0f;
|
|
/// <summary>
|
|
/// The real word wrap value based on the position of the text.
|
|
/// </summary>
|
|
private float realWrap = 0f;
|
|
|
|
#endregion
|
|
|
|
#region Public Properties
|
|
|
|
/// <summary>
|
|
/// The position of the text.
|
|
/// </summary>
|
|
public PointF Position
|
|
{
|
|
get => absolutePosition;
|
|
set
|
|
{
|
|
absolutePosition = value;
|
|
relativePosition = value.ToRelative();
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// The text to draw.
|
|
/// </summary>
|
|
public string Text
|
|
{
|
|
get => text;
|
|
set
|
|
{
|
|
text = value;
|
|
Slice();
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// The color of the text.
|
|
/// </summary>
|
|
public Color Color { get; set; } = Color.FromArgb(255, 255, 255, 255);
|
|
/// <summary>
|
|
/// The game font to use.
|
|
/// </summary>
|
|
public Font Font { get; set; } = Font.ChaletLondon;
|
|
/// <summary>
|
|
/// The scale of the text.
|
|
/// </summary>
|
|
public float Scale { get; set; } = 1f;
|
|
/// <summary>
|
|
/// If the text should have a drop down shadow.
|
|
/// </summary>
|
|
public bool Shadow { get; set; } = false;
|
|
/// <summary>
|
|
/// If the test should have an outline.
|
|
/// </summary>
|
|
public bool Outline { get; set; } = false;
|
|
/// <summary>
|
|
/// The alignment of the text.
|
|
/// </summary>
|
|
public Alignment Alignment
|
|
{
|
|
get => alignment;
|
|
set
|
|
{
|
|
alignment = value;
|
|
Recalculate();
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// The distance from the start position where the text will be wrapped into new lines.
|
|
/// </summary>
|
|
public float WordWrap
|
|
{
|
|
get
|
|
{
|
|
return internalWrap;
|
|
}
|
|
set
|
|
{
|
|
internalWrap = value;
|
|
Recalculate();
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// The width that the text takes from the screen.
|
|
/// </summary>
|
|
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<float>(Natives.EndTextCommandGetWidth) * 1f.ToXAbsolute();
|
|
#elif RPH
|
|
NativeFunction.CallByHash<int>(0x54CE8AC98E120CAB, "CELL_EMAIL_BCON");
|
|
Add();
|
|
return NativeFunction.CallByHash<float>(0x85F061DA64ED2F67, true) * 1f.ToXAbsolute();
|
|
#elif SHVDN3
|
|
Function.Call(Hash._BEGIN_TEXT_COMMAND_GET_WIDTH, "CELL_EMAIL_BCON");
|
|
Add();
|
|
return Function.Call<float>(Hash._END_TEXT_COMMAND_GET_WIDTH, true) * 1f.ToXAbsolute();
|
|
#endif
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// The number of lines used by this text.
|
|
/// </summary>
|
|
public int LineCount
|
|
{
|
|
get
|
|
{
|
|
#if FIVEM
|
|
API.BeginTextCommandLineCount("CELL_EMAIL_BCON");
|
|
#elif RAGEMP
|
|
Invoker.Invoke(Natives.BeginTextCommandLineCount, "CELL_EMAIL_BCON");
|
|
#elif RPH
|
|
NativeFunction.CallByHash<int>(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<int>(Natives.EndTextCommandGetLineCount, relativePosition.X, relativePosition.Y);
|
|
#elif RPH
|
|
return NativeFunction.CallByHash<int>(0x9040DFB09BE75706, relativePosition.X, relativePosition.Y);
|
|
#elif SHVDN3
|
|
return Function.Call<int>(Hash._END_TEXT_COMMAND_LINE_COUNT, relativePosition.X, relativePosition.Y);
|
|
#endif
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// The relative height of each line in the text.
|
|
/// </summary>
|
|
public float LineHeight
|
|
{
|
|
get
|
|
{
|
|
// Height will always be 1080
|
|
#if FIVEM
|
|
return 1080 * API.GetTextScaleHeight(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
|
|
return 1080 * Function.Call<float>(Hash.GET_RENDERED_CHARACTER_HEIGHT, Scale, (int)Font);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Constructors
|
|
|
|
/// <summary>
|
|
/// Creates a text with the specified options.
|
|
/// </summary>
|
|
/// <param name="pos">The position where the text should be located.</param>
|
|
/// <param name="text">The text to show.</param>
|
|
public ScaledText(PointF pos, string text) : this(pos, text, 1f, Font.ChaletLondon)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a text with the specified options.
|
|
/// </summary>
|
|
/// <param name="pos">The position where the text should be located.</param>
|
|
/// <param name="text">The text to show.</param>
|
|
/// <param name="scale">The scale of the text.</param>
|
|
public ScaledText(PointF pos, string text, float scale) : this(pos, text, scale, Font.ChaletLondon)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a text with the specified options
|
|
/// </summary>
|
|
/// <param name="pos">The position where the text should be located.</param>
|
|
/// <param name="text">The text to show.</param>
|
|
/// <param name="scale">The scale of the text.</param>
|
|
/// <param name="font">The font to use.</param>
|
|
public ScaledText(PointF pos, string text, float scale, Font font)
|
|
{
|
|
Position = pos;
|
|
Text = text;
|
|
Scale = scale;
|
|
Font = font;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Tools
|
|
|
|
/// <summary>
|
|
/// Adds the text information for measurement.
|
|
/// </summary>
|
|
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<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
|
|
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
|
|
}
|
|
/// <summary>
|
|
/// Slices the string of text into appropiately saved chunks.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
/// <summary>
|
|
/// Recalculates the size, position and word wrap of this item.
|
|
/// </summary>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Draws the text on the screen.
|
|
/// </summary>
|
|
public void Draw()
|
|
{
|
|
#if FIVEM
|
|
API.SetTextEntry("CELL_EMAIL_BCON");
|
|
#elif RAGEMP
|
|
Invoker.Invoke(Natives.BeginTextCommandDisplayText, "CELL_EMAIL_BCON");
|
|
#elif RPH
|
|
NativeFunction.CallByHash<int>(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<int>(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
|
|
}
|
|
}
|