Clean up ScrollbarPainter (#107179)

diff --git a/packages/flutter/lib/src/widgets/scrollbar.dart b/packages/flutter/lib/src/widgets/scrollbar.dart
index 67d28bc..971d9ee 100644
--- a/packages/flutter/lib/src/widgets/scrollbar.dart
+++ b/packages/flutter/lib/src/widgets/scrollbar.dart
@@ -283,6 +283,7 @@
     _shape = value;
     notifyListeners();
   }
+
   /// The amount of space by which to inset the scrollbar's start and end, as
   /// well as its side to the nearest edge, in logical pixels.
   ///
@@ -304,7 +305,6 @@
     notifyListeners();
   }
 
-
   /// The preferred smallest size the scrollbar thumb can shrink to when the total
   /// scrollable extent is large, the current visible viewport is small, and the
   /// viewport is not overscrolled.
@@ -391,23 +391,129 @@
     notifyListeners();
   }
 
+  // - Scrollbar Details
+
+  Rect? _trackRect;
+  // The full painted length of the track
+  double get _trackExtent => _lastMetrics!.viewportDimension - _totalTrackMainAxisOffsets;
+  // The full length of the track that the thumb can travel
+  double get _traversableTrackExtent => _trackExtent - (2 * mainAxisMargin);
+  // Track Offsets
+  // The track is offset by only padding.
+  double get _totalTrackMainAxisOffsets => _isVertical ? padding.vertical : padding.horizontal;
+  double get _leadingTrackMainAxisOffset {
+    switch(_resolvedOrientation) {
+      case ScrollbarOrientation.left:
+      case ScrollbarOrientation.right:
+        return padding.top;
+      case ScrollbarOrientation.top:
+      case ScrollbarOrientation.bottom:
+        return padding.left;
+    }
+  }
+
+  Rect? _thumbRect;
+  // The current scroll position + _leadingThumbMainAxisOffset
+  late double _thumbOffset;
+  // The fraction visible in relation to the trversable length of the track.
+  late double _thumbExtent;
+  // Thumb Offsets
+  // The thumb is offset by padding and margins.
+  double get _leadingThumbMainAxisOffset {
+    switch(_resolvedOrientation) {
+      case ScrollbarOrientation.left:
+      case ScrollbarOrientation.right:
+        return padding.top + mainAxisMargin;
+      case ScrollbarOrientation.top:
+      case ScrollbarOrientation.bottom:
+        return padding.left + mainAxisMargin;
+    }
+  }
+  void _setThumbExtent() {
+    // Thumb extent reflects fraction of content visible, as long as this
+    // isn't less than the absolute minimum size.
+    // _totalContentExtent >= viewportDimension, so (_totalContentExtent - _mainAxisPadding) > 0
+    final double fractionVisible = clampDouble(
+      (_lastMetrics!.extentInside - _totalTrackMainAxisOffsets)
+        / (_totalContentExtent - _totalTrackMainAxisOffsets),
+      0.0,
+      1.0,
+    );
+
+    final double thumbExtent = math.max(
+      math.min(_traversableTrackExtent, minOverscrollLength),
+      _traversableTrackExtent * fractionVisible,
+    );
+
+    final double fractionOverscrolled = 1.0 - _lastMetrics!.extentInside / _lastMetrics!.viewportDimension;
+    final double safeMinLength = math.min(minLength, _traversableTrackExtent);
+    final double newMinLength = (_beforeExtent > 0 && _afterExtent > 0)
+    // Thumb extent is no smaller than minLength if scrolling normally.
+      ? safeMinLength
+    // User is overscrolling. Thumb extent can be less than minLength
+    // but no smaller than minOverscrollLength. We can't use the
+    // fractionVisible to produce intermediate values between minLength and
+    // minOverscrollLength when the user is transitioning from regular
+    // scrolling to overscrolling, so we instead use the percentage of the
+    // content that is still in the viewport to determine the size of the
+    // thumb. iOS behavior appears to have the thumb reach its minimum size
+    // with ~20% of overscroll. We map the percentage of minLength from
+    // [0.8, 1.0] to [0.0, 1.0], so 0% to 20% of overscroll will produce
+    // values for the thumb that range between minLength and the smallest
+    // possible value, minOverscrollLength.
+        : safeMinLength * (1.0 - clampDouble(fractionOverscrolled, 0.0, 0.2) / 0.2);
+
+    // The `thumbExtent` should be no greater than `trackSize`, otherwise
+    // the scrollbar may scroll towards the wrong direction.
+    _thumbExtent = clampDouble(thumbExtent, newMinLength, _traversableTrackExtent);
+  }
+
+  // - Scrollable Details
+
+  ScrollMetrics? _lastMetrics;
+  bool get _lastMetricsAreScrollable => _lastMetrics!.minScrollExtent != _lastMetrics!.maxScrollExtent;
+  AxisDirection? _lastAxisDirection;
+
+  bool get _isVertical => _lastAxisDirection == AxisDirection.down || _lastAxisDirection == AxisDirection.up;
+  bool get _isReversed => _lastAxisDirection == AxisDirection.up || _lastAxisDirection == AxisDirection.left;
+  // The amount of scroll distance before and after the current position.
+  double get _beforeExtent => _isReversed ? _lastMetrics!.extentAfter : _lastMetrics!.extentBefore;
+  double get _afterExtent => _isReversed ? _lastMetrics!.extentBefore : _lastMetrics!.extentAfter;
+
+  // The total size of the scrollable content.
+  double get _totalContentExtent {
+    return _lastMetrics!.maxScrollExtent
+      - _lastMetrics!.minScrollExtent
+      + _lastMetrics!.viewportDimension;
+  }
+
+  ScrollbarOrientation get _resolvedOrientation {
+    if (scrollbarOrientation == null) {
+      if (_isVertical) {
+        return textDirection == TextDirection.ltr
+          ? ScrollbarOrientation.right
+          : ScrollbarOrientation.left;
+      }
+      return ScrollbarOrientation.bottom;
+    }
+    return scrollbarOrientation!;
+  }
+
   void _debugAssertIsValidOrientation(ScrollbarOrientation orientation) {
     assert(
-    (_isVertical && _isVerticalOrientation(orientation)) || (!_isVertical && !_isVerticalOrientation(orientation)),
-    'The given ScrollbarOrientation: $orientation is incompatible with the current AxisDirection: $_lastAxisDirection.'
+      () {
+        bool isVerticalOrientation(ScrollbarOrientation orientation) =>
+          orientation == ScrollbarOrientation.left
+            || orientation == ScrollbarOrientation.right;
+        return (_isVertical && isVerticalOrientation(orientation))
+           || (!_isVertical && !isVerticalOrientation(orientation));
+      }(),
+      'The given ScrollbarOrientation: $orientation is incompatible with the '
+      'current AxisDirection: $_lastAxisDirection.'
     );
   }
 
