File NativeColorPanel.cs
File List > LemonUI > LemonUI > Menus > NativeColorPanel.cs
Go to the documentation of this file
#if FIVEM
using CitizenFX.Core;
using CitizenFX.Core.UI;
#elif ALTV
using AltV.Net.Client;
#elif RAGEMP
using RAGE.Game;
#elif RPH
using Control = Rage.GameControl;
#elif SHVDN3 || SHVDNC
using GTA;
using GTA.UI;
#endif
using LemonUI.Elements;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
namespace LemonUI.Menus
{
public class NativeColorPanel : NativePanel, IEnumerable<NativeColorData>
{
#region Constants
private const float leftDifference = 16;
private const float rightDifference = 12;
#endregion
#region Fields
private PointF lastPosition = PointF.Empty;
private float lastWidth = 0;
private ScaledText title = new ScaledText(PointF.Empty, string.Empty, 0.325f)
{
Alignment = Alignment.Center
};
private ScaledRectangle selectionRectangle = new ScaledRectangle(PointF.Empty, SizeF.Empty);
private ScaledText opacityText = new ScaledText(PointF.Empty, "Opacity", 0.325f)
{
Alignment = Alignment.Center
};
private ScaledText percentMin = new ScaledText(PointF.Empty, "0%", 0.325f);
private ScaledText percentMax = new ScaledText(PointF.Empty, "100%", 0.325f);
private ScaledRectangle opacityForeground = new ScaledRectangle(PointF.Empty, SizeF.Empty)
{
Color = Color.FromArgb(255, 240, 240, 240)
};
private ScaledRectangle opacityBackground = new ScaledRectangle(PointF.Empty, SizeF.Empty)
{
Color = Color.FromArgb(150, 88, 88, 88)
};
private bool showOpacity = false;
private int opacity = 0;
private int index = 0;
private int firstItem = 0;
private int maxItems = 9;
private string simpleTitle = "Color";
private ColorTitleStyle titleStyle = ColorTitleStyle.Simple;
private bool showCount = true;
private List<NativeColorData> visibleItems = new List<NativeColorData>();
public static readonly Sound DefaultSound = new Sound("HUD_FRONTEND_DEFAULT_SOUNDSET", "NAV_LEFT_RIGHT");
#endregion
#region Properties
public override bool Clickable => true;
public bool ShowOpacity
{
get => showOpacity;
set
{
showOpacity = value;
Recalculate();
}
}
public int Opacity
{
get
{
if (!ShowOpacity)
{
return -1;
}
return opacity;
}
set
{
if (!ShowOpacity)
{
return;
}
if (value > 100 || value < 0)
{
throw new IndexOutOfRangeException("The value needs to be over 0 and under 100.");
}
opacity = value;
UpdateOpacityBar();
}
}
public Color SelectedColor
{
get
{
// If there is no selected color information, return
NativeColorData data = SelectedItem;
if (data == null)
{
return default;
}
// Otherwise, return the color
return Color.FromArgb(ShowOpacity ? (int)(255 * (Opacity * 0.01f)) : 255, data.Color.R, data.Color.G, data.Color.B);
}
}
public NativeColorData SelectedItem
{
get
{
if (Colors.Count == 0 || index >= Colors.Count)
{
return null;
}
return Colors[SelectedIndex];
}
}
public int SelectedIndex
{
get
{
if (Colors.Count == 0 || index >= Colors.Count)
{
return -1;
}
return index;
}
set
{
// If the list of items is empty, don't allow the user to set the index
if (Colors == null || Colors.Count == 0)
{
throw new InvalidOperationException("There are no items in this menu.");
}
// If the value is over or equal than the number of items, raise an exception
else if (value >= Colors.Count)
{
throw new InvalidOperationException($"The index is over {Colors.Count - 1}");
}
// Calculate the bounds of the menu
int lower = firstItem;
int upper = firstItem + maxItems;
// Time to set the first item based on the total number of items
// If the item is between the allowed values, do nothing because we are on the correct first item
if (value >= lower && value < upper - 1)
{
}
// If the upper bound + 1 equals the new index, increase it by one
else if (upper == value)
{
firstItem += 1;
}
// If the first item minus one equals the value, decrease it by one
else if (lower - 1 == value)
{
firstItem -= 1;
}
// Otherwise, set it somewhere
else
{
// If the value is under the max items, set it to zero
if (value < maxItems)
{
firstItem = 0;
}
// Otherwise, set it at the bottom
else
{
firstItem = value - maxItems + 1;
}
}
// Save the index and update the items
index = value;
UpdateItems();
// Finally, play the switch change sound
Sound?.PlayFrontend();
}
}
public string Title
{
get => simpleTitle;
set
{
simpleTitle = value ?? throw new ArgumentNullException(nameof(value));
UpdateTitle();
}
}
public ColorTitleStyle TitleStyle
{
get => titleStyle;
set
{
titleStyle = value;
UpdateTitle();
}
}
public bool ShowCount
{
get => showCount;
set
{
showCount = value;
UpdateTitle();
}
}
public int MaxItems
{
get => maxItems;
set
{
if (value == maxItems)
{
return;
}
maxItems = value;
UpdateItems();
UpdateTitle();
}
}
public List<NativeColorData> Colors { get; } = new List<NativeColorData>();
public Sound Sound { get; set; } = DefaultSound;
#endregion
#region Constructors
public NativeColorPanel() : this(string.Empty)
{
}
public NativeColorPanel(string title, params NativeColorData[] colors)
{
Title = title ?? throw new ArgumentNullException(nameof(title));
Colors.AddRange(colors);
}
#endregion
#region Tools
private void UpdateTitle()
{
string newTitle = string.Empty;
// Add the title based on the correct style
switch (titleStyle)
{
case ColorTitleStyle.Simple:
newTitle = Title;
break;
case ColorTitleStyle.ColorName:
newTitle = SelectedItem == null ? string.Empty : SelectedItem.Name;
break;
}
// If we need to add the count of colors
if (ShowCount)
{
// Add a space at the end if required
if (!newTitle.EndsWith(" "))
{
newTitle += " ";
}
// And add the item count
newTitle += $"({SelectedIndex + 1} of {Colors.Count})";
}
// And finally set the new title
title.Text = newTitle;
}
private void UpdateItems()
{
// See UpdateItemList() on LemonUI.Menus.NativeMenu to understand this section
List<NativeColorData> list = new List<NativeColorData>();
for (int i = 0; i < MaxItems; i++)
{
int start = firstItem + i;
if (start >= Colors.Count)
{
break;
}
list.Add(Colors[start]);
}
visibleItems = list;
// Get the width based on the maximum number of items
float width = (lastWidth - leftDifference - rightDifference) / maxItems;
// And store the number of items already completed
int count = 0;
// Select the correct extra distance based on the prescence of the Opacity toggle
float extra = ShowOpacity ? 78 : 0;
// Then, start iterating over the colors visible on the screen
foreach (NativeColorData color in visibleItems)
{
// Set the position based on the number of items completed
color.rectangle.Position = new PointF(lastPosition.X + leftDifference + (width * count), lastPosition.Y + extra + 54);
// And set the size of it based on the number of items
color.rectangle.Size = new SizeF(width, 45);
// Finally, increase the count by one
count++;
}
// If there is a selected color item
if (SelectedItem != null)
{
// Set the position and size of the selection rectangle based on the currently selected color
ScaledRectangle colorRect = SelectedItem.rectangle;
const float height = 8;
selectionRectangle.Position = new PointF(colorRect.Position.X, colorRect.Position.Y - height);
selectionRectangle.Size = new SizeF(colorRect.Size.Width, height);
}
// Finally, update the text of the title
UpdateTitle();
}
private void UpdateOpacityBar()
{
// If the opacity bar is disabled, return
if (!ShowOpacity)
{
return;
}
// Otherwise, set the size based in the last known position
float x = lastPosition.X + 13;
float y = lastPosition.Y + 48;
float width = lastWidth - leftDifference - rightDifference;
const float height = 9;
opacityBackground.Position = new PointF(x, y);
opacityBackground.Size = new SizeF(width, height);
opacityForeground.Position = new PointF(x, y);
opacityForeground.Size = new SizeF(width * (Opacity * 0.01f), height);
}
private void Recalculate() => Recalculate(lastPosition, lastWidth);
#endregion
#region Functions
public IEnumerator<NativeColorData> GetEnumerator() => Colors.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public void Previous()
{
// If there are no items, return
if (Colors.Count == 0)
{
return;
}
// If we are on the first item, go back to the last one
if (SelectedIndex == 0)
{
SelectedIndex = Colors.Count - 1;
}
// Otherwise, reduce it by one
else
{
SelectedIndex -= 1;
}
}
public void Next()
{
// If there are no items, return
if (Colors.Count == 0)
{
return;
}
// If we are on the last item, go back to the first one
if (Colors.Count - 1 == SelectedIndex)
{
SelectedIndex = 0;
}
// Otherwise, increase it by one
else
{
SelectedIndex += 1;
}
}
public void Add(NativeColorData color)
{
if (Colors.Contains(color))
{
throw new ArgumentException("Color is already part of the Panel.", nameof(color));
}
Colors.Add(color);
Recalculate();
}
public void Remove(NativeColorData color)
{
// Remove it if there
// If not, ignore it
Colors.Remove(color);
// If the index is higher or equal than the max number of items
// Set the max - 1
if (SelectedIndex >= Colors.Count)
{
SelectedIndex = Colors.Count - 1;
}
else
{
UpdateItems();
}
}
public void Remove(Func<NativeColorData, bool> func)
{
for (int i = 0; i < Colors.Count; i++)
{
NativeColorData color = Colors[i];
if (func(color))
{
Colors.Remove(color);
}
}
Recalculate();
}
public void Clear()
{
Colors.Clear();
Recalculate();
}
public void Contains(NativeColorData color) => Colors.Contains(color);
public override void Recalculate(PointF position, float width)
{
// Save the last position and width
lastPosition = position;
lastWidth = width;
// Select the correct extra distance based on the prescence of the Opacity toggle
float extra = ShowOpacity ? 78 : 0;
// Set the position and size of the Background
Background.Position = position;
Background.Size = new SizeF(width, ShowOpacity ? 188 : 111);
// And then set the position of the text
title.Position = new PointF(position.X + (width * 0.5f), position.Y + extra + 10f);
// Then, set the position of the opacity bar and texts
UpdateOpacityBar();
opacityText.Position = new PointF(position.X + (width * 0.5f), position.Y + 10f);
percentMin.Position = new PointF(position.X + 9, position.Y + 11);
percentMax.Position = new PointF(position.X + width - 60, position.Y + 11);
// Finally, update the list of items
UpdateItems();
}
public override void Process()
{
// If the user pressed one of the keys, move to the left or right
#if ALTV
if (Controls.IsJustPressed(Control.FrontendLT))
#else
if (Controls.IsJustPressed(Control.FrontendLt))
#endif
{
Previous();
}
#if ALTV
else if (Controls.IsJustPressed(Control.FrontendRT))
#else
else if (Controls.IsJustPressed(Control.FrontendRt))
#endif
{
Next();
}
// If the user pressed one of the bumpers with the Opacity bar enabled, increase or decrease it
#if ALTV
else if (ShowOpacity && Controls.IsJustPressed(Control.FrontendLB))
#else
else if (ShowOpacity && Controls.IsJustPressed(Control.FrontendLb))
#endif
{
if (Opacity > 0)
{
Opacity--;
Sound?.PlayFrontend();
}
}
#if ALTV
else if (ShowOpacity && Controls.IsJustPressed(Control.FrontendRB))
#else
else if (ShowOpacity && Controls.IsJustPressed(Control.FrontendRb))
#endif
{
if (Opacity < 100)
{
Opacity++;
Sound?.PlayFrontend();
}
}
// Draw the items
base.Process();
title.Draw();
foreach (NativeColorData color in visibleItems)
{
color.rectangle.Draw();
}
if (Colors.Count != 0)
{
selectionRectangle.Draw();
}
if (ShowOpacity)
{
opacityText.Draw();
percentMin.Draw();
percentMax.Draw();
opacityBackground.Draw();
opacityForeground.Draw();
}
}
#endregion
}
}