// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:math' as math;

import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'button_style.dart';
import 'color_scheme.dart';
import 'colors.dart';
import 'icons.dart';
import 'material.dart';
import 'material_state.dart';
import 'segmented_button_theme.dart';
import 'text_button.dart';
import 'text_button_theme.dart';
import 'theme.dart';
import 'tooltip.dart';

/// Data describing a segment of a [SegmentedButton].
class ButtonSegment<T> {
  /// Construct a SegmentData
  ///
  /// One of [icon] or [label] must be non-null.
  const ButtonSegment({
    required this.value,
    this.icon,
    this.label,
    this.tooltip,
    this.enabled = true,
  }) : assert(icon != null || label != null);

  /// Value used to identify the segment.
  ///
  /// This value must be unique across all segments in a [SegmentedButton].
  final T value;

  /// Optional icon displayed in the segment.
  final Widget? icon;

  /// Optional label displayed in the segment.
  final Widget? label;

  /// Optional tooltip for the segment
  final String? tooltip;

  /// Determines if the segment is available for selection.
  final bool enabled;
}

/// A Material button that allows the user to select from limited set of options.
///
/// Segmented buttons are used to help people select options, switch views, or
/// sort elements. They are typically used in cases where there are only 2-5
/// options.
///
/// The options are represented by segments described with [ButtonSegment]
/// entries in the [segments] field. Each segment has a [ButtonSegment.value]
/// that is used to indicate which segments are selected.
///
/// The [selected] field is a set of selected [ButtonSegment.value]s. This
/// should be updated by the app in response to [onSelectionChanged] updates.
///
/// By default, only a single segment can be selected (for mutually exclusive
/// choices). This can be relaxed with the [multiSelectionEnabled] field.
///
/// Like [ButtonStyleButton]s, the [SegmentedButton]'s visuals can be
/// configured with a [ButtonStyle] [style] field. However, unlike other
/// buttons, some of the style parameters are applied to the entire segmented
/// button, and others are used for each of the segments.
///
/// By default, a checkmark icon is used to show selected items. To configure
/// this behavior, you can use the [showSelectedIcon] and [selectedIcon] fields.
///
/// Individual segments can be enabled or disabled with their
/// [ButtonSegment.enabled] flag. If the [onSelectionChanged] field is null,
/// then the entire segmented button will be disabled, regardless of the
/// individual segment settings.
///
/// {@tool dartpad}
/// This sample shows how to display a [SegmentedButton] with either a single or
/// multiple selection.
///
/// ** See code in examples/api/lib/material/segmented_button/segmented_button.0.dart **
/// {@end-tool}
///
/// See also:
///
///   * Material Design spec: <https://m3.material.io/components/segmented-buttons/overview>
///   * [ButtonStyle], which can be used in the [style] field to configure
///     the appearance of the button and its segments.
///   * [ToggleButtons], a similar widget that was built for Material 2.
///     [SegmentedButton] should be considered as a replacement for
///     [ToggleButtons].
///   * [Radio], an alternative way to present the user with a mutually exclusive set of options.
///   * [FilterChip], [ChoiceChip], which can be used when you need to show more than five options.
class SegmentedButton<T> extends StatelessWidget {
  /// Creates a const [SegmentedButton].
  ///
  /// [segments] must contain at least one segment, but it is recommended
  /// to have two to five segments. If you need only single segment,
  /// consider using a [Checkbox] or [Radio] widget instead. If you need
  /// more than five options, consider using [FilterChip] or [ChoiceChip]
  /// widgets.
  ///
  /// If [onSelectionChanged] is null, then the entire segmented button will
  /// be disabled.
  ///
  /// By default [selected] must only contain one entry. However, if
  /// [multiSelectionEnabled] is true, then [selected] can contain multiple
  /// entries. If [emptySelectionAllowed] is true, then [selected] can be empty.
  const SegmentedButton({
    super.key,
    required this.segments,
    required this.selected,
    this.onSelectionChanged,
    this.multiSelectionEnabled = false,
    this.emptySelectionAllowed = false,
    this.style,
    this.showSelectedIcon = true,
    this.selectedIcon,
  })  : assert(segments.length > 0),
        assert(selected.length > 0 || emptySelectionAllowed),
        assert(selected.length < 2 || multiSelectionEnabled);

