onUpdate method
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();
});