Fix `Scrollbar` thumb drag behavior on desktop. (#111250)

diff --git a/packages/flutter/lib/src/widgets/scrollbar.dart b/packages/flutter/lib/src/widgets/scrollbar.dart
index 00cd2a7..15b3874 100644
--- a/packages/flutter/lib/src/widgets/scrollbar.dart
+++ b/packages/flutter/lib/src/widgets/scrollbar.dart
@@ -1447,6 +1447,7 @@
 /// scrollbar track.
 class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProviderStateMixin<T> {
   Offset? _dragScrollbarAxisOffset;
+  late double? _thumbPress;
   ScrollController? _currentController;
   Timer? _fadeoutTimer;
   late AnimationController _fadeoutAnimationController;
@@ -1785,6 +1786,9 @@
     _fadeoutTimer?.cancel();
     _fadeoutAnimationController.forward();
     _dragScrollbarAxisOffset = localPosition;
+    _thumbPress = direction == Axis.vertical
+      ? localPosition.dy - scrollbarPainter._thumbOffset
+      : localPosition.dx - scrollbarPainter._thumbOffset;
   }
 
   /// Handler called when a currently active long press gesture moves.
@@ -1802,10 +1806,28 @@
     if (direction == null) {
       return;
     }
-    _updateScrollPosition(localPosition);
+    switch (position.axisDirection) {
+      case AxisDirection.up:
+      case AxisDirection.down:
+        if (_canDragThumb(_dragScrollbarAxisOffset!.dy, position.viewportDimension, _thumbPress!)) {
+          _updateScrollPosition(localPosition);
+        }
+        break;
+      case AxisDirection.left:
+      case AxisDirection.right:
+        if (_canDragThumb(_dragScrollbarAxisOffset!.dx, position.viewportDimension, _thumbPress!)) {
+          _updateScrollPosition(localPosition);
+        }
+        break;
+    }
     _dragScrollbarAxisOffset = localPosition;
   }
 
+  bool _canDragThumb(double dragOffset, double viewport, double thumbPress) {
+    return dragOffset >= thumbPress
+      && dragOffset <= viewport - (scrollbarPainter._thumbExtent - thumbPress);
+  }
+
   /// Handler called when a long press has ended.
   @protected
   @mustCallSuper
diff --git a/packages/flutter/test/widgets/scrollbar_test.dart b/packages/flutter/test/widgets/scrollbar_test.dart
index 2c32042..2d0c86d 100644
--- a/packages/flutter/test/widgets/scrollbar_test.dart
+++ b/packages/flutter/test/widgets/scrollbar_test.dart
@@ -2717,4 +2717,142 @@
 
     expect(scrollController.offset, 0.0);
   });
+
+  testWidgets('Scrollbar thumb can only be dragged from long press point', (WidgetTester tester) async {
+    // Regression test for https://github.com/flutter/flutter/issues/107765
+
+    final ScrollController scrollController = ScrollController();
+    final UniqueKey uniqueKey = UniqueKey();
+    await tester.pumpWidget(
+      Directionality(
+        textDirection: TextDirection.ltr,
+        child: MediaQuery(
+          data: const MediaQueryData(),
+          child: ScrollConfiguration(
+            behavior: const ScrollBehavior().copyWith(
+              scrollbars: false,
+            ),
+            child: PrimaryScrollController(
+              controller: scrollController,
+              child: RawScrollbar(
+                isAlwaysShown: true,
+                controller: scrollController,
+                child: CustomScrollView(
+                  primary: true,
+                  slivers: <Widget>[
+                    SliverToBoxAdapter(
+                      child: Container(
+                        height: 600.0,
+                      ),
+                    ),
+                    SliverToBoxAdapter(
+                      key: uniqueKey,
+                      child: Container(
+                        height: 600.0,
+                      ),
+                    ),
+                    SliverToBoxAdapter(
+                      child: Container(
+                        height: 600.0,
+                      ),
+                    ),
+                  ],
+                ),
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+    await tester.pumpAndSettle();
+    expect(scrollController.offset, 0.0);
+    expect(
+      find.byType(RawScrollbar),
+      paints
+        ..rect(rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 600.0))
+        ..rect(
+          rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 200.0),
+          color: const Color(0x66BCBCBC),
+        ),
+    );
+
+    // Long press on the thumb in the center and drag down to the bottom.
+    const double scrollAmount = 400.0;
+    final TestGesture dragScrollbarGesture = await tester.startGesture(const Offset(797.0, 100.0));
+    await tester.pumpAndSettle();
+    await dragScrollbarGesture.moveBy(const Offset(0.0, scrollAmount));
+    await tester.pumpAndSettle();
+
+    // Drag down past the long press point.
+    await dragScrollbarGesture.moveBy(const Offset(0.0, 100));
+    await tester.pumpAndSettle();
+
+    // Drag up without reaching press point on the thumb.
+    await dragScrollbarGesture.moveBy(const Offset(0.0, -50));
+    await tester.pumpAndSettle();
+
+    // Thumb should not move yet.
+    expect(
+      find.byType(RawScrollbar),
+      paints
+        ..rect(rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 600.0))
+        ..rect(
+          rect: const Rect.fromLTRB(794.0, 400.0, 800.0, 600.0),
+          color: const Color(0x66BCBCBC),
+        ),
+    );
+
+    // Drag up to reach press point on the thumb.
+    await dragScrollbarGesture.moveBy(const Offset(0.0, -50));
+    await tester.pumpAndSettle();
+
+    // Drag up.
+    await dragScrollbarGesture.moveBy(const Offset(0.0, -300));
+    await tester.pumpAndSettle();
+
+    // Thumb should be moved.
+    expect(
+      find.byType(RawScrollbar),
+      paints
+        ..rect(rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 600.0))
+        ..rect(
+          rect: const Rect.fromLTRB(794.0, 100.0, 800.0, 300.0),
+          color: const Color(0x66BCBCBC),
+        ),
+    );
+
+    // Drag up to reach the top and exceed the long press point.
+    await dragScrollbarGesture.moveBy(const Offset(0.0, -200));
+    await tester.pumpAndSettle();
+
+    // Drag down to reach the long press point.
+    await dragScrollbarGesture.moveBy(const Offset(0.0, 100));
+    await tester.pumpAndSettle();
+
+    // Thumb should not move yet.
+    expect(
+      find.byType(RawScrollbar),
+      paints
+        ..rect(rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 600.0))
+        ..rect(
+          rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 200.0),
+          color: const Color(0x66BCBCBC),
+        ),
+    );
+
+    // Drag down past the long press point.
+    await dragScrollbarGesture.moveBy(const Offset(0.0, 100));
+    await tester.pumpAndSettle();
+
+    // Thumb should be moved.
+    expect(
+      find.byType(RawScrollbar),
+      paints
+        ..rect(rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 600.0))
+        ..rect(
+          rect: const Rect.fromLTRB(794.0, 100.0, 800.0, 300.0),
+          color: const Color(0x66BCBCBC),
+        ),
+    );
+  }, variant: TargetPlatformVariant.desktop());
 }