  /// Descriptions of the segments in the button.
  ///
  /// This a required parameter and must contain at least one segment,
  /// but it is recommended to contain two to five segments. If you need only
  /// a single segment, consider using a [Checkbox] or [Radio] widget instead.
  /// If you need more than five options, consider using [FilterChip] or
  /// [ChoiceChip] widgets.
  final List<ButtonSegment<T>> segments;

  /// The set of [ButtonSegment.value]s that indicate which [segments] are
  /// selected.
  ///
  /// As the [SegmentedButton] does not maintain the state of the selection,
  /// you will need to update this in response to [onSelectionChanged] calls.
  ///
  /// This is a required parameter.
  final Set<T> selected;

  /// The function that is called when the selection changes.
  ///
  /// The callback's parameter indicates which of the segments are selected.
  ///
  /// When the callback is null, the entire [SegmentedButton] is disabled,
  /// and will not respond to input.
  ///
  /// The default is null.
  final void Function(Set<T>)? onSelectionChanged;

  /// Determines if multiple segments can be selected at one time.
  ///
  /// If true, more than one segment can be selected. When selecting a
  /// segment, the other selected segments will stay selected. Selecting an
  /// already selected segment will unselect it.
  ///
  /// If false, only one segment may be selected at a time. When a segment
  /// is selected, any previously selected segment will be unselected.
  ///
  /// The default is false, so only a single segement may be selected at one
  /// time.
  final bool multiSelectionEnabled;

  /// Determines if having no selected segments is allowed.
  ///
  /// If true, then it is acceptable for none of the segments to be selected.
  /// This means that [selected] can be empty. If the user taps on a
  /// selected segment, it will be removed from the selection set passed into
  /// [onSelectionChanged].
  ///
  /// If false (the default), there must be at least one segment selected. If
  /// the user taps on the only selected segment it will not be deselected, and
  /// [onSelectionChanged] will not be called.
  final bool emptySelectionAllowed;

  /// Customizes this button's appearance.
  ///
  /// The following style properties apply to the entire segmented button:
  ///
  ///   * [ButtonStyle.shadowColor]
  ///   * [ButtonStyle.elevation]
  ///   * [ButtonStyle.side] - which is used for both the outer shape and
  ///     dividers between segments.
  ///   * [ButtonStyle.shape]
  ///
  /// The following style properties are applied to each of the individual
  /// button segments. For properties that are a [MaterialStateProperty],
  /// they will be resolved with the current state of the segment:
  ///
  ///   * [ButtonStyle.textStyle]
  ///   * [ButtonStyle.backgroundColor]
  ///   * [ButtonStyle.foregroundColor]
  ///   * [ButtonStyle.overlayColor]
  ///   * [ButtonStyle.surfaceTintColor]
  ///   * [ButtonStyle.elevation]
  ///   * [ButtonStyle.padding]
  ///   * [ButtonStyle.iconColor]
  ///   * [ButtonStyle.iconSize]
  ///   * [ButtonStyle.mouseCursor]
  ///   * [ButtonStyle.visualDensity]
  ///   * [ButtonStyle.tapTargetSize]
  ///   * [ButtonStyle.animationDuration]
  ///   * [ButtonStyle.enableFeedback]
  ///   * [ButtonStyle.alignment]
  ///   * [ButtonStyle.splashFactory]
  final ButtonStyle? style;

  /// Determines if the [selectedIcon] (usually an icon using [Icons.check])
  /// is displayed on the selected segments.
  ///
  /// If true, the [selectedIcon] will be displayed at the start of the segment.
  /// If both the [ButtonSegment.label] and [ButtonSegment.icon] are provided,
  /// then the icon will be replaced with the [selectedIcon]. If only the icon
  /// or the label is present then the [selectedIcon] will be shown at the start
  /// of the segment.
  ///
  /// If false, then the [selectedIcon] is not used and will not be displayed
  /// on selected segments.
  ///
  /// The default is true, meaning the [selectedIcon] will be shown on selected
  /// segments.
  final bool showSelectedIcon;

  /// An icon that is used to indicate a segment is selected.
  ///
  /// If [showSelectedIcon] is true then for selected segments this icon
  /// will be shown before the [ButtonSegment.label], replacing the
  /// [ButtonSegment.icon] if it is specified.
  ///
  /// Defaults to an [Icon] with [Icons.check].
  final Widget? selectedIcon;

