Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Silksong.ModMenu/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@
It should follow the format major.minor.patch (semantic versioning). If you publish your mod
as a library to NuGet, this version will also be used as the package version.
-->
<Version>0.7.4</Version>
<Version>0.7.5</Version>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a case where bumping the version should be done outside the PRs - the other PR has breaking changes so the version should be 0.8 rather than 0.7.5, but it's weird to bump the version here based on the changes in the other PR. I don't think we should create a release with the other PR without this one being included - can you remove the version update from this PR and (if you want to) add it to the other PR?

</PropertyGroup>
</Project>
43 changes: 43 additions & 0 deletions Silksong.ModMenu/Elements/TextInput.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ public class TextInput<T> : SelectableValueElement<T>
typeof(ulong),
];
private static readonly HashSet<Type> floatTypes = [typeof(float), typeof(double)];
private static readonly HashSet<Type> floatListTypes =
Comment thread
kaycodes13 marked this conversation as resolved.
[
typeof(Vector2),
typeof(Vector3),
typeof(Vector4),
typeof(Quaternion),
typeof(Rect),
];

/// <summary>
/// Construct a basic text input.
Expand All @@ -48,6 +56,14 @@ public TextInput(LocalizedText label, ITextModel<T> model, LocalizedText descrip
InputField.contentType = InputField.ContentType.IntegerNumber;
else if (floatTypes.Contains(typeof(T)))
InputField.contentType = InputField.ContentType.DecimalNumber;
else if (floatListTypes.Contains(typeof(T)))
{
InputField.contentType = InputField.ContentType.Custom;
InputField.onValidateInput = FloatListValidation;
ApplyDefaultColors = true;
OnTextValueChanged += _ =>
State = TextModel.IsTextValid ? ElementState.DEFAULT : ElementState.INVALID;
}
}

/// <summary>
Expand Down Expand Up @@ -100,4 +116,31 @@ public override void SetFontSizes(FontSizes fontSizes)
DescriptionText.fontSize = fontSizes.DescriptionSize();
InputField.textComponent.fontSize = fontSizes.ChoiceSize();
}

/// <summary>
/// <see cref="UnityEngine.UI.InputField"/> validation for comma-delimited lists
/// of float values, with or without enclosing brackets.
/// </summary>
static char FloatListValidation(string input, int pos, char ch)
{
int leftPos = input.IndexOf('('),
rightPos = input.IndexOf(')');

// Everything must go within the brackets, if they're present
if (leftPos >= pos || (rightPos < pos && rightPos >= 0))
return '\0';

if (
// Brackets must be unique and positioned on the appropriate end of the string
(ch == '(' && leftPos == -1 && pos == 0)
|| (ch == ')' && rightPos == -1 && pos == input.Length)
// Other valid characters
|| char.IsDigit(ch)
|| char.IsWhiteSpace(ch)
|| ",.-".Contains(ch)
)
return ch;

return '\0';
}
}
14 changes: 14 additions & 0 deletions Silksong.ModMenu/Generator/RGBElementFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Silksong.ModMenu.Elements;
using UnityEngine;

namespace Silksong.ModMenu.Generator;

/// <summary>
/// Generator for a <see cref="Color"/> element which only accepts RGB values with full alpha.
/// </summary>
public class RGBElementFactory : IElementFactory<Color, ColorInput>
{
/// <inheritdoc/>
public ColorInput CreateElement(LocalizedText name, LocalizedText description) =>
new(name, description) { Format = ColorInput.InputFormat.RGB };
}
6 changes: 6 additions & 0 deletions Silksong.ModMenu/Internal/MenuPrefabs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,12 @@ private MenuPrefabs(UIManager uiManager)
textInputChild.FindChild("CursorRight")!.GetComponent<Animator>(),
];

GameObject underlineObj = new("Underline") { layer = (int)PhysLayers.UI };
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How wedded are you to this underline? I think it looked better without the underline, personally

(To be clear, I don't mean we should keep - or remove - it based solely on my opinion, but I'd like it to be scrutinized further)

underlineObj.transform.SetParentReset(textInputField.textComponent.transform);
underlineObj.AddComponent<Image>();
underlineObj.RectTransform.FitToParentHorizontal(anchorY: 0);
underlineObj.RectTransform.sizeDelta = new Vector2(0, 3);

sliderTemplate = Object.Instantiate(
canvas.FindChild("AudioMenuScreen/Content/MasterVolume")!
);
Expand Down
152 changes: 152 additions & 0 deletions Silksong.ModMenu/Models/TextModels.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Silksong.ModMenu.Internal;
using UnityEngine;
Expand All @@ -17,6 +18,8 @@ public static class TextModels
public static ParserTextModel<string> ForStrings() =>
new(DefaultUnparse<string>, DefaultUnparse<string>);

#region Numbers

/// <summary>
/// An ITextModel which parses its input into a signed byte.
/// </summary>
Expand Down Expand Up @@ -172,6 +175,8 @@ public static ParserTextModel<double> ForDoubles(double min, double max)
return model;
}

#endregion

private static bool DefaultUnparse<T>(T value, out string text)
{
text = $"{value}";
Expand All @@ -193,6 +198,8 @@ bool Check(T value)
return Check;
}

