moveUp function
NavigationResult
moveUp(
- 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 up by one LogicalLine.
preferredX is the x coordinate in pixels to maintain.
If it's -1.0, it's calculated from the current position via resolveX.
allStops is the full document stop list used for cross-node fallback.
Implementation
NavigationResult moveUp(
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);
// Single-pass: find targetY, then find nearest-X stop on that 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) {
final docStops = allStops ?? stops_;
if (_isInFirstNode(root, current.fragmentId,
parentResolver: parentResolver, containerResolver: containerResolver,
topLevelIndexResolver: topLevelIndexResolver)) {
final first = docStops.first;
if (first == current) return NavigationResult.none;
return NavigationResult(position: first, 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 >= 0; i--) {
final prevNode = root.nodes[i];
for (int j = docStops.length - 1; j >= 0; j--) {
if (_stopBelongsToNode(docStops[j], prevNode, root, containerResolver: containerResolver, parentResolver: parentResolver, topLevelIndexResolver: topLevelIndexResolver)) {
return NavigationResult(position: docStops[j], preferredX: x);
}
}
}
}
final first = docStops.first;
if (first == current) return NavigationResult.none;
return NavigationResult(position: first, 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);
}