Skip to content

Make a textbox

Textboxes in Tick UI use the invisible-widget idiom. ui.Textbox(...) claims input, updates the bound string, and returns a layout struct holding the text, view, caret, and selection-highlight rects so the caller can draw the visual itself.

To make a textbox

  1. Push a layout rect for the textbox.
    using (ui.Auto().Top(28))
    {
        // declare config + layout, call Textbox, then draw
    }
    
  2. Build a TextboxConfig. Start from TextboxConfig.Default and override only what differs:
    var config = TextboxConfig.Default;
    config.Font = "roboto_mono";
    config.FontSize = 14;
    
  3. Declare an output layout struct.
    TextboxLayout layout;
    
  4. Call Textbox. The call updates the bound string and fills layout with the rects for text, view, caret, and selection.
    TextboxAction action = ui.Textbox("username", ref m_username,
        config, out layout);
    
  5. Draw the visual using the layout rects:
  6. Background fill (or rounded fill).
  7. Selection highlight at layout.HighlightRect.
  8. Caret at layout.CaretRect.
  9. Text at layout.TextRect (or layout.ViewRect for the clipped viewport). Draw the text from layout.RenderText, not from the bound ref string. Refer to Render from layout.RenderText.

The full pattern

using (ui.Auto().Top(28))
{
    var config = TextboxConfig.Default;
    config.Font = "roboto_mono";
    config.FontSize = 14;

    TextboxLayout layout;
    TextboxAction action = ui.Textbox("username", ref m_username,
        config, out layout);

    ui.FillRounded(backgroundColor, 4);

    ui.PushRelativeRect(layout.HighlightRect);
        ui.Fill(selectionColor);
    ui.Pop();

    ui.PushRelativeRect(layout.CaretRect);
        ui.Fill(Color.white);
    ui.Pop();

    ui.PushRelativeRect(layout.TextRect);
        ui.Text(layout.RenderText, config.Font, config.FontSize,
            textColor, TextAlign.MiddleLeft);
    ui.Pop();

    if (action == TextboxAction.Enter)
        OnUsernameSubmitted(m_username);
}

TextboxConfig fields

TextboxConfig carries every per-textbox knob:

Field Purpose
Font Font name to look up through the resource provider chain.
FontSize Font size variant.
Filter A TextboxKeyFilter delegate that vets each typed character.
ReadOnly Disables typing.
IsNumber Restricts input to numeric characters.
Padding A Margin applied around the text rect.
CaretWidth Caret thickness in pixels.
CaretTopBottomPadding Vertical padding inside the caret rect.
CaretBlinkTime Blink period in seconds.

TextboxConfig.Default provides reasonable values for every field; override only what you need. Note the default Font is "roboto_mono", which is not in the bundled DefaultTickConfig. For projects relying on the bundled fonts, set config.Font to "roboto_mono" (the bundled monospace font) or to a font registered through your own GUIResourceProvider.

Selecting text with the keyboard

Holding Shift while moving the caret selects instead of moving: Shift+Left, Shift+Right, Shift+Home, and Shift+End all extend the selection from wherever the caret was when the run began. Releasing Shift and moving again collapses the selection, and shrinking a selection back onto its starting point leaves nothing selected.

Typing, Backspace, and Delete act on the selected range, so the keyboard reaches everything a click-drag or double-click selection does.

Shift is read directly from the input provider (GetShiftHeld), not through TextboxBindings, so there is no separate action to rebind — customising CaretLeft and friends changes the shifted variants with them.

Focus and the action return

Tick UI tracks the focused textbox internally. Clicking a textbox gives it focus; pressing Tab or clicking elsewhere takes focus away. Type-input is routed to the focused textbox.

The Textbox call returns an TextboxAction:

Value Meaning
None No state change this frame.
Enter The user pressed Enter.
Escape The user pressed Escape.
Changed The bound string changed (any keystroke that produced a different string).

Use the return value to commit on Enter, cancel on Escape, or react to every change.

To set focus programmatically, call ui.SetFocus("textbox_id") from outside the textbox call.

Render from layout.RenderText

Both Textbox and TextboxSubmit fill layout.RenderText with the live working buffer every frame, including any uncommitted keystrokes. Draw the textbox from layout.RenderText. Under TextboxSubmit, the ref string updates on Enter and RenderText updates per keystroke. Drawing from layout.RenderText keeps the textbox responsive between keystroke and Enter in both modes.

The rule is the same in both modes, so the call site applies one convention:

Read layout.RenderText to render. Read the ref text for the value your program acts on.

Choose live or commit-on-Enter

Two methods set the contract on ref text:

  • ui.Textbox(...): live edit. The bound ref text updates per keystroke. Changed fires on each frame the string changes. Enter fires when the user presses Return.
  • ui.TextboxSubmit(...): commit on Enter. The bound ref text holds its value while the user types. Pressing Return commits the working buffer into text and returns Enter. The visible textbox stays live throughout, because rendering comes from layout.RenderText.

Choose the method by the contract you want on ref text. The method name states that contract at the call site.

Pitfalls

The caret does not appear. : The drawing order is wrong. Fill the background first, then draw highlight, caret, and text on top.

Two textboxes share state. : Both calls passed the same string ID. Use unique IDs, or wrap repeated textboxes in PushID(item.Id).

Type-input goes to the wrong textbox. : Focus belongs to a textbox in a non-hovered panel. Click the visible textbox to give it focus, or audit panel input priority via Input and focus.

The configured font does not appear. : The font name in config.Font is not registered with any provider in the chain. Add it to a GUIResourceProvider. The bundled DefaultTickConfig ships roboto_mono only.

Additional resources