From 108560dab793c56e3b09d6e0bb0f52247158aa66 Mon Sep 17 00:00:00 2001 From: Chris Loer Date: Thu, 6 Dec 2018 13:31:42 +0200 Subject: [core, darwin, android] Add onDidEnterIdle to MapObserver. didEnterIdle fires whenever render completes and no repaint is scheduled. --- include/mbgl/map/map_observer.hpp | 1 + .../mapbox/mapboxsdk/maps/MapChangeReceiver.java | 25 ++++++++++++++++ .../java/com/mapbox/mapboxsdk/maps/MapView.java | 35 +++++++++++++++++++++- .../com/mapbox/mapboxsdk/maps/NativeMapView.java | 7 +++++ .../mapboxsdk/maps/MapChangeReceiverTest.java | 33 ++++++++++++++++++++ .../activity/maplayout/MapChangeActivity.java | 1 + platform/android/src/native_map_view.cpp | 9 ++++++ platform/android/src/native_map_view.hpp | 1 + platform/ios/src/MGLMapViewDelegate.h | 13 ++++++++ platform/macos/src/MGLMapView.mm | 14 +++++++++ platform/macos/src/MGLMapViewDelegate.h | 13 ++++++++ .../test/MGLMapViewDelegateIntegrationTests.swift | 2 ++ scripts/changelog_staging/idle-event.json | 6 ++++ src/mbgl/map/map.cpp | 2 ++ 14 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 scripts/changelog_staging/idle-event.json diff --git a/include/mbgl/map/map_observer.hpp b/include/mbgl/map/map_observer.hpp index f63e5f2af3..9c3345d614 100644 --- a/include/mbgl/map/map_observer.hpp +++ b/include/mbgl/map/map_observer.hpp @@ -39,6 +39,7 @@ public: virtual void onDidFinishRenderingMap(RenderMode) {} virtual void onDidFinishLoadingStyle() {} virtual void onSourceChanged(style::Source&) {} + virtual void onDidEnterIdle() {} }; } // namespace mbgl diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapChangeReceiver.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapChangeReceiver.java index 3eaa381239..ac83e37a1f 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapChangeReceiver.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapChangeReceiver.java @@ -27,6 +27,8 @@ class MapChangeReceiver implements NativeMapView.StateCallback { = new CopyOnWriteArrayList<>(); private final List onDidFinishRenderingMapListenerList = new CopyOnWriteArrayList<>(); + private final List onDidEnterIdleListenerList + = new CopyOnWriteArrayList<>(); private final List onDidFinishLoadingStyleListenerList = new CopyOnWriteArrayList<>(); private final List onSourceChangedListenerList = new CopyOnWriteArrayList<>(); @@ -171,6 +173,20 @@ class MapChangeReceiver implements NativeMapView.StateCallback { } } + @Override + public void onDidEnterIdle() { + try { + if (!onDidEnterIdleListenerList.isEmpty()) { + for (MapView.OnDidEnterIdleListener listener : onDidEnterIdleListenerList) { + listener.onDidEnterIdle(); + } + } + } catch (Throwable err) { + Logger.e(TAG, "Exception in onDidEnterIdle", err); + throw err; + } + } + @Override public void onDidFinishLoadingStyle() { try { @@ -279,6 +295,14 @@ class MapChangeReceiver implements NativeMapView.StateCallback { onDidFinishRenderingMapListenerList.remove(listener); } + void addOnDidEnterIdleListener(MapView.OnDidEnterIdleListener listener) { + onDidEnterIdleListenerList.add(listener); + } + + void removeOnDidEnterIdleListener(MapView.OnDidEnterIdleListener listener) { + onDidEnterIdleListenerList.remove(listener); + } + void addOnDidFinishLoadingStyleListener(MapView.OnDidFinishLoadingStyleListener listener) { onDidFinishLoadingStyleListenerList.add(listener); } @@ -306,6 +330,7 @@ class MapChangeReceiver implements NativeMapView.StateCallback { onDidFinishRenderingFrameList.clear(); onWillStartRenderingMapListenerList.clear(); onDidFinishRenderingMapListenerList.clear(); + onDidEnterIdleListenerList.clear(); onDidFinishLoadingStyleListenerList.clear(); onSourceChangedListenerList.clear(); } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java index 0a2ece7378..9ca1c595b1 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java @@ -858,12 +858,32 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { /** * Remove a callback that's invoked when the map has finished rendering. * - * @param listener The callback that's invoked when the map has finished rendering + * @param listener The callback that's invoked when the map has has finished rendering. */ public void removeOnDidFinishRenderingMapListener(OnDidFinishRenderingMapListener listener) { mapChangeReceiver.removeOnDidFinishRenderingMapListener(listener); } + /** + * Set a callback that's invoked when the map has entered the idle state. + * + * @param listener The callback that's invoked when the map has entered the idle state. + */ + public void addOnDidEnterIdleListener(OnDidEnterIdleListener listener) { + mapChangeReceiver.addOnDidEnterIdleListener(listener); + } + + /** + * Remove a callback that's invoked when the map has entered the idle state. + * + * @param listener The callback that's invoked when the map has entered the idle state. + */ + public void removeOnDidEnterIdleListener(OnDidEnterIdleListener listener) { + mapChangeReceiver.removeOnDidEnterIdleListener(listener); + } + + /** + /** * Set a callback that's invoked when the style has finished loading. * @@ -1037,6 +1057,19 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { void onDidFinishRenderingMap(boolean fully); } + /** + * Interface definition for a callback to be invoked when the map has entered the idle state. + *

