Ensure that rapid zoom/location changes are both honored.

This commit is contained in:
Russell Keith-Magee 2024-03-22 10:52:16 +08:00
parent 89f18a074b
commit a136ec2549
No known key found for this signature in database
GPG key ID: 3D2DAB6A37BB5BC3
2 changed files with 74 additions and 16 deletions

View file

@ -15,6 +15,10 @@ from ..libs import (
from .base import Widget
def approx(a, b):
return abs(a - b) < 0.00001
class TogaMapView(MKMapView):
interface = objc_property(object, weak=True)
impl = objc_property(object, weak=True)
@ -26,8 +30,20 @@ class TogaMapView(MKMapView):
@objc_method
def mapView_regionDidChangeAnimated_(self, mapView, animated: bool) -> None:
# Once an animation finishes, we know the future location has been reached.
self.impl.future_location = None
# Once an animation finishes, compare the currently viewable region with any
# future values
region = self.impl.native.region
if (
self.impl.future_location
and approx(region.center.latitude, self.impl.future_location.latitude)
and approx(region.center.longitude, self.impl.future_location.longitude)
):
self.impl.future_location = None
if self.impl.future_delta and approx(
region.span.longitudeDelta, self.impl.future_delta
):
self.impl.future_delta = None
class MapView(Widget):
@ -43,8 +59,11 @@ class MapView(Widget):
# finished animating to the new location yet. Whenever we change location, store
# the desired future location, so that any zoom changes that occur during
# animation can use the desired *future* location as the center of the zoom
# window, rather than wherever the window is mid-pan.
# window, rather than wherever the window is mid-pan. Similarly, store the
# future span delta, so that the desired span can be applied rather than the
# current span.
self.future_location = None
self.future_delta = None
# Setting the zoom level also requires knowing the size of the rendered area.
# We won't know this until layout happens at least once, so retain a backlog
@ -88,10 +107,17 @@ class MapView(Widget):
def set_location(self, position):
if self.backlog is None:
self.future_location = CLLocationCoordinate2D(*position)
self.native.setCenterCoordinate(
self.future_location,
animated=True,
delta = (
self.future_delta
if self.future_delta
else self.native.region.span.longitudeDelta
)
region = MKCoordinateRegion(
self.future_location,
MKCoordinateSpan(0, delta),
)
self.native.setRegion(region, animated=True)
else:
self.backlog["location"] = position
@ -112,7 +138,10 @@ class MapView(Widget):
# The horizontal axis can't show more than 360 degrees of longitude, so clip
# the range to that value. The OSM zoom level is based on 360 degrees of
# longitude being split into 2**zoom sections.
delta = min(360.0, (360 * self.interface.layout.width) / (256 * 2**zoom))
self.future_delta = min(
360.0,
(360 * self.interface.layout.width) / (256 * 2**zoom),
)
# If we're currently panning to a new location, use the desired *future*
# location as the center of the zoom region. Otherwise use the current center
@ -124,7 +153,7 @@ class MapView(Widget):
)
region = MKCoordinateRegion(
center,
MKCoordinateSpan(0, delta),
MKCoordinateSpan(0, self.future_delta),
)
self.native.setRegion(region, animated=True)
else:

View file

@ -15,6 +15,10 @@ from ..libs import (
from .base import Widget
def approx(a, b):
return abs(a - b) < 0.00001
class TogaMapView(MKMapView):
interface = objc_property(object, weak=True)
impl = objc_property(object, weak=True)
@ -26,8 +30,20 @@ class TogaMapView(MKMapView):
@objc_method
def mapView_regionDidChangeAnimated_(self, mapView, animated: bool) -> None:
# Once an animation finishes, we know the future location has been reached.
self.impl.future_location = None
# Once an animation finishes, compare the currently viewable region with any
# future values
region = self.impl.native.region
if (
self.impl.future_location
and approx(region.center.latitude, self.impl.future_location.latitude)
and approx(region.center.longitude, self.impl.future_location.longitude)
):
self.impl.future_location = None
if self.impl.future_delta and approx(
region.span.longitudeDelta, self.impl.future_delta
):
self.impl.future_delta = None
class MapView(Widget):
@ -43,8 +59,11 @@ class MapView(Widget):
# finished animating to the new location yet. Whenever we change location, store
# the desired future location, so that any zoom changes that occur during
# animation can use the desired *future* location as the center of the zoom
# window, rather than wherever the window is mid-pan.
# window, rather than wherever the window is mid-pan. Similarly, store the
# future span delta, so that the desired span can be applied rather than the
# current span.
self.future_location = None
self.future_delta = None
# Setting the zoom level also requires knowing the size of the rendered area.
# We won't know this until layout happens at least once, so retain a backlog
@ -88,10 +107,17 @@ class MapView(Widget):
def set_location(self, position):
if self.backlog is None:
self.future_location = CLLocationCoordinate2D(*position)
self.native.setCenterCoordinate(
self.future_location,
animated=True,
delta = (
self.future_delta
if self.future_delta
else self.native.region.span.longitudeDelta
)
region = MKCoordinateRegion(
self.future_location,
MKCoordinateSpan(0, delta),
)
self.native.setRegion(region, animated=True)
else:
self.backlog["location"] = position
@ -112,7 +138,10 @@ class MapView(Widget):
# The horizontal axis can't show more than 360 degrees of longitude, so clip
# the range to that value. The OSM zoom level is based on 360 degrees of
# longitude being split into 2**zoom sections.
delta = min(360.0, (360 * self.interface.layout.width) / (256 * 2**zoom))
self.future_delta = min(
360.0,
(360 * self.interface.layout.width) / (256 * 2**zoom),
)
# If we're currently panning to a new location, use the desired *future*
# location as the center of the zoom region. Otherwise use the current center
@ -124,7 +153,7 @@ class MapView(Widget):
)
region = MKCoordinateRegion(
center,
MKCoordinateSpan(0, delta),
MKCoordinateSpan(0, self.future_delta),
)
self.native.setRegion(region, animated=True)
else: