diff --git a/example/lib/pages/polygon.dart b/example/lib/pages/polygon.dart index 02f646041..d623e9bfc 100644 --- a/example/lib/pages/polygon.dart +++ b/example/lib/pages/polygon.dart @@ -18,9 +18,10 @@ class PolygonPage extends StatefulWidget { } class _PolygonPageState extends State { + LayerHitTestStrategy _hitTestStrategy = LayerHitTestStrategy.allElements; + final LayerHitNotifier _hitNotifier = ValueNotifier(null); - List? _prevHitValues; - List>? _hoverGons; + final _hoverGons = >[]; bool _useInvertedFill = false; @@ -29,14 +30,13 @@ class _PolygonPageState extends State { points: const [ LatLng(51.5, -0.09), LatLng(53.3498, -6.2603), + LatLng(52.230366, -5.767677), LatLng(48.8566, 2.3522), ], borderColor: Colors.red, borderStrokeWidth: 4, - hitValue: ( - title: 'Basic Unfilled Polygon', - subtitle: 'Nothing really special here...', - ), + label: 'Non-interactive', + labelStyle: const TextStyle(color: Colors.black), ), Polygon( points: const [ @@ -52,6 +52,20 @@ class _PolygonPageState extends State { subtitle: 'Nothing really special here...', ), ), + Polygon( + points: const [ + LatLng(53.688428, 0.842058), + LatLng(51.962732, 1.589128), + LatLng(53.844279, 3.91823), + ], + color: Colors.purple.withAlpha(255 ~/ 2), + borderColor: Colors.orange, + borderStrokeWidth: 4, + hitValue: ( + title: 'Interactive Overlap', + subtitle: 'Both polygons appear, this should be on top', + ), + ), Polygon( points: const [ LatLng(46.35, 4.94), @@ -62,10 +76,6 @@ class _PolygonPageState extends State { borderStrokeWidth: 4, borderColor: Colors.lightBlue, color: Colors.yellow, - hitValue: ( - title: 'Polygon With Dashed Borders', - subtitle: '...', - ), ), Polygon( points: const [ @@ -77,10 +87,7 @@ class _PolygonPageState extends State { borderStrokeWidth: 4, borderColor: Colors.purple, label: 'Label!', - hitValue: ( - title: 'Polygon With Label', - subtitle: 'This is a very descriptive label!', - ), + labelStyle: const TextStyle(color: Colors.black), ), Polygon( points: const [ @@ -95,6 +102,7 @@ class _PolygonPageState extends State { label: 'Rotated!', rotateLabel: true, labelPlacement: PolygonLabelPlacement.polylabel, + labelStyle: const TextStyle(color: Colors.black), hitValue: ( title: 'Polygon With Rotated Label', subtitle: "Now you don't have to turn your head so much", @@ -140,7 +148,8 @@ class _PolygonPageState extends State { labelStyle: const TextStyle(color: Colors.black), hitValue: ( title: 'Polygon With Hole', - subtitle: 'A bit like Swiss cheese maybe?', + subtitle: 'A bit like Swiss cheese maybe? Also overlaps a ' + 'non-interactive polygon.', ), ), Polygon( @@ -183,10 +192,6 @@ class _PolygonPageState extends State { rotateLabel: true, labelPlacement: PolygonLabelPlacement.centroid, labelStyle: const TextStyle(color: Colors.black), - hitValue: ( - title: 'Polygon With Hole & Self Intersection', - subtitle: 'This one still works with performant rendering', - ), ), Polygon( points: const [ @@ -304,15 +309,13 @@ class _PolygonPageState extends State { hitTestBehavior: HitTestBehavior.deferToChild, cursor: SystemMouseCursors.click, onHover: (_) { - final hitValues = _hitNotifier.value?.hitValues.toList(); - if (hitValues == null) return; + _hoverGons.clear(); - if (listEquals(hitValues, _prevHitValues)) return; - _prevHitValues = hitValues; + final hitValues = _hitNotifier.value?.hitValues.toList(); + if (hitValues == null) return setState(() {}); - final hoverLines = hitValues.map((v) { + _hoverGons.addAll(hitValues.map((v) { final original = _polygons[v]!; - return Polygon( points: original.points, holePointsList: original.holePointsList, @@ -321,35 +324,30 @@ class _PolygonPageState extends State { borderColor: Colors.green, disableHolesBorder: original.disableHolesBorder, ); - }).toList(); - setState(() => _hoverGons = hoverLines); - }, - onExit: (_) { - _prevHitValues = null; - setState(() => _hoverGons = null); + })); + setState(() {}); }, + onExit: (_) => setState(_hoverGons.clear), child: GestureDetector( onTap: () => _openTouchedGonsModal( 'Tapped', - _hitNotifier.value!.hitValues, - _hitNotifier.value!.coordinate, + _hitNotifier.value, ), onLongPress: () => _openTouchedGonsModal( 'Long pressed', - _hitNotifier.value!.hitValues, - _hitNotifier.value!.coordinate, + _hitNotifier.value, ), onSecondaryTap: () => _openTouchedGonsModal( 'Secondary tapped', - _hitNotifier.value!.hitValues, - _hitNotifier.value!.coordinate, + _hitNotifier.value, ), child: PolygonLayer( hitNotifier: _hitNotifier, simplificationTolerance: 0, + hitTestStrategy: _hitTestStrategy, invertedFill: _useInvertedFill ? Colors.pink.withAlpha(170) : null, - polygons: [..._polygonsRaw, ...?_hoverGons], + polygons: [..._polygonsRaw, ..._hoverGons], ), ), ), @@ -431,69 +429,119 @@ class _PolygonPageState extends State { Positioned( top: 16, right: 16, - child: ClipRRect( - borderRadius: BorderRadius.circular(kIsWeb ? 16 : 32), - child: ColoredBox( - color: Theme.of(context).colorScheme.surface, - child: Column( - children: [ - Padding( - padding: const EdgeInsets.only( - left: 12, - right: 8, - top: 4, - bottom: 4, - ), - child: Row( - mainAxisSize: MainAxisSize.max, - spacing: 8, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Tooltip( - message: 'Use Inverted Fill', - child: Icon(Icons.invert_colors), - ), - Switch.adaptive( - value: _useInvertedFill, - onChanged: (v) => - setState(() => _useInvertedFill = v), - ), - ], - ), + child: Column( + spacing: 8, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + DecoratedBox( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: BorderRadius.circular(32), + ), + child: Padding( + padding: const EdgeInsets.only( + left: 16, + right: 16, + top: 4, + bottom: 4, ), - if (kIsWeb) - ColoredBox( - color: Colors.amber, - child: Padding( + child: Row( + mainAxisSize: MainAxisSize.min, + spacing: 16, + children: [ + const Tooltip( + message: 'Adjust Hit Test Strategy', + child: Icon(Icons.ads_click), + ), + DropdownButton( + value: _hitTestStrategy, + items: const [ + DropdownMenuItem( + value: LayerHitTestStrategy.allElements, + child: Text('All Elements'), + ), + DropdownMenuItem( + value: + LayerHitTestStrategy.onlyInteractiveElements, + child: Text('Only Interactive Elements'), + ), + DropdownMenuItem( + value: LayerHitTestStrategy.inverted, + child: Text('Inverted'), + ), + ], + onChanged: (v) => + setState(() => _hitTestStrategy = v!), + ), + ], + ), + ), + ), + ClipRRect( + borderRadius: BorderRadius.circular(kIsWeb ? 16 : 32), + child: ColoredBox( + color: Theme.of(context).colorScheme.surface, + child: Column( + children: [ + Padding( padding: const EdgeInsets.only( - left: 16, - right: 16, - top: 6, - bottom: 6, + left: 12, + right: 8, + top: 4, + bottom: 4, ), child: Row( - mainAxisSize: MainAxisSize.min, + mainAxisSize: MainAxisSize.max, spacing: 8, + mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon(Icons.warning), - const Icon(Icons.web_asset_off), - IconButton( - onPressed: () => launchUrl(Uri.parse( - 'https://docs.fleaflet.dev/layers/polygon-layer#inverted-filling', - )), - style: ButtonStyle( - backgroundColor: - WidgetStatePropertyAll(Colors.amber[100]), - ), - icon: const Icon(Icons.open_in_new), + const Tooltip( + message: 'Use Inverted Fill', + child: Icon(Icons.invert_colors), + ), + Switch.adaptive( + value: _useInvertedFill, + onChanged: (v) => + setState(() => _useInvertedFill = v), ), ], ), ), - ), - ], + if (kIsWeb) + ColoredBox( + color: Colors.amber, + child: Padding( + padding: const EdgeInsets.only( + left: 16, + right: 16, + top: 6, + bottom: 6, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + spacing: 8, + children: [ + const Icon(Icons.warning), + const Icon(Icons.web_asset_off), + IconButton( + onPressed: () => launchUrl(Uri.parse( + 'https://docs.fleaflet.dev/layers/polygon-layer#inverted-filling', + )), + style: ButtonStyle( + backgroundColor: WidgetStatePropertyAll( + Colors.amber[100]), + ), + icon: const Icon(Icons.open_in_new), + ), + ], + ), + ), + ), + ], + ), + ), ), - ), + ], ), ), ], @@ -503,9 +551,17 @@ class _PolygonPageState extends State { void _openTouchedGonsModal( String eventType, - List tappedLines, - LatLng coords, + LayerHitResult? hitResult, ) { + if (hitResult == null) { + ScaffoldMessenger.of(context) + ..hideCurrentSnackBar() + ..showSnackBar( + const SnackBar(content: Text('Hit detected outside of polygons')), + ); + return; + } + showModalBottomSheet( context: context, builder: (context) => Padding( @@ -514,31 +570,58 @@ class _PolygonPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( - 'Tapped Polygon(s)', + 'Hit Polygon(s)', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), Text( - '$eventType at point: (${coords.latitude.toStringAsFixed(6)}, ${coords.longitude.toStringAsFixed(6)})', + '$eventType at coords: (' + '${hitResult.coordinate.latitude.toStringAsFixed(4)}, ' + '${hitResult.coordinate.longitude.toStringAsFixed(4)})', ), const SizedBox(height: 8), - Expanded( - child: ListView.builder( - itemBuilder: (context, index) { - final tappedLineData = tappedLines[index]; - return ListTile( - leading: index == 0 - ? const Icon(Icons.vertical_align_top) - : index == tappedLines.length - 1 - ? const Icon(Icons.vertical_align_bottom) - : const SizedBox.shrink(), - title: Text(tappedLineData.title), - subtitle: Text(tappedLineData.subtitle), - dense: true, - ); - }, - itemCount: tappedLines.length, + if (hitResult.hitValues.isNotEmpty) + Expanded( + child: ListView.builder( + itemBuilder: (context, index) { + if (index == hitResult.hitValues.length) { + return const ListTile( + leading: Icon(Icons.highlight_alt_rounded), + title: + Text('Potentially other non-`hitValue` polygons'), + dense: true, + ); + } + + final hitValue = hitResult.hitValues[index]; + return ListTile( + leading: index == 0 + ? const Icon(Icons.vertical_align_top) + : index == hitResult.hitValues.length - 1 + ? const Icon(Icons.vertical_align_bottom) + : const SizedBox.shrink(), + title: Text(hitValue.title), + subtitle: Text(hitValue.subtitle), + dense: true, + ); + }, + itemCount: hitResult.hitValues.length + 1, + ), + ) + else + const Expanded( + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.highlight_alt_rounded, size: 42), + Text( + 'Polygon(s) were hit, but none had `hitValues`', + textAlign: TextAlign.center, + ), + ], + ), + ), ), - ), const SizedBox(height: 8), Align( alignment: Alignment.bottomCenter, diff --git a/lib/flutter_map.dart b/lib/flutter_map.dart index acb68995f..7180212af 100644 --- a/lib/flutter_map.dart +++ b/lib/flutter_map.dart @@ -35,6 +35,7 @@ export 'package:flutter_map/src/layer/polyline_layer/polyline_layer.dart'; export 'package:flutter_map/src/layer/scalebar/scalebar.dart'; export 'package:flutter_map/src/layer/shared/layer_interactivity/layer_hit_notifier.dart'; export 'package:flutter_map/src/layer/shared/layer_interactivity/layer_hit_result.dart'; +export 'package:flutter_map/src/layer/shared/layer_interactivity/layer_hit_test_strategy.dart'; export 'package:flutter_map/src/layer/shared/line_patterns/stroke_pattern.dart'; export 'package:flutter_map/src/layer/shared/mobile_layer_transformer.dart'; export 'package:flutter_map/src/layer/shared/translucent_pointer.dart'; diff --git a/lib/src/layer/circle_layer/circle_layer.dart b/lib/src/layer/circle_layer/circle_layer.dart index 72fdbdb58..af7d28974 100644 --- a/lib/src/layer/circle_layer/circle_layer.dart +++ b/lib/src/layer/circle_layer/circle_layer.dart @@ -16,14 +16,18 @@ class CircleLayer extends StatelessWidget { /// The list of [CircleMarker]s. final List> circles; - /// {@macro fm.lhn.layerHitNotifier.usage} + /// {@macro fm.layerHitNotifier.usage} final LayerHitNotifier? hitNotifier; + /// {@macro fm.layerHitTestStrategy.usage} + final LayerHitTestStrategy hitTestStrategy; + /// Create a new [CircleLayer] as a child for [FlutterMap] const CircleLayer({ super.key, required this.circles, this.hitNotifier, + this.hitTestStrategy = LayerHitTestStrategy.allElements, }); @override @@ -36,6 +40,7 @@ class CircleLayer extends StatelessWidget { circles: circles, camera: camera, hitNotifier: hitNotifier, + hitTestStrategy: hitTestStrategy, ), size: camera.size, isComplex: true, diff --git a/lib/src/layer/circle_layer/painter.dart b/lib/src/layer/circle_layer/painter.dart index 8fd6b2058..8c8fc3a28 100644 --- a/lib/src/layer/circle_layer/painter.dart +++ b/lib/src/layer/circle_layer/painter.dart @@ -12,12 +12,16 @@ class CirclePainter extends CustomPainter @override final LayerHitNotifier? hitNotifier; + @override + final LayerHitTestStrategy hitTestStrategy; + /// Create a [CirclePainter] instance by providing the required /// reference objects. CirclePainter({ required this.circles, required this.camera, required this.hitNotifier, + required this.hitTestStrategy, }); @override @@ -45,7 +49,7 @@ class CirclePainter extends CustomPainter } @override - Iterable> get elements => circles; + List> get elements => circles; @override void paint(Canvas canvas, Size size) { diff --git a/lib/src/layer/polygon_layer/painter.dart b/lib/src/layer/polygon_layer/painter.dart index 8406fa299..4ce84d88b 100644 --- a/lib/src/layer/polygon_layer/painter.dart +++ b/lib/src/layer/polygon_layer/painter.dart @@ -9,7 +9,7 @@ class _PolygonPainter extends CustomPainter final List<_ProjectedPolygon> polygons; @override - Iterable<_ProjectedPolygon> get elements => polygons; + List<_ProjectedPolygon> get elements => polygons; /// Triangulated [polygons] if available /// @@ -47,6 +47,9 @@ class _PolygonPainter extends CustomPainter @override final LayerHitNotifier? hitNotifier; + @override + final LayerHitTestStrategy hitTestStrategy; + /// Create a new [_PolygonPainter] instance. _PolygonPainter({ required this.polygons, @@ -57,6 +60,7 @@ class _PolygonPainter extends CustomPainter required this.camera, required this.invertedFill, required this.hitNotifier, + required this.hitTestStrategy, }) : bounds = camera.visibleBounds; /// Corner coordinates of the polygon painted onto the entire world when using diff --git a/lib/src/layer/polygon_layer/polygon_layer.dart b/lib/src/layer/polygon_layer/polygon_layer.dart index dfddb6d31..cdc1bf7a8 100644 --- a/lib/src/layer/polygon_layer/polygon_layer.dart +++ b/lib/src/layer/polygon_layer/polygon_layer.dart @@ -78,9 +78,12 @@ base class PolygonLayer /// > for more info. final Color? invertedFill; - /// {@macro fm.lhn.layerHitNotifier.usage} + /// {@macro fm.layerHitNotifier.usage} final LayerHitNotifier? hitNotifier; + /// {@macro fm.layerHitTestStrategy.usage} + final LayerHitTestStrategy hitTestStrategy; + /// Create a new [PolygonLayer] for the [FlutterMap] widget. const PolygonLayer({ super.key, @@ -92,6 +95,7 @@ base class PolygonLayer this.drawLabelsLast = false, this.invertedFill, this.hitNotifier, + this.hitTestStrategy = LayerHitTestStrategy.allElements, super.simplificationTolerance, }) : super(); @@ -209,6 +213,7 @@ class _PolygonLayerState extends State> invertedFill: widget.invertedFill, debugAltRenderer: widget.debugAltRenderer, hitNotifier: widget.hitNotifier, + hitTestStrategy: widget.hitTestStrategy, ), size: camera.size, ), diff --git a/lib/src/layer/polyline_layer/painter.dart b/lib/src/layer/polyline_layer/painter.dart index 661b5afe9..c63dd1e17 100644 --- a/lib/src/layer/polyline_layer/painter.dart +++ b/lib/src/layer/polyline_layer/painter.dart @@ -14,12 +14,16 @@ class _PolylinePainter extends CustomPainter @override final LayerHitNotifier? hitNotifier; + @override + final LayerHitTestStrategy hitTestStrategy; + /// Create a new [_PolylinePainter] instance _PolylinePainter({ required this.polylines, required this.minimumHitbox, required this.camera, required this.hitNotifier, + required this.hitTestStrategy, }); @override @@ -78,7 +82,7 @@ class _PolylinePainter extends CustomPainter } @override - Iterable<_ProjectedPolyline> get elements => polylines; + List<_ProjectedPolyline> get elements => polylines; @override void paint(Canvas canvas, Size size) { diff --git a/lib/src/layer/polyline_layer/polyline_layer.dart b/lib/src/layer/polyline_layer/polyline_layer.dart index 75d7df43d..8bad76dea 100644 --- a/lib/src/layer/polyline_layer/polyline_layer.dart +++ b/lib/src/layer/polyline_layer/polyline_layer.dart @@ -36,9 +36,12 @@ base class PolylineLayer /// Defaults to 10. Set to `null` to disable culling. final double? cullingMargin; - /// {@macro fm.lhn.layerHitNotifier.usage} + /// {@macro fm.layerHitNotifier.usage} final LayerHitNotifier? hitNotifier; + /// {@macro fm.layerHitTestStrategy.usage} + final LayerHitTestStrategy hitTestStrategy; + /// The minimum radius of the hittable area around each [Polyline] in logical /// pixels /// @@ -54,6 +57,7 @@ base class PolylineLayer required this.polylines, this.cullingMargin = 10, this.hitNotifier, + this.hitTestStrategy = LayerHitTestStrategy.allElements, this.minimumHitbox = 10, super.simplificationTolerance, }) : super(); @@ -111,6 +115,7 @@ class _PolylineLayerState extends State> polylines: culled, camera: camera, hitNotifier: widget.hitNotifier, + hitTestStrategy: widget.hitTestStrategy, minimumHitbox: widget.minimumHitbox, ), size: camera.size, diff --git a/lib/src/layer/shared/layer_interactivity/internal_hit_detectable.dart b/lib/src/layer/shared/layer_interactivity/internal_hit_detectable.dart index 85e2e89c9..22cf59e93 100644 --- a/lib/src/layer/shared/layer_interactivity/internal_hit_detectable.dart +++ b/lib/src/layer/shared/layer_interactivity/internal_hit_detectable.dart @@ -12,6 +12,10 @@ mixin HitDetectableElement { /// Elements without a defined [hitValue] are still hit tested, but are not /// notified about. /// + /// When a [hitValue] is defined on a layer, that layer will always capture a + /// hit, and the value will always appear in the list of hits in + /// [LayerHitResult.hitValues] (if a notifier is defined). + /// /// The object should have a valid & useful equality, as it may be used /// by FM internals. R? get hitValue; @@ -22,12 +26,13 @@ mixin HitDetectablePainter> on CustomPainter { abstract final MapCamera camera; abstract final LayerHitNotifier? hitNotifier; + abstract final LayerHitTestStrategy hitTestStrategy; /// Elements that should be possibly be hit tested by [elementHitTest] /// ([hitTest]) /// /// See [elementHitTest] for more information. - Iterable get elements; + List get elements; /// Method invoked by [hitTest] for every element (each of [elements] in /// reverse order) that requires testing @@ -57,31 +62,50 @@ mixin HitDetectablePainter> @override @mustCallSuper bool? hitTest(Offset position) { - _hits.clear(); - bool hasHit = false; + final coordinate = camera.screenOffsetToLatLng(position); + bool? hitResult; - final point = position; - final coordinate = camera.screenOffsetToLatLng(point); + _hits.clear(); for (int i = elements.length - 1; i >= 0; i--) { - final element = elements.elementAt(i); - if (hasHit && element.hitValue == null) continue; - if (elementHitTest(element, point: point, coordinate: coordinate)) { - if (element.hitValue != null) _hits.add(element.hitValue!); - hasHit = true; + final element = elements[i]; + + // If we're not going to change anything even if we hit, don't bother + // testing for a hit + late final addsToHitsList = element.hitValue != null; + late final setsHitResult = + hitTestStrategy == LayerHitTestStrategy.allElements && + hitResult != true; + late final unsetsHitResult = + hitTestStrategy == LayerHitTestStrategy.inverted && hitResult == null; + + if ((addsToHitsList || setsHitResult || unsetsHitResult) && + elementHitTest(element, point: position, coordinate: coordinate)) { + if (element.hitValue != null) { + _hits.add(element.hitValue!); + hitResult = true; + continue; + } + if (hitTestStrategy == LayerHitTestStrategy.allElements) { + hitResult = true; + } + if (hitTestStrategy == LayerHitTestStrategy.inverted) { + hitResult ??= false; + } } } - if (!hasHit) { - hitNotifier?.value = null; - return false; + if (hitResult ?? false) { + hitNotifier?.value = LayerHitResult( + hitValues: _hits, + coordinate: coordinate, + point: position, + ); + return true; } - hitNotifier?.value = LayerHitResult( - hitValues: _hits, - coordinate: coordinate, - point: point, - ); - return true; + hitNotifier?.value = null; + return hitTestStrategy == LayerHitTestStrategy.inverted && + hitResult == null; } } diff --git a/lib/src/layer/shared/layer_interactivity/layer_hit_notifier.dart b/lib/src/layer/shared/layer_interactivity/layer_hit_notifier.dart index 50b2d1474..9818cad28 100644 --- a/lib/src/layer/shared/layer_interactivity/layer_hit_notifier.dart +++ b/lib/src/layer/shared/layer_interactivity/layer_hit_notifier.dart @@ -1,8 +1,8 @@ import 'package:flutter/foundation.dart'; -import 'package:flutter_map/src/layer/shared/layer_interactivity/layer_hit_result.dart'; +import 'package:flutter_map/flutter_map.dart'; -/// A [ValueNotifier] that notifies: +/// A [ValueNotifier] that emits: /// /// * a [LayerHitResult] when a hit is detected on an element in a layer /// * `null` when a hit is detected on the layer but not on an element @@ -11,9 +11,17 @@ import 'package:flutter_map/src/layer/shared/layer_interactivity/layer_hit_resul /// ```dart /// final LayerHitNotifier hitNotifier = ValueNotifier(null); /// ``` +/// +/// Note that whether or not this is defined on a layer does not affect whether +/// the layer conducts hit testing. +/// +/// A layer's hit test result, the behaviour of which is determined by +/// [LayerHitTestStrategy], is independent of the values emitted by any notifier +/// attached to the layer. The layer's hit test result can only be a boolean +/// flag, whereas the notifier allows more detail to be emitted. typedef LayerHitNotifier = ValueNotifier?>; -/// {@template fm.lhn.layerHitNotifier.usage} +/// {@template fm.layerHitNotifier.usage} /// A notifier to be notified when a hit test occurs on the layer /// /// Notified with a [LayerHitResult] if any elements are hit, otherwise @@ -25,4 +33,4 @@ typedef LayerHitNotifier = ValueNotifier?>; /// example project for an example implementation. /// {@endtemplate} // ignore: unused_element, constant_identifier_names -const _doc_fmLHNLayerHitNotiferUsage = null; +const _doc_fmLayerHitNotifierUsage = null; diff --git a/lib/src/layer/shared/layer_interactivity/layer_hit_test_strategy.dart b/lib/src/layer/shared/layer_interactivity/layer_hit_test_strategy.dart new file mode 100644 index 000000000..55e469263 --- /dev/null +++ b/lib/src/layer/shared/layer_interactivity/layer_hit_test_strategy.dart @@ -0,0 +1,39 @@ +import 'package:flutter_map/flutter_map.dart'; + +/// Controls the results of hit tests performed on hittable layers +/// +/// Elements within hittable layers are always (at least for the purpose of +/// external API usage) hit tested when the layers are hit tested. This controls +/// how the results of each element's hit test affects the layer's hit test +/// result (potentially depending on whether elements have non-null +/// `hitValue`s). +/// +/// A layer's hit test result is independent of the [LayerHitResult]s emitted by +/// any [LayerHitNotifier] attached to the layer. The layer's hit test result +/// can only be a boolean flag, whereas the notifier allows more detail to be +/// emitted. +enum LayerHitTestStrategy { + /// Positive if any element has been hit + /// + /// The default behaviour. + allElements, + + /// Positive if any element with a non-null `hitValue` has been hit + onlyInteractiveElements, + + /// Positive if no elements have been hit, or any element with a non-null + /// `hitValue` has been hit + inverted, +} + +/// {@template fm.layerHitTestStrategy.usage} +/// The strategy to use to determine the result of a hit test performed on this +/// layer +/// +/// Defaults to [LayerHitTestStrategy.allElements]. +/// +/// See online documentation for more detailed usage instructions. See the +/// example project for an example implementation. +/// {@endtemplate} +// ignore: unused_element, constant_identifier_names +const _doc_fmLayerHitTestStrategyUsage = null;