Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
305 changes: 194 additions & 111 deletions example/lib/pages/polygon.dart

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions lib/flutter_map.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
7 changes: 6 additions & 1 deletion lib/src/layer/circle_layer/circle_layer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,18 @@ class CircleLayer<R extends Object> extends StatelessWidget {
/// The list of [CircleMarker]s.
final List<CircleMarker<R>> circles;

/// {@macro fm.lhn.layerHitNotifier.usage}
/// {@macro fm.layerHitNotifier.usage}
final LayerHitNotifier<R>? 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
Expand All @@ -36,6 +40,7 @@ class CircleLayer<R extends Object> extends StatelessWidget {
circles: circles,
camera: camera,
hitNotifier: hitNotifier,
hitTestStrategy: hitTestStrategy,
),
size: camera.size,
isComplex: true,
Expand Down
6 changes: 5 additions & 1 deletion lib/src/layer/circle_layer/painter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@ class CirclePainter<R extends Object> extends CustomPainter
@override
final LayerHitNotifier<R>? 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
Expand Down Expand Up @@ -45,7 +49,7 @@ class CirclePainter<R extends Object> extends CustomPainter
}

@override
Iterable<CircleMarker<R>> get elements => circles;
List<CircleMarker<R>> get elements => circles;

@override
void paint(Canvas canvas, Size size) {
Expand Down
6 changes: 5 additions & 1 deletion lib/src/layer/polygon_layer/painter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class _PolygonPainter<R extends Object> extends CustomPainter
final List<_ProjectedPolygon<R>> polygons;

@override
Iterable<_ProjectedPolygon<R>> get elements => polygons;
List<_ProjectedPolygon<R>> get elements => polygons;

/// Triangulated [polygons] if available
///
Expand Down Expand Up @@ -47,6 +47,9 @@ class _PolygonPainter<R extends Object> extends CustomPainter
@override
final LayerHitNotifier<R>? hitNotifier;

@override
final LayerHitTestStrategy hitTestStrategy;

/// Create a new [_PolygonPainter] instance.
_PolygonPainter({
required this.polygons,
Expand All @@ -57,6 +60,7 @@ class _PolygonPainter<R extends Object> 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
Expand Down
7 changes: 6 additions & 1 deletion lib/src/layer/polygon_layer/polygon_layer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,12 @@ base class PolygonLayer<R extends Object>
/// > for more info.
final Color? invertedFill;

/// {@macro fm.lhn.layerHitNotifier.usage}
/// {@macro fm.layerHitNotifier.usage}
final LayerHitNotifier<R>? hitNotifier;

/// {@macro fm.layerHitTestStrategy.usage}
final LayerHitTestStrategy hitTestStrategy;

/// Create a new [PolygonLayer] for the [FlutterMap] widget.
const PolygonLayer({
super.key,
Expand All @@ -92,6 +95,7 @@ base class PolygonLayer<R extends Object>
this.drawLabelsLast = false,
this.invertedFill,
this.hitNotifier,
this.hitTestStrategy = LayerHitTestStrategy.allElements,
super.simplificationTolerance,
}) : super();

Expand Down Expand Up @@ -209,6 +213,7 @@ class _PolygonLayerState<R extends Object> extends State<PolygonLayer<R>>
invertedFill: widget.invertedFill,
debugAltRenderer: widget.debugAltRenderer,
hitNotifier: widget.hitNotifier,
hitTestStrategy: widget.hitTestStrategy,
),
size: camera.size,
),
Expand Down
6 changes: 5 additions & 1 deletion lib/src/layer/polyline_layer/painter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@ class _PolylinePainter<R extends Object> extends CustomPainter
@override
final LayerHitNotifier<R>? 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
Expand Down Expand Up @@ -78,7 +82,7 @@ class _PolylinePainter<R extends Object> extends CustomPainter
}

@override
Iterable<_ProjectedPolyline<R>> get elements => polylines;
List<_ProjectedPolyline<R>> get elements => polylines;

@override
void paint(Canvas canvas, Size size) {
Expand Down
7 changes: 6 additions & 1 deletion lib/src/layer/polyline_layer/polyline_layer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,12 @@ base class PolylineLayer<R extends Object>
/// Defaults to 10. Set to `null` to disable culling.
final double? cullingMargin;

/// {@macro fm.lhn.layerHitNotifier.usage}
/// {@macro fm.layerHitNotifier.usage}
final LayerHitNotifier<R>? hitNotifier;

/// {@macro fm.layerHitTestStrategy.usage}
final LayerHitTestStrategy hitTestStrategy;

/// The minimum radius of the hittable area around each [Polyline] in logical
/// pixels
///
Expand All @@ -54,6 +57,7 @@ base class PolylineLayer<R extends Object>
required this.polylines,
this.cullingMargin = 10,
this.hitNotifier,
this.hitTestStrategy = LayerHitTestStrategy.allElements,
this.minimumHitbox = 10,
super.simplificationTolerance,
}) : super();
Expand Down Expand Up @@ -111,6 +115,7 @@ class _PolylineLayerState<R extends Object> extends State<PolylineLayer<R>>
polylines: culled,
camera: camera,
hitNotifier: widget.hitNotifier,
hitTestStrategy: widget.hitTestStrategy,
minimumHitbox: widget.minimumHitbox,
),
size: camera.size,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ mixin HitDetectableElement<R extends Object> {
/// 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;
Expand All @@ -22,12 +26,13 @@ mixin HitDetectablePainter<R extends Object, E extends HitDetectableElement<R>>
on CustomPainter {
abstract final MapCamera camera;
abstract final LayerHitNotifier<R>? hitNotifier;
abstract final LayerHitTestStrategy hitTestStrategy;

/// Elements that should be possibly be hit tested by [elementHitTest]
/// ([hitTest])
///
/// See [elementHitTest] for more information.
Iterable<E> get elements;
List<E> get elements;

/// Method invoked by [hitTest] for every element (each of [elements] in
/// reverse order) that requires testing
Expand Down Expand Up @@ -57,31 +62,50 @@ mixin HitDetectablePainter<R extends Object, E extends HitDetectableElement<R>>
@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;
}
}
16 changes: 12 additions & 4 deletions lib/src/layer/shared/layer_interactivity/layer_hit_notifier.dart
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -11,9 +11,17 @@ import 'package:flutter_map/src/layer/shared/layer_interactivity/layer_hit_resul
/// ```dart
/// final LayerHitNotifier<Object> 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<R extends Object> = ValueNotifier<LayerHitResult<R>?>;

/// {@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
Expand All @@ -25,4 +33,4 @@ typedef LayerHitNotifier<R extends Object> = ValueNotifier<LayerHitResult<R>?>;
/// example project for an example implementation.
/// {@endtemplate}
// ignore: unused_element, constant_identifier_names
const _doc_fmLHNLayerHitNotiferUsage = null;
const _doc_fmLayerHitNotifierUsage = null;
Original file line number Diff line number Diff line change
@@ -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;