computeLayout method

SearchablePillLayout computeLayout({
  1. required double totalW,
  2. required bool searching,
  3. required bool expandWhenActive,
  4. required double barHeight,
  5. required double searchBarHeight,
  6. required double spacing,
  7. required bool hasDismiss,
  8. required bool dismissVisible,
  9. required double? collapsedTabWidth,
  10. required GlassTabPillAnchor tabPillAnchor,
  11. required double extraFullW,
  12. required ExtraButtonPosition extraPos,
  13. required bool extraCollapsesOnSearch,
  14. required bool isKeyboardActive,
  15. required double keyboardH,
  16. required int tabCount,
  17. required double? perTabWidth,
})

Computes pill layout targets from the given constraints.

Pure function — no Flutter dependency, fully unit testable.

totalW — available width from LayoutBuilder.maxWidth. searching — whether search is currently active. barHeight — full tab-bar height (pixels). searchBarHeight — compact pill height when search is active. spacing — gap between adjacent pills. hasDismiss — whether the dismiss pill is configured. dismissVisible — whether the dismiss pill is currently shown. collapsedTabWidth — explicit collapsed tab width, or null → uses targetH. tabPillAnchor — start or center anchor mode. extraFullW — extra button's full size (0 if none). extraPos — whether extra button is before or after search pill. extraCollapsesOnSearch — whether extra button hides when focused. isKeyboardActive — _searchFocused && keyboardPresent. keyboardH — current keyboard height (for floatY). perTabWidth — width per tab slot when compact sizing is used; null means the tab pill fills all available space. tabCount — number of tabs; used with perTabWidth to compute natural pill width.

Implementation

SearchablePillLayout computeLayout({
  required double totalW,
  required bool searching,
  required bool expandWhenActive,
  required double barHeight,
  required double searchBarHeight,
  required double spacing,
  required bool hasDismiss,
  required bool dismissVisible,
  required double? collapsedTabWidth,
  required GlassTabPillAnchor tabPillAnchor,
  required double extraFullW,
  required ExtraButtonPosition extraPos,
  required bool extraCollapsesOnSearch,
  required bool isKeyboardActive,
  required double keyboardH,
  required int tabCount,
  required double? perTabWidth,
}) {
  final targetH = searching ? searchBarHeight : barHeight;

  // ── Extra button sizing ────────────────────────────────────────────────
  final extraTargetW = extraFullW > 0
      ? (searching ? math.min(extraFullW, targetH) : extraFullW)
      : 0.0;

  final extraWLeft =
      (extraFullW > 0 && extraPos == ExtraButtonPosition.beforeSearch)
          ? (extraTargetW + spacing)
          : 0.0;
  final extraWRight =
      (extraFullW > 0 && extraPos == ExtraButtonPosition.afterSearch)
          ? (extraTargetW + spacing)
          : 0.0;
  final extraFullWLeft =
      (extraFullW > 0 && extraPos == ExtraButtonPosition.beforeSearch)
          ? (extraFullW + spacing)
          : 0.0;
  final extraFullWRight =
      (extraFullW > 0 && extraPos == ExtraButtonPosition.afterSearch)
          ? (extraFullW + spacing)
          : 0.0;

  final doCollapseLayout = isKeyboardActive && extraCollapsesOnSearch;
  final curExtraWLeft = doCollapseLayout ? 0.0 : extraWLeft;
  final curExtraWRight = doCollapseLayout ? 0.0 : extraWRight;

  // ── Dismiss pill ───────────────────────────────────────────────────────
  final targetCompactW = targetH;
  final dismissReserve = hasDismiss ? (targetH + spacing) : 0.0;

  // maxTabW: maximum space the tab pill can ever occupy (when fully expanded).
  // Uses FULL (non-collapsed) extra widths for stability across states.
  final maxTabW =
      totalW - targetCompactW - spacing - extraFullWLeft - extraFullWRight;

  // naturalTabW: intrinsic width when perTabWidth is specified.
  // Delegates to the shared resolveTabPillWidth function, which is also
  // used by GlassBottomBar — single source of truth for this calculation.
  final naturalTabW = resolveTabPillWidth(
    tabWidth: perTabWidth,
    tabCount: tabCount,
    maxAvailable: maxTabW,
  );

  final targetTabW =
      !searching ? naturalTabW : (collapsedTabWidth ?? targetH);

  // ── Search pill ────────────────────────────────────────────────────────
  final centeredTab = tabPillAnchor == GlassTabPillAnchor.center;

  final targetSearchLeft = !searching || !expandWhenActive
      ? totalW - targetCompactW - extraWRight
      : isKeyboardActive
          ? curExtraWLeft
          : centeredTab
              ? (maxTabW + targetTabW) / 2 + curExtraWLeft + spacing
              : targetTabW + curExtraWLeft + spacing;

  final targetSearchW = !searching || !expandWhenActive
      ? targetCompactW
      : totalW -
          targetSearchLeft -
          curExtraWRight -
          (dismissVisible ? dismissReserve : 0.0);

  // ── Keyboard float ─────────────────────────────────────────────────────
  final floatY = (_searchFocused && keyboardH > 0) ? keyboardH : 0.0;

  return SearchablePillLayout(
    targetTabW: targetTabW,
    targetSearchLeft: targetSearchLeft,
    targetSearchW: targetSearchW,
    floatY: floatY,
    extraTargetW: extraTargetW,
    dismissReserve: dismissReserve,
  );
}