moveDown function
NavigationResult
moveDown(
- Root root,
- CaretStop current,
- double preferredX,
- CaretXResolver resolveX,
- CaretYResolver resolveY, {
- List<
CaretStop> ? stops, - List<
CaretStop> ? allStops,
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,
}) {
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);
// Find the lowest y that is strictly below the current line
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) {
// Use full document stop list for cross-node fallback so we are not
// limited to the candidate subset passed by the caller.
final docStops = allStops ?? stops_;
if (_isInLastNode(root, current.fragmentId)) {
final last = docStops.last;
if (last == current) return NavigationResult.none;
return NavigationResult(position: last, preferredX: x);
}
// No line below in this node → jump to the first stop of the next
// top-level node that has caret stops (skip block nodes like HR).
final currentNodeId = _findTopLevelNodeId(root, current.fragmentId);
if (currentNodeId != null) {
final currentNodeIdx = 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 (_nodeContainsFragment(nextNode, stop.fragmentId)) {
return NavigationResult(position: stop, preferredX: x);
}
}
}
}
// Nothing below → go to the end of the document
final last = docStops.last;
if (last == current) return NavigationResult.none;
return NavigationResult(position: last, preferredX: x);
}
final lineStops = stops_
.where((s) => (cachedY(s) - targetY!).abs() <= _kLineYTolerance)
.toList();
final best = _stopNearestX(lineStops, x, cachedX);
return NavigationResult(position: best, preferredX: x);
}