Working color picker UI

This commit is contained in:
2020-07-22 22:37:16 -07:00
parent e57bed6ed9
commit 10da3dd402
28 changed files with 515 additions and 591 deletions

View File

@ -102,6 +102,7 @@
<Compile Include="Util/ParseUtil.cs" />
<Compile Include="UI/BoxSlider.cs" />
<Compile Include="Util\ColorHSL.cs" />
<Compile Include="Util\ColorHSV.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)/Microsoft.CSharp.targets" />
<PropertyGroup>

View File

@ -1,7 +1,5 @@
using System;
using ConformalDecals.Text;
using ConformalDecals.UI;
using ConformalDecals.Util;
using TMPro;
using UnityEngine;
@ -11,29 +9,27 @@ namespace ConformalDecals {
[KSPField(isPersistant = true)] public string font = "Calibri SDF";
[KSPField(isPersistant = true)] public int style;
[KSPField(isPersistant = true)] public bool vertical;
[KSPField(isPersistant = true)] public Color color = Color.black;
[KSPField(isPersistant = true)] public Color fillColor = Color.black;
[KSPField(isPersistant = true)] public Color outlineColor = Color.white;
[KSPField(isPersistant = true)] public float outlineWidth;
private DecalText _text;
private TextEntryController _textEntryController;
public override void OnLoad(ConfigNode node) {
base.OnLoad(node);
}
private TextEntryController _textEntryController;
private ColorPickerController _fillColorPickerController;
private ColorPickerController _outlineColorPickerCOntroller;
public override void OnStart(StartState state) {
base.OnStart(state);
var decalFont = DecalConfig.GetFont(font);
var decalFont = DecalConfig.GetFont(font);
_text = new DecalText {
text = text,
font = decalFont,
style = (FontStyles) style,
vertical = vertical,
color = color,
color = fillColor,
outlineColor = outlineColor,
outlineWidth = outlineWidth
};
@ -43,11 +39,33 @@ namespace ConformalDecals {
_text = newText;
}
public void OnFillColorUpdate(Color rgb, Util.ColorHSV hsv) {
Debug.Log($"new fill color: {rgb}, {hsv}");
}
public void OnOutlineColorUpdate(Color rgb, Util.ColorHSV hsv) {
Debug.Log($"new outline color: {rgb}, {hsv}");
}
[KSPEvent(guiActive = false, guiActiveEditor = true, guiName = "#LOC_ConformalDecals_gui-select-flag")]
public void SetText() {
if (_textEntryController == null) {
_textEntryController = TextEntryController.Create(_text, OnTextUpdate);
}
}
[KSPEvent(guiActive = false, guiActiveEditor = true, guiName = "Set Fill Color")]
public void SetFillColor() {
if (_fillColorPickerController == null) {
_fillColorPickerController = ColorPickerController.Create(fillColor, OnFillColorUpdate);
}
}
[KSPEvent(guiActive = false, guiActiveEditor = true, guiName = "Set Outline Color")]
public void SetOutlineColor() {
if (_outlineColorPickerCOntroller == null) {
_outlineColorPickerCOntroller = ColorPickerController.Create(outlineColor, OnOutlineColorUpdate);
}
}
}
}

View File

@ -30,6 +30,7 @@ namespace ConformalDecals.UI {
get => _value;
set {
_value = value;
_onValueChanged.Invoke(value);
UpdateVisuals();
}
}

View File

