/*
* 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;
}
}
}
}