@@ -3483,3 +3483,185 @@ void _debugDrawShadow(Canvas canvas, Path path, double elevation) {
3483
3483
);
3484
3484
}
3485
3485
}
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