10 TextMode – Selection and Mouse

7 min read

Let players drag-select and copy text out of a TextMode display, and let a hosted program read the mouse the way a real terminal reports it.

Summary

Two different jobs share this page because they both start from “where did the pointer land on the grid“, and they are mutually exclusive in practice.

Selection is for the player. The player drags across text, watches it highlight, and copies it to the system clipboard. That is what someone does when they want to paste an error message into a bug report or lift a seed out of a roguelike’s death screen. TextModeSelectionManager handles the highlighting, the range tracking and the text extraction, including turning a rectangular cell range back into sensible lines.

Mouse reporting is for a hosted program. A terminal that is running someone else’s software has to tell that software where the mouse is, in the encoding the terminal protocol defines, so a full-screen program can implement its own clicking and dragging. TextModeMouseReporter does that translation, and it only matters when a program has actually asked for mouse tracking by setting one of the DEC modes.

The two conflict by nature: if the program wants the mouse, the player cannot use drag-selection for their own purposes, which is exactly the tension real terminals have. Most terminal emulators resolve it with a modifier key that forces local selection, and that is the pattern to copy if your game hosts programs that track the mouse.

For most games only selection matters. Mouse reporting is there for the case where you are genuinely hosting terminal software.

Use this page when you need to:

  • Let a player copy text out of a log, a console, or a death screen.
  • Highlight a range of cells programmatically, for a search result or a tutorial pointer.
  • Host a program that implements its own mouse interaction.
  • Understand why drag-selecting stops working once a program enables tracking.

Features

  • Drag selection. Click and drag across cells with live highlighting, using the cell attribute the shader already understands, so highlighting costs nothing per frame.
  • Clipboard copy. Selected text goes to the system clipboard through Unity, with the cell range converted to lines.
  • Programmatic selection. Set a range, select a row, or select all from code, for search results and tutorial highlights.
  • Text extraction. Read the selected text without copying it, when your game wants to do something else with it.
  • Terminal mouse reporting. Normal, button-event and any-event tracking modes.
  • Two encodings. The default encoding and the SGR extended encoding, which handles coordinates past column 223.
  • Driven by the program. Reporting activates only when a hosted program sets the corresponding DEC private mode, so it costs nothing until something asks.

Tracking modes and encodings

A program chooses both, through escape sequences. You do not normally set these yourself; you host them.

ModeReportsNotes
NoneNothingThe default. The mouse belongs to the player and to your game.
NormalButton press and releaseMode 1000. Enough for a program that wants clicks and nothing else.
ButtonEventPress, release, and motion while a button is heldMode 1002. This is what a program implementing drag needs.
AnyEventPress, release, and all motionMode 1003. Hover effects and mouse-over highlights, at the cost of a report for every movement.
EncodingCoordinate rangeNotes
DefaultUp to column and row 223The original encoding, which packs coordinates into single bytes with an offset. It cannot express a position past 223, which a modern grid can easily exceed.
SGRUnbounded in practiceMode 1006. Coordinates are decimal text rather than packed bytes, and release events are distinguishable from presses. Prefer this whenever the program supports it.

Recipes

Let the player copy an error message

Problem. Your in-game console printed a stack trace and the player wants to paste it into a bug report. Retyping it is not an option.

Solution. The terminal frontend already wires selection to its drag handlers; you connect them to your pointer events and offer a copy action.

/* Three calls matching the three phases of a drag. The manager highlights as the player
   moves, using a cell attribute rather than a redraw, so dragging is cheap. */
public void OnPointerDown(PointerEventData e) => _terminal.HandleDragStart(e.position);
public void OnDrag(PointerEventData e)        => _terminal.HandleDrag(e.position);
public void OnPointerUp(PointerEventData e)   => _terminal.HandleDragEnd(e.position);

/* And the action the player is actually after. HasSelection lets you grey out the menu item
   when there is nothing to copy. */
private void OnCopyPressed()
{
    if (_terminal.HasSelection)
    {
        _terminal.CopySelectionToClipboard();
    }
}