#region Colors

/// <summary>
/// An ITextModel which parses 3, 6, or 8 character hex strings to and from <see cref="Color"/>s.
/// </summary>
Expand Down Expand Up @@ -243,4 +250,149 @@ private static bool HexUnparser(Color c, out string x)
x = "###";
return false;
}

#endregion
#region Vectors & Quaternions & Rects

/// <summary>
/// An ITextModel which parses comma-delimited pairs of numbers to and from <see cref="Vector2"/>s.
/// </summary>
public static ParserTextModel<Vector2> ForVector2() =>
new(Vector2Parser, Vector2Unparser, INVALID_VECTOR);

/// <summary>
/// An ITextModel which parses comma-delimited trios of numbers to and from <see cref="Vector3"/>s.
/// </summary>
public static ParserTextModel<Vector3> ForVector3() =>
new(Vector3Parser, Vector3Unparser, INVALID_VECTOR);

/// <summary>
/// An ITextModel which parses comma-delimited quartets of numbers to and from <see cref="Vector4"/>s.
/// </summary>
public static ParserTextModel<Vector4> ForVector4() =>
new(Vector4Parser, Vector4Unparser, INVALID_VECTOR);

/// <summary>
/// An ITextModel which parses comma-delimited quartets of numbers to and from <see cref="Quaternion"/>s.
/// </summary>
public static ParserTextModel<Quaternion> ForQuaternion() =>
new(QuaternionParser, QuaternionUnparser, INVALID_QUATERNION);

/// <summary>
/// An ITextModel which parses comma-delimited quartets of numbers to and from <see cref="Rect"/>s.
/// </summary>
public static ParserTextModel<Rect> ForRect() => new(RectParser, RectUnparser, INVALID_RECT);

private static readonly Vector4 INVALID_VECTOR = Vector4.positiveInfinity;
private static readonly Quaternion INVALID_QUATERNION = new(
float.PositiveInfinity,
float.PositiveInfinity,
float.PositiveInfinity,
float.PositiveInfinity
);
private static readonly Rect INVALID_RECT = new(
float.PositiveInfinity,
float.PositiveInfinity,
float.PositiveInfinity,
float.PositiveInfinity
);

private static bool Vector2Parser(string x, out Vector2 v)
{
bool success = FloatListParser(x, out float[] c) && c.Length == 2;
v = success ? new(c[0], c[1]) : INVALID_VECTOR;
return success;
}

private static bool Vector2Unparser(Vector2 v, out string x)
{
x = v.ToString("F2", CultureInfo.InvariantCulture.NumberFormat);
return true;
}

private static bool Vector3Parser(string x, out Vector3 v)
{
bool success = FloatListParser(x, out float[] c) && c.Length == 3;
v = success ? new(c[0], c[1], c[2]) : INVALID_VECTOR;
return success;
}

private static bool Vector3Unparser(Vector3 v, out string x)
{
x = v.ToString("F2", CultureInfo.InvariantCulture.NumberFormat);
return true;
}

private static bool Vector4Parser(string x, out Vector4 v)
{
bool success = FloatListParser(x, out float[] c) && c.Length == 4;
v = success ? new(c[0], c[1], c[2], c[3]) : INVALID_VECTOR;
return success;
}

private static bool Vector4Unparser(Vector4 v, out string x)
{
x = v.ToString("F2", CultureInfo.InvariantCulture.NumberFormat);
return true;
}

private static bool QuaternionParser(string x, out Quaternion q)
{
bool success = FloatListParser(x, out float[] c) && c.Length == 4;
q = success ? new(c[0], c[1], c[2], c[3]) : INVALID_QUATERNION;
return success;
}

private static bool QuaternionUnparser(Quaternion q, out string x)
{
x = q.ToString("F3", CultureInfo.InvariantCulture.NumberFormat);
return true;
}

private static bool RectParser(string x, out Rect r)
{
bool success = FloatListParser(x, out float[] c) && c.Length == 4;
r = success ? new(c[0], c[1], c[2], c[3]) : INVALID_RECT;
return success;
}

private static bool RectUnparser(Rect r, out string x)
{
const string format = "F2";
var provider = CultureInfo.InvariantCulture.NumberFormat;
x = UnityString.Format(
"({0}, {1}, {2}, {3})",
r.x.ToString(format, provider),
r.y.ToString(format, provider),
r.width.ToString(format, provider),
r.height.ToString(format, provider)
);
return true;
}

/// <summary>
/// Parses strings of the format "(-0.0, -0.0, ... , -0.0)" with or without brackets
/// into an array of floats.
/// </summary>
private static bool FloatListParser(string x, out float[] results)
{
x = x.Trim();
if (x[0] == '(')
x = x[1..];
if (x[^1] == ')')
x = x[..^1];

string[] rawVals = x.Split(',');
results = new float[rawVals.Length];

NumberFormatInfo format = CultureInfo.InvariantCulture.NumberFormat;

for (int i = 0; i < rawVals.Length; i++)
if (!float.TryParse(rawVals[i], NumberStyles.Float, format, out results[i]))
return false;

return true;
}

#endregion
}
Loading