Advertisement
Armen_06

Untitled

Apr 20th, 2024 (edited)
436
0
22 hours
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Dart 95.14 KB | None | 0 0
  1. // Copyright 2014 The Flutter Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4.  
  5. part of 'package:nested_scroll_views/src/assembly/widgets/scrollable.dart';
  6.  
  7. /// Signature used by [Scrollable] to build the viewport through which the
  8. /// scrollable content is displayed.
  9. typedef ViewportBuilder = Widget Function(
  10.     BuildContext context, ViewportOffset position);
  11.  
  12. /// Signature used by [TwoDimensionalScrollable] to build the viewport through
  13. /// which the scrollable content is displayed.
  14. typedef TwoDimensionalViewportBuilder = Widget Function(BuildContext context,
  15.     ViewportOffset verticalPosition, ViewportOffset horizontalPosition);
  16.  
  17. // The return type of _performEnsureVisible.
  18. //
  19. // The list of futures represents each pending ScrollPosition call to
  20. // ensureVisible. The returned ScrollableState's context is used to find the
  21. // next potential ancestor Scrollable.
  22. typedef _EnsureVisibleResults = (List<Future<void>>, ScrollableState);
  23.  
  24. /// A widget that manages scrolling in one dimension and informs the [Viewport]
  25. /// through which the content is viewed.
  26. ///
  27. /// [Scrollable] implements the interaction model for a scrollable widget,
  28. /// including gesture recognition, but does not have an opinion about how the
  29. /// viewport, which actually displays the children, is constructed.
  30. ///
  31. /// It's rare to construct a [Scrollable] directly. Instead, consider [ListView]
  32. /// or [GridView], which combine scrolling, viewporting, and a layout model. To
  33. /// combine layout models (or to use a custom layout mode), consider using
  34. /// [CustomScrollView].
  35. ///
  36. /// The static [Scrollable.of] and [Scrollable.ensureVisible] functions are
  37. /// often used to interact with the [Scrollable] widget inside a [ListView] or
  38. /// a [GridView].
  39. ///
  40. /// To further customize scrolling behavior with a [Scrollable]:
  41. ///
  42. /// 1. You can provide a [viewportBuilder] to customize the child model. For
  43. ///    example, [SingleChildScrollView] uses a viewport that displays a single
  44. ///    box child whereas [CustomScrollView] uses a [Viewport] or a
  45. ///    [ShrinkWrappingViewport], both of which display a list of slivers.
  46. ///
  47. /// 2. You can provide a custom [ScrollController] that creates a custom
  48. ///    [ScrollPosition] subclass. For example, [PageView] uses a
  49. ///    [PageController], which creates a page-oriented scroll position subclass
  50. ///    that keeps the same page visible when the [Scrollable] resizes.
  51. ///
  52. /// ## Persisting the scroll position during a session
  53. ///
  54. /// Scrollables attempt to persist their scroll position using [PageStorage].
  55. /// This can be disabled by setting [ScrollController.keepScrollOffset] to false
  56. /// on the [controller]. If it is enabled, using a [PageStorageKey] for the
  57. /// [key] of this widget (or one of its ancestors, e.g. a [ScrollView]) is
  58. /// recommended to help disambiguate different [Scrollable]s from each other.
  59. ///
  60. /// See also:
  61. ///
  62. ///  * [ListView], which is a commonly used [ScrollView] that displays a
  63. ///    scrolling, linear list of child widgets.
  64. ///  * [PageView], which is a scrolling list of child widgets that are each the
  65. ///    size of the viewport.
  66. ///  * [GridView], which is a [ScrollView] that displays a scrolling, 2D array
  67. ///    of child widgets.
  68. ///  * [CustomScrollView], which is a [ScrollView] that creates custom scroll
  69. ///    effects using slivers.
  70. ///  * [SingleChildScrollView], which is a scrollable widget that has a single
  71. ///    child.
  72. ///  * [ScrollNotification] and [NotificationListener], which can be used to watch
  73. ///    the scroll position without using a [ScrollController].
  74. class Scrollable extends StatefulWidget {
  75.   /// Creates a widget that scrolls.
  76.   const Scrollable({
  77.     super.key,
  78.     this.axisDirection = AxisDirection.down,
  79.     this.controller,
  80.     this.physics,
  81.     required this.viewportBuilder,
  82.     this.incrementCalculator,
  83.     this.excludeFromSemantics = false,
  84.     this.semanticChildCount,
  85.     this.dragStartBehavior = DragStartBehavior.start,
  86.     this.restorationId,
  87.     this.scrollBehavior,
  88.     this.clipBehavior = Clip.hardEdge,
  89.   }) : assert(semanticChildCount == null || semanticChildCount >= 0);
  90.  
  91.   /// {@template flutter.widgets.Scrollable.axisDirection}
  92.   /// The direction in which this widget scrolls.
  93.   ///
  94.   /// For example, if the [Scrollable.axisDirection] is [AxisDirection.down],
  95.   /// increasing the scroll position will cause content below the bottom of the
  96.   /// viewport to become visible through the viewport. Similarly, if the
  97.   /// axisDirection is [AxisDirection.right], increasing the scroll position
  98.   /// will cause content beyond the right edge of the viewport to become visible
  99.   /// through the viewport.
  100.   ///
  101.   /// Defaults to [AxisDirection.down].
  102.   /// {@endtemplate}
  103.   final AxisDirection axisDirection;
  104.  
  105.   /// {@template flutter.widgets.Scrollable.controller}
  106.   /// An object that can be used to control the position to which this widget is
  107.   /// scrolled.
  108.   ///
  109.   /// A [ScrollController] serves several purposes. It can be used to control
  110.   /// the initial scroll position (see [ScrollController.initialScrollOffset]).
  111.   /// It can be used to control whether the scroll view should automatically
  112.   /// save and restore its scroll position in the [PageStorage] (see
  113.   /// [ScrollController.keepScrollOffset]). It can be used to read the current
  114.   /// scroll position (see [ScrollController.offset]), or change it (see
  115.   /// [ScrollController.animateTo]).
  116.   ///
  117.   /// If null, a [ScrollController] will be created internally by [Scrollable]
  118.   /// in order to create and manage the [ScrollPosition].
  119.   ///
  120.   /// See also:
  121.   ///
  122.   ///  * [Scrollable.ensureVisible], which animates the scroll position to
  123.   ///    reveal a given [BuildContext].
  124.   /// {@endtemplate}
  125.   final ScrollController? controller;
  126.  
  127.   /// {@template flutter.widgets.Scrollable.physics}
  128.   /// How the widgets should respond to user input.
  129.   ///
  130.   /// For example, determines how the widget continues to animate after the
  131.   /// user stops dragging the scroll view.
  132.   ///
  133.   /// Defaults to matching platform conventions via the physics provided from
  134.   /// the ambient [ScrollConfiguration].
  135.   ///
  136.   /// If an explicit [ScrollBehavior] is provided to
  137.   /// [Scrollable.scrollBehavior], the [ScrollPhysics] provided by that behavior
  138.   /// will take precedence after [Scrollable.physics].
  139.   ///
  140.   /// The physics can be changed dynamically, but new physics will only take
  141.   /// effect if the _class_ of the provided object changes. Merely constructing
  142.   /// a new instance with a different configuration is insufficient to cause the
  143.   /// physics to be reapplied. (This is because the final object used is
  144.   /// generated dynamically, which can be relatively expensive, and it would be
  145.   /// inefficient to speculatively create this object each frame to see if the
  146.   /// physics should be updated.)
  147.   ///
  148.   /// See also:
  149.   ///
  150.   ///  * [AlwaysScrollableScrollPhysics], which can be used to indicate that the
  151.   ///    scrollable should react to scroll requests (and possible overscroll)
  152.   ///    even if the scrollable's contents fit without scrolling being necessary.
  153.   /// {@endtemplate}
  154.   final ScrollPhysics? physics;
  155.  
  156.   /// Builds the viewport through which the scrollable content is displayed.
  157.   ///
  158.   /// A typical viewport uses the given [ViewportOffset] to determine which part
  159.   /// of its content is actually visible through the viewport.
  160.   ///
  161.   /// See also:
  162.   ///
  163.   ///  * [Viewport], which is a viewport that displays a list of slivers.
  164.   ///  * [ShrinkWrappingViewport], which is a viewport that displays a list of
  165.   ///    slivers and sizes itself based on the size of the slivers.
  166.   final ViewportBuilder viewportBuilder;
  167.  
  168.   /// {@template flutter.widgets.Scrollable.incrementCalculator}
  169.   /// An optional function that will be called to calculate the distance to
  170.   /// scroll when the scrollable is asked to scroll via the keyboard using a
  171.   /// [ScrollAction].
  172.   ///
  173.   /// If not supplied, the [Scrollable] will scroll a default amount when a
  174.   /// keyboard navigation key is pressed (e.g. pageUp/pageDown, control-upArrow,
  175.   /// etc.), or otherwise invoked by a [ScrollAction].
  176.   ///
  177.   /// If [incrementCalculator] is null, the default for
  178.   /// [ScrollIncrementType.page] is 80% of the size of the scroll window, and
  179.   /// for [ScrollIncrementType.line], 50 logical pixels.
  180.   /// {@endtemplate}
  181.   final ScrollIncrementCalculator? incrementCalculator;
  182.  
  183.   /// {@template flutter.widgets.scrollable.excludeFromSemantics}
  184.   /// Whether the scroll actions introduced by this [Scrollable] are exposed
  185.   /// in the semantics tree.
  186.   ///
  187.   /// Text fields with an overflow are usually scrollable to make sure that the
  188.   /// user can get to the beginning/end of the entered text. However, these
  189.   /// scrolling actions are generally not exposed to the semantics layer.
  190.   /// {@endtemplate}
  191.   ///
  192.   /// See also:
  193.   ///
  194.   ///  * [GestureDetector.excludeFromSemantics], which is used to accomplish the
  195.   ///    exclusion.
  196.   final bool excludeFromSemantics;
  197.  
  198.   /// The number of children that will contribute semantic information.
  199.   ///
  200.   /// The value will be null if the number of children is unknown or unbounded.
  201.   ///
  202.   /// Some subtypes of [ScrollView] can infer this value automatically. For
  203.   /// example [ListView] will use the number of widgets in the child list,
  204.   /// while the [ListView.separated] constructor will use half that amount.
  205.   ///
  206.   /// For [CustomScrollView] and other types which do not receive a builder
  207.   /// or list of widgets, the child count must be explicitly provided.
  208.   ///
  209.   /// See also:
  210.   ///
  211.   ///  * [CustomScrollView], for an explanation of scroll semantics.
  212.   ///  * [SemanticsConfiguration.scrollChildCount], the corresponding semantics property.
  213.   final int? semanticChildCount;
  214.  
  215.   // TODO(jslavitz): Set the DragStartBehavior default to be start across all widgets.
  216.   /// {@template flutter.widgets.scrollable.dragStartBehavior}
  217.   /// Determines the way that drag start behavior is handled.
  218.   ///
  219.   /// If set to [DragStartBehavior.start], scrolling drag behavior will
  220.   /// begin at the position where the drag gesture won the arena. If set to
  221.   /// [DragStartBehavior.down] it will begin at the position where a down
  222.   /// event is first detected.
  223.   ///
  224.   /// In general, setting this to [DragStartBehavior.start] will make drag
  225.   /// animation smoother and setting it to [DragStartBehavior.down] will make
  226.   /// drag behavior feel slightly more reactive.
  227.   ///
  228.   /// By default, the drag start behavior is [DragStartBehavior.start].
  229.   ///
  230.   /// See also:
  231.   ///
  232.   ///  * [DragGestureRecognizer.dragStartBehavior], which gives an example for
  233.   ///    the different behaviors.
  234.   ///
  235.   /// {@endtemplate}
  236.   final DragStartBehavior dragStartBehavior;
  237.  
  238.   /// {@template flutter.widgets.scrollable.restorationId}
  239.   /// Restoration ID to save and restore the scroll offset of the scrollable.
  240.   ///
  241.   /// If a restoration id is provided, the scrollable will persist its current
  242.   /// scroll offset and restore it during state restoration.
  243.   ///
  244.   /// The scroll offset is persisted in a [RestorationBucket] claimed from
  245.   /// the surrounding [RestorationScope] using the provided restoration ID.
  246.   ///
  247.   /// See also:
  248.   ///
  249.   ///  * [RestorationManager], which explains how state restoration works in
  250.   ///    Flutter.
  251.   /// {@endtemplate}
  252.   final String? restorationId;
  253.  
  254.   /// {@macro flutter.widgets.shadow.scrollBehavior}
  255.   ///
  256.   /// [ScrollBehavior]s also provide [ScrollPhysics]. If an explicit
  257.   /// [ScrollPhysics] is provided in [physics], it will take precedence,
  258.   /// followed by [scrollBehavior], and then the inherited ancestor
  259.   /// [ScrollBehavior].
  260.   final ScrollBehavior? scrollBehavior;
  261.  
  262.   /// {@macro flutter.material.Material.clipBehavior}
  263.   ///
  264.   /// Defaults to [Clip.hardEdge].
  265.   ///
  266.   /// This is passed to decorators in [ScrollableDetails], and does not directly affect
  267.   /// clipping of the [Scrollable]. This reflects the same [Clip] that is provided
  268.   /// to [ScrollView.clipBehavior] and is supplied to the [Viewport].
  269.   final Clip clipBehavior;
  270.  
  271.   /// The axis along which the scroll view scrolls.
  272.   ///
  273.   /// Determined by the [axisDirection].
  274.   Axis get axis => axisDirectionToAxis(axisDirection);
  275.  
  276.   @override
  277.   ScrollableState createState() => ScrollableState();
  278.  
  279.   @override
  280.   void debugFillProperties(DiagnosticPropertiesBuilder properties) {
  281.     super.debugFillProperties(properties);
  282.     properties.add(EnumProperty<AxisDirection>('axisDirection', axisDirection));
  283.     properties.add(DiagnosticsProperty<ScrollPhysics>('physics', physics));
  284.     properties.add(StringProperty('restorationId', restorationId));
  285.   }
  286.  
  287.   /// The state from the closest instance of this class that encloses the given
  288.   /// context, or null if none is found.
  289.   ///
  290.   /// Typical usage is as follows:
  291.   ///
  292.   /// ```dart
  293.   /// ScrollableState? scrollable = Scrollable.maybeOf(context);
  294.   /// ```
  295.   ///
  296.   /// Calling this method will create a dependency on the [ScrollableState]
  297.   /// that is returned, if there is one. This is typically the closest
  298.   /// [Scrollable], but may be a more distant ancestor if [axis] is used to
  299.   /// target a specific [Scrollable].
  300.   ///
  301.   /// Using the optional [Axis] is useful when Scrollables are nested and the
  302.   /// target [Scrollable] is not the closest instance. When [axis] is provided,
  303.   /// the nearest enclosing [ScrollableState] in that [Axis] is returned, or
  304.   /// null if there is none.
  305.   ///
  306.   /// This finds the nearest _ancestor_ [Scrollable] of the `context`. This
  307.   /// means that if the `context` is that of a [Scrollable], it will _not_ find
  308.   /// _that_ [Scrollable].
  309.   ///
  310.   /// See also:
  311.   ///
  312.   /// * [Scrollable.of], which is similar to this method, but asserts
  313.   ///   if no [Scrollable] ancestor is found.
  314.   static ScrollableState? maybeOf(BuildContext context, {Axis? axis}) {
  315.     // This is the context that will need to establish the dependency.
  316.     final BuildContext originalContext = context;
  317.     InheritedElement? element =
  318.         context.getElementForInheritedWidgetOfExactType<_ScrollableScope>();
  319.     while (element != null) {
  320.       final ScrollableState scrollable =
  321.           (element.widget as _ScrollableScope).scrollable;
  322.       if (axis == null ||
  323.           axisDirectionToAxis(scrollable.axisDirection) == axis) {
  324.         // Establish the dependency on the correct context.
  325.         originalContext.dependOnInheritedElement(element);
  326.         return scrollable;
  327.       }
  328.       context = scrollable.context;
  329.       element =
  330.           context.getElementForInheritedWidgetOfExactType<_ScrollableScope>();
  331.     }
  332.     return null;
  333.   }
  334.  
  335.   /// The state from the closest instance of this class that encloses the given
  336.   /// context.
  337.   ///
  338.   /// Typical usage is as follows:
  339.   ///
  340.   /// ```dart
  341.   /// ScrollableState scrollable = Scrollable.of(context);
  342.   /// ```
  343.   ///
  344.   /// Calling this method will create a dependency on the [ScrollableState]
  345.   /// that is returned, if there is one. This is typically the closest
  346.   /// [Scrollable], but may be a more distant ancestor if [axis] is used to
  347.   /// target a specific [Scrollable].
  348.   ///
  349.   /// Using the optional [Axis] is useful when Scrollables are nested and the
  350.   /// target [Scrollable] is not the closest instance. When [axis] is provided,
  351.   /// the nearest enclosing [ScrollableState] in that [Axis] is returned.
  352.   ///
  353.   /// This finds the nearest _ancestor_ [Scrollable] of the `context`. This
  354.   /// means that if the `context` is that of a [Scrollable], it will _not_ find
  355.   /// _that_ [Scrollable].
  356.   ///
  357.   /// If no [Scrollable] ancestor is found, then this method will assert in
  358.   /// debug mode, and throw an exception in release mode.
  359.   ///
  360.   /// See also:
  361.   ///
  362.   /// * [Scrollable.maybeOf], which is similar to this method, but returns null
  363.   ///   if no [Scrollable] ancestor is found.
  364.   static ScrollableState of(BuildContext context, {Axis? axis}) {
  365.     final ScrollableState? scrollableState = maybeOf(context, axis: axis);
  366.     assert(() {
  367.       if (scrollableState == null) {
  368.         throw FlutterError.fromParts(<DiagnosticsNode>[
  369.           ErrorSummary(
  370.             'Scrollable.of() was called with a context that does not contain a '
  371.             'Scrollable widget.',
  372.           ),
  373.           ErrorDescription(
  374.             'No Scrollable widget ancestor could be found '
  375.             '${axis == null ? '' : 'for the provided Axis: $axis '}'
  376.             'starting from the context that was passed to Scrollable.of(). This '
  377.             'can happen because you are using a widget that looks for a Scrollable '
  378.             'ancestor, but no such ancestor exists.\n'
  379.             'The context used was:\n'
  380.             '  $context',
  381.           ),
  382.           if (axis != null)
  383.             ErrorHint(
  384.               'When specifying an axis, this method will only look for a Scrollable '
  385.               'that matches the given Axis.',
  386.             ),
  387.         ]);
  388.       }
  389.       return true;
  390.     }());
  391.     return scrollableState!;
  392.   }
  393.  
  394.   /// Provides a heuristic to determine if expensive frame-bound tasks should be
  395.   /// deferred for the [context] at a specific point in time.
  396.   ///
  397.   /// Calling this method does _not_ create a dependency on any other widget.
  398.   /// This also means that the value returned is only good for the point in time
  399.   /// when it is called, and callers will not get updated if the value changes.
  400.   ///
  401.   /// The heuristic used is determined by the [physics] of this [Scrollable]
  402.   /// via [ScrollPhysics.recommendDeferredLoading]. That method is called with
  403.   /// the current [ScrollPosition.activity]'s [ScrollActivity.velocity].
  404.   ///
  405.   /// The optional [Axis] allows targeting of a specific [Scrollable] of that
  406.   /// axis, useful when Scrollables are nested. When [axis] is provided,
  407.   /// [ScrollPosition.recommendDeferredLoading] is called for the nearest
  408.   /// [Scrollable] in that [Axis].
  409.   ///
  410.   /// If there is no [Scrollable] in the widget tree above the [context], this
  411.   /// method returns false.
  412.   static bool recommendDeferredLoadingForContext(BuildContext context,
  413.       {Axis? axis}) {
  414.     _ScrollableScope? widget =
  415.         context.getInheritedWidgetOfExactType<_ScrollableScope>();
  416.     while (widget != null) {
  417.       if (axis == null ||
  418.           axisDirectionToAxis(widget.scrollable.axisDirection) == axis) {
  419.         return widget.position.recommendDeferredLoading(context);
  420.       }
  421.       context = widget.scrollable.context;
  422.       widget = context.getInheritedWidgetOfExactType<_ScrollableScope>();
  423.     }
  424.     return false;
  425.   }
  426.  
  427.   /// Scrolls the scrollables that enclose the given context so as to make the
  428.   /// given context visible.
  429.   ///
  430.   /// If the [Scrollable] of the provided [BuildContext] is a
  431.   /// [TwoDimensionalScrollable], both vertical and horizontal axes will ensure
  432.   /// the target is made visible.
  433.   static Future<void> ensureVisible(
  434.     BuildContext context, {
  435.     double alignment = 0.0,
  436.     Duration duration = Duration.zero,
  437.     Curve curve = Curves.ease,
  438.     ScrollPositionAlignmentPolicy alignmentPolicy =
  439.         ScrollPositionAlignmentPolicy.explicit,
  440.   }) {
  441.     final List<Future<void>> futures = <Future<void>>[];
  442.  
  443.     // The `targetRenderObject` is used to record the first target renderObject.
  444.     // If there are multiple scrollable widgets nested, we should let
  445.     // the `targetRenderObject` as visible as possible to improve the user experience.
  446.     // Otherwise, let the outer renderObject as visible as possible maybe cause
  447.     // the `targetRenderObject` invisible.
  448.     // Also see https://github.com/flutter/flutter/issues/65100
  449.     RenderObject? targetRenderObject;
  450.     ScrollableState? scrollable = Scrollable.maybeOf(context);
  451.     while (scrollable != null) {
  452.       final List<Future<void>> newFutures;
  453.       (newFutures, scrollable) = scrollable._performEnsureVisible(
  454.         context.findRenderObject()!,
  455.         alignment: alignment,
  456.         duration: duration,
  457.         curve: curve,
  458.         alignmentPolicy: alignmentPolicy,
  459.         targetRenderObject: targetRenderObject,
  460.       );
  461.       futures.addAll(newFutures);
  462.  
  463.       targetRenderObject = targetRenderObject ?? context.findRenderObject();
  464.       context = scrollable.context;
  465.       scrollable = Scrollable.maybeOf(context);
  466.     }
  467.  
  468.     if (futures.isEmpty || duration == Duration.zero) {
  469.       return Future<void>.value();
  470.     }
  471.     if (futures.length == 1) {
  472.       return futures.single;
  473.     }
  474.     return Future.wait<void>(futures).then<void>((List<void> _) => null);
  475.   }
  476. }
  477.  
  478. // Enable Scrollable.of() to work as if ScrollableState was an inherited widget.
  479. // ScrollableState.build() always rebuilds its _ScrollableScope.
  480. class _ScrollableScope extends InheritedWidget {
  481.   const _ScrollableScope({
  482.     required this.scrollable,
  483.     required this.position,
  484.     required super.child,
  485.   });
  486.  
  487.   final ScrollableState scrollable;
  488.   final ScrollPosition position;
  489.  
  490.   @override
  491.   bool updateShouldNotify(_ScrollableScope old) {
  492.     return position != old.position;
  493.   }
  494. }
  495.  
  496. /// State object for a [Scrollable] widget.
  497. ///
  498. /// To manipulate a [Scrollable] widget's scroll position, use the object
  499. /// obtained from the [position] property.
  500. ///
  501. /// To be informed of when a [Scrollable] widget is scrolling, use a
  502. /// [NotificationListener] to listen for [ScrollNotification] notifications.
  503. ///
  504. /// This class is not intended to be subclassed. To specialize the behavior of a
  505. /// [Scrollable], provide it with a [ScrollPhysics].
  506. class ScrollableState extends State<Scrollable>
  507.     with TickerProviderStateMixin, RestorationMixin
  508.     implements ScrollContext {
  509.   // GETTERS
  510.  
  511.   /// The manager for this [Scrollable] widget's viewport position.
  512.   ///
  513.   /// To control what kind of [ScrollPosition] is created for a [Scrollable],
  514.   /// provide it with custom [ScrollController] that creates the appropriate
  515.   /// [ScrollPosition] in its [ScrollController.createScrollPosition] method.
  516.   ScrollPosition get position => _position!;
  517.   ScrollPosition? _position;
  518.  
  519.   /// The resolved [ScrollPhysics] of the [ScrollableState].
  520.   ScrollPhysics? get resolvedPhysics => _physics;
  521.   ScrollPhysics? _physics;
  522.  
  523.   /// An [Offset] that represents the absolute distance from the origin, or 0,
  524.   /// of the [ScrollPosition] expressed in the associated [Axis].
  525.   ///
  526.   /// Used by [EdgeDraggingAutoScroller] to progress the position forward when a
  527.   /// drag gesture reaches the edge of the [Viewport].
  528.   Offset get deltaToScrollOrigin {
  529.     switch (axisDirection) {
  530.       case AxisDirection.down:
  531.         return Offset(0, position.pixels);
  532.       case AxisDirection.up:
  533.         return Offset(0, -position.pixels);
  534.       case AxisDirection.left:
  535.         return Offset(-position.pixels, 0);
  536.       case AxisDirection.right:
  537.         return Offset(position.pixels, 0);
  538.     }
  539.   }
  540.  
  541.   ScrollController get _effectiveScrollController =>
  542.       widget.controller ?? _fallbackScrollController!;
  543.  
  544.   @override
  545.   AxisDirection get axisDirection => widget.axisDirection;
  546.  
  547.   @override
  548.   TickerProvider get vsync => this;
  549.  
  550.   @override
  551.   double get devicePixelRatio => _devicePixelRatio;
  552.   late double _devicePixelRatio;
  553.  
  554.   @override
  555.   BuildContext? get notificationContext => _gestureDetectorKey.currentContext;
  556.  
  557.   @override
  558.   BuildContext get storageContext => context;
  559.  
  560.   @override
  561.   String? get restorationId => widget.restorationId;
  562.   final _RestorableScrollOffset _persistedScrollOffset =
  563.       _RestorableScrollOffset();
  564.  
  565.   late ScrollBehavior _configuration;
  566.   ScrollController? _fallbackScrollController;
  567.   DeviceGestureSettings? _mediaQueryGestureSettings;
  568.  
  569.   // Only call this from places that will definitely trigger a rebuild.
  570.   void _updatePosition() {
  571.     _configuration = widget.scrollBehavior ?? ScrollConfiguration.of(context);
  572.     _physics = _configuration.getScrollPhysics(context);
  573.     if (widget.physics != null) {
  574.       _physics = widget.physics!.applyTo(_physics);
  575.     } else if (widget.scrollBehavior != null) {
  576.       _physics =
  577.           widget.scrollBehavior!.getScrollPhysics(context).applyTo(_physics);
  578.     }
  579.     final ScrollPosition? oldPosition = _position;
  580.     if (oldPosition != null) {
  581.       _effectiveScrollController.detach(oldPosition);
  582.       // It's important that we not dispose the old position until after the
  583.       // viewport has had a chance to unregister its listeners from the old
  584.       // position. So, schedule a microtask to do it.
  585.       scheduleMicrotask(oldPosition.dispose);
  586.     }
  587.  
  588.     _position = _effectiveScrollController.createScrollPosition(
  589.         _physics!, this, oldPosition);
  590.     assert(_position != null);
  591.     _effectiveScrollController.attach(position);
  592.   }
  593.  
  594.   @override
  595.   void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
  596.     registerForRestoration(_persistedScrollOffset, 'offset');
  597.     assert(_position != null);
  598.     if (_persistedScrollOffset.value != null) {
  599.       position.restoreOffset(_persistedScrollOffset.value!,
  600.           initialRestore: initialRestore);
  601.     }
  602.   }
  603.  
  604.   @override
  605.   void saveOffset(double offset) {
  606.     assert(debugIsSerializableForRestoration(offset));
  607.     _persistedScrollOffset.value = offset;
  608.     // [saveOffset] is called after a scrolling ends and it is usually not
  609.     // followed by a frame. Therefore, manually flush restoration data.
  610.     ServicesBinding.instance.restorationManager.flushData();
  611.   }
  612.  
  613.   @override
  614.   void initState() {
  615.     if (widget.controller == null) {
  616.       _fallbackScrollController = ScrollController();
  617.     }
  618.     super.initState();
  619.   }
  620.  
  621.   @override
  622.   void didChangeDependencies() {
  623.     _mediaQueryGestureSettings = MediaQuery.maybeGestureSettingsOf(context);
  624.     _devicePixelRatio = MediaQuery.maybeDevicePixelRatioOf(context) ??
  625.         View.of(context).devicePixelRatio;
  626.     _updatePosition();
  627.     super.didChangeDependencies();
  628.   }
  629.  
  630.   bool _shouldUpdatePosition(Scrollable oldWidget) {
  631.     if ((widget.scrollBehavior == null) != (oldWidget.scrollBehavior == null)) {
  632.       return true;
  633.     }
  634.     if (widget.scrollBehavior != null &&
  635.         oldWidget.scrollBehavior != null &&
  636.         widget.scrollBehavior!.shouldNotify(oldWidget.scrollBehavior!)) {
  637.       return true;
  638.     }
  639.     ScrollPhysics? newPhysics =
  640.         widget.physics ?? widget.scrollBehavior?.getScrollPhysics(context);
  641.     ScrollPhysics? oldPhysics = oldWidget.physics ??
  642.         oldWidget.scrollBehavior?.getScrollPhysics(context);
  643.     do {
  644.       if (newPhysics?.runtimeType != oldPhysics?.runtimeType) {
  645.         return true;
  646.       }
  647.       newPhysics = newPhysics?.parent;
  648.       oldPhysics = oldPhysics?.parent;
  649.     } while (newPhysics != null || oldPhysics != null);
  650.  
  651.     return widget.controller?.runtimeType != oldWidget.controller?.runtimeType;
  652.   }
  653.  
  654.   @override
  655.   void didUpdateWidget(Scrollable oldWidget) {
  656.     super.didUpdateWidget(oldWidget);
  657.  
  658.     if (widget.controller != oldWidget.controller) {
  659.       if (oldWidget.controller == null) {
  660.         // The old controller was null, meaning the fallback cannot be null.
  661.         // Dispose of the fallback.
  662.         assert(_fallbackScrollController != null);
  663.         assert(widget.controller != null);
  664.         _fallbackScrollController!.detach(position);
  665.         _fallbackScrollController!.dispose();
  666.         _fallbackScrollController = null;
  667.       } else {
  668.         // The old controller was not null, detach.
  669.         oldWidget.controller?.detach(position);
  670.         if (widget.controller == null) {
  671.           // If the new controller is null, we need to set up the fallback
  672.           // ScrollController.
  673.           _fallbackScrollController = ScrollController();
  674.         }
  675.       }
  676.       // Attach the updated effective scroll controller.
  677.       _effectiveScrollController.attach(position);
  678.     }
  679.  
  680.     if (_shouldUpdatePosition(oldWidget)) {
  681.       _updatePosition();
  682.     }
  683.   }
  684.  
  685.   @override
  686.   void dispose() {
  687.     if (widget.controller != null) {
  688.       widget.controller!.detach(position);
  689.     } else {
  690.       _fallbackScrollController?.detach(position);
  691.       _fallbackScrollController?.dispose();
  692.     }
  693.  
  694.     position.dispose();
  695.     _persistedScrollOffset.dispose();
  696.     super.dispose();
  697.   }
  698.  
  699.   // SEMANTICS
  700.  
  701.   final GlobalKey _scrollSemanticsKey = GlobalKey();
  702.  
  703.   @override
  704.   @protected
  705.   void setSemanticsActions(Set<SemanticsAction> actions) {
  706.     if (_gestureDetectorKey.currentState != null) {
  707.       _gestureDetectorKey.currentState!.replaceSemanticsActions(actions);
  708.     }
  709.   }
  710.  
  711.   // GESTURE RECOGNITION AND POINTER IGNORING
  712.  
  713.   final GlobalKey<RawGestureDetectorState> _gestureDetectorKey =
  714.       GlobalKey<RawGestureDetectorState>();
  715.   final GlobalKey _ignorePointerKey = GlobalKey();
  716.  
  717.   // This field is set during layout, and then reused until the next time it is set.
  718.   Map<Type, GestureRecognizerFactory> _gestureRecognizers =
  719.       const <Type, GestureRecognizerFactory>{};
  720.   bool _shouldIgnorePointer = false;
  721.  
  722.   bool? _lastCanDrag;
  723.   Axis? _lastAxisDirection;
  724.  
  725.   @override
  726.   @protected
  727.   void setCanDrag(bool value) {
  728.     if (value == _lastCanDrag &&
  729.         (!value || widget.axis == _lastAxisDirection)) {
  730.       return;
  731.     }
  732.     if (!value) {
  733.       _gestureRecognizers = const <Type, GestureRecognizerFactory>{};
  734.       // Cancel the active hold/drag (if any) because the gesture recognizers
  735.       // will soon be disposed by our RawGestureDetector, and we won't be
  736.       // receiving pointer up events to cancel the hold/drag.
  737.       _handleDragCancel();
  738.     } else {
  739.       switch (widget.axis) {
  740.         case Axis.vertical:
  741.           _gestureRecognizers = <Type, GestureRecognizerFactory>{
  742.             VerticalDragGestureRecognizer: GestureRecognizerFactoryWithHandlers<
  743.                 VerticalDragGestureRecognizer>(
  744.               () => VerticalDragGestureRecognizer(
  745.                   supportedDevices: _configuration.dragDevices),
  746.               (VerticalDragGestureRecognizer instance) {
  747.                 instance
  748.                   ..onDown = _handleDragDown
  749.                   ..onStart = _handleDragStart
  750.                   ..onUpdate = _handleDragUpdate
  751.                   ..onEnd = _handleDragEnd
  752.                   ..onCancel = _handleDragCancel
  753.                   ..minFlingDistance = _physics?.minFlingDistance
  754.                   ..minFlingVelocity = _physics?.minFlingVelocity
  755.                   ..maxFlingVelocity = _physics?.maxFlingVelocity
  756.                   ..velocityTrackerBuilder =
  757.                       _configuration.velocityTrackerBuilder(context)
  758.                   ..dragStartBehavior = widget.dragStartBehavior
  759.                   ..gestureSettings = _mediaQueryGestureSettings
  760.                   ..supportedDevices = _configuration.dragDevices;
  761.               },
  762.             ),
  763.           };
  764.         case Axis.horizontal:
  765.           _gestureRecognizers = <Type, GestureRecognizerFactory>{
  766.             HorizontalDragGestureRecognizer:
  767.                 GestureRecognizerFactoryWithHandlers<
  768.                     HorizontalDragGestureRecognizer>(
  769.               () => HorizontalDragGestureRecognizer(
  770.                   supportedDevices: _configuration.dragDevices),
  771.               (HorizontalDragGestureRecognizer instance) {
  772.                 instance
  773.                   ..onDown = _handleDragDown
  774.                   ..onStart = _handleDragStart
  775.                   ..onUpdate = _handleDragUpdate
  776.                   ..onEnd = _handleDragEnd
  777.                   ..onCancel = _handleDragCancel
  778.                   ..minFlingDistance = _physics?.minFlingDistance
  779.                   ..minFlingVelocity = _physics?.minFlingVelocity
  780.                   ..maxFlingVelocity = _physics?.maxFlingVelocity
  781.                   ..velocityTrackerBuilder =
  782.                       _configuration.velocityTrackerBuilder(context)
  783.                   ..dragStartBehavior = widget.dragStartBehavior
  784.                   ..gestureSettings = _mediaQueryGestureSettings
  785.                   ..supportedDevices = _configuration.dragDevices;
  786.               },
  787.             ),
  788.           };
  789.       }
  790.     }
  791.     _lastCanDrag = value;
  792.     _lastAxisDirection = widget.axis;
  793.     if (_gestureDetectorKey.currentState != null) {
  794.       _gestureDetectorKey.currentState!
  795.           .replaceGestureRecognizers(_gestureRecognizers);
  796.     }
  797.   }
  798.  
  799.   @override
  800.   @protected
  801.   void setIgnorePointer(bool value) {
  802.     if (_shouldIgnorePointer == value) {
  803.       return;
  804.     }
  805.     _shouldIgnorePointer = value;
  806.     if (_ignorePointerKey.currentContext != null) {
  807.       final RenderIgnorePointer renderBox = _ignorePointerKey.currentContext!
  808.           .findRenderObject()! as RenderIgnorePointer;
  809.       renderBox.ignoring = _shouldIgnorePointer;
  810.     }
  811.   }
  812.  
  813.   // TOUCH HANDLERS
  814.  
  815.   Drag? _drag;
  816.   ScrollHoldController? _hold;
  817.  
  818.   void _handleDragDown(DragDownDetails details) {
  819.     assert(_drag == null);
  820.     assert(_hold == null);
  821.     _hold = position.hold(_disposeHold);
  822.   }
  823.  
  824.   void _handleDragStart(DragStartDetails details) {
  825.     // It's possible for _hold to become null between _handleDragDown and
  826.     // _handleDragStart, for example if some user code calls jumpTo or otherwise
  827.     // triggers a new activity to begin.
  828.     assert(_drag == null);
  829.     _drag = position.drag(details, _disposeDrag);
  830.     assert(_drag != null);
  831.     assert(_hold == null);
  832.   }
  833.  
  834.   void _handleDragUpdate(DragUpdateDetails details) {
  835.     // _drag might be null if the drag activity ended and called _disposeDrag.
  836.     assert(_hold == null || _drag == null);
  837.     _drag?.update(details);
  838.   }
  839.  
  840.   void _handleDragEnd(DragEndDetails details) {
  841.     // _drag might be null if the drag activity ended and called _disposeDrag.
  842.     assert(_hold == null || _drag == null);
  843.     _drag?.end(details);
  844.     assert(_drag == null);
  845.   }
  846.  
  847.   void _handleDragCancel() {
  848.     if (_gestureDetectorKey.currentContext == null) {
  849.       // The cancel was caused by the GestureDetector getting disposed, which
  850.       // means we will get disposed momentarily as well and shouldn't do
  851.       // any work.
  852.       return;
  853.     }
  854.     // _hold might be null if the drag started.
  855.     // _drag might be null if the drag activity ended and called _disposeDrag.
  856.     assert(_hold == null || _drag == null);
  857.     _hold?.cancel();
  858.     _drag?.cancel();
  859.     assert(_hold == null);
  860.     assert(_drag == null);
  861.   }
  862.  
  863.   void _disposeHold() {
  864.     _hold = null;
  865.   }
  866.  
  867.   void _disposeDrag() {
  868.     _drag = null;
  869.   }
  870.  
  871.   // SCROLL WHEEL
  872.  
  873.   // Returns the offset that should result from applying [event] to the current
  874.   // position, taking min/max scroll extent into account.
  875.   double _targetScrollOffsetForPointerScroll(double delta) {
  876.     return math.min(
  877.       math.max(position.pixels + delta, position.minScrollExtent),
  878.       position.maxScrollExtent,
  879.     );
  880.   }
  881.  
  882.   // Returns the delta that should result from applying [event] with axis,
  883.   // direction, and any modifiers specified by the ScrollBehavior taken into
  884.   // account.
  885.   double _pointerSignalEventDelta(PointerScrollEvent event) {
  886.     late double delta;
  887.     final Set<LogicalKeyboardKey> pressed =
  888.         HardwareKeyboard.instance.logicalKeysPressed;
  889.     final bool flipAxes = pressed
  890.             .any(_configuration.pointerAxisModifiers.contains) &&
  891.         // Axes are only flipped for physical mouse wheel input.
  892.         // On some platforms, like web, trackpad input is handled through pointer
  893.         // signals, but should not be included in this axis modifying behavior.
  894.         // This is because on a trackpad, all directional axes are available to
  895.         // the user, while mouse scroll wheels typically are restricted to one
  896.         // axis.
  897.         event.kind == PointerDeviceKind.mouse;
  898.  
  899.     switch (widget.axis) {
  900.       case Axis.horizontal:
  901.         delta = flipAxes ? event.scrollDelta.dy : event.scrollDelta.dx;
  902.       case Axis.vertical:
  903.         delta = flipAxes ? event.scrollDelta.dx : event.scrollDelta.dy;
  904.     }
  905.  
  906.     if (axisDirectionIsReversed(widget.axisDirection)) {
  907.       delta *= -1;
  908.     }
  909.     return delta;
  910.   }
  911.  
  912.   void _receivedPointerSignal(PointerSignalEvent event) {
  913.     if (event is PointerScrollEvent && _position != null) {
  914.       if (_physics != null && !_physics!.shouldAcceptUserOffset(position)) {
  915.         return;
  916.       }
  917.       final double delta = _pointerSignalEventDelta(event);
  918.       final double targetScrollOffset =
  919.           _targetScrollOffsetForPointerScroll(delta);
  920.       // Only express interest in the event if it would actually result in a scroll.
  921.       if (delta != 0.0 && targetScrollOffset != position.pixels) {
  922.         GestureBinding.instance.pointerSignalResolver
  923.             .register(event, _handlePointerScroll);
  924.       }
  925.     } else if (event is PointerScrollInertiaCancelEvent) {
  926.       position.pointerScroll(0);
  927.       // Don't use the pointer signal resolver, all hit-tested scrollables should stop.
  928.     }
  929.   }
  930.  
  931.   void _handlePointerScroll(PointerEvent event) {
  932.     assert(event is PointerScrollEvent);
  933.     final double delta = _pointerSignalEventDelta(event as PointerScrollEvent);
  934.     final double targetScrollOffset =
  935.         _targetScrollOffsetForPointerScroll(delta);
  936.     if (delta != 0.0 && targetScrollOffset != position.pixels) {
  937.       position.pointerScroll(delta);
  938.     }
  939.   }
  940.  
  941.   bool _handleScrollMetricsNotification(
  942.       ScrollMetricsNotification notification) {
  943.     if (notification.depth == 0) {
  944.       final RenderObject? scrollSemanticsRenderObject =
  945.           _scrollSemanticsKey.currentContext?.findRenderObject();
  946.       if (scrollSemanticsRenderObject != null) {
  947.         scrollSemanticsRenderObject.markNeedsSemanticsUpdate();
  948.       }
  949.     }
  950.     return false;
  951.   }
  952.  
  953.   Widget _buildChrome(BuildContext context, Widget child) {
  954.     final ScrollableDetails details = ScrollableDetails(
  955.       direction: widget.axisDirection,
  956.       controller: _effectiveScrollController,
  957.       decorationClipBehavior: widget.clipBehavior,
  958.     );
  959.  
  960.     return _configuration.buildScrollbar(
  961.       context,
  962.       _configuration.buildOverscrollIndicator(context, child, details),
  963.       details,
  964.     );
  965.   }
  966.  
  967.   // DESCRIPTION
  968.  
  969.   @override
  970.   Widget build(BuildContext context) {
  971.     assert(_position != null);
  972.     // _ScrollableScope must be placed above the BuildContext returned by notificationContext
  973.     // so that we can get this ScrollableState by doing the following:
  974.     //
  975.     // ScrollNotification notification;
  976.     // Scrollable.of(notification.context)
  977.     //
  978.     // Since notificationContext is pointing to _gestureDetectorKey.context, _ScrollableScope
  979.     // must be placed above the widget using it: RawGestureDetector
  980.     Widget result = _ScrollableScope(
  981.       scrollable: this,
  982.       position: position,
  983.       child: Listener(
  984.         onPointerSignal: _receivedPointerSignal,
  985.         child: RawGestureDetector(
  986.           key: _gestureDetectorKey,
  987.           gestures: _gestureRecognizers,
  988.           behavior: HitTestBehavior.opaque,
  989.           excludeFromSemantics: widget.excludeFromSemantics,
  990.           child: Semantics(
  991.             explicitChildNodes: !widget.excludeFromSemantics,
  992.             child: IgnorePointer(
  993.               key: _ignorePointerKey,
  994.               ignoring: _shouldIgnorePointer,
  995.               child: widget.viewportBuilder(context, position),
  996.             ),
  997.           ),
  998.         ),
  999.       ),
  1000.     );
  1001.  
  1002.     if (!widget.excludeFromSemantics) {
  1003.       result = NotificationListener<ScrollMetricsNotification>(
  1004.           onNotification: _handleScrollMetricsNotification,
  1005.           child: _ScrollSemantics(
  1006.             key: _scrollSemanticsKey,
  1007.             position: position,
  1008.             allowImplicitScrolling: _physics!.allowImplicitScrolling,
  1009.             semanticChildCount: widget.semanticChildCount,
  1010.             child: result,
  1011.           ));
  1012.     }
  1013.  
  1014.     result = _buildChrome(context, result);
  1015.  
  1016.     // Selection is only enabled when there is a parent registrar.
  1017.     final SelectionRegistrar? registrar = SelectionContainer.maybeOf(context);
  1018.     if (registrar != null) {
  1019.       result = _ScrollableSelectionHandler(
  1020.         state: this,
  1021.         position: position,
  1022.         registrar: registrar,
  1023.         child: result,
  1024.       );
  1025.     }
  1026.  
  1027.     return result;
  1028.   }
  1029.  
  1030.   // Returns the Future from calling ensureVisible for the ScrollPosition, as
  1031.   // as well as this ScrollableState instance so its context can be used to
  1032.   // check for other ancestor Scrollables in executing ensureVisible.
  1033.   _EnsureVisibleResults _performEnsureVisible(
  1034.     RenderObject object, {
  1035.     double alignment = 0.0,
  1036.     Duration duration = Duration.zero,
  1037.     Curve curve = Curves.ease,
  1038.     ScrollPositionAlignmentPolicy alignmentPolicy =
  1039.         ScrollPositionAlignmentPolicy.explicit,
  1040.     RenderObject? targetRenderObject,
  1041.   }) {
  1042.     final Future<void> ensureVisibleFuture = position.ensureVisible(
  1043.       object,
  1044.       alignment: alignment,
  1045.       duration: duration,
  1046.       curve: curve,
  1047.       alignmentPolicy: alignmentPolicy,
  1048.       targetRenderObject: targetRenderObject,
  1049.     );
  1050.     return (<Future<void>>[ensureVisibleFuture], this);
  1051.   }
  1052.  
  1053.   @override
  1054.   void debugFillProperties(DiagnosticPropertiesBuilder properties) {
  1055.     super.debugFillProperties(properties);
  1056.     properties.add(DiagnosticsProperty<ScrollPosition>('position', _position));
  1057.     properties
  1058.         .add(DiagnosticsProperty<ScrollPhysics>('effective physics', _physics));
  1059.   }
  1060. }
  1061.  
  1062. /// A widget to handle selection for a scrollable.
  1063. ///
  1064. /// This widget registers itself to the [registrar] and uses
  1065. /// [SelectionContainer] to collect selectables from its subtree.
  1066. class _ScrollableSelectionHandler extends StatefulWidget {
  1067.   const _ScrollableSelectionHandler({
  1068.     required this.state,
  1069.     required this.position,
  1070.     required this.registrar,
  1071.     required this.child,
  1072.   });
  1073.  
  1074.   final ScrollableState state;
  1075.   final ScrollPosition position;
  1076.   final Widget child;
  1077.   final SelectionRegistrar registrar;
  1078.  
  1079.   @override
  1080.   _ScrollableSelectionHandlerState createState() =>
  1081.       _ScrollableSelectionHandlerState();
  1082. }
  1083.  
  1084. class _ScrollableSelectionHandlerState
  1085.     extends State<_ScrollableSelectionHandler> {
  1086.   late _ScrollableSelectionContainerDelegate _selectionDelegate;
  1087.  
  1088.   @override
  1089.   void initState() {
  1090.     super.initState();
  1091.     _selectionDelegate = _ScrollableSelectionContainerDelegate(
  1092.       state: widget.state,
  1093.       position: widget.position,
  1094.     );
  1095.   }
  1096.  
  1097.   @override
  1098.   void didUpdateWidget(_ScrollableSelectionHandler oldWidget) {
  1099.     super.didUpdateWidget(oldWidget);
  1100.     if (oldWidget.position != widget.position) {
  1101.       _selectionDelegate.position = widget.position;
  1102.     }
  1103.   }
  1104.  
  1105.   @override
  1106.   void dispose() {
  1107.     _selectionDelegate.dispose();
  1108.     super.dispose();
  1109.   }
  1110.  
  1111.   @override
  1112.   Widget build(BuildContext context) {
  1113.     return SelectionContainer(
  1114.       registrar: widget.registrar,
  1115.       delegate: _selectionDelegate,
  1116.       child: widget.child,
  1117.     );
  1118.   }
  1119. }
  1120.  
  1121. /// This updater handles the case where the selectables change frequently, and
  1122. /// it optimizes toward scrolling updates.
  1123. ///
  1124. /// It keeps track of the drag start offset relative to scroll origin for every
  1125. /// selectable. The records are used to determine whether the selection is up to
  1126. /// date with the scroll position when it sends the drag update event to a
  1127. /// selectable.
  1128. class _ScrollableSelectionContainerDelegate
  1129.     extends MultiSelectableSelectionContainerDelegate {
  1130.   _ScrollableSelectionContainerDelegate(
  1131.       {required this.state, required ScrollPosition position})
  1132.       : _position = position,
  1133.         _autoScroller = EdgeDraggingAutoScroller(state,
  1134.             velocityScalar: _kDefaultSelectToScrollVelocityScalar) {
  1135.     _position.addListener(_scheduleLayoutChange);
  1136.   }
  1137.  
  1138.   // Pointer drag is a single point, it should not have a size.
  1139.   static const double _kDefaultDragTargetSize = 0;
  1140.  
  1141.   // An eye-balled value for a smooth scrolling speed.
  1142.   static const double _kDefaultSelectToScrollVelocityScalar = 30;
  1143.  
  1144.   final ScrollableState state;
  1145.   final EdgeDraggingAutoScroller _autoScroller;
  1146.   bool _scheduledLayoutChange = false;
  1147.   Offset? _currentDragStartRelatedToOrigin;
  1148.   Offset? _currentDragEndRelatedToOrigin;
  1149.  
  1150.   // The scrollable only auto scrolls if the selection starts in the scrollable.
  1151.   bool _selectionStartsInScrollable = false;
  1152.  
  1153.   ScrollPosition get position => _position;
  1154.   ScrollPosition _position;
  1155.   set position(ScrollPosition other) {
  1156.     if (other == _position) {
  1157.       return;
  1158.     }
  1159.     _position.removeListener(_scheduleLayoutChange);
  1160.     _position = other;
  1161.     _position.addListener(_scheduleLayoutChange);
  1162.   }
  1163.  
  1164.   // The layout will only be updated a frame later than position changes.
  1165.   // Schedule PostFrameCallback to capture the accurate layout.
  1166.   void _scheduleLayoutChange() {
  1167.     if (_scheduledLayoutChange) {
  1168.       return;
  1169.     }
  1170.     _scheduledLayoutChange = true;
  1171.     SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) {
  1172.       if (!_scheduledLayoutChange) {
  1173.         return;
  1174.       }
  1175.       _scheduledLayoutChange = false;
  1176.       layoutDidChange();
  1177.     });
  1178.   }
  1179.  
  1180.   /// Stores the scroll offset when a scrollable receives the last
  1181.   /// [SelectionEdgeUpdateEvent].
  1182.   ///
  1183.   /// The stored scroll offset may be null if a scrollable never receives a
  1184.   /// [SelectionEdgeUpdateEvent].
  1185.   ///
  1186.   /// When a new [SelectionEdgeUpdateEvent] is dispatched to a selectable, this
  1187.   /// updater checks the current scroll offset against the one stored in these
  1188.   /// records. If the scroll offset is different, it synthesizes an opposite
  1189.   /// [SelectionEdgeUpdateEvent] and dispatches the event before dispatching the
  1190.   /// new event.
  1191.   ///
  1192.   /// For example, if a selectable receives an end [SelectionEdgeUpdateEvent]
  1193.   /// and its scroll offset in the records is different from the current value,
  1194.   /// it synthesizes a start [SelectionEdgeUpdateEvent] and dispatches it before
  1195.   /// dispatching the original end [SelectionEdgeUpdateEvent].
  1196.   final Map<Selectable, double> _selectableStartEdgeUpdateRecords =
  1197.       <Selectable, double>{};
  1198.   final Map<Selectable, double> _selectableEndEdgeUpdateRecords =
  1199.       <Selectable, double>{};
  1200.  
  1201.   @override
  1202.   void didChangeSelectables() {
  1203.     final Set<Selectable> selectableSet = selectables.toSet();
  1204.     _selectableStartEdgeUpdateRecords.removeWhere(
  1205.         (Selectable key, double value) => !selectableSet.contains(key));
  1206.     _selectableEndEdgeUpdateRecords.removeWhere(
  1207.         (Selectable key, double value) => !selectableSet.contains(key));
  1208.     super.didChangeSelectables();
  1209.   }
  1210.  
  1211.   @override
  1212.   SelectionResult handleClearSelection(ClearSelectionEvent event) {
  1213.     _selectableStartEdgeUpdateRecords.clear();
  1214.     _selectableEndEdgeUpdateRecords.clear();
  1215.     _currentDragStartRelatedToOrigin = null;
  1216.     _currentDragEndRelatedToOrigin = null;
  1217.     _selectionStartsInScrollable = false;
  1218.     return super.handleClearSelection(event);
  1219.   }
  1220.  
  1221.   @override
  1222.   SelectionResult handleSelectionEdgeUpdate(SelectionEdgeUpdateEvent event) {
  1223.     if (_currentDragEndRelatedToOrigin == null &&
  1224.         _currentDragStartRelatedToOrigin == null) {
  1225.       assert(!_selectionStartsInScrollable);
  1226.       _selectionStartsInScrollable =
  1227.           _globalPositionInScrollable(event.globalPosition);
  1228.     }
  1229.     final Offset deltaToOrigin = _getDeltaToScrollOrigin(state);
  1230.     if (event.type == SelectionEventType.endEdgeUpdate) {
  1231.       _currentDragEndRelatedToOrigin =
  1232.           _inferPositionRelatedToOrigin(event.globalPosition);
  1233.       final Offset endOffset = _currentDragEndRelatedToOrigin!
  1234.           .translate(-deltaToOrigin.dx, -deltaToOrigin.dy);
  1235.       event = SelectionEdgeUpdateEvent.forEnd(
  1236.           globalPosition: endOffset, granularity: event.granularity);
  1237.     } else {
  1238.       _currentDragStartRelatedToOrigin =
  1239.           _inferPositionRelatedToOrigin(event.globalPosition);
  1240.       final Offset startOffset = _currentDragStartRelatedToOrigin!
  1241.           .translate(-deltaToOrigin.dx, -deltaToOrigin.dy);
  1242.       event = SelectionEdgeUpdateEvent.forStart(
  1243.           globalPosition: startOffset, granularity: event.granularity);
  1244.     }
  1245.     final SelectionResult result = super.handleSelectionEdgeUpdate(event);
  1246.  
  1247.     // Result may be pending if one of the selectable child is also a scrollable.
  1248.     // In that case, the parent scrollable needs to wait for the child to finish
  1249.     // scrolling.
  1250.     if (result == SelectionResult.pending) {
  1251.       _autoScroller.stopAutoScroll();
  1252.       return result;
  1253.     }
  1254.     if (_selectionStartsInScrollable) {
  1255.       _autoScroller.startAutoScrollIfNecessary(_dragTargetFromEvent(event));
  1256.       if (_autoScroller.scrolling) {
  1257.         return SelectionResult.pending;
  1258.       }
  1259.     }
  1260.     return result;
  1261.   }
  1262.  
  1263.   Offset _inferPositionRelatedToOrigin(Offset globalPosition) {
  1264.     final RenderBox box = state.context.findRenderObject()! as RenderBox;
  1265.     final Offset localPosition = box.globalToLocal(globalPosition);
  1266.     if (!_selectionStartsInScrollable) {
  1267.       // If the selection starts outside of the scrollable, selecting across the
  1268.       // scrollable boundary will act as selecting the entire content in the
  1269.       // scrollable. This logic move the offset to the 0.0 or infinity to cover
  1270.       // the entire content if the input position is outside of the scrollable.
  1271.       if (localPosition.dy < 0 || localPosition.dx < 0) {
  1272.         return box.localToGlobal(Offset.zero);
  1273.       }
  1274.       if (localPosition.dy > box.size.height ||
  1275.           localPosition.dx > box.size.width) {
  1276.         return Offset.infinite;
  1277.       }
  1278.     }
  1279.     final Offset deltaToOrigin = _getDeltaToScrollOrigin(state);
  1280.     return box.localToGlobal(
  1281.         localPosition.translate(deltaToOrigin.dx, deltaToOrigin.dy));
  1282.   }
  1283.  
  1284.   /// Infers the [_currentDragStartRelatedToOrigin] and
  1285.   /// [_currentDragEndRelatedToOrigin] from the geometry.
  1286.   ///
  1287.   /// This method is called after a select word and select all event where the
  1288.   /// selection is triggered by none drag events. The
  1289.   /// [_currentDragStartRelatedToOrigin] and [_currentDragEndRelatedToOrigin]
  1290.   /// are essential to handle future [SelectionEdgeUpdateEvent]s.
  1291.   void _updateDragLocationsFromGeometries(
  1292.       {bool forceUpdateStart = true, bool forceUpdateEnd = true}) {
  1293.     final Offset deltaToOrigin = _getDeltaToScrollOrigin(state);
  1294.     final RenderBox box = state.context.findRenderObject()! as RenderBox;
  1295.     final Matrix4 transform = box.getTransformTo(null);
  1296.     if (currentSelectionStartIndex != -1 &&
  1297.         (_currentDragStartRelatedToOrigin == null || forceUpdateStart)) {
  1298.       final SelectionGeometry geometry =
  1299.           selectables[currentSelectionStartIndex].value;
  1300.       assert(geometry.hasSelection);
  1301.       final SelectionPoint start = geometry.startSelectionPoint!;
  1302.       final Matrix4 childTransform =
  1303.           selectables[currentSelectionStartIndex].getTransformTo(box);
  1304.       final Offset localDragStart = MatrixUtils.transformPoint(
  1305.         childTransform,
  1306.         start.localPosition + Offset(0, -start.lineHeight / 2),
  1307.       );
  1308.       _currentDragStartRelatedToOrigin =
  1309.           MatrixUtils.transformPoint(transform, localDragStart + deltaToOrigin);
  1310.     }
  1311.     if (currentSelectionEndIndex != -1 &&
  1312.         (_currentDragEndRelatedToOrigin == null || forceUpdateEnd)) {
  1313.       final SelectionGeometry geometry =
  1314.           selectables[currentSelectionEndIndex].value;
  1315.       assert(geometry.hasSelection);
  1316.       final SelectionPoint end = geometry.endSelectionPoint!;
  1317.       final Matrix4 childTransform =
  1318.           selectables[currentSelectionEndIndex].getTransformTo(box);
  1319.       final Offset localDragEnd = MatrixUtils.transformPoint(
  1320.         childTransform,
  1321.         end.localPosition + Offset(0, -end.lineHeight / 2),
  1322.       );
  1323.       _currentDragEndRelatedToOrigin =
  1324.           MatrixUtils.transformPoint(transform, localDragEnd + deltaToOrigin);
  1325.     }
  1326.   }
  1327.  
  1328.   @override
  1329.   SelectionResult handleSelectAll(SelectAllSelectionEvent event) {
  1330.     assert(!_selectionStartsInScrollable);
  1331.     final SelectionResult result = super.handleSelectAll(event);
  1332.     assert(
  1333.         (currentSelectionStartIndex == -1) == (currentSelectionEndIndex == -1));
  1334.     if (currentSelectionStartIndex != -1) {
  1335.       _updateDragLocationsFromGeometries();
  1336.     }
  1337.     return result;
  1338.   }
  1339.  
  1340.   @override
  1341.   SelectionResult handleSelectWord(SelectWordSelectionEvent event) {
  1342.     _selectionStartsInScrollable =
  1343.         _globalPositionInScrollable(event.globalPosition);
  1344.     final SelectionResult result = super.handleSelectWord(event);
  1345.     _updateDragLocationsFromGeometries();
  1346.     return result;
  1347.   }
  1348.  
  1349.   @override
  1350.   SelectionResult handleGranularlyExtendSelection(
  1351.       GranularlyExtendSelectionEvent event) {
  1352.     final SelectionResult result = super.handleGranularlyExtendSelection(event);
  1353.     // The selection geometry may not have the accurate offset for the edges
  1354.     // that are outside of the viewport whose transform may not be valid. Only
  1355.     // the edge this event is updating is sure to be accurate.
  1356.     _updateDragLocationsFromGeometries(
  1357.       forceUpdateStart: !event.isEnd,
  1358.       forceUpdateEnd: event.isEnd,
  1359.     );
  1360.     if (_selectionStartsInScrollable) {
  1361.       _jumpToEdge(event.isEnd);
  1362.     }
  1363.     return result;
  1364.   }
  1365.  
  1366.   @override
  1367.   SelectionResult handleDirectionallyExtendSelection(
  1368.       DirectionallyExtendSelectionEvent event) {
  1369.     final SelectionResult result =
  1370.         super.handleDirectionallyExtendSelection(event);
  1371.     // The selection geometry may not have the accurate offset for the edges
  1372.     // that are outside of the viewport whose transform may not be valid. Only
  1373.     // the edge this event is updating is sure to be accurate.
  1374.     _updateDragLocationsFromGeometries(
  1375.       forceUpdateStart: !event.isEnd,
  1376.       forceUpdateEnd: event.isEnd,
  1377.     );
  1378.     if (_selectionStartsInScrollable) {
  1379.       _jumpToEdge(event.isEnd);
  1380.     }
  1381.     return result;
  1382.   }
  1383.  
  1384.   void _jumpToEdge(bool isExtent) {
  1385.     final Selectable selectable;
  1386.     final double? lineHeight;
  1387.     final SelectionPoint? edge;
  1388.     if (isExtent) {
  1389.       selectable = selectables[currentSelectionEndIndex];
  1390.       edge = selectable.value.endSelectionPoint;
  1391.       lineHeight = selectable.value.endSelectionPoint!.lineHeight;
  1392.     } else {
  1393.       selectable = selectables[currentSelectionStartIndex];
  1394.       edge = selectable.value.startSelectionPoint;
  1395.       lineHeight = selectable.value.startSelectionPoint?.lineHeight;
  1396.     }
  1397.     if (lineHeight == null || edge == null) {
  1398.       return;
  1399.     }
  1400.     final RenderBox scrollableBox =
  1401.         state.context.findRenderObject()! as RenderBox;
  1402.     final Matrix4 transform = selectable.getTransformTo(scrollableBox);
  1403.     final Offset edgeOffsetInScrollableCoordinates =
  1404.         MatrixUtils.transformPoint(transform, edge.localPosition);
  1405.     final Rect scrollableRect = Rect.fromLTRB(
  1406.         0, 0, scrollableBox.size.width, scrollableBox.size.height);
  1407.     switch (state.axisDirection) {
  1408.       case AxisDirection.up:
  1409.         final double edgeBottom = edgeOffsetInScrollableCoordinates.dy;
  1410.         final double edgeTop =
  1411.             edgeOffsetInScrollableCoordinates.dy - lineHeight;
  1412.         if (edgeBottom >= scrollableRect.bottom &&
  1413.             edgeTop <= scrollableRect.top) {
  1414.           return;
  1415.         }
  1416.         if (edgeBottom > scrollableRect.bottom) {
  1417.           position.jumpTo(position.pixels + scrollableRect.bottom - edgeBottom);
  1418.           return;
  1419.         }
  1420.         if (edgeTop < scrollableRect.top) {
  1421.           position.jumpTo(position.pixels + scrollableRect.top - edgeTop);
  1422.         }
  1423.         return;
  1424.       case AxisDirection.right:
  1425.         final double edge = edgeOffsetInScrollableCoordinates.dx;
  1426.         if (edge >= scrollableRect.right && edge <= scrollableRect.left) {
  1427.           return;
  1428.         }
  1429.         if (edge > scrollableRect.right) {
  1430.           position.jumpTo(position.pixels + edge - scrollableRect.right);
  1431.           return;
  1432.         }
  1433.         if (edge < scrollableRect.left) {
  1434.           position.jumpTo(position.pixels + edge - scrollableRect.left);
  1435.         }
  1436.         return;
  1437.       case AxisDirection.down:
  1438.         final double edgeBottom = edgeOffsetInScrollableCoordinates.dy;
  1439.         final double edgeTop =
  1440.             edgeOffsetInScrollableCoordinates.dy - lineHeight;
  1441.         if (edgeBottom >= scrollableRect.bottom &&
  1442.             edgeTop <= scrollableRect.top) {
  1443.           return;
  1444.         }
  1445.         if (edgeBottom > scrollableRect.bottom) {
  1446.           position.jumpTo(position.pixels + edgeBottom - scrollableRect.bottom);
  1447.           return;
  1448.         }
  1449.         if (edgeTop < scrollableRect.top) {
  1450.           position.jumpTo(position.pixels + edgeTop - scrollableRect.top);
  1451.         }
  1452.         return;
  1453.       case AxisDirection.left:
  1454.         final double edge = edgeOffsetInScrollableCoordinates.dx;
  1455.         if (edge >= scrollableRect.right && edge <= scrollableRect.left) {
  1456.           return;
  1457.         }
  1458.         if (edge > scrollableRect.right) {
  1459.           position.jumpTo(position.pixels + scrollableRect.right - edge);
  1460.           return;
  1461.         }
  1462.         if (edge < scrollableRect.left) {
  1463.           position.jumpTo(position.pixels + scrollableRect.left - edge);
  1464.         }
  1465.         return;
  1466.     }
  1467.   }
  1468.  
  1469.   bool _globalPositionInScrollable(Offset globalPosition) {
  1470.     final RenderBox box = state.context.findRenderObject()! as RenderBox;
  1471.     final Offset localPosition = box.globalToLocal(globalPosition);
  1472.     final Rect rect = Rect.fromLTWH(0, 0, box.size.width, box.size.height);
  1473.     return rect.contains(localPosition);
  1474.   }
  1475.  
  1476.   Rect _dragTargetFromEvent(SelectionEdgeUpdateEvent event) {
  1477.     return Rect.fromCenter(
  1478.         center: event.globalPosition,
  1479.         width: _kDefaultDragTargetSize,
  1480.         height: _kDefaultDragTargetSize);
  1481.   }
  1482.  
  1483.   @override
  1484.   SelectionResult dispatchSelectionEventToChild(
  1485.       Selectable selectable, SelectionEvent event) {
  1486.     switch (event.type) {
  1487.       case SelectionEventType.startEdgeUpdate:
  1488.         _selectableStartEdgeUpdateRecords[selectable] = state.position.pixels;
  1489.         ensureChildUpdated(selectable);
  1490.       case SelectionEventType.endEdgeUpdate:
  1491.         _selectableEndEdgeUpdateRecords[selectable] = state.position.pixels;
  1492.         ensureChildUpdated(selectable);
  1493.       case SelectionEventType.granularlyExtendSelection:
  1494.       case SelectionEventType.directionallyExtendSelection:
  1495.         ensureChildUpdated(selectable);
  1496.         _selectableStartEdgeUpdateRecords[selectable] = state.position.pixels;
  1497.         _selectableEndEdgeUpdateRecords[selectable] = state.position.pixels;
  1498.       case SelectionEventType.clear:
  1499.         _selectableEndEdgeUpdateRecords.remove(selectable);
  1500.         _selectableStartEdgeUpdateRecords.remove(selectable);
  1501.       case SelectionEventType.selectAll:
  1502.       case SelectionEventType.selectWord:
  1503.         _selectableEndEdgeUpdateRecords[selectable] = state.position.pixels;
  1504.         _selectableStartEdgeUpdateRecords[selectable] = state.position.pixels;
  1505.     }
  1506.     return super.dispatchSelectionEventToChild(selectable, event);
  1507.   }
  1508.  
  1509.   @override
  1510.   void ensureChildUpdated(Selectable selectable) {
  1511.     final double newRecord = state.position.pixels;
  1512.     final double? previousStartRecord =
  1513.         _selectableStartEdgeUpdateRecords[selectable];
  1514.     if (_currentDragStartRelatedToOrigin != null &&
  1515.         (previousStartRecord == null ||
  1516.             (newRecord - previousStartRecord).abs() >
  1517.                 precisionErrorTolerance)) {
  1518.       // Make sure the selectable has up to date events.
  1519.       final Offset deltaToOrigin = _getDeltaToScrollOrigin(state);
  1520.       final Offset startOffset = _currentDragStartRelatedToOrigin!
  1521.           .translate(-deltaToOrigin.dx, -deltaToOrigin.dy);
  1522.       selectable.dispatchSelectionEvent(
  1523.           SelectionEdgeUpdateEvent.forStart(globalPosition: startOffset));
  1524.       // Make sure we track that we have synthesized a start event for this selectable,
  1525.       // so we don't synthesize events unnecessarily.
  1526.       _selectableStartEdgeUpdateRecords[selectable] = state.position.pixels;
  1527.     }
  1528.     final double? previousEndRecord =
  1529.         _selectableEndEdgeUpdateRecords[selectable];
  1530.     if (_currentDragEndRelatedToOrigin != null &&
  1531.         (previousEndRecord == null ||
  1532.             (newRecord - previousEndRecord).abs() > precisionErrorTolerance)) {
  1533.       // Make sure the selectable has up to date events.
  1534.       final Offset deltaToOrigin = _getDeltaToScrollOrigin(state);
  1535.       final Offset endOffset = _currentDragEndRelatedToOrigin!
  1536.           .translate(-deltaToOrigin.dx, -deltaToOrigin.dy);
  1537.       selectable.dispatchSelectionEvent(
  1538.           SelectionEdgeUpdateEvent.forEnd(globalPosition: endOffset));
  1539.       // Make sure we track that we have synthesized an end event for this selectable,
  1540.       // so we don't synthesize events unnecessarily.
  1541.       _selectableEndEdgeUpdateRecords[selectable] = state.position.pixels;
  1542.     }
  1543.   }
  1544.  
  1545.   @override
  1546.   void dispose() {
  1547.     _selectableStartEdgeUpdateRecords.clear();
  1548.     _selectableEndEdgeUpdateRecords.clear();
  1549.     _scheduledLayoutChange = false;
  1550.     _autoScroller.stopAutoScroll();
  1551.     super.dispose();
  1552.   }
  1553. }
  1554.  
  1555. Offset _getDeltaToScrollOrigin(ScrollableState scrollableState) {
  1556.   switch (scrollableState.axisDirection) {
  1557.     case AxisDirection.down:
  1558.       return Offset(0, scrollableState.position.pixels);
  1559.     case AxisDirection.up:
  1560.       return Offset(0, -scrollableState.position.pixels);
  1561.     case AxisDirection.left:
  1562.       return Offset(-scrollableState.position.pixels, 0);
  1563.     case AxisDirection.right:
  1564.       return Offset(scrollableState.position.pixels, 0);
  1565.   }
  1566. }
  1567.  
  1568. /// With [_ScrollSemantics] certain child [SemanticsNode]s can be
  1569. /// excluded from the scrollable area for semantics purposes.
  1570. ///
  1571. /// Nodes, that are to be excluded, have to be tagged with
  1572. /// [RenderViewport.excludeFromScrolling] and the [RenderAbstractViewport] in
  1573. /// use has to add the [RenderViewport.useTwoPaneSemantics] tag to its
  1574. /// [SemanticsConfiguration] by overriding
  1575. /// [RenderObject.describeSemanticsConfiguration].
  1576. ///
  1577. /// If the tag [RenderViewport.useTwoPaneSemantics] is present on the viewport,
  1578. /// two semantics nodes will be used to represent the [Scrollable]: The outer
  1579. /// node will contain all children, that are excluded from scrolling. The inner
  1580. /// node, which is annotated with the scrolling actions, will house the
  1581. /// scrollable children.
  1582. class _ScrollSemantics extends SingleChildRenderObjectWidget {
  1583.   const _ScrollSemantics({
  1584.     super.key,
  1585.     required this.position,
  1586.     required this.allowImplicitScrolling,
  1587.     required this.semanticChildCount,
  1588.     super.child,
  1589.   }) : assert(semanticChildCount == null || semanticChildCount >= 0);
  1590.  
  1591.   final ScrollPosition position;
  1592.   final bool allowImplicitScrolling;
  1593.   final int? semanticChildCount;
  1594.  
  1595.   @override
  1596.   _RenderScrollSemantics createRenderObject(BuildContext context) {
  1597.     return _RenderScrollSemantics(
  1598.       position: position,
  1599.       allowImplicitScrolling: allowImplicitScrolling,
  1600.       semanticChildCount: semanticChildCount,
  1601.     );
  1602.   }
  1603.  
  1604.   @override
  1605.   void updateRenderObject(
  1606.       BuildContext context, _RenderScrollSemantics renderObject) {
  1607.     renderObject
  1608.       ..allowImplicitScrolling = allowImplicitScrolling
  1609.       ..position = position
  1610.       ..semanticChildCount = semanticChildCount;
  1611.   }
  1612. }
  1613.  
  1614. class _RenderScrollSemantics extends RenderProxyBox {
  1615.   _RenderScrollSemantics({
  1616.     required ScrollPosition position,
  1617.     required bool allowImplicitScrolling,
  1618.     required int? semanticChildCount,
  1619.     RenderBox? child,
  1620.   })  : _position = position,
  1621.         _allowImplicitScrolling = allowImplicitScrolling,
  1622.         _semanticChildCount = semanticChildCount,
  1623.         super(child) {
  1624.     position.addListener(markNeedsSemanticsUpdate);
  1625.   }
  1626.  
  1627.   /// Whether this render object is excluded from the semantic tree.
  1628.   ScrollPosition get position => _position;
  1629.   ScrollPosition _position;
  1630.   set position(ScrollPosition value) {
  1631.     if (value == _position) {
  1632.       return;
  1633.     }
  1634.     _position.removeListener(markNeedsSemanticsUpdate);
  1635.     _position = value;
  1636.     _position.addListener(markNeedsSemanticsUpdate);
  1637.     markNeedsSemanticsUpdate();
  1638.   }
  1639.  
  1640.   /// Whether this node can be scrolled implicitly.
  1641.   bool get allowImplicitScrolling => _allowImplicitScrolling;
  1642.   bool _allowImplicitScrolling;
  1643.   set allowImplicitScrolling(bool value) {
  1644.     if (value == _allowImplicitScrolling) {
  1645.       return;
  1646.     }
  1647.     _allowImplicitScrolling = value;
  1648.     markNeedsSemanticsUpdate();
  1649.   }
  1650.  
  1651.   int? get semanticChildCount => _semanticChildCount;
  1652.   int? _semanticChildCount;
  1653.   set semanticChildCount(int? value) {
  1654.     if (value == semanticChildCount) {
  1655.       return;
  1656.     }
  1657.     _semanticChildCount = value;
  1658.     markNeedsSemanticsUpdate();
  1659.   }
  1660.  
  1661.   @override
  1662.   void describeSemanticsConfiguration(SemanticsConfiguration config) {
  1663.     super.describeSemanticsConfiguration(config);
  1664.     config.isSemanticBoundary = true;
  1665.     if (position.haveDimensions) {
  1666.       config
  1667.         ..hasImplicitScrolling = allowImplicitScrolling
  1668.         ..scrollPosition = _position.pixels
  1669.         ..scrollExtentMax = _position.maxScrollExtent
  1670.         ..scrollExtentMin = _position.minScrollExtent
  1671.         ..scrollChildCount = semanticChildCount;
  1672.     }
  1673.   }
  1674.  
  1675.   SemanticsNode? _innerNode;
  1676.  
  1677.   @override
  1678.   void assembleSemanticsNode(SemanticsNode node, SemanticsConfiguration config,
  1679.       Iterable<SemanticsNode> children) {
  1680.     if (children.isEmpty ||
  1681.         !children.first.isTagged(RenderViewport.useTwoPaneSemantics)) {
  1682.       _innerNode = null;
  1683.       super.assembleSemanticsNode(node, config, children);
  1684.       return;
  1685.     }
  1686.  
  1687.     _innerNode ??= SemanticsNode(showOnScreen: showOnScreen);
  1688.     _innerNode!
  1689.       ..isMergedIntoParent = node.isPartOfNodeMerging
  1690.       ..rect = node.rect;
  1691.  
  1692.     int? firstVisibleIndex;
  1693.     final List<SemanticsNode> excluded = <SemanticsNode>[_innerNode!];
  1694.     final List<SemanticsNode> included = <SemanticsNode>[];
  1695.     for (final SemanticsNode child in children) {
  1696.       assert(child.isTagged(RenderViewport.useTwoPaneSemantics));
  1697.       if (child.isTagged(RenderViewport.excludeFromScrolling)) {
  1698.         excluded.add(child);
  1699.       } else {
  1700.         if (!child.hasFlag(SemanticsFlag.isHidden)) {
  1701.           firstVisibleIndex ??= child.indexInParent;
  1702.         }
  1703.         included.add(child);
  1704.       }
  1705.     }
  1706.     config.scrollIndex = firstVisibleIndex;
  1707.     node.updateWith(config: null, childrenInInversePaintOrder: excluded);
  1708.     _innerNode!
  1709.         .updateWith(config: config, childrenInInversePaintOrder: included);
  1710.   }
  1711.  
  1712.   @override
  1713.   void clearSemantics() {
  1714.     super.clearSemantics();
  1715.     _innerNode = null;
  1716.   }
  1717. }
  1718.  
  1719. // Not using a RestorableDouble because we want to allow null values and override
  1720. // [enabled].
  1721. class _RestorableScrollOffset extends RestorableValue<double?> {
  1722.   @override
  1723.   double? createDefaultValue() => null;
  1724.  
  1725.   @override
  1726.   void didUpdateValue(double? oldValue) {
  1727.     notifyListeners();
  1728.   }
  1729.  
  1730.   @override
  1731.   double fromPrimitives(Object? data) {
  1732.     return data! as double;
  1733.   }
  1734.  
  1735.   @override
  1736.   Object? toPrimitives() {
  1737.     return value;
  1738.   }
  1739.  
  1740.   @override
  1741.   bool get enabled => value != null;
  1742. }
  1743.  
  1744. // 2D SCROLLING
  1745.  
  1746. /// Specifies how to configure the [DragGestureRecognizer]s of a
  1747. /// [TwoDimensionalScrollable].
  1748. // TODO(Piinks): Add sample code, https://github.com/flutter/flutter/issues/126298
  1749. enum DiagonalDragBehavior {
  1750.   /// This behavior will not allow for any diagonal scrolling.
  1751.   ///
  1752.   /// Drag gestures in one direction or the other will lock the input axis until
  1753.   /// the gesture is released.
  1754.   none,
  1755.  
  1756.   /// This behavior will only allow diagonal scrolling on a weighted
  1757.   /// scale per gesture event.
  1758.   ///
  1759.   /// This means that after initially evaluating the drag gesture, the weighted
  1760.   /// evaluation (based on [kTouchSlop]) stands until the gesture is released.
  1761.   weightedEvent,
  1762.  
  1763.   /// This behavior will only allow diagonal scrolling on a weighted
  1764.   /// scale that is evaluated throughout a gesture event.
  1765.   ///
  1766.   /// This means that during each update to the drag gesture, the scrolling
  1767.   /// axis will be allowed to scroll diagonally if it exceeds the
  1768.   /// [kTouchSlop].
  1769.   weightedContinuous,
  1770.  
  1771.   /// This behavior allows free movement in any and all directions when
  1772.   /// dragging.
  1773.   free,
  1774. }
  1775.  
  1776. /// A widget that manages scrolling in both the vertical and horizontal
  1777. /// dimensions and informs the [TwoDimensionalViewport] through which the
  1778. /// content is viewed.
  1779. ///
  1780. /// [TwoDimensionalScrollable] implements the interaction model for a scrollable
  1781. /// widget in both the vertical and horizontal axes, including gesture
  1782. /// recognition, but does not have an opinion about how the
  1783. /// [TwoDimensionalViewport], which actually displays the children, is
  1784. /// constructed.
  1785. ///
  1786. /// It's rare to construct a [TwoDimensionalScrollable] directly. Instead,
  1787. /// consider subclassing [TwoDimensionalScrollView], which combines scrolling,
  1788. /// viewporting, and a layout model in both dimensions.
  1789. ///
  1790. /// See also:
  1791. ///
  1792. ///  * [TwoDimensionalScrollView], an abstract base class for displaying a
  1793. ///    scrolling array of children in both directions.
  1794. ///  * [TwoDimensionalViewport], which can be used to customize the child layout
  1795. ///    model.
  1796. class TwoDimensionalScrollable extends StatefulWidget {
  1797.   /// Creates a widget that scrolls in two dimensions.
  1798.   ///
  1799.   /// The [horizontalDetails], [verticalDetails], and [viewportBuilder] must not
  1800.   /// be null.
  1801.   const TwoDimensionalScrollable({
  1802.     super.key,
  1803.     required this.horizontalDetails,
  1804.     required this.verticalDetails,
  1805.     required this.viewportBuilder,
  1806.     this.incrementCalculator,
  1807.     this.restorationId,
  1808.     this.excludeFromSemantics = false,
  1809.     this.diagonalDragBehavior = DiagonalDragBehavior.none,
  1810.     this.dragStartBehavior = DragStartBehavior.start,
  1811.   });
  1812.  
  1813.   /// How scrolling gestures should lock to one axis, or allow free movement
  1814.   /// in both axes.
  1815.   final DiagonalDragBehavior diagonalDragBehavior;
  1816.  
  1817.   /// The configuration of the horizontal [Scrollable].
  1818.   ///
  1819.   /// These [ScrollableDetails] can be used to set the [AxisDirection],
  1820.   /// [ScrollController], [ScrollPhysics] and more for the horizontal axis.
  1821.   final ScrollableDetails horizontalDetails;
  1822.  
  1823.   /// The configuration of the vertical [Scrollable].
  1824.   ///
  1825.   /// These [ScrollableDetails] can be used to set the [AxisDirection],
  1826.   /// [ScrollController], [ScrollPhysics] and more for the vertical axis.
  1827.   final ScrollableDetails verticalDetails;
  1828.  
  1829.   /// Builds the viewport through which the scrollable content is displayed.
  1830.   ///
  1831.   /// A [TwoDimensionalViewport] uses two given [ViewportOffset]s to determine
  1832.   /// which part of its content is actually visible through the viewport.
  1833.   ///
  1834.   /// See also:
  1835.   ///
  1836.   ///  * [TwoDimensionalViewport], which is a viewport that displays a span of
  1837.   ///    widgets in both dimensions.
  1838.   final TwoDimensionalViewportBuilder viewportBuilder;
  1839.  
  1840.   /// {@macro flutter.widgets.Scrollable.incrementCalculator}
  1841.   ///
  1842.   /// This value applies in both axes.
  1843.   final ScrollIncrementCalculator? incrementCalculator;
  1844.  
  1845.   /// {@macro flutter.widgets.scrollable.restorationId}
  1846.   ///
  1847.   /// Internally, the [TwoDimensionalScrollable] will introduce a
  1848.   /// [RestorationScope] that will be assigned this value. The two [Scrollable]s
  1849.   /// within will then be given unique IDs within this scope.
  1850.   final String? restorationId;
  1851.  
  1852.   /// {@macro flutter.widgets.scrollable.excludeFromSemantics}
  1853.   ///
  1854.   /// This value applies to both axes.
  1855.   final bool excludeFromSemantics;
  1856.  
  1857.   /// {@macro flutter.widgets.scrollable.dragStartBehavior}
  1858.   ///
  1859.   /// This value applies in both axes.
  1860.   final DragStartBehavior dragStartBehavior;
  1861.  
  1862.   @override
  1863.   State<TwoDimensionalScrollable> createState() =>
  1864.       TwoDimensionalScrollableState();
  1865.  
  1866.   /// The state from the closest instance of this class that encloses the given
  1867.   /// context, or null if none is found.
  1868.   ///
  1869.   /// Typical usage is as follows:
  1870.   ///
  1871.   /// ```dart
  1872.   /// TwoDimensionalScrollableState? scrollable = TwoDimensionalScrollable.maybeOf(context);
  1873.   /// ```
  1874.   ///
  1875.   /// Calling this method will create a dependency on the closest
  1876.   /// [TwoDimensionalScrollable] in the [context]. The internal [Scrollable]s
  1877.   /// can be accessed through [TwoDimensionalScrollableState.verticalScrollable]
  1878.   /// and [TwoDimensionalScrollableState.horizontalScrollable].
  1879.   ///
  1880.   /// Alternatively, [Scrollable.maybeOf] can be used by providing the desired
  1881.   /// [Axis] to the `axis` parameter.
  1882.   ///
  1883.   /// See also:
  1884.   ///
  1885.   /// * [TwoDimensionalScrollable.of], which is similar to this method, but
  1886.   ///   asserts if no [Scrollable] ancestor is found.
  1887.   static TwoDimensionalScrollableState? maybeOf(BuildContext context) {
  1888.     final _TwoDimensionalScrollableScope? widget = context
  1889.         .dependOnInheritedWidgetOfExactType<_TwoDimensionalScrollableScope>();
  1890.     return widget?.twoDimensionalScrollable;
  1891.   }
  1892.  
  1893.   /// The state from the closest instance of this class that encloses the given
  1894.   /// context.
  1895.   ///
  1896.   /// Typical usage is as follows:
  1897.   ///
  1898.   /// ```dart
  1899.   /// TwoDimensionalScrollableState scrollable = TwoDimensionalScrollable.of(context);
  1900.   /// ```
  1901.   ///
  1902.   /// Calling this method will create a dependency on the closest
  1903.   /// [TwoDimensionalScrollable] in the [context]. The internal [Scrollable]s
  1904.   /// can be accessed through [TwoDimensionalScrollableState.verticalScrollable]
  1905.   /// and [TwoDimensionalScrollableState.horizontalScrollable].
  1906.   ///
  1907.   /// If no [TwoDimensionalScrollable] ancestor is found, then this method will
  1908.   /// assert in debug mode, and throw an exception in release mode.
  1909.   ///
  1910.   /// Alternatively, [Scrollable.of] can be used by providing the desired [Axis]
  1911.   /// to the `axis` parameter.
  1912.   ///
  1913.   /// See also:
  1914.   ///
  1915.   /// * [TwoDimensionalScrollable.maybeOf], which is similar to this method,
  1916.   ///   but returns null if no [TwoDimensionalScrollable] ancestor is found.
  1917.   static TwoDimensionalScrollableState of(BuildContext context) {
  1918.     final TwoDimensionalScrollableState? scrollableState = maybeOf(context);
  1919.     assert(() {
  1920.       if (scrollableState == null) {
  1921.         throw FlutterError.fromParts(<DiagnosticsNode>[
  1922.           ErrorSummary(
  1923.               'TwoDimensionalScrollable.of() was called with a context that does '
  1924.               'not contain a TwoDimensionalScrollable widget.\n'),
  1925.           ErrorDescription(
  1926.             'No TwoDimensionalScrollable widget ancestor could be found starting '
  1927.             'from the context that was passed to TwoDimensionalScrollable.of(). '
  1928.             'This can happen because you are using a widget that looks for a '
  1929.             'TwoDimensionalScrollable ancestor, but no such ancestor exists.\n'
  1930.             'The context used was:\n'
  1931.             '  $context',
  1932.           ),
  1933.         ]);
  1934.       }
  1935.       return true;
  1936.     }());
  1937.     return scrollableState!;
  1938.   }
  1939. }
  1940.  
  1941. /// State object for a [TwoDimensionalScrollable] widget.
  1942. ///
  1943. /// To manipulate one of the internal [Scrollable] widget's scroll position, use
  1944. /// the object obtained from the [verticalScrollable] or [horizontalScrollable]
  1945. /// property.
  1946. ///
  1947. /// To be informed of when a [TwoDimensionalScrollable] widget is scrolling,
  1948. /// use a [NotificationListener] to listen for [ScrollNotification]s.
  1949. /// Both axes will have the same viewport depth since there is only one
  1950. /// viewport, and so should be differentiated by the [Axis] of the
  1951. /// [ScrollMetrics] provided by the notification.
  1952. class TwoDimensionalScrollableState extends State<TwoDimensionalScrollable> {
  1953.   ScrollController? _verticalFallbackController;
  1954.   ScrollController? _horizontalFallbackController;
  1955.   final GlobalKey<ScrollableState> _verticalOuterScrollableKey =
  1956.       GlobalKey<ScrollableState>();
  1957.   final GlobalKey<ScrollableState> _horizontalInnerScrollableKey =
  1958.       GlobalKey<ScrollableState>();
  1959.  
  1960.   /// The [ScrollableState] of the vertical axis.
  1961.   ///
  1962.   /// Accessible by calling [TwoDimensionalScrollable.of].
  1963.   ///
  1964.   /// Alternatively, [Scrollable.of] can be used by providing [Axis.vertical]
  1965.   /// to the `axis` parameter.
  1966.   ScrollableState get verticalScrollable {
  1967.     assert(_verticalOuterScrollableKey.currentState != null);
  1968.     return _verticalOuterScrollableKey.currentState!;
  1969.   }
  1970.  
  1971.   /// The [ScrollableState] of the horizontal axis.
  1972.   ///
  1973.   /// Accessible by calling [TwoDimensionalScrollable.of].
  1974.   ///
  1975.   /// Alternatively, [Scrollable.of] can be used by providing [Axis.horizontal]
  1976.   /// to the `axis` parameter.
  1977.   ScrollableState get horizontalScrollable {
  1978.     assert(_horizontalInnerScrollableKey.currentState != null);
  1979.     return _horizontalInnerScrollableKey.currentState!;
  1980.   }
  1981.  
  1982.   @override
  1983.   void initState() {
  1984.     if (widget.verticalDetails.controller == null) {
  1985.       _verticalFallbackController = ScrollController();
  1986.     }
  1987.     if (widget.horizontalDetails.controller == null) {
  1988.       _horizontalFallbackController = ScrollController();
  1989.     }
  1990.     super.initState();
  1991.   }
  1992.  
  1993.   @override
  1994.   void didUpdateWidget(TwoDimensionalScrollable oldWidget) {
  1995.     super.didUpdateWidget(oldWidget);
  1996.     // Handle changes in the provided/fallback scroll controllers
  1997.  
  1998.     // Vertical
  1999.     if (oldWidget.verticalDetails.controller !=
  2000.         widget.verticalDetails.controller) {
  2001.       if (oldWidget.verticalDetails.controller == null) {
  2002.         // The old controller was null, meaning the fallback cannot be null.
  2003.         // Dispose of the fallback.
  2004.         assert(_verticalFallbackController != null);
  2005.         assert(widget.verticalDetails.controller != null);
  2006.         _verticalFallbackController!.dispose();
  2007.         _verticalFallbackController = null;
  2008.       } else if (widget.verticalDetails.controller == null) {
  2009.         // If the new controller is null, we need to set up the fallback
  2010.         // ScrollController.
  2011.         assert(_verticalFallbackController == null);
  2012.         _verticalFallbackController = ScrollController();
  2013.       }
  2014.     }
  2015.  
  2016.     // Horizontal
  2017.     if (oldWidget.horizontalDetails.controller !=
  2018.         widget.horizontalDetails.controller) {
  2019.       if (oldWidget.horizontalDetails.controller == null) {
  2020.         // The old controller was null, meaning the fallback cannot be null.
  2021.         // Dispose of the fallback.
  2022.         assert(_horizontalFallbackController != null);
  2023.         assert(widget.horizontalDetails.controller != null);
  2024.         _horizontalFallbackController!.dispose();
  2025.         _horizontalFallbackController = null;
  2026.       } else if (widget.horizontalDetails.controller == null) {
  2027.         // If the new controller is null, we need to set up the fallback
  2028.         // ScrollController.
  2029.         assert(_horizontalFallbackController == null);
  2030.         _horizontalFallbackController = ScrollController();
  2031.       }
  2032.     }
  2033.   }
  2034.  
  2035.   @override
  2036.   Widget build(BuildContext context) {
  2037.     assert(
  2038.         axisDirectionToAxis(widget.verticalDetails.direction) == Axis.vertical,
  2039.         'TwoDimensionalScrollable.verticalDetails are not Axis.vertical.');
  2040.     assert(
  2041.         axisDirectionToAxis(widget.horizontalDetails.direction) ==
  2042.             Axis.horizontal,
  2043.         'TwoDimensionalScrollable.horizontalDetails are not Axis.horizontal.');
  2044.  
  2045.     final Widget result = RestorationScope(
  2046.         restorationId: widget.restorationId,
  2047.         child: _VerticalOuterDimension(
  2048.             key: _verticalOuterScrollableKey,
  2049.             axisDirection: widget.verticalDetails.direction,
  2050.             controller: widget.verticalDetails.controller ??
  2051.                 _verticalFallbackController!,
  2052.             physics: widget.verticalDetails.physics,
  2053.             clipBehavior: widget.verticalDetails.clipBehavior ??
  2054.                 widget.verticalDetails.decorationClipBehavior ??
  2055.                 Clip.hardEdge,
  2056.             incrementCalculator: widget.incrementCalculator,
  2057.             excludeFromSemantics: widget.excludeFromSemantics,
  2058.             restorationId: 'OuterVerticalTwoDimensionalScrollable',
  2059.             dragStartBehavior: widget.dragStartBehavior,
  2060.             diagonalDragBehavior: widget.diagonalDragBehavior,
  2061.             viewportBuilder:
  2062.                 (BuildContext context, ViewportOffset verticalOffset) {
  2063.               return _HorizontalInnerDimension(
  2064.                 key: _horizontalInnerScrollableKey,
  2065.                 axisDirection: widget.horizontalDetails.direction,
  2066.                 controller: widget.horizontalDetails.controller ??
  2067.                     _horizontalFallbackController!,
  2068.                 physics: widget.horizontalDetails.physics,
  2069.                 clipBehavior: widget.horizontalDetails.clipBehavior ??
  2070.                     widget.horizontalDetails.decorationClipBehavior ??
  2071.                     Clip.hardEdge,
  2072.                 incrementCalculator: widget.incrementCalculator,
  2073.                 excludeFromSemantics: widget.excludeFromSemantics,
  2074.                 restorationId: 'InnerHorizontalTwoDimensionalScrollable',
  2075.                 dragStartBehavior: widget.dragStartBehavior,
  2076.                 diagonalDragBehavior: widget.diagonalDragBehavior,
  2077.                 viewportBuilder:
  2078.                     (BuildContext context, ViewportOffset horizontalOffset) {
  2079.                   return widget.viewportBuilder(
  2080.                       context, verticalOffset, horizontalOffset);
  2081.                 },
  2082.               );
  2083.             }));
  2084.  
  2085.     // TODO(Piinks): Build scrollbars for 2 dimensions instead of 1,
  2086.     //  https://github.com/flutter/flutter/issues/122348
  2087.  
  2088.     return _TwoDimensionalScrollableScope(
  2089.       twoDimensionalScrollable: this,
  2090.       child: result,
  2091.     );
  2092.   }
  2093.  
  2094.   @override
  2095.   void dispose() {
  2096.     _verticalFallbackController?.dispose();
  2097.     _horizontalFallbackController?.dispose();
  2098.     super.dispose();
  2099.   }
  2100. }
  2101.  
  2102. // Enable TwoDimensionalScrollable.of() to work as if
  2103. // TwoDimensionalScrollableState was an inherited widget.
  2104. // TwoDimensionalScrollableState.build() always rebuilds its
  2105. // _TwoDimensionalScrollableScope.
  2106. class _TwoDimensionalScrollableScope extends InheritedWidget {
  2107.   const _TwoDimensionalScrollableScope({
  2108.     required this.twoDimensionalScrollable,
  2109.     required super.child,
  2110.   });
  2111.  
  2112.   final TwoDimensionalScrollableState twoDimensionalScrollable;
  2113.  
  2114.   @override
  2115.   bool updateShouldNotify(_TwoDimensionalScrollableScope old) => false;
  2116. }
  2117.  
  2118. // Vertical outer scrollable of 2D scrolling
  2119. class _VerticalOuterDimension extends Scrollable {
  2120.   const _VerticalOuterDimension({
  2121.     super.key,
  2122.     required super.viewportBuilder,
  2123.     required super.axisDirection,
  2124.     super.controller,
  2125.     super.physics,
  2126.     super.clipBehavior,
  2127.     super.incrementCalculator,
  2128.     super.excludeFromSemantics,
  2129.     super.dragStartBehavior,
  2130.     super.restorationId,
  2131.     this.diagonalDragBehavior = DiagonalDragBehavior.none,
  2132.   }) : assert(axisDirection == AxisDirection.up ||
  2133.             axisDirection == AxisDirection.down);
  2134.  
  2135.   final DiagonalDragBehavior diagonalDragBehavior;
  2136.  
  2137.   @override
  2138.   _VerticalOuterDimensionState createState() => _VerticalOuterDimensionState();
  2139. }
  2140.  
  2141. class _VerticalOuterDimensionState extends ScrollableState {
  2142.   DiagonalDragBehavior get diagonalDragBehavior =>
  2143.       (widget as _VerticalOuterDimension).diagonalDragBehavior;
  2144.  
  2145.   // Implemented in the _HorizontalInnerDimension instead.
  2146.   @override
  2147.   _EnsureVisibleResults _performEnsureVisible(
  2148.     RenderObject object, {
  2149.     double alignment = 0.0,
  2150.     Duration duration = Duration.zero,
  2151.     Curve curve = Curves.ease,
  2152.     ScrollPositionAlignmentPolicy alignmentPolicy =
  2153.         ScrollPositionAlignmentPolicy.explicit,
  2154.     RenderObject? targetRenderObject,
  2155.   }) {
  2156.     assert(
  2157.         false,
  2158.         'The _performEnsureVisible method was called for the vertical scrollable '
  2159.         'of a TwoDimensionalScrollable. This should not happen as the horizontal '
  2160.         'scrollable handles both axes.');
  2161.     return (<Future<void>>[], this);
  2162.   }
  2163.  
  2164.   @override
  2165.   void setCanDrag(bool value) {
  2166.     switch (diagonalDragBehavior) {
  2167.       case DiagonalDragBehavior.none:
  2168.         // If we aren't scrolling diagonally, the default drag gesture
  2169.         // recognizer is used.
  2170.         super.setCanDrag(value);
  2171.         return;
  2172.       case DiagonalDragBehavior.weightedEvent:
  2173.       case DiagonalDragBehavior.weightedContinuous:
  2174.       case DiagonalDragBehavior.free:
  2175.         if (value) {
  2176.           // If a type of diagonal scrolling is enabled, a panning gesture
  2177.           // recognizer will be created for the _InnerDimension. So in this
  2178.           // case, the _OuterDimension does not require a gesture recognizer.
  2179.           _gestureRecognizers = const <Type, GestureRecognizerFactory>{};
  2180.           // Cancel the active hold/drag (if any) because the gesture recognizers
  2181.           // will soon be disposed by our RawGestureDetector, and we won't be
  2182.           // receiving pointer up events to cancel the hold/drag.
  2183.           _handleDragCancel();
  2184.           _lastCanDrag = value;
  2185.           _lastAxisDirection = widget.axis;
  2186.           if (_gestureDetectorKey.currentState != null) {
  2187.             _gestureDetectorKey.currentState!
  2188.                 .replaceGestureRecognizers(_gestureRecognizers);
  2189.           }
  2190.         }
  2191.         return;
  2192.     }
  2193.   }
  2194.  
  2195.   @override
  2196.   Widget _buildChrome(BuildContext context, Widget child) {
  2197.     final ScrollableDetails details = ScrollableDetails(
  2198.       direction: widget.axisDirection,
  2199.       controller: _effectiveScrollController,
  2200.       clipBehavior: widget.clipBehavior,
  2201.     );
  2202.     // Skip building a scrollbar here, the dual scrollbar is added in
  2203.     // TwoDimensionalScrollableState.
  2204.     return _configuration.buildOverscrollIndicator(context, child, details);
  2205.   }
  2206. }
  2207.  
  2208. // Horizontal inner scrollable of 2D scrolling
  2209. class _HorizontalInnerDimension extends Scrollable {
  2210.   const _HorizontalInnerDimension({
  2211.     super.key,
  2212.     required super.viewportBuilder,
  2213.     required super.axisDirection,
  2214.     super.controller,
  2215.     super.physics,
  2216.     super.clipBehavior,
  2217.     super.incrementCalculator,
  2218.     super.excludeFromSemantics,
  2219.     super.dragStartBehavior,
  2220.     super.restorationId,
  2221.     this.diagonalDragBehavior = DiagonalDragBehavior.none,
  2222.   }) : assert(axisDirection == AxisDirection.left ||
  2223.             axisDirection == AxisDirection.right);
  2224.  
  2225.   final DiagonalDragBehavior diagonalDragBehavior;
  2226.  
  2227.   @override
  2228.   _HorizontalInnerDimensionState createState() =>
  2229.       _HorizontalInnerDimensionState();
  2230. }
  2231.  
  2232. class _HorizontalInnerDimensionState extends ScrollableState {
  2233.   late ScrollableState verticalScrollable;
  2234.   Axis? lockedAxis;
  2235.   Offset? lastDragOffset;
  2236.  
  2237.   DiagonalDragBehavior get diagonalDragBehavior =>
  2238.       (widget as _HorizontalInnerDimension).diagonalDragBehavior;
  2239.  
  2240.   @override
  2241.   void didChangeDependencies() {
  2242.     verticalScrollable = Scrollable.of(context);
  2243.     assert(
  2244.         axisDirectionToAxis(verticalScrollable.axisDirection) == Axis.vertical);
  2245.     super.didChangeDependencies();
  2246.   }
  2247.  
  2248.   // Returns the Future from calling ensureVisible for the ScrollPosition, as
  2249.   // as well as the vertical ScrollableState instance so its context can be
  2250.   // used to check for other ancestor Scrollables in executing ensureVisible.
  2251.   @override
  2252.   _EnsureVisibleResults _performEnsureVisible(
  2253.     RenderObject object, {
  2254.     double alignment = 0.0,
  2255.     Duration duration = Duration.zero,
  2256.     Curve curve = Curves.ease,
  2257.     ScrollPositionAlignmentPolicy alignmentPolicy =
  2258.         ScrollPositionAlignmentPolicy.explicit,
  2259.     RenderObject? targetRenderObject,
  2260.   }) {
  2261.     final List<Future<void>> newFutures = <Future<void>>[];
  2262.  
  2263.     newFutures.add(position.ensureVisible(
  2264.       object,
  2265.       alignment: alignment,
  2266.       duration: duration,
  2267.       curve: curve,
  2268.       alignmentPolicy: alignmentPolicy,
  2269.     ));
  2270.  
  2271.     newFutures.add(verticalScrollable.position.ensureVisible(
  2272.       object,
  2273.       alignment: alignment,
  2274.       duration: duration,
  2275.       curve: curve,
  2276.       alignmentPolicy: alignmentPolicy,
  2277.     ));
  2278.  
  2279.     return (newFutures, verticalScrollable);
  2280.   }
  2281.  
  2282.   void _evaluateLockedAxis(Offset offset) {
  2283.     assert(lastDragOffset != null);
  2284.     final Offset offsetDelta = lastDragOffset! - offset;
  2285.     final double axisDifferential = offsetDelta.dx.abs() - offsetDelta.dy.abs();
  2286.     if (axisDifferential.abs() >= kTouchSlop) {
  2287.       // We have single axis winner.
  2288.       lockedAxis = axisDifferential > 0.0 ? Axis.horizontal : Axis.vertical;
  2289.     } else {
  2290.       lockedAxis = null;
  2291.     }
  2292.   }
  2293.  
  2294.   @override
  2295.   void _handleDragDown(DragDownDetails details) {
  2296.     switch (diagonalDragBehavior) {
  2297.       case DiagonalDragBehavior.none:
  2298.         break;
  2299.       case DiagonalDragBehavior.weightedEvent:
  2300.       case DiagonalDragBehavior.weightedContinuous:
  2301.       case DiagonalDragBehavior.free:
  2302.         // Initiate hold. If one or the other wins the gesture, cancel the
  2303.         // opposite axis.
  2304.         verticalScrollable._handleDragDown(details);
  2305.     }
  2306.     super._handleDragDown(details);
  2307.   }
  2308.  
  2309.   @override
  2310.   void _handleDragStart(DragStartDetails details) {
  2311.     lastDragOffset = details.globalPosition;
  2312.     switch (diagonalDragBehavior) {
  2313.       case DiagonalDragBehavior.none:
  2314.         break;
  2315.       case DiagonalDragBehavior.weightedEvent:
  2316.       case DiagonalDragBehavior.weightedContinuous:
  2317.         // See if one axis wins the drag.
  2318.         _evaluateLockedAxis(details.globalPosition);
  2319.         switch (lockedAxis) {
  2320.           case null:
  2321.             // Prepare to scroll diagonally
  2322.             verticalScrollable._handleDragStart(details);
  2323.           case Axis.horizontal:
  2324.             // Prepare to scroll horizontally.
  2325.             super._handleDragStart(details);
  2326.             return;
  2327.           case Axis.vertical:
  2328.             // Prepare to scroll vertically.
  2329.             verticalScrollable._handleDragStart(details);
  2330.             return;
  2331.         }
  2332.       case DiagonalDragBehavior.free:
  2333.         verticalScrollable._handleDragStart(details);
  2334.     }
  2335.     super._handleDragStart(details);
  2336.   }
  2337.  
  2338.   @override
  2339.   void _handleDragUpdate(DragUpdateDetails details) {
  2340.     final DragUpdateDetails verticalDragDetails = DragUpdateDetails(
  2341.       sourceTimeStamp: details.sourceTimeStamp,
  2342.       delta: Offset(0.0, details.delta.dy),
  2343.       primaryDelta: details.delta.dy,
  2344.       globalPosition: details.globalPosition,
  2345.       localPosition: details.localPosition,
  2346.     );
  2347.  
  2348.     final DragUpdateDetails horizontalDragDetails = DragUpdateDetails(
  2349.       sourceTimeStamp: details.sourceTimeStamp,
  2350.       delta: Offset(details.delta.dx, 0.0),
  2351.       primaryDelta: details.delta.dx,
  2352.       globalPosition: details.globalPosition,
  2353.       localPosition: details.localPosition,
  2354.     );
  2355.     switch (diagonalDragBehavior) {
  2356.       case DiagonalDragBehavior.none:
  2357.         // Default gesture handling from super class.
  2358.         super._handleDragUpdate(horizontalDragDetails);
  2359.         return;
  2360.       case DiagonalDragBehavior.free:
  2361.         // Scroll both axes
  2362.         verticalScrollable._handleDragUpdate(verticalDragDetails);
  2363.         super._handleDragUpdate(horizontalDragDetails);
  2364.         return;
  2365.       case DiagonalDragBehavior.weightedContinuous:
  2366.         // Re-evaluate locked axis for every update.
  2367.         _evaluateLockedAxis(details.globalPosition);
  2368.         lastDragOffset = details.globalPosition;
  2369.       case DiagonalDragBehavior.weightedEvent:
  2370.         // Lock axis only once per gesture.
  2371.         if (lockedAxis == null && lastDragOffset != null) {
  2372.           // A winner has not been declared yet.
  2373.           // See if one axis has won the drag.
  2374.           _evaluateLockedAxis(details.globalPosition);
  2375.         }
  2376.     }
  2377.     switch (lockedAxis) {
  2378.       case null:
  2379.         // Scroll diagonally
  2380.         verticalScrollable._handleDragUpdate(verticalDragDetails);
  2381.         super._handleDragUpdate(horizontalDragDetails);
  2382.       case Axis.horizontal:
  2383.         // Scroll horizontally
  2384.         super._handleDragUpdate(horizontalDragDetails);
  2385.         return;
  2386.       case Axis.vertical:
  2387.         // Scroll vertically
  2388.         verticalScrollable._handleDragUpdate(verticalDragDetails);
  2389.         return;
  2390.     }
  2391.   }
  2392.  
  2393.   @override
  2394.   void _handleDragEnd(DragEndDetails details) {
  2395.     lastDragOffset = null;
  2396.     lockedAxis = null;
  2397.     final double dx = details.velocity.pixelsPerSecond.dx;
  2398.     final double dy = details.velocity.pixelsPerSecond.dy;
  2399.     final DragEndDetails verticalDragDetails = DragEndDetails(
  2400.       velocity: Velocity(pixelsPerSecond: Offset(0.0, dy)),
  2401.       primaryVelocity: details.velocity.pixelsPerSecond.dy,
  2402.     );
  2403.     final DragEndDetails horizontalDragDetails = DragEndDetails(
  2404.       velocity: Velocity(pixelsPerSecond: Offset(dx, 0.0)),
  2405.       primaryVelocity: details.velocity.pixelsPerSecond.dx,
  2406.     );
  2407.     switch (diagonalDragBehavior) {
  2408.       case DiagonalDragBehavior.none:
  2409.         break;
  2410.       case DiagonalDragBehavior.weightedEvent:
  2411.       case DiagonalDragBehavior.weightedContinuous:
  2412.       case DiagonalDragBehavior.free:
  2413.         verticalScrollable._handleDragEnd(verticalDragDetails);
  2414.     }
  2415.     super._handleDragEnd(horizontalDragDetails);
  2416.   }
  2417.  
  2418.   @override
  2419.   void _handleDragCancel() {
  2420.     lastDragOffset = null;
  2421.     lockedAxis = null;
  2422.     switch (diagonalDragBehavior) {
  2423.       case DiagonalDragBehavior.none:
  2424.         break;
  2425.       case DiagonalDragBehavior.weightedEvent:
  2426.       case DiagonalDragBehavior.weightedContinuous:
  2427.       case DiagonalDragBehavior.free:
  2428.         verticalScrollable._handleDragCancel();
  2429.     }
  2430.     super._handleDragCancel();
  2431.   }
  2432.  
  2433.   @override
  2434.   void setCanDrag(bool value) {
  2435.     switch (diagonalDragBehavior) {
  2436.       case DiagonalDragBehavior.none:
  2437.         // If we aren't scrolling diagonally, the default drag gesture recognizer
  2438.         // is used.
  2439.         super.setCanDrag(value);
  2440.         return;
  2441.       case DiagonalDragBehavior.weightedEvent:
  2442.       case DiagonalDragBehavior.weightedContinuous:
  2443.       case DiagonalDragBehavior.free:
  2444.         if (value) {
  2445.           // Replaces the typical vertical/horizontal drag gesture recognizers
  2446.           // with a pan gesture recognizer to allow bidirectional scrolling.
  2447.           // Based on the diagonalDragBehavior, valid horizontal deltas are
  2448.           // applied to this scrollable, while vertical deltas are routed to
  2449.           // the vertical scrollable.
  2450.           _gestureRecognizers = <Type, GestureRecognizerFactory>{
  2451.             PanGestureRecognizer:
  2452.                 GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
  2453.               () => PanGestureRecognizer(
  2454.                   supportedDevices: _configuration.dragDevices),
  2455.               (PanGestureRecognizer instance) {
  2456.                 instance
  2457.                   ..onDown = _handleDragDown
  2458.                   ..onStart = _handleDragStart
  2459.                   ..onUpdate = _handleDragUpdate
  2460.                   ..onEnd = _handleDragEnd
  2461.                   ..onCancel = _handleDragCancel
  2462.                   ..minFlingDistance = _physics?.minFlingDistance
  2463.                   ..minFlingVelocity = _physics?.minFlingVelocity
  2464.                   ..maxFlingVelocity = _physics?.maxFlingVelocity
  2465.                   ..velocityTrackerBuilder =
  2466.                       _configuration.velocityTrackerBuilder(context)
  2467.                   ..dragStartBehavior = widget.dragStartBehavior
  2468.                   ..gestureSettings = _mediaQueryGestureSettings;
  2469.               },
  2470.             ),
  2471.           };
  2472.           // Cancel the active hold/drag (if any) because the gesture recognizers
  2473.           // will soon be disposed by our RawGestureDetector, and we won't be
  2474.           // receiving pointer up events to cancel the hold/drag.
  2475.           _handleDragCancel();
  2476.           _lastCanDrag = value;
  2477.           _lastAxisDirection = widget.axis;
  2478.           if (_gestureDetectorKey.currentState != null) {
  2479.             _gestureDetectorKey.currentState!
  2480.                 .replaceGestureRecognizers(_gestureRecognizers);
  2481.           }
  2482.         }
  2483.         return;
  2484.     }
  2485.   }
  2486.  
  2487.   @override
  2488.   Widget _buildChrome(BuildContext context, Widget child) {
  2489.     final ScrollableDetails details = ScrollableDetails(
  2490.       direction: widget.axisDirection,
  2491.       controller: _effectiveScrollController,
  2492.       clipBehavior: widget.clipBehavior,
  2493.     );
  2494.     // Skip building a scrollbar here, the dual scrollbar is added in
  2495.     // TwoDimensionalScrollableState.
  2496.     return _configuration.buildOverscrollIndicator(context, child, details);
  2497.   }
  2498. }
  2499.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement