Skip to content

Commit

Permalink
Add an onTap callback to polylines.
Browse files Browse the repository at this point in the history
  • Loading branch information
ignatz committed Nov 10, 2023
1 parent 270b331 commit 571f008
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 16 deletions.
13 changes: 13 additions & 0 deletions example/lib/pages/polyline.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class _PolylinePageState extends State<PolylinePage> {
userAgentPackageName: 'dev.fleaflet.flutter_map.example',
),
PolylineLayer(
interactive: true,
polylines: [
Polyline(
points: [
Expand Down Expand Up @@ -72,6 +73,18 @@ class _PolylinePageState extends State<PolylinePage> {
color: Colors.blue.withOpacity(0.6),
borderStrokeWidth: 20,
borderColor: Colors.red.withOpacity(0.4),
onTap: (LatLng point) {
showDialog<void>(
context: context,
builder: (BuildContext context) {
return Dialog(
child: Padding(
padding: const EdgeInsets.all(8),
child: Text('clicked $point'),
),
);
});
},
),
Polyline(
points: [
Expand Down
137 changes: 121 additions & 16 deletions lib/src/layer/polyline_layer.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'dart:core';
import 'dart:math' as math;
import 'dart:ui' as ui;

import 'package:flutter/widgets.dart';
Expand All @@ -20,6 +21,7 @@ class Polyline {
final StrokeCap strokeCap;
final StrokeJoin strokeJoin;
final bool useStrokeWidthInMeter;
final void Function(LatLng point)? onTap;

LatLngBounds? _boundingBox;

Expand All @@ -38,6 +40,7 @@ class Polyline {
this.strokeCap = StrokeCap.round,
this.strokeJoin = StrokeJoin.round,
this.useStrokeWidthInMeter = false,
this.onTap,
});

/// Used to batch draw calls to the canvas.
Expand All @@ -54,45 +57,78 @@ class Polyline {
useStrokeWidthInMeter);
}

class _Hit {
final Polyline polyline;
final LatLng point;

const _Hit(this.polyline, this.point);
}

class _LastHit {
_Hit? hit;
}

@immutable
class PolylineLayer extends StatelessWidget {
final List<Polyline> polylines;
final bool polylineCulling;
final bool interactive;

const PolylineLayer({
super.key,
required this.polylines,
this.polylineCulling = false,
//@Deprecated('Let's always cull')
bool polylineCulling = true,
this.interactive = false,
});

@override
Widget build(BuildContext context) {
final map = MapCamera.of(context);

final lastHit = _LastHit();
final paint = CustomPaint(
painter: _PolylinePainter(
polylines
.where((p) => p.boundingBox.isOverlapping(map.visibleBounds))
.toList(),
map,
interactive ? lastHit : null,
),
size: Size(map.size.x, map.size.y),
isComplex: true,
);

if (!interactive) {
return MobileLayerTransformer(child: paint);
}

return MobileLayerTransformer(
child: CustomPaint(
painter: PolylinePainter(
polylineCulling
? polylines
.where((p) => p.boundingBox.isOverlapping(map.visibleBounds))
.toList()
: polylines,
map,
),
size: Size(map.size.x, map.size.y),
isComplex: true,
child: GestureDetector(
behavior: HitTestBehavior.deferToChild,
onTap: () {
final hit = lastHit.hit;
if (hit == null) return;

final onTap = hit.polyline.onTap;
if (onTap != null) {
onTap(hit.point);
}
},
child: paint,
),
);
}
}

class PolylinePainter extends CustomPainter {
class _PolylinePainter extends CustomPainter {
final List<Polyline> polylines;

final MapCamera map;
final LatLngBounds bounds;
final _LastHit? lastHit;

PolylinePainter(this.polylines, this.map) : bounds = map.visibleBounds;
_PolylinePainter(this.polylines, this.map, this.lastHit)
: bounds = map.visibleBounds;

int get hash => _hash ??= Object.hashAll(polylines);

Expand All @@ -112,6 +148,56 @@ class PolylinePainter extends CustomPainter {
);
}

@override
bool? hitTest(Offset position) {
if (lastHit == null) {
return null;
}

final hit = map.pointToLatLng(math.Point(position.dx, position.dy));
final origin = map.project(map.center).toOffset() - map.size.toOffset() / 2;

final candidates = <Polyline>[];

outer:
for (final p in polylines) {
if (p.onTap == null) {
continue;
}

if (!p.boundingBox.contains(hit)) {
continue;
}

final offsets = getOffsets(origin, p.points);
for (int i = 0; i < offsets.length - 1; i++) {
final o1 = offsets[i];
final o2 = offsets[i + 1];

final distance = math.sqrt(_distToSegmentSquared(
position.dx,
position.dy,
o1.dx,
o1.dy,
o2.dx,
o2.dy,
));
if (distance < p.strokeWidth) {
candidates.add(p);
continue outer;
}
}
}

if (candidates.isNotEmpty) {
lastHit!.hit = _Hit(candidates.last, hit);
return true;
}

lastHit!.hit = null;
return false;
}

@override
void paint(Canvas canvas, Size size) {
final rect = Offset.zero & size;
Expand Down Expand Up @@ -291,9 +377,28 @@ class PolylinePainter extends CustomPainter {
}

@override
bool shouldRepaint(PolylinePainter oldDelegate) {
bool shouldRepaint(_PolylinePainter oldDelegate) {
return oldDelegate.bounds != bounds ||
oldDelegate.polylines.length != polylines.length ||
oldDelegate.hash != hash;
}
}

double _distanceSq(double x0, double y0, double x1, double y1) {
final dx = x0 - x1;
final dy = y0 - y1;
return dx * dx + dy * dy;
}

double _distToSegmentSquared(
double px, double py, double x0, double y0, double x1, double y1) {
final dx = x1 - x0;
final dy = y1 - y0;
final distanceSq = dx * dx + dy * dy;
if (distanceSq == 0) {
return _distanceSq(px, py, x0, y0);
}

final t = (((px - x0) * dx + (py - y0) * dy) / distanceSq).clamp(0, 1);
return _distanceSq(px, py, x0 + t * dx, y0 + t * dy);
}

0 comments on commit 571f008

Please sign in to comment.