Highlight a search result

Problem. Your console has a find command and you want the match highlighted the way a selection would be, without the player having dragged anything.

Solution. Set the selection range directly. The highlight is the same mechanism, so it looks identical.

/* A range in cell coordinates. The manager applies the selection attribute across it and
   refreshes the visuals, so the match reads exactly like a manual selection. */
_selectionManager.SetSelection(new CellRange(startRow, startColumn, endRow, endColumn));

/* SelectRow is the convenience for a whole line, which is what a log search usually wants. */
_selectionManager.SelectRow(matchRow);

Read selected text without copying it

Problem. Your game lets the player select a coordinate off a map readout and jump to it. Copying to the clipboard would be a side effect nobody asked for.

Solution. Extract the text and use it.

/* Returns the selection as text, with the cell range resolved into lines. Trailing blanks on
   each row are trimmed, so a selection across a sparse screen does not come back padded. */
var text = _selectionManager.GetSelectedText();

if (TryParseCoordinate(text, out var target))
{
    JumpTo(target);
    _selectionManager.ClearSelection();
}

Host a program that wants the mouse

Problem. You are running a full-screen program that implements its own clicking, and right now your clicks select text instead of reaching it.

Solution. Attach the reporter and point it at the emulator. It activates when the program asks, and stays inert until then.

/* The reporter reads the emulator's tracking mode and encoding, both of which the hosted
   program sets through escape sequences. Until a program enables tracking, this does nothing
   and the player keeps the mouse. */
var reporter = viewObject.AddComponent<TextModeMouseReporter>();
reporter.Emulator = _terminal.Emulator;

Disable selection on a display that should not be copied

Problem. A diegetic screen is part of the fiction, and letting the player drag a highlight across it breaks the illusion.

Solution. Turn selection off in the terminal’s settings.

var settings = TextModeTerminalSettings.CreateDefault();

/* No highlighting, no clipboard, no drag handling. The display is output only. */
settings.SelectionEnabled = false;

API Sketch

class TextModeSelectionManager : MonoBehaviour {
    bool HasSelection, IsSelecting;
    CellRange CurrentRange;
    int PinnedBottomRows { get; set; }

    void SetSelection(CellRange range);
    void SelectRow(int row);
    void SelectAll();
    void ClearSelection();
    string GetSelectedText();
    void CopyToClipboard();
    void RefreshSelectionVisuals();
    void ShiftSelection(int rows);
}

class TextModeMouseReporter : MonoBehaviour {
    TerminalEmulator Emulator { get; set; }
}

enum MouseTrackingMode { None, Normal, ButtonEvent, AnyEvent }
enum MouseEncodingMode { Default, SGR }

See the API reference for full signatures, remarks, and exception contracts on every member.

Best Practices

  • Use the terminal frontend’s drag handlers rather than the manager directly when you have a terminal. It already owns a selection manager and wires the visuals.
  • Gate your copy action on HasSelection. A copy command that silently does nothing is worse than one that is visibly unavailable.
  • Clear the selection after acting on it. A highlight that outlives the action it triggered reads as a stuck state.
  • Prefer SGR encoding when hosting. The default encoding cannot express a coordinate past 223, and a modern grid exceeds that easily.
  • Decide the modifier before you host mouse-aware programs. The conflict between program tracking and player selection is inherent; the only question is whether you resolved it deliberately.
  • Turn selection off for diegetic displays. If the screen is a prop rather than an interface, being able to highlight it is a bug in the fiction.
  • Do not poll the tracking mode to decide what your game does with clicks. Ask the emulator once when it changes; the mode is set by escape sequence, not by frame.
  • Remember highlighting is an attribute, not a redraw. Selecting a large region costs a pass over the cells to set flags, and nothing per frame after that.
  • Convert pointer positions through the view. ScreenPointToCell accounts for the transform, the canvas scale factor and the camera, which hand-rolled math does not.
  • Treat extracted text as untrusted if it came from a hosted program. It is whatever that program drew, which is not necessarily what you would have written.

Pitfalls