placeTooltip function
- required Rect screen,
- required Rect hole,
- required TooltipPosition position,
- required Size size,
- required EdgeInsets safeArea,
- required List<
Rect> avoid, - double gap = _kGap,
Pure placement core, shared by the single and multi delegates: the offset
for a tooltip of size around hole, picking the best fitting side from
position's order (preferred → mirrored → orthogonal; auto — by free
space) and vetoing placements that overlap avoid or leave the safe
rect.
Anchor off the safe rect (the target is off screen — autoScroll is still scrolling it in, or the page was scrolled away): no side can fit, so the preferred side's offset is clamped into the safe rect instead — the tooltip stays anchored to the direction the target comes from (never a screen corner that has nothing to do with it) and joins the side placement continuously as the target arrives.
avoid must include the tooltip's own anchor hole — a tooltip never
covers a spotlighted target. For multi-content slots the callers add
the already-placed tooltip rects, so slots never overlap each other.
Implementation
Offset placeTooltip({
required Rect screen,
required Rect hole,
required TooltipPosition position,
required Size size,
required EdgeInsets safeArea,
required List<Rect> avoid,
double gap = _kGap,
}) {
final safe = _safeRectOf(screen, safeArea);
for (final side in _sideOrder(position, safe, hole)) {
final offset = _offsetFor(side, size, hole, safe, gap);
if (_fits(offset, size, safe, avoid)) return offset;
}
// No side fit and the anchor itself is off the safe rect — autoScroll is
// still bringing the target in, or the page was scrolled away from it. The
// corner chain below would park the tooltip in a screen corner that has no
// relation to the target (the "top-left flash" with a per-step
// `autoScroll`). Stay on the target's preferred side instead, clamped into
// the safe rect: the tooltip waits at the screen edge the target is coming
// from and slides onto it, and the clamp joins the side placement above
// continuously on the way in.
if (!hole.overlaps(safe)) {
for (final side in _sideOrder(position, safe, hole)) {
final clamped =
_clampIntoSafe(_offsetFor(side, size, hole, safe, gap), size, safe);
// `avoid` still wins: a multi slot gives up its preferred side rather
// than overlapping an already-placed tooltip.
if (_fits(clamped, size, safe, avoid)) return clamped;
}
}
// No side fit while the anchor IS on screen (tooltip/hole larger than the
// safe rect, or every side is blocked) — try the safe-rect corners with a
// margin (a multi slot does not land on an already-placed tooltip when a
// corner is free); the last resort is the top-left corner (may still
// overflow the safe rect bottom/right — nothing fits; the margin keeps it
// as close as the degenerate case allows).
final corners = [
Offset(safe.left + _fallbackPadding, safe.top + _fallbackPadding),
Offset(
safe.right - _fallbackPadding - size.width,
safe.top + _fallbackPadding,
),
Offset(
safe.left + _fallbackPadding,
safe.bottom - _fallbackPadding - size.height,
),
Offset(
safe.right - _fallbackPadding - size.width,
safe.bottom - _fallbackPadding - size.height,
),
];
for (final corner in corners) {
if (_fits(corner, size, safe, avoid)) return corner;
}
return corners.first;
}