@ -1,20 +1,16 @@
using ConformalDecals.Util;
using UnityEngine;
using UnityEngine.UI;
namespace ConformalDecals.UI {
public class ColorBoxSlider : MonoBehaviour {
[SerializeField] private ColorPickerController.ChannelUpdateEvent _onXChannelChanged = new ColorPickerController.ChannelUpdateEvent();
[SerializeField] private ColorPickerController.ChannelUpdateEvent _onYChannelChanged = new ColorPickerController.ChannelUpdateEvent();
[SerializeField] public ColorPickerController.SVUpdateEvent onValueChanged = new ColorPickerController.SVUpdateEvent();
[SerializeField] private Vector2 _value;
[SerializeField] private Vector2Int _channel;
[SerializeField] private bool _hsl;
[SerializeField] private BoxSlider _slider;
[SerializeField] private Image _image;
private static readonly int Hue = Shader.PropertyToID("_Hue");
private bool _ignoreUpdates;
public Vector2 Value {
get => _value;
@ -22,31 +18,46 @@ namespace ConformalDecals.UI {
_value.x = Mathf.Clamp01(value.x);
_value.y = Mathf.Clamp01(value.y);
UpdateSlider();
OnChannelUpdate();
UpdateChannels();
}
}
public void OnColorUpdate(Color rgb, Util.ColorHSV hsv) {
if (_ignoreUpdates) return;
_image.material.SetColor(PropertyIDs._Color, (Vector4) hsv);
_value.x = hsv.s;
_value.y = hsv.v;
UpdateSlider();
}
public void OnSliderUpdate(Vector2 value) {
_value = value;
OnChannelUpdate();
}
public void OnChannelUpdate() {
_onXChannelChanged.Invoke(_value.x, _channel.x, _hsl);
_onYChannelChanged.Invoke(_value.y, _channel.y, _hsl);
}
public void OnColorUpdate(Color rgb, ColorHSL hsl) {
Vector2 newValue;
newValue.x = _hsl ? hsl[_channel.x] : rgb[_channel.x];
newValue.y = _hsl ? hsl[_channel.y] : rgb[_channel.y];
Value = newValue;
if (_ignoreUpdates) return;
_image.material.SetFloat(Hue, hsl.h);
_value = value;
UpdateChannels();
}
public void UpdateSlider() {
private void Awake() {
var boxSlider = gameObject.GetComponentInChildren<BoxSlider>();
boxSlider.OnValueChanged.AddListener(OnSliderUpdate);
}
private void UpdateChannels() {
_ignoreUpdates = true;
onValueChanged.Invoke(_value);
_ignoreUpdates = false;
}
private void UpdateSlider() {
_ignoreUpdates = true;
_slider.Value = _value;
_ignoreUpdates = false;
}
}
}

View File

@ -1,15 +1,14 @@
using ConformalDecals.Util;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace ConformalDecals.UI {
public class ColorChannelSlider : MonoBehaviour {
[SerializeField] private ColorPickerController.ChannelUpdateEvent _onChannelChanged = new ColorPickerController.ChannelUpdateEvent();
[SerializeField] public ColorPickerController.ChannelUpdateEvent onChannelChanged = new ColorPickerController.ChannelUpdateEvent();
[SerializeField] private float _value;
[SerializeField] private int _channel;
[SerializeField] private bool _hsl;
[SerializeField] private bool _hsv;
[SerializeField] private Selectable _textBox;
[SerializeField] private Slider _slider;
@ -23,17 +22,27 @@ namespace ConformalDecals.UI {
_value = Mathf.Clamp01(value);
UpdateSlider();
UpdateTextbox();
OnChannelUpdate();
UpdateChannel();
}
}
public void OnColorUpdate(Color rgb, Util.ColorHSV hsv) {
if (_ignoreUpdates) return;
_image.material.SetColor(PropertyIDs._Color, _hsv ? (Color) (Vector4) hsv : rgb);
_value = _hsv ? hsv[_channel] : rgb[_channel];
UpdateSlider();
UpdateTextbox();
}
public void OnTextBoxUpdate(string text) {
if (_ignoreUpdates) return;
if (byte.TryParse(text, out byte byteValue)) {
_value = (float) byteValue / 255;
UpdateSlider();
OnChannelUpdate();
UpdateChannel();
}
else {
// value is invalid, reset value
@ -46,25 +55,20 @@ namespace ConformalDecals.UI {
_value = value;
UpdateTextbox();
OnChannelUpdate();
UpdateChannel();
}
public void OnChannelUpdate() {
_onChannelChanged.Invoke(_value, _channel, _hsl);
private void UpdateChannel() {
onChannelChanged.Invoke(_value, _channel, _hsv);
}
public void OnColorUpdate(Color rgb, ColorHSL hsl) {
_image.material.SetColor(PropertyIDs._Color, rgb);
Value = _hsl ? hsl[_channel] : rgb[_channel];
}
public void UpdateSlider() {
private void UpdateSlider() {
_ignoreUpdates = true;
_slider.value = _value;
_ignoreUpdates = false;
}
public void UpdateTextbox() {
private void UpdateTextbox() {
if (_textBox == null) return;
_ignoreUpdates = true;

View File

@ -8,12 +8,15 @@ using UnityEngine.UI;
namespace ConformalDecals.UI {
public class ColorPickerController : MonoBehaviour {
[Serializable]
public class ColorUpdateEvent : UnityEvent<Color, ColorHSL> { }
public class ColorUpdateEvent : UnityEvent<Color, Util.ColorHSV> { }
[Serializable]
public class ChannelUpdateEvent : UnityEvent<float, int, bool> { }
[SerializeField] private ColorUpdateEvent _onColorChanged = new ColorUpdateEvent();
[Serializable]
public class SVUpdateEvent : UnityEvent<Vector2> { }
[SerializeField] public ColorUpdateEvent onColorChanged = new ColorUpdateEvent();
[SerializeField] private Color _value;
[SerializeField] private Image _previewImage;
@ -29,23 +32,23 @@ namespace ConformalDecals.UI {
}
}
public ColorHSL HSL {
get => ColorHSL.RGB2HSL(_value);
public Util.ColorHSV HSV {
get => Util.ColorHSV.RGB2HSV(_value);
set {
_value = ColorHSL.HSL2RGB(value);
_value = Util.ColorHSV.HSV2RGB(value);
OnColorUpdate();
}
}
public static ColorPickerController Create(Color rgb, UnityAction<Color, ColorHSL> colorUpdateCallback) {
public static ColorPickerController Create(Color rgb, UnityAction<Color, Util.ColorHSV> colorUpdateCallback) {
var menu = Instantiate(UILoader.ColorPickerPrefab, MainCanvasUtil.MainCanvas.transform, true);
menu.AddComponent<DragPanel>();
MenuNavigation.SpawnMenuNavigation(menu, Navigation.Mode.Automatic, true);
var controller = menu.GetComponent<ColorPickerController>();
controller.RGB = rgb;
controller._onColorChanged.AddListener(colorUpdateCallback);
controller.onColorChanged.AddListener(colorUpdateCallback);
return controller;
}
@ -53,9 +56,30 @@ namespace ConformalDecals.UI {
Destroy(gameObject);
}
public void OnChannelUpdate(float value, int channel, bool hsv) {
if (hsv) {
var newHSV = HSV;
newHSV[channel] = value;
HSV = newHSV;
}
else {
var newRGB = RGB;
newRGB[channel] = value;
RGB = newRGB;
}
}
public void OnSVUpdate(Vector2 sv) {
var newHSV = HSV;
newHSV.s = sv.x;
newHSV.v = sv.y;
HSV = newHSV;
}
public void OnColorUpdate() {
_onColorChanged.Invoke(RGB, HSL);
onColorChanged.Invoke(RGB, HSV);
_previewImage.material.SetColor(PropertyIDs._Color, RGB);
UpdateHexColor();
}
public void OnHexColorUpdate(string text) {
@ -70,23 +94,24 @@ namespace ConformalDecals.UI {
}
}
public void UpdateHexColor() {
_ignoreUpdate = true;
((TMP_InputField) _hexTextBox).text = $"{RGB.r:x2}{RGB.g:x2}{RGB.b:x2}";
_ignoreUpdate = false;
private void Awake() {
foreach (var slider in gameObject.GetComponentsInChildren<ColorChannelSlider>()) {
slider.onChannelChanged.AddListener(OnChannelUpdate);
onColorChanged.AddListener(slider.OnColorUpdate);
}
foreach (var box in gameObject.GetComponentsInChildren<ColorBoxSlider>()) {
box.onValueChanged.AddListener(OnSVUpdate);
onColorChanged.AddListener(box.OnColorUpdate);
}
}
public void OnChannelUpdate(float value, int channel, bool hsl) {
if (hsl) {
var newHSL = HSL;
newHSL[channel] = value;
HSL = newHSL;
}
else {
var newRGB = RGB;
newRGB[channel] = value;
RGB = newRGB;
}
private void UpdateHexColor() {
_ignoreUpdate = true;
var byteColor = (Color32) RGB;
var hexColor = $"{byteColor.r:x2}{byteColor.g:x2}{byteColor.b:x2}";
((TMP_InputField) _hexTextBox).text = hexColor;
_ignoreUpdate = false;
}
}
}

View File

@ -11,7 +11,7 @@ namespace ConformalDecals.UI {
[Serializable]
public class FontUpdateEvent : UnityEvent<DecalFont> { }
[SerializeField] private FontUpdateEvent _onFontChanged = new FontUpdateEvent();
[SerializeField] public FontUpdateEvent onFontChanged = new FontUpdateEvent();
[SerializeField] private GameObject _menuItem;
[SerializeField] private GameObject _menuList;
@ -25,7 +25,7 @@ namespace ConformalDecals.UI {
var controller = menu.GetComponent<FontMenuController>();
controller._currentFont = currentFont;
controller._onFontChanged.AddListener(fontUpdateCallback);
controller.onFontChanged.AddListener(fontUpdateCallback);
controller.Populate(fonts);
return controller;
@ -37,7 +37,7 @@ namespace ConformalDecals.UI {
public void OnFontSelected(DecalFont font) {
_currentFont = font ?? throw new ArgumentNullException(nameof(font));
_onFontChanged.Invoke(_currentFont);
onFontChanged.Invoke(_currentFont);
}
public void Populate(IEnumerable<DecalFont> fonts) {

View File

@ -10,7 +10,7 @@ namespace ConformalDecals.UI {
[Serializable]
public class TextUpdateEvent : UnityEvent<DecalText> { }
[SerializeField] private TextUpdateEvent _onTextUpdate = new TextUpdateEvent();
[SerializeField] public TextUpdateEvent onTextUpdate = new TextUpdateEvent();
[SerializeField] private Selectable _textBox;
[SerializeField] private Button _fontButton;
@ -32,7 +32,7 @@ namespace ConformalDecals.UI {
var controller = window.GetComponent<TextEntryController>();
controller._decalText = text;
controller._onTextUpdate.AddListener(textUpdateCallback);
controller.onTextUpdate.AddListener(textUpdateCallback);
return controller;
}
@ -57,7 +57,7 @@ namespace ConformalDecals.UI {
}
public void OnAnyUpdate() {
_onTextUpdate.Invoke(_decalText);
onTextUpdate.Invoke(_decalText);
}
public void OnTextUpdate(string newText) {

View File

@ -1,4 +1,3 @@
using System;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
@ -63,7 +62,7 @@ namespace ConformalDecals.UI {
ProcessBoxSlider(tag.gameObject, skin.horizontalSlider, skin.horizontalSliderThumb);
break;
case UITag.UIType.Slider:
ProcessSlider(tag.gameObject, skin.horizontalSlider, skin.horizontalSliderThumb, skin.verticalSlider, skin.verticalSliderThumb);
ProcessSlider(tag.gameObject, skin.horizontalSlider, skin.horizontalSliderThumb, skin.horizontalSlider, skin.horizontalSliderThumb);
break;
case UITag.UIType.Box:
ProcessSelectable(tag.gameObject, skin.box);
@ -82,7 +81,10 @@ namespace ConformalDecals.UI {
}
private static void ProcessImage(GameObject gameObject, UIStyle style) {
ProcessImage(gameObject.GetComponent<Image>(), style.normal);
var image = gameObject.GetComponent<Image>();
if (image != null) {
ProcessImage(image, style.normal);
}
}
private static void ProcessImage(Image image, UIStyleState state) {
@ -135,10 +137,9 @@ namespace ConformalDecals.UI {
ProcessSelectable(gameObject, thumbStyle);
var back = gameObject.transform.Find("Background").GetComponent<Image>();
var back = gameObject.transform.Find("Background");
if (back != null) {
back.sprite = sliderStyle.normal.background;
back.type = Image.Type.Sliced;
ProcessImage(back.gameObject, sliderStyle);
}
}
}
@ -146,9 +147,9 @@ namespace ConformalDecals.UI {
private static void ProcessBoxSlider(GameObject gameObject, UIStyle backgroundStyle, UIStyle thumbStyle) {
ProcessSelectable(gameObject, thumbStyle);
var background = gameObject.transform.Find("Background").gameObject;
var background = gameObject.transform.Find("Background");
if (background != null) {
ProcessImage(background, backgroundStyle);
ProcessImage(background.gameObject, backgroundStyle);
}
}

View File

@ -0,0 +1,123 @@
using System;
using System.Globalization;
using UnityEngine;
namespace ConformalDecals.Util {
public struct ColorHSV : IEquatable<Color> {
public float h;
public float s;
public float v;
public float a;
public ColorHSV(float h, float s = 1, float v = 1, float a = 1) {
this.h = h;
this.s = s;
this.v = v;
this.a = a;
}
public override string ToString() {
return $"HSVA({this.h:F3}, {this.s:F3}, {this.v:F3}, {this.a:F3})";
}
public string ToString(string format) {
return
"HSVA(" +
$"{this.h.ToString(format, CultureInfo.InvariantCulture.NumberFormat)}, " +
$"{this.s.ToString(format, CultureInfo.InvariantCulture.NumberFormat)}, " +
$"{this.v.ToString(format, CultureInfo.InvariantCulture.NumberFormat)}, " +
$"{this.a.ToString(format, CultureInfo.InvariantCulture.NumberFormat)})";
}
public bool Equals(ColorHSL other) {
return (this.h.Equals(other.h) && this.s.Equals(other.s) && this.v.Equals(other.l) && this.a.Equals(other.a));
}
public bool Equals(Color other) {
var rgb = HSV2RGB(this);
return rgb.Equals(other);
}
public override bool Equals(object obj) {
if (obj is ColorHSL otherHSL) return Equals(otherHSL);
if (obj is Color otherRGB) return Equals(otherRGB);
return false;
}
public override int GetHashCode() {
return ((Vector4) this).GetHashCode();
}
public float this[int index] {
get {
switch (index) {
case 0:
return this.h;
case 1:
return this.s;
case 2:
return this.v;
case 3:
return this.a;
default:
throw new IndexOutOfRangeException("Invalid Vector3 index!");
}
}
set {
switch (index) {
case 0:
this.h = value;
break;
case 1:
this.s = value;
break;
case 2:
this.v = value;
break;
case 3:
this.a = value;
break;
default:
throw new IndexOutOfRangeException("Invalid Vector3 index!");
}
}
}
public static bool operator ==(ColorHSV lhs, ColorHSV rhs) {
return lhs.Equals(rhs);
}
public static bool operator !=(ColorHSV lhs, ColorHSV rhs) {
return !(lhs == rhs);
}
public static implicit operator Vector4(ColorHSV c) {
return new Vector4(c.h, c.s, c.v, c.a);
}
public static implicit operator ColorHSV(Vector4 v) {
return new ColorHSV(v.x, v.y, v.z, v.w);
}
public static implicit operator ColorHSV(Color rgb) {
return RGB2HSV(rgb);
}
public static implicit operator Color(ColorHSV hsv) {
return HSV2RGB(hsv);
}
public static Color HSV2RGB(ColorHSV hsv) {
var rgb = Color.HSVToRGB(hsv.h, hsv.s, hsv.v, false);
rgb.a = hsv.a;
return rgb;
}
public static ColorHSV RGB2HSV(Color rgb) {
var hsv = new ColorHSV {a = rgb.a};
Color.RGBToHSV(rgb, out hsv.h, out hsv.s, out hsv.v);
return hsv;
}
}
}