moveDown function
NavigationResult
moveDown(
- Root root,
- CaretStop current,
- double preferredX,
- CaretXResolver resolveX,
- CaretYResolver resolveY, {
- List<
CaretStop> ? stops, - List<
CaretStop> ? allStops, - ParentResolver? parentResolver,
- ContainerResolver? containerResolver,
- TopLevelIndexResolver? topLevelIndexResolver,
Moves the cursor down by one LogicalLine.
allStops is the full document stop list used for cross-node fallback.
Implementation
NavigationResult moveDown(
Root root,
CaretStop current,
double preferredX,
CaretXResolver resolveX,
CaretYResolver resolveY, {
List<CaretStop>? stops,
List<CaretStop>? allStops,
ParentResolver? parentResolver,
ContainerResolver? containerResolver,
TopLevelIndexResolver? topLevelIndexResolver,
}) {
final stops_ = stops ?? buildAllStops(root);
if (stops_.isEmpty) return NavigationResult.none;
final yCache = <CaretStop, double>{};
double cachedY(CaretStop s) => yCache[s] ??= resolveY(s);
final xCache = <CaretStop, double>{};
double cachedX(CaretStop s) => xCache[s] ??= resolveX(s);
final x = preferredX >= 0.0 ? preferredX : cachedX(current);
final currentY = cachedY(current);
double? targetY;
for (final stop in stops_) {
final y = cachedY(stop);
if (y > currentY + _kLineYTolerance) {
if (targetY == null || y < targetY) targetY = y;
}
}
if (targetY == null) {
final docStops = allStops ?? stops_;
if (_isInLastNode(root, current.fragmentId,
parentResolver: parentResolver, containerResolver: containerResolver,
topLevelIndexResolver: topLevelIndexResolver)) {
final last = docStops.last;
if (last == current) return NavigationResult.none;
return NavigationResult(position: last, preferredX: x);
}
final currentNodeId = _findTopLevelNodeId(root, current.fragmentId,
parentResolver: parentResolver, containerResolver: containerResolver,
topLevelIndexResolver: topLevelIndexResolver);
if (currentNodeId != null) {
final currentNodeIdx = topLevelIndexResolver != null
? topLevelIndexResolver(currentNodeId)
: root.nodes.indexWhere((n) => n.id == currentNodeId);
for (int i = currentNodeIdx + 1; i < root.nodes.length; i++) {
final nextNode = root.nodes[i];
for (final stop in docStops) {
if (_stopBelongsToNode(stop, nextNode, root, containerResolver: containerResolver, parentResolver: parentResolver, topLevelIndexResolver: topLevelIndexResolver)) {
return NavigationResult(position: stop, preferredX: x);
}
}
}
}
final last = docStops.last;
if (last == current) return NavigationResult.none;
return NavigationResult(position: last, preferredX: x);
}
// Single-pass: find nearest-X stop on the target line without allocating.
CaretStop? best;
double bestDist = double.infinity;
for (final stop in stops_) {
if ((cachedY(stop) - targetY).abs() <= _kLineYTolerance) {
final dist = (cachedX(stop) - x).abs();
if (dist < bestDist) {
bestDist = dist;
best = stop;
}
}
}
if (best == null) return NavigationResult(position: current, preferredX: x);
return NavigationResult(position: best, preferredX: x);
}