#if FIVEM using CitizenFX.Core; using CitizenFX.Core.UI; #elif RAGEMP using RAGE.Game; #elif RPH using Control = Rage.GameControl; #elif SHVDN3 using GTA; using GTA.UI; #endif using LemonUI.Elements; using System; using System.Collections.Generic; using System.Drawing; namespace LemonUI.Menus { /// /// A Panel that allows you to select a Color. /// public class NativeColorPanel : NativePanel { #region Constants /// /// The space difference for the colors and opacity bar on the left. /// private const float leftDifference = 16; /// /// The space difference for the colors and opacity bar on the left. /// private const float rightDifference = 12; #endregion #region Private Fields /// /// The position reported after the last Recalculation. /// private PointF lastPosition = PointF.Empty; /// /// The Width reported after the last Recalculation. /// private float lastWidth = 0; /// /// The title of the Color Panel. /// private ScaledText title = new ScaledText(PointF.Empty, string.Empty, 0.325f) { Alignment = Alignment.Center }; /// /// The rectangle used for marking the item selection on the screen. /// private ScaledRectangle selectionRectangle = new ScaledRectangle(PointF.Empty, SizeF.Empty); /// /// The "Opacity" text when the opacity bar is enabled /// private ScaledText opacityText = new ScaledText(PointF.Empty, "Opacity", 0.325f) { Alignment = Alignment.Center }; /// /// The zero percent text when the opacity bar is enabled. /// private ScaledText percentMin = new ScaledText(PointF.Empty, "0%", 0.325f); /// /// The 100 percent text when the opacity bar is enabled. /// private ScaledText percentMax = new ScaledText(PointF.Empty, "100%", 0.325f); /// /// The top section of the opacity bar. /// private ScaledRectangle opacityForeground = new ScaledRectangle(PointF.Empty, SizeF.Empty) { Color = Color.FromArgb(255, 240, 240, 240) }; /// /// The background of the opacity bar. /// private ScaledRectangle opacityBackground = new ScaledRectangle(PointF.Empty, SizeF.Empty) { Color = Color.FromArgb(150, 88, 88, 88) }; /// /// If the opacity bar is available to the user. /// private bool showOpacity = false; /// /// The current value of the opacity slider. /// private int opacity = 0; /// /// The current index of the Colors. /// private int index = 0; /// /// The position of the first item. /// private int firstItem = 0; /// /// The maximum number of items shown at once. /// private int maxItems = 9; /// /// The generic title for this color. /// private string simpleTitle = "Color"; /// /// The style of the title. /// private ColorTitleStyle titleStyle = ColorTitleStyle.Simple; /// /// If the number of colors should be shown. /// private bool showCount = true; /// /// The items that are currently visible on the screen. /// private List visibleItems = new List(); #endregion #region Public Fields /// /// The default sound used for the Color Navigation. /// public static readonly Sound DefaultSound = new Sound("HUD_FRONTEND_DEFAULT_SOUNDSET", "NAV_LEFT_RIGHT"); #endregion #region Public Properties /// public override bool Clickable => true; /// /// If the Opacity selector should be shown. /// public bool ShowOpacity { get => showOpacity; set { showOpacity = value; Recalculate(); } } /// /// The opacity value of the color. /// /// /// The value needs to be set between 100 and 0. /// It will return -1 if is set to . /// 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(); } } /// /// The currently selected color. /// /// /// If is set to . /// 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); } } /// /// Returns the currently selected . /// public NativeColorData SelectedItem { get { if (Colors.Count == 0 || index >= Colors.Count) { return null; } return Colors[SelectedIndex]; } } /// /// The index of the currently selected Color. /// 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(); } } /// /// The Title used for the Panel when is set to . /// public string Title { get => simpleTitle; set { simpleTitle = value; UpdateTitle(); } } /// /// The style of the Panel Title. /// public ColorTitleStyle TitleStyle { get => titleStyle; set { titleStyle = value; UpdateTitle(); } } /// /// If the count of items should be shown as part of the title. /// public bool ShowCount { get => showCount; set { showCount = value; UpdateTitle(); } } /// /// THe maximum number of items shown on the screen. /// public int MaxItems { get => maxItems; set { if (value == maxItems) { return; } maxItems = value; UpdateItems(); UpdateTitle(); } } /// /// The colors shown on this Panel. /// public List Colors { get; } = new List(); /// /// The sound played when the item is changed. /// public Sound Sound { get; set; } = DefaultSound; #endregion #region Constructors /// /// Creates a color panel with no Items or Title. /// public NativeColorPanel() : this(string.Empty) { } /// /// Creates a Panel with a specific Title and set of Colors. /// /// The title of the panel. /// The colors of the panel. public NativeColorPanel(string title, params NativeColorData[] colors) { // Set the title of the Panel Title = title; // Add the colors that we got Colors.AddRange(colors); } #endregion #region Private Functions /// /// Updates the Text of the Title. /// 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; } /// /// Updates the position of the Items. /// private void UpdateItems() { // See UpdateItemList() on LemonUI.Menus.NativeMenu to understand this section List list = new List(); 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(); } /// /// Updates the size of the opacity bar. /// 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); } /// /// Recalculates the Color panel with the last known Position and Width. /// private void Recalculate() => Recalculate(lastPosition, lastWidth); #endregion #region Public Functions /// /// Moves to the Previous Color. /// 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; } } /// /// Moves to the Next Color. /// 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; } } /// /// Adds a color to the Panel. /// /// The color to add. 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(); } /// /// Removes a color from the panel. /// /// The color to remove. 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(); } } /// /// Removes all of the /// /// public void Remove(Func func) { foreach (NativeColorData color in new List(Colors)) { if (func(color)) { Colors.Remove(color); } } Recalculate(); } /// /// Removes all of the colors from the Panel. /// public void Clear() { Colors.Clear(); Recalculate(); } /// /// Checks if the Color Data is present on this Panel. /// /// The Color Data to check. public void Contains(NativeColorData color) => Colors.Contains(color); /// /// Recalculates the position of the Color Panel. /// /// The position of the panel. /// The width of the menu. 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(); } /// /// Draws the Color Panel. /// public override void Process() { // If the user pressed one of the keys, move to the left or right if (Controls.IsJustPressed(Control.FrontendLt)) { Previous(); } else if (Controls.IsJustPressed(Control.FrontendRt)) { Next(); } // If the user pressed one of the bumpers with the Opacity bar enabled, increase or decrease it else if (ShowOpacity && Controls.IsJustPressed(Control.FrontendLb)) { if (Opacity > 0) { Opacity--; Sound?.PlayFrontend(); } } else if (ShowOpacity && Controls.IsJustPressed(Control.FrontendRb)) { 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 } }