// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. namespace FlaxEngine.GUI { /// /// Text box control which can gather text input from the user. /// [ActorToolbox("GUI")] public class TextBox : TextBoxBase { private TextLayoutOptions _layout; /// /// The watermark text. /// protected LocalizedString _watermarkText; /// /// Gets or sets the watermark text to show grayed when textbox is empty. /// [EditorOrder(20), Tooltip("The watermark text to show grayed when textbox is empty.")] public LocalizedString WatermarkText { get => _watermarkText; set => _watermarkText = value; } /// /// Whether to Obfuscate the text with a different character. /// [EditorOrder(21), Tooltip("Whether to Obfuscate the text with a different character.")] public bool ObfuscateText = false; /// /// The character to Obfuscate the text. /// [EditorOrder(22), VisibleIf(nameof(ObfuscateText)), Tooltip("The character to Obfuscate the text.")] public char ObfuscateCharacter = '\u25cf'; /// /// The text case. /// [EditorDisplay("Text Style"), EditorOrder(2000), Tooltip("The case of the text.")] public TextCaseOptions CaseOption { get; set; } = TextCaseOptions.None; /// /// Whether to bold the text. /// [EditorDisplay("Text Style"), EditorOrder(2001), Tooltip("Bold the text.")] public bool Bold { get; set; } = false; /// /// Whether to italicize the text. /// [EditorDisplay("Text Style"), EditorOrder(2002), Tooltip("Italicize the text.")] public bool Italic { get; set; } = false; /// /// The vertical alignment of the text. /// [EditorDisplay("Text Style"), EditorOrder(2023), Tooltip("The vertical alignment of the text.")] public TextAlignment VerticalAlignment { get => _layout.VerticalAlignment; set => _layout.VerticalAlignment = value; } /// /// The vertical alignment of the text. /// [EditorDisplay("Text Style"), EditorOrder(2024), Tooltip("The horizontal alignment of the text.")] public TextAlignment HorizontalAlignment { get => _layout.HorizontalAlignment; set => _layout.HorizontalAlignment = value; } /// /// Gets or sets the text wrapping within the control bounds. /// [EditorDisplay("Text Style"), EditorOrder(2025), Tooltip("The text wrapping within the control bounds.")] public TextWrapping Wrapping { get => _layout.TextWrapping; set => _layout.TextWrapping = value; } /// /// Gets or sets the font. /// [EditorDisplay("Text Style"), EditorOrder(2026)] public FontReference Font { get; set; } /// /// Gets or sets the custom material used to render the text. It must has domain set to GUI and have a public texture parameter named Font used to sample font atlas texture with font characters data. /// [EditorDisplay("Text Style"), EditorOrder(2027), Tooltip("Custom material used to render the text. It must has domain set to GUI and have a public texture parameter named Font used to sample font atlas texture with font characters data.")] public MaterialBase TextMaterial { get; set; } /// /// Gets or sets the color of the text. /// [EditorDisplay("Text Style"), EditorOrder(2020), Tooltip("The color of the text."), ExpandGroups] public Color TextColor { get; set; } /// /// Gets or sets the color of the text. /// [EditorDisplay("Text Style"), EditorOrder(2021), Tooltip("The color of the watermark text.")] public Color WatermarkTextColor { get; set; } /// /// Gets or sets the color of the selection (Transparent if not used). /// [EditorDisplay("Text Style"), EditorOrder(2022), Tooltip("The color of the selection (Transparent if not used).")] public Color SelectionColor { get; set; } /// /// Initializes a new instance of the class. /// public TextBox() : this(false, 0, 0) { } /// /// Init /// /// Enable/disable multiline text input support /// Position X coordinate /// Position Y coordinate /// Width public TextBox(bool isMultiline, float x, float y, float width = 120) : base(isMultiline, x, y, width) { _layout = TextLayoutOptions.Default; _layout.VerticalAlignment = IsMultiline ? TextAlignment.Near : TextAlignment.Center; _layout.TextWrapping = TextWrapping.NoWrap; _layout.Bounds = new Rectangle(DefaultMargin, 1, Width - 2 * DefaultMargin, Height - 2); var style = Style.Current; Font = new FontReference(style.FontMedium); TextColor = style.Foreground; WatermarkTextColor = style.ForegroundDisabled; SelectionColor = style.BackgroundSelected; } /// public override Float2 GetTextSize() { var font = GetFont(); if (font == null) { return Float2.Zero; } return font.MeasureText(ConvertedText(), ref _layout); } private Font GetFont() { Font font; if (Bold) font = Italic ? Font.GetBold().GetItalic().GetFont() : Font.GetBold().GetFont(); else if (Italic) font = Font.GetItalic().GetFont(); else font = Font.GetFont(); return font; } private string ConvertedText() { if (ObfuscateText) return new string(ObfuscateCharacter, _text.Length); string text = _text; switch (CaseOption) { case TextCaseOptions.Uppercase: text = text.ToUpper(); break; case TextCaseOptions.Lowercase: text = text.ToLower(); break; } return text; } /// public override Float2 GetCharPosition(int index, out float height) { var font = GetFont(); if (font == null) { height = Height; return Float2.Zero; } height = font.Height / DpiScale; return font.GetCharPosition(ConvertedText(), index, ref _layout); } /// public override int HitTestText(Float2 location) { var font = GetFont(); if (font == null) { return 0; } return font.HitTestText(ConvertedText(), location, ref _layout); } /// protected override void OnIsMultilineChanged() { base.OnIsMultilineChanged(); _layout.VerticalAlignment = IsMultiline ? TextAlignment.Near : TextAlignment.Center; } /// public override void DrawSelf() { // Cache data var rect = new Rectangle(Float2.Zero, Size); bool enabled = EnabledInHierarchy; var font = GetFont(); if (!font) return; // Background Color backColor = BackgroundColor; if (IsMouseOver || IsNavFocused) backColor = BackgroundSelectedColor; Render2D.FillRectangle(rect, backColor); if (HasBorder) Render2D.DrawRectangle(rect, IsFocused ? BorderSelectedColor : BorderColor, BorderThickness); // Apply view offset and clip mask if (ClipText) Render2D.PushClip(TextClipRectangle); bool useViewOffset = !_viewOffset.IsZero; if (useViewOffset) Render2D.PushTransform(Matrix3x3.Translation2D(-_viewOffset)); var text = ConvertedText(); // Check if sth is selected to draw selection if (HasSelection) { var leftEdge = font.GetCharPosition(text, SelectionLeft, ref _layout); var rightEdge = font.GetCharPosition(text, SelectionRight, ref _layout); var fontHeight = font.Height; var textHeight = fontHeight / DpiScale; // Draw selection background float alpha = Mathf.Min(1.0f, Mathf.Cos(_animateTime * BackgroundSelectedFlashSpeed) * 0.5f + 1.3f); alpha *= alpha; Color selectionColor = SelectionColor * alpha; // int selectedLinesCount = 1 + Mathf.FloorToInt((rightEdge.Y - leftEdge.Y) / textHeight); if (selectedLinesCount == 1) { // Selected is part of single line Rectangle r1 = new Rectangle(leftEdge.X, leftEdge.Y, rightEdge.X - leftEdge.X, fontHeight); Render2D.FillRectangle(r1, selectionColor); } else { float leftMargin = _layout.Bounds.Location.X; // Selected is more than one line Rectangle r1 = new Rectangle(leftEdge.X, leftEdge.Y, 1000000000, fontHeight); Render2D.FillRectangle(r1, selectionColor); // for (int i = 3; i <= selectedLinesCount; i++) { leftEdge.Y += textHeight; Rectangle r = new Rectangle(leftMargin, leftEdge.Y, 1000000000, fontHeight); Render2D.FillRectangle(r, selectionColor); } // Rectangle r2 = new Rectangle(leftMargin, rightEdge.Y, rightEdge.X - leftMargin, fontHeight); Render2D.FillRectangle(r2, selectionColor); } } // Text or watermark if (text.Length > 0) { var color = TextColor; if (!enabled) color *= 0.6f; Render2D.DrawText(font, text, color, ref _layout, TextMaterial); } else if (!string.IsNullOrEmpty(_watermarkText)) { Render2D.DrawText(font, _watermarkText, WatermarkTextColor, ref _layout, TextMaterial); } // Caret if (IsFocused && CaretPosition > -1) { float alpha = Mathf.Saturate(Mathf.Cos(_animateTime * CaretFlashSpeed) * 0.5f + 0.7f); alpha = alpha * alpha * alpha * alpha * alpha * alpha; Render2D.FillRectangle(CaretBounds, CaretColor * alpha); } // Restore rendering state if (useViewOffset) Render2D.PopTransform(); if (ClipText) Render2D.PopClip(); } /// public override bool OnMouseDoubleClick(Float2 location, MouseButton button) { if (IsSelectable) { SelectAll(); } return base.OnMouseDoubleClick(location, button); } /// protected override void OnSizeChanged() { base.OnSizeChanged(); _layout.Bounds = TextRectangle; } } }