onUpdate method

  1. @override
  2. @mustCallSuper
void onUpdate(
  1. double dt
)
override

Override to react during the update phase each frame.

Called after all registered listenOnUpdate listeners.

Implementation

@override
@mustCallSuper
void onUpdate(double dt) => on2<CTransform<T>, CRectCollider<T>>((t, c) {
  t.position = worldPosition;
  c.size = size.copy();

  final RectangleD rect = .rect(t.position.x, t.position.y, size.x, size.y);
  _hovered = backend.collision.pointRectangle(backend.mouse.position, rect);

  // focus on click
  if (_hovered && backend.mouse.btnLeft.pressed) {
    isFocused = true;
    final localX = backend.mouse.position.x - t.position.x;
    _cursorIndex = _charIndexAtX(localX);
    _selectionAnchor = null;
    _cursorBlinkTimer = 0;
    _cursorVisible = true;
  } else if (!_hovered && backend.mouse.btnLeft.pressed) {
    isFocused = false;
  }

  if (!isFocused || isWidgetDisabled) return;

  // cursor blink
  _cursorBlinkTimer += dt;
  if (_cursorBlinkTimer >= _cursorBlinkRate) {
    _cursorBlinkTimer = 0;
    _cursorVisible = !_cursorVisible;
  }

  // ── keyboard input ────────────────────────────────────────────────────
  final shift =
    backend.input.isKeyDown(.KEY_LEFT_SHIFT) ||
    backend.input.isKeyDown(.KEY_RIGHT_SHIFT);
  final ctrl =
    backend.input.isKeyDown(.KEY_LEFT_CONTROL) ||
    backend.input.isKeyDown(.KEY_RIGHT_CONTROL);

  // printable characters
  var charCodes = input.keycodes();
  while (charCodes.isNotEmpty) {
    final key = charCodes.removeAt(0);

    if (maxLength == null || _text.length < maxLength!) {
      if (_hasSelection) {
        final (lo, hi) = _selectionRange;
        _deleteRange(lo, hi);
      }
      _insertAt(_cursorIndex, String.fromCharCode(key));
      _cursorBlinkTimer = 0;
      _cursorVisible = true;
    }
  }

  bool isKeyPressedOrRepeat(KeyboardKey key)
    => backend.input.isKeyPressed(key) || backend.input.isKeyPressed(key);

  // control keys
  if (isKeyPressedOrRepeat(.KEY_BACKSPACE)) {
    if (_hasSelection) {
      final (lo, hi) = _selectionRange;
      _deleteRange(lo, hi);
    } else if (_cursorIndex > 0) {
      if (ctrl) {
        // delete word to the left
        int i = _cursorIndex - 1;
        while (i > 0 && _text[i - 1] == ' ') i--;
        while (i > 0 && _text[i - 1] != ' ') i--;
        _deleteRange(i, _cursorIndex);
      } else {
        _deleteRange(_cursorIndex - 1, _cursorIndex);
      }
    }
  }

  if (isKeyPressedOrRepeat(.KEY_DELETE)) {
    if (_hasSelection) {
      final (lo, hi) = _selectionRange;
      _deleteRange(lo, hi);
    } else if (_cursorIndex < _text.length) {
      _deleteRange(_cursorIndex, _cursorIndex + 1);
    }
  }

  if (isKeyPressedOrRepeat(.KEY_LEFT)) {
    if (ctrl) {
      // jump word left
      int i = _cursorIndex;
      while (i > 0 && _text[i - 1] == ' ') i--;
      while (i > 0 && _text[i - 1] != ' ') i--;
      if (shift) { _selectionAnchor ??= _cursorIndex; } else { _selectionAnchor = null; }
      _cursorIndex = i;
    } else {
      _moveCursor(-1, selecting: shift);
    }
    _cursorBlinkTimer = 0; _cursorVisible = true;
  }

  if (isKeyPressedOrRepeat(.KEY_RIGHT)) {
    if (ctrl) {
      int i = _cursorIndex;
      while (i < _text.length && _text[i] == ' ') i++;
      while (i < _text.length && _text[i] != ' ') i++;
      if (shift) { _selectionAnchor ??= _cursorIndex; } else { _selectionAnchor = null; }
      _cursorIndex = i;
    } else {
      _moveCursor(1, selecting: shift);
    }
    _cursorBlinkTimer = 0; _cursorVisible = true;
  }

  if (backend.input.isKeyPressed(.KEY_HOME)) {
    if (shift) { _selectionAnchor ??= _cursorIndex; } else { _selectionAnchor = null; }
    _cursorIndex = 0;
  }

  if (backend.input.isKeyPressed(.KEY_END)) {
    if (shift) { _selectionAnchor ??= _cursorIndex; } else { _selectionAnchor = null; }
    _cursorIndex = _text.length;
  }

  if (ctrl && backend.input.isKeyPressed(.KEY_A)) {
    _selectionAnchor = 0;
    _cursorIndex = _text.length;
  }

  if (ctrl && backend.input.isKeyPressed(.KEY_C)) {
    if (_hasSelection) {
      final (lo, hi) = _selectionRange;
      backend.setClipboardText(_text.substring(lo, hi));
    }
  }

  if (ctrl && backend.input.isKeyPressed(.KEY_X)) {
    if (_hasSelection) {
      final (lo, hi) = _selectionRange;
      backend.setClipboardText(_text.substring(lo, hi));
      _deleteRange(lo, hi);
    }
  }

  if (ctrl && backend.input.isKeyPressed(.KEY_V)) {
    final clip = getClipboardTextFn?.call() ?? backend.getClipboardText();
    if (clip.isNotEmpty) {
      if (_hasSelection) {
        final (lo, hi) = _selectionRange;
        _deleteRange(lo, hi);
      }
      final toInsert = maxLength != null
          ? clip.substring(0, (maxLength! - _text.length).clamp(0, clip.length))
          : clip;
      _insertAt(_cursorIndex, toInsert);
    }
  }

  if (
    backend.input.isKeyPressed(.KEY_ENTER) ||
    backend.input.isKeyPressed(.KEY_KP_ENTER)
  ) {
    onSubmitFn?.call(this, _text);
    isFocused = false;
  }

  if (backend.input.isKeyPressed(.KEY_ESCAPE)) {
    isFocused = false;
  }

  _clampScrollToCursor();
});