/* * Copyright 2022 Peter Han * Permission is hereby granted, free of charge, to any person obtaining a copy of this software * and associated documentation files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or * substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ using PeterHan.PLib.Core; using PeterHan.PLib.UI; using TMPro; using UnityEngine; using UnityEngine.UI; namespace PeterHan.PLib.Options { /// /// The abstract base of options entries that display a color picker with sliders. /// internal abstract class ColorBaseOptionsEntry : OptionsEntry { /// /// The margin between the color sliders and the rest of the dialog. /// protected static readonly RectOffset ENTRY_MARGIN = new RectOffset(10, 10, 2, 5); /// /// The margin around each slider. /// protected static readonly RectOffset SLIDER_MARGIN = new RectOffset(10, 0, 2, 2); /// /// The size of the sample swatch. /// protected const float SWATCH_SIZE = 32.0f; /// /// The hue displayed gradient. /// protected ColorGradient hueGradient; /// /// The hue slider. /// protected KSlider hueSlider; /// /// The realized text field for BLUE. /// protected TMP_InputField blue; /// /// The realized text field for GREEN. /// protected TMP_InputField green; /// /// The realized text field for RED. /// protected TMP_InputField red; /// /// The saturation displayed gradient. /// protected ColorGradient satGradient; /// /// The saturation slider. /// protected KSlider satSlider; /// /// The color sample swatch. /// protected Image swatch; /// /// The value displayed gradient. /// protected ColorGradient valGradient; /// /// The value slider. /// protected KSlider valSlider; /// /// The value as a Color. /// protected Color value; protected ColorBaseOptionsEntry(string field, IOptionSpec spec) : base(field, spec) { value = Color.white; blue = null; green = null; red = null; hueGradient = null; hueSlider = null; satGradient = null; satSlider = null; valGradient = null; valSlider = null; swatch = null; } public override void CreateUIEntry(PGridPanel parent, ref int row) { base.CreateUIEntry(parent, ref row); // Add 3 rows for the H, S, and V parent.AddRow(new GridRowSpec()); var h = new PSliderSingle("Hue") { ToolTip = PLibStrings.TOOLTIP_HUE, MinValue = 0.0f, MaxValue = 1.0f, CustomTrack = true, FlexSize = Vector2.right, OnValueChanged = OnHueChanged }.AddOnRealize(OnHueRealized); var s = new PSliderSingle("Saturation") { ToolTip = PLibStrings.TOOLTIP_SATURATION, MinValue = 0.0f, MaxValue = 1.0f, CustomTrack = true, FlexSize = Vector2.right, OnValueChanged = OnSatChanged }.AddOnRealize(OnSatRealized); var v = new PSliderSingle("Value") { ToolTip = PLibStrings.TOOLTIP_VALUE, MinValue = 0.0f, MaxValue = 1.0f, CustomTrack = true, FlexSize = Vector2.right, OnValueChanged = OnValChanged }.AddOnRealize(OnValRealized); var sw = new PLabel("Swatch") { ToolTip = LookInStrings(Tooltip), DynamicSize = false, Sprite = PUITuning.Images.BoxBorder, SpriteMode = Image.Type.Sliced, SpriteSize = new Vector2(SWATCH_SIZE, SWATCH_SIZE) }.AddOnRealize(OnSwatchRealized); var panel = new PRelativePanel("ColorPicker") { FlexSize = Vector2.right, DynamicSize = false }.AddChild(h).AddChild(s).AddChild(v).AddChild(sw).SetRightEdge(h, fraction: 1.0f). SetRightEdge(s, fraction: 1.0f).SetRightEdge(v, fraction: 1.0f). SetLeftEdge(sw, fraction: 0.0f).SetMargin(h, SLIDER_MARGIN). SetMargin(s, SLIDER_MARGIN).SetMargin(v, SLIDER_MARGIN).AnchorYAxis(sw). SetLeftEdge(h, toRight: sw).SetLeftEdge(s, toRight: sw). SetLeftEdge(v, toRight: sw).SetTopEdge(h, fraction: 1.0f). SetBottomEdge(v, fraction: 0.0f).SetTopEdge(s, below: h). SetTopEdge(v, below: s); parent.AddChild(panel, new GridComponentSpec(++row, 0) { ColumnSpan = 2, Margin = ENTRY_MARGIN }); } public override GameObject GetUIComponent() { Color32 rgb = value; var go = new PPanel("RGB") { DynamicSize = false, Alignment = TextAnchor.MiddleRight, Spacing = 5, Direction = PanelDirection.Horizontal }.AddChild(new PLabel("Red") { TextStyle = PUITuning.Fonts.TextLightStyle, Text = PLibStrings.LABEL_R }).AddChild(new PTextField("RedValue") { OnTextChanged = OnRGBChanged, ToolTip = PLibStrings.TOOLTIP_RED, Text = rgb.r.ToString(), MinWidth = 32, MaxLength = 3, Type = PTextField.FieldType.Integer }.AddOnRealize(OnRedRealized)).AddChild(new PLabel("Green") { TextStyle = PUITuning.Fonts.TextLightStyle, Text = PLibStrings.LABEL_G }).AddChild(new PTextField("GreenValue") { OnTextChanged = OnRGBChanged, ToolTip = PLibStrings.TOOLTIP_GREEN, Text = rgb.g.ToString(), MinWidth = 32, MaxLength = 3, Type = PTextField.FieldType.Integer }.AddOnRealize(OnGreenRealized)).AddChild(new PLabel("Blue") { TextStyle = PUITuning.Fonts.TextLightStyle, Text = PLibStrings.LABEL_B }).AddChild(new PTextField("BlueValue") { OnTextChanged = OnRGBChanged, ToolTip = PLibStrings.TOOLTIP_BLUE, Text = rgb.b.ToString(), MinWidth = 32, MaxLength = 3, Type = PTextField.FieldType.Integer }.AddOnRealize(OnBlueRealized)).Build(); UpdateAll(); return go; } private void OnBlueRealized(GameObject realized) { blue = realized.GetComponentInChildren(); } private void OnGreenRealized(GameObject realized) { green = realized.GetComponentInChildren(); } private void OnHueChanged(GameObject _, float newHue) { if (hueGradient != null && hueSlider != null) { float oldAlpha = value.a; hueGradient.Position = hueSlider.value; value = hueGradient.SelectedColor; value.a = oldAlpha; UpdateRGB(); UpdateSat(false); UpdateVal(false); } } private void OnHueRealized(GameObject realized) { hueGradient = realized.AddOrGet(); realized.TryGetComponent(out hueSlider); } private void OnRedRealized(GameObject realized) { red = realized.GetComponentInChildren(); } /// /// Called when the red, green, or blue field's text is changed. /// /// The new color value. protected void OnRGBChanged(GameObject _, string text) { Color32 rgb = value; if (byte.TryParse(red.text, out byte r)) rgb.r = r; if (byte.TryParse(green.text, out byte g)) rgb.g = g; if (byte.TryParse(blue.text, out byte b)) rgb.b = b; value = rgb; UpdateAll(); } private void OnSatChanged(GameObject _, float newSat) { if (satGradient != null && satSlider != null) { float oldAlpha = value.a; satGradient.Position = satSlider.value; value = satGradient.SelectedColor; value.a = oldAlpha; UpdateRGB(); UpdateHue(false); UpdateVal(false); } } private void OnSatRealized(GameObject realized) { satGradient = realized.AddOrGet(); realized.TryGetComponent(out satSlider); } private void OnSwatchRealized(GameObject realized) { swatch = realized.GetComponentInChildren(); } private void OnValChanged(GameObject _, float newValue) { if (valGradient != null && valSlider != null) { float oldAlpha = value.a; valGradient.Position = valSlider.value; value = valGradient.SelectedColor; value.a = oldAlpha; UpdateRGB(); UpdateHue(false); UpdateSat(false); } } private void OnValRealized(GameObject realized) { valGradient = realized.AddOrGet(); realized.TryGetComponent(out valSlider); } /// /// If the color is changed externally, updates all sliders. /// protected void UpdateAll() { UpdateRGB(); UpdateHue(true); UpdateSat(true); UpdateVal(true); } /// /// Updates the position of the hue slider with the currently selected color. /// /// true to move the slider handle if necessary, or false to /// leave it where it is. protected void UpdateHue(bool moveSlider) { if (hueGradient != null && hueSlider != null) { Color.RGBToHSV(value, out _, out float s, out float v); hueGradient.SetRange(0.0f, 1.0f, s, s, v, v); hueGradient.SelectedColor = value; if (moveSlider) hueSlider.value = hueGradient.Position; } } /// /// Updates the displayed value. /// protected void UpdateRGB() { Color32 rgb = value; if (red != null) red.text = rgb.r.ToString(); if (green != null) green.text = rgb.g.ToString(); if (blue != null) blue.text = rgb.b.ToString(); if (swatch != null) swatch.color = value; } /// /// Updates the position of the saturation slider with the currently selected color. /// /// true to move the slider handle if necessary, or false to /// leave it where it is. protected void UpdateSat(bool moveSlider) { if (satGradient != null && satSlider != null) { Color.RGBToHSV(value, out float h, out _, out float v); satGradient.SetRange(h, h, 0.0f, 1.0f, v, v); satGradient.SelectedColor = value; if (moveSlider) satSlider.value = satGradient.Position; } } /// /// Updates the position of the value slider with the currently selected color. /// /// true to move the slider handle if necessary, or false to /// leave it where it is. protected void UpdateVal(bool moveSlider) { if (valGradient != null && valSlider != null) { Color.RGBToHSV(value, out float h, out float s, out _); valGradient.SetRange(h, h, s, s, 0.0f, 1.0f); valGradient.SelectedColor = value; if (moveSlider) valSlider.value = valGradient.Position; } } } }