-  /// Check whether given scrollbar orientation is vertical
-  bool _isVerticalOrientation(ScrollbarOrientation orientation) =>
-    orientation == ScrollbarOrientation.left
-    || orientation == ScrollbarOrientation.right;
-
-  ScrollMetrics? _lastMetrics;
-  AxisDirection? _lastAxisDirection;
-  Rect? _thumbRect;
-  Rect? _trackRect;
-  late double _thumbOffset;
+  // - Updating
 
   /// Update with new [ScrollMetrics]. If the metrics change, the scrollbar will
   /// show and redraw itself based on these new metrics.
@@ -433,7 +539,6 @@
     if (!needPaint(oldMetrics) && !needPaint(metrics)) {
       return;
     }
-
     notifyListeners();
   }
 
@@ -443,6 +548,8 @@
     radius = nextRadius;
   }
 
+  // - Painting
+
   Paint get _paintThumb {
     return Paint()
       ..color = color.withOpacity(color.opacity * fadeoutOpacityAnimation.value);
@@ -459,67 +566,50 @@
       ..color = trackColor.withOpacity(trackColor.opacity * fadeoutOpacityAnimation.value);
   }
 
-  void _paintScrollbar(Canvas canvas, Size size, double thumbExtent, AxisDirection direction) {
+  void _paintScrollbar(Canvas canvas, Size size) {
     assert(
       textDirection != null,
       'A TextDirection must be provided before a Scrollbar can be painted.',
     );
 
-    final ScrollbarOrientation resolvedOrientation;
-
-    if (scrollbarOrientation == null) {
-      if (_isVertical) {
-        resolvedOrientation = textDirection == TextDirection.ltr
-          ? ScrollbarOrientation.right
-          : ScrollbarOrientation.left;
-      } else {
-        resolvedOrientation = ScrollbarOrientation.bottom;
-      }
-    }
-    else {
-      resolvedOrientation = scrollbarOrientation!;
-    }
-
     final double x, y;
     final Size thumbSize, trackSize;
     final Offset trackOffset, borderStart, borderEnd;
-
-    _debugAssertIsValidOrientation(resolvedOrientation);
-
-    switch(resolvedOrientation) {
+    _debugAssertIsValidOrientation(_resolvedOrientation);
+    switch(_resolvedOrientation) {
       case ScrollbarOrientation.left:
-        thumbSize = Size(thickness, thumbExtent);
+        thumbSize = Size(thickness, _thumbExtent);
         trackSize = Size(thickness + 2 * crossAxisMargin, _trackExtent);
         x = crossAxisMargin + padding.left;
         y = _thumbOffset;
-        trackOffset = Offset(x - crossAxisMargin, padding.top);
+        trackOffset = Offset(x - crossAxisMargin, _leadingTrackMainAxisOffset);
         borderStart = trackOffset + Offset(trackSize.width, 0.0);
         borderEnd = Offset(trackOffset.dx + trackSize.width, trackOffset.dy + _trackExtent);
         break;
       case ScrollbarOrientation.right:
-        thumbSize = Size(thickness, thumbExtent);
+        thumbSize = Size(thickness, _thumbExtent);
         trackSize = Size(thickness + 2 * crossAxisMargin, _trackExtent);
         x = size.width - thickness - crossAxisMargin - padding.right;
         y = _thumbOffset;
-        trackOffset = Offset(x - crossAxisMargin, padding.top);
+        trackOffset = Offset(x - crossAxisMargin, _leadingTrackMainAxisOffset);
         borderStart = trackOffset;
         borderEnd = Offset(trackOffset.dx, trackOffset.dy + _trackExtent);
         break;
       case ScrollbarOrientation.top:
-        thumbSize = Size(thumbExtent, thickness);
+        thumbSize = Size(_thumbExtent, thickness);
         trackSize = Size(_trackExtent, thickness + 2 * crossAxisMargin);
         x = _thumbOffset;
         y = crossAxisMargin + padding.top;
-        trackOffset = Offset(padding.left, y - crossAxisMargin);
+        trackOffset = Offset(_leadingTrackMainAxisOffset, y - crossAxisMargin);
         borderStart = trackOffset + Offset(0.0, trackSize.height);
         borderEnd = Offset(trackOffset.dx + _trackExtent, trackOffset.dy + trackSize.height);
         break;
       case ScrollbarOrientation.bottom:
-        thumbSize = Size(thumbExtent, thickness);
+        thumbSize = Size(_thumbExtent, thickness);
         trackSize = Size(_trackExtent, thickness + 2 * crossAxisMargin);
         x = _thumbOffset;
         y = size.height - thickness - crossAxisMargin - padding.bottom;
-        trackOffset = Offset(padding.left, y - crossAxisMargin);
+        trackOffset = Offset(_leadingTrackMainAxisOffset, y - crossAxisMargin);
         borderStart = trackOffset;
         borderEnd = Offset(trackOffset.dx + _trackExtent, trackOffset.dy);
         break;
@@ -557,69 +647,32 @@
     }
   }
 
-  double _thumbExtent() {
-    // Thumb extent reflects fraction of content visible, as long as this
-    // isn't less than the absolute minimum size.
-    // _totalContentExtent >= viewportDimension, so (_totalContentExtent - _mainAxisPadding) > 0
-    final double fractionVisible = clampDouble(
-        (_lastMetrics!.extentInside - _mainAxisPadding) /
-            (_totalContentExtent - _mainAxisPadding),
-        0.0,
-        1.0);
-
-    final double thumbExtent = math.max(
-      math.min(_traversableTrackExtent, minOverscrollLength),
-      _traversableTrackExtent * fractionVisible,
-    );
-
-    final double fractionOverscrolled = 1.0 - _lastMetrics!.extentInside / _lastMetrics!.viewportDimension;
-    final double safeMinLength = math.min(minLength, _traversableTrackExtent);
-    final double newMinLength = (_beforeExtent > 0 && _afterExtent > 0)
-      // Thumb extent is no smaller than minLength if scrolling normally.
-      ? safeMinLength
-      // User is overscrolling. Thumb extent can be less than minLength
-      // but no smaller than minOverscrollLength. We can't use the
-      // fractionVisible to produce intermediate values between minLength and
-      // minOverscrollLength when the user is transitioning from regular
-      // scrolling to overscrolling, so we instead use the percentage of the
-      // content that is still in the viewport to determine the size of the
-      // thumb. iOS behavior appears to have the thumb reach its minimum size
-      // with ~20% of overscroll. We map the percentage of minLength from
-      // [0.8, 1.0] to [0.0, 1.0], so 0% to 20% of overscroll will produce
-      // values for the thumb that range between minLength and the smallest
-      // possible value, minOverscrollLength.
-      : safeMinLength * (1.0 - clampDouble(fractionOverscrolled, 0.0, 0.2) / 0.2);
-
-    // The `thumbExtent` should be no greater than `trackSize`, otherwise
-    // the scrollbar may scroll towards the wrong direction.
-    final double extent = clampDouble(thumbExtent, newMinLength, _traversableTrackExtent);
-    return extent;
-  }
-
   @override
-  void dispose() {
-    fadeoutOpacityAnimation.removeListener(notifyListeners);
-    super.dispose();
+  void paint(Canvas canvas, Size size) {
+    if (_lastAxisDirection == null
+        || _lastMetrics == null
+        || _lastMetrics!.maxScrollExtent <= _lastMetrics!.minScrollExtent) {
+      return;
+    }
+    // Skip painting if there's not enough space.
+    if (_traversableTrackExtent <= 0) {
+      return;
+    }
+    // Do not paint a scrollbar if the scroll view is infinitely long.
+    // TODO(Piinks): Special handling for infinite scroll views,
+    //  https://github.com/flutter/flutter/issues/41434
+    if (_lastMetrics!.maxScrollExtent.isInfinite) {
+      return;
+    }
+
+    _setThumbExtent();
+    final double thumbPositionOffset = _getScrollToTrack(_lastMetrics!, _thumbExtent);
+    _thumbOffset = thumbPositionOffset + _leadingThumbMainAxisOffset;
+
+    return _paintScrollbar(canvas, size);
   }
 
-  bool get _isVertical => _lastAxisDirection == AxisDirection.down || _lastAxisDirection == AxisDirection.up;
-  bool get _isReversed => _lastAxisDirection == AxisDirection.up || _lastAxisDirection == AxisDirection.left;
-  // The amount of scroll distance before and after the current position.
-  double get _beforeExtent => _isReversed ? _lastMetrics!.extentAfter : _lastMetrics!.extentBefore;
-  double get _afterExtent => _isReversed ? _lastMetrics!.extentBefore : _lastMetrics!.extentAfter;
-  // Padding of the thumb track.
-  double get _mainAxisPadding => _isVertical ? padding.vertical : padding.horizontal;
-  // The length of the painted track.
-  double get _trackExtent => _lastMetrics!.viewportDimension - _mainAxisPadding;
-  // The length of the track that is traversable by the thumb.
-  double get _traversableTrackExtent => _trackExtent - (2 * mainAxisMargin);
-
-  // The total size of the scrollable content.
-  double get _totalContentExtent {
-    return _lastMetrics!.maxScrollExtent
-      - _lastMetrics!.minScrollExtent
-      + _lastMetrics!.viewportDimension;
-  }
+  // - Scroll Position Conversion
 
   /// Convert between a thumb track position and the corresponding scroll
   /// position.
@@ -628,7 +681,7 @@
   double getTrackToScroll(double thumbOffsetLocal) {
     assert(thumbOffsetLocal != null);
     final double scrollableExtent = _lastMetrics!.maxScrollExtent - _lastMetrics!.minScrollExtent;
-    final double thumbMovableExtent = _traversableTrackExtent - _thumbExtent();
+    final double thumbMovableExtent = _traversableTrackExtent - _thumbExtent;
 
     return scrollableExtent * thumbOffsetLocal / thumbMovableExtent;
   }
@@ -645,35 +698,27 @@
     return (_isReversed ? 1 - fractionPast : fractionPast) * (_traversableTrackExtent - thumbExtent);
   }
 
+  // - Hit Testing
+
   @override
-  void paint(Canvas canvas, Size size) {
-    if (_lastAxisDirection == null
-        || _lastMetrics == null
-        || _lastMetrics!.maxScrollExtent <= _lastMetrics!.minScrollExtent) {
-      return;
+  bool? hitTest(Offset? position) {
+    // There is nothing painted to hit.
+    if (_thumbRect == null) {
+      return null;
     }
 
-    // Skip painting if there's not enough space.
-    if (_lastMetrics!.viewportDimension <= _mainAxisPadding || _traversableTrackExtent <= 0) {
-      return;
+    // Interaction disabled.
+    if (ignorePointer
+     // The thumb is not able to be hit when transparent.
+     || fadeoutOpacityAnimation.value == 0.0
+     // Not scrollable
+     || !_lastMetricsAreScrollable) {
+      return false;
     }
 
-    final double beforePadding = _isVertical ? padding.top : padding.left;
-    final double thumbExtent = _thumbExtent();
-    final double thumbOffsetLocal = _getScrollToTrack(_lastMetrics!, thumbExtent);
-    _thumbOffset = thumbOffsetLocal + mainAxisMargin + beforePadding;
-
-    // Do not paint a scrollbar if the scroll view is infinitely long.
-    // TODO(Piinks): Special handling for infinite scroll views, https://github.com/flutter/flutter/issues/41434
-    if (_lastMetrics!.maxScrollExtent.isInfinite) {
-      return;
-    }
-
-    return _paintScrollbar(canvas, size, thumbExtent, _lastAxisDirection!);
+    return _trackRect!.contains(position!);
   }
 
-  bool get _lastMetricsAreScrollable => _lastMetrics!.minScrollExtent != _lastMetrics!.maxScrollExtent;
-
   /// Same as hitTest, but includes some padding when the [PointerEvent] is
   /// caused by [PointerDeviceKind.touch] to make sure that the region
   /// isn't too small to be interacted with by the user.
@@ -756,28 +801,6 @@
     }
   }
 
-  // Scrollbars are interactive.
-  @override
-  bool? hitTest(Offset? position) {
-    if (_thumbRect == null) {
-      return null;
-    }
-    if (ignorePointer) {
-      return false;
-    }
-
-    // The thumb is not able to be hit when transparent.
-    if (fadeoutOpacityAnimation.value == 0.0) {
-      return false;
-    }
-
-    if (!_lastMetricsAreScrollable) {
-      return false;
-    }
-
-    return _trackRect!.contains(position!);
-  }
-
   @override
   bool shouldRepaint(ScrollbarPainter oldDelegate) {
     // Should repaint if any properties changed.
@@ -807,6 +830,12 @@
 
   @override
   String toString() => describeIdentity(this);
+
+  @override
+  void dispose() {
+    fadeoutOpacityAnimation.removeListener(notifyListeners);
+    super.dispose();
+  }
 }
 
 /// An extendable base class for building scrollbars that fade in and out.
@@ -1669,7 +1698,7 @@
         case TargetPlatform.iOS:
         case TargetPlatform.android:
           // We can only drag the scrollbar into overscroll on mobile
-          // platforms, and only if the physics allow it.
+          // platforms, and only then if the physics allow it.
           break;
       }
       position.jumpTo(newPosition);
@@ -1908,7 +1937,7 @@
         () => _ThumbPressGestureRecognizer(
           debugOwner: this,
           customPaintKey: _scrollbarPainterKey,
-          pressDuration: widget.pressDuration,
+          duration: widget.pressDuration,
         ),
         (_ThumbPressGestureRecognizer instance) {
           instance.onLongPress = handleThumbPress;
@@ -2074,11 +2103,8 @@
   _ThumbPressGestureRecognizer({
     required Object super.debugOwner,
     required GlobalKey customPaintKey,
-    required Duration pressDuration,
-  }) : _customPaintKey = customPaintKey,
-       super(
-         duration: pressDuration,
-       );
+    required super.duration,
+  }) : _customPaintKey = customPaintKey;
 
   final GlobalKey _customPaintKey;
 
@@ -2105,10 +2131,9 @@
 // track and ignores everything else, including the thumb.
 class _TrackTapGestureRecognizer extends TapGestureRecognizer {
   _TrackTapGestureRecognizer({
-    required Object debugOwner,
+    required super.debugOwner,
     required GlobalKey customPaintKey,
-  }) : _customPaintKey = customPaintKey,
-       super(debugOwner: debugOwner);
+  }) : _customPaintKey = customPaintKey;
 
   final GlobalKey _customPaintKey;