update method
Call each frame with the currently gazed node ID (or null).
Set dwellEnabled to false while another input source suppresses auto
selection. Hover and explicit taps remain available, but dwell progress
is discarded. Re-enabling starts a fresh dwell even on the same target.
Implementation
void update(double dt, String? hitNodeId, {bool dwellEnabled = true}) {
if (!dwellEnabled) {
_gazeTimer = 0;
_selected = false;
_graceSavedGazeTimer = 0;
dwellProgress = 0;
final target = _gazeTargetId;
if (target != null) onDwellProgress?.call(target, 0);
}
// Grace period: brief loss of the target preserves the dwell timer.
if (hitNodeId == null && _gazeTargetId != null && !_selected) {
if (_graceTargetId != _gazeTargetId) {
_graceTargetId = _gazeTargetId;
_graceTimer = 0;
_graceSavedGazeTimer = _gazeTimer;
}
_graceTimer += dt;
if (_graceTimer < gazeGracePeriod) {
// Hold progress steady during the grace window.
return;
}
}
if (hitNodeId != _gazeTargetId) {
// Gaze changed target
if (_gazeTargetId != null) onGazeExit?.call(_gazeTargetId!);
// Restoring the same target within the grace window resumes the timer.
final resumed =
hitNodeId != null &&
hitNodeId == _graceTargetId &&
_graceTimer < gazeGracePeriod;
_gazeTargetId = hitNodeId;
_gazeTimer = resumed ? _graceSavedGazeTimer : 0;
_selected = false;
dwellProgress = resumed
? (_gazeTimer / _effectiveDwell).clamp(0.0, 1.0)
: 0;
_graceTargetId = null;
_graceTimer = 0;
if (hitNodeId != null) {
if (enableHaptics) {
HapticFeedback.selectionClick();
}
onGazeEnter?.call(hitNodeId);
}
return;
}
if (hitNodeId == null) {
dwellProgress = 0;
return;
}
_graceTargetId = null;
_graceTimer = 0;
if (!dwellEnabled) return;
_gazeTimer += dt;
dwellProgress = (_gazeTimer / _effectiveDwell).clamp(0.0, 1.0);
onDwellProgress?.call(hitNodeId, dwellProgress);
if (dwellProgress >= 1.0 && !_selected) {
_selected = true;
_applyAdaptation(hitNodeId);
if (enableHaptics) {
HapticFeedback.lightImpact();
}
onDwellSelect?.call(hitNodeId);
}
}