Skip to content

Commit

Permalink
feat: add more events (#41)
Browse files Browse the repository at this point in the history
- [x] on map idle (no movement / no rendering)
- [x] on map camera idle (no movement)
- [x] on start change camera with reason
  • Loading branch information
josxha authored Sep 23, 2024
1 parent d6920d1 commit 2028462
Show file tree
Hide file tree
Showing 16 changed files with 1,260 additions and 230 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ import org.maplibre.android.geometry.LatLng
import org.maplibre.android.geometry.LatLngBounds
import org.maplibre.android.geometry.LatLngQuad
import org.maplibre.android.maps.MapLibreMap
import org.maplibre.android.maps.MapLibreMap.OnCameraMoveStartedListener.REASON_API_ANIMATION
import org.maplibre.android.maps.MapLibreMap.OnCameraMoveStartedListener.REASON_API_GESTURE
import org.maplibre.android.maps.MapLibreMap.OnCameraMoveStartedListener.REASON_DEVELOPER_ANIMATION
import org.maplibre.android.maps.MapLibreMapOptions
import org.maplibre.android.maps.MapView
import org.maplibre.android.maps.OnMapReadyCallback
Expand Down Expand Up @@ -124,7 +127,18 @@ class MapLibreMapController(
val target = mapLibreMap.cameraPosition.target!!
val center = LngLat(target.longitude, target.latitude)
val camera = MapCamera(center, position.zoom, position.tilt, position.bearing)
flutterApi.onCameraMoved(camera) {}
flutterApi.onMoveCamera(camera) {}
}
this.mapLibreMap.addOnCameraIdleListener { flutterApi.onCameraIdle { } }
this.mapView.addOnDidBecomeIdleListener { flutterApi.onIdle { } }
this.mapLibreMap.addOnCameraMoveStartedListener { reason ->
val changeReason = when (reason) {
REASON_API_ANIMATION -> CameraChangeReason.API_ANIMATION
REASON_API_GESTURE -> CameraChangeReason.API_GESTURE
REASON_DEVELOPER_ANIMATION -> CameraChangeReason.DEVELOPER_ANIMATION
else -> null
}
if (changeReason != null) flutterApi.onStartMoveCamera(changeReason) { }
}
val style = Style.Builder().fromUri(mapOptions.style)
mapLibreMap.setStyle(style) { loadedStyle ->
Expand Down Expand Up @@ -560,17 +574,22 @@ class MapLibreMapController(
// remove map bounds
mapLibreMap.setLatLngBoundsForCameraTarget(null)
} else if (oldBounds == null && newBounds != null) {
val bounds = LatLngBounds.from(newBounds.latitudeNorth, newBounds.longitudeEast,
newBounds.latitudeSouth, newBounds.longitudeWest)
val bounds = LatLngBounds.from(
newBounds.latitudeNorth, newBounds.longitudeEast,
newBounds.latitudeSouth, newBounds.longitudeWest
)
mapLibreMap.setLatLngBoundsForCameraTarget(bounds)
} else if (newBounds != null &&
// can get improved when https://github.com/flutter/flutter/issues/118087 is implemented
(oldBounds?.latitudeNorth != newBounds.latitudeNorth
|| oldBounds.latitudeSouth != newBounds.latitudeSouth
|| oldBounds.longitudeEast != newBounds.longitudeEast
|| oldBounds.longitudeWest != newBounds.longitudeWest)) {
val bounds = LatLngBounds.from(newBounds.latitudeNorth, newBounds.longitudeEast,
newBounds.latitudeSouth, newBounds.longitudeWest)
|| oldBounds.latitudeSouth != newBounds.latitudeSouth
|| oldBounds.longitudeEast != newBounds.longitudeEast
|| oldBounds.longitudeWest != newBounds.longitudeWest)
) {
val bounds = LatLngBounds.from(
newBounds.latitudeNorth, newBounds.longitudeEast,
newBounds.latitudeSouth, newBounds.longitudeWest
)
mapLibreMap.setLatLngBoundsForCameraTarget(bounds)
}
}
Expand Down
101 changes: 90 additions & 11 deletions android/src/main/kotlin/com/github/josxha/maplibre/Pigeon.g.kt
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,22 @@ enum class RasterDemEncoding(val raw: Int) {
}
}

/** The reason the camera is changing. */
enum class CameraChangeReason(val raw: Int) {
/** Developer animation. */
DEVELOPER_ANIMATION(0),
/** API animation. */
API_ANIMATION(1),
/** API gesture */
API_GESTURE(2);

companion object {
fun ofRaw(raw: Int): CameraChangeReason? {
return values().firstOrNull { it.raw == raw }
}
}
}

/**
* The map options define initial values for the MapLibre map.
*
Expand Down Expand Up @@ -275,26 +291,31 @@ private open class PigeonPigeonCodec : StandardMessageCodec() {
}
}
131.toByte() -> {
return (readValue(buffer) as Long?)?.let {
CameraChangeReason.ofRaw(it.toInt())
}
}
132.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
MapOptions.fromList(it)
}
}
132.toByte() -> {
133.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
LngLat.fromList(it)
}
}
133.toByte() -> {
134.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
ScreenLocation.fromList(it)
}
}
134.toByte() -> {
135.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
MapCamera.fromList(it)
}
}
135.toByte() -> {
136.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
LngLatBounds.fromList(it)
}
Expand All @@ -312,24 +333,28 @@ private open class PigeonPigeonCodec : StandardMessageCodec() {
stream.write(130)
writeValue(stream, value.raw)
}
is MapOptions -> {
is CameraChangeReason -> {
stream.write(131)
writeValue(stream, value.raw)
}
is MapOptions -> {
stream.write(132)
writeValue(stream, value.toList())
}
is LngLat -> {
stream.write(132)
stream.write(133)
writeValue(stream, value.toList())
}
is ScreenLocation -> {
stream.write(133)
stream.write(134)
writeValue(stream, value.toList())
}
is MapCamera -> {
stream.write(134)
stream.write(135)
writeValue(stream, value.toList())
}
is LngLatBounds -> {
stream.write(135)
stream.write(136)
writeValue(stream, value.toList())
}
else -> super.writeValue(stream, value)
Expand Down Expand Up @@ -1094,6 +1119,42 @@ class MapLibreFlutterApi(private val binaryMessenger: BinaryMessenger, private v
}
}
}
/** Callback when the map idles. */
fun onIdle(callback: (Result<Unit>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.maplibre.MapLibreFlutterApi.onIdle$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(null) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
} else {
callback(Result.success(Unit))
}
} else {
callback(Result.failure(createConnectionError(channelName)))
}
}
}
/** Callback when the map camera idles. */
fun onCameraIdle(callback: (Result<Unit>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.maplibre.MapLibreFlutterApi.onCameraIdle$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(null) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
} else {
callback(Result.success(Unit))
}
} else {
callback(Result.failure(createConnectionError(channelName)))
}
}
}
/**
* Callback when the user performs a secondary click on the map
* (e.g. by default a click with the right mouse button).
Expand Down Expand Up @@ -1152,10 +1213,10 @@ class MapLibreFlutterApi(private val binaryMessenger: BinaryMessenger, private v
}
}
/** Callback when the map camera changes. */
fun onCameraMoved(cameraArg: MapCamera, callback: (Result<Unit>) -> Unit)
fun onMoveCamera(cameraArg: MapCamera, callback: (Result<Unit>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.maplibre.MapLibreFlutterApi.onCameraMoved$separatedMessageChannelSuffix"
val channelName = "dev.flutter.pigeon.maplibre.MapLibreFlutterApi.onMoveCamera$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(cameraArg)) {
if (it is List<*>) {
Expand All @@ -1169,4 +1230,22 @@ class MapLibreFlutterApi(private val binaryMessenger: BinaryMessenger, private v
}
}
}
/** Callback when the map camera starts changing. */
fun onStartMoveCamera(reasonArg: CameraChangeReason, callback: (Result<Unit>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.maplibre.MapLibreFlutterApi.onStartMoveCamera$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(reasonArg)) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
} else {
callback(Result.success(Unit))
}
} else {
callback(Result.failure(createConnectionError(channelName)))
}
}
}
}
36 changes: 6 additions & 30 deletions docs/docs/map-events.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,36 +59,12 @@ void _onEvent(event) {
|--------------------------|-------------|-----------------------------------|-----|---------|-------|-------|
| MapEventMapCreated | | | | | | |
| MapEventStyleLoaded | load | (OnDidFinishLoadingStyleListener) | | | | |
| MapEventClicked | click | | | | | |
| MapEventClicked | click | OnMapClickListener | | | | |
| MapEventDoubleClicked | dblclick | | | | | |
| MapEventSecondaryClicked | contextmenu | | | | | |
| MapEventLongClicked | | | | | | |
| | drag | | | | | |
| | dragstart | | | | | |
| | dragend | | | | | |
| | error | | | | | |
| | idle | OnDidBecomeIdleListener | | | | |
| MapEventLongClicked | | OnMapLongClickListener | | | | |
| MapEventIdle | idle | OnDidBecomeIdleListener | | | | |
| | mousedown | | | | | |
| | mousemove | | | | | |
| | mouseout | | | | | |
| | mouseover | | | | | |
| | mouseup | | | | | |
| | move | OnCameraIsChangingListener | | | | |
| | movestart | OnCameraWillChangeListener | | | | |
| MapEventMovementStopped | moveend | OnCameraDidChangeListener | | | | |
| | pitch | | | | | |
| | pitchstart | | | | | |
| | pitchend | | | | | |
| | pitchend | | | | | |
| | rotate | | | | | |
| | rotatestart | | | | | |
| | rotateend | | | | | |
| | rotateend | | | | | |
| | touchcancel | | | | | |
| | touchend | | | | | |
| | touchmove | | | | | |
| | touchstart | | | | | |
| | wheel | | | | | |
| | zoom | | | | | |
| | zoomstart | | | | | |
| | zoomend | | | | | |
| MapEventStartMoveCamera | | OnCameraMoveStartedListener | | | | |
| MapEventMoveCamera | move | OnCameraMoveListener | | | | |
| MapEventCameraIdle | moveend | OnCameraMoveCanceledListener | | | | |
40 changes: 23 additions & 17 deletions example/lib/events_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,23 @@ class EventsPage extends StatefulWidget {
}

class _EventsPageState extends State<EventsPage> {
String _lastEventMessage = '';
final _eventMessages = <String>[];

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Events')),
body: Column(
body: Stack(
children: [
Padding(
padding: const EdgeInsets.all(8),
child: Text(_lastEventMessage, textAlign: TextAlign.center),
MapLibreMap(
options: MapOptions(center: Position(9.17, 47.68)),
onEvent: _onEvent,
),
Expanded(
child: MapLibreMap(
options: MapOptions(center: Position(9.17, 47.68)),
onEvent: _onEvent,
IgnorePointer(
child: Container(
padding: const EdgeInsets.all(8),
alignment: Alignment.bottomLeft,
child: Text(_eventMessages.join('\n')),
),
),
],
Expand All @@ -38,28 +39,33 @@ class _EventsPageState extends State<EventsPage> {
void _onEvent(MapEvent event) => switch (event) {
MapEventMapCreated() => _print('map created'),
MapEventStyleLoaded() => _print('style loaded'),
MapEventCameraMoved() => _print(
'camera moved: center ${_formatPosition(event.camera.center)}, '
MapEventMoveCamera() => _print(
'move camera: center ${_formatPosition(event.camera.center)}, '
'zoom ${event.camera.zoom.toStringAsFixed(2)}, '
'tilt ${event.camera.tilt.toStringAsFixed(2)}, '
'bearing ${event.camera.bearing.toStringAsFixed(2)}',
),
MapEventClicked() => _print('clicked: ${_formatPosition(event.point)}'),
MapEventDoubleClicked() =>
MapEventStartMoveCamera() =>
_print('start move camera, reason: ${event.reason.name}'),
MapEventClick() => _print('clicked: ${_formatPosition(event.point)}'),
MapEventDoubleClick() =>
_print('double clicked: ${_formatPosition(event.point)}'),
MapEventLongClicked() =>
MapEventLongClick() =>
_print('long clicked: ${_formatPosition(event.point)}'),
MapEventSecondaryClicked() =>
MapEventSecondaryClick() =>
_print('secondary clicked: ${_formatPosition(event.point)}'),
MapEventIdle() => _print('idle'),
MapEventCameraIdle() => _print('camera idle'),
};

void _print(String message) {
debugPrint('[MapLibreMap] $message');
setState(() {
_lastEventMessage = message;
_eventMessages.add(message);
if (_eventMessages.length > 10) _eventMessages.removeAt(0);
});
}

String _formatPosition(Position point) =>
'${point.lng.toStringAsFixed(6)}, ${point.lat.toStringAsFixed(6)}';
'${point.lng.toStringAsFixed(3)}, ${point.lat.toStringAsFixed(3)}';
}
Loading

0 comments on commit 2028462

Please sign in to comment.