Skip to content

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

    }
}