Open ouvreboite opened 1 year ago
That is a very good question I don't have a good answer for. I think this projects goal is to be somewhere in between those two, leaning more towards abstraction, wherever its feasible to abstract the differences away.
In this case, though, I don't think there is a good way for us to do that. We could intercept the click events and only forward them if no double click event occurs shortly after, but I think that is not a good solution. So if you need this changed, I think you should file a feature request upstream. We could and probably should also document this behavior though.
Thank you. A workaround is simple, so I won't push to have it fixed upstream.
Talking about documenting web-specific click behavior: in web, the click events are received by the map even though another widget is in front of the map (the dropdown of a SearchAnchor, for example). This is not a MapLibre bug, but more a known limitation of Flutter's HtmlElementView. The solution is to wrap said widget with a PointerInterceptor.
For reference, here is my own solution, using a mixin to isolate the logic.
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
mixin DoubleClickDisambiguate on Widget{
DateTime? lastClick;
int millisecondsDoubleClickThreshold = 500;
/// On web platform, ensure the provided callback is only called for single click, and not for double-click.
/// On non-web platforms, the provided callback is called immediately (no disambiguation needed)
Future<void> disambiguateSingleClick(Function() callback) async {
// disambiguation is only needed on web
if(!kIsWeb){
await callback();
return;
}
var isSecondClick = _isLastClickWithinDoubleClickThreshold();
lastClick = DateTime.now();
if(!isSecondClick){
await Future.delayed(Duration(milliseconds: millisecondsDoubleClickThreshold+1));
var wasSingleClick = !_isLastClickWithinDoubleClickThreshold();
if (wasSingleClick) {
await callback();
}
}
}
_isLastClickWithinDoubleClickThreshold(){
return lastClick != null
&& lastClick!.millisecondsSinceEpoch + millisecondsDoubleClickThreshold > DateTime.now().millisecondsSinceEpoch;
}
}
It can be called like that:
class MapWidget extends StatelessWidget with DoubleClickDisambiguate {
@override
Widget build(BuildContext context){
return MaplibreMap(
onMapClick: (point, coordinates) async {
await disambiguateSingleClick(() => print("Map single-clicked on $coordinates"));
},
);
}
}
Sadly, the disambiguation does not work on all web platforms: chrome for Android does not support dbClick, so maplibre-gl-js
relies on touchstart/touchend to detect a "double-tap". In a double-tap context, only a single "click" event is fired, so there is no way to know if it's part of a double-tap or a legitimate single click.
cf https://github.com/maplibre/maplibre-gl-js/issues/3242
From what I see, this leaves a few possibilities:
flutter-maplibre-gl
also expose the touchstart/touchend events, to allow user to implement their own disambiguationflutter-maplibre-gl
expose internally the touchstart/touchend event, and implement disambiguation in https://github.com/maplibre/flutter-maplibre-gl/tree/main/maplibre_gl_webmaplibre-gl-js
handle disambiguation internally and expose some new event (~'onMapSingleClick') and expose it in flutter-maplibre-gl
Thank you for investigating this and sharing your findings.
I like the last option the most, because I think this would be a useful feature for other users of maplibre-gl-js as well. But if that is not possible or realistically will not be implemented soon, the third option also sounds fine to me (especially if someone could contribute a PR).
On the web platform,
onMapClick
will be called when performing a zoom (double-clicking).It seems it's somehow expected for the web (cf old comment on mapbox-gl-js: https://github.com/mapbox/mapbox-gl-js/issues/6524#issuecomment-382034961):
But that's not the case for iOS/Android: there, the onMapClick is distinct from the double-click-to-zoom.
This is unhelpful, especially considering that
onMapLongClick
is not supported on the web, meaning there is no simple way to trigger an event via the mouse on the web that won't interfere with the double click.From my point of view, either: