/* * 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 { /// /// A custom UI text field factory class. /// public sealed class PTextField : IUIComponent { /// /// Configures a Text Mesh Pro field. /// /// The text component to configure. /// The desired text color, font, and style. /// The text alignment. /// The component, for call chaining. 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; } /// /// Gets a text field's text. /// /// The UI element to retrieve. /// The current text in the field. public static string GetText(GameObject textField) { if (textField == null) throw new ArgumentNullException(nameof(textField)); return textField.TryGetComponent(out TMP_InputField field) ? field.text : ""; } /// /// The text field's background color. /// public Color BackColor { get; set; } /// /// Retrieves the built-in field type used for Text Mesh Pro. /// 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; } } /// /// The flexible size bounds of this component. /// public Vector2 FlexSize { get; set; } /// /// The maximum number of characters in this text field. /// public int MaxLength { get; set; } /// /// The minimum width in units (not characters!) of this text field. /// public int MinWidth { get; set; } /// /// The placeholder text style (including color, font, and word wrap settings) if the /// field is empty. /// public TextStyleSetting PlaceholderStyle { get; set; } /// /// The placeholder text if the field is empty. /// public string PlaceholderText { get; set; } public string Name { get; } /// /// The text alignment in the text field. /// public TextAlignmentOptions TextAlignment { get; set; } /// /// The initial text in the text field. /// public string Text { get; set; } /// /// The text field's text color, font, word wrap settings, and font size. /// public TextStyleSetting TextStyle { get; set; } /// /// The tool tip text. /// public string ToolTip { get; set; } /// /// The field type. /// public FieldType Type { get; set; } public event PUIDelegates.OnRealize OnRealize; /// /// The action to trigger on text change. It is passed the realized source object. /// public PUIDelegates.OnTextChanged OnTextChanged { get; set; } /// /// The callback to invoke when validating input. /// 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; } /// /// Adds a handler when this text field is realized. /// /// The handler to invoke on realization. /// This text field for call chaining. 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(); 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().color = BackColor; var mask = textArea.AddComponent(); // Scrollable text var textBox = PUIElements.CreateUI(textArea, "Text"); // Text to display var textDisplay = ConfigureField(textBox.AddComponent(), style, TextAlignment); textDisplay.enableWordWrapping = false; textDisplay.maxVisibleLines = 1; textDisplay.raycastTarget = true; // Text field itself textField.SetActive(false); var textEntry = textField.AddComponent(); 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(), PlaceholderStyle ?? style, TextAlignment); textPlace.maxVisibleLines = 1; textPlace.text = PlaceholderText; textEntry.placeholder = textPlace; } // Events! ConfigureTextEntry(textEntry); var events = textField.AddComponent(); 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(); layout.flexibleWidth = FlexSize.x; layout.flexibleHeight = FlexSize.y; OnRealize?.Invoke(textField); return textField; } /// /// Sets up the text entry field. /// /// The input field to configure. 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; } /// /// Sets the default Klei pink style as this text field's color and text style. /// /// This text field for call chaining. public PTextField SetKleiPinkStyle() { TextStyle = PUITuning.Fonts.UILightStyle; BackColor = PUITuning.Colors.ButtonPinkStyle.inactiveColor; return this; } /// /// Sets the default Klei blue style as this text field's color and text style. /// /// This text field for call chaining. public PTextField SetKleiBlueStyle() { TextStyle = PUITuning.Fonts.UILightStyle; BackColor = PUITuning.Colors.ButtonBlueStyle.inactiveColor; return this; } /// /// Sets the minimum (and preferred) width of this text field in characters. /// /// The width is computed using the currently selected text style. /// /// The number of characters to be displayed. /// This text field for call chaining. 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); } /// /// The valid text field types supported by this class. /// public enum FieldType { Text, Integer, Float } } }