buildFluentSplitButton function

Widget buildFluentSplitButton(
  1. FluentSplitButtonBaseState state,
  2. FluentSplitButtonStyle style,
  3. Set<WidgetState> states, {
  4. required FluentSplitButtonSide side,
})

Renders one half of a split button from a resolved state and style.

One half rather than the pair, because the halves are siblings with independent interaction states: nesting one interaction surface inside the other would make hovering the chevron light up the primary action too. FluentSplitButton calls this twice, once per side, and lays the results out in a row.

Takes FluentSplitButtonBaseState rather than FluentSplitButtonState on purpose: it never reads appearance, size or shape, so a consumer can supply their own style and still use Fluent's rendering.

states is the live interaction set of this half from FluentInteractive.

Implementation

Widget buildFluentSplitButton(
  FluentSplitButtonBaseState state,
  FluentSplitButtonStyle style,
  Set<WidgetState> states, {
  required FluentSplitButtonSide side,
}) => Builder(
  // The reading direction is read here rather than taken as an argument: the
  // pair mirrors under RTL and every caller would otherwise have to know that.
  builder: (BuildContext context) {
    final roundsLeft = _roundsLeft(side, Directionality.of(context));
    final button = style.button ?? const FluentButtonStyle();
    final radius =
        button.borderRadius?.resolve(states) ?? FluentRadius.allMedium;
    final borderWidth =
        button.borderWidth?.resolve(states) ?? FluentStroke.none;
    // Upstream's rule is transparent rather than absent on enabled subtle and
    // transparent, so it fades in with the border transition when the half is
    // disabled; standing null in as transparent does the same, and keeps the
    // painter — and so the half's subtree — mounted across the change.
    const clear = Color(0x00000000);
    final borderColor = button.borderColor?.resolve(states) ?? clear;
    final dividerColor = style.dividerColor?.resolve(states) ?? clear;

    // Each half keeps only the two corners on its own outer edge. The inner
    // edge is square, which is what makes the pair read as one container.
    final halfRadius = roundsLeft
        ? BorderRadius.only(
            topLeft: radius.topLeft,
            bottomLeft: radius.bottomLeft,
          )
        : BorderRadius.only(
            topRight: radius.topRight,
            bottomRight: radius.bottomRight,
          );

    // The border is painted by FluentSplitButtonEdgePainter, not by the half's
    // own decoration: it is painted together with the rule between the halves,
    // whose colour is styled on its own, and a Border under a border radius
    // accepts only one visible colour.
    final half = buildFluentButton(
      state.half(side),
      button.copyWith(
        borderRadius: WidgetStatePropertyAll<BorderRadius?>(halfRadius),
        borderWidth: const WidgetStatePropertyAll<double?>(FluentStroke.none),
      ),
      states,
    );

    return FluentAnimatedStyle<FluentSplitButtonEdgeColors>(
      // Upstream transitions `border` alongside `background` and `color` on the
      // button root, at the same duration and curve — the divider is part of
      // that border, so it tweens with the surface rather than snapping.
      value: FluentSplitButtonEdgeColors(
        border: borderColor,
        divider: dividerColor,
      ),
      spec: FluentMotionSpec.buttonSurface,
      lerp: FluentSplitButtonEdgeColors.lerp,
      builder: (_, colors) => CustomPaint(
        foregroundPainter: FluentSplitButtonEdgePainter(
          side: side,
          borderColor: colors.border,
          borderWidth: borderWidth,
          dividerColor: colors.divider,
          radius: halfRadius,
          roundsLeft: roundsLeft,
        ),
        child: half,
      ),
    );
  },
);