@@ -3483,3 +3483,185 @@ void _debugDrawShadow(Canvas canvas, Path path, double elevation) {
34833483 );
34843484 }
34853485}
3486+
3487+ /// The default shape of a Material 3 [Slider] 's value indicator.
3488+ ///
3489+ /// See also:
3490+ ///
3491+ /// * [Slider] , which includes a value indicator defined by this shape.
3492+ /// * [SliderTheme] , which can be used to configure the slider value indicator
3493+ /// of all sliders in a widget subtree.
3494+ class DropSliderValueIndicatorShape extends SliderComponentShape {
3495+ /// Create a slider value indicator that resembles a drop shape.
3496+ const DropSliderValueIndicatorShape ();
3497+
3498+ static const _DropSliderValueIndicatorPathPainter _pathPainter = _DropSliderValueIndicatorPathPainter ();
3499+
3500+ @override
3501+ Size getPreferredSize (
3502+ bool isEnabled,
3503+ bool isDiscrete, {
3504+ TextPainter ? labelPainter,
3505+ double ? textScaleFactor,
3506+ }) {
3507+ assert (labelPainter != null );
3508+ assert (textScaleFactor != null && textScaleFactor >= 0 );
3509+ return _pathPainter.getPreferredSize (labelPainter! , textScaleFactor! );
3510+ }
3511+
3512+ @override
3513+ void paint (
3514+ PaintingContext context,
3515+ Offset center, {
3516+ required Animation <double > activationAnimation,
3517+ required Animation <double > enableAnimation,
3518+ required bool isDiscrete,
3519+ required TextPainter labelPainter,
3520+ required RenderBox parentBox,
3521+ required SliderThemeData sliderTheme,
3522+ required TextDirection textDirection,
3523+ required double value,
3524+ required double textScaleFactor,
3525+ required Size sizeWithOverflow,
3526+ }) {
3527+ final Canvas canvas = context.canvas;
3528+ final double scale = activationAnimation.value;
3529+ _pathPainter.paint (
3530+ parentBox: parentBox,
3531+ canvas: canvas,
3532+ center: center,
3533+ scale: scale,
3534+ labelPainter: labelPainter,
3535+ textScaleFactor: textScaleFactor,
3536+ sizeWithOverflow: sizeWithOverflow,
3537+ backgroundPaintColor: sliderTheme.valueIndicatorColor! ,
3538+ );
3539+ }
3540+ }
3541+
3542+ class _DropSliderValueIndicatorPathPainter {
3543+ const _DropSliderValueIndicatorPathPainter ();
3544+
3545+ static const double _triangleHeight = 10.0 ;
3546+ static const double _labelPadding = 8.0 ;
3547+ static const double _preferredHeight = 32.0 ;
3548+ static const double _minLabelWidth = 20.0 ;
3549+ static const double _minRectHeight = 28.0 ;
3550+ static const double _rectYOffset = 6.0 ;
3551+ static const double _bottomTipYOffset = 16.0 ;
3552+ static const double _preferredHalfHeight = _preferredHeight / 2 ;
3553+ static const double _upperRectRadius = 4 ;
3554+
3555+ Size getPreferredSize (
3556+ TextPainter labelPainter,
3557+ double textScaleFactor,
3558+ ) {
3559+ assert (labelPainter != null );
3560+ final double width = math.max (_minLabelWidth, labelPainter.width) + _labelPadding * 2 * textScaleFactor;
3561+ return Size (width, _preferredHeight * textScaleFactor);
3562+ }
3563+
3564+ double getHorizontalShift ({
3565+ required RenderBox parentBox,
3566+ required Offset center,
3567+ required TextPainter labelPainter,
3568+ required double textScaleFactor,
3569+ required Size sizeWithOverflow,
3570+ required double scale,
3571+ }) {
3572+ assert (! sizeWithOverflow.isEmpty);
3573+
3574+ const double edgePadding = 8.0 ;
3575+ final double rectangleWidth = _upperRectangleWidth (labelPainter, scale);
3576+ /// Value indicator draws on the Overlay and by using the global Offset
3577+ /// we are making sure we use the bounds of the Overlay instead of the Slider.
3578+ final Offset globalCenter = parentBox.localToGlobal (center);
3579+
3580+ // The rectangle must be shifted towards the center so that it minimizes the
3581+ // chance of it rendering outside the bounds of the render box. If the shift
3582+ // is negative, then the lobe is shifted from right to left, and if it is
3583+ // positive, then the lobe is shifted from left to right.
3584+ final double overflowLeft = math.max (0 , rectangleWidth / 2 - globalCenter.dx + edgePadding);
3585+ final double overflowRight = math.max (0 , rectangleWidth / 2 - (sizeWithOverflow.width - globalCenter.dx - edgePadding));
3586+
3587+ if (rectangleWidth < sizeWithOverflow.width) {
3588+ return overflowLeft - overflowRight;
3589+ } else if (overflowLeft - overflowRight > 0 ) {
3590+ return overflowLeft - (edgePadding * textScaleFactor);
3591+ } else {
3592+ return - overflowRight + (edgePadding * textScaleFactor);
3593+ }
3594+ }
3595+
3596+ double _upperRectangleWidth (TextPainter labelPainter, double scale) {
3597+ final double unscaledWidth = math.max (_minLabelWidth, labelPainter.width) + _labelPadding;
3598+ return unscaledWidth * scale;
3599+ }
3600+
3601+ BorderRadius _adjustBorderRadius (Rect rect) {
3602+ const double rectness = 0.0 ;
3603+ return BorderRadius .lerp (
3604+ BorderRadius .circular (_upperRectRadius),
3605+ BorderRadius .all (Radius .circular (rect.shortestSide / 2.0 )),
3606+ 1.0 - rectness,
3607+ )! ;
3608+ }
3609+
3610+ void paint ({
3611+ required RenderBox parentBox,
3612+ required Canvas canvas,
3613+ required Offset center,
3614+ required double scale,
3615+ required TextPainter labelPainter,
3616+ required double textScaleFactor,
3617+ required Size sizeWithOverflow,
3618+ required Color backgroundPaintColor,
3619+ Color ? strokePaintColor,
3620+ }) {
3621+ if (scale == 0.0 ) {
3622+ // Zero scale essentially means "do not draw anything", so it's safe to just return.
3623+ return ;
3624+ }
3625+ assert (! sizeWithOverflow.isEmpty);
3626+
3627+ final double rectangleWidth = _upperRectangleWidth (labelPainter, scale);
3628+ final double horizontalShift = getHorizontalShift (
3629+ parentBox: parentBox,
3630+ center: center,
3631+ labelPainter: labelPainter,
3632+ textScaleFactor: textScaleFactor,
3633+ sizeWithOverflow: sizeWithOverflow,
3634+ scale: scale,
3635+ );
3636+ final Rect upperRect = Rect .fromLTWH (
3637+ - rectangleWidth / 2 + horizontalShift,
3638+ - _rectYOffset - _minRectHeight,
3639+ rectangleWidth,
3640+ _minRectHeight,
3641+ );
3642+
3643+ final Paint fillPaint = Paint ()..color = backgroundPaintColor;
3644+
3645+ canvas.save ();
3646+ canvas.translate (center.dx, center.dy - _bottomTipYOffset);
3647+ canvas.scale (scale, scale);
3648+
3649+ final BorderRadius adjustedBorderRadius = _adjustBorderRadius (upperRect);
3650+ final RRect borderRect = adjustedBorderRadius.resolve (labelPainter.textDirection).toRRect (upperRect);
3651+ final Path trianglePath = Path ()
3652+ ..lineTo (- _triangleHeight, - _triangleHeight)
3653+ ..lineTo (_triangleHeight, - _triangleHeight)
3654+ ..close ();
3655+ canvas.drawPath (trianglePath, fillPaint);
3656+ canvas.drawRRect (borderRect, fillPaint);
3657+
3658+ // The label text is centered within the value indicator.
3659+ final double bottomTipToUpperRectTranslateY = - _preferredHalfHeight / 2 - upperRect.height;
3660+ canvas.translate (0 , bottomTipToUpperRectTranslateY);
3661+ final Offset boxCenter = Offset (horizontalShift, upperRect.height / 1.75 );
3662+ final Offset halfLabelPainterOffset = Offset (labelPainter.width / 2 , labelPainter.height / 2 );
3663+ final Offset labelOffset = boxCenter - halfLabelPainterOffset;
3664+ labelPainter.paint (canvas, labelOffset);
3665+ canvas.restore ();
3666+ }
3667+ }
0 commit comments