fitGlassNavStripGroups function

List<GlassNavBarGroup> fitGlassNavStripGroups(
  1. List<GlassNavBarGroup> groups,
  2. double available
)

Fits a strip's groups into available points of height.

Natively, a strip that runs out of room keeps what fits from the top and collapses the rest into a ••• menu at the end: whole groups while they fit, then as many items of the next as still do — split off into a group of their own, a lone one standing as a circle — and everything after that becomes an entry in the menu. A GlassBarItem.menu among them contributes its own entries.

Shared with GlassPinnedBarChrome's in-route strip; this library is not exported from the package barrel.

Implementation

List<GlassNavBarGroup> fitGlassNavStripGroups(
  List<GlassNavBarGroup> groups,
  double available,
) {
  double extentOf(int count, GlassBarItemBackground background) =>
      count == 1 || background != GlassBarItemBackground.shared
          ? count * GlassVerticalBarMetrics.controlExtent
          : count * GlassVerticalBarMetrics.itemExtent;

  var total = 0.0;
  for (final group in groups) {
    if (total > 0) total += GlassVerticalBarMetrics.spacing;
    total += extentOf(group.items.length, group.background);
  }
  if (total <= available) return groups;

  // Room for everything that stays, with the ••• circle held back at the end.
  final budget = available -
      GlassVerticalBarMetrics.controlExtent -
      GlassVerticalBarMetrics.spacing;
  final kept = <GlassNavBarGroup>[];
  final overflow = <GlassBarActionItem>[];
  var used = 0.0;
  for (final group in groups) {
    if (overflow.isNotEmpty) {
      overflow.addAll(group.items);
      continue;
    }
    final gap = kept.isEmpty ? 0.0 : GlassVerticalBarMetrics.spacing;
    final extent = extentOf(group.items.length, group.background);
    if (used + gap + extent <= budget) {
      kept.add(group);
      used += gap + extent;
      continue;
    }
    var fits = 0;
    while (fits < group.items.length &&
        used + gap + extentOf(fits + 1, group.background) <= budget) {
      fits++;
    }
    if (fits > 0) {
      kept.add(GlassNavBarGroup(
        items: group.items.sublist(0, fits),
        background:
            fits == 1 ? GlassBarItemBackground.separate : group.background,
        axis: Axis.vertical,
      ));
      used += gap + extentOf(fits, group.background);
    }
    overflow.addAll(group.items.sublist(fits));
  }

  final entries = <Widget>[];
  for (final item in overflow) {
    if (item is GlassBarMenuItem) {
      if (entries.isNotEmpty) entries.add(const GlassMenuDivider());
      entries.addAll(item.menuItems);
      continue;
    }
    if (!item.hasTap) continue;
    entries.add(GlassMenuItem(
      title: item.label ?? '',
      icon: switch (item) {
        GlassBarIconItem(:final icon) => icon,
        GlassBarSheetItem(:final icon) => icon,
        _ => null,
      },
      enabled: item.enabled,
      onTap:
          item is GlassBarSheetItem ? () => item.onPresent(null) : item.onTap,
    ));
  }
  if (entries.isEmpty) return kept;
  return [
    ...kept,
    GlassNavBarGroup(
      items: [
        GlassBarMenuItem(
          icon: const Icon(CupertinoIcons.ellipsis),
          menuItems: entries,
          id: _overflowItemId,
          background: GlassBarItemBackground.separate,
        ),
      ],
      background: GlassBarItemBackground.separate,
      axis: Axis.vertical,
    ),
  ];
}