  bool get _enabled => onSelectionChanged != null;

  void _handleOnPressed(T segmentValue) {
    if (!_enabled) {
      return;
    }
    final bool onlySelectedSegment = selected.length == 1 && selected.contains(segmentValue);
    final bool validChange = emptySelectionAllowed || !onlySelectedSegment;
    if (validChange) {
      final bool toggle = multiSelectionEnabled || (emptySelectionAllowed && onlySelectedSegment);
      final Set<T> pressedSegment = <T>{segmentValue};
      late final Set<T> updatedSelection;
      if (toggle) {
        updatedSelection = selected.contains(segmentValue)
          ? selected.difference(pressedSegment)
          : selected.union(pressedSegment);
      } else {
        updatedSelection = pressedSegment;
      }
      if (!setEquals(updatedSelection, selected)) {
        onSelectionChanged!(updatedSelection);
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    final SegmentedButtonThemeData theme = SegmentedButtonTheme.of(context);
    final SegmentedButtonThemeData defaults = _SegmentedButtonDefaultsM3(context);
    final TextDirection direction = Directionality.of(context);

    const Set<MaterialState> enabledState = <MaterialState>{};
    const Set<MaterialState> disabledState = <MaterialState>{ MaterialState.disabled };
    final Set<MaterialState> currentState = _enabled ? enabledState : disabledState;

    P? effectiveValue<P>(P? Function(ButtonStyle? style) getProperty) {
      late final P? widgetValue  = getProperty(style);
      late final P? themeValue   = getProperty(theme.style);
      late final P? defaultValue = getProperty(defaults.style);
      return widgetValue ?? themeValue ?? defaultValue;
    }

    P? resolve<P>(MaterialStateProperty<P>? Function(ButtonStyle? style) getProperty, [Set<MaterialState>? states]) {
      return effectiveValue(
        (ButtonStyle? style) => getProperty(style)?.resolve(states ?? currentState),
      );
    }

    ButtonStyle segmentStyleFor(ButtonStyle? style) {
      return ButtonStyle(
        textStyle: style?.textStyle,
        backgroundColor: style?.backgroundColor,
        foregroundColor: style?.foregroundColor,
        overlayColor: style?.overlayColor,
        surfaceTintColor: style?.surfaceTintColor,
        elevation: style?.elevation,
        padding: style?.padding,
        iconColor: style?.iconColor,
        iconSize: style?.iconSize,
        shape: const MaterialStatePropertyAll<OutlinedBorder>(RoundedRectangleBorder()),
        mouseCursor: style?.mouseCursor,
        visualDensity: style?.visualDensity,
        tapTargetSize: style?.tapTargetSize,
        animationDuration: style?.animationDuration,
        enableFeedback: style?.enableFeedback,
        alignment: style?.alignment,
        splashFactory: style?.splashFactory,
      );
    }

    final ButtonStyle segmentStyle = segmentStyleFor(style);
    final ButtonStyle segmentThemeStyle = segmentStyleFor(theme.style).merge(segmentStyleFor(defaults.style));
    final Widget? selectedIcon = showSelectedIcon
      ? this.selectedIcon ?? theme.selectedIcon ?? defaults.selectedIcon
      : null;

    Widget buttonFor(ButtonSegment<T> segment) {
      final Widget label = segment.label ?? segment.icon ?? const SizedBox.shrink();
      final bool segmentSelected = selected.contains(segment.value);
      final Widget? icon = (segmentSelected && showSelectedIcon)
        ? selectedIcon
        : segment.label != null
          ? segment.icon
          : null;
      final MaterialStatesController controller = MaterialStatesController(
        <MaterialState>{
          if (segmentSelected) MaterialState.selected,
        }
      );

      final Widget button = icon != null
        ? TextButton.icon(
            style: segmentStyle,
            statesController: controller,
            onPressed: (_enabled && segment.enabled) ? () => _handleOnPressed(segment.value) : null,
            icon: icon,
            label: label,
          )
        : TextButton(
            style: segmentStyle,
            statesController: controller,
            onPressed: (_enabled && segment.enabled) ? () => _handleOnPressed(segment.value) : null,
            child: label,
          );

      final Widget buttonWithTooltip = segment.tooltip != null
        ? Tooltip(
            message: segment.tooltip,
            child: button,
          )
        : button;

      return MergeSemantics(
        child: Semantics(
          checked: segmentSelected,
          inMutuallyExclusiveGroup: multiSelectionEnabled ? null : true,
          child: buttonWithTooltip,
        ),
      );
    }

    final OutlinedBorder resolvedEnabledBorder = resolve<OutlinedBorder?>((ButtonStyle? style) => style?.shape, disabledState) ?? const RoundedRectangleBorder();
    final OutlinedBorder resolvedDisabledBorder = resolve<OutlinedBorder?>((ButtonStyle? style) => style?.shape, disabledState)?? const RoundedRectangleBorder();
    final BorderSide enabledSide = resolve<BorderSide?>((ButtonStyle? style) => style?.side, enabledState) ?? BorderSide.none;
    final BorderSide disabledSide = resolve<BorderSide?>((ButtonStyle? style) => style?.side, disabledState) ?? BorderSide.none;
    final OutlinedBorder enabledBorder = resolvedEnabledBorder.copyWith(side: enabledSide);
    final OutlinedBorder disabledBorder = resolvedDisabledBorder.copyWith(side: disabledSide);

    final List<Widget> buttons = segments.map(buttonFor).toList();

    return Material(
      type: MaterialType.transparency,
      shape: enabledBorder.copyWith(side: BorderSide.none),
      elevation: resolve<double?>((ButtonStyle? style) => style?.elevation)!,
      shadowColor: resolve<Color?>((ButtonStyle? style) => style?.shadowColor),
      surfaceTintColor: resolve<Color?>((ButtonStyle? style) => style?.surfaceTintColor),
      child: TextButtonTheme(
        data: TextButtonThemeData(style: segmentThemeStyle),
        child: _SegmentedButtonRenderWidget<T>(
          segments: segments,
          enabledBorder: _enabled ? enabledBorder : disabledBorder,
          disabledBorder: disabledBorder,
          direction: direction,
          children: buttons,
        ),
      ),
    );
  }
}
class _SegmentedButtonRenderWidget<T> extends MultiChildRenderObjectWidget {
  const _SegmentedButtonRenderWidget({
    super.key,
    required this.segments,
    required this.enabledBorder,
    required this.disabledBorder,
    required this.direction,
    required super.children,
  }) : assert(children.length == segments.length);

  final List<ButtonSegment<T>> segments;
  final OutlinedBorder enabledBorder;
  final OutlinedBorder disabledBorder;
  final TextDirection direction;

  @override
  RenderObject createRenderObject(BuildContext context) {
    return _RenderSegmentedButton<T>(
      segments: segments,
      enabledBorder: enabledBorder,
      disabledBorder: disabledBorder,
      textDirection: direction,
    );
  }

  @override
  void updateRenderObject(BuildContext context, _RenderSegmentedButton<T> renderObject) {
    renderObject
      ..segments = segments
      ..enabledBorder = enabledBorder
      ..disabledBorder = disabledBorder
      ..textDirection = direction;
  }
}

class _SegmentedButtonContainerBoxParentData extends ContainerBoxParentData<RenderBox> {
  RRect? surroundingRect;
}

typedef _NextChild = RenderBox? Function(RenderBox child);

class _RenderSegmentedButton<T> extends RenderBox with
     ContainerRenderObjectMixin<RenderBox, ContainerBoxParentData<RenderBox>>,
     RenderBoxContainerDefaultsMixin<RenderBox, ContainerBoxParentData<RenderBox>> {
  _RenderSegmentedButton({
    required List<ButtonSegment<T>> segments,
    required OutlinedBorder enabledBorder,
    required OutlinedBorder disabledBorder,
    required TextDirection textDirection,
  }) : _segments = segments,
       _enabledBorder = enabledBorder,
       _disabledBorder = disabledBorder,
       _textDirection = textDirection;

  List<ButtonSegment<T>> get segments => _segments;
  List<ButtonSegment<T>> _segments;
  set segments(List<ButtonSegment<T>> value) {
    if (listEquals(segments, value)) {
      return;
    }
    _segments = value;
    markNeedsLayout();
  }

  OutlinedBorder get enabledBorder => _enabledBorder;
  OutlinedBorder _enabledBorder;
  set enabledBorder(OutlinedBorder value) {
    if (_enabledBorder == value) {
      return;
    }
    _enabledBorder = value;
    markNeedsLayout();
  }

  OutlinedBorder get disabledBorder => _disabledBorder;
  OutlinedBorder _disabledBorder;
  set disabledBorder(OutlinedBorder value) {
    if (_disabledBorder == value) {
      return;
    }
    _disabledBorder = value;
    markNeedsLayout();
  }

  TextDirection get textDirection => _textDirection;
  TextDirection _textDirection;
  set textDirection(TextDirection value) {
    if (value == _textDirection) {
      return;
    }
    _textDirection = value;
    markNeedsLayout();
  }

  @override
  double computeMinIntrinsicWidth(double height) {
    RenderBox? child = firstChild;
    double minWidth = 0.0;
    while (child != null) {
      final _SegmentedButtonContainerBoxParentData childParentData = child.parentData! as _SegmentedButtonContainerBoxParentData;
      final double childWidth = child.getMinIntrinsicWidth(height);
      minWidth = math.max(minWidth, childWidth);
      child = childParentData.nextSibling;
    }
    return minWidth * childCount;
  }

  @override
  double computeMaxIntrinsicWidth(double height) {
    RenderBox? child = firstChild;
    double maxWidth = 0.0;
    while (child != null) {
      final _SegmentedButtonContainerBoxParentData childParentData = child.parentData! as _SegmentedButtonContainerBoxParentData;
      final double childWidth = child.getMaxIntrinsicWidth(height);
      maxWidth = math.max(maxWidth, childWidth);
      child = childParentData.nextSibling;
    }
    return maxWidth * childCount;
  }

  @override
  double computeMinIntrinsicHeight(double width) {
    RenderBox? child = firstChild;
    double minHeight = 0.0;
    while (child != null) {
      final _SegmentedButtonContainerBoxParentData childParentData = child.parentData! as _SegmentedButtonContainerBoxParentData;
      final double childHeight = child.getMinIntrinsicHeight(width);
      minHeight = math.max(minHeight, childHeight);
      child = childParentData.nextSibling;
    }
    return minHeight;
  }

  @override
  double computeMaxIntrinsicHeight(double width) {
    RenderBox? child = firstChild;
    double maxHeight = 0.0;
    while (child != null) {
      final _SegmentedButtonContainerBoxParentData childParentData = child.parentData! as _SegmentedButtonContainerBoxParentData;
      final double childHeight = child.getMaxIntrinsicHeight(width);
      maxHeight = math.max(maxHeight, childHeight);
      child = childParentData.nextSibling;
    }
    return maxHeight;
  }

  @override
  double? computeDistanceToActualBaseline(TextBaseline baseline) {
    return defaultComputeDistanceToHighestActualBaseline(baseline);
  }

  @override
  void setupParentData(RenderBox child) {
    if (child.parentData is! _SegmentedButtonContainerBoxParentData) {
      child.parentData = _SegmentedButtonContainerBoxParentData();
    }
  }

  void _layoutRects(_NextChild nextChild, RenderBox? leftChild, RenderBox? rightChild) {
    RenderBox? child = leftChild;
    double start = 0.0;
    while (child != null) {
      final _SegmentedButtonContainerBoxParentData childParentData = child.parentData! as _SegmentedButtonContainerBoxParentData;
      final Offset childOffset = Offset(start, 0.0);
      childParentData.offset = childOffset;
      final Rect childRect = Rect.fromLTWH(start, 0.0, child.size.width, child.size.height);
      final RRect rChildRect = RRect.fromRectAndCorners(childRect);
      childParentData.surroundingRect = rChildRect;
      start += child.size.width;
      child = nextChild(child);
    }
  }

  Size _calculateChildSize(BoxConstraints constraints) {
    double maxHeight = 0;
    double childWidth = constraints.minWidth / childCount;
    RenderBox? child = firstChild;
    while (child != null) {
      childWidth = math.max(childWidth, child.getMaxIntrinsicWidth(double.infinity));
      child = childAfter(child);
    }
    childWidth = math.min(childWidth, constraints.maxWidth / childCount);
    child = firstChild;
    while (child != null) {
      final double boxHeight = child.getMaxIntrinsicHeight(childWidth);
      maxHeight = math.max(maxHeight, boxHeight);
      child = childAfter(child);
    }
    return Size(childWidth, maxHeight);
  }

  Size _computeOverallSizeFromChildSize(Size childSize) {
    return constraints.constrain(Size(childSize.width * childCount, childSize.height));
  }

  @override
  Size computeDryLayout(BoxConstraints constraints) {
    final Size childSize = _calculateChildSize(constraints);
    return _computeOverallSizeFromChildSize(childSize);
  }

  @override
  void performLayout() {
    final BoxConstraints constraints = this.constraints;
    final Size childSize = _calculateChildSize(constraints);

    final BoxConstraints childConstraints = BoxConstraints.tightFor(
      width: childSize.width,
      height: childSize.height,
    );

    RenderBox? child = firstChild;
    while (child != null) {
      child.layout(childConstraints, parentUsesSize: true);
      child = childAfter(child);
    }

    switch (textDirection) {
      case TextDirection.rtl:
        _layoutRects(
          childBefore,
          lastChild,
          firstChild,
        );
      case TextDirection.ltr:
        _layoutRects(
          childAfter,
          firstChild,
          lastChild,
        );
    }

    size = _computeOverallSizeFromChildSize(childSize);
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    final Canvas canvas = context.canvas;
    final Rect borderRect = offset & size;
    final Path borderClipPath = enabledBorder.getInnerPath(borderRect, textDirection: textDirection);
    RenderBox? child = firstChild;
    RenderBox? previousChild;
    int index = 0;
    Path? enabledClipPath;
    Path? disabledClipPath;

    canvas..save()..clipPath(borderClipPath);
    while (child != null) {
      final _SegmentedButtonContainerBoxParentData childParentData = child.parentData! as _SegmentedButtonContainerBoxParentData;
      final Rect childRect = childParentData.surroundingRect!.outerRect.shift(offset);

      canvas..save()..clipRect(childRect);
      context.paintChild(child, childParentData.offset + offset);
      canvas.restore();

      // Compute a clip rect for the outer border of the child.
      late final double segmentLeft;
      late final double segmentRight;
      late final double dividerPos;
      final double borderOutset = math.max(enabledBorder.side.strokeOutset, disabledBorder.side.strokeOutset);
      switch (textDirection) {
        case TextDirection.rtl:
          segmentLeft = child == lastChild ? borderRect.left - borderOutset : childRect.left;
          segmentRight = child == firstChild ? borderRect.right + borderOutset : childRect.right;
          dividerPos = segmentRight;
        case TextDirection.ltr:
          segmentLeft = child == firstChild ? borderRect.left - borderOutset : childRect.left;
          segmentRight = child == lastChild ? borderRect.right + borderOutset : childRect.right;
          dividerPos = segmentLeft;
      }
      final Rect segmentClipRect = Rect.fromLTRB(
        segmentLeft, borderRect.top - borderOutset,
        segmentRight, borderRect.bottom + borderOutset);

      // Add the clip rect to the appropriate border clip path
      if (segments[index].enabled) {
        enabledClipPath = (enabledClipPath ?? Path())..addRect(segmentClipRect);
      } else {
        disabledClipPath = (disabledClipPath ?? Path())..addRect(segmentClipRect);
      }

      // Paint the divider between this segment and the previous one.
      if (previousChild != null) {
        final BorderSide divider = segments[index - 1].enabled || segments[index].enabled
          ? enabledBorder.side.copyWith(strokeAlign: 0.0)
          : disabledBorder.side.copyWith(strokeAlign: 0.0);
        final Offset top = Offset(dividerPos, childRect.top);
        final Offset bottom = Offset(dividerPos, childRect.bottom);
        canvas.drawLine(top, bottom, divider.toPaint());
      }

      previousChild = child;
      child = childAfter(child);
      index += 1;
    }
    canvas.restore();

    // Paint the outer border for both disabled and enabled clip rect if needed.
    if (disabledClipPath == null) {
      // Just paint the enabled border with no clip.
      enabledBorder.paint(context.canvas, borderRect, textDirection: textDirection);
    } else if (enabledClipPath == null) {
      // Just paint the disabled border with no.
      disabledBorder.paint(context.canvas, borderRect, textDirection: textDirection);
    } else {
      // Paint both of them clipped appropriately for the children segments.
      canvas..save()..clipPath(enabledClipPath);
      enabledBorder.paint(context.canvas, borderRect, textDirection: textDirection);
      canvas..restore()..save()..clipPath(disabledClipPath);
      disabledBorder.paint(context.canvas, borderRect, textDirection: textDirection);
      canvas.restore();
    }
  }

  @override
  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
    RenderBox? child = lastChild;
    while (child != null) {
      final _SegmentedButtonContainerBoxParentData childParentData = child.parentData! as _SegmentedButtonContainerBoxParentData;
      if (childParentData.surroundingRect!.contains(position)) {
        return result.addWithPaintOffset(
          offset: childParentData.offset,
          position: position,
          hitTest: (BoxHitTestResult result, Offset localOffset) {
            assert(localOffset == position - childParentData.offset);
            return child!.hitTest(result, position: localOffset);
          },
        );
      }
      child = childParentData.previousSibling;
    }
    return false;
  }
}

// BEGIN GENERATED TOKEN PROPERTIES - SegmentedButton

// Do not edit by hand. The code between the "BEGIN GENERATED" and
// "END GENERATED" comments are generated from data in the Material
// Design token database by the script:
//   dev/tools/gen_defaults/bin/gen_defaults.dart.

class _SegmentedButtonDefaultsM3 extends SegmentedButtonThemeData {
  _SegmentedButtonDefaultsM3(this.context);
  final BuildContext context;
  late final ThemeData _theme = Theme.of(context);
  late final ColorScheme _colors = _theme.colorScheme;
  @override ButtonStyle? get style {
    return ButtonStyle(
      textStyle: MaterialStatePropertyAll<TextStyle?>(Theme.of(context).textTheme.labelLarge),
      backgroundColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
        if (states.contains(MaterialState.disabled)) {
          return null;
        }
        if (states.contains(MaterialState.selected)) {
          return _colors.secondaryContainer;
        }
        return null;
      }),
      foregroundColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
        if (states.contains(MaterialState.disabled)) {
          return _colors.onSurface.withOpacity(0.38);
        }
        if (states.contains(MaterialState.selected)) {
          if (states.contains(MaterialState.pressed)) {
            return _colors.onSecondaryContainer;
          }
          if (states.contains(MaterialState.hovered)) {
            return _colors.onSecondaryContainer;
          }
          if (states.contains(MaterialState.focused)) {
            return _colors.onSecondaryContainer;
          }
          return _colors.onSecondaryContainer;
        } else {
          if (states.contains(MaterialState.pressed)) {
            return _colors.onSurface;
          }
          if (states.contains(MaterialState.hovered)) {
            return _colors.onSurface;
          }
          if (states.contains(MaterialState.focused)) {
            return _colors.onSurface;
          }
          return _colors.onSurface;
        }
      }),
      overlayColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
        if (states.contains(MaterialState.selected)) {
          if (states.contains(MaterialState.pressed)) {
            return _colors.onSecondaryContainer.withOpacity(0.12);
          }
          if (states.contains(MaterialState.hovered)) {
            return _colors.onSecondaryContainer.withOpacity(0.08);
          }
          if (states.contains(MaterialState.focused)) {
            return _colors.onSecondaryContainer.withOpacity(0.12);
          }
        } else {
          if (states.contains(MaterialState.pressed)) {
            return _colors.onSurface.withOpacity(0.12);
          }
          if (states.contains(MaterialState.hovered)) {
            return _colors.onSurface.withOpacity(0.08);
          }
          if (states.contains(MaterialState.focused)) {
            return _colors.onSurface.withOpacity(0.12);
          }
        }
        return null;
      }),
      surfaceTintColor: const MaterialStatePropertyAll<Color>(Colors.transparent),
      elevation: const MaterialStatePropertyAll<double>(0),
      iconSize: const MaterialStatePropertyAll<double?>(18.0),
      side: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
        if (states.contains(MaterialState.disabled)) {
          return BorderSide(color: _colors.onSurface.withOpacity(0.12));
        }
        return BorderSide(color: _colors.outline);
      }),
      shape: const MaterialStatePropertyAll<OutlinedBorder>(StadiumBorder()),
      minimumSize: const MaterialStatePropertyAll<Size?>(Size.fromHeight(40.0)),
    );
  }
  @override
  Widget? get selectedIcon => const Icon(Icons.check);
}

// END GENERATED TOKEN PROPERTIES - SegmentedButton
