oni-priority-ux/mod/PLibUI/PTextField.cs

303 lines
10 KiB
C#

/*
* 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 System;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace PeterHan.PLib.UI {
/// <summary>
/// A custom UI text field factory class.
/// </summary>
public sealed class PTextField : IUIComponent {
/// <summary>
/// Configures a Text Mesh Pro field.
/// </summary>
/// <param name="component">The text component to configure.</param>
/// <param name="style">The desired text color, font, and style.</param>
/// <param name="alignment">The text alignment.</param>
/// <returns>The component, for call chaining.</returns>
internal static TextMeshProUGUI ConfigureField(TextMeshProUGUI component,
TextStyleSetting style, TextAlignmentOptions alignment) {
component.alignment = alignment;
component.autoSizeTextContainer = false;
component.enabled = true;
component.color = style.textColor;
component.font = style.sdfFont;
component.fontSize = style.fontSize;
component.fontStyle = style.style;
component.overflowMode = TextOverflowModes.Overflow;
return component;
}
/// <summary>
/// Gets a text field's text.
/// </summary>
/// <param name="textField">The UI element to retrieve.</param>
/// <returns>The current text in the field.</returns>
public static string GetText(GameObject textField) {
if (textField == null)
throw new ArgumentNullException(nameof(textField));
return textField.TryGetComponent(out TMP_InputField field) ? field.text : "";
}
/// <summary>
/// The text field's background color.
/// </summary>
public Color BackColor { get; set; }
/// <summary>
/// Retrieves the built-in field type used for Text Mesh Pro.
/// </summary>
private TMP_InputField.ContentType ContentType {
get {
TMP_InputField.ContentType cType;
switch (Type) {
case FieldType.Float:
cType = TMP_InputField.ContentType.DecimalNumber;
break;
case FieldType.Integer:
cType = TMP_InputField.ContentType.IntegerNumber;
break;
case FieldType.Text:
default:
cType = TMP_InputField.ContentType.Standard;
break;
}
return cType;
}
}
/// <summary>
/// The flexible size bounds of this component.
/// </summary>
public Vector2 FlexSize { get; set; }
/// <summary>
/// The maximum number of characters in this text field.
/// </summary>
public int MaxLength { get; set; }
/// <summary>
/// The minimum width in units (not characters!) of this text field.
/// </summary>
public int MinWidth { get; set; }
/// <summary>
/// The placeholder text style (including color, font, and word wrap settings) if the
/// field is empty.
/// </summary>
public TextStyleSetting PlaceholderStyle { get; set; }
/// <summary>
/// The placeholder text if the field is empty.
/// </summary>
public string PlaceholderText { get; set; }
public string Name { get; }
/// <summary>
/// The text alignment in the text field.
/// </summary>
public TextAlignmentOptions TextAlignment { get; set; }
/// <summary>
/// The initial text in the text field.
/// </summary>
public string Text { get; set; }
/// <summary>
/// The text field's text color, font, word wrap settings, and font size.
/// </summary>
public TextStyleSetting TextStyle { get; set; }
/// <summary>
/// The tool tip text.
/// </summary>
public string ToolTip { get; set; }
/// <summary>
/// The field type.
/// </summary>
public FieldType Type { get; set; }
public event PUIDelegates.OnRealize OnRealize;
/// <summary>
/// The action to trigger on text change. It is passed the realized source object.
/// </summary>
public PUIDelegates.OnTextChanged OnTextChanged { get; set; }
/// <summary>
/// The callback to invoke when validating input.
/// </summary>
public TMP_InputField.OnValidateInput OnValidate { get; set; }
public PTextField() : this(null) { }
public PTextField(string name) {
BackColor = PUITuning.Colors.BackgroundLight;
FlexSize = Vector2.zero;
MaxLength = 256;
MinWidth = 32;
Name = name ?? "TextField";
PlaceholderText = null;
Text = null;
TextAlignment = TextAlignmentOptions.Center;
TextStyle = PUITuning.Fonts.TextDarkStyle;
PlaceholderStyle = TextStyle;
ToolTip = "";
Type = FieldType.Text;
}
/// <summary>
/// Adds a handler when this text field is realized.
/// </summary>
/// <param name="onRealize">The handler to invoke on realization.</param>
/// <returns>This text field for call chaining.</returns>
public PTextField AddOnRealize(PUIDelegates.OnRealize onRealize) {
OnRealize += onRealize;
return this;
}
public GameObject Build() {
var textField = PUIElements.CreateUI(null, Name);
var style = TextStyle ?? PUITuning.Fonts.TextLightStyle;
// Background
var border = textField.AddComponent<Image>();
border.sprite = PUITuning.Images.BoxBorderWhite;
border.type = Image.Type.Sliced;
border.color = style.textColor;
// Text box with rectangular clipping area
var textArea = PUIElements.CreateUI(textField, "Text Area", false);
textArea.AddComponent<Image>().color = BackColor;
var mask = textArea.AddComponent<RectMask2D>();
// Scrollable text
var textBox = PUIElements.CreateUI(textArea, "Text");
// Text to display
var textDisplay = ConfigureField(textBox.AddComponent<TextMeshProUGUI>(), style,
TextAlignment);
textDisplay.enableWordWrapping = false;
textDisplay.maxVisibleLines = 1;
textDisplay.raycastTarget = true;
// Text field itself
textField.SetActive(false);
var textEntry = textField.AddComponent<TMP_InputField>();
textEntry.textComponent = textDisplay;
textEntry.textViewport = textArea.rectTransform();
textEntry.text = Text ?? "";
textDisplay.text = Text ?? "";
// Placeholder
if (PlaceholderText != null) {
var placeholder = PUIElements.CreateUI(textArea, "Placeholder Text");
var textPlace = ConfigureField(placeholder.AddComponent<TextMeshProUGUI>(),
PlaceholderStyle ?? style, TextAlignment);
textPlace.maxVisibleLines = 1;
textPlace.text = PlaceholderText;
textEntry.placeholder = textPlace;
}
// Events!
ConfigureTextEntry(textEntry);
var events = textField.AddComponent<PTextFieldEvents>();
events.OnTextChanged = OnTextChanged;
events.OnValidate = OnValidate;
events.TextObject = textBox;
// Add tooltip
PUIElements.SetToolTip(textField, ToolTip);
mask.enabled = true;
PUIElements.SetAnchorOffsets(textBox, new RectOffset());
textField.SetActive(true);
// Lay out
var rt = textBox.rectTransform();
LayoutRebuilder.ForceRebuildLayoutImmediate(rt);
var layout = PUIUtils.InsetChild(textField, textArea, Vector2.one, new Vector2(
MinWidth, LayoutUtility.GetPreferredHeight(rt))).AddOrGet<LayoutElement>();
layout.flexibleWidth = FlexSize.x;
layout.flexibleHeight = FlexSize.y;
OnRealize?.Invoke(textField);
return textField;
}
/// <summary>
/// Sets up the text entry field.
/// </summary>
/// <param name="textEntry">The input field to configure.</param>
private void ConfigureTextEntry(TMP_InputField textEntry) {
textEntry.characterLimit = Math.Max(1, MaxLength);
textEntry.contentType = ContentType;
textEntry.enabled = true;
textEntry.inputType = TMP_InputField.InputType.Standard;
textEntry.interactable = true;
textEntry.isRichTextEditingAllowed = false;
textEntry.keyboardType = TouchScreenKeyboardType.Default;
textEntry.lineType = TMP_InputField.LineType.SingleLine;
textEntry.navigation = Navigation.defaultNavigation;
textEntry.richText = false;
textEntry.selectionColor = PUITuning.Colors.SelectionBackground;
textEntry.transition = Selectable.Transition.None;
textEntry.restoreOriginalTextOnEscape = true;
}
/// <summary>
/// Sets the default Klei pink style as this text field's color and text style.
/// </summary>
/// <returns>This text field for call chaining.</returns>
public PTextField SetKleiPinkStyle() {
TextStyle = PUITuning.Fonts.UILightStyle;
BackColor = PUITuning.Colors.ButtonPinkStyle.inactiveColor;
return this;
}
/// <summary>
/// Sets the default Klei blue style as this text field's color and text style.
/// </summary>
/// <returns>This text field for call chaining.</returns>
public PTextField SetKleiBlueStyle() {
TextStyle = PUITuning.Fonts.UILightStyle;
BackColor = PUITuning.Colors.ButtonBlueStyle.inactiveColor;
return this;
}
/// <summary>
/// Sets the minimum (and preferred) width of this text field in characters.
///
/// The width is computed using the currently selected text style.
/// </summary>
/// <param name="chars">The number of characters to be displayed.</param>
/// <returns>This text field for call chaining.</returns>
public PTextField SetMinWidthInCharacters(int chars) {
int width = Mathf.RoundToInt(chars * PUIUtils.GetEmWidth(TextStyle));
if (width > 0)
MinWidth = width;
return this;
}
public override string ToString() {
return string.Format("PTextField[Name={0},Type={1}]", Name, Type);
}
/// <summary>
/// The valid text field types supported by this class.
/// </summary>
public enum FieldType {
Text, Integer, Float
}
}
}