+ * {@link MapView#addOnDidEnterIdleListener(OnDidEnterIdleListener)} + *

+ */ + public interface OnDidEnterIdleListener { + /** + * Called when the map has entered the idle state. + */ + void onDidEnterIdle(); + } + /** * Interface definition for a callback to be invoked when the map has loaded the style. *

diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java index 48c571ee98..2a1cd29b94 100755 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java @@ -992,6 +992,11 @@ final class NativeMapView { onMapChanged(fully ? MapView.DID_FINISH_RENDERING_MAP_FULLY_RENDERED : MapView.DID_FINISH_RENDERING_MAP); } + @Keep + private void onDidEnterIdle() { + stateCallback.onDidEnterIdle(); + } + @Keep private void onDidFinishLoadingStyle() { stateCallback.onDidFinishLoadingStyle(); @@ -1445,6 +1450,8 @@ final class NativeMapView { void onDidFinishLoadingStyle(); + void onDidEnterIdle(); + void onSourceChanged(String sourceId); } } diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapChangeReceiverTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapChangeReceiverTest.java index 688b4badec..816bbaee1c 100644 --- a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapChangeReceiverTest.java +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapChangeReceiverTest.java @@ -52,6 +52,9 @@ public class MapChangeReceiverTest { @Mock private MapView.OnDidFinishRenderingMapListener onDidFinishRenderingMapListener; + @Mock + private MapView.OnDidEnterIdleListener onDidEnterIdleListener; + @Mock private MapView.OnDidFinishLoadingStyleListener onDidFinishLoadingStyleListener; @@ -306,6 +309,36 @@ public class MapChangeReceiverTest { verify(loggerDefinition).e(anyString(), anyString(), eq(err)); } + @Test + public void testOnDidEnterIdleListener() { + mapChangeEventManager.addOnDidEnterIdleListener(onDidEnterIdleListener); + mapChangeEventManager.onDidEnterIdle(); + verify(onDidEnterIdleListener).onDidEnterIdle(); + mapChangeEventManager.removeOnDidEnterIdleListener(onDidEnterIdleListener); + mapChangeEventManager.onDidEnterIdle(); + verify(onDidEnterIdleListener).onDidEnterIdle(); + + mapChangeEventManager.addOnDidEnterIdleListener(onDidEnterIdleListener); + Logger.setLoggerDefinition(loggerDefinition); + Exception exc = new RuntimeException(); + doThrow(exc).when(onDidEnterIdleListener).onDidEnterIdle(); + try { + mapChangeEventManager.onDidEnterIdle(); + Assert.fail("The exception should've been re-thrown."); + } catch (RuntimeException throwable) { + verify(loggerDefinition).e(anyString(), anyString(), eq(exc)); + } + + Error err = new ExecutionError("", new Error()); + doThrow(err).when(onDidEnterIdleListener).onDidEnterIdle(); + try { + mapChangeEventManager.onDidEnterIdle(); + Assert.fail("The exception should've been re-thrown."); + } catch (ExecutionError throwable) { + verify(loggerDefinition).e(anyString(), anyString(), eq(err)); + } + } + @Test public void testOnDidFinishLoadingStyleListener() { mapChangeEventManager.addOnDidFinishLoadingStyleListener(onDidFinishLoadingStyleListener); diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/maplayout/MapChangeActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/maplayout/MapChangeActivity.java index e3af101a8d..ba669d8260 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/maplayout/MapChangeActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/maplayout/MapChangeActivity.java @@ -33,6 +33,7 @@ public class MapChangeActivity extends AppCompatActivity { mapView.addOnDidFinishLoadingStyleListener(() -> Timber.v("OnDidFinishLoadingStyle")); mapView.addOnDidFinishRenderingFrameListener(fully -> Timber.v("OnDidFinishRenderingFrame: fully: %s", fully)); mapView.addOnDidFinishRenderingMapListener(fully -> Timber.v("OnDidFinishRenderingMap: fully: %s", fully)); + mapView.addOnDidEnterIdleListener(() -> Timber.v("OnDidEnterIdle")); mapView.addOnSourceChangedListener(sourceId -> Timber.v("OnSourceChangedListener: source with id: %s", sourceId)); mapView.addOnWillStartLoadingMapListener(() -> Timber.v("OnWillStartLoadingMap")); mapView.addOnWillStartRenderingFrameListener(() -> Timber.v("OnWillStartRenderingFrame")); diff --git a/platform/android/src/native_map_view.cpp b/platform/android/src/native_map_view.cpp index f12c48f938..4874f5ea34 100755 --- a/platform/android/src/native_map_view.cpp +++ b/platform/android/src/native_map_view.cpp @@ -186,6 +186,15 @@ void NativeMapView::onDidFinishRenderingMap(MapObserver::RenderMode mode) { javaPeer.get(*_env).Call(*_env, onDidFinishRenderingMap, (jboolean) (mode != MapObserver::RenderMode::Partial)); } +void NativeMapView::onDidEnterIdle() { + assert(vm != nullptr); + + android::UniqueEnv _env = android::AttachEnv(); + static auto& javaClass = jni::Class::Singleton(*_env); + static auto onDidEnterIdle = javaClass.GetMethod(*_env, "onDidEnterIdle"); + javaPeer.get(*_env).Call(*_env, onDidEnterIdle); +} + void NativeMapView::onDidFinishLoadingStyle() { assert(vm != nullptr); diff --git a/platform/android/src/native_map_view.hpp b/platform/android/src/native_map_view.hpp index 08f1efa46d..94f8acdada 100755 --- a/platform/android/src/native_map_view.hpp +++ b/platform/android/src/native_map_view.hpp @@ -67,6 +67,7 @@ public: void onDidFinishRenderingFrame(MapObserver::RenderMode) override; void onWillStartRenderingMap() override; void onDidFinishRenderingMap(MapObserver::RenderMode) override; + void onDidEnterIdle() override; void onDidFinishLoadingStyle() override; void onSourceChanged(mbgl::style::Source&) override; diff --git a/platform/ios/src/MGLMapViewDelegate.h b/platform/ios/src/MGLMapViewDelegate.h index 77dd2e4ef4..60f3cc7370 100644 --- a/platform/ios/src/MGLMapViewDelegate.h +++ b/platform/ios/src/MGLMapViewDelegate.h @@ -229,6 +229,19 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)mapViewDidFinishRenderingFrame:(MGLMapView *)mapView fullyRendered:(BOOL)fullyRendered; +/** + Tells the delegate that the map view is entering an idle state, and no more + drawing will be necessary until new data is loaded or there is some interaction + with the map. + + - No camera transitions are in progress + - All currently requested tiles have loaded + - All fade/transition animations have completed + + @param mapView The map view that has just entered the idle state. + */ +- (void)mapViewDidEnterIdle:(MGLMapView *)mapView; + /** Tells the delegate that the map has just finished loading a style. diff --git a/platform/macos/src/MGLMapView.mm b/platform/macos/src/MGLMapView.mm index 9f9bb855d2..67bf94ca11 100644 --- a/platform/macos/src/MGLMapView.mm +++ b/platform/macos/src/MGLMapView.mm @@ -928,6 +928,16 @@ public: } } +- (void)mapViewDidEnterIdle { + if (!_mbglMap) { + return; + } + + if ([self.delegate respondsToSelector:@selector(mapViewDidEnterIdle)]) { + [self.delegate mapViewDidEnterIdle:self]; + } +} + - (void)mapViewDidFinishLoadingStyle { if (!_mbglMap) { return; @@ -3012,6 +3022,10 @@ public: bool fullyRendered = mode == mbgl::MapObserver::RenderMode::Full; [nativeView mapViewDidFinishRenderingMapFullyRendered:fullyRendered]; } + + void onDidEnterIdle() override { + [nativeView mapViewDidEnterIdle]; + } void onDidFinishLoadingStyle() override { [nativeView mapViewDidFinishLoadingStyle]; diff --git a/platform/macos/src/MGLMapViewDelegate.h b/platform/macos/src/MGLMapViewDelegate.h index 2a8b28c1b4..ad59f5bd39 100644 --- a/platform/macos/src/MGLMapViewDelegate.h +++ b/platform/macos/src/MGLMapViewDelegate.h @@ -151,6 +151,19 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)mapViewDidFinishRenderingFrame:(MGLMapView *)mapView fullyRendered:(BOOL)fullyRendered; +/** + Tells the delegate that the map view is entering an idle state, and no more + drawing will be necessary until new data is loaded or there is some interaction + with the map. + + - No camera transitions are in progress + - All currently requested tiles have loaded + - All fade/transition animations have completed + + @param mapView The map view that has just entered the idle state. + */ +- (void)mapViewDidEnterIdle:(MGLMapView *)mapView; + /** Tells the delegate that the map has just finished loading a style. diff --git a/platform/macos/test/MGLMapViewDelegateIntegrationTests.swift b/platform/macos/test/MGLMapViewDelegateIntegrationTests.swift index 00635d97eb..109c279a09 100644 --- a/platform/macos/test/MGLMapViewDelegateIntegrationTests.swift +++ b/platform/macos/test/MGLMapViewDelegateIntegrationTests.swift @@ -24,6 +24,8 @@ extension MGLMapViewDelegateIntegrationTests: MGLMapViewDelegate { func mapViewDidFinishRenderingFrame(_ mapView: MGLMapView, fullyRendered: Bool) {} func mapViewDidFinishRenderingMap(_ mapView: MGLMapView, fullyRendered: Bool) {} + + func mapViewDidEnterIdle(_ mapView: MGLMapView) {} func mapViewDidFailLoadingMap(_ mapView: MGLMapView, withError error: Error) {} diff --git a/scripts/changelog_staging/idle-event.json b/scripts/changelog_staging/idle-event.json new file mode 100644 index 0000000000..96db06eb99 --- /dev/null +++ b/scripts/changelog_staging/idle-event.json @@ -0,0 +1,6 @@ +{ + "core": "Add onDidEnterIdle to MapObserver, which fires whenever render completes and no repaint is scheduled.", + "darwin": "Add mapViewDidEnterIdle to MGLMapViewDelegate, which fires whenever render completes and no repaint is scheduled.", + "android": "Add onDidEnterIdle listener to MapView, which fires whenever render completes and no repaint is scheduled.", + "issue": 13469 +} diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp index 1f64cf3acc..7d7d94a0d2 100644 --- a/src/mbgl/map/map.cpp +++ b/src/mbgl/map/map.cpp @@ -208,6 +208,8 @@ void Map::Impl::onDidFinishRenderingFrame(RenderMode renderMode, bool needsRepai if (needsRepaint || transform.inTransition()) { onUpdate(); + } else if (rendererFullyLoaded) { + observer.onDidEnterIdle(); } } else if (stillImageRequest && rendererFullyLoaded) { auto request = std::move(stillImageRequest); -- cgit v1.2.1