Skip to content

LayrzTextSelectionControls

Kenny Mochizuki Escalona edited this page Aug 21, 2026 · 2 revisions

LayrzTextSelectionControls

A Material-free text selection controls implementation providing selection handles and context menu integration for LayrzTextInput and other editable text fields.

Metadata
Domain: Selection
Phase: M2 (Selection framework)
Primitive: TextSelectionControls, TextSelectionHandleControls
Status: Shipped in 0.0.12.


Overview

LayrzTextSelectionControls implements the Flutter TextSelectionControls interface to provide styled selection handles (for touch drag-to-select) and a Material-free selection toolbar integration. It is a singleton to prevent the EditableText widget from recreating the selection overlay on every rebuild.

Why a Singleton?

EditableText.didUpdateWidget disposes and recreates the selection overlay whenever the selectionControls parameter differs between builds. If a new instance is passed on every rebuild, the overlay flickers and the selection is lost. By using a singleton instance, this is avoided: all EditableText widgets in the app share the same controls, and the overlay survives rebuilds.

Tokens are accessed from BuildContext at render time, so theme changes are reflected automatically without recreating the instance.


API Reference

class LayrzTextSelectionControls extends TextSelectionControls 
    with TextSelectionHandleControls {
  /// Returns the singleton instance of [LayrzTextSelectionControls].
  static LayrzTextSelectionControls get instance => _instance;

  /// Creates an instance (private; use [instance] getter instead).
  LayrzTextSelectionControls._internal();
}

Usage

Assign the singleton to an editable field's selectionControls parameter:

EditableText(
  controller: myController,
  focusNode: myFocusNode,
  style: myTextStyle,
  cursorColor: Colors.blue,
  backgroundCursorColor: Colors.grey,
  selectionControls: LayrzTextSelectionControls.instance,  // ← Use singleton
  // ... other parameters
)

For LayrzTextInput, this is already integrated — no additional configuration needed.


Selection Handles

The controls render selection handles as teardrop-shaped glyphs that mark the start and end positions of a text selection.

Handle Styling

  • Color: tokens.colors.primary (brand color)
  • Size: 22 × 22 logical pixels
  • Shape: Teardrop (circle with a square corner in one quadrant)
  • Corner orientation: Rotated to point toward the selected text
    • Left handle (selection start): rotated 90° clockwise → corner points up-right (NE)
    • Right handle (selection end): no rotation → corner points up-left (NW)
    • Collapsed cursor (single caret): rotated 45° clockwise → corner points up (N)

Critical: Handle Rotation Detail

The unrotated teardrop corner is at the top-left (NW). Transform.rotate uses clockwise rotation for positive angles in Flutter. This mapping is exact:

// left: π/2 radians = 90° → corner at NE
Transform.rotate(angle: math.pi / 2.0, child: handle)

// right: 0 radians = 0° → corner at NW (unrotated)
handle

// collapsed: π/4 radians = 45° → corner at N
Transform.rotate(angle: math.pi / 4.0, child: handle)

This exact geometry was determined during testing and is not arbitrary. Changing these angles will misalign the handles with the text selection endpoints.

Touch Interaction

  • Single tap: Places the caret at the tapped position (collapsed cursor)
  • Tap on a handle: Allows dragging the handle to resize the selection
  • Long-press + drag: Selects text by dragging a handle; magnifier appears on touch devices (see LayrzSelectionMagnifier)

Context Menu / Toolbar

The controls integrate with Flutter's TextSelectionHandleControls mixin, which means the toolbar is shown via EditableText.contextMenuBuilder rather than the deprecated buildToolbar method.

The toolbar displays actions from LayrzSelectableAction:

  • Built-in actions: Copy, Cut, Paste, Select All (shown as permitted by field state)
  • Custom actions: application-specific operations (e.g., "Translate", "Share")

The toolbar is rendered by LayrzSelectionToolbar, which applies the dark overlay surface treatment (fg1 background with sf1 content).


Equality and Hashing

The singleton overrides operator== and hashCode for identity equality:

@override
bool operator ==(Object other) {
  return identical(this, other) || other is LayrzTextSelectionControls;
}

@override
int get hashCode => 0; // Constant hash for singleton

This ensures that comparing any LayrzTextSelectionControls instance to any other always returns true (because all are the same singleton), and the hash is constant.


Technical Notes

  • Singleton stability: The one instance persists for the app's lifetime. Creating it multiple times or recreating it on rebuild will break overlay stability.
  • Theme integration: All visual properties (colors, sizes, spacing) are derived from context.tokens, allowing theme changes to be reflected at render time without recreating the instance.
  • Handle anchor calculation: The anchor point determines where the teardrop corner attaches to the text. For each handle type, the anchor is positioned precisely to align the corner with the selection edge, accounting for the 22×22 bounding box.
  • Accessibility: Selection handles are announced to screen readers; dragging is reported as a selection extension operation.

Related Components


Last updated: 2026-08-20
Related documents: LayrzTextInput, LayrzSelectionToolbar, Selection (D50)

Clone